commit dd3484bb44163c2ad5324b3dab7020a97088ae9e Author: archive Date: Thu Sep 27 00:00:00 2007 +0000 as released 2007-09-27 diff --git a/Readme Painkeep Arena Source Info.txt b/Readme Painkeep Arena Source Info.txt new file mode 100644 index 0000000..6b351b1 --- /dev/null +++ b/Readme Painkeep Arena Source Info.txt @@ -0,0 +1,107 @@ +--------------------------------------------------------------------------- +--------------------------------------------------------------------------- +PainKeep Arena 3.0n Source Release +Release Date: September 27,2007 + +Materials in this release are based on the +PainKeep Arena 3.0n Full Release - March 19, 2004 +Copyright (c)2007-2008 Team Evolve. +All rights reserved. +--------------------------------------------------------------------------- +--------------------------------------------------------------------------- + +Team Evolve: +Team Evolve Web Site - http://www.teamevolve.com + +Community Information: +http://www.groundplan.com/gplan_forum + +--------------------------------------------------------------------------- + +Team Evolve welcomes you to the source files of PainKeep Arena! We release this information in the hopes that it will contribute to the gaming community with a jumpstart into the next generation of online games. Should you use any information within the source files toward your game then please give credit to Team Evolve in the form of a textual reference in the public readme file. + +Team Evolve releases the source in an AS-IS form and will not support nor answer any questions into its workings. Team Evolve will not support nor be responsible in any way to extensions made to Painkeep Arena from third party modifications to source or content. + +--------------------------------------------------------------------------- + +PainKeep Arena is based on the 1.32 version of Quake III Arena. IMPORTANT: In order to use any modification to the PainKeep Arena source code will require: 1) Quake III Arena 1.32 installed and 2) the Full Install of PainKeep Arena 3.0n. + +This help document is based on Windows XP SP2. If you use another OS then you will need to adjust search PATH information accordingly. + +The PainKeep Arena source code is based on the GAME Source released by id Software. PainKeep Arena source must be installed into the same place as the id software GAME Source, C:\QUAKE3, in order for the lcc and q3cpp compilation paths to be correct. + +You must place the compilation executables in your system’s path. The lcc executable is located in c:\quake3\bin_nt directory. Add to your systems path using the Control Panel’s System tool, select the Advanced Tab and then click on the “Environment Variables” Button. Under the “Systems Variables”, select “Path” and Click the “Edit” button to add to the end of the existing path line - “;c:\quake3\bin_nt” - {without the quotes, of course}. Click OK a few time to save the new Path setting. You may need to reboot Windows for new Path setting to be recognized by the system. + +The PainKeep Arena source code contains the procedures and logic that govern the User Interface, Game Logic, Client Presentation. Each section is primarily controlled by a specific directory within the source code… +User Interface - c:\quake3\source\code\ui\ +Game Logic - c:\quake3\source\code\game\ +Client Presentation - c:\quake3\source\code\cgame\ + +Please note that some modifications to the User Interface will require changes in Client Presentation area. + +PainKeep Arena uses QVM files to hold the compiled game code. Each section (User Interface, Game Logic, Client Presentation) has an associated and separate QVM file. To create, (or compile), a new QVM, each section have a .BAT file that can be used to generate a new QVM. + +To compile a new… +User Interface: + execute c:\quake3\source\code\ui\ui.bat +Game Logic: + execute c:\quake3\source\code\game\game.bat +Client Presentation: + execute c:\quake3\source\code\cgame\cgame.bat + +The GAME and CLIENT QVM files will be generated in directory: + c:\quake3\baseq3\vm\ + gagame.qvm + cgame.qvm + +The USER INTERFACE QVM file will be generated in directory: + c:\quake3\missionpack\vm\ + ui.qvm + +After you make your QVM files you will need to make a .PK3 file that holds your newly generated QVMs and any other Modification materials. Please note that .PK3 files can be managed by ZIP tools. + +Create a .PK3 file with the new QVM files in a \vm subdirectory. + +Place the newly generated .PK3 file into your mod (or \PKARENA) directory under the Quake III Arena Execution directory. Reminder, Quake III Arena will use the .PK3 File’s material that comes last alphabetically. For example if you make a simple MOD for PainKeep Arena, place your materials in… + ..\Quake III Arena\pkarena\zpktext.pk3 + (in this example zpktest.pk3 will be loaded last and your updates will act as the effective .QVM files) + +Good Luck! +Yours, Team Evolve and Ergodic + +--------------------------------------------------------------------------- +--------------------------------------------------------------------------- + +THE LEGAL CRAP +------------------- + + +A. Copyright Notice +This production in its entirety and all derivative works are copyright (c)2007-2008 Team Evolve. All rights reserved. + + + + +Ownership of all new components, including, but not limited to; source code, compiled code, graphics, textures, sounds, models and maps, remain with Team Evolve and the individual authors respectively. Some components are the exclusive property of their authors and owners and are used with kind permission. +All original components are copyright (c)2001, iD Software. +Quake III Arena and the stylized 'Q' are trademarks of iD Software. + + + +All other trademarks are property of their respective owners and are hereby acknowledged. + + +B. Distribution and Usage Permissions +Team Evolve grants to the final end user an exclusive right to use this production for the purposes of personal entertainment only. Team Evolve grants to the final end user an exclusive right to freely distribute this production in its undisturbed and unaltered entirety provided no exchange, monetary or otherwise, is requested. All other media entities are expressly excluded from this right prior to acknowledge and consent from Team Evolve or one of Team Evolve's duly appointed representatives, agents or subsidiaries. + +By using this product you agree to exempt, without reservation, the authors and +owners of this production or components thereof from any responsibility for liability, damage caused, or loss, directly or indirectly, by this software, including but not limited to, any interruptions of service, loss of business, or any other consequential damages resulting from the use of or operation of this product or components thereof. + +No warranties are made, expressed or implied, regarding the usage, functionality, or implied operability of this product. All elements are available solely on an +"as-is" basis. Usage is subject to the user's own risk. + +New or altered source code components are included with kind permission of the respective authors and owners and are provided with the only intention of facilitating in the integration of this production, or components thereof, with other such freely available and non-commercial productions. Authors are expressly forbidden to use these components, or any other component of this production, as a basis for other commercially available works or demonstration systems without prior acknowledge and consent from Team Evolve or one of Team Evolve's duly appointed representatives, agents or subsidiaries. +------------------------------------------------------------------------------------- + +PainKeep Arena is RATED M: Mature Audiences Only + diff --git a/quake3/Readme PainKeep Arena 3_1.txt b/quake3/Readme PainKeep Arena 3_1.txt new file mode 100644 index 0000000..81a7715 --- /dev/null +++ b/quake3/Readme PainKeep Arena 3_1.txt @@ -0,0 +1,62 @@ +--------------------------------------------------------------------------- +--------------------------------------------------------------------------- +PainKeep Arena 3.0 Full Release is RATED M: Mature Audiences Only +--------------------------------------------------------------------------- + + +PainKeep Arena 3.0 Full Release - March 19, 2004 +Copyright (c)2004-2005 Team Evolve. +All rights reserved. + + +Team Evolve: +Team Evolve Web Site - http://www.team-evolve.com + +Find help & Post Bug Comments at: +http://www.groundplan.com/gplan_forum +--------------------------------------------------------------------------- + +For information regarding PainKeep Arena please view the PKA Manual by opening the +file index.html in the /pkamanual directory. A typical installation will locate the +help manual in the following location: + +/quake III arena/pkarena/pkamanual/index.html + + + + + +VIII. THE LEGAL CRAP +------------------- + + +A. Copyright Notice +This production in its entirety and all derivative works are copyright (c)2004-2005 Team Evolve. All rights reserved. + + + + +Ownership of all new components, including, but not limited to; source code, compiled code, graphics, textures, sounds, models and maps, remain with Team Evolve and the individual authors respectively. Some components are the exclusive property of their authors and owners and are used with kind permission. +All original components are copyright (c)2001, iD Software. +Quake III Arena and the stylized 'Q' are trademarks of iD Software. + + + +All other trademarks are property of their respective owners and are hereby acknowledged. + + +B. Distribution and Usage Permissions +Team Evolve grants to the final end user an exclusive right to use this production for the purposes of personal entertainment only. Team Evolve grants to the final end user an exclusive right to freely distribute this production in its undisturbed and unaltered entirety provided no exchange, monetary or otherwise, is requested. All other media entities are expressly excluded from this right prior to acknowledge and consent from Team Evolve or one of Team Evolve's duly appointed representatives, agents or subsidiaries. + +By using this product you agree to exempt, without reservation, the authors and +owners of this production or components thereof from any responsibility for liability, damage caused, or loss, directly or indirectly, by this software, including but not limited to, any interruptions of service, loss of business, or any other consequential damages resulting from the use of or operation of this product or components thereof. + +No warranties are made, expressed or implied, regarding the usage, functionality, or implied operability of this product. All elements are available solely on an +"as-is" basis. Usage is subject to the user's own risk. + +New or altered source code components are included with kind permission of the respective authors and owners and are provided with the only intention of facilitating in the integration of this production, or components thereof, with other such freely available and non-commercial productions. Authors are expressly forbidden to use these components, or any other component of this production, as a basis for other commercially available works or demonstration systems without prior acknowledge and consent from Team Evolve or one of Team Evolve's duly appointed representatives, agents or subsidiaries. +------------------------------------------------------------------------------------- + +PainKeep Arena is RATED M: Mature Audiences Only + +Now Bring on the killing! diff --git a/quake3/Readme Painkeep Arena Source Info.txt b/quake3/Readme Painkeep Arena Source Info.txt new file mode 100644 index 0000000..6b351b1 --- /dev/null +++ b/quake3/Readme Painkeep Arena Source Info.txt @@ -0,0 +1,107 @@ +--------------------------------------------------------------------------- +--------------------------------------------------------------------------- +PainKeep Arena 3.0n Source Release +Release Date: September 27,2007 + +Materials in this release are based on the +PainKeep Arena 3.0n Full Release - March 19, 2004 +Copyright (c)2007-2008 Team Evolve. +All rights reserved. +--------------------------------------------------------------------------- +--------------------------------------------------------------------------- + +Team Evolve: +Team Evolve Web Site - http://www.teamevolve.com + +Community Information: +http://www.groundplan.com/gplan_forum + +--------------------------------------------------------------------------- + +Team Evolve welcomes you to the source files of PainKeep Arena! We release this information in the hopes that it will contribute to the gaming community with a jumpstart into the next generation of online games. Should you use any information within the source files toward your game then please give credit to Team Evolve in the form of a textual reference in the public readme file. + +Team Evolve releases the source in an AS-IS form and will not support nor answer any questions into its workings. Team Evolve will not support nor be responsible in any way to extensions made to Painkeep Arena from third party modifications to source or content. + +--------------------------------------------------------------------------- + +PainKeep Arena is based on the 1.32 version of Quake III Arena. IMPORTANT: In order to use any modification to the PainKeep Arena source code will require: 1) Quake III Arena 1.32 installed and 2) the Full Install of PainKeep Arena 3.0n. + +This help document is based on Windows XP SP2. If you use another OS then you will need to adjust search PATH information accordingly. + +The PainKeep Arena source code is based on the GAME Source released by id Software. PainKeep Arena source must be installed into the same place as the id software GAME Source, C:\QUAKE3, in order for the lcc and q3cpp compilation paths to be correct. + +You must place the compilation executables in your system’s path. The lcc executable is located in c:\quake3\bin_nt directory. Add to your systems path using the Control Panel’s System tool, select the Advanced Tab and then click on the “Environment Variables” Button. Under the “Systems Variables”, select “Path” and Click the “Edit” button to add to the end of the existing path line - “;c:\quake3\bin_nt” - {without the quotes, of course}. Click OK a few time to save the new Path setting. You may need to reboot Windows for new Path setting to be recognized by the system. + +The PainKeep Arena source code contains the procedures and logic that govern the User Interface, Game Logic, Client Presentation. Each section is primarily controlled by a specific directory within the source code… +User Interface - c:\quake3\source\code\ui\ +Game Logic - c:\quake3\source\code\game\ +Client Presentation - c:\quake3\source\code\cgame\ + +Please note that some modifications to the User Interface will require changes in Client Presentation area. + +PainKeep Arena uses QVM files to hold the compiled game code. Each section (User Interface, Game Logic, Client Presentation) has an associated and separate QVM file. To create, (or compile), a new QVM, each section have a .BAT file that can be used to generate a new QVM. + +To compile a new… +User Interface: + execute c:\quake3\source\code\ui\ui.bat +Game Logic: + execute c:\quake3\source\code\game\game.bat +Client Presentation: + execute c:\quake3\source\code\cgame\cgame.bat + +The GAME and CLIENT QVM files will be generated in directory: + c:\quake3\baseq3\vm\ + gagame.qvm + cgame.qvm + +The USER INTERFACE QVM file will be generated in directory: + c:\quake3\missionpack\vm\ + ui.qvm + +After you make your QVM files you will need to make a .PK3 file that holds your newly generated QVMs and any other Modification materials. Please note that .PK3 files can be managed by ZIP tools. + +Create a .PK3 file with the new QVM files in a \vm subdirectory. + +Place the newly generated .PK3 file into your mod (or \PKARENA) directory under the Quake III Arena Execution directory. Reminder, Quake III Arena will use the .PK3 File’s material that comes last alphabetically. For example if you make a simple MOD for PainKeep Arena, place your materials in… + ..\Quake III Arena\pkarena\zpktext.pk3 + (in this example zpktest.pk3 will be loaded last and your updates will act as the effective .QVM files) + +Good Luck! +Yours, Team Evolve and Ergodic + +--------------------------------------------------------------------------- +--------------------------------------------------------------------------- + +THE LEGAL CRAP +------------------- + + +A. Copyright Notice +This production in its entirety and all derivative works are copyright (c)2007-2008 Team Evolve. All rights reserved. + + + + +Ownership of all new components, including, but not limited to; source code, compiled code, graphics, textures, sounds, models and maps, remain with Team Evolve and the individual authors respectively. Some components are the exclusive property of their authors and owners and are used with kind permission. +All original components are copyright (c)2001, iD Software. +Quake III Arena and the stylized 'Q' are trademarks of iD Software. + + + +All other trademarks are property of their respective owners and are hereby acknowledged. + + +B. Distribution and Usage Permissions +Team Evolve grants to the final end user an exclusive right to use this production for the purposes of personal entertainment only. Team Evolve grants to the final end user an exclusive right to freely distribute this production in its undisturbed and unaltered entirety provided no exchange, monetary or otherwise, is requested. All other media entities are expressly excluded from this right prior to acknowledge and consent from Team Evolve or one of Team Evolve's duly appointed representatives, agents or subsidiaries. + +By using this product you agree to exempt, without reservation, the authors and +owners of this production or components thereof from any responsibility for liability, damage caused, or loss, directly or indirectly, by this software, including but not limited to, any interruptions of service, loss of business, or any other consequential damages resulting from the use of or operation of this product or components thereof. + +No warranties are made, expressed or implied, regarding the usage, functionality, or implied operability of this product. All elements are available solely on an +"as-is" basis. Usage is subject to the user's own risk. + +New or altered source code components are included with kind permission of the respective authors and owners and are provided with the only intention of facilitating in the integration of this production, or components thereof, with other such freely available and non-commercial productions. Authors are expressly forbidden to use these components, or any other component of this production, as a basis for other commercially available works or demonstration systems without prior acknowledge and consent from Team Evolve or one of Team Evolve's duly appointed representatives, agents or subsidiaries. +------------------------------------------------------------------------------------- + +PainKeep Arena is RATED M: Mature Audiences Only + diff --git a/quake3/bin_nt/lcc.exe b/quake3/bin_nt/lcc.exe new file mode 100644 index 0000000..ff0b286 Binary files /dev/null and b/quake3/bin_nt/lcc.exe differ diff --git a/quake3/bin_nt/q3asm.exe b/quake3/bin_nt/q3asm.exe new file mode 100644 index 0000000..ba9fd3d Binary files /dev/null and b/quake3/bin_nt/q3asm.exe differ diff --git a/quake3/bin_nt/q3cpp.exe b/quake3/bin_nt/q3cpp.exe new file mode 100644 index 0000000..55e68a1 Binary files /dev/null and b/quake3/bin_nt/q3cpp.exe differ diff --git a/quake3/bin_nt/q3rcc.exe b/quake3/bin_nt/q3rcc.exe new file mode 100644 index 0000000..8f09cab Binary files /dev/null and b/quake3/bin_nt/q3rcc.exe differ diff --git a/quake3/source/QIIIA Game Source License.doc b/quake3/source/QIIIA Game Source License.doc new file mode 100644 index 0000000..790e7fe Binary files /dev/null and b/quake3/source/QIIIA Game Source License.doc differ diff --git a/quake3/source/code/Debug_TA/qagamex86.dll b/quake3/source/code/Debug_TA/qagamex86.dll new file mode 100644 index 0000000..982b012 Binary files /dev/null and b/quake3/source/code/Debug_TA/qagamex86.dll differ diff --git a/quake3/source/code/cgame/Debug_TA/bg_lib.obj b/quake3/source/code/cgame/Debug_TA/bg_lib.obj new file mode 100644 index 0000000..5ed2fb1 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/bg_lib.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/bg_lib.sbr b/quake3/source/code/cgame/Debug_TA/bg_lib.sbr new file mode 100644 index 0000000..8d4b68d Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/bg_lib.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/bg_misc.obj b/quake3/source/code/cgame/Debug_TA/bg_misc.obj new file mode 100644 index 0000000..9c2b18e Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/bg_misc.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/bg_misc.sbr b/quake3/source/code/cgame/Debug_TA/bg_misc.sbr new file mode 100644 index 0000000..b458e7c Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/bg_misc.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/bg_pmove.obj b/quake3/source/code/cgame/Debug_TA/bg_pmove.obj new file mode 100644 index 0000000..3d0a168 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/bg_pmove.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/bg_pmove.sbr b/quake3/source/code/cgame/Debug_TA/bg_pmove.sbr new file mode 100644 index 0000000..69f14bf Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/bg_pmove.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/bg_slidemove.obj b/quake3/source/code/cgame/Debug_TA/bg_slidemove.obj new file mode 100644 index 0000000..1bdb8dd Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/bg_slidemove.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/bg_slidemove.sbr b/quake3/source/code/cgame/Debug_TA/bg_slidemove.sbr new file mode 100644 index 0000000..0f014ba Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/bg_slidemove.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_consolecmds.obj b/quake3/source/code/cgame/Debug_TA/cg_consolecmds.obj new file mode 100644 index 0000000..bb44f81 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_consolecmds.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_consolecmds.sbr b/quake3/source/code/cgame/Debug_TA/cg_consolecmds.sbr new file mode 100644 index 0000000..18aea45 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_consolecmds.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_draw.obj b/quake3/source/code/cgame/Debug_TA/cg_draw.obj new file mode 100644 index 0000000..07e549b Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_draw.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_draw.sbr b/quake3/source/code/cgame/Debug_TA/cg_draw.sbr new file mode 100644 index 0000000..e02b967 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_draw.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_drawtools.obj b/quake3/source/code/cgame/Debug_TA/cg_drawtools.obj new file mode 100644 index 0000000..a903c93 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_drawtools.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_drawtools.sbr b/quake3/source/code/cgame/Debug_TA/cg_drawtools.sbr new file mode 100644 index 0000000..5dc1c70 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_drawtools.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_effects.obj b/quake3/source/code/cgame/Debug_TA/cg_effects.obj new file mode 100644 index 0000000..b8846cf Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_effects.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_effects.sbr b/quake3/source/code/cgame/Debug_TA/cg_effects.sbr new file mode 100644 index 0000000..8ed3403 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_effects.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_ents.obj b/quake3/source/code/cgame/Debug_TA/cg_ents.obj new file mode 100644 index 0000000..68c5a13 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_ents.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_event.obj b/quake3/source/code/cgame/Debug_TA/cg_event.obj new file mode 100644 index 0000000..79d1228 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_event.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_event.sbr b/quake3/source/code/cgame/Debug_TA/cg_event.sbr new file mode 100644 index 0000000..04af784 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_event.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_info.obj b/quake3/source/code/cgame/Debug_TA/cg_info.obj new file mode 100644 index 0000000..27b30d6 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_info.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_info.sbr b/quake3/source/code/cgame/Debug_TA/cg_info.sbr new file mode 100644 index 0000000..84544c3 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_info.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_localents.obj b/quake3/source/code/cgame/Debug_TA/cg_localents.obj new file mode 100644 index 0000000..7dc7553 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_localents.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_localents.sbr b/quake3/source/code/cgame/Debug_TA/cg_localents.sbr new file mode 100644 index 0000000..51ef14f Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_localents.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_main.obj b/quake3/source/code/cgame/Debug_TA/cg_main.obj new file mode 100644 index 0000000..6a85afb Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_main.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_main.sbr b/quake3/source/code/cgame/Debug_TA/cg_main.sbr new file mode 100644 index 0000000..67ff371 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_main.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_marks.obj b/quake3/source/code/cgame/Debug_TA/cg_marks.obj new file mode 100644 index 0000000..d20536d Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_marks.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_marks.sbr b/quake3/source/code/cgame/Debug_TA/cg_marks.sbr new file mode 100644 index 0000000..270f3a9 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_marks.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_newDraw.obj b/quake3/source/code/cgame/Debug_TA/cg_newDraw.obj new file mode 100644 index 0000000..891aa62 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_newDraw.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_newDraw.sbr b/quake3/source/code/cgame/Debug_TA/cg_newDraw.sbr new file mode 100644 index 0000000..1832435 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_newDraw.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_players.obj b/quake3/source/code/cgame/Debug_TA/cg_players.obj new file mode 100644 index 0000000..097caf0 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_players.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_players.sbr b/quake3/source/code/cgame/Debug_TA/cg_players.sbr new file mode 100644 index 0000000..a951c1a Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_players.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_playerstate.obj b/quake3/source/code/cgame/Debug_TA/cg_playerstate.obj new file mode 100644 index 0000000..c349fd2 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_playerstate.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_playerstate.sbr b/quake3/source/code/cgame/Debug_TA/cg_playerstate.sbr new file mode 100644 index 0000000..fd2ba4a Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_playerstate.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_predict.obj b/quake3/source/code/cgame/Debug_TA/cg_predict.obj new file mode 100644 index 0000000..f04d88e Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_predict.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_predict.sbr b/quake3/source/code/cgame/Debug_TA/cg_predict.sbr new file mode 100644 index 0000000..b721dc8 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_predict.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_scoreboard.obj b/quake3/source/code/cgame/Debug_TA/cg_scoreboard.obj new file mode 100644 index 0000000..4d307ec Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_scoreboard.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_scoreboard.sbr b/quake3/source/code/cgame/Debug_TA/cg_scoreboard.sbr new file mode 100644 index 0000000..b5d0678 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_scoreboard.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_servercmds.obj b/quake3/source/code/cgame/Debug_TA/cg_servercmds.obj new file mode 100644 index 0000000..dfad813 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_servercmds.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_servercmds.sbr b/quake3/source/code/cgame/Debug_TA/cg_servercmds.sbr new file mode 100644 index 0000000..3ba0f8a Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_servercmds.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_snapshot.obj b/quake3/source/code/cgame/Debug_TA/cg_snapshot.obj new file mode 100644 index 0000000..ab41cff Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_snapshot.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_snapshot.sbr b/quake3/source/code/cgame/Debug_TA/cg_snapshot.sbr new file mode 100644 index 0000000..442eeb8 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_snapshot.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_syscalls.obj b/quake3/source/code/cgame/Debug_TA/cg_syscalls.obj new file mode 100644 index 0000000..b7394a6 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_syscalls.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_syscalls.sbr b/quake3/source/code/cgame/Debug_TA/cg_syscalls.sbr new file mode 100644 index 0000000..694a8bc Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_syscalls.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_view.obj b/quake3/source/code/cgame/Debug_TA/cg_view.obj new file mode 100644 index 0000000..cb8cd77 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_view.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_view.sbr b/quake3/source/code/cgame/Debug_TA/cg_view.sbr new file mode 100644 index 0000000..1d0e83b Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_view.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_weapons.obj b/quake3/source/code/cgame/Debug_TA/cg_weapons.obj new file mode 100644 index 0000000..d613fad Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_weapons.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/cg_weapons.sbr b/quake3/source/code/cgame/Debug_TA/cg_weapons.sbr new file mode 100644 index 0000000..d6751a6 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cg_weapons.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/cgame.pch b/quake3/source/code/cgame/Debug_TA/cgame.pch new file mode 100644 index 0000000..77bfc24 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/cgame.pch differ diff --git a/quake3/source/code/cgame/Debug_TA/q_math.obj b/quake3/source/code/cgame/Debug_TA/q_math.obj new file mode 100644 index 0000000..c6cf341 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/q_math.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/q_math.sbr b/quake3/source/code/cgame/Debug_TA/q_math.sbr new file mode 100644 index 0000000..6591a83 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/q_math.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/q_shared.obj b/quake3/source/code/cgame/Debug_TA/q_shared.obj new file mode 100644 index 0000000..df96127 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/q_shared.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/q_shared.sbr b/quake3/source/code/cgame/Debug_TA/q_shared.sbr new file mode 100644 index 0000000..e8bbd7d Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/q_shared.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/ui_shared.obj b/quake3/source/code/cgame/Debug_TA/ui_shared.obj new file mode 100644 index 0000000..6053384 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/ui_shared.obj differ diff --git a/quake3/source/code/cgame/Debug_TA/ui_shared.sbr b/quake3/source/code/cgame/Debug_TA/ui_shared.sbr new file mode 100644 index 0000000..f47cad3 Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/ui_shared.sbr differ diff --git a/quake3/source/code/cgame/Debug_TA/vc50.idb b/quake3/source/code/cgame/Debug_TA/vc50.idb new file mode 100644 index 0000000..e022f5c Binary files /dev/null and b/quake3/source/code/cgame/Debug_TA/vc50.idb differ diff --git a/quake3/source/code/cgame/cg_consolecmds.c b/quake3/source/code/cgame/cg_consolecmds.c new file mode 100644 index 0000000..39c886c --- /dev/null +++ b/quake3/source/code/cgame/cg_consolecmds.c @@ -0,0 +1,611 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_consolecmds.c -- text commands typed in at the local console, or +// executed by a key binding + +#include "cg_local.h" +#include "../ui/ui_shared.h" +#ifdef MISSIONPACK +extern menuDef_t *menuScoreboard; +#endif + + + +//PKMOD - Ergodic 12/09/03 - add command dynamic hub voting +static void CG_HubAlternates_f (void ) { + trap_SendConsoleCommand( "hubalternates\n" ); +} + +void CG_TargetCommand_f( void ) { + int targetNum; + char test[4]; + + targetNum = CG_CrosshairPlayer(); + if (!targetNum ) { + return; + } + + trap_Argv( 1, test, 4 ); + trap_SendConsoleCommand( va( "gc %i %i", targetNum, atoi( test ) ) ); +} + + + +/* +================= +CG_SizeUp_f + +Keybinding command +================= +*/ +static void CG_SizeUp_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer+10))); +} + + +/* +================= +CG_SizeDown_f + +Keybinding command +================= +*/ +static void CG_SizeDown_f (void) { + trap_Cvar_Set("cg_viewsize", va("%i",(int)(cg_viewsize.integer-10))); +} + + +/* +============= +CG_Viewpos_f + +Debugging command to print the current position +============= +*/ +static void CG_Viewpos_f (void) { + CG_Printf ("(%i %i %i) : %i\n", (int)cg.refdef.vieworg[0], + (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], + (int)cg.refdefViewAngles[YAW]); +} + + +static void CG_ScoresDown_f( void ) { + +#ifdef MISSIONPACK + CG_BuildSpectatorString(); +#endif + if ( cg.scoresRequestTime + 2000 < cg.time ) { + // the scores are more than two seconds out of data, + // so request new ones + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + + // leave the current scores up if they were already + // displayed, but if this is the first hit, clear them out + if ( !cg.showScores ) { + cg.showScores = qtrue; + cg.numScores = 0; + } + } else { + // show the cached contents even if they just pressed if it + // is within two seconds + cg.showScores = qtrue; + } +} + +static void CG_ScoresUp_f( void ) { + if ( cg.showScores ) { + cg.showScores = qfalse; + cg.scoreFadeTime = cg.time; + } +} + +//PKMOD - Ergodic 02/23/04 - include HUD commands +//#ifdef MISSIONPACK +extern menuDef_t *menuScoreboard; +void Menu_Reset(); // FIXME: add to right include file + +static void CG_LoadHud_f( void) { + char buff[1024]; + const char *hudSet; + memset(buff, 0, sizeof(buff)); + + String_Init(); + Menu_Reset(); + + trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); + hudSet = buff; + if (hudSet[0] == '\0') { + hudSet = "ui/hud.txt"; + } + + CG_LoadMenus(hudSet); + menuScoreboard = NULL; +} + + +static void CG_scrollScoresDown_f( void) { + if (menuScoreboard && cg.scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qtrue); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qtrue); + } +} + + +static void CG_scrollScoresUp_f( void) { + if (menuScoreboard && cg.scoreBoardShowing) { + Menu_ScrollFeeder(menuScoreboard, FEEDER_SCOREBOARD, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_REDTEAM_LIST, qfalse); + Menu_ScrollFeeder(menuScoreboard, FEEDER_BLUETEAM_LIST, qfalse); + } +} + +//PKMOD - Ergodic 02/23/04 - include HUD commands: move ifdef here +#ifdef MISSIONPACK + +static void CG_spWin_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + CG_AddBufferedSound(cgs.media.winnerSound); + //trap_S_StartLocalSound(cgs.media.winnerSound, CHAN_ANNOUNCER); + CG_CenterPrint("YOU WIN!", SCREEN_HEIGHT * .30, 0); +} + +static void CG_spLose_f( void) { + trap_Cvar_Set("cg_cameraOrbit", "2"); + trap_Cvar_Set("cg_cameraOrbitDelay", "35"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + CG_AddBufferedSound(cgs.media.loserSound); + //trap_S_StartLocalSound(cgs.media.loserSound, CHAN_ANNOUNCER); + CG_CenterPrint("YOU LOSE...", SCREEN_HEIGHT * .30, 0); +} + +#endif + +static void CG_TellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_TellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "tell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_VoiceTellTarget_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +static void CG_VoiceTellAttacker_f( void ) { + int clientNum; + char command[128]; + char message[128]; + + clientNum = CG_LastAttacker(); + if ( clientNum == -1 ) { + return; + } + + trap_Args( message, 128 ); + Com_sprintf( command, 128, "vtell %i %s", clientNum, message ); + trap_SendClientCommand( command ); +} + +//PKMOD - Ergodic 02/23/04 - enable HUD functions +//#ifdef MISSIONPACK +static void CG_NextTeamMember_f( void ) { + CG_SelectNextPlayer(); +} + +static void CG_PrevTeamMember_f( void ) { + CG_SelectPrevPlayer(); +} + +//PKMOD - Ergodic 02/23/04 - enable HUD functions: move the ifdef statment here +#ifdef MISSIONPACK + + +// ASS U ME's enumeration order as far as task specific orders, OFFENSE is zero, CAMP is last +// +static void CG_NextOrder_f( void ) { + clientInfo_t *ci = cgs.clientinfo + cg.snap->ps.clientNum; + if (ci) { + if (!ci->teamLeader && sortedTeamPlayers[cg_currentSelectedPlayer.integer] != cg.snap->ps.clientNum) { + return; + } + } + if (cgs.currentOrder < TEAMTASK_CAMP) { + cgs.currentOrder++; + + if (cgs.currentOrder == TEAMTASK_RETRIEVE) { + if (!CG_OtherTeamHasFlag()) { + cgs.currentOrder++; + } + } + + if (cgs.currentOrder == TEAMTASK_ESCORT) { + if (!CG_YourTeamHasFlag()) { + cgs.currentOrder++; + } + } + + } else { + cgs.currentOrder = TEAMTASK_OFFENSE; + } + cgs.orderPending = qtrue; + cgs.orderTime = cg.time + 3000; +} + + +static void CG_ConfirmOrder_f (void ) { + trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_YES)); + trap_SendConsoleCommand("+button5; wait; -button5"); + if (cg.time < cgs.acceptOrderTime) { + trap_SendClientCommand(va("teamtask %d\n", cgs.acceptTask)); + cgs.acceptOrderTime = 0; + } +} + +static void CG_DenyOrder_f (void ) { + trap_SendConsoleCommand(va("cmd vtell %d %s\n", cgs.acceptLeader, VOICECHAT_NO)); + trap_SendConsoleCommand("+button6; wait; -button6"); + if (cg.time < cgs.acceptOrderTime) { + cgs.acceptOrderTime = 0; + } +} + +static void CG_TaskOffense_f (void ) { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONGETFLAG)); + } else { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONOFFENSE)); + } + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_OFFENSE)); +} + +static void CG_TaskDefense_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONDEFENSE)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_DEFENSE)); +} + +static void CG_TaskPatrol_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONPATROL)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_PATROL)); +} + +static void CG_TaskCamp_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONCAMPING)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_CAMP)); +} + +static void CG_TaskFollow_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOW)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_FOLLOW)); +} + +static void CG_TaskRetrieve_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONRETURNFLAG)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_RETRIEVE)); +} + +static void CG_TaskEscort_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_ONFOLLOWCARRIER)); + trap_SendClientCommand(va("teamtask %d\n", TEAMTASK_ESCORT)); +} + +static void CG_TaskOwnFlag_f (void ) { + trap_SendConsoleCommand(va("cmd vsay_team %s\n", VOICECHAT_IHAVEFLAG)); +} + +static void CG_TauntKillInsult_f (void ) { + trap_SendConsoleCommand("cmd vsay kill_insult\n"); +} + +static void CG_TauntPraise_f (void ) { + trap_SendConsoleCommand("cmd vsay praise\n"); +} + +static void CG_TauntTaunt_f (void ) { + trap_SendConsoleCommand("cmd vtaunt\n"); +} + +static void CG_TauntDeathInsult_f (void ) { + trap_SendConsoleCommand("cmd vsay death_insult\n"); +} + +static void CG_TauntGauntlet_f (void ) { + trap_SendConsoleCommand("cmd vsay kill_guantlet\n"); +} + +static void CG_TaskSuicide_f (void ) { + int clientNum; + char command[128]; + + clientNum = CG_CrosshairPlayer(); + if ( clientNum == -1 ) { + return; + } + + Com_sprintf( command, 128, "tell %i suicide", clientNum ); + trap_SendClientCommand( command ); +} + + + + +/* +================== +CG_TeamMenu_f +================== +*/ +/* +static void CG_TeamMenu_f( void ) { + if (trap_Key_GetCatcher() & KEYCATCH_CGAME) { + CG_EventHandling(CGAME_EVENT_NONE); + trap_Key_SetCatcher(0); + } else { + CG_EventHandling(CGAME_EVENT_TEAMMENU); + //trap_Key_SetCatcher(KEYCATCH_CGAME); + } +} +*/ + +/* +================== +CG_EditHud_f +================== +*/ +/* +static void CG_EditHud_f( void ) { + //cls.keyCatchers ^= KEYCATCH_CGAME; + //VM_Call (cgvm, CG_EVENT_HANDLING, (cls.keyCatchers & KEYCATCH_CGAME) ? CGAME_EVENT_EDITHUD : CGAME_EVENT_NONE); +} +*/ + +#endif + +/* +================== +CG_StartOrbit_f +================== +*/ + +static void CG_StartOrbit_f( void ) { + char var[MAX_TOKEN_CHARS]; + + trap_Cvar_VariableStringBuffer( "developer", var, sizeof( var ) ); + if ( !atoi(var) ) { + return; + } + if (cg_cameraOrbit.value != 0) { + trap_Cvar_Set ("cg_cameraOrbit", "0"); + trap_Cvar_Set("cg_thirdPerson", "0"); + } else { + trap_Cvar_Set("cg_cameraOrbit", "5"); + trap_Cvar_Set("cg_thirdPerson", "1"); + trap_Cvar_Set("cg_thirdPersonAngle", "0"); + trap_Cvar_Set("cg_thirdPersonRange", "100"); + } +} + +/* +static void CG_Camera_f( void ) { + char name[1024]; + trap_Argv( 1, name, sizeof(name)); + if (trap_loadCamera(name)) { + cg.cameraMode = qtrue; + trap_startCamera(cg.time); + } else { + CG_Printf ("Unable to load camera %s\n",name); + } +} +*/ + + +typedef struct { + char *cmd; + void (*function)(void); +} consoleCommand_t; + +static consoleCommand_t commands[] = { + { "testgun", CG_TestGun_f }, + { "testmodel", CG_TestModel_f }, + { "nextframe", CG_TestModelNextFrame_f }, + { "prevframe", CG_TestModelPrevFrame_f }, + { "nextskin", CG_TestModelNextSkin_f }, + { "prevskin", CG_TestModelPrevSkin_f }, + { "viewpos", CG_Viewpos_f }, + { "+scores", CG_ScoresDown_f }, + { "-scores", CG_ScoresUp_f }, + { "+zoom", CG_ZoomDown_f }, + { "-zoom", CG_ZoomUp_f }, + { "sizeup", CG_SizeUp_f }, + { "sizedown", CG_SizeDown_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "weapon", CG_Weapon_f }, +//PKMOD - Ergodic 07/02/00 PKA weapon commands + { "gravitywell", CG_Weapon_GravityWell }, + { "autosentry", CG_Weapon_Sentry }, + { "beartrap", CG_Weapon_BearTrap }, + { "beans", CG_Weapon_Beans }, +//PKMOD - Ergodic 07/12/00 PKA weapon commands + { "gauntlet", CG_Weapon_Gauntlet }, + { "machinegun", CG_Weapon_MachineGun }, +//PKMOD - Ergodic 03/22/01 - change shotgun name to boomstick + { "boomstick", CG_Weapon_ShotGun }, + { "airfist", CG_Weapon_AirFist }, + { "nailgun", CG_Weapon_NailGun }, + { "grenades", CG_Weapon_GrenadeLauncher }, + { "rockets", CG_Weapon_RocketLauncher }, + { "lightning", CG_Weapon_LightningGun }, +//PKMOD - Ergodic 07/12/01 - change railgun name to magnum + { "magnum", CG_Weapon_RailGun }, + { "dragon", CG_Weapon_Harpoon }, +//PKMOD - Ergodic 03/01/01 - dragon deploy pka weapon +//PKMOD - Ergodic 03/27/01 - remove dragon deploy for beta 2 launch (sniff) +//PKMOD - Ergodic 05/08/01 - re-enable dragon deploy + { "dragondeploy", CG_Weapon_DragonDeploy }, +//PKMOD - Ergodic 04/04/01 - add last weapon command + { "weaplast", CG_LastWeapon_f }, + + { "tell_target", CG_TellTarget_f }, + { "tell_attacker", CG_TellAttacker_f }, + { "vtell_target", CG_VoiceTellTarget_f }, + { "vtell_attacker", CG_VoiceTellAttacker_f }, + { "tcmd", CG_TargetCommand_f }, +//PKMOD - Ergodic 02/23/04 - include HUD commands + { "loadhud", CG_LoadHud_f }, + { "nextTeamMember", CG_NextTeamMember_f }, + { "prevTeamMember", CG_PrevTeamMember_f }, + { "scoresDown", CG_scrollScoresDown_f }, + { "scoresUp", CG_scrollScoresUp_f }, +#ifdef MISSIONPACK + { "loadhud", CG_LoadHud_f }, + { "nextTeamMember", CG_NextTeamMember_f }, + { "prevTeamMember", CG_PrevTeamMember_f }, + { "nextOrder", CG_NextOrder_f }, + { "confirmOrder", CG_ConfirmOrder_f }, + { "denyOrder", CG_DenyOrder_f }, + { "taskOffense", CG_TaskOffense_f }, + { "taskDefense", CG_TaskDefense_f }, + { "taskPatrol", CG_TaskPatrol_f }, + { "taskCamp", CG_TaskCamp_f }, + { "taskFollow", CG_TaskFollow_f }, + { "taskRetrieve", CG_TaskRetrieve_f }, + { "taskEscort", CG_TaskEscort_f }, + { "taskSuicide", CG_TaskSuicide_f }, + { "taskOwnFlag", CG_TaskOwnFlag_f }, + { "tauntKillInsult", CG_TauntKillInsult_f }, + { "tauntPraise", CG_TauntPraise_f }, + { "tauntTaunt", CG_TauntTaunt_f }, + { "tauntDeathInsult", CG_TauntDeathInsult_f }, + { "tauntGauntlet", CG_TauntGauntlet_f }, + { "spWin", CG_spWin_f }, + { "spLose", CG_spLose_f }, + { "scoresDown", CG_scrollScoresDown_f }, + { "scoresUp", CG_scrollScoresUp_f }, +#endif + { "startOrbit", CG_StartOrbit_f }, + //{ "camera", CG_Camera_f }, + { "loaddeferred", CG_LoadDeferredPlayers }, + //PKMOD - Ergodic 12/09/03 - Add dynamic hub vote command + { "cghubalternates", CG_HubAlternates_f } +}; + + +/* +================= +CG_ConsoleCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +qboolean CG_ConsoleCommand( void ) { + const char *cmd; + int i; + + cmd = CG_Argv(0); + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + if ( !Q_stricmp( cmd, commands[i].cmd ) ) { + commands[i].function(); + return qtrue; + } + } + + return qfalse; +} + + +/* +================= +CG_InitConsoleCommands + +Let the client system know about all of our commands +so it can perform tab completion +================= +*/ +void CG_InitConsoleCommands( void ) { + int i; + + for ( i = 0 ; i < sizeof( commands ) / sizeof( commands[0] ) ; i++ ) { + trap_AddCommand( commands[i].cmd ); + } + + // + // the game server will interpret these commands, which will be automatically + // forwarded to the server after they are not recognized locally + // + trap_AddCommand ("kill"); + trap_AddCommand ("say"); + trap_AddCommand ("say_team"); + trap_AddCommand ("tell"); + trap_AddCommand ("vsay"); + trap_AddCommand ("vsay_team"); + trap_AddCommand ("vtell"); + trap_AddCommand ("vtaunt"); + trap_AddCommand ("vosay"); + trap_AddCommand ("vosay_team"); + trap_AddCommand ("votell"); + trap_AddCommand ("give"); + trap_AddCommand ("god"); + trap_AddCommand ("notarget"); + trap_AddCommand ("noclip"); + trap_AddCommand ("team"); + trap_AddCommand ("follow"); + trap_AddCommand ("levelshot"); + trap_AddCommand ("addbot"); + trap_AddCommand ("setviewpos"); + trap_AddCommand ("callvote"); + trap_AddCommand ("vote"); + trap_AddCommand ("callteamvote"); + trap_AddCommand ("teamvote"); + trap_AddCommand ("stats"); + trap_AddCommand ("teamtask"); + trap_AddCommand ("loaddefered"); // spelled wrong, but not changing for demo + //PKMOD - Ergodic 03/04/01 - add dragondeploy command + //PKMOD - Ergodic 03/27/01 - remove dragon deploy for beta 2 launch (sniff) + //PKMOD - Ergodic 05/08/01 - re-enable dragon deploy + trap_AddCommand ("gdeploy"); + //PKMOD - Ergodic 12/10/03 - add command for dynamic hub messaging +// trap_AddCommand ("cghubalternates"); +} diff --git a/quake3/source/code/cgame/cg_draw.c b/quake3/source/code/cgame/cg_draw.c new file mode 100644 index 0000000..f915c5d --- /dev/null +++ b/quake3/source/code/cgame/cg_draw.c @@ -0,0 +1,2872 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_draw.c -- draw all of the graphical elements during +// active (after loading) gameplay + +#include "cg_local.h" + +//PKMOD - Ergodic 01/17/04 - Enable Hud Code +//#ifdef MISSIONPACK +#include "../ui/ui_shared.h" + +// used for scoreboard +extern displayContextDef_t cgDC; +menuDef_t *menuScoreboard = NULL; +//#else +//PKMOD - Ergodic 01/30/04 - Enable Hud Code: drawTeamOverlayModificationCount is defined in cg_newdraw +//int drawTeamOverlayModificationCount = -1; +//#endif + +int sortedTeamPlayers[TEAM_MAXOVERLAY]; +int numSortedTeamPlayers; + +char systemChat[256]; +char teamChat1[256]; +char teamChat2[256]; +//PKMOD - Ergodic 10/13/00 - define hubinfo lines +int numHubInfoLines; + +//PKMOD - Ergodic 01/17/04 - Enable Hud Code +//#ifdef MISSIONPACK + +int CG_Text_Width(const char *text, float scale, int limit) { + int count,len; + float out; + glyphInfo_t *glyph; + float useScale; +// FIXME: see ui_main.c, same problem +// const unsigned char *s = text; + const char *s = text; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + out = 0; + if (text) { + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + if ( Q_IsColorString(s) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + out += glyph->xSkip; + s++; + count++; + } + } + } + return out * useScale; +} + +int CG_Text_Height(const char *text, float scale, int limit) { + int len, count; + float max; + glyphInfo_t *glyph; + float useScale; +// TTimo: FIXME +// const unsigned char *s = text; + const char *s = text; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + max = 0; + if (text) { + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + if ( Q_IsColorString(s) ) { + s += 2; + continue; + } else { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + if (max < glyph->height) { + max = glyph->height; + } + s++; + count++; + } + } + } + return max * useScale; +} + +void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader) { + float w, h; + w = width * scale; + h = height * scale; + CG_AdjustFrom640( &x, &y, &w, &h ); + trap_R_DrawStretchPic( x, y, w, h, s, t, s2, t2, hShader ); +} + +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + float useScale; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + if (text) { +// TTimo: FIXME +// const unsigned char *s = text; + const char *s = text; + trap_R_SetColor( color ); + memcpy(&newColor[0], &color[0], sizeof(vec4_t)); + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + //int yadj = Assets.textFont.glyphs[text[i]].bottom + Assets.textFont.glyphs[text[i]].top; + //float yadj = scale * (Assets.textFont.glyphs[text[i]].imageHeight - Assets.textFont.glyphs[text[i]].height); + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if (style == ITEM_TEXTSTYLE_SHADOWED || style == ITEM_TEXTSTYLE_SHADOWEDMORE) { + int ofs = style == ITEM_TEXTSTYLE_SHADOWED ? 1 : 2; + colorBlack[3] = newColor[3]; + trap_R_SetColor( colorBlack ); + CG_Text_PaintChar(x + ofs, y - yadj + ofs, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph); + colorBlack[3] = 1.0; + trap_R_SetColor( newColor ); + } + CG_Text_PaintChar(x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph); + // CG_DrawPic(x, y - yadj, scale * cgDC.Assets.textFont.glyphs[text[i]].imageWidth, scale * cgDC.Assets.textFont.glyphs[text[i]].imageHeight, cgDC.Assets.textFont.glyphs[text[i]].glyph); + x += (glyph->xSkip * useScale) + adjust; + s++; + count++; + } + } + trap_R_SetColor( NULL ); + } +} + + +//PKMOD - Ergodic 01/17/04 - Enable Hud Code +//#endif + +/* +============== +CG_DrawField + +Draws large numbers for status bar and powerups +============== +*/ +#ifndef MISSIONPACK +static void CG_DrawField (int x, int y, int width, int value) { + char num[16], *ptr; + int l; + int frame; + + if ( width < 1 ) { + return; + } + + // draw number string + if ( width > 5 ) { + width = 5; + } + + switch ( width ) { + case 1: + value = value > 9 ? 9 : value; + value = value < 0 ? 0 : value; + break; + case 2: + value = value > 99 ? 99 : value; + value = value < -9 ? -9 : value; + break; + case 3: + value = value > 999 ? 999 : value; + value = value < -99 ? -99 : value; + break; + case 4: + value = value > 9999 ? 9999 : value; + value = value < -999 ? -999 : value; + break; + } + + Com_sprintf (num, sizeof(num), "%i", value); + l = strlen(num); + if (l > width) + l = width; + x += 2 + CHAR_WIDTH*(width - l); + + ptr = num; + while (*ptr && l) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + CG_DrawPic( x,y, CHAR_WIDTH, CHAR_HEIGHT, cgs.media.numberShaders[frame] ); + x += CHAR_WIDTH; + ptr++; + l--; + } +} +#endif // MISSIONPACK + +/* +================ +CG_Draw3DModel + +================ +*/ +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) { + refdef_t refdef; + refEntity_t ent; + + if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { + return; + } + + CG_AdjustFrom640( &x, &y, &w, &h ); + + memset( &refdef, 0, sizeof( refdef ) ); + + memset( &ent, 0, sizeof( ent ) ); + AnglesToAxis( angles, ent.axis ); + VectorCopy( origin, ent.origin ); + ent.hModel = model; + ent.customSkin = skin; + ent.renderfx = RF_NOSHADOW; // no stencil shadows + + refdef.rdflags = RDF_NOWORLDMODEL; + + AxisClear( refdef.viewaxis ); + + refdef.fov_x = 30; + refdef.fov_y = 30; + + refdef.x = x; + refdef.y = y; + refdef.width = w; + refdef.height = h; + + refdef.time = cg.time; + + trap_R_ClearScene(); + trap_R_AddRefEntityToScene( &ent ); + trap_R_RenderScene( &refdef ); +} + +/* +================ +CG_DrawHead + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) { + clipHandle_t cm; + clientInfo_t *ci; + float len; + vec3_t origin; + vec3_t mins, maxs; + + ci = &cgs.clientinfo[ clientNum ]; + + if ( cg_draw3dIcons.integer ) { + cm = ci->headModel; + if ( !cm ) { + return; + } + + // offset the origin y and z to center the head + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the head nearly fills the box + // assume heads are taller than wide + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + // allow per-model tweaking + VectorAdd( origin, ci->headOffset, origin ); + + CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, origin, headAngles ); + } else if ( cg_drawIcons.integer ) { + CG_DrawPic( x, y, w, h, ci->modelIcon ); + } + + // if they are deferred, draw a cross out + if ( ci->deferred ) { + CG_DrawPic( x, y, w, h, cgs.media.deferShader ); + } +} + +/* +================ +CG_DrawFlagModel + +Used for both the status bar and the scoreboard +================ +*/ +void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) { + qhandle_t cm; + float len; + vec3_t origin, angles; + vec3_t mins, maxs; + qhandle_t handle; + + if ( !force2D && cg_draw3dIcons.integer ) { + + VectorClear( angles ); + + cm = cgs.media.redFlagModel; + + // offset the origin y and z to center the flag + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the flag nearly fills the box + // assume heads are taller than wide + len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + angles[YAW] = 60 * sin( cg.time / 2000.0 );; + + if( team == TEAM_RED ) { + handle = cgs.media.redFlagModel; + } else if( team == TEAM_BLUE ) { + handle = cgs.media.blueFlagModel; + } else if( team == TEAM_FREE ) { + handle = cgs.media.neutralFlagModel; + } else { + return; + } + CG_Draw3DModel( x, y, w, h, handle, 0, origin, angles ); + } else if ( cg_drawIcons.integer ) { + gitem_t *item; + + if( team == TEAM_RED ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + } else if( team == TEAM_BLUE ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + } else if( team == TEAM_FREE ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + } else { + return; + } + if (item) { + CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon ); + } + } +} + +/* +================ +CG_DrawStatusBarHead + +================ +*/ +#ifndef MISSIONPACK + +static void CG_DrawStatusBarHead( float x ) { + vec3_t angles; + float size, stretch; + float frac; + + VectorClear( angles ); + + if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { + frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; + size = ICON_SIZE * 1.25 * ( 1.5 - frac * 0.5 ); + + stretch = size - ICON_SIZE * 1.25; + // kick in the direction of damage + x -= stretch * 0.5 + cg.damageX * stretch * 0.5; + + cg.headStartYaw = 180 + cg.damageX * 45; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + + cg.headStartTime = cg.time; + cg.headEndTime = cg.time + 100 + random() * 2000; + } else { + if ( cg.time >= cg.headEndTime ) { + // select a new head angle + cg.headStartYaw = cg.headEndYaw; + cg.headStartPitch = cg.headEndPitch; + cg.headStartTime = cg.headEndTime; + cg.headEndTime = cg.time + 100 + random() * 2000; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + } + + size = ICON_SIZE * 1.25; + } + + // if the server was frozen for a while we may have a bad head start time + if ( cg.headStartTime > cg.time ) { + cg.headStartTime = cg.time; + } + + frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); + frac = frac * frac * ( 3 - 2 * frac ); + angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; + angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; + + CG_DrawHead( x, 480 - size, size, size, + cg.snap->ps.clientNum, angles ); +} +#endif // MISSIONPACK + +/* +================ +CG_DrawStatusBarFlag + +================ +*/ +#ifndef MISSIONPACK +static void CG_DrawStatusBarFlag( float x, int team ) { + CG_DrawFlagModel( x, 480 - ICON_SIZE, ICON_SIZE, ICON_SIZE, team, qfalse ); +} +#endif // MISSIONPACK + +/* +================ +CG_DrawTeamBackground + +================ +*/ +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) +{ + vec4_t hcolor; + + hcolor[3] = alpha; + if ( team == TEAM_RED ) { + hcolor[0] = 1; + hcolor[1] = 0; + hcolor[2] = 0; + } else if ( team == TEAM_BLUE ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 1; + } else { + return; + } + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); +} + +/* +================ +CG_DrawStatusBar + +================ +*/ +#ifndef MISSIONPACK +static void CG_DrawStatusBar( void ) { + int color; + centity_t *cent; + playerState_t *ps; + int value; + vec4_t hcolor; + vec3_t angles; + vec3_t origin; +#ifdef MISSIONPACK + qhandle_t handle; +#endif + static float colors[4][4] = { +// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; + { 1.0f, 0.69f, 0.0f, 1.0f }, // normal + { 1.0f, 0.2f, 0.2f, 1.0f }, // low health + { 0.5f, 0.5f, 0.5f, 1.0f }, // weapon firing + { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + // draw the team background + CG_DrawTeamBackground( 0, 420, 640, 60, 0.33f, cg.snap->ps.persistant[PERS_TEAM] ); + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + VectorClear( angles ); + + // draw any 3D icons first, so the changes back to 2D are minimized + if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { + origin[0] = 70; + origin[1] = 0; + origin[2] = 0; + angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); + CG_Draw3DModel( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, + cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); + } + + CG_DrawStatusBarHead( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE ); + + if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { + CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_RED ); + } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { + CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_BLUE ); + } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { + CG_DrawStatusBarFlag( 185 + CHAR_WIDTH*3 + TEXT_ICON_SPACE + ICON_SIZE, TEAM_FREE ); + } + + if ( ps->stats[ STAT_ARMOR ] ) { + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + CG_Draw3DModel( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, + cgs.media.armorModel, 0, origin, angles ); + } +#ifdef MISSIONPACK + if( cgs.gametype == GT_HARVESTER ) { + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeModel; + } else { + handle = cgs.media.blueCubeModel; + } + CG_Draw3DModel( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 416, ICON_SIZE, ICON_SIZE, handle, 0, origin, angles ); + } +#endif + // + // ammo + // + if ( cent->currentState.weapon ) { + value = ps->ammo[cent->currentState.weapon]; + if ( value > -1 ) { + if ( cg.predictedPlayerState.weaponstate == WEAPON_FIRING + && cg.predictedPlayerState.weaponTime > 100 ) { + // draw as dark grey when reloading + color = 2; // dark grey + } else { + if ( value >= 0 ) { + color = 0; // green + } else { + color = 1; // red + } + } + trap_R_SetColor( colors[color] ); + + CG_DrawField (0, 432, 3, value); + trap_R_SetColor( NULL ); + + // if we didn't draw a 3D icon, draw a 2D icon for ammo + if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + qhandle_t icon; + + icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; + if ( icon ) { + CG_DrawPic( CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, icon ); + } + } + } + } + + // + // health + // + value = ps->stats[STAT_HEALTH]; + if ( value > 100 ) { + trap_R_SetColor( colors[3] ); // white + } else if (value > 25) { + trap_R_SetColor( colors[0] ); // green + } else if (value > 0) { + color = (cg.time >> 8) & 1; // flash + trap_R_SetColor( colors[color] ); + } else { + trap_R_SetColor( colors[1] ); // red + } + + // stretch the health up when taking damage + CG_DrawField ( 185, 432, 3, value); + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + + + // + // armor + // + value = ps->stats[STAT_ARMOR]; + if (value > 0 ) { + trap_R_SetColor( colors[0] ); + CG_DrawField (370, 432, 3, value); + trap_R_SetColor( NULL ); + // if we didn't draw a 3D icon, draw a 2D icon for armor + if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + CG_DrawPic( 370 + CHAR_WIDTH*3 + TEXT_ICON_SPACE, 432, ICON_SIZE, ICON_SIZE, cgs.media.armorIcon ); + } + + } +#ifdef MISSIONPACK + // + // cubes + // + if( cgs.gametype == GT_HARVESTER ) { + value = ps->generic1; + if( value > 99 ) { + value = 99; + } + trap_R_SetColor( colors[0] ); + CG_DrawField (640 - (CHAR_WIDTH*2 + TEXT_ICON_SPACE + ICON_SIZE), 432, 2, value); + trap_R_SetColor( NULL ); + // if we didn't draw a 3D icon, draw a 2D icon for armor + if ( !cg_draw3dIcons.integer && cg_drawIcons.integer ) { + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeIcon; + } else { + handle = cgs.media.blueCubeIcon; + } + CG_DrawPic( 640 - (TEXT_ICON_SPACE + ICON_SIZE), 432, ICON_SIZE, ICON_SIZE, handle ); + } + } +#endif +} +#endif + +/* +=========================================================================================== + + UPPER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================ +CG_DrawAttacker + +================ +*/ +static float CG_DrawAttacker( float y ) { + int t; + float size; + vec3_t angles; + const char *info; + const char *name; + int clientNum; + + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return y; + } + + if ( !cg.attackerTime ) { + return y; + } + + clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER]; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) { + return y; + } + + t = cg.time - cg.attackerTime; + if ( t > ATTACKER_HEAD_TIME ) { + cg.attackerTime = 0; + return y; + } + + size = ICON_SIZE * 1.25; + + angles[PITCH] = 0; + angles[YAW] = 180; + angles[ROLL] = 0; + CG_DrawHead( 640 - size, y, size, size, clientNum, angles ); + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + name = Info_ValueForKey( info, "n" ); + y += size; + CG_DrawBigString( 640 - ( Q_PrintStrlen( name ) * BIGCHAR_WIDTH), y, name, 0.5 ); + + return y + BIGCHAR_HEIGHT + 2; +} + +/* +================== +CG_DrawSnapshot +================== +*/ +static float CG_DrawSnapshot( float y ) { + char *s; + int w; + + s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, + cg.latestSnapshotNum, cgs.serverCommandSequence ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( 635 - w, y + 2, s, 1.0F); + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================== +CG_DrawFPS +================== +*/ +#define FPS_FRAMES 4 +static float CG_DrawFPS( float y ) { + char *s; + int w; + static int previousTimes[FPS_FRAMES]; + static int index; + int i, total; + int fps; + static int previous; + int t, frameTime; + + // don't use serverTime, because that will be drifting to + // correct for internet lag changes, timescales, timedemos, etc + t = trap_Milliseconds(); + frameTime = t - previous; + previous = t; + + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + if ( index > FPS_FRAMES ) { + // average multiple frames together to smooth changes out a bit + total = 0; + for ( i = 0 ; i < FPS_FRAMES ; i++ ) { + total += previousTimes[i]; + } + if ( !total ) { + total = 1; + } + fps = 1000 * FPS_FRAMES / total; + + s = va( "%ifps", fps ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( 635 - w, y + 2, s, 1.0F); + } + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================= +CG_DrawTimer +================= +*/ +static float CG_DrawTimer( float y ) { + char *s; + int w; + int mins, seconds, tens; + int msec; + + msec = cg.time - cgs.levelStartTime; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + s = va( "%i:%i%i", mins, tens, seconds ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + + CG_DrawBigString( 635 - w, y + 2, s, 1.0F); + + return y + BIGCHAR_HEIGHT + 4; +} + +//PKMOD - Ergodic 10/13/00 display hub voting statistics +/* +================= +CG_DrawHubOverlay +================= +*/ + +#define HUB_OVERLAY_MAXNAME_WIDTH 25 +#define HUB_OVERLAY_MAXLOCATION_WIDTH 16 + +void CG_DrawHubOverlay( void ) { + int x, h, xx; + int i, len; + vec4_t hcolor; + int pwidth; + + float y; + + y = 100; + x = 0; + + //PKMOD - Ergodic 12/16/00 - change PERS_HUB_FLAG to be first bit of PERS_PAINKILLER_COUNT + // if ( cg.snap->ps.persistant[PERS_HUB_FLAG] != 1 ) { + if ( !( cg.snap->ps.persistant[PERS_PAINKILLER_COUNT] & 1 ) ) { + return; + } + + if ( numHubInfoLines == 0 ) + return; + + // max hubinfo name width + pwidth = 0; + for (i = 0; i < numHubInfoLines; i++) { + len = CG_DrawStrlen(cgs.hubInfoDisplay[i].info); + if (len > pwidth) { + pwidth = len; + } + } + + if (pwidth > HUB_OVERLAY_MAXNAME_WIDTH) + pwidth = HUB_OVERLAY_MAXNAME_WIDTH; + +// lwidth = HUB_OVERLAY_MAXLOCATION_WIDTH; + +// w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH; + + h = (3 + 2) * TINYCHAR_HEIGHT; + +// y += h; +// ret_y = y; + +// hcolor[0] = 0.2; +// hcolor[1] = 1; +// hcolor[2] = 0.2; +// hcolor[3] = 0.33; +// trap_R_SetColor( hcolor ); + +// CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + xx = TINYCHAR_WIDTH; + +// hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0; + //PKMOD - Ergodic 12/16/00 - add "(float)" to force a float declaration + hcolor[0] = (float)0.2; + hcolor[1] = (float)1; + hcolor[2] = (float)0.2; + hcolor[3] = (float)0.33; + + CG_DrawStringExt( xx, y, + "PainKeepArena Hub", hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, HUB_OVERLAY_MAXNAME_WIDTH); + + y += TINYCHAR_HEIGHT; + + CG_DrawStringExt( xx, y, + " Votes Map", hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, HUB_OVERLAY_MAXNAME_WIDTH); + + y += TINYCHAR_HEIGHT; + + //PKMOD - Ergodic 11/01/00 - update code to include the time remaining + for (i = 0; i < numHubInfoLines - 1; i++) { + + CG_DrawStringExt( xx, y, + cgs.hubInfoDisplay[i].info, hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, HUB_OVERLAY_MAXNAME_WIDTH); + + y += TINYCHAR_HEIGHT; + } + + y += TINYCHAR_HEIGHT; //add a spacer + CG_DrawStringExt( xx, y, + cgs.hubInfoDisplay[i].info, hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, HUB_OVERLAY_MAXNAME_WIDTH); + + return; +} + + + + +/* +================= +CG_DrawTeamOverlay +================= +*/ + +static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) { + int x, w, h, xx; + int i, j, len; + const char *p; + vec4_t hcolor; + int pwidth, lwidth; + int plyrs; + char st[16]; + clientInfo_t *ci; + gitem_t *item; + int ret_y, count; + + if ( !cg_drawTeamOverlay.integer ) { + return y; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) { + return y; // Not on any team + } + + plyrs = 0; + + // max player name width + pwidth = 0; + count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + plyrs++; + len = CG_DrawStrlen(ci->name); + if (len > pwidth) + pwidth = len; + } + } + + if (!plyrs) + return y; + + if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH) + pwidth = TEAM_OVERLAY_MAXNAME_WIDTH; + + // max location name width + lwidth = 0; + for (i = 1; i < MAX_LOCATIONS; i++) { + p = CG_ConfigString(CS_LOCATIONS + i); + if (p && *p) { + len = CG_DrawStrlen(p); + if (len > lwidth) + lwidth = len; + } + } + + if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH) + lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH; + + w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH; + + if ( right ) + x = 640 - w; + else + x = 0; + + h = plyrs * TINYCHAR_HEIGHT; + + if ( upper ) { + ret_y = y + h; + } else { + y -= h; + ret_y = y; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 1.0f; + hcolor[1] = 0.0f; + hcolor[2] = 0.0f; + hcolor[3] = 0.33f; + } else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) + hcolor[0] = 0.0f; + hcolor[1] = 0.0f; + hcolor[2] = 1.0f; + hcolor[3] = 0.33f; + } + trap_R_SetColor( hcolor ); + CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + + hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0; + + xx = x + TINYCHAR_WIDTH; + + CG_DrawStringExt( xx, y, + ci->name, hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH); + + if (lwidth) { + p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) + p = "unknown"; + len = CG_DrawStrlen(p); + if (len > lwidth) + len = lwidth; + +// xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth + +// ((lwidth/2 - len/2) * TINYCHAR_WIDTH); + xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth; + CG_DrawStringExt( xx, y, + p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + TEAM_OVERLAY_MAXLOCATION_WIDTH); + } + + CG_GetColorForHealth( ci->health, ci->armor, hcolor ); + + Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); + + xx = x + TINYCHAR_WIDTH * 3 + + TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; + + CG_DrawStringExt( xx, y, + st, hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + + // draw weapon icon + xx += TINYCHAR_WIDTH * 3; + + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + cgs.media.deferShader ); + } + + // Draw powerup icons + if (right) { + xx = x; + } else { + xx = x + w - TINYCHAR_WIDTH; + } + for (j = 0; j <= PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + + item = BG_FindItemForPowerup( j ); + + if (item) { + CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + trap_R_RegisterShader( item->icon ) ); + if (right) { + xx -= TINYCHAR_WIDTH; + } else { + xx += TINYCHAR_WIDTH; + } + } + } + } + + y += TINYCHAR_HEIGHT; + } + } + + return ret_y; +//#endif +} + + +/* +===================== +CG_DrawUpperRight + +===================== +*/ +static void CG_DrawUpperRight( void ) { + float y; + + y = 0; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) { + y = CG_DrawTeamOverlay( y, qtrue, qtrue ); + } + if ( cg_drawSnapshot.integer ) { + y = CG_DrawSnapshot( y ); + } + if ( cg_drawFPS.integer ) { + y = CG_DrawFPS( y ); + } + if ( cg_drawTimer.integer ) { + y = CG_DrawTimer( y ); + } + if ( cg_drawAttacker.integer ) { + y = CG_DrawAttacker( y ); + } + +} + +/* +=========================================================================================== + + LOWER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================= +CG_DrawScores + +Draw the small two score display +================= +*/ +#ifndef MISSIONPACK +static float CG_DrawScores( float y ) { + const char *s; + int s1, s2, score; + int x, w; + int v; + vec4_t color; + float y1; + gitem_t *item; + + s1 = cgs.scores1; + s2 = cgs.scores2; + + y -= BIGCHAR_HEIGHT + 8; + + y1 = y; + + // draw from the right side to left + if ( cgs.gametype >= GT_TEAM ) { + x = 640; + color[0] = 0.0f; + color[1] = 0.0f; + color[2] = 1.0f; + color[3] = 0.33f; + s = va( "%2i", s2 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + + if ( cgs.gametype == GT_CTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.blueFlagShader[cgs.blueflag] ); + } + } + } + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 0.33f; + s = va( "%2i", s1 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + + if ( cgs.gametype == GT_CTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_REDFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.redflag >= 0 && cgs.redflag <= 2 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.redFlagShader[cgs.redflag] ); + } + } + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_1FCTF ) { + // Display flag status + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + + if (item) { + y1 = y - BIGCHAR_HEIGHT - 8; + if( cgs.flagStatus >= 0 && cgs.flagStatus <= 3 ) { + CG_DrawPic( x, y1-4, w, BIGCHAR_HEIGHT+8, cgs.media.flagShader[cgs.flagStatus] ); + } + } + } +#endif + if ( cgs.gametype >= GT_CTF ) { + v = cgs.capturelimit; + } else { + v = cgs.fraglimit; + } + if ( v ) { + s = va( "%2i", v ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + } else { + qboolean spectator; + + x = 640; + score = cg.snap->ps.persistant[PERS_SCORE]; + spectator = ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ); + + // always show your score in the second box if not in first place + if ( s1 != score ) { + s2 = score; + } + if ( s2 != SCORE_NOT_PRESENT ) { + s = va( "%2i", s2 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + if ( !spectator && score == s2 && score != s1 ) { + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } else { + color[0] = 0.5f; + color[1] = 0.5f; + color[2] = 0.5f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + // first place + if ( s1 != SCORE_NOT_PRESENT ) { + s = va( "%2i", s1 ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + if ( !spectator && score == s1 ) { + color[0] = 0.0f; + color[1] = 0.0f; + color[2] = 1.0f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + CG_DrawPic( x, y-4, w, BIGCHAR_HEIGHT+8, cgs.media.selectShader ); + } else { + color[0] = 0.5f; + color[1] = 0.5f; + color[2] = 0.5f; + color[3] = 0.33f; + CG_FillRect( x, y-4, w, BIGCHAR_HEIGHT+8, color ); + } + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + if ( cgs.fraglimit ) { + s = va( "%2i", cgs.fraglimit ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH + 8; + x -= w; + CG_DrawBigString( x + 4, y, s, 1.0F); + } + + } + + return y1 - 8; +} +#endif // MISSIONPACK + +/* +================ +CG_DrawPowerups +================ +*/ +#ifndef MISSIONPACK +static float CG_DrawPowerups( float y ) { + int sorted[MAX_POWERUPS]; + int sortedTime[MAX_POWERUPS]; + int i, j, k; + int active; + playerState_t *ps; + int t; + gitem_t *item; + int x; + int color; + float size; + float f; + static float colors[2][4] = { + { 0.2f, 1.0f, 0.2f, 1.0f } , + { 1.0f, 0.2f, 0.2f, 1.0f } + }; + + ps = &cg.snap->ps; + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + return y; + } + + // sort the list by time remaining + active = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( !ps->powerups[ i ] ) { + continue; + } + t = ps->powerups[ i ] - cg.time; + // ZOID--don't draw if the power up has unlimited time (999 seconds) + // This is true of the CTF flags + if ( t < 0 || t > 999000) { + continue; + } + + // insert into the list + for ( j = 0 ; j < active ; j++ ) { + if ( sortedTime[j] >= t ) { + for ( k = active - 1 ; k >= j ; k-- ) { + sorted[k+1] = sorted[k]; + sortedTime[k+1] = sortedTime[k]; + } + break; + } + } + sorted[j] = i; + sortedTime[j] = t; + active++; + } + + // draw the icons and timers + x = 640 - ICON_SIZE - CHAR_WIDTH * 2; + for ( i = 0 ; i < active ; i++ ) { + + //PKMOD - Ergodic 10/24/01 - add logic for radiation holdable/powerup + if ( sorted[i] == PW_RADIATE ) { + item = BG_FindItemForHoldable( HI_RADIATE ); + } + //PKMOD - Ergodic 05/07/02 - add logic for Personal Sentry holdable/powerup + else if ( sorted[i] == PW_PERSENTRY ) { + item = BG_FindItemForHoldable( HI_PERSENTRY ); + } + else { + item = BG_FindItemForPowerup( sorted[i] ); + } + + if (item) { + + color = 1; + + y -= ICON_SIZE; + + trap_R_SetColor( colors[color] ); + CG_DrawField( x, y, 2, sortedTime[ i ] / 1000 ); + + t = ps->powerups[ sorted[i] ]; + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + trap_R_SetColor( NULL ); + } else { + vec4_t modulate; + + f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; + f -= (int)f; + modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; + trap_R_SetColor( modulate ); + } + + if ( cg.powerupActive == sorted[i] && + cg.time - cg.powerupTime < PULSE_TIME ) { + f = 1.0 - ( ( (float)cg.time - cg.powerupTime ) / PULSE_TIME ); + size = ICON_SIZE * ( 1.0 + ( PULSE_SCALE - 1.0 ) * f ); + } else { + size = ICON_SIZE; + } + + CG_DrawPic( 640 - size, y + ICON_SIZE / 2 - size / 2, + size, size, trap_R_RegisterShader( item->icon ) ); + } + } + trap_R_SetColor( NULL ); + + return y; +} +#endif // MISSIONPACK + +/* +===================== +CG_DrawLowerRight + +===================== +*/ +#ifndef MISSIONPACK +static void CG_DrawLowerRight( void ) { + float y; + + y = 480 - ICON_SIZE; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 2 ) { + y = CG_DrawTeamOverlay( y, qtrue, qfalse ); + } + + y = CG_DrawScores( y ); + y = CG_DrawPowerups( y ); +} +#endif // MISSIONPACK + +/* +=================== +CG_DrawPickupItem +=================== +*/ +#ifndef MISSIONPACK +static int CG_DrawPickupItem( int y ) { + int value; + float *fadeColor; + + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + return y; + } + + y -= ICON_SIZE; + + value = cg.itemPickup; + if ( value ) { + fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); + if ( fadeColor ) { + CG_RegisterItemVisuals( value ); + trap_R_SetColor( fadeColor ); + CG_DrawPic( 8, y, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + CG_DrawBigString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, fadeColor[0] ); + trap_R_SetColor( NULL ); + } + } + + return y; +} +#endif // MISSIONPACK + +/* +===================== +CG_DrawLowerLeft + +===================== +*/ +#ifndef MISSIONPACK +static void CG_DrawLowerLeft( void ) { + float y; + + y = 480 - ICON_SIZE; + + if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 3 ) { + y = CG_DrawTeamOverlay( y, qfalse, qfalse ); + } + + + y = CG_DrawPickupItem( y ); +} +#endif // MISSIONPACK + + +//=========================================================================================== + +/* +================= +CG_DrawTeamInfo +================= +*/ +#ifndef MISSIONPACK +static void CG_DrawTeamInfo( void ) { + int w, h; + int i, len; + vec4_t hcolor; + int chatHeight; + +#define CHATLOC_Y 420 // bottom end +#define CHATLOC_X 0 + + if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) + chatHeight = cg_teamChatHeight.integer; + else + chatHeight = TEAMCHAT_HEIGHT; + if (chatHeight <= 0) + return; // disabled + + if (cgs.teamLastChatPos != cgs.teamChatPos) { + if (cg.time - cgs.teamChatMsgTimes[cgs.teamLastChatPos % chatHeight] > cg_teamChatTime.integer) { + cgs.teamLastChatPos++; + } + + h = (cgs.teamChatPos - cgs.teamLastChatPos) * TINYCHAR_HEIGHT; + + w = 0; + + for (i = cgs.teamLastChatPos; i < cgs.teamChatPos; i++) { + len = CG_DrawStrlen(cgs.teamChatMsgs[i % chatHeight]); + if (len > w) + w = len; + } + w *= TINYCHAR_WIDTH; + w += TINYCHAR_WIDTH * 2; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { + hcolor[0] = 1.0f; + hcolor[1] = 0.0f; + hcolor[2] = 0.0f; + hcolor[3] = 0.33f; + } else if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + hcolor[0] = 0.0f; + hcolor[1] = 0.0f; + hcolor[2] = 1.0f; + hcolor[3] = 0.33f; + } else { + hcolor[0] = 0.0f; + hcolor[1] = 1.0f; + hcolor[2] = 0.0f; + hcolor[3] = 0.33f; + } + + trap_R_SetColor( hcolor ); + CG_DrawPic( CHATLOC_X, CHATLOC_Y - h, 640, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); + + hcolor[0] = hcolor[1] = hcolor[2] = 1.0f; + hcolor[3] = 1.0f; + + for (i = cgs.teamChatPos - 1; i >= cgs.teamLastChatPos; i--) { + CG_DrawStringExt( CHATLOC_X + TINYCHAR_WIDTH, + CHATLOC_Y - (cgs.teamChatPos - i)*TINYCHAR_HEIGHT, + cgs.teamChatMsgs[i % chatHeight], hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); + } + } +} +#endif // MISSIONPACK + +/* +=================== +CG_DrawHoldableItem +=================== +*/ +//PKMOD - Ergodic 05/11/01 - allow holding of more than 1 type of +// holdable but only 1 of each kind +// Display holdables along the right edge of the screen +static void CG_DrawHoldableItem( void ) { + int i; + int holdable_index; + int y; + +// value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; +// CG_RegisterItemVisuals( value ); + + if ( cg.snap->ps.stats[STAT_HOLDABLE_ITEM] ) { + //PKMOD - Ergodic 10/13/01 - move holdable icons higher up on the screen + // change start height from 1/2 to 1/4 from top of screen + // y = (SCREEN_HEIGHT-ICON_SIZE_SMALL)/2; + y = (SCREEN_HEIGHT-ICON_SIZE_SMALL)/4; + holdable_index = cg.snap->ps.stats[STAT_ACTIVE_HOLDABLE]; + for (i = 1; i < HI_NUM_HOLDABLE; i++) { + if ( cg.snap->ps.stats[STAT_HOLDABLE_ITEM] & ( 1 << holdable_index ) ) { + //PKMOD - Ergodic 12/07/01 - display Private Bot pieces as only one Icon + if ( ( holdable_index < HI_BOTLEGS ) || ( holdable_index > HI_BOTHEAD )) { + //only display valid holdables + if ( holdable_index < HI_NUM_HOLDABLE ) { + CG_RegisterItemVisuals( cg_holdable[holdable_index] ); + CG_DrawPic( 640-ICON_SIZE, y, ICON_SIZE_SMALL, ICON_SIZE_SMALL, cg_items[ cg_holdable[holdable_index] ].icon ); + } + //get next y offset + y += 1.2 * ICON_SIZE_SMALL; + } + } + //PKMOD - Ergodic 12/07/01 - now at end of Private Bot pieces list - then display + if ( holdable_index == HI_BOTHEAD ) { + //PKMOD - Ergodic 12/07/01 - hold private bot parts + int pribot_parts; + qhandle_t pribotIcon; + + pribot_parts = cg.snap->ps.stats[STAT_HOLDABLE_ITEM] & ( 7 << HI_BOTLEGS ); + if ( pribot_parts ) { + //encoding bits (LEGS TORSO HEAD) + switch ( pribot_parts >> HI_BOTLEGS ) { + case 1: + pribotIcon = cgs.media.pkapribot_100Icon; + break; + case 2: + pribotIcon = cgs.media.pkapribot_010Icon; + break; + case 3: + pribotIcon = cgs.media.pkapribot_110Icon; + break; + case 4: + pribotIcon = cgs.media.pkapribot_001Icon; + break; + case 5: + pribotIcon = cgs.media.pkapribot_101Icon; + break; + case 6: + pribotIcon = cgs.media.pkapribot_011Icon; + break; + case 7: + pribotIcon = cgs.media.pkapribot_111Icon; + break; + } + CG_DrawPic( 640-ICON_SIZE, y, ICON_SIZE_SMALL, ICON_SIZE_SMALL, pribotIcon ); + //get next y offset + y += 1.2 * ICON_SIZE_SMALL; + } + } + + //set next index, wrap around if at the end of the list + holdable_index = holdable_index + 1; + if ( holdable_index >= HI_NUM_HOLDABLE ) + holdable_index = 1; + + } + } + +} + +#ifdef MISSIONPACK +/* +=================== +CG_DrawPersistantPowerup +=================== +*/ +#if 0 // sos001208 - DEAD +static void CG_DrawPersistantPowerup( void ) { + int value; + + value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; + if ( value ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( 640-ICON_SIZE, (SCREEN_HEIGHT-ICON_SIZE)/2 - ICON_SIZE, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + } +} +#endif +#endif // MISSIONPACK + + +/* +=================== +CG_DrawReward +=================== +*/ +static void CG_DrawReward( void ) { + float *color; + int i, count; + float x, y; + char buf[32]; + + if ( !cg_drawRewards.integer ) { + return; + } + + color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); + if ( !color ) { + if (cg.rewardStack > 0) { + for(i = 0; i < cg.rewardStack; i++) { + cg.rewardSound[i] = cg.rewardSound[i+1]; + cg.rewardShader[i] = cg.rewardShader[i+1]; + cg.rewardCount[i] = cg.rewardCount[i+1]; + } + cg.rewardTime = cg.time; + cg.rewardStack--; + color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); + trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER); + } else { + return; + } + } + + trap_R_SetColor( color ); + + /* + count = cg.rewardCount[0]/10; // number of big rewards to draw + + if (count) { + y = 4; + x = 320 - count * ICON_SIZE; + for ( i = 0 ; i < count ; i++ ) { + CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] ); + x += (ICON_SIZE*2); + } + } + + count = cg.rewardCount[0] - count*10; // number of small rewards to draw + */ + + if ( cg.rewardCount[0] >= 10 ) { + y = 56; + x = 320 - ICON_SIZE/2; + CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); + Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]); + x = ( SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen( buf ) ) / 2; + CG_DrawStringExt( x, y+ICON_SIZE, buf, color, qfalse, qtrue, + SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); + } + else { + + count = cg.rewardCount[0]; + + y = 56; + x = 320 - count * ICON_SIZE/2; + for ( i = 0 ; i < count ; i++ ) { + CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); + x += ICON_SIZE; + } + } + trap_R_SetColor( NULL ); +} + + +/* +=============================================================================== + +LAGOMETER + +=============================================================================== +*/ + +#define LAG_SAMPLES 128 + + +typedef struct { + int frameSamples[LAG_SAMPLES]; + int frameCount; + int snapshotFlags[LAG_SAMPLES]; + int snapshotSamples[LAG_SAMPLES]; + int snapshotCount; +} lagometer_t; + +lagometer_t lagometer; + +/* +============== +CG_AddLagometerFrameInfo + +Adds the current interpolate / extrapolate bar for this frame +============== +*/ +void CG_AddLagometerFrameInfo( void ) { + int offset; + + offset = cg.time - cg.latestSnapshotTime; + lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset; + lagometer.frameCount++; +} + +/* +============== +CG_AddLagometerSnapshotInfo + +Each time a snapshot is received, log its ping time and +the number of snapshots that were dropped before it. + +Pass NULL for a dropped packet. +============== +*/ +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { + // dropped packet + if ( !snap ) { + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1; + lagometer.snapshotCount++; + return; + } + + // add this snapshot's info + lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping; + lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags; + lagometer.snapshotCount++; +} + +/* +============== +CG_DrawDisconnect + +Should we draw something differnet for long lag vs no packets? +============== +*/ +static void CG_DrawDisconnect( void ) { + float x, y; + int cmdNum; + usercmd_t cmd; + const char *s; + int w; // bk010215 - FIXME char message[1024]; + + // draw the phone jack if we are completely past our buffers + cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &cmd ); + if ( cmd.serverTime <= cg.snap->ps.commandTime + || cmd.serverTime > cg.time ) { // special check for map_restart // bk 0102165 - FIXME + return; + } + + // also add text in center of screen + s = "Connection Interrupted"; // bk 010215 - FIXME + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w/2, 100, s, 1.0F); + + // blink the icon + if ( ( cg.time >> 9 ) & 1 ) { + return; + } + + //PKMOD - Ergodic 03/15/04 - Draw jack to overlay the lagometer + // which was moved higher up on the screen so it will not be in the way of the PKA HUD + //x = 640 - 48; + //y = 480 - 48; + x = 640 - 48; + y = 480 - 108; //03/15/04 - was "480 - 48" + + + CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) ); +} + + +#define MAX_LAGOMETER_PING 900 +#define MAX_LAGOMETER_RANGE 300 + +/* +============== +CG_DrawLagometer +============== +*/ +static void CG_DrawLagometer( void ) { + int a, x, y, i; + float v; + float ax, ay, aw, ah, mid, range; + int color; + float vscale; + + if ( !cg_lagometer.integer || cgs.localServer ) { + CG_DrawDisconnect(); + return; + } + + // + // draw the graph + // +#ifdef MISSIONPACK + x = 640 - 48; + y = 480 - 144; +#else +//PKMOD - Ergodic 02/27/04 - Draw lagometer higher up on the screen so it will not be in the way of the PKA HUD + x = 640 - 48; + y = 480 - 108; //02/27/04 - was "480 - 48" +#endif + + trap_R_SetColor( NULL ); + CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); + + ax = x; + ay = y; + aw = 48; + ah = 48; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + color = -1; + range = ah / 3; + mid = ay + range; + + vscale = range / MAX_LAGOMETER_RANGE; + + // draw the frame interpoalte / extrapolate graph + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.frameSamples[i]; + v *= vscale; + if ( v > 0 ) { + if ( color != 1 ) { + color = 1; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 2 ) { + color = 2; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] ); + } + v = -v; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + // draw the snapshot latency / drop graph + range = ah / 2; + vscale = range / MAX_LAGOMETER_PING; + + for ( a = 0 ; a < aw ; a++ ) { + i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1); + v = lagometer.snapshotSamples[i]; + if ( v > 0 ) { + if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { + if ( color != 5 ) { + color = 5; // YELLOW for rate delay + trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); + } + } else { + if ( color != 3 ) { + color = 3; + trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] ); + } + } + v = v * vscale; + if ( v > range ) { + v = range; + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); + } else if ( v < 0 ) { + if ( color != 4 ) { + color = 4; // RED for dropped snapshots + trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] ); + } + trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); + } + } + + trap_R_SetColor( NULL ); + + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_DrawBigString( ax, ay, "snc", 1.0 ); + } + + CG_DrawDisconnect(); +} + + + +/* +=============================================================================== + +CENTER PRINTING + +=============================================================================== +*/ + + +/* +============== +CG_CenterPrint + +Called for important messages that should stay in the center of the screen +for a few moments +============== +*/ +void CG_CenterPrint( const char *str, int y, int charWidth ) { + char *s; + + Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); + + cg.centerPrintTime = cg.time; + cg.centerPrintY = y; + cg.centerPrintCharWidth = charWidth; + + // count the number of lines for centering + cg.centerPrintLines = 1; + s = cg.centerPrint; + while( *s ) { + if (*s == '\n') + cg.centerPrintLines++; + s++; + } +} + + +/* +=================== +CG_DrawCenterString +=================== +*/ +static void CG_DrawCenterString( void ) { + char *start; + int l; + int x, y, w; +#ifdef MISSIONPACK // bk010221 - unused else + int h; +#endif + float *color; + + if ( !cg.centerPrintTime ) { + return; + } + + color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); + if ( !color ) { + return; + } + + trap_R_SetColor( color ); + + start = cg.centerPrint; + + y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; + + while ( 1 ) { + char linebuffer[1024]; + + for ( l = 0; l < 50; l++ ) { + if ( !start[l] || start[l] == '\n' ) { + break; + } + linebuffer[l] = start[l]; + } + linebuffer[l] = 0; + +#ifdef MISSIONPACK + w = CG_Text_Width(linebuffer, 0.5, 0); + h = CG_Text_Height(linebuffer, 0.5, 0); + x = (SCREEN_WIDTH - w) / 2; + CG_Text_Paint(x, y + h, 0.5, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); + y += h + 6; +#else + w = cg.centerPrintCharWidth * CG_DrawStrlen( linebuffer ); + + x = ( SCREEN_WIDTH - w ) / 2; + + CG_DrawStringExt( x, y, linebuffer, color, qfalse, qtrue, + cg.centerPrintCharWidth, (int)(cg.centerPrintCharWidth * 1.5), 0 ); + + y += cg.centerPrintCharWidth * 1.5; +#endif + while ( *start && ( *start != '\n' ) ) { + start++; + } + if ( !*start ) { + break; + } + start++; + } + + trap_R_SetColor( NULL ); +} + + + +/* +================================================================================ + +CROSSHAIR + +================================================================================ +*/ + + +/* +================= +CG_DrawCrosshair +================= +*/ +static void CG_DrawCrosshair(void) { + float w, h; + qhandle_t hShader; + float f; + float x, y; + int ca; + + if ( !cg_drawCrosshair.integer ) { + return; + } + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) { + return; + } + + if ( cg.renderingThirdPerson ) { + return; + } + + // set color based on health + if ( cg_crosshairHealth.integer ) { + vec4_t hcolor; + + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + } else { + trap_R_SetColor( NULL ); + } + + w = h = cg_crosshairSize.value; + + // pulse the size of the crosshair when picking up items + f = cg.time - cg.itemPickupBlendTime; + if ( f > 0 && f < ITEM_BLOB_TIME ) { + f /= ITEM_BLOB_TIME; + w *= ( 1 + f ); + h *= ( 1 + f ); + } + + x = cg_crosshairX.integer; + y = cg_crosshairY.integer; + CG_AdjustFrom640( &x, &y, &w, &h ); + + ca = cg_drawCrosshair.integer; + if (ca < 0) { + ca = 0; + } + hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ]; + + trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), + y + cg.refdef.y + 0.5 * (cg.refdef.height - h), + w, h, 0, 0, 1, 1, hShader ); +} + + + +/* +================= +CG_ScanForCrosshairEntity +================= +*/ +static void CG_ScanForCrosshairEntity( void ) { + trace_t trace; + vec3_t start, end; + int content; + + VectorCopy( cg.refdef.vieworg, start ); + VectorMA( start, 131072, cg.refdef.viewaxis[0], end ); + + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.entityNum >= MAX_CLIENTS ) { + return; + } + + // if the player is in fog, don't show it + content = trap_CM_PointContents( trace.endpos, 0 ); + if ( content & CONTENTS_FOG ) { + return; + } + + // if the player is invisible, don't show it + if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) ) { + return; + } + + // update the fade timer + cg.crosshairClientNum = trace.entityNum; + cg.crosshairClientTime = cg.time; +} + + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ +static void CG_DrawCrosshairNames( void ) { + float *color; + char *name; + float w; + + if ( !cg_drawCrosshair.integer ) { + return; + } + if ( !cg_drawCrosshairNames.integer ) { + return; + } + if ( cg.renderingThirdPerson ) { + return; + } + + // scan the known entities to see if the crosshair is sighted on one + CG_ScanForCrosshairEntity(); + + // draw the name of the player being looked at + color = CG_FadeColor( cg.crosshairClientTime, 1000 ); + if ( !color ) { + trap_R_SetColor( NULL ); + return; + } + + name = cgs.clientinfo[ cg.crosshairClientNum ].name; +#ifdef MISSIONPACK + color[3] *= 0.5f; + w = CG_Text_Width(name, 0.3f, 0); + CG_Text_Paint( 320 - w / 2, 190, 0.3f, color, name, 0, 0, ITEM_TEXTSTYLE_SHADOWED); +#else + w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w / 2, 170, name, color[3] * 0.5f ); +#endif + trap_R_SetColor( NULL ); +} + + +//============================================================================== + +/* +================= +CG_DrawSpectator +================= +*/ +static void CG_DrawSpectator(void) { + CG_DrawBigString(320 - 9 * 8, 440, "SPECTATOR", 1.0F); + if ( cgs.gametype == GT_TOURNAMENT ) { + CG_DrawBigString(320 - 15 * 8, 460, "waiting to play", 1.0F); + } + else if ( cgs.gametype >= GT_TEAM ) { + CG_DrawBigString(320 - 39 * 8, 460, "press ESC and use the JOIN menu to play", 1.0F); + } +} + +/* +================= +CG_DrawVote +================= +*/ +static void CG_DrawVote(void) { + char *s; + int sec; + + if ( !cgs.voteTime ) { + return; + } + + // play a talk beep whenever it is modified + if ( cgs.voteModified ) { + cgs.voteModified = qfalse; + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } +#ifdef MISSIONPACK + s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo); + CG_DrawSmallString( 0, 58, s, 1.0F ); + s = "or press ESC then click Vote"; + CG_DrawSmallString( 0, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F ); +#else + s = va("VOTE(%i):%s yes:%i no:%i", sec, cgs.voteString, cgs.voteYes, cgs.voteNo ); + CG_DrawSmallString( 0, 58, s, 1.0F ); +#endif +} + +/* +================= +CG_DrawTeamVote +================= +*/ +static void CG_DrawTeamVote(void) { + char *s; + int sec, cs_offset; + + if ( cgs.clientinfo->team == TEAM_RED ) + cs_offset = 0; + else if ( cgs.clientinfo->team == TEAM_BLUE ) + cs_offset = 1; + else + return; + + if ( !cgs.teamVoteTime[cs_offset] ) { + return; + } + + // play a talk beep whenever it is modified + if ( cgs.teamVoteModified[cs_offset] ) { + cgs.teamVoteModified[cs_offset] = qfalse; + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + } + + sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[cs_offset] ) ) / 1000; + if ( sec < 0 ) { + sec = 0; + } + s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], + cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); + CG_DrawSmallString( 0, 90, s, 1.0F ); +} + + +static qboolean CG_DrawScoreboard() { +//PKMOD - Ergodic 02/23/04 - enable HUD based scoreboard +//#ifdef MISSIONPACK + static qboolean firstTime = qtrue; + float fade, *fadeColor; + + if (menuScoreboard) { + menuScoreboard->window.flags &= ~WINDOW_FORCED; + } + if (cg_paused.integer) { + cg.deferredPlayerLoading = 0; + firstTime = qtrue; + return qfalse; + } + + // should never happen in Team Arena + if (cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + cg.deferredPlayerLoading = 0; + firstTime = qtrue; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg.warmup && !cg.showScores ) { + return qfalse; + } + + if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + fade = 1.0; + fadeColor = colorWhite; + } else { + fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); + if ( !fadeColor ) { + // next time scoreboard comes up, don't print killer + cg.deferredPlayerLoading = 0; + cg.killerName[0] = 0; + firstTime = qtrue; + return qfalse; + } + fade = *fadeColor; + } + + + if (menuScoreboard == NULL) { + if ( cgs.gametype >= GT_TEAM ) { + menuScoreboard = Menus_FindByName("teamscore_menu"); + } else { + menuScoreboard = Menus_FindByName("score_menu"); + } + } + + if (menuScoreboard) { + if (firstTime) { + CG_SetScoreSelection(menuScoreboard); + firstTime = qfalse; + } + Menu_Paint(menuScoreboard, qtrue); + } + + // load any models that have been deferred + if ( ++cg.deferredPlayerLoading > 10 ) { + CG_LoadDeferredPlayers(); + } + + return qtrue; +//PKMOD - Ergodic 02/23/04 - enable HUD based scoreboard +//#else +// return CG_DrawOldScoreboard(); +//#endif +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) { +// int key; +#ifdef MISSIONPACK + //if (cg_singlePlayer.integer) { + // CG_DrawCenterString(); + // return; + //} +#else + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + CG_DrawCenterString(); + return; + } +#endif + cg.scoreFadeTime = cg.time; + cg.scoreBoardShowing = CG_DrawScoreboard(); +} + +/* +================= +CG_DrawFollow +================= +*/ +static qboolean CG_DrawFollow( void ) { + float x; + vec4_t color; + const char *name; + + if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) { + return qfalse; + } + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + + CG_DrawBigString( 320 - 9 * 8, 24, "following", 1.0F ); + + name = cgs.clientinfo[ cg.snap->ps.clientNum ].name; + + x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( name ) ); + + CG_DrawStringExt( x, 40, name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + + return qtrue; +} + + + +/* +================= +CG_DrawAmmoWarning +================= +*/ +static void CG_DrawAmmoWarning( void ) { + const char *s; + int w; + + if ( cg_drawAmmoWarning.integer == 0 ) { + return; + } + + if ( !cg.lowAmmoWarning ) { + return; + } + + if ( cg.lowAmmoWarning == 2 ) { + + //PKMOD - Ergodic 01/04/04 - Don't display message in the hub + const char *info; + char *mapname; + info = CG_ConfigString( CS_SERVERINFO ); + mapname = Info_ValueForKey( info, "mapname" ); + if ( !strcmp( mapname, "hub_30" ) ) { + return; + } + if ( !strcmp( mapname, "hub" ) ) { + return; + } + + s = "OUT OF AMMO"; + } else { + s = "LOW AMMO WARNING"; + } + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString(320 - w / 2, 64, s, 1.0F); +} + + +#ifdef MISSIONPACK +/* +================= +CG_DrawProxWarning +================= +*/ +static void CG_DrawProxWarning( void ) { + char s [32]; + int w; + static int proxTime; + static int proxCounter; + static int proxTick; + + if( !(cg.snap->ps.eFlags & EF_TICKING ) ) { + proxTime = 0; + return; + } + + if (proxTime == 0) { + proxTime = cg.time + 5000; + proxCounter = 5; + proxTick = 0; + } + + if (cg.time > proxTime) { + proxTick = proxCounter--; + proxTime = cg.time + 1000; + } + + if (proxTick != 0) { + Com_sprintf(s, sizeof(s), "INTERNAL COMBUSTION IN: %i", proxTick); + } else { + Com_sprintf(s, sizeof(s), "YOU HAVE BEEN MINED"); + } + + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigStringColor( 320 - w / 2, 64 + BIGCHAR_HEIGHT, s, g_color_table[ColorIndex(COLOR_RED)] ); +} +#endif + + +/* +================= +CG_DrawWarmup +================= +*/ +static void CG_DrawWarmup( void ) { + int w; + int sec; + int i; + float scale; + clientInfo_t *ci1, *ci2; + int cw; + const char *s; + + sec = cg.warmup; + if ( !sec ) { + return; + } + + if ( sec < 0 ) { + s = "Waiting for players"; + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString(320 - w / 2, 24, s, 1.0F); + cg.warmupCount = 0; + return; + } + + if (cgs.gametype == GT_TOURNAMENT) { + // find the two active players + ci1 = NULL; + ci2 = NULL; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) { + if ( !ci1 ) { + ci1 = &cgs.clientinfo[i]; + } else { + ci2 = &cgs.clientinfo[i]; + } + } + } + + if ( ci1 && ci2 ) { + s = va( "%s vs %s", ci1->name, ci2->name ); +#ifdef MISSIONPACK + w = CG_Text_Width(s, 0.6f, 0); + CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +#else + w = CG_DrawStrlen( s ); + if ( w > 640 / GIANT_WIDTH ) { + cw = 640 / w; + } else { + cw = GIANT_WIDTH; + } + CG_DrawStringExt( 320 - w * cw/2, 20,s, colorWhite, + qfalse, qtrue, cw, (int)(cw * 1.5f), 0 ); +#endif + } + } else { + if ( cgs.gametype == GT_FFA ) { + s = "Free For All"; + } else if ( cgs.gametype == GT_TEAM ) { + s = "Team Deathmatch"; + } else if ( cgs.gametype == GT_CTF ) { + s = "Capture the Flag"; +#ifdef MISSIONPACK + } else if ( cgs.gametype == GT_1FCTF ) { + s = "One Flag CTF"; + } else if ( cgs.gametype == GT_OBELISK ) { + s = "Overload"; + } else if ( cgs.gametype == GT_HARVESTER ) { + s = "Harvester"; +#endif + } else { + s = ""; + } +#ifdef MISSIONPACK + w = CG_Text_Width(s, 0.6f, 0); + CG_Text_Paint(320 - w / 2, 90, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +#else + w = CG_DrawStrlen( s ); + if ( w > 640 / GIANT_WIDTH ) { + cw = 640 / w; + } else { + cw = GIANT_WIDTH; + } + CG_DrawStringExt( 320 - w * cw/2, 25,s, colorWhite, + qfalse, qtrue, cw, (int)(cw * 1.1f), 0 ); +#endif + } + + sec = ( sec - cg.time ) / 1000; + if ( sec < 0 ) { + cg.warmup = 0; + sec = 0; + } + s = va( "Starts in: %i", sec + 1 ); + if ( sec != cg.warmupCount ) { + cg.warmupCount = sec; + switch ( sec ) { + case 0: + trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); + break; + case 1: + trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); + break; + case 2: + trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); + break; + default: + break; + } + } + scale = 0.45f; + switch ( cg.warmupCount ) { + case 0: + cw = 28; + scale = 0.54f; + break; + case 1: + cw = 24; + scale = 0.51f; + break; + case 2: + cw = 20; + scale = 0.48f; + break; + default: + cw = 16; + scale = 0.45f; + break; + } + +#ifdef MISSIONPACK + w = CG_Text_Width(s, scale, 0); + CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE); +#else + w = CG_DrawStrlen( s ); + CG_DrawStringExt( 320 - w * cw/2, 70, s, colorWhite, + qfalse, qtrue, cw, (int)(cw * 1.5), 0 ); +#endif +} + +//================================================================================== +//PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD +//#ifdef MISSIONPACK +/* +================= +CG_DrawTimedMenus +================= +*/ +void CG_DrawTimedMenus() { + if (cg.voiceTime) { + int t = cg.time - cg.voiceTime; + if ( t > 2500 ) { + Menus_CloseByName("voiceMenu"); + trap_Cvar_Set("cl_conXOffset", "0"); + cg.voiceTime = 0; + } + } +} +//#endif //Ergodic 02/01/04 + +// +/* +================= +CG_Draw2D +================= +*/ +static void CG_Draw2D( void ) { +//PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD +//#ifdef MISSIONPACK + if (cgs.orderPending && cg.time > cgs.orderTime) { + CG_CheckOrderPending(); + } +//#endif //PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD + + // if we are taking a levelshot for the menu, don't draw anything + if ( cg.levelShot ) { + return; + } + + if ( cg_draw2D.integer == 0 ) { + return; + } + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + CG_DrawIntermission(); + return; + } + +/* + if (cg.cameraMode) { + return; + } +*/ + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + CG_DrawSpectator(); + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + } else { + // don't draw any status if dead or the scoreboard is being explicitly shown + if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) { + +//PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD +//#ifdef MISSIONPACK + if ( cg_drawStatus.integer ) { + Menu_PaintAll(); + CG_DrawTimedMenus(); + } +//#else //Ergodic 02/01/04 +// CG_DrawStatusBar(); +//#endif //Ergodic 02/01/04 + + CG_DrawAmmoWarning(); + +#ifdef MISSIONPACK + CG_DrawProxWarning(); +#endif + CG_DrawCrosshair(); + CG_DrawCrosshairNames(); + CG_DrawWeaponSelect(); + +//PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD +//#ifndef MISSIONPACK + CG_DrawHoldableItem(); +//#else //PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD + //CG_DrawPersistantPowerup(); +//#endif //PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD + CG_DrawReward(); + } + +//PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD +// if ( cgs.gametype >= GT_TEAM ) { +//#ifndef MISSIONPACK +// CG_DrawTeamInfo(); +//#endif +// } + } + + CG_DrawVote(); + CG_DrawTeamVote(); + + CG_DrawLagometer(); + +//PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD +//#ifdef MISSIONPACK + if (!cg_paused.integer) { + CG_DrawUpperRight(); + } +//#else //PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD +// CG_DrawUpperRight(); +//#endif //PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD + + +//PKMOD - Ergodic 02/01/04 - Enable Dynamic HUD +//#ifndef MISSIONPACK +// CG_DrawLowerRight(); +// CG_DrawLowerLeft(); +//#endif + + //PKMOD - Ergodic 10/13/00 - Draw hub info + CG_DrawHubOverlay(); + + if ( !CG_DrawFollow() ) { + CG_DrawWarmup(); + } + + // don't draw center string if scoreboard is up + cg.scoreBoardShowing = CG_DrawScoreboard(); + if ( !cg.scoreBoardShowing) { + CG_DrawCenterString(); + } +} + + +static void CG_DrawTourneyScoreboard() { +#ifdef MISSIONPACK +#else + CG_DrawOldTourneyScoreboard(); +#endif +} + +/* +===================== +CG_DrawActive + +Perform all drawing needed to completely fill the screen +===================== +*/ +void CG_DrawActive( stereoFrame_t stereoView ) { + float separation; + vec3_t baseOrg; + + // optionally draw the info screen instead + if ( !cg.snap ) { + CG_DrawInformation(); + return; + } + + // optionally draw the tournement scoreboard instead + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && + ( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) { + CG_DrawTourneyScoreboard(); + return; + } + + switch ( stereoView ) { + case STEREO_CENTER: + separation = 0; + break; + case STEREO_LEFT: + separation = -cg_stereoSeparation.value / 2; + break; + case STEREO_RIGHT: + separation = cg_stereoSeparation.value / 2; + break; + default: + separation = 0; + CG_Error( "CG_DrawActive: Undefined stereoView" ); + } + + + // clear around the rendered view if sized down + CG_TileClear(); + + // offset vieworg appropriately if we're doing stereo separation + VectorCopy( cg.refdef.vieworg, baseOrg ); + if ( separation != 0 ) { + VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); + } + + // draw 3D view + trap_R_RenderScene( &cg.refdef ); + + // restore original viewpoint if running stereo + if ( separation != 0 ) { + VectorCopy( baseOrg, cg.refdef.vieworg ); + } + + // draw status bar and other floating elements + CG_Draw2D(); +} + +//PKMOD - Ergodic 09/24/00 - Display Entity Voting Information +/* +================= +CG_DrawVoting +================= +*/ +static void CG_DrawVoting( void ) { + if ( cgs.gametype == GT_SINGLE_PLAYER ) { + CG_DrawCenterString(); + return; + } + + cg.scoreFadeTime = cg.time; + CG_DrawScoreboard(); +} + + diff --git a/quake3/source/code/cgame/cg_drawtools.c b/quake3/source/code/cgame/cg_drawtools.c new file mode 100644 index 0000000..b5bd26f --- /dev/null +++ b/quake3/source/code/cgame/cg_drawtools.c @@ -0,0 +1,944 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_drawtools.c -- helper functions called by cg_draw, cg_scoreboard, cg_info, etc +#include "cg_local.h" + +/* +================ +CG_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ) { +#if 0 + // adjust for wide screens + if ( cgs.glconfig.vidWidth * 480 > cgs.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cgs.glconfig.vidWidth - ( cgs.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + // scale for screen sizes + *x *= cgs.screenXScale; + *y *= cgs.screenYScale; + *w *= cgs.screenXScale; + *h *= cgs.screenYScale; +} + +/* +================ +CG_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_FillRect( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader ); + + trap_R_SetColor( NULL ); +} + +/* +================ +CG_DrawSides + +Coords are virtual 640x480 +================ +*/ +void CG_DrawSides(float x, float y, float w, float h, float size) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenXScale; + trap_R_DrawStretchPic( x, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x + w - size, y, size, h, 0, 0, 0, 0, cgs.media.whiteShader ); +} + +void CG_DrawTopBottom(float x, float y, float w, float h, float size) { + CG_AdjustFrom640( &x, &y, &w, &h ); + size *= cgs.screenYScale; + trap_R_DrawStretchPic( x, y, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); + trap_R_DrawStretchPic( x, y + h - size, w, size, 0, 0, 0, 0, cgs.media.whiteShader ); +} +/* +================ +UI_DrawRect + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ) { + trap_R_SetColor( color ); + + CG_DrawTopBottom(x, y, width, height, size); + CG_DrawSides(x, y, width, height, size); + + trap_R_SetColor( NULL ); +} + + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + + CG_AdjustFrom640( &x, &y, &width, &height ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + + +/* +=============== +CG_DrawChar + +Coordinates and size in 640*480 virtual screen size +=============== +*/ +void CG_DrawChar( int x, int y, int width, int height, int ch ) { + int row, col; + float frow, fcol; + float size; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + ax = x; + ay = y; + aw = width; + ah = height; + CG_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + trap_R_DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size, + cgs.media.charsetShader ); +} + + +/* +================== +CG_DrawStringExt + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) { + vec4_t color; + const char *s; + int xx; + int cnt; + + if (maxChars <= 0) + maxChars = 32767; // do them all! + + // draw the drop shadow + if (shadow) { + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + trap_R_SetColor( color ); + s = string; + xx = x; + cnt = 0; + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + CG_DrawChar( xx + 2, y + 2, charWidth, charHeight, *s ); + cnt++; + xx += charWidth; + s++; + } + } + + // draw the colored text + s = string; + xx = x; + cnt = 0; + trap_R_SetColor( setColor ); + while ( *s && cnt < maxChars) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + trap_R_SetColor( color ); + } + s += 2; + continue; + } + CG_DrawChar( xx, y, charWidth, charHeight, *s ); + xx += charWidth; + cnt++; + s++; + } + trap_R_SetColor( NULL ); +} + +void CG_DrawBigString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt( x, y, s, color, qfalse, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt( x, y, s, color, qtrue, qtrue, BIGCHAR_WIDTH, BIGCHAR_HEIGHT, 0 ); +} + +void CG_DrawSmallString( int x, int y, const char *s, float alpha ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + CG_DrawStringExt( x, y, s, color, qfalse, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ) { + CG_DrawStringExt( x, y, s, color, qtrue, qfalse, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); +} + +/* +================= +CG_DrawStrlen + +Returns character count, skiping color escape codes +================= +*/ +int CG_DrawStrlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +============= +CG_TileClearBox + +This repeats a 64*64 tile graphic to fill the screen around a sized down +refresh window. +============= +*/ +static void CG_TileClearBox( int x, int y, int w, int h, qhandle_t hShader ) { + float s1, t1, s2, t2; + + s1 = x/64.0; + t1 = y/64.0; + s2 = (x+w)/64.0; + t2 = (y+h)/64.0; + trap_R_DrawStretchPic( x, y, w, h, s1, t1, s2, t2, hShader ); +} + + + +/* +============== +CG_TileClear + +Clear around a sized down screen +============== +*/ +void CG_TileClear( void ) { + int top, bottom, left, right; + int w, h; + + w = cgs.glconfig.vidWidth; + h = cgs.glconfig.vidHeight; + + if ( cg.refdef.x == 0 && cg.refdef.y == 0 && + cg.refdef.width == w && cg.refdef.height == h ) { + return; // full screen rendering + } + + top = cg.refdef.y; + bottom = top + cg.refdef.height-1; + left = cg.refdef.x; + right = left + cg.refdef.width-1; + + // clear above view screen + CG_TileClearBox( 0, 0, w, top, cgs.media.backTileShader ); + + // clear below view screen + CG_TileClearBox( 0, bottom, w, h - bottom, cgs.media.backTileShader ); + + // clear left of view screen + CG_TileClearBox( 0, top, left, bottom - top + 1, cgs.media.backTileShader ); + + // clear right of view screen + CG_TileClearBox( right, top, w - right, bottom - top + 1, cgs.media.backTileShader ); +} + + + +/* +================ +CG_FadeColor +================ +*/ +float *CG_FadeColor( int startMsec, int totalMsec ) { + static vec4_t color; + int t; + + if ( startMsec == 0 ) { + return NULL; + } + + t = cg.time - startMsec; + + if ( t >= totalMsec ) { + return NULL; + } + + // fade out + if ( totalMsec - t < FADE_TIME ) { + color[3] = ( totalMsec - t ) * 1.0/FADE_TIME; + } else { + color[3] = 1.0; + } + color[0] = color[1] = color[2] = 1; + + return color; +} + + +/* +================ +CG_TeamColor +================ +*/ +float *CG_TeamColor( int team ) { + static vec4_t red = {1, 0.2f, 0.2f, 1}; + static vec4_t blue = {0.2f, 0.2f, 1, 1}; + static vec4_t other = {1, 1, 1, 1}; + static vec4_t spectator = {0.7f, 0.7f, 0.7f, 1}; + + switch ( team ) { + case TEAM_RED: + return red; + case TEAM_BLUE: + return blue; + case TEAM_SPECTATOR: + return spectator; + default: + return other; + } +} + + + +/* +================= +CG_GetColorForHealth +================= +*/ +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ) { + int count; + int max; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + if ( health <= 0 ) { + VectorClear( hcolor ); // black + hcolor[3] = 1; + return; + } + count = armor; + max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + if ( max < count ) { + count = max; + } + health += count; + + // set the color based on health + hcolor[0] = 1.0; + hcolor[3] = 1.0; + if ( health >= 100 ) { + hcolor[2] = 1.0; + } else if ( health < 66 ) { + hcolor[2] = 0; + } else { + hcolor[2] = ( health - 66 ) / 33.0; + } + + if ( health > 60 ) { + hcolor[1] = 1.0; + } else if ( health < 30 ) { + hcolor[1] = 0; + } else { + hcolor[1] = ( health - 30 ) / 30.0; + } +} + +/* +================= +CG_ColorForHealth +================= +*/ +void CG_ColorForHealth( vec4_t hcolor ) { + + CG_GetColorForHealth( cg.snap->ps.stats[STAT_HEALTH], + cg.snap->ps.stats[STAT_ARMOR], hcolor ); +} + + + + +// bk001205 - code below duplicated in q3_ui/ui-atoms.c +// bk001205 - FIXME: does this belong in ui_shared.c? +// bk001205 - FIXME: HARD_LINKED flags not visible here +#ifndef Q3_STATIC // bk001205 - q_shared defines not visible here +/* +================= +UI_DrawProportionalString2 +================= +*/ +static int propMap[128][3] = { +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, +{0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + +{0, 0, PROP_SPACE_WIDTH}, // SPACE +/* PKMOD - Ergodic 02/26/01 - original font locations +{11, 122, 7}, // ! +{154, 181, 14}, // " +{55, 122, 17}, // # +{79, 122, 18}, // $ +{101, 122, 23}, // % +{153, 122, 18}, // & +{9, 93, 7}, // ' +{207, 122, 8}, // ( +{230, 122, 9}, // ) +{177, 122, 18}, // * +{30, 152, 18}, // + +{85, 181, 7}, // , +{34, 93, 11}, // - +{110, 181, 6}, // . +{130, 152, 14}, // / + +{22, 64, 17}, // 0 +{41, 64, 12}, // 1 +{58, 64, 17}, // 2 +{78, 64, 18}, // 3 +{98, 64, 19}, // 4 +{120, 64, 18}, // 5 +{141, 64, 18}, // 6 +{204, 64, 16}, // 7 +{162, 64, 17}, // 8 +{182, 64, 18}, // 9 +{59, 181, 7}, // : +{35,181, 7}, // ; +{203, 152, 14}, // < +{56, 93, 14}, // = +{228, 152, 14}, // > +{177, 181, 18}, // ? + +{28, 122, 22}, // @ +{5, 4, 18}, // A +{27, 4, 18}, // B +{48, 4, 18}, // C +{69, 4, 17}, // D +{90, 4, 13}, // E +{106, 4, 13}, // F +{121, 4, 18}, // G +{143, 4, 17}, // H +{164, 4, 8}, // I +{175, 4, 16}, // J +{195, 4, 18}, // K +{216, 4, 12}, // L +{230, 4, 23}, // M +{6, 34, 18}, // N +{27, 34, 18}, // O + +{48, 34, 18}, // P +{68, 34, 18}, // Q +{90, 34, 17}, // R +{110, 34, 18}, // S +{130, 34, 14}, // T +{146, 34, 18}, // U +{166, 34, 19}, // V +{185, 34, 29}, // W +{215, 34, 18}, // X +{234, 34, 18}, // Y +{5, 64, 14}, // Z +{60, 152, 7}, // [ +{106, 151, 13}, // '\' +{83, 152, 7}, // ] +{128, 122, 17}, // ^ +{4, 152, 21}, // _ + +{134, 181, 5}, // ' +{5, 4, 18}, // A +{27, 4, 18}, // B +{48, 4, 18}, // C +{69, 4, 17}, // D +{90, 4, 13}, // E +{106, 4, 13}, // F +{121, 4, 18}, // G +{143, 4, 17}, // H +{164, 4, 8}, // I +{175, 4, 16}, // J +{195, 4, 18}, // K +{216, 4, 12}, // L +{230, 4, 23}, // M +{6, 34, 18}, // N +{27, 34, 18}, // O + +{48, 34, 18}, // P +{68, 34, 18}, // Q +{90, 34, 17}, // R +{110, 34, 18}, // S +{130, 34, 14}, // T +{146, 34, 18}, // U +{166, 34, 19}, // V +{185, 34, 29}, // W +{215, 34, 18}, // X +{234, 34, 18}, // Y +{5, 64, 14}, // Z +{153, 152, 13}, // { +{11, 181, 5}, // | +{180, 152, 13}, // } +{79, 93, 17}, // ~ +{0, 0, -1} // DEL +*/ +{35, 134, 6}, // ! (ck) +{193, 92, 10}, // " (ck) +{47, 135, 21}, // # (ck) +{27, 221, 10}, // $ (ck) +{90, 134, 29}, // % (ck) +{144, 135, 19}, // & (ck) +{80, 134, 4}, // ' (ck) +{195, 134, 8}, // ( (ck) +{182, 134, 8}, // ) (ck) +{167, 129, 11}, // * (ck) +{48, 177, 20}, // + (ck) +{156, 181, 6}, // , (ck) +{226, 93, 6}, // - (ck) +{165, 176, 6}, // . (ck) //06/11/01 was {165, 177, 6} +{117, 176, 11}, // / (ck) +{10, 92, 19}, // 0 +{34, 92, 4}, // 1 +{43, 92, 14}, // 2 +{61, 92, 13}, // 3 +{80, 92, 16}, // 4 +{97, 92, 14}, // 5 (bump up 1 unit) +{113, 92, 14}, // 6 +{132, 92, 12}, // 7 +{148, 92, 14}, // 8 +{168, 92, 14}, // 9 (bump up 1 unit) +{147, 181, 6}, // : (ck) +{138, 181, 6}, // ; (ck) +{117, 219, 18}, // < (ck) +{209, 134, 19}, // = (ck) +{140, 219, 18}, // > (ck) +{173, 177, 12}, // ? (ck) +{48, 224, 27}, // @ (ck) +{9, 8, 18}, // A +{34, 8, 12}, // B +{52, 8, 18}, // C +{75, 8, 16}, // D +{94, 8, 11}, // E +{108, 8, 11}, // F +{122, 8, 17}, // G +{143, 8, 13}, // H +{161, 8, 5}, // I +{169, 8, 13}, // J +{187, 8, 14}, // K +{206, 8, 12}, // L +{221, 8, 24}, // M +{11, 50, 15}, // N +{29, 50, 21}, // O +{57, 50, 13}, // P +{75, 50, 22}, // Q +{101, 50, 12}, // R (bump up 1 unit) +{119, 50, 13}, // S +{133, 50, 16}, // T +{151, 50, 14}, // U +{171, 50, 16}, // V +{190, 176, 21}, // W +{192, 50, 13}, // X +{209, 50, 17}, // Y +{229, 50, 11}, // Z +{82, 177, 7}, // [ (ck) +{98, 176, 10}, // '\' (ck) +{71, 177, 7}, // ] (ck) +{124, 134, 15}, // ^ (ck) +{27, 181, 19}, // _ (ck) +{80, 134, 4}, // ` (ck) + +{9, 8, 18}, // A +{34, 8, 12}, // B +{52, 8, 18}, // C +{75, 8, 16}, // D +{94, 8, 11}, // E +{108, 8, 11}, // F +{122, 8, 17}, // G +{143, 8, 13}, // H +{161, 8, 5}, // I +{169, 8, 13}, // J +{187, 8, 14}, // K +{206, 8, 12}, // L +{221, 8, 24}, // M +{11, 50, 15}, // N +{29, 50, 21}, // O +{57, 50, 13}, // P +{75, 50, 22}, // Q +{101, 50, 12}, // R (bump up 1 unit) +{119, 50, 13}, // S +{133, 50, 16}, // T +{151, 50, 14}, // U +{171, 50, 16}, // V +{190, 176, 21}, // W +{192, 50, 13}, // X +{209, 50, 17}, // Y +{229, 50, 11}, // Z +{84, 224, 8}, // { (ck) +{17, 221, 2}, // | (ck) +{102, 224, 8}, // } (ck) +{12, 134, 19}, // ~ (ck) +{0, 0, -1} // DEL +}; + +static int propMapB[26][3] = { +/* PKMOD - Ergodic 02/26/01 - original font locations +{11, 12, 33}, +{49, 12, 31}, +{85, 12, 31}, +{120, 12, 30}, +{156, 12, 21}, +{183, 12, 21}, +{207, 12, 32}, + +{13, 55, 30}, +{49, 55, 13}, +{66, 55, 29}, +{101, 55, 31}, +{135, 55, 21}, +{158, 55, 40}, +{204, 55, 32}, + +{12, 97, 31}, +{48, 97, 31}, +{82, 97, 30}, +{118, 97, 30}, +{153, 97, 30}, +{185, 97, 25}, +{213, 97, 30}, + +{11, 139, 32}, +{42, 139, 51}, +{93, 139, 32}, +{126, 139, 31}, +{158, 139, 25}, +*/ +{25, 25, 31}, //A +{64, 25, 22}, //B +{93, 25, 30}, //C +{131, 25, 27}, //D +{164, 25, 18}, //E +{187, 25, 19}, //F + +{27, 79, 28}, //G +{60, 79, 23}, //H +{91, 79, 8}, //I +{102, 79, 22}, //J +{133, 79, 23}, //K +{164, 79, 20}, //L +{197, 79, 40}, //M + +{28, 133, 26}, //N +{59, 133, 36}, //O +{105, 133, 22}, //P +{134, 133, 37}, //Q +{177, 133, 21}, //R +{206, 133, 22}, //S + +{25, 187, 28}, //T +{56, 187, 23}, //U +{87, 187, 27}, //V +{121, 187, 35}, //W +{163, 187, 22}, //X +{192, 187, 28}, //Y +{225, 187, 19}, //Z + +}; + +#define PROPB_GAP_WIDTH 4 +#define PROPB_SPACE_WIDTH 12 +#define PROPB_HEIGHT 36 + +/* +================= +UI_DrawBannerString +================= +*/ +static void UI_DrawBannerString2( int x, int y, const char* str, vec4_t color ) +{ + const char* s; + unsigned char ch; // bk001204 : array subscript + float ax; + float ay; + float aw; + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + + // draw the colored text + trap_R_SetColor( color ); + + ax = x * cgs.screenXScale + cgs.screenXBias; + ay = y * cgs.screenXScale; + + s = str; + while ( *s ) + { + ch = *s & 127; + if ( ch == ' ' ) { + ax += ((float)PROPB_SPACE_WIDTH + (float)PROPB_GAP_WIDTH)* cgs.screenXScale; + } + else if ( ch >= 'A' && ch <= 'Z' ) { + ch -= 'A'; + fcol = (float)propMapB[ch][0] / 256.0f; + frow = (float)propMapB[ch][1] / 256.0f; + fwidth = (float)propMapB[ch][2] / 256.0f; + fheight = (float)PROPB_HEIGHT / 256.0f; + aw = (float)propMapB[ch][2] * cgs.screenXScale; + ah = (float)PROPB_HEIGHT * cgs.screenXScale; + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, cgs.media.charsetPropB ); + ax += (aw + (float)PROPB_GAP_WIDTH * cgs.screenXScale); + } + s++; + } + + trap_R_SetColor( NULL ); +} + +void UI_DrawBannerString( int x, int y, const char* str, int style, vec4_t color ) { + const char * s; + int ch; + int width; + vec4_t drawcolor; + + // find the width of the drawn text + s = str; + width = 0; + while ( *s ) { + ch = *s; + if ( ch == ' ' ) { + width += PROPB_SPACE_WIDTH; + } + else if ( ch >= 'A' && ch <= 'Z' ) { + width += propMapB[ch - 'A'][2] + PROPB_GAP_WIDTH; + } + s++; + } + width -= PROPB_GAP_WIDTH; + + switch( style & UI_FORMATMASK ) { + case UI_CENTER: + x -= width / 2; + break; + + case UI_RIGHT: + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + if ( style & UI_DROPSHADOW ) { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + UI_DrawBannerString2( x+2, y+2, str, drawcolor ); + } + + UI_DrawBannerString2( x, y, str, color ); +} + + +int UI_ProportionalStringWidth( const char* str ) { + const char * s; + int ch; + int charWidth; + int width; + + s = str; + width = 0; + while ( *s ) { + ch = *s & 127; + charWidth = propMap[ch][2]; + if ( charWidth != -1 ) { + width += charWidth; + width += PROP_GAP_WIDTH; + } + s++; + } + + width -= PROP_GAP_WIDTH; + return width; +} + +static void UI_DrawProportionalString2( int x, int y, const char* str, vec4_t color, float sizeScale, qhandle_t charset ) +{ + const char* s; + unsigned char ch; // bk001204 - unsigned + float ax; + float ay; + float aw; + float ah; + float frow; + float fcol; + float fwidth; + float fheight; + + // draw the colored text + trap_R_SetColor( color ); + + ax = x * cgs.screenXScale + cgs.screenXBias; + ay = y * cgs.screenXScale; + + s = str; + while ( *s ) + { + ch = *s & 127; + if ( ch == ' ' ) { + aw = (float)PROP_SPACE_WIDTH * cgs.screenXScale * sizeScale; + } else if ( propMap[ch][2] != -1 ) { + fcol = (float)propMap[ch][0] / 256.0f; + frow = (float)propMap[ch][1] / 256.0f; + fwidth = (float)propMap[ch][2] / 256.0f; + fheight = (float)PROP_HEIGHT / 256.0f; + aw = (float)propMap[ch][2] * cgs.screenXScale * sizeScale; + ah = (float)PROP_HEIGHT * cgs.screenXScale * sizeScale; + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol+fwidth, frow+fheight, charset ); + } else { + aw = 0; + } + + ax += (aw + (float)PROP_GAP_WIDTH * cgs.screenXScale * sizeScale); + s++; + } + + trap_R_SetColor( NULL ); +} + +/* +================= +UI_ProportionalSizeScale +================= +*/ +float UI_ProportionalSizeScale( int style ) { +//PKMOD - Ergodic 11/27/01 - add new font sizes "smaller" and "micro" + if( style & UI_SMALLFONT ) { + return 0.75; + } + + if( style & UI_PKA_SMALLERFONT ) { + return (float) PROP_PKA_SMALLER_SIZE_SCALE; + } + + if( style & UI_PKA_MICROFONT ) { + return (float) PROP_PKA_MICRO_SIZE_SCALE; + } + + return 1.00; +} + + +/* +================= +UI_DrawProportionalString +================= +*/ +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) { + vec4_t drawcolor; + int width; + float sizeScale; + + sizeScale = UI_ProportionalSizeScale( style ); + + switch( style & UI_FORMATMASK ) { + case UI_CENTER: + width = UI_ProportionalStringWidth( str ) * sizeScale; + x -= width / 2; + break; + + case UI_RIGHT: + width = UI_ProportionalStringWidth( str ) * sizeScale; + x -= width; + break; + + case UI_LEFT: + default: + break; + } + + if ( style & UI_DROPSHADOW ) { + drawcolor[0] = drawcolor[1] = drawcolor[2] = 0; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x+2, y+2, str, drawcolor, sizeScale, cgs.media.charsetProp ); + } + + if ( style & UI_INVERSE ) { + drawcolor[0] = color[0] * 0.8; + drawcolor[1] = color[1] * 0.8; + drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetProp ); + return; + } + + if ( style & UI_PULSE ) { + drawcolor[0] = color[0] * 0.8; + drawcolor[1] = color[1] * 0.8; + drawcolor[2] = color[2] * 0.8; + drawcolor[3] = color[3]; + UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); + + drawcolor[0] = color[0]; + drawcolor[1] = color[1]; + drawcolor[2] = color[2]; + drawcolor[3] = 0.5 + 0.5 * sin( cg.time / PULSE_DIVISOR ); + UI_DrawProportionalString2( x, y, str, drawcolor, sizeScale, cgs.media.charsetPropGlow ); + return; + } + + UI_DrawProportionalString2( x, y, str, color, sizeScale, cgs.media.charsetProp ); +} +#endif // Q3STATIC diff --git a/quake3/source/code/cgame/cg_effects.c b/quake3/source/code/cgame/cg_effects.c new file mode 100644 index 0000000..9883500 --- /dev/null +++ b/quake3/source/code/cgame/cg_effects.c @@ -0,0 +1,1401 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_effects.c -- these functions generate localentities, usually as a result +// of event processing + +#include "cg_local.h" + + +/* +================== +CG_BubbleTrail + +Bullets shot underwater +================== +*/ +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ) { + vec3_t move; + vec3_t vec; + float len; + int i; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + + // advance a random amount first + i = rand() % (int)spacing; + VectorMA( move, i, vec, move ); + + VectorScale (vec, spacing, vec); + + for ( ; i < len; i += spacing ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + 1000 + random() * 250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + re->shaderTime = cg.time / 1000.0f; + + re->reType = RT_SPRITE; + re->rotation = 0; + re->radius = 3; + re->customShader = cgs.media.waterBubbleShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + + le->color[3] = 1.0; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + VectorCopy( move, le->pos.trBase ); + le->pos.trDelta[0] = crandom()*5; + le->pos.trDelta[1] = crandom()*5; + le->pos.trDelta[2] = crandom()*5 + 6; + + VectorAdd (move, vec, move); + } +} + +/* +===================== +CG_SmokePuff + +Adds a smoke puff or blood trail localEntity. +===================== +*/ +localEntity_t *CG_SmokePuff( const vec3_t p, const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ) { + static int seed = 0x92; + localEntity_t *le; + refEntity_t *re; +// int fadeInTime = startTime + duration / 2; + + le = CG_AllocLocalEntity(); + le->leFlags = leFlags; + le->radius = radius; + + re = &le->refEntity; + re->rotation = Q_random( &seed ) * 360; + re->radius = radius; + re->shaderTime = startTime / 1000.0f; + + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = startTime; + le->fadeInTime = fadeInTime; + le->endTime = startTime + duration; + if ( fadeInTime > startTime ) { + le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); + } + else { + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + } + le->color[0] = r; + le->color[1] = g; + le->color[2] = b; + le->color[3] = a; + + + le->pos.trType = TR_LINEAR; + le->pos.trTime = startTime; + VectorCopy( vel, le->pos.trDelta ); + VectorCopy( p, le->pos.trBase ); + + VectorCopy( p, re->origin ); + re->customShader = hShader; + + // rage pro can't alpha fade, so use a different shader + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + re->customShader = cgs.media.smokePuffRageProShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + } else { + re->shaderRGBA[0] = le->color[0] * 0xff; + re->shaderRGBA[1] = le->color[1] * 0xff; + re->shaderRGBA[2] = le->color[2] * 0xff; + re->shaderRGBA[3] = 0xff; + } + + re->reType = RT_SPRITE; + re->radius = le->radius; + + return le; +} + +/* +================== +CG_SpawnEffect + +Player teleporting in or out +================== +*/ +void CG_SpawnEffect( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + 500; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + +#ifndef MISSIONPACK + re->customShader = cgs.media.teleportEffectShader; +#endif + re->hModel = cgs.media.teleportEffectModel; + AxisClear( re->axis ); + + VectorCopy( org, re->origin ); +#ifdef MISSIONPACK + re->origin[2] += 16; +#else + re->origin[2] -= 24; +#endif +} + +/* +================== +CG_PersentrySpawnEffect + +PKMOD - Ergodic 06/08/02 - add personal sentry teleport model +================== +*/ +void CG_PersentrySpawnEffect( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + 500; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.persentry_teleportEffectModel; + AxisClear( re->axis ); + + VectorCopy( org, re->origin ); + re->origin[2] -= 24; +} + + +#ifdef MISSIONPACK +/* +=============== +CG_LightningBoltBeam +=============== +*/ +void CG_LightningBoltBeam( vec3_t start, vec3_t end ) { + localEntity_t *le; + refEntity_t *beam; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_SHOWREFENTITY; + le->startTime = cg.time; + le->endTime = cg.time + 50; + + beam = &le->refEntity; + + VectorCopy( start, beam->origin ); + // this is the end point + VectorCopy( end, beam->oldorigin ); + + beam->reType = RT_LIGHTNING; + beam->customShader = cgs.media.lightningShader; +} + +/* +================== +CG_KamikazeEffect +================== +*/ +void CG_KamikazeEffect( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_KAMIKAZE; + le->startTime = cg.time; + le->endTime = cg.time + 3000;//2250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + VectorClear(le->angles.trBase); + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.kamikazeEffectModel; + + VectorCopy( org, re->origin ); + +} + +/* +================== +CG_ObeliskExplode +================== +*/ +void CG_ObeliskExplode( vec3_t org, int entityNum ) { + localEntity_t *le; + vec3_t origin; + + // create an explosion + VectorCopy( org, origin ); + origin[2] += 64; + le = CG_MakeExplosion( origin, vec3_origin, + cgs.media.dishFlashModel, + cgs.media.rocketExplosionShader, + 600, qtrue ); + le->light = 300; + le->lightColor[0] = 1; + le->lightColor[1] = 0.75; + le->lightColor[2] = 0.0; +} + +/* +================== +CG_ObeliskPain +================== +*/ +void CG_ObeliskPain( vec3_t org ) { + float r; + sfxHandle_t sfx; + + // hit sound + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.obeliskHitSound1; + } else if ( r == 2 ) { + sfx = cgs.media.obeliskHitSound2; + } else { + sfx = cgs.media.obeliskHitSound3; + } + trap_S_StartSound ( org, ENTITYNUM_NONE, CHAN_BODY, sfx ); +} + + +/* +================== +CG_InvulnerabilityImpact +================== +*/ +void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ) { + localEntity_t *le; + refEntity_t *re; + int r; + sfxHandle_t sfx; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_INVULIMPACT; + le->startTime = cg.time; + le->endTime = cg.time + 1000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.invulnerabilityImpactModel; + + VectorCopy( org, re->origin ); + AnglesToAxis( angles, re->axis ); + + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.invulnerabilityImpactSound1; + } else if ( r == 2 ) { + sfx = cgs.media.invulnerabilityImpactSound2; + } else { + sfx = cgs.media.invulnerabilityImpactSound3; + } + trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, sfx ); +} + +/* +================== +CG_InvulnerabilityJuiced +================== +*/ +void CG_InvulnerabilityJuiced( vec3_t org ) { + localEntity_t *le; + refEntity_t *re; + vec3_t angles; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_INVULJUICED; + le->startTime = cg.time; + le->endTime = cg.time + 10000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + + re = &le->refEntity; + + re->reType = RT_MODEL; + re->shaderTime = cg.time / 1000.0f; + + re->hModel = cgs.media.invulnerabilityJuicedModel; + + VectorCopy( org, re->origin ); + VectorClear(angles); + AnglesToAxis( angles, re->axis ); + + trap_S_StartSound (org, ENTITYNUM_NONE, CHAN_BODY, cgs.media.invulnerabilityJuicedSound ); +} + +#endif + +/* +================== +CG_ScorePlum +================== +*/ +void CG_ScorePlum( int client, vec3_t org, int score ) { + localEntity_t *le; + refEntity_t *re; + vec3_t angles; + static vec3_t lastPos; + + // only visualize for the client that scored + if (client != cg.predictedPlayerState.clientNum || cg_scorePlum.integer == 0) { + return; + } + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->leType = LE_SCOREPLUM; + le->startTime = cg.time; + le->endTime = cg.time + 4000; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + + le->color[0] = le->color[1] = le->color[2] = le->color[3] = 1.0; + le->radius = score; + + VectorCopy( org, le->pos.trBase ); + if (org[2] >= lastPos[2] - 20 && org[2] <= lastPos[2] + 20) { + le->pos.trBase[2] -= 20; + } + + //CG_Printf( "Plum origin %i %i %i -- %i\n", (int)org[0], (int)org[1], (int)org[2], (int)Distance(org, lastPos)); + VectorCopy(org, lastPos); + + + re = &le->refEntity; + + re->reType = RT_SPRITE; + re->radius = 16; + + VectorClear(angles); + AnglesToAxis( angles, re->axis ); +} + + +/* +==================== +CG_MakeExplosion +==================== +*/ +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, + int msec, qboolean isSprite ) { + float ang; + localEntity_t *ex; + int offset; + vec3_t tmpVec, newOrigin; + + if ( msec <= 0 ) { + CG_Error( "CG_MakeExplosion: msec = %i", msec ); + } + + // skew the time a bit so they aren't all in sync + offset = rand() & 63; + + ex = CG_AllocLocalEntity(); + if ( isSprite ) { + ex->leType = LE_SPRITE_EXPLOSION; + + // randomly rotate sprite orientation + ex->refEntity.rotation = rand() % 360; + VectorScale( dir, 16, tmpVec ); + VectorAdd( tmpVec, origin, newOrigin ); + } else { + ex->leType = LE_EXPLOSION; + VectorCopy( origin, newOrigin ); + + // set axis with random rotate + if ( !dir ) { + AxisClear( ex->refEntity.axis ); + } else { + ang = rand() % 360; + VectorCopy( dir, ex->refEntity.axis[0] ); + RotateAroundDirection( ex->refEntity.axis, ang ); + } + } + + ex->startTime = cg.time - offset; + ex->endTime = ex->startTime + msec; + + // bias the time so all shader effects start correctly + ex->refEntity.shaderTime = ex->startTime / 1000.0f; + + ex->refEntity.hModel = hModel; + ex->refEntity.customShader = shader; + + // set origin + VectorCopy( newOrigin, ex->refEntity.origin ); + VectorCopy( newOrigin, ex->refEntity.oldorigin ); + + ex->color[0] = ex->color[1] = ex->color[2] = 1.0; + + return ex; +} + + +/* +================= +CG_Bleed + +This is the spurt of blood when a character gets hit +================= +*/ +void CG_Bleed( vec3_t origin, int entityNum ) { + localEntity_t *ex; + + if ( !cg_blood.integer ) { + return; + } + + ex = CG_AllocLocalEntity(); + ex->leType = LE_EXPLOSION; + + ex->startTime = cg.time; + ex->endTime = ex->startTime + 500; + + VectorCopy ( origin, ex->refEntity.origin); + ex->refEntity.reType = RT_SPRITE; + ex->refEntity.rotation = rand() % 360; + ex->refEntity.radius = 24; + + ex->refEntity.customShader = cgs.media.bloodExplosionShader; + + // don't show player's own blood in view + if ( entityNum == cg.snap->ps.clientNum ) { + ex->refEntity.renderfx |= RF_THIRD_PERSON; + } +} + + + +/* +================== +CG_LaunchGib +================== +*/ +void CG_LaunchGib( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 5000 + random() * 3000; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + le->bounceFactor = 0.6f; + + le->leBounceSoundType = LEBS_BLOOD; + le->leMarkType = LEMT_BLOOD; +} + +/* +================== +//PKMOD - Ergodic 12/14/00 - make gib less intensive +CG_LaunchGibAlternate +================== +*/ +void CG_LaunchGibAlternate( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 3000 + random() * 2500; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + //PKMOD - Ergodic 12/16/00 - use "f" to force float + le->bounceFactor = 0.3f; + + if ( random() > 0.4 ) { + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; + } + else { + le->leBounceSoundType = LEBS_BLOOD; + le->leMarkType = LEMT_BLOOD; + } +} + +/* +=================== +CG_GibPlayer + +Generated a bunch of gibs launching out from the bodies location +=================== +*/ +#define GIB_VELOCITY 250 +#define GIB_JUMP 250 +void CG_GibPlayer( vec3_t playerOrigin ) { + vec3_t origin, velocity; + + if ( !cg_blood.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + if ( rand() & 1 ) { + CG_LaunchGib( origin, velocity, cgs.media.gibSkull ); + } else { + CG_LaunchGib( origin, velocity, cgs.media.gibBrain ); + } + + // allow gibs to be turned off for speed + if ( !cg_gibs.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibAbdomen ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibArm ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibChest ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibFist ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibFoot ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibForearm ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibIntestine ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.gibLeg ); +} + +/* +================== +CG_LaunchGib +================== +*/ +void CG_LaunchExplode( vec3_t origin, vec3_t velocity, qhandle_t hModel ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + 10000 + random() * 6000; + + VectorCopy( origin, re->origin ); + AxisCopy( axisDefault, re->axis ); + re->hModel = hModel; + + le->pos.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + le->pos.trTime = cg.time; + + le->bounceFactor = 0.1f; + + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; +} + +#define EXP_VELOCITY 100 +#define EXP_JUMP 150 +/* +=================== +CG_GibPlayer + +Generated a bunch of gibs launching out from the bodies location +=================== +*/ +void CG_BigExplode( vec3_t playerOrigin ) { + vec3_t origin, velocity; + + if ( !cg_blood.integer ) { + return; + } + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY; + velocity[1] = crandom()*EXP_VELOCITY; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY; + velocity[1] = crandom()*EXP_VELOCITY; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY*1.5; + velocity[1] = crandom()*EXP_VELOCITY*1.5; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY*2.0; + velocity[1] = crandom()*EXP_VELOCITY*2.0; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); + + VectorCopy( playerOrigin, origin ); + velocity[0] = crandom()*EXP_VELOCITY*2.5; + velocity[1] = crandom()*EXP_VELOCITY*2.5; + velocity[2] = EXP_JUMP + crandom()*EXP_VELOCITY; + CG_LaunchExplode( origin, velocity, cgs.media.smoke2 ); +} + +//PKMOD - Ergodic 12/14/00 - add autosentry gib models +#define AUTOSENTRY_SHOWGIB 0.7 //show gib 70% of the time +//PKMOD - Ergodic 11/22/00 - autosentry death routine +//PKMOD - Ergodic 12/14/00 - add autosentry gib models +void CG_AutoSentryDie( vec3_t AutoSentryOrigin ) { + vec3_t origin, velocity; + + //PKMOD Ergodic debug 12/14/00 inactive +// Com_Printf("CG_AutoSentryDie\n" ); + + + VectorCopy( AutoSentryOrigin, origin ); + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib1 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib2 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib3 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib4 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib5 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib6 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib7 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib8 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib9 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib10 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib11 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib12 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.pkasentry_gib13 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib14 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib15 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib16 ); + } + + if ( random() < AUTOSENTRY_SHOWGIB ) { + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGibAlternate( origin, velocity, cgs.media.pkasentry_gib17 ); + } + +} + +//PMKOD - Ergodic - 05/26/00 - Beartrap death +/* +================== +CG_BearTrapDie +================== +*/ +void CG_BearTrapDie( vec3_t BearTrapOrigin ) { + vec3_t origin, velocity; + int number_of_gibs; + + VectorCopy( BearTrapOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.pkabeartrapgib1 ); + + VectorCopy( BearTrapOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.pkabeartrapgib2 ); + + VectorCopy( BearTrapOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.pkabeartrapgib3 ); + + number_of_gibs = random() * 5; + + while (number_of_gibs > 0) { + VectorCopy( BearTrapOrigin, origin ); + velocity[0] = crandom()*GIB_VELOCITY; + velocity[1] = crandom()*GIB_VELOCITY; + velocity[2] = GIB_JUMP + crandom()*GIB_VELOCITY; + CG_LaunchGib( origin, velocity, cgs.media.pkabeartrapgib4 ); + number_of_gibs--; + } + +} + + +//PKMOD - Ergodic debug position +char *CG_vtos_2( const 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; +} + +/* +================== +CG_Lightning_FX + +Create a light flash effect +Note: effect must be turned off +================== +*/ +//PKMOD - Ergodic 09/30/01 - event parm will send intensity +//PKMOD - Ergodic 10/01/01 - add mulitple points +#define MAX_LIGHTNING_POINTS 25 +#define MEAN_LIGHTNING_DISTANCE 20 + +void CG_Lightning_FX( vec3_t position, int intensity ) { + int i, r, g, b; + int indx; + vec3_t hold_position; + + //PKMOD Ergodic debug 07/19/00 (inactive) +//Com_Printf("CG_Lightning_FX - position>%s<, intensity>%d<\n", CG_vtos_2(position), intensity ); + + + r = 255; + g = 255; + b = 255; + i = intensity; + for (indx = 1; indx < MAX_LIGHTNING_POINTS; indx++ ) { + hold_position[0] = position[0] + crandom() * MEAN_LIGHTNING_DISTANCE; + hold_position[1] = position[1] + crandom() * MEAN_LIGHTNING_DISTANCE; + hold_position[2] = position[2] + crandom() * MEAN_LIGHTNING_DISTANCE; + trap_R_AddLightToScene( hold_position, i, r, g, b ); + } + +} + + +/* +================== +CG_Lightning_Water_Discharge + +//PKMOD - Ergodic 01/15/01 - add Lightning Discharge in water +================== +*/ +void CG_Lightning_Water_Discharge (vec3_t origin, int msec) { + + localEntity_t *le; + + if (msec <= 0) + msec = 300; + + le = CG_SmokePuff ( origin, // where + vec3_origin, // where to + ((48 + (msec * 10)) / 16), // radius + 1, 1, 1, 1, // RGBA color shift + 300 + msec, // duration + cg.time, // start when? + 0, //fade in time + 0, // flags (?) + trap_R_RegisterShader ("models/weaphits/electric.tga")); + + le->leType = LE_SCALE_FADE; +} + + +/* +================== +CG_TootBubbles +//PKMOD - Ergodic 04/13/01 - Toots underwater +================== +*/ +#define TootBubbleCount 10 +//PKMOD - Ergodic 04/14/01 - Soft code +#define TootDisplacementMax 6 // displacement from center +#define TootDisplacementOffset 13 // 1 + 2 * TootDisplacementMax +#define Toot_H_VelocityMax 7 // horizontal velocity of bubbles +#define Toot_H_VelocityOffset 15 // 1 + 2 * Toot_H_VelocityMax +#define Toot_V_VelocityMax 20 // Vertical velocity of bubbles + + +void CG_TootBubbles( vec3_t start ) { + vec3_t hold_vec; + int i; + int bubbles; + + bubbles = 10 + rand() % TootBubbleCount; //{10, 19} + VectorCopy ( start, hold_vec ); + + for ( i = 1; i < bubbles; i++ ) { + localEntity_t *le; + refEntity_t *re; + + VectorCopy ( start, hold_vec ); //random location neary the hiney + hold_vec[0] += TootDisplacementMax - rand() % TootDisplacementOffset; //{-6, 6} + hold_vec[1] += TootDisplacementMax - rand() % TootDisplacementOffset; //{-6, 6} + hold_vec[2] += TootDisplacementMax - rand() % TootDisplacementOffset; //{-6, 6} + + le = CG_AllocLocalEntity(); + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + 1000 + random() * 250; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + re->shaderTime = cg.time / 1000.0f; + + re->reType = RT_SPRITE; + re->rotation = 0; + re->radius = 3; + re->customShader = cgs.media.waterBubbleShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + + le->color[3] = 1.0; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + VectorCopy( hold_vec, le->pos.trBase ); + + le->pos.trDelta[0] = Toot_H_VelocityMax - rand() % Toot_H_VelocityOffset; //{-7, 7} + le->pos.trDelta[1] = Toot_H_VelocityMax - rand() % Toot_H_VelocityOffset; //{-7, 7} + le->pos.trDelta[2] = Toot_V_VelocityMax + rand() % Toot_V_VelocityMax; //{20, 39} + + } +} + +/* +====================== +CG_BeansToot + +//PKMOD - Ergodic 04/13/01 - Renders fecal plumes or bubbles from eating beans +====================== +*/ +void CG_BeansToot( vec3_t origin ) { + int sourceContentType; + vec3_t hiney_location; + localEntity_t *smoke; + vec3_t up = {0, 0, 5}; + + VectorCopy( origin, hiney_location); + hiney_location[2] +=16; //waist displacement height + + + sourceContentType = trap_CM_PointContents( hiney_location, 0 ); + + if ( sourceContentType & CONTENTS_WATER ) { + CG_TootBubbles( hiney_location ); + } + else { + //PKMOD - Ergodic 12/27/00 add beans shader + smoke = CG_SmokePuff( hiney_location, up, + 32, + 1, 1, 1, 0.33f, + 1500, + cg.time, 0, + LEF_PUFF_DONT_SCALE, + cgs.media.pkafartPuffShader ); + } + +} + + //PKMOD - Ergodic 01/05/04 - add quad farting logic for differing CG graphic sequence +/* +====================== +CG_BeansToot + +PKMOD - Ergodic 01/05/04 - add quad farting logic for differing CG graphic sequence + - Renders fecal plumes or bubbles from eating beans + Ergodic 01/12/04 - add in scaling function local entity +====================== +*/ +void CG_QuadBeansToot( vec3_t origin ) { + int sourceContentType; + vec3_t hiney_location; + localEntity_t *smoke; +// vec3_t up = {0, 0, 5}; + + + //PKMOD - Ergodic 01/12/04 - debug quad fart activation (inactive) + //Com_Printf( "inside cg_quadbeanstoot\n" ); + + VectorCopy( origin, hiney_location); + hiney_location[2] +=16; //waist displacement height + + + sourceContentType = trap_CM_PointContents( hiney_location, 0 ); + + if ( sourceContentType & CONTENTS_WATER ) { + CG_TootBubbles( hiney_location ); + } + + smoke = CG_AllocLocalEntity(); + + smoke->leType = LE_SCALED_SPRITE_EXPLOSION; + + smoke->startTime = cg.time; + smoke->endTime = smoke->startTime + 1100; //Keep in sync with cg_localents + + // bias the time so all shader effects start correctly + smoke->refEntity.shaderTime = smoke->startTime / 1000.0f; + + smoke->refEntity.hModel = cgs.media.pkaquadbeansModel; + smoke->refEntity.customShader = cgs.media.pkaquadbeansShader; + + // set origin + VectorCopy( hiney_location, smoke->refEntity.origin ); + VectorCopy( hiney_location, smoke->refEntity.oldorigin ); + + smoke->light = 300; + smoke->lightColor[0] = 1; + smoke->lightColor[1] = 0.75; + smoke->lightColor[2] = 0.0; + +} + +/* +================== +CG_Radiation + +//PKMOD - Ergodic 10/14/01 - add radiation sparking effect +================== +*/ + +#define CG_RADIATION_COUNT 20 +#define CG_RADIATION_MIN_RADIUS 10 +#define CG_RADIATION_DELTA_RADIUS 25 //delta radius = max radius - min radius +#define CG_RADIATION_MIN_TIME 300 +#define CG_RADIATION_DELTA_TIME 500 //delta time = max time - min time +void CG_Radiation( vec3_t start ) { + vec3_t move; + int i; + + for ( i = 1; i < CG_RADIATION_COUNT; i++ ) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + CG_RADIATION_MIN_TIME + random() * CG_RADIATION_DELTA_TIME; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + re->shaderTime = cg.time / 1000.0f; + + re->reType = RT_SPRITE; + re->rotation = 0; + re->radius = 2; + switch ( rand() % 6 ) { //random numbers: { 0, 1, 2, 3, 4, 5 } + case 0: + re->customShader = cgs.media.radiate1Shader; + break; + case 1: + re->customShader = cgs.media.radiate2Shader; + break; + case 2: + re->customShader = cgs.media.radiate3Shader; + break; + case 3: + re->customShader = cgs.media.radiate4Shader; + break; + case 4: + re->customShader = cgs.media.radiate5Shader; + break; + default: + re->customShader = cgs.media.radiate6Shader; + break; + } + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + + le->color[3] = 1.0; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + move[0] = start[0] + crandom() * ( CG_RADIATION_MIN_RADIUS + random() * CG_RADIATION_DELTA_RADIUS ); + move[1] = start[1] + crandom() * ( CG_RADIATION_MIN_RADIUS + random() * CG_RADIATION_DELTA_RADIUS ); + move[2] = start[2] + crandom() * ( CG_RADIATION_MIN_RADIUS + random() * CG_RADIATION_DELTA_RADIUS ); + + VectorCopy( move, le->pos.trBase ); + le->pos.trDelta[0] = crandom()*10; + le->pos.trDelta[1] = crandom()*10; + le->pos.trDelta[2] = crandom()*10; + + } +} + +/* +===================== +CG_RadiationTrail + +PKMOD - Ergodic 11/30/01 - add radiation trail to infected player +===================== +*/ +void CG_RadiationTrail0( const vec3_t p ) { + + vec3_t vel; + static int seed = 0x92; + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leFlags = 0; + le->radius = 3; + + re = &le->refEntity; + re->rotation = Q_random( &seed ) * 360; + re->radius = 3; + re->shaderTime = cg.time / 1000.0f; + + le->leType = LE_FALL_SCALE_FADE; + le->startTime = cg.time; + le->fadeInTime = 0; + le->endTime = cg.time + 1000 + (rand() % 1001); + if ( le->fadeInTime > le->startTime ) { + le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); + } + else { + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + } + le->color[0] = 1; + le->color[1] = 1; + le->color[2] = 1; + le->color[3] = 1; + + + le->pos.trType = TR_LINEAR; + le->pos.trTime = le->startTime; + vel[0] = 25 - 2 * ( rand() % 25 ); + vel[1] = 25 - 2 * ( rand() % 25 ); + vel[2] = 25 - 2 * ( rand() % 25 ); + VectorCopy( vel, le->pos.trDelta ); + VectorCopy( p, le->pos.trBase ); + + VectorCopy( p, re->origin ); + re->customShader = cgs.media.radiationTrailShader; + + // rage pro can't alpha fade, so use a different shader + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + re->customShader = cgs.media.smokePuffRageProShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + } else { + re->shaderRGBA[0] = le->color[0] * 0xff; + re->shaderRGBA[1] = le->color[1] * 0xff; + re->shaderRGBA[2] = le->color[2] * 0xff; + re->shaderRGBA[3] = 0xff; + } + + re->reType = RT_SPRITE; + re->radius = le->radius; + + // drop a total of 2 units over its lifetime + le->pos.trDelta[2] = 2; + +} + + +/* +===================== +CG_RadiationTrail + +PKMOD - Ergodic 11/30/01 - add radiation trail to infected player +===================== +*/ +void CG_RadiationTrail( const vec3_t p, vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ) { +// static int seed = 0x92; + localEntity_t *le; + refEntity_t *re; +// int fadeInTime = startTime + duration / 2; + + le = CG_AllocLocalEntity(); + le->leFlags = leFlags; + le->radius = radius; + + re = &le->refEntity; +// re->rotation = Q_random( &seed ) * 360; + re->rotation = rand() % 360; //PKMOD - Ergodic 11/30/01 + re->radius = radius; + re->shaderTime = startTime / 1000.0f; + + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = startTime; + le->fadeInTime = fadeInTime; + le->endTime = startTime + duration; + if ( fadeInTime > startTime ) { + le->lifeRate = 1.0 / ( le->endTime - le->fadeInTime ); + } + else { + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + } + le->color[0] = r; + le->color[1] = g; + le->color[2] = b; + le->color[3] = a; + + + le->pos.trType = TR_LINEAR; + le->pos.trTime = startTime; + VectorCopy( vel, le->pos.trDelta ); + VectorCopy( p, le->pos.trBase ); + + VectorCopy( p, re->origin ); + re->customShader = hShader; + + // rage pro can't alpha fade, so use a different shader + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + re->customShader = cgs.media.smokePuffRageProShader; + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + re->shaderRGBA[3] = 0xff; + } else { + re->shaderRGBA[0] = le->color[0] * 0xff; + re->shaderRGBA[1] = le->color[1] * 0xff; + re->shaderRGBA[2] = le->color[2] * 0xff; + re->shaderRGBA[3] = 0xff; + } + + re->reType = RT_SPRITE; + re->radius = le->radius; + + // drop a total of 5 units over its lifetime + le->pos.trDelta[2] = 20; + +} + diff --git a/quake3/source/code/cgame/cg_ents.c b/quake3/source/code/cgame/cg_ents.c new file mode 100644 index 0000000..f59771a --- /dev/null +++ b/quake3/source/code/cgame/cg_ents.c @@ -0,0 +1,3508 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_ents.c -- present snapshot entities, happens every single frame + +#include "cg_local.h" + + +//PKMOD - Ergodic debug position +char *CG_vtos( const 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; +} + + +/* +====================== +CG_PositionEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( lerped.axis, ((refEntity_t *)parent)->axis, entity->axis ); + entity->backlerp = parent->backlerp; +} + + +/* +====================== +CG_PositionRotatedEntityOnTag + +Modifies the entities position and axis by the given +tag location +====================== +*/ +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ) { + int i; + orientation_t lerped; + vec3_t tempAxis[3]; + +//AxisClear( entity->axis ); + // lerp the tag + trap_R_LerpTag( &lerped, parentModel, parent->oldframe, parent->frame, + 1.0 - parent->backlerp, tagName ); + + // FIXME: allow origin offsets along tag? + VectorCopy( parent->origin, entity->origin ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( entity->origin, lerped.origin[i], parent->axis[i], entity->origin ); + } + + // had to cast away the const to avoid compiler problems... + MatrixMultiply( entity->axis, lerped.axis, tempAxis ); + MatrixMultiply( tempAxis, ((refEntity_t *)parent)->axis, entity->axis ); +} + + + +/* +========================================================================== + +FUNCTIONS CALLED EACH FRAME + +========================================================================== +*/ + +/* +====================== +CG_SetEntitySoundPosition + +Also called by event processing code +====================== +*/ +void CG_SetEntitySoundPosition( centity_t *cent ) { + if ( cent->currentState.solid == SOLID_BMODEL ) { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_UpdateEntityPosition( cent->currentState.number, origin ); + } else { + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); + } +} + +/* +================== +CG_EntityEffects + +Add continuous entity effects, like local entity emission and lighting +================== +*/ +static void CG_EntityEffects( centity_t *cent ) { + + // update sound origins + CG_SetEntitySoundPosition( cent ); + + //PKMOD - Ergodic 11/19/02 - add code to work-around loopSound Bug (does not carry over to cgame) + if ( cent->currentState.time2 && ( cent->currentState.eType == ET_MOVER ) ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.time2 ] ); + } + // add loop sound + else { + if ( cent->currentState.loopSound ) { + if (cent->currentState.eType != ET_SPEAKER) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } else { + trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + cgs.gameSounds[ cent->currentState.loopSound ] ); + } + } + } + + + // constant light glow + if ( cent->currentState.constantLight ) { + int cl; + int i, r, g, b; + + cl = cent->currentState.constantLight; + r = cl & 255; + g = ( cl >> 8 ) & 255; + b = ( cl >> 16 ) & 255; + i = ( ( cl >> 24 ) & 255 ) * 4; + trap_R_AddLightToScene( cent->lerpOrigin, i, r, g, b ); + } + +} + + +/* +================== +CG_General +================== +*/ +static void CG_General( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // if set to invisible, skip + if (!s1->modelindex) { + return; + } + + memset (&ent, 0, sizeof(ent)); + + // set frame + + ent.frame = s1->frame; + ent.oldframe = ent.frame; + ent.backlerp = 0; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.hModel = cgs.gameModels[s1->modelindex]; + + // player model + if (s1->number == cg.snap->ps.clientNum) { + ent.renderfx |= RF_THIRD_PERSON; // only draw from mirrors + } + + // convert angles to axis + AnglesToAxis( cent->lerpAngles, ent.axis ); + + // add to refresh list + trap_R_AddRefEntityToScene (&ent); +} + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) { + if ( ! cent->currentState.clientNum ) { // FIXME: use something other than clientNum... + return; // not auto triggering + } + + if ( cg.time < cent->miscTime ) { + return; + } + + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.gameSounds[cent->currentState.eventParm] ); + + // ent->s.frame = ent->wait * 10; + // ent->s.clientNum = ent->random * 10; + cent->miscTime = cg.time + cent->currentState.frame * 100 + cent->currentState.clientNum * 100 * crandom(); +} + +/* +================== +CG_Item +================== +*/ +static void CG_Item( centity_t *cent ) { + refEntity_t ent; + entityState_t *es; + gitem_t *item; + int msec; + float frac; + float scale; + weaponInfo_t *wi; + + es = ¢->currentState; + if ( es->modelindex >= bg_numItems ) { + CG_Error( "Bad item index %i on entity", es->modelindex ); + } + + // if set to invisible, skip + if ( !es->modelindex || ( es->eFlags & EF_NODRAW ) ) { + return; + } + + item = &bg_itemlist[ es->modelindex ]; + if ( cg_simpleItems.integer && item->giType != IT_TEAM ) { + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.radius = 14; + + //PKMOD - Ergodic 11/16/01 - add logic to display radiation effect for simple item + if ( es->modelindex2 & PKAEF_IRRADIATED ) { + if ( cent->PKA_RadiateTime < cg.time ) { + switch (rand() % 3) { //Generate random numbers: {0,1,2} + case 0: + cent->PKA_customShader = cgs.media.radiate1SimpleIcon; + break; + case 1: + cent->PKA_customShader = cgs.media.radiate2SimpleIcon; + break; + default: + cent->PKA_customShader = cgs.media.radiate3SimpleIcon; + break; + } + cent->PKA_RadiateTime = cg.time + 2000; //every 1 seconds + ent.customShader = cent->PKA_customShader; + } + else if ( ( cent->PKA_RadiateTime - cg.time ) > 1000 ) + ent.customShader = cent->PKA_customShader; + else + ent.customShader = cg_items[es->modelindex].icon; + } + else // item is not radiated + ent.customShader = cg_items[es->modelindex].icon; + + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene(&ent); + return; + } + + //PKMOD - Ergodic 10/29/01 - add logic to display radiation effect + if ( es->modelindex2 & PKAEF_IRRADIATED ) { + if ( cent->PKA_RadiateTime < cg.time ) { + CG_Radiation( cent->lerpOrigin ); + cent->PKA_RadiateTime = cg.time + 1000; //every 1 seconds + } + } + + // items bob up and down continuously + scale = 0.005 + cent->currentState.number * 0.00001; + cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; + + memset (&ent, 0, sizeof(ent)); + + //PKMOD - Ergodic 12/03/01 - add custom skins for Private Bot + if ( item->giType == IT_HOLDABLE ) { + switch ( item->giTag ) { + case HI_BOTLEGS: + ent.customSkin = cgs.media.privatebot_legsSkin; + //PKMOD - Ergodic 12/01/01 - raise the bot legs by several units + cent->lerpOrigin[2] += 22; + break; + case HI_BOTTORSO: + ent.customSkin = cgs.media.privatebot_torsoSkin; + break; + case HI_BOTHEAD: + ent.customSkin = cgs.media.privatebot_headSkin; + break; + default: + break; + } + } + + //PKMOD - Ergodic 06/01/02 - set the frame for the autosentry + // autosentry frames are 0..29 + if (( item->giType == IT_WEAPON ) && ( item->giTag == WP_SENTRY )) { + ent.frame = 29; + } + + //PKMOD - Ergodic 01/27/02 - we will do the rotation function later + // autorotate at one of two speeds +// if ( item->giType == IT_HEALTH ) { +// VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); +// AxisCopy( cg.autoAxisFast, ent.axis ); +// } else { +// VectorCopy( cg.autoAngles, cent->lerpAngles ); +// AxisCopy( cg.autoAxis, ent.axis ); +// } + + wi = NULL; + + //PKMOD - Ergodic 09/21/00 - add logic so that voting entity will not rotate + if ( item->giType == IT_VOTING ) { + //PKMOD - Ergodic 09/28/00 - debug set angle to 45 +// VectorSet( cent->lerpAngles, 0, 45, 0 ); +// Com_Printf("CG_Item: IT_VOTING - cent->lerpAngles>%s<\n", CG_vtos(cent->lerpAngles)); + +// AxisClear( ent.axis ); + + // convert lerpangles into axis + AnglesToAxis( cent->lerpAngles, ent.axis ); + + //PKMOD - Ergodic 09/29/00 - debug +// Com_Printf("CG_Item - cent->currentState.otherEntityNum>%d<, hubInfo[cent->currentState.otherEntityNum].map_shader_index>%d<\n", cent->currentState.otherEntityNum, hubInfo[cent->currentState.otherEntityNum].map_shader_index ); + + //PKMOD - Ergodic 09/21/00 - Set the voting image shader + //PKMOD - Ergodic 10/10/00 - use otherEntityNum2 as flag for registering shader + switch ( cent->currentState.otherEntityNum ) { + case 0: + if ( cg_voting_shader_flag[0] == '0' ) { + cgs.media.voting_levelshot_0 = trap_R_RegisterShaderNoMip( "voting_levelshot_0" ); + cg_voting_shader_flag[0] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_0; + break; + case 1: + if ( cg_voting_shader_flag[1] == '0' ) { + cgs.media.voting_levelshot_1 = trap_R_RegisterShaderNoMip( "voting_levelshot_1" ); + cg_voting_shader_flag[1] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_1; + break; + case 2: + if ( cg_voting_shader_flag[2] == '0' ) { + cgs.media.voting_levelshot_2 = trap_R_RegisterShaderNoMip( "voting_levelshot_2" ); + cg_voting_shader_flag[2] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_2; + break; + case 3: + if ( cg_voting_shader_flag[3] == '0' ) { + cgs.media.voting_levelshot_3 = trap_R_RegisterShaderNoMip( "voting_levelshot_3" ); + cg_voting_shader_flag[4] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_3; + break; + case 4: + if ( cg_voting_shader_flag[4] == '0' ) { + cgs.media.voting_levelshot_4 = trap_R_RegisterShaderNoMip( "voting_levelshot_4" ); + cg_voting_shader_flag[4] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_4; + break; + case 5: + if ( cg_voting_shader_flag[5] == '0' ) { + cgs.media.voting_levelshot_5 = trap_R_RegisterShaderNoMip( "voting_levelshot_5" ); + cg_voting_shader_flag[5] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_5; + break; + case 6: + if ( cg_voting_shader_flag[6] == '0' ) { + cgs.media.voting_levelshot_6 = trap_R_RegisterShaderNoMip( "voting_levelshot_6" ); + cg_voting_shader_flag[6] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_6; + break; + case 7: + //PKMOD - Ergodic 10/10/00 - debug +// Com_Printf("CG_Item - case 7 cg_voting_shader_flag[7]>%c<\n", cg_voting_shader_flag[7] ); + + if ( cg_voting_shader_flag[7] == '0' ) { + //PKMOD - Ergodic 10/10/00 - debug +// Com_Printf("CG_Item - setting cg_voting_shader_flag[7]\n" ); + cgs.media.voting_levelshot_7 = trap_R_RegisterShaderNoMip( "voting_levelshot_7" ); + cg_voting_shader_flag[7] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_7; + break; + case 8: + if ( cg_voting_shader_flag[8] == '0' ) { + cgs.media.voting_levelshot_8 = trap_R_RegisterShaderNoMip( "voting_levelshot_8" ); + cg_voting_shader_flag[8] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_8; + break; + case 9: + if ( cg_voting_shader_flag[9] == '0' ) { + cgs.media.voting_levelshot_9 = trap_R_RegisterShaderNoMip( "voting_levelshot_9" ); + cg_voting_shader_flag[9] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_9; + break; + case 10: + if ( cg_voting_shader_flag[10] == '0' ) { + cgs.media.voting_levelshot_10 = trap_R_RegisterShaderNoMip( "voting_levelshot_10" ); + cg_voting_shader_flag[10] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_10; + break; + case 11: + if ( cg_voting_shader_flag[11] == '0' ) { + cgs.media.voting_levelshot_11 = trap_R_RegisterShaderNoMip( "voting_levelshot_11" ); + cg_voting_shader_flag[11] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_11; + break; + case 12: + if ( cg_voting_shader_flag[12] == '0' ) { + cgs.media.voting_levelshot_12 = trap_R_RegisterShaderNoMip( "voting_levelshot_12" ); + cg_voting_shader_flag[12] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_12; + break; + case 13: + if ( cg_voting_shader_flag[13] == '0' ) { + cgs.media.voting_levelshot_13 = trap_R_RegisterShaderNoMip( "voting_levelshot_13" ); + cg_voting_shader_flag[13] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_13; + break; + case 14: + if ( cg_voting_shader_flag[14] == '0' ) { + cgs.media.voting_levelshot_14 = trap_R_RegisterShaderNoMip( "voting_levelshot_14" ); + cg_voting_shader_flag[14] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_14; + break; + case 15: + if ( cg_voting_shader_flag[15] == '0' ) { + cgs.media.voting_levelshot_15 = trap_R_RegisterShaderNoMip( "voting_levelshot_15" ); + cg_voting_shader_flag[15] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_15; + break; + case 16: + if ( cg_voting_shader_flag[16] == '0' ) { + cgs.media.voting_levelshot_16 = trap_R_RegisterShaderNoMip( "voting_levelshot_16" ); + cg_voting_shader_flag[16] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_16; + break; + case 17: + if ( cg_voting_shader_flag[17] == '0' ) { + cgs.media.voting_levelshot_17 = trap_R_RegisterShaderNoMip( "voting_levelshot_17" ); + cg_voting_shader_flag[17] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_17; + break; + case 18: + if ( cg_voting_shader_flag[18] == '0' ) { + cgs.media.voting_levelshot_18 = trap_R_RegisterShaderNoMip( "voting_levelshot_18" ); + cg_voting_shader_flag[18] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_18; + break; + case 19: + if ( cg_voting_shader_flag[19] == '0' ) { + cgs.media.voting_levelshot_19 = trap_R_RegisterShaderNoMip( "voting_levelshot_19" ); + cg_voting_shader_flag[19] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_19; + break; + case 20: + if ( cg_voting_shader_flag[20] == '0' ) { + cgs.media.voting_levelshot_20 = trap_R_RegisterShaderNoMip( "voting_levelshot_20" ); + cg_voting_shader_flag[20] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_20; + break; + case 21: + if ( cg_voting_shader_flag[21] == '0' ) { + cgs.media.voting_levelshot_21 = trap_R_RegisterShaderNoMip( "voting_levelshot_21" ); + cg_voting_shader_flag[21] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_21; + break; + case 22: + if ( cg_voting_shader_flag[22] == '0' ) { + cgs.media.voting_levelshot_22 = trap_R_RegisterShaderNoMip( "voting_levelshot_22" ); + cg_voting_shader_flag[22] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_22; + break; + case 23: + if ( cg_voting_shader_flag[23] == '0' ) { + cgs.media.voting_levelshot_23 = trap_R_RegisterShaderNoMip( "voting_levelshot_23" ); + cg_voting_shader_flag[23] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_23; + break; + case 24: + if ( cg_voting_shader_flag[24] == '0' ) { + cgs.media.voting_levelshot_24 = trap_R_RegisterShaderNoMip( "voting_levelshot_24" ); + cg_voting_shader_flag[24] = '1'; + } + ent.customShader = cgs.media.voting_levelshot_24; + break; + default: + ent.customShader = trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ); + break; + } + +// ent.hModel = cg_items[es->modelindex].models[0]; + +// VectorCopy( cent->lerpOrigin, ent.origin); +// VectorCopy( cent->lerpOrigin, ent.oldorigin); + + // add to refresh list +// trap_R_AddRefEntityToScene(&ent); +// return; + } + else { + // autorotate at one of two speeds + if ( item->giType == IT_HEALTH ) { + VectorCopy( cg.autoAnglesFast, cent->lerpAngles ); + AxisCopy( cg.autoAxisFast, ent.axis ); + //PKMOD - Ergodic 01/15/04 - change logic to "else if" for addition of code to rotate ammo slowly + } else if ( ( item->giType == IT_HOLDABLE ) && ( item->giTag == HI_RADIATE ) ) { + //PKMOD - Ergodic 01/27/02 - rotate the radiate pickup model very slowly + VectorCopy( cg.autoAnglesSlow, cent->lerpAngles ); + AxisCopy( cg.autoAxisSlow, ent.axis ); + } else if ( item->giType == IT_AMMO ) { //default rotation + //PKMOD - Ergodic 01/15/04 - add code to rotate ammo slowly + VectorCopy( cg.autoAnglesSlow, cent->lerpAngles ); + AxisCopy( cg.autoAxisSlow, ent.axis ); + } else { + VectorCopy( cg.autoAngles, cent->lerpAngles ); + AxisCopy( cg.autoAxis, ent.axis ); + } + + } + + wi = NULL; + // the weapons have their origin where they attatch to player + // models, so we need to offset them or they will rotate + // eccentricly + if ( item->giType == IT_WEAPON ) { + wi = &cg_weapons[item->giTag]; + cent->lerpOrigin[0] -= + wi->weaponMidpoint[0] * ent.axis[0][0] + + wi->weaponMidpoint[1] * ent.axis[1][0] + + wi->weaponMidpoint[2] * ent.axis[2][0]; + cent->lerpOrigin[1] -= + wi->weaponMidpoint[0] * ent.axis[0][1] + + wi->weaponMidpoint[1] * ent.axis[1][1] + + wi->weaponMidpoint[2] * ent.axis[2][1]; + cent->lerpOrigin[2] -= + wi->weaponMidpoint[0] * ent.axis[0][2] + + wi->weaponMidpoint[1] * ent.axis[1][2] + + wi->weaponMidpoint[2] * ent.axis[2][2]; + + cent->lerpOrigin[2] += 8; // an extra height boost + } + + //PKMOD - Ergodic 03/05/01 - CLG has different pickup model than handhold model (inactive) + //PKMOD - Ergodic 03/27/01 - code was inactivated due to CLG will not have a rotating barrel +// if ( ( item->giTag == WP_LIGHTNING ) && ( item->giType == IT_WEAPON ) ) +// ent.hModel = cg_items[es->modelindex].models[1]; +// else +// ent.hModel = cg_items[es->modelindex].models[0]; + + ent.hModel = cg_items[es->modelindex].models[0]; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.nonNormalizedAxes = qfalse; + + // if just respawned, slowly scale up + msec = cg.time - cent->miscTime; + if ( msec >= 0 && msec < ITEM_SCALEUP_TIME ) { + frac = (float)msec / ITEM_SCALEUP_TIME; + VectorScale( ent.axis[0], frac, ent.axis[0] ); + VectorScale( ent.axis[1], frac, ent.axis[1] ); + VectorScale( ent.axis[2], frac, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } else { + frac = 1.0; + } + + // items without glow textures need to keep a minimum light value + // so they are always visible + if ( ( item->giType == IT_WEAPON ) || + ( item->giType == IT_ARMOR ) ) { + ent.renderfx |= RF_MINLIGHT; + } + + // increase the size of the weapons when they are presented as items + if ( item->giType == IT_WEAPON ) { + VectorScale( ent.axis[0], 1.5, ent.axis[0] ); + VectorScale( ent.axis[1], 1.5, ent.axis[1] ); + VectorScale( ent.axis[2], 1.5, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; +#ifdef MISSIONPACK + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); +#endif + } + +#ifdef MISSIONPACK + if ( item->giType == IT_HOLDABLE && item->giTag == HI_KAMIKAZE ) { + VectorScale( ent.axis[0], 2, ent.axis[0] ); + VectorScale( ent.axis[1], 2, ent.axis[1] ); + VectorScale( ent.axis[2], 2, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } +#endif + + //PKMOD - Ergodic 05/19/02 - resize the Private Bot (head) to make it larger + if ( item->giType == IT_HOLDABLE && item->giTag == HI_BOTHEAD ) { + VectorScale( ent.axis[0], 2, ent.axis[0] ); + VectorScale( ent.axis[1], 2, ent.axis[1] ); + VectorScale( ent.axis[2], 2, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + + //PKMOD - Ergodic 05/16/03 - add blade to "pickup" lightning gun model + if ( ( item->giType == IT_WEAPON ) && ( item->giTag == WP_LIGHTNING ) ) { + refEntity_t barrel; + + memset( &barrel, 0, sizeof( barrel ) ); + + barrel.hModel = wi->barrelModel; + + VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = ent.shadowPlane; + barrel.renderfx = ent.renderfx; + + CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); + + AxisCopy( ent.axis, barrel.axis ); + barrel.nonNormalizedAxes = ent.nonNormalizedAxes; + + trap_R_AddRefEntityToScene( &barrel ); + } + + +#ifdef MISSIONPACK + if ( item->giType == IT_WEAPON && wi->barrelModel ) { + refEntity_t barrel; + + memset( &barrel, 0, sizeof( barrel ) ); + + barrel.hModel = wi->barrelModel; + + VectorCopy( ent.lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = ent.shadowPlane; + barrel.renderfx = ent.renderfx; + + CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); + + AxisCopy( ent.axis, barrel.axis ); + barrel.nonNormalizedAxes = ent.nonNormalizedAxes; + + trap_R_AddRefEntityToScene( &barrel ); + } +#endif + + // accompanying rings / spheres for powerups + if ( !cg_simpleItems.integer ) + { + vec3_t spinAngles; + + VectorClear( spinAngles ); + + if ( item->giType == IT_HEALTH || item->giType == IT_POWERUP ) + { + if ( ( ent.hModel = cg_items[es->modelindex].models[1] ) != 0 ) + { + if ( item->giType == IT_POWERUP ) + { + ent.origin[2] += 12; + spinAngles[1] = ( cg.time & 1023 ) * 360 / -1024.0f; + } + AnglesToAxis( spinAngles, ent.axis ); + + // scale up if respawning + if ( frac != 1.0 ) { + VectorScale( ent.axis[0], frac, ent.axis[0] ); + VectorScale( ent.axis[1], frac, ent.axis[1] ); + VectorScale( ent.axis[2], frac, ent.axis[2] ); + ent.nonNormalizedAxes = qtrue; + } + trap_R_AddRefEntityToScene( &ent ); + } + } + } +} + +//============================================================================ + +/* +=============== +CG_Missile +=============== +*/ +static void CG_Missile( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; +// int col; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } +/* + if ( cent->currentState.modelindex == TEAM_RED ) { + col = 1; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + col = 2; + } + else { + col = 0; + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[col][0], weapon->missileDlightColor[col][1], weapon->missileDlightColor[col][2] ); + } +*/ + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + if ( cent->currentState.weapon == WP_PLASMAGUN ) { + ent.reType = RT_SPRITE; + ent.radius = 16; + ent.rotation = 0; + ent.customShader = cgs.media.plasmaBallShader; + trap_R_AddRefEntityToScene( &ent ); + return; + } + + //PKMOD - Ergodic 06/12/02 - add logic personalsentry missile sprite [time2 == 251] + //PKMOD - Ergodic 04/06/01 - add autosentry missile sprite + if ( cent->currentState.weapon == WP_SENTRY ) { + ent.reType = RT_SPRITE; + ent.rotation = 0; + if ( cent->currentState.time2 == 251 ) { + //personalsentry missile details + if ( ( rand() % 101 ) > 50 ) + ent.radius = 8; + else + ent.radius = 2; + ent.customShader = cgs.media.personalsentryBallShader; + } + else { + //autosentry missile details + if ( ( rand() % 101 ) > 96 ) + ent.radius = 16; + else + ent.radius = 2; + ent.customShader = cgs.media.autosentryBallShader; + } + trap_R_AddRefEntityToScene( &ent ); + return; + } + + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + ent.hModel = weapon->missileModel; + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + +#ifdef MISSIONPACK + if ( cent->currentState.weapon == WP_PROX_LAUNCHER ) { + if (s1->generic1 == TEAM_BLUE) { + ent.hModel = cgs.media.blueProxMine; + } + } +#endif + + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { + ent.axis[0][2] = 1; + } + + // spin as it moves + //PKMOD - Ergodic 04/27/01 - make launched gravity well spin + if ( ( s1->pos.trType != TR_STATIONARY ) || ( cent->currentState.weapon == WP_GRAVITY ) ) { + RotateAroundDirection( ent.axis, cg.time / 4 ); + } else { +#ifdef MISSIONPACK + if ( s1->weapon == WP_PROX_LAUNCHER ) { + AnglesToAxis( cent->lerpAngles, ent.axis ); + } + else +#endif + { + RotateAroundDirection( ent.axis, s1->time ); + } + } + + // add to refresh list, possibly with quad glow + CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); +} + +/* +=============== +CG_Grapple + +This is called when the grapple is sitting up against the wall +=============== +*/ +static void CG_Grapple( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + +#if 0 // FIXME add grapple pull sound here..? + // add missile sound + if ( weapon->missileSound ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->missileSound ); + } +#endif + + // Will draw cable if needed + CG_GrappleTrail ( cent, weapon ); + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + ent.hModel = weapon->missileModel; + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { + ent.axis[0][2] = 1; + } + + trap_R_AddRefEntityToScene( &ent ); +} + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + //PKMOD Ergodic debug 06/29/01 (inactive) +// if ( (rand() % 101) > 97 ) +// Com_Printf("CG_mover - eType>%d, cent->lerpAngles>%s<, cent->lerpOrigin>%s<, generic1>%d<\n", cent->currentState.eType, CG_vtos(cent->lerpAngles), CG_vtos(cent->lerpOrigin), cent->currentState.generic1); + + //PKMOD - Ergodic 11/19/02 - debug mover sound (inactive) +// if ( cent->currentState.time2 ) { +// Com_Printf( "CG_Mover - mover soundindex: %d (time2: %d) at lerp>%s<, vec3>%s<\n", cent->currentState.loopSound, cent->currentState.time2, CG_vtos(cent->lerpOrigin), CG_vtos(vec3_origin) ); +// } + + //PKMOD - Ergodic 06/11/01 - if zombie mover... + if ( cent->currentState.generic1 ) { + //PKMOD - Ergodic 06/11/01 - for zombie + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + int frame; + + memset( &legs, 0, sizeof(legs) ); + memset( &torso, 0, sizeof(torso) ); + memset( &head, 0, sizeof(head) ); + + frame = ( (cg.time - s1->apos.trTime) / 1000 ) % 31 + 30; + + //PKMOD Ergodic debug 06/20/01 (inactive) +// Com_Printf("CG_mover - zombie frame>%d<\n", frame); + + + VectorCopy( cent->lerpOrigin, legs.origin); + VectorCopy( cent->lerpOrigin, legs.oldorigin); + AnglesToAxis( cent->lerpAngles, legs.axis ); + + // legs + legs.customSkin = cgs.media.pkazombie_legsSkin; + legs.hModel = cgs.media.pkazombie_legsModel; + legs.frame = frame; + trap_R_AddRefEntityToScene(&legs); + + // torso + AnglesToAxis( cent->lerpAngles, torso.axis ); + torso.customSkin = cgs.media.pkazombie_torsoSkin; + torso.hModel = cgs.media.pkazombie_torsoModel; + torso.frame = frame; + + CG_PositionRotatedEntityOnTag( &torso, &legs, legs.hModel, "tag_torso"); + trap_R_AddRefEntityToScene(&torso); + + // head + AnglesToAxis( cent->lerpAngles, head.axis ); + head.customSkin = cgs.media.pkazombie_headSkin; + head.hModel = cgs.media.pkazombie_headModel; + head.frame = frame; + + CG_PositionRotatedEntityOnTag( &head, &torso, torso.hModel, "tag_head"); + trap_R_AddRefEntityToScene(&head); + + return; + } + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; + + // flicker between two skins (FIXME?) + ent.skinNum = ( cg.time >> 6 ) & 1; + + // get the model, either as a bmodel or a modelindex + if ( s1->solid == SOLID_BMODEL ) { + ent.hModel = cgs.inlineDrawModel[s1->modelindex]; + } else { + ent.hModel = cgs.gameModels[s1->modelindex]; + } + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + + // add the secondary model + if ( s1->modelindex2 ) { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[s1->modelindex2]; + trap_R_AddRefEntityToScene(&ent); + } + +} + +/* +=============== +CG_Beam + +Also called as an event +=============== +*/ +void CG_Beam( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( s1->pos.trBase, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + AxisClear( ent.axis ); + ent.reType = RT_BEAM; + + ent.renderfx = RF_NOSHADOW; + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +=============== +CG_Portal +=============== +*/ +static void CG_Portal( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin ); + VectorCopy( s1->origin2, ent.oldorigin ); + ByteToDir( s1->eventParm, ent.axis[0] ); + PerpendicularVector( ent.axis[1], ent.axis[0] ); + + // negating this tends to get the directions like they want + // we really should have a camera roll value + VectorSubtract( vec3_origin, ent.axis[1], ent.axis[1] ); + + CrossProduct( ent.axis[0], ent.axis[1], ent.axis[2] ); + ent.reType = RT_PORTALSURFACE; + ent.oldframe = s1->powerups; + ent.frame = s1->frame; // rotation speed + ent.skinNum = s1->clientNum/256.0 * 360; // roll offset + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +================== +PKMOD - CG_BearTrap_Follow, Ergodic 05/30/00 modeled from CG_BearTrap +PKMOD - Ergodic 06/29/00 - modify code to be called by cg_player +PKMOD - Ergodic 07/01/00 - Add blood trails and random displacement +================== +*/ +#define MAX_BEARTRAPS_VIEWABLE 3 +#define MAX_BEARTRAP_DISPLACEMENT 3 +#define BEARTRAP_BLOOD_FREQUENCY 90 + + +static void CG_BeartTrap_BloodTrail( centity_t *cent ) { + localEntity_t *blood; + vec3_t origin; + + VectorCopy( cent->lerpOrigin, origin ); + origin[2] -= 8; + + //PKMOD - Ergodic 12/01/01 - debug vec3_origin (inactive) +// Com_Printf("CG_BearTraps_BloodTrail - vec3_origin>%s<\n", CG_vtos(vec3_origin)); + + blood = CG_SmokePuff( origin, vec3_origin, + 20, // radius + 1, 1, 1, 1, // color + 800, // trailTime + cg.time, // startTime + 0, //12/16/00 - add fadeInTime + 0, // flags + cgs.media.bloodTrailShader ); + + // use the optimized version + blood->leType = LE_FALL_SCALE_FADE; + // drop a total of 40 units over its lifetime + blood->pos.trDelta[2] = 10; +} + + +//PKMOD - Ergodic 07/05/00 add viewheight logic +void CG_BearTrap_Display ( centity_t *cent, int player_viewheight ) { + refEntity_t ent; + +//PKMOD Ergodic debug 06/28/00 +//Com_Printf("CG_BearTraps_Follow - cent->lerpAngles>%s<\n", CG_vtos(cent->lerpAngles)); + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + //Add random displacement + ent.origin[0] += (rand() % (MAX_BEARTRAP_DISPLACEMENT * 2 + 1) ) - MAX_BEARTRAP_DISPLACEMENT; + ent.origin[1] += (rand() % (MAX_BEARTRAP_DISPLACEMENT * 2 + 1) ) - MAX_BEARTRAP_DISPLACEMENT; + //PKMOD - Ergodic 07/05/00 add viewheight logic + //PKMOD - Ergodic 07/29/00 modify view height logic for death height + if (player_viewheight == DEFAULT_VIEWHEIGHT) + ent.origin[2] += (rand() % (MAX_BEARTRAP_DISPLACEMENT * 2 + 1) ) - MAX_BEARTRAP_DISPLACEMENT - 6; //above viewheight? + else if (player_viewheight == CROUCH_VIEWHEIGHT) + ent.origin[2] += (rand() % (MAX_BEARTRAP_DISPLACEMENT + 1) ) - (MAX_BEARTRAP_DISPLACEMENT + 24); //below viewheight? + else // else DEAD_VIEWHEIGHT + ent.origin[2] += (rand() % (MAX_BEARTRAP_DISPLACEMENT + 1) ) - (MAX_BEARTRAP_DISPLACEMENT + 29); //death height + + AnglesToAxis( cent->lerpAngles, ent.axis ); +// ent.reType = RT_SPRITE; + ent.hModel = cgs.media.pkabeartrapfollow; +// ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + + trap_R_AddRefEntityToScene( &ent ); + +} + +//PKMOD Ergodic 07/01/00 added more logic - multi-beartraps and blood +//PKMOD - Ergodic 07/05/00 add viewheight logic +//PKMOD - Ergodic 07/10/00 modified to place beartrap on bots +void CG_BearTraps_Follow( centity_t *cent ) { + int i; + int beartrap_count; + int player_viewheight; + +//PKMOD - Ergodic 07/10/00 unpack the beartraps_attached variable +//PKMOD - Ergodic 12/19/00 use time2 instead of angles2 +// player_viewheight = cent->currentState.angles2[BEARTRAPS_ATTACHED] / 100; +// beartrap_count = cent->currentState.angles2[BEARTRAPS_ATTACHED] - (player_viewheight * 100); + beartrap_count = cent->currentState.time2 & 3; //mask of max three beartraps + //PKMOD - Ergodic 07/07/01 - use new packing scheme to encode beartrap viewheight + player_viewheight = ( cent->currentState.time2 >> 2 ) & 3; //mask off the encoded viewheight + + if ( player_viewheight == 0 ) + player_viewheight = DEFAULT_VIEWHEIGHT; + else if ( player_viewheight == CROUCH_VIEWHEIGHT ) + player_viewheight = CROUCH_VIEWHEIGHT; + else + player_viewheight = DEAD_VIEWHEIGHT; + +// player_viewheight -= 50; //un-normalize the viewpoint that was done in bg_misc.c; +//PKMOD Ergodic debug 12/19/00 inactive +//Com_Printf("CG_BearTraps_Follow - cent->lerpAngles>%s<\n", CG_vtos(cent->lerpAngles)); + + for (i = 1; i <= beartrap_count; i++) { + if (i > MAX_BEARTRAPS_VIEWABLE) //don't display more than MAX_BEARTRAPS_VIEWABLE beartraps + break; + CG_BearTrap_Display ( cent, player_viewheight ); + } + + if (BEARTRAP_BLOOD_FREQUENCY > ( rand() % 100 ) ) + CG_BeartTrap_BloodTrail( cent ); + +} + + +/* +================== +PKMOD - CG_BearTrap, Ergodic 05/30/00 modeled from CG_ITEM +================== +*/ + +static void CG_BearTrap( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; + + //PKMOD - Ergodic 12/13/03 - add timing for animation + int msec; + + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + + //PKMOD - Ergodic 08/07/00 - set axis so that beartrap will fly flat + AxisClear( ent.axis ); + + + // spin as it moves +// if ( s1->pos.trType != TR_STATIONARY ) { +// RotateAroundDirection( ent.axis, cg.time / 4 ); +// } else { + RotateAroundDirection( ent.axis, s1->time ); +// } + + + //PKMOD - Ergodic 03/23/01 - add team parameters + switch ( cent->currentState.modelindex ) { + case TEAM_RED: + ent.hModel = cgs.media.pkabeartrap_red; + break; + case TEAM_BLUE: + ent.hModel = cgs.media.pkabeartrap_blue; + break; + default: + ent.hModel = cgs.media.pkabeartrap; + ent.frame = 0; + //PKMOD - Ergodic 07/17/03 - set animation frame based on entity duration. + // The beartrap model has 55 frames (0..54) of animation. + // + // Want to show 55 frames in 2500 milliseconds (2.5 second). + // Thus, each frame is shown for 2500/55 = ~45 milliseconds per frame + + //PKMOD - Ergodic 10/10/03 - [UPDATE] animation frame based on generic1 + + //PKMOD - Ergodic 10/10/03 - Co-opt the centity_t variables: + // PKA_RadiateTime :: last invisibility charge state; + // PKA_RadiateInfectTime :: time for particle effect; + + if ( s1->generic1 ) { + //here if Beartrap has a charge... + + //PKMOD - Ergodic 10/10/03 - Check if particles should be displayed + // Display particles if duration for particle effect is current + + //has the invisibility charge changed? + if ( s1->generic1 != cent->PKA_RadiateTime ) { + cent->PKA_RadiateTime = s1->generic1; //Set client invisibility state + cent->PKA_RadiateInfectTime = cg.time + 750; //every 3/4 seconds + } + + //is the invisibility duration still in effect? + if ( cent->PKA_RadiateInfectTime > cg.time ) { + //PKMOD - Ergodic 07/18/03 - calculate the position of the beartrap's "particle_tag" + int i; + orientation_t lerped; + vec3_t particles; + + //particle parameters + vec3_t vel; + int duration; + float x_offset; + float y_offset; + float speed; + int indx; + vec3_t spark_origin; + + /*+++++ + // Determine the location of the particles + //----*/ + //void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + // qhandle_t parentModel, char *tagName ) { + // lerp the tag (HERE: msec is the frame) + + //calculate particle frame based on generic1 with values of [0 ... MAX_INVISIBILITY_CHARGE] + // within a total of 55 frames of animation + msec = 55 * s1->generic1 / MAX_INVISIBILITY_CHARGE; + + ent.frame = msec; + + trap_R_LerpTag( &lerped, ent.hModel, msec, msec, 1.0 - ent.backlerp, "tag_particles" ); + VectorCopy( ent.origin, particles ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( particles, lerped.origin[i], ent.axis[i], particles ); + } + + /*+++++ + // Show the particles + //----*/ + VectorCopy(particles, spark_origin); + spark_origin[2] += 5; + + for ( indx = 1; indx < 2; indx++ ) { + vel[0] = 2 * crandom(); + vel[1] = 2 * crandom(); + //PKMOD - Ergodic 10/15/03 - force all particles up + vel[2] = 10 * random(); + + //PKMOD - Ergodic 10/15/03 - vary the duration + duration = 750 + 50 * crandom(); + + x_offset = 2 * crandom(); + y_offset = 2 * crandom(); + + speed = 5 * crandom(); + + //PKMOD - Ergodic 07/20/03 - draw midsized particles... + CG_ParticleSparks3 (spark_origin, vel, duration, x_offset, y_offset, speed); + } + } + + } + else //here if s1->generic1 + //PKMOD - Ergodic 07/17/03 - default msec to 0 + msec = 0; + + + + //PKMOD - Ergodic 07/18/03 - set custom shader for the invisibility process + // The Beartrap has 20 shaders to apply + // + // Want to show 20 shaders in 2500 milliseconds (2.5 seconds) + // Thus each shader is show for 2500/20 = ~125 milliseconds per shader + //PKMOD - Ergodic 08/18/03 - only show invisible shaders if beartrap hit the floor + + //PKMOD - Ergodic 10/10/04 - [UPDATE] shader frame based on generic1 + + //calculate particle frame based on generic1 with values of [0 ... MAX_INVISIBILITY_CHARGE] + // within a total of 20 invisibility shaders + msec = 20 * s1->generic1 / MAX_INVISIBILITY_CHARGE; + + switch ( msec ) { + case 0: + ent.customShader = cgs.media.pkainvisbeartrap1; + break; + case 1: + ent.customShader = cgs.media.pkainvisbeartrap2; + break; + case 2: + ent.customShader = cgs.media.pkainvisbeartrap3; + break; + case 3: + ent.customShader = cgs.media.pkainvisbeartrap4; + break; + case 4: + ent.customShader = cgs.media.pkainvisbeartrap5; + break; + case 5: + ent.customShader = cgs.media.pkainvisbeartrap6; + break; + case 6: + ent.customShader = cgs.media.pkainvisbeartrap7; + break; + case 7: + ent.customShader = cgs.media.pkainvisbeartrap8; + break; + case 8: + ent.customShader = cgs.media.pkainvisbeartrap9; + break; + case 9: + ent.customShader = cgs.media.pkainvisbeartrap10; + break; + case 10: + ent.customShader = cgs.media.pkainvisbeartrap11; + break; + case 11: + ent.customShader = cgs.media.pkainvisbeartrap12; + break; + case 12: + ent.customShader = cgs.media.pkainvisbeartrap13; + break; + case 13: + ent.customShader = cgs.media.pkainvisbeartrap14; + break; + case 14: + ent.customShader = cgs.media.pkainvisbeartrap15; + break; + case 15: + ent.customShader = cgs.media.pkainvisbeartrap16; + break; + case 16: + ent.customShader = cgs.media.pkainvisbeartrap17; + break; + case 17: + ent.customShader = cgs.media.pkainvisbeartrap18; + break; + case 18: + ent.customShader = cgs.media.pkainvisbeartrap19; + break; + default: + ent.customShader = cgs.media.pkainvisbeartrap20; + break; + } + break; + } + + + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + + // add to refresh list, possibly with quad glow + CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); +} + + +/* +================== +PKMOD - CG_AutoSentry, Ergodic 11/22/00 modeled from CG_BearTrap + +changes: Ergodic 05/31/02 - add new animated models +================== +*/ + +static void CG_AutoSentry( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; + vec3_t hold_dir; + + //PKMOD - Ergodic 11/25/00 add timing for animation + int msec; + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + //PKMOD Ergodic 11/26/00 + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + //PKMOD Ergodic debug 11/26/00 - inactive +// if ( (rand() % 1000) > 975 ) +// Com_Printf("CG_AutoSentry - lerpAngles>%s<\n", CG_vtos( cent->lerpAngles ) ); + + + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; +// ent.hModel = cgs.media.pkasentry_pickup; + + //PKMOD - Ergodic 11/25/00 add timing for animation + // every 0.15 seconds (150 milliseconds) + // if miscTime has not been set, by drop event, then model + // should default to folded type + + //PKMOD - Ergodic 06/01/02 - remove the following old animation code +// if ( cent->miscTime ) +// msec = ( cg.time - cent->miscTime ) / 150; +// else +// msec = 0; + + //PKMOD - Ergodic 06/01/02 - set animation frame based on entity duration. + // The autosentry model has 30 frames (1..30) of animation that is reversed + // in time. Frame 1 is the final animation frame and frame 30 is the first + // animation frame. + // + // each animation frame is 1/30 seconds in duration. + // 1/30 seconds ~ 35 MilliSeconds + // 30 frames * 35 MS/frame = 1050 milliseconds + if ( cent->miscTime ) + msec = ( cg.time - cent->miscTime ) / 35; + else + //PKMOD - Ergodic 10/22/02 - default msec to 0 + //msec = 29; //was 29 -> produced a deployed sentry when launched + msec = 0; + + //PKMOD - Ergodic 06/01/02 - calculate the frame + if ( msec > 29 ) + ent.frame = 0; + else + ent.frame = 29 - msec; + + //PKMOD Ergodic debug 06/02/02 - debug autosentry animations (inactive) +// if ( (rand() % 1000) > 900 ) +// Com_Printf("CG_AutoSentry - msec: %d, miscTime: %d\n", msec, cent->miscTime ); + + //PKMOD Ergodic debug 11/26/00 - inactive +// if ( (rand() % 1000) > 200 ) +// Com_Printf("CG_AutoSentry 0 - msec: %d, miscTime: %d\n", msec, cent->miscTime ); + + //PKMOD - Ergodic 03/20/01 - add team parameters + switch ( cent->currentState.modelindex ) { + case TEAM_RED: + ent.hModel = cgs.media.pkasentry_red; + break; + case TEAM_BLUE: + ent.hModel = cgs.media.pkasentry_blue; + break; + default: + ent.hModel = cgs.media.pkasentry; + break; + } + + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + + // convert direction of travel into axis +// if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { +// ent.axis[0][1] = 1; +// ent.axis[0][1] = 1; +// ent.axis[0][0] = 1; +// } + + //PKMOD - Ergodic 08/07/00 - set axis so that autosentry will fly flat +// AxisClear( ent.axis ); + + //PKMOD Ergodic 11/26/00 - set the initial direction +// VectorCopy( cent->lerpAngles, ent.axis[0] ); +// RotateAroundDirection( ent.axis, s1->time ); + + +// hold_dir[0] = cent->lerpAngles[0]; +// hold_dir[1] = cent->lerpAngles[1]; +// hold_dir[2] = 0; + +// if ( VectorNormalize2( hold_dir, ent.axis[0] ) == 0 ) { +// ent.axis[0][2] = 1; +// } + + + //PKMOD Ergodic 11/27/00 - Finally - This works! + vectoangles( cent->lerpAngles, hold_dir); + hold_dir[0] = 0; + hold_dir[1] -= 90; //offset + hold_dir[2] = 0; + AnglesToAxis( hold_dir, ent.axis ); + + //PKMOD Ergodic 11/26/00 - remove this +// AxisCopy( cg.autoAxis, ent.axis ); + + //PKMOD Ergodic 11/26/00 - remove this +// RotateAroundDirection( ent.axis, hold_dir[YAW] ); + + //PKMOD Ergodic 11/26/00 - add entity to scene + trap_R_AddRefEntityToScene (&ent); + + + // add to refresh list, possibly with quad glow +// CG_AddRefEntityWithPowerups( &ent, s1->powerups, TEAM_FREE ); +} + +/* +================== +PKMOD - CG_AutoSentry_Base, Ergodic 12/02/00 modeled from CG_AutoSentry +changes: Ergodic 12/13/03 - install invisibility code +================== +*/ + +static void CG_AutoSentry_Base( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; + vec3_t hold_dir; + + //PKMOD - Ergodic 12/13/03 - add timing for animation + int msec; + + //PKMOD Ergodic debug 12/02/00 - inactive +// if ( (rand() % 1000) > 900 ) +// Com_Printf( "CG_AutoSentry_Base\n" ); + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + //PKMOD Ergodic 11/26/00 + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + //PKMOD Ergodic debug 11/26/00 - inactive +// if ( (rand() % 1000) > 975 ) +// Com_Printf("CG_AutoSentry - lerpAngles>%s<\n", CG_vtos( cent->lerpAngles ) ); + + + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + + //PKMOD - Ergodic 03/20/01 - add team parameters + switch ( cent->currentState.modelindex ) { + case TEAM_RED: + ent.hModel = cgs.media.pkasentry_base_red; + break; + case TEAM_BLUE: + ent.hModel = cgs.media.pkasentry_base_blue; + break; + default: + ent.hModel = cgs.media.pkasentry_base; + //PKMOD - Ergodic 10/10/03 - Install invisibility function + // Autosentry_Base will not show particles + + //PKMOD - Ergodic 07/18/03 - set custom shader for the invisibility process + // The Autosentry has 20 shaders to apply + // + // Want to show 20 shaders in 2500 milliseconds (2.5 seconds) + // Thus each shader is show for 2500/20 = ~125 milliseconds per shader + //PKMOD - Ergodic 08/18/03 - only show invisible shaders if beartrap hit the floor + + //PKMOD - Ergodic 10/10/04 - [UPDATE] shader frame based on generic1 + + //calculate particle frame based on generic1 with values of [0 ... MAX_INVISIBILITY_CHARGE] + // within a total of 20 invisibility shaders + msec = 20 * s1->generic1 / MAX_INVISIBILITY_CHARGE; + + switch ( msec ) { + case 0: + ent.customShader = cgs.media.pkainvisautosentry1; + break; + case 1: + ent.customShader = cgs.media.pkainvisautosentry2; + break; + case 2: + ent.customShader = cgs.media.pkainvisautosentry3; + break; + case 3: + ent.customShader = cgs.media.pkainvisautosentry4; + break; + case 4: + ent.customShader = cgs.media.pkainvisautosentry5; + break; + case 5: + ent.customShader = cgs.media.pkainvisautosentry6; + break; + case 6: + ent.customShader = cgs.media.pkainvisautosentry7; + break; + case 7: + ent.customShader = cgs.media.pkainvisautosentry8; + break; + case 8: + ent.customShader = cgs.media.pkainvisautosentry9; + break; + case 9: + ent.customShader = cgs.media.pkainvisautosentry10; + break; + case 10: + ent.customShader = cgs.media.pkainvisautosentry11; + break; + case 11: + ent.customShader = cgs.media.pkainvisautosentry12; + break; + case 12: + ent.customShader = cgs.media.pkainvisautosentry13; + break; + case 13: + ent.customShader = cgs.media.pkainvisautosentry14; + break; + case 14: + ent.customShader = cgs.media.pkainvisautosentry15; + break; + case 15: + ent.customShader = cgs.media.pkainvisautosentry16; + break; + case 16: + ent.customShader = cgs.media.pkainvisautosentry17; + break; + case 17: + ent.customShader = cgs.media.pkainvisautosentry18; + break; + case 18: + ent.customShader = cgs.media.pkainvisautosentry19; + break; + default: + ent.customShader = cgs.media.pkainvisautosentry20; + break; + } + break; + } + + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + + //PKMOD Ergodic 11/27/00 - Finally - This works! + vectoangles( cent->lerpAngles, hold_dir); + hold_dir[0] = 0; + hold_dir[1] -= 90; //offset + hold_dir[2] = 0; + AnglesToAxis( hold_dir, ent.axis ); + + //PKMOD Ergodic 11/26/00 - add entity to scene + trap_R_AddRefEntityToScene (&ent); + + // add to refresh list, possibly with quad glow +// CG_AddRefEntityWithPowerups( &ent, s1->powerups, TEAM_FREE ); +} + + +/* +================== +PKMOD - CG_AutoSentry_Turret, Ergodic 12/02/00 modeled from CG_AutoSentry + modified 12/28/00 to add flash when firing +changes: Ergodic 12/13/03 - install invisibility code +================== +*/ + +static void CG_AutoSentry_Turret( centity_t *cent ) { + refEntity_t turret; + refEntity_t flash; + entityState_t *s1; + const weaponInfo_t *weapon; +// vec3_t hold_dir; //020522 - do not need +// vec3_t hold_autosentry_forward; //020522 - do not need +// int debug_yaw; + vec3_t angles; + //PKMOD - Ergodic 04/12/01 - add quad effects + qhandle_t quadShader; +// float hold_angle; +// vec3_t hold_angles; //020522 - debug variable (inactive) + //PKMOD - Ergodic 12/13/01 add timing for invisible shaders + int msec; + + //PKMOD Ergodic debug 12/02/00 - inactive +// if ( (rand() % 1000) > 900 ) +// Com_Printf( "CG_AutoSentry_Turret\n" ); + + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + //PKMOD Ergodic 11/26/00 + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + //PKMOD - Ergodic 05/22/02 - do not need +// AngleVectorsForward( cent->lerpAngles, hold_autosentry_forward ); + + //PKMOD Ergodic debug 05/18/02 (inactive) +// if ( (rand() % 1000) > 975 ) { +// VectorScale( s1->angles, 100, hold_angles); +// Com_Printf("CG_AutoSentry - lerpAngles>%s<, hold_angles>%s<\n", CG_vtos( cent->lerpAngles ), CG_vtos( hold_angles ) ); +// } + + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } + + // create the render entity + memset (&turret, 0, sizeof(turret)); + VectorCopy( cent->lerpOrigin, turret.origin); + VectorCopy( cent->lerpOrigin, turret.oldorigin); + + // setup skins + turret.skinNum = cg.clientFrame & 1; + turret.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + + //PKMOD - Ergodic 03/20/01 - add team parameters + switch ( cent->currentState.modelindex ) { + case TEAM_RED: + quadShader = cgs.media.redQuadShader; + turret.hModel = cgs.media.pkasentry_turret_red; + break; + case TEAM_BLUE: + quadShader = cgs.media.quadShader; + turret.hModel = cgs.media.pkasentry_turret_blue; + break; + default: + quadShader = cgs.media.quadShader; + turret.hModel = cgs.media.pkasentry_turret; + //PKMOD - Ergodic 10/10/03 - Install invisibility function + // Co-opt the centity_t variables: + // PKA_RadiateTime :: last invisibility charge state; + // PKA_RadiateInfectTime :: time for particle effect; + + //PKMOD Ergodic 12/13/02 - debug invisibility (inactive) + //Com_Printf("CG_AutoSentry_Turret - invisibility charge>%d<\n", s1->generic1 ); + + if ( s1->generic1 ) { + //here if autosentry has a charge... + + //PKMOD - Ergodic 10/10/03 - Check if particles should be displayed + // Display particles if duration for particle effect is current + + //has the invisibility charge changed? + if ( s1->generic1 != cent->PKA_RadiateTime ) { + cent->PKA_RadiateTime = s1->generic1; //Set client invisibility state + cent->PKA_RadiateInfectTime = cg.time + 750; //every 3/4 seconds + } + + //is the invisibility duration still in effect? + if ( cent->PKA_RadiateInfectTime > cg.time ) { + //PKMOD - Ergodic 07/18/03 - calculate the position of the beartrap's "particle_tag" + int i; + orientation_t lerped; + vec3_t particles; + + //particle parameters + vec3_t vel; + int duration; + float x_offset; + float y_offset; + float speed; + int indx; + vec3_t spark_origin; + + /*+++++ + // Determine the location of the particles + //----*/ + if ( rand() % 2 ) + trap_R_LerpTag( &lerped, turret.hModel, msec, msec, 1.0 - turret.backlerp, "tag_flash_1" ); + else + trap_R_LerpTag( &lerped, turret.hModel, msec, msec, 1.0 - turret.backlerp, "tag_flash_2" ); + VectorCopy( turret.origin, particles ); + for ( i = 0 ; i < 3 ; i++ ) { + VectorMA( particles, lerped.origin[i], turret.axis[i], particles ); + } + + /*+++++ + // Show the particles + //----*/ + VectorCopy(particles, spark_origin); + spark_origin[2] += 5; + + //PKMOD - Ergodic 12/13/03 - add more particles from 2 to 4 + for ( indx = 1; indx < 4; indx++ ) { + vel[0] = 2 * crandom(); + vel[1] = 2 * crandom(); + //PKMOD - Ergodic 10/15/03 - force all particles up + vel[2] = 10 * random(); + + //PKMOD - Ergodic 10/15/03 - vary the duration + duration = 750 + 50 * crandom(); + + x_offset = 2 * crandom(); + y_offset = 2 * crandom(); + + speed = 5 * crandom(); + + //PKMOD - Ergodic 07/20/03 - draw midsized particles... + CG_ParticleSparks3 (spark_origin, vel, duration, x_offset, y_offset, speed); + } + } + + } + else //here if s1->generic1 + //PKMOD - Ergodic 07/17/03 - default msec to 0 + msec = 0; + + + + //PKMOD - Ergodic 07/18/03 - set custom shader for the invisibility process + // The Autosentry has 20 shaders to apply + // + // Want to show 20 shaders in 2500 milliseconds (2.5 seconds) + // Thus each shader is show for 2500/20 = ~125 milliseconds per shader + //PKMOD - Ergodic 08/18/03 - only show invisible shaders if beartrap hit the floor + + //PKMOD - Ergodic 10/10/04 - [UPDATE] shader frame based on generic1 + + //calculate particle frame based on generic1 with values of [0 ... MAX_INVISIBILITY_CHARGE] + // within a total of 20 invisibility shaders + msec = 20 * s1->generic1 / MAX_INVISIBILITY_CHARGE; + + switch ( msec ) { + case 0: + turret.customShader = cgs.media.pkainvisautosentry1; + break; + case 1: + turret.customShader = cgs.media.pkainvisautosentry2; + break; + case 2: + turret.customShader = cgs.media.pkainvisautosentry3; + break; + case 3: + turret.customShader = cgs.media.pkainvisautosentry4; + break; + case 4: + turret.customShader = cgs.media.pkainvisautosentry5; + break; + case 5: + turret.customShader = cgs.media.pkainvisautosentry6; + break; + case 6: + turret.customShader = cgs.media.pkainvisautosentry7; + break; + case 7: + turret.customShader = cgs.media.pkainvisautosentry8; + break; + case 8: + turret.customShader = cgs.media.pkainvisautosentry9; + break; + case 9: + turret.customShader = cgs.media.pkainvisautosentry10; + break; + case 10: + turret.customShader = cgs.media.pkainvisautosentry11; + break; + case 11: + turret.customShader = cgs.media.pkainvisautosentry12; + break; + case 12: + turret.customShader = cgs.media.pkainvisautosentry13; + break; + case 13: + turret.customShader = cgs.media.pkainvisautosentry14; + break; + case 14: + turret.customShader = cgs.media.pkainvisautosentry15; + break; + case 15: + turret.customShader = cgs.media.pkainvisautosentry16; + break; + case 16: + turret.customShader = cgs.media.pkainvisautosentry17; + break; + case 17: + turret.customShader = cgs.media.pkainvisautosentry18; + break; + case 18: + turret.customShader = cgs.media.pkainvisautosentry19; + break; + default: + turret.customShader = cgs.media.pkainvisautosentry20; + break; + } + break; + } + + //PKMOD Ergodic 11/27/00 - Finally - This works! + //PKMOD - Ergodic 02/09/02 - modify this +// vectoangles( cent->lerpAngles, hold_dir); + + //PKMOD Ergodic 12/02/00 - debug inactive +// if ( (rand() % 1000) > 975 ) { +// debug_yaw = hold_dir[1]; +// Com_Printf("CG_AutoSentry_Turret - debug_yaw>%d<\n", debug_yaw ); +// } + + //PKMOD - Ergodic 05/17/02 - no need to touch the angles.. +// hold_dir[0] += 30; +// hold_dir[1] -= 90; //offset +// hold_dir[2] -= 30; +// AnglesToAxis( hold_dir, turret.axis ); + //PKMOD - Ergodic 02/18/02 - add this... +// AngleVectors( hold_dir, turret.axis[0], turret.axis[1], turret.axis[2] ); + + //PKMOD - 02/08/02 - new axis code +// if ( VectorNormalize2( cent->lerpAngles, turret.axis[0] ) == 0 ) { +// turret.axis[0][2] = 1; +// } + +//>>>>> + //PKMOD - Ergodic 05/19/02 - new axis code (revisited) + // convert direction of travel into axis +// if ( VectorNormalize2( hold_dir, turret.axis[0] ) == 0 ) { +// turret.axis[0][2] = 1; +// } + +// AngleVectors (hold_angles, hold_forward, hold_right, hold_up); +// vectorCopy( cent->lerpAngles, hold_dir); + AnglesToAxis( cent->lerpAngles, turret.axis ); + +// AngleVectorsForward( cent->lerpAngles, hold_autosentry_forward ); +// AnglesToAxis( hold_autosentry_forward, turret.axis ); + trap_R_AddRefEntityToScene ( &turret ); + +// RotateAroundDirection( turret.axis, s1->time ); + + // add to refresh list, possibly with quad glow +// CG_AddRefEntityWithPowerups( &turret, s1, TEAM_FREE ); +//>>>>> + + //PKMOD Ergodic 06/29/01 - use pos.trDelta to compute angle + // convert direction of travel into axis +// if ( VectorNormalize2( s1->pos.trDelta, turret.axis[0] ) == 0 ) { +// turret.axis[0][2] = 1; +// } + +// vectoangles( s1->pos.trDelta, hold_dir ); + //PKMOD - Ergodic 02/18/02 - remove this... +// AnglesToAxis( hold_dir, turret.axis ); + + + + //PKMOD Ergodic 11/26/00 - add entity to scene +// trap_R_AddRefEntityToScene ( &turret ); + + // add to refresh list, possibly with quad glow +// CG_AddRefEntityWithPowerups( &ent, s1->powerups, TEAM_FREE ); + + //PKMOD - Ergodic 04/12/01 - add quad effects if enabled in co-opted variable:time2 + if ( s1->time2 & 2 ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); + turret.customShader = quadShader; + trap_R_AddRefEntityToScene( &turret ); + } + + //PKMOD - Ergodic 12/28/00 - add firing logic (co-opt the time2 variable) + //PKMOD - Ergodic 04/12/01 - modify so that time2 will be a packed variable + if ( !( s1->time2 & 1) ) //if not firing then return + return; + + memset( &flash, 0, sizeof( flash ) ); + VectorCopy( cent->lerpOrigin, flash.lightingOrigin ); +// flash.shadowPlane = RF_SHADOW_PLANE; + flash.renderfx = RF_MINLIGHT; + + flash.hModel = cgs.media.autosentryFlashModel; + cent->muzzleFlashTime = cg.time + 100; + + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = crandom() * 10; + AnglesToAxis( angles, flash.axis ); + + if ( random() > 0.5 ) //50 Percent + CG_PositionRotatedEntityOnTag( &flash, &turret, cgs.media.pkasentry_turret, "tag_flash_1"); + else + CG_PositionRotatedEntityOnTag( &flash, &turret, cgs.media.pkasentry_turret, "tag_flash_2"); + + trap_R_AddRefEntityToScene( &flash ); + + +} + + +/* +=============== +PKMOD - CG_ChainShaft, Ergodic 07/12/00 display the inter-player lightning shafts + + loosely based on CG_LightningBolt + +Origin will be the exact tag point, which is slightly +different than the muzzle point used for determining hits. +The cent should be the non-predicted cent if it is from the player, +so the endpoint will reflect the simulated strike (lagging the predicted +angle) +=============== +*/ + +//Ergodic - 07/12/00 this code is superceded by CG_ChainLightning and should be removed +void CG_ChainShaft( entityState_t *es ) { + refEntity_t beam; + vec3_t angles; + vec3_t dir; + + //PKMOD Ergodic debug 07/12/00 +//Com_Printf("CG_ChainShaft - origin>%s<, angles>%s<\n", CG_vtos(es->origin), CG_vtos(es->angles)); + + + memset( &beam, 0, sizeof( beam ) ); + + VectorCopy( es->origin, beam.origin ); + VectorCopy( es->angles, beam.oldorigin ); //PKMOD - Ergodic 07/12/00 - co-opt hack: angles = target->origin + +//PKMOD - Ergodic 08/21/00 change flash to railgun style to make a tighter beam + beam.reType = RT_RAIL_CORE; +// beam.reType = RT_LIGHTNING; + //PKMOD - Ergodic 08/21/00 set the shader to chainlightning + beam.customShader = cgs.media.chainlightningShader; + trap_R_AddRefEntityToScene( &beam ); + + // add the impact flare if it hit something + + VectorSubtract( beam.oldorigin, beam.origin, dir ); + VectorNormalize( dir ); + + memset( &beam, 0, sizeof( beam ) ); + beam.hModel = cgs.media.lightningExplosionModel; + + VectorMA( beam.oldorigin, -16, dir, beam.origin ); + + // make a random orientation + angles[0] = rand() % 360; + angles[1] = rand() % 360; + angles[2] = rand() % 360; + AnglesToAxis( angles, beam.axis ); + trap_R_AddRefEntityToScene( &beam ); + +} + +void CG_ChainLightning( centity_t *cent ) { + refEntity_t beam; + entityState_t *es; + vec3_t angles; +// vec3_t dir; //05/12/02 - don't need this variable + + es = ¢->currentState; +//PKMOD - Ergodic 07/12/00 - debug inactive +//Com_Printf("CG_ChainShaft - start\n"); +// if ( (rand() % 1000) > 950 ) +// Com_Printf("CG_ChainLightning - origin>%s<, angles>%s<\n", CG_vtos(cent->lerpOrigin), CG_vtos(es->angles)); + + //PKMOD - Ergodic 05/12/02 - debug 000 location of clg beam (inactive) +// Com_Printf("CG_ChainLightning - time>%d<, origin>%s<, angles>%s<\n", cg.time, CG_vtos(cent->lerpOrigin), CG_vtos(es->angles)); + + + memset( &beam, 0, sizeof( beam ) ); + + VectorCopy( cent->lerpOrigin, beam.origin ); + VectorCopy( es->angles, beam.oldorigin ); //PKMOD - Ergodic 07/12/00 - co-opt hack: angles = target->origin + +//PKMOD - Ergodic 08/21/00 change flash to railgun style to make a tighter beam + beam.reType = RT_RAIL_CORE; +// beam.reType = RT_LIGHTNING; + //PKMOD - Ergodic 08/21/00 set the shader to chainlightning + beam.customShader = cgs.media.chainlightningShader; + trap_R_AddRefEntityToScene( &beam ); + + // add the impact flare if it hit something + + //PKMOD - Ergodic 05/12/02 - don't need to do these next two steps +// VectorSubtract( beam.oldorigin, beam.origin, dir ); +// VectorNormalize( dir ); + + memset( &beam, 0, sizeof( beam ) ); + //PKMOD - Ergodic 05/12/02 - reinitialize the location of lightning explosion + VectorCopy( es->angles, beam.origin ); //PKMOD - Ergodic 07/12/00 - co-opt hack: angles = target->origin + + beam.hModel = cgs.media.lightningExplosionModel; + + //test comment 12/05/00 +// VectorMA( beam.oldorigin, -16, dir, beam.origin ); + + // make a random orientation + angles[0] = rand() % 360; + angles[1] = rand() % 360; + angles[2] = rand() % 360; + AnglesToAxis( angles, beam.axis ); + trap_R_AddRefEntityToScene( &beam ); + +} + +//PKMOD - Ergodic 08/20/032 - copy of CG_ChainLightning for use in shooter lightning effect +void CG_ShooterLightning( centity_t *cent ) { + refEntity_t beam; + entityState_t *es; + vec3_t angles; +// vec3_t dir; //05/12/02 - don't need this variable + + es = ¢->currentState; +//PKMOD - Ergodic 07/12/00 - debug inactive +//Com_Printf("CG_ChainShaft - start\n"); +// if ( (rand() % 1000) > 950 ) +// Com_Printf("CG_ChainLightning - origin>%s<, angles>%s<\n", CG_vtos(cent->lerpOrigin), CG_vtos(es->angles)); + + //PKMOD - Ergodic 05/12/02 - debug 000 location of clg beam (inactive) +// Com_Printf("CG_ChainLightning - time>%d<, origin>%s<, angles>%s<\n", cg.time, CG_vtos(cent->lerpOrigin), CG_vtos(es->angles)); + + + memset( &beam, 0, sizeof( beam ) ); + + VectorCopy( cent->lerpOrigin, beam.origin ); + VectorCopy( es->angles, beam.oldorigin ); //PKMOD - Ergodic 07/12/00 - co-opt hack: angles = target->origin + +//PKMOD - Ergodic 08/21/00 change flash to railgun style to make a tighter beam + beam.reType = RT_RAIL_CORE; +// beam.reType = RT_LIGHTNING; + //PKMOD - Ergodic 08/21/00 set the shader to chainlightning + beam.customShader = cgs.media.shooterlightningShader; + trap_R_AddRefEntityToScene( &beam ); + + // add the impact flare if it hit something + + //PKMOD - Ergodic 05/12/02 - don't need to do these next two steps +// VectorSubtract( beam.oldorigin, beam.origin, dir ); +// VectorNormalize( dir ); + + memset( &beam, 0, sizeof( beam ) ); + //PKMOD - Ergodic 05/12/02 - reinitialize the location of lightning explosion + VectorCopy( es->angles, beam.origin ); //PKMOD - Ergodic 07/12/00 - co-opt hack: angles = target->origin + + beam.hModel = cgs.media.lightningExplosionModel; + + //test comment 12/05/00 +// VectorMA( beam.oldorigin, -16, dir, beam.origin ); + + // make a random orientation + angles[0] = rand() % 360; + angles[1] = rand() % 360; + angles[2] = rand() % 360; + AnglesToAxis( angles, beam.axis ); + trap_R_AddRefEntityToScene( &beam ); + +} + + +/* +================== +PKMOD - CG_Nail, Ergodic 08/03/00 display the Nail +================== +*/ +static void CG_Nail( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + memset (&ent, 0, sizeof(ent)); + + // set frame + + ent.frame = s1->frame; + ent.oldframe = ent.frame; + ent.backlerp = 0; + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + ent.hModel = cgs.media.nail1; + + +// VectorSet(ent.axis[1], 0,0,0); +// VectorSet(ent.axis[2], 0,0,0); + + VectorCopy( s1->apos.trBase, ent.axis[0] ); + RotateAroundDirection( ent.axis, s1->time ); + + //Ergodic debug - 08/04/00 - inactive +// Com_Printf("CG_Nail - ent.axis[0]>%s<, [1]>%s<, [2]>%s<\n", CG_vtos(ent.axis[0]), CG_vtos(ent.axis[1]), CG_vtos(ent.axis[2]) ); + + + // add to refresh list + trap_R_AddRefEntityToScene (&ent); +} + +/* +================== +PKMOD - CG_Dragon_Deploy, Ergodic 03/14/01 - add dragon deployable model + + Will display the deployed model after it is launched form the dragon +================== +*/ + +static void CG_Dragon_Deploy( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + const weaponInfo_t *weapon; //weapon == dragon + const weaponInfo_t *weapon_deployed; //weapon2 == deployable weapon + vec3_t hold_dir; + + + s1 = ¢->currentState; + if ( s1->weapon > WP_NUM_WEAPONS ) { + s1->weapon = 0; + } + weapon = &cg_weapons[s1->weapon]; + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + // add trails + if ( weapon->missileTrailFunc ) + { + weapon->missileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->missileDlight ) { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->missileDlight, + weapon->missileDlightColor[0], weapon->missileDlightColor[1], weapon->missileDlightColor[2] ); + } + + // add missile sound + if ( weapon->missileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + //PKMOD - Ergodic 02/14/02 - add bladewhirl sound if gauntlet, else play standard sound + if ( ( s1->generic1 & 15 ) == WP_GAUNTLET ) + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->flashSound[3] ); + else + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->missileSound ); + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + + //get the deployed weapon's information + weapon_deployed = &cg_weapons[ s1->generic1 & 15 ]; + + //PKMOD - Ergodic 08/07/00 - set axis so that beartrap will fly flat + AxisClear( ent.axis ); + + //add the appropriate model + switch ( s1->generic1 & 15 ) { + case WP_GRAVITY: + ent.hModel = weapon_deployed->weaponModel; + //PKMOD - Ergodic 03/01/01 - debug (inactive) +// Com_Printf( "CG_Dragon_Deploy - WP_GRAVITY\n" ); + break; + case WP_SENTRY: +// ent.hModel = weapon_deployed->weaponModel; + //PKMOD - Ergodic 03/01/01 - debug (inactive) +// Com_Printf( "CG_Dragon_Deploy - WP_SENTRY\n" ); + //PKMOD - Ergodic 07/11/01 - rotate autosentry by 90 degrees + // calculate the axis + vectoangles( cent->lerpAngles, hold_dir); + hold_dir[0] = 0; + hold_dir[1] -= 90; //offset + hold_dir[2] = 0; + AnglesToAxis( hold_dir, ent.axis ); + + //PKMOD - Ergodic 06/02/02 - set the autosentry frame + ent.frame = 29; + + //PKMOD - Ergodic 06/23/01 - add team parameters + switch ( cent->currentState.modelindex ) { + case TEAM_RED: + ent.hModel = cgs.media.pkasentry_red; + break; + case TEAM_BLUE: + ent.hModel = cgs.media.pkasentry_blue; + break; + default: + ent.hModel = cgs.media.pkasentry; + break; + } + break; + case WP_BEARTRAP: + ent.hModel = cgs.media.pkabeartrap; + //PKMOD - Ergodic 03/01/01 - debug (inactive) +// Com_Printf( "CG_Dragon_Deploy - WP_BEARTRAP\n" ); + //PKMOD - Ergodic 06/23/01 - add team parameters + switch ( cent->currentState.modelindex ) { + case TEAM_RED: + ent.hModel = cgs.media.pkabeartrap_red; + break; + case TEAM_BLUE: + ent.hModel = cgs.media.pkabeartrap_blue; + break; + default: + ent.hModel = cgs.media.pkabeartrap; + break; + } + break; + case WP_BEANS: + ent.hModel = weapon_deployed->weaponModel; + //PKMOD - Ergodic 03/01/01 - debug (inactive) +// Com_Printf( "CG_Dragon_Deploy - WP_BEANS\n" ); + break; + + //PKMOD - Ergodic 08/29/01 - add case for deploying flag + case PW_REDFLAG: + //PKMOD - Ergodic 03/04/01 - debug (inactive) +// Com_Printf( "CG_Dragon_Deploy - PW_REDFLAG\n" ); + ent.hModel = cgs.media.redFlagModel; + break; + + //PKMOD - Ergodic 08/29/01 - add case for deploying flag + case PW_BLUEFLAG: + //PKMOD - Ergodic 03/04/01 - debug (inactive) +// Com_Printf( "CG_Dragon_Deploy - PW_BLUEFLAG\n" ); + ent.hModel = cgs.media.blueFlagModel; + break; + + //PKMOD - Ergodic 12/15/01 - add case for deploying gauntlet + case WP_GAUNTLET: + //PKMOD - Ergodic 12/15/01 - debug (inactive) +// Com_Printf( "CG_Dragon_Deploy - WP_GAUNTLET\n" ); + //PKMOD - Ergodic 12/16/01 - add new model for repositioned deployed gauntlet blade + //PKMOD - Ergodic 12/17/01 - use the barrel model + weapon = &cg_weapons[ WP_GAUNTLET ]; + vectoangles( cent->lerpAngles, hold_dir); + //PKMOD - Ergodic 12/20/01 - rotate the blade to the proper angle + hold_dir[PITCH] += 90; + AnglesToAxis( hold_dir, ent.axis ); + + //PKMOD - Ergodic 12/17/01 - use the barrel model + ent.hModel = weapon->barrelModel; + break; + + default: //exit since current weapon can not be deployed (default to tongue) + ent.hModel = weapon->weaponModel; + //PKMOD - Ergodic 03/01/01 - debug (inactive) +// Com_Printf( "CG_Weapon_DragonDeploy - invalid weapon for dragon deploy\n" ); + break; + } + + + ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; + + // convert direction of travel into axis +// if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { +// ent.axis[0][1] = 1; +// ent.axis[0][1] = 1; +// ent.axis[0][0] = 1; +// } + + + // spin as it moves (removed 07/11/01) +// RotateAroundDirection( ent.axis, s1->time ); + + // add to refresh list, possibly with quad glow + CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); +} + +/* +==================== +PKMOD - Ergodic 11/02/02 - Code was moved from cg_localents to fix Invisible Gravity Well Bug + Timing logic was copied from cg_autosentry +==================== +*/ +void CG_GravityWell_Activate( centity_t *cent ) { + refEntity_t orb; + entityState_t *s1; + + float c; + vec3_t axis[3]; + vec3_t hold_dir; + int t; + float dynamic_lumens; + vec3_t dynamic_color; + int frame; + float size; + float wsize; + //particle parameters + vec3_t vel; + int duration; + float x_offset; + float y_offset; + float speed; + int indx; + vec3_t spark_origin; + + //get entity state information + s1 = ¢->currentState; + + // create the render entity + memset (&orb, 0, sizeof(orb)); + VectorCopy( cent->lerpOrigin, orb.origin); + VectorCopy( cent->lerpOrigin, orb.oldorigin); +// orb.reType = RT_MODEL; + orb.hModel = cgs.media.pkagravitywelluniverse; + //03/18/01 - add a shader +// orb.customShader = cgs.media.quadShader; + orb.skinNum = cg.clientFrame & 1; + orb.renderfx = RF_MINLIGHT | RF_NOSHADOW; + + // calculate the axis + VectorClear( hold_dir ); + AnglesToAxis( hold_dir, axis ); + + //PKMOD - Ergodic 11/02/02 - debug invisible GW (inactive) +// Com_Printf( "CG_GravityWell_Activate - cg.time>%d<, time2>%d<, cent->miscTime>%d<\n", cg.time, s1->time2, cent->miscTime ); + + //PKMOD - Ergodic 11/03/02 - Use the server's level.time to drive the Gravity Well animations + // this will address the invisible Gravity Well bug + t = cg.time - s1->time2; + //calculate the time + //if ( cent->miscTime ) + // t = cg.time - cent->miscTime; + //else + //PKMOD - Ergodic 10/22/02 - default msec to 0 + //msec = 29; //was 29 -> produced a deployed sentry when launched + //PKMOD - Ergodic 11/02/02 - debug invisible GW + // t = 0; + // t = 1000; + + //PKMOD - Ergodic 03/26/01 - add dynamic lighting + MAKERGB( dynamic_color, 0.5f, 0.5f, 1.f ); + + //++++++++++++++++++++++ + // First Expansion + //++++++++++++++++++++++ + if (t >= GWELL_EXPAND_STARTTIME_1 && t < GWELL_EXPAND_ENDTIME_1) { + +// if (!(le->leFlags & LEF_SOUND1)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); +//pkmod trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); +// le->leFlags |= LEF_SOUND1; +// } + + //PKMOD - Ergodic 03/26/01 set model + orb.hModel = cgs.media.pkagravitywelluniverse; + + size = (float)(t - GWELL_EXPAND_STARTTIME_1) / (float)(GWELL_EXPAND_ENDTIME_1 - GWELL_EXPAND_STARTTIME_1); + //PKMOD - Ergodic 02/27/01 - Debug force to One (inactive) +// c = 0.25f; + //PKMOD - Ergodic 10/13/02 - Add new animated gravity well model (resize) + size = size / 3.0; + + + //PKMOD - Ergodic 03/26/01 - add dynamic lighting + dynamic_lumens = 50 + 100 * size; + trap_R_AddLightToScene(orb.origin, dynamic_lumens, dynamic_color[0], dynamic_color[1], dynamic_color[2] ); + + VectorScale( axis[0], size * GWELL_ORB_RADIUS_1, orb.axis[0] ); + VectorScale( axis[1], size * GWELL_ORB_RADIUS_1, orb.axis[1] ); + VectorScale( axis[2], size * GWELL_ORB_RADIUS_1, orb.axis[2] ); + orb.nonNormalizedAxes = qtrue; + + if (t > GWELL_EXPAND_FADETIME_1) { + c = (float)(t - GWELL_EXPAND_FADETIME_1) / (float)(GWELL_EXPAND_ENDTIME_1 - GWELL_EXPAND_FADETIME_1); + } + else { + c = 0; + } + c *= 0xff; + orb.shaderRGBA[0] = 0xff - c; + orb.shaderRGBA[1] = 0xff - c; + orb.shaderRGBA[2] = 0xff - c; + orb.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &orb ); + return;// 03/25/01 - exit + } + + //++++++++++++++++++++++ + // First Contraction + //++++++++++++++++++++++ + if (t >= GWELL_CONTRACTION_STARTTIME_1 && t < GWELL_CONTRACTION_ENDTIME_1) { + +// if (!(le->leFlags & LEF_SOUND1)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); +//pkmod trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); +// le->leFlags |= LEF_SOUND1; +// } + + //PKMOD - Ergodic 03/26/01 set model +// orb.hModel = cgs.media.pkagravitywell_contraction; + //PKMOD - Ergodic 10/13/02 - Add new animated gravity well model + orb.hModel = cgs.media.pkagravitywelluniverse; + + + size = 0.75 + 0.25 * (float)(GWELL_CONTRACTION_ENDTIME_1 - t) / (float)(GWELL_CONTRACTION_ENDTIME_1 - GWELL_CONTRACTION_STARTTIME_1); + //PKMOD - Ergodic 02/27/01 - Debug force to One (inactive) +// c = 0.25f; + //PKMOD - Ergodic 10/13/02 - Add new animated gravity well model (resize) + size = size / 3.0; + + //PKMOD - Ergodic 03/26/01 - add dynamic lighting + dynamic_lumens = 50 + 50 * size; + trap_R_AddLightToScene(orb.origin, dynamic_lumens, dynamic_color[0], dynamic_color[1], dynamic_color[2] ); + + VectorScale( axis[0], size * GWELL_ORB_RADIUS_1, orb.axis[0] ); + VectorScale( axis[1], size * GWELL_ORB_RADIUS_1, orb.axis[1] ); + VectorScale( axis[2], size * GWELL_ORB_RADIUS_1, orb.axis[2] ); + orb.nonNormalizedAxes = qtrue; + + if (t > GWELL_CONTRACTION_FADETIME_1) { + c = (float)(t - GWELL_CONTRACTION_FADETIME_1) / (float)(GWELL_CONTRACTION_ENDTIME_1 - GWELL_CONTRACTION_FADETIME_1); + } + else { + c = 0; + } + c *= 0xff; + orb.shaderRGBA[0] = 0xff - c; + orb.shaderRGBA[1] = 0xff - c; + orb.shaderRGBA[2] = 0xff - c; + orb.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &orb ); + + return;// 03/25/01 - exit + } + + //++++++++++++++++++++++ + // Second Expansion + //++++++++++++++++++++++ + if (t >= GWELL_EXPAND_STARTTIME_2 && t < GWELL_EXPAND_ENDTIME_2) { + + //PKMOD - Ergodic 10/13/02 - frame animations... + //PKMOD - Ergodic 10/17/02 - frame animations... + // Start frame animation for sphere crimping + // 20 frames / second = 1 new frame every 50 ms + frame = (t - GWELL_EXPAND_STARTTIME_2) / 50; + if ( frame > 29 ) + orb.frame = 29; + else + orb.frame = frame; + +// if (!(le->leFlags & LEF_SOUND1)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); +//pkmod trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); +// le->leFlags |= LEF_SOUND1; +// } + + //PKMOD - Ergodic 03/26/01 set model + orb.hModel = cgs.media.pkagravitywelluniverse; + + size = 0.75 + (float)(t - GWELL_EXPAND_STARTTIME_2) / (float)(GWELL_EXPAND_ENDTIME_2 - GWELL_EXPAND_STARTTIME_2); + //PKMOD - Ergodic 02/27/01 - Debug force to One (inactive) +// c = 0.25f; + //PKMOD - Ergodic 10/13/02 - Add new animated gravity well model (resize) + size = size / 3.0; + + //PKMOD - Ergodic 03/26/01 - add dynamic lighting + dynamic_lumens = 50 + 150 * size; + if ( dynamic_lumens > 255 ) + dynamic_lumens = 255; + + trap_R_AddLightToScene(orb.origin, dynamic_lumens, dynamic_color[0], dynamic_color[1], dynamic_color[2] ); + + + VectorScale( axis[0], size * GWELL_ORB_RADIUS_2, orb.axis[0] ); + VectorScale( axis[1], size * GWELL_ORB_RADIUS_2, orb.axis[1] ); + VectorScale( axis[2], size * GWELL_ORB_RADIUS_2, orb.axis[2] ); + orb.nonNormalizedAxes = qtrue; + + if (t > GWELL_EXPAND_FADETIME_2) { + c = (float)(t - GWELL_EXPAND_FADETIME_2) / (float)(GWELL_EXPAND_ENDTIME_2 - GWELL_EXPAND_FADETIME_2); + } + else { + c = 0; + } + c *= 0xff; + orb.shaderRGBA[0] = 0xff - c; + orb.shaderRGBA[1] = 0xff - c; + orb.shaderRGBA[2] = 0xff - c; + orb.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &orb ); + return;// 03/25/01 - exit + } + + //++++++++++++++++++++++ + // Second Contraction + //++++++++++++++++++++++ + if (t >= GWELL_CONTRACTION_STARTTIME_2 && t < GWELL_CONTRACTION_ENDTIME_2) { + + //PKMOD - Ergodic 10/17/02 - frame animations... + // Start frame animation for sphere crimping + // 20 frames / second = 1 new frame every 50 ms + frame = (t - GWELL_EXPAND_STARTTIME_2) / 50; + if ( frame > 79 ) + orb.frame = 40 + frame % 40; + else + orb.frame = frame; + +// if (!(le->leFlags & LEF_SOUND1)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); +//pkmod trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); +// le->leFlags |= LEF_SOUND1; +// } + + //PKMOD - Ergodic 03/26/01 set model +// orb.hModel = cgs.media.pkagravitywell_contraction; + //PKMOD - Ergodic 10/13/02 - Add new animated gravity well model + orb.hModel = cgs.media.pkagravitywelluniverse; + + size = 1.5 + 0.25 * (float)(GWELL_CONTRACTION_ENDTIME_2 - t) / (float)(GWELL_CONTRACTION_ENDTIME_2 - GWELL_CONTRACTION_STARTTIME_2); + //PKMOD - Ergodic 02/27/01 - Debug force to One (inactive) +// c = 0.25f; + //PKMOD - Ergodic 10/13/02 - Add new animated gravity well model (resize) + size = size / 3.0; + + //PKMOD - Ergodic 03/26/01 - add dynamic lighting + dynamic_lumens = 50 + 100 * size; + if ( dynamic_lumens > 255 ) + dynamic_lumens = 255; + + trap_R_AddLightToScene(orb.origin, dynamic_lumens, dynamic_color[0], dynamic_color[1], dynamic_color[2] ); + + + VectorScale( axis[0], size * GWELL_ORB_RADIUS_2, orb.axis[0] ); + VectorScale( axis[1], size * GWELL_ORB_RADIUS_2, orb.axis[1] ); + VectorScale( axis[2], size * GWELL_ORB_RADIUS_2, orb.axis[2] ); + orb.nonNormalizedAxes = qtrue; + + if (t > GWELL_CONTRACTION_FADETIME_2) { + c = (float)(t - GWELL_CONTRACTION_FADETIME_2) / (float)(GWELL_CONTRACTION_ENDTIME_2 - GWELL_CONTRACTION_FADETIME_2); + } + else { + c = 0; + } + c *= 0xff; + orb.shaderRGBA[0] = 0xff - c; + orb.shaderRGBA[1] = 0xff - c; + orb.shaderRGBA[2] = 0xff - c; + orb.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &orb ); + + //+++ + //PKMOD - Ergodic 10/15/02 - Add the gravity well expanding wave + //+++ + wsize = 0.5 * (float)(t - GWELL_CONTRACTION_STARTTIME_2) / (float)(GWELL_EXPAND_ENDTIME_3 - GWELL_CONTRACTION_STARTTIME_2); + orb.hModel = cgs.media.pkagravitywellwave; + + VectorScale( axis[0], wsize * GWELL_ORB_RADIUS_2, orb.axis[0] ); + VectorScale( axis[1], wsize * GWELL_ORB_RADIUS_2, orb.axis[1] ); + VectorScale( axis[2], size * GWELL_ORB_RADIUS_2, orb.axis[2] ); + orb.nonNormalizedAxes = qtrue; + + if (t > GWELL_EXPAND_FADETIME_3) { + c = (float)(t - GWELL_EXPAND_FADETIME_3) / (float)(GWELL_EXPAND_ENDTIME_3 - GWELL_EXPAND_FADETIME_3); + } + else { + c = 0; + } + c *= 0xff; + orb.shaderRGBA[0] = 0xff - c; + orb.shaderRGBA[1] = 0xff - c; + orb.shaderRGBA[2] = 0xff - c; + orb.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &orb ); + + return;// 03/25/01 - exit + } + + //++++++++++++++++++++++ + // Third Expansion + //++++++++++++++++++++++ + if (t >= GWELL_EXPAND_STARTTIME_3 && t < GWELL_EXPAND_ENDTIME_3) { + //PKMOD - Ergodic 10/17/02 - frame animations... + // Start frame animation for sphere crimping + // 20 frames / second = 1 new frame every 50 ms + frame = (t - GWELL_EXPAND_STARTTIME_2) / 50; + if ( frame > 79 ) + orb.frame = 40 + frame % 40; + else + orb.frame = frame; + +// if (!(le->leFlags & LEF_SOUND1)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); +//pkmod trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); +// le->leFlags |= LEF_SOUND1; +// } + + //PKMOD - Ergodic 03/26/01 set model + orb.hModel = cgs.media.pkagravitywelluniverse; + + size = 1.5 + (float)(t - GWELL_EXPAND_STARTTIME_3) / (float)(GWELL_EXPAND_ENDTIME_3 - GWELL_EXPAND_STARTTIME_3); + //PKMOD - Ergodic 02/27/01 - Debug force to One (inactive) +// c = 0.25f; + //PKMOD - Ergodic 10/13/02 - Add new animated gravity well model (resize) + size = size / 3.0; + + //PKMOD - Ergodic 03/26/01 - add dynamic lighting + dynamic_lumens = 50 + 175 * size; + if ( dynamic_lumens > 255 ) + dynamic_lumens = 255; + + trap_R_AddLightToScene(orb.origin, dynamic_lumens, dynamic_color[0], dynamic_color[1], dynamic_color[2] ); + + + VectorScale( axis[0], size * GWELL_ORB_RADIUS_3, orb.axis[0] ); + VectorScale( axis[1], size * GWELL_ORB_RADIUS_3, orb.axis[1] ); + VectorScale( axis[2], size * GWELL_ORB_RADIUS_3, orb.axis[2] ); + orb.nonNormalizedAxes = qtrue; + + if (t > GWELL_EXPAND_FADETIME_3) { + c = (float)(t - GWELL_EXPAND_FADETIME_3) / (float)(GWELL_EXPAND_ENDTIME_3 - GWELL_EXPAND_FADETIME_3); + } + else { + c = 0; + } + c *= 0xff; + orb.shaderRGBA[0] = 0xff - c; + orb.shaderRGBA[1] = 0xff - c; + orb.shaderRGBA[2] = 0xff - c; + orb.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &orb ); + + //+++ + //PKMOD - Ergodic 10/15/02 - Add the gravity well expanding wave + //+++ + wsize = 0.75 * (float)(t - GWELL_CONTRACTION_STARTTIME_2) / (float)(GWELL_EXPAND_ENDTIME_3 - GWELL_CONTRACTION_STARTTIME_2); + orb.hModel = cgs.media.pkagravitywellwave; + + VectorScale( axis[0], wsize * GWELL_ORB_RADIUS_2, orb.axis[0] ); + VectorScale( axis[1], wsize * GWELL_ORB_RADIUS_2, orb.axis[1] ); + VectorScale( axis[2], size * GWELL_ORB_RADIUS_2, orb.axis[2] ); + orb.nonNormalizedAxes = qtrue; + + if (t > GWELL_EXPAND_FADETIME_3) { + c = (float)(t - GWELL_EXPAND_FADETIME_3) / (float)(GWELL_EXPAND_ENDTIME_3 - GWELL_EXPAND_FADETIME_3); + } + else { + c = 0; + } + c *= 0xff; + orb.shaderRGBA[0] = 0xff - c; + orb.shaderRGBA[1] = 0xff - c; + orb.shaderRGBA[2] = 0xff - c; + orb.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &orb ); + + return;// 03/25/01 - exit + } + + //++++++++++++++++++++++ + // Third Contraction + //++++++++++++++++++++++ + if (t >= GWELL_CONTRACTION_STARTTIME_3 && t < GWELL_CONTRACTION_ENDTIME_3) { + //PKMOD - Ergodic 10/17/02 - frame animations... + // Start frame animation for sphere crimping + // 20 frames / second = 1 new frame every 50 ms + frame = (t - GWELL_EXPAND_STARTTIME_2) / 50; + if ( frame > 79 ) + orb.frame = 40 + frame % 40; + else + orb.frame = frame; + + //PKMOD - Ergodic 10/16/01 - debug last area (inactive) +// if ( (t > ( GWELL_CONTRACTION_STARTTIME_3 + 300 )) && (t < ( GWELL_CONTRACTION_STARTTIME_3 + 700 ) ) ) { +// Com_Printf("CG_GravityWell_Expand - t>%d<, frame>%d<, orb.frame>%d<\n", t, frame, orb.frame ); +// } + + +// if (!(le->leFlags & LEF_SOUND1)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); +//pkmod trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); +// le->leFlags |= LEF_SOUND1; +// } + + //PKMOD - Ergodic 03/26/01 set model +// orb.hModel = cgs.media.pkagravitywell_contraction; + //PKMOD - Ergodic 10/13/02 - Add new animated gravity well model + orb.hModel = cgs.media.pkagravitywelluniverse; + + size = (float)(GWELL_CONTRACTION_ENDTIME_3 - t) / (float)(GWELL_CONTRACTION_ENDTIME_3 - GWELL_CONTRACTION_STARTTIME_3); + //PKMOD - Ergodic 02/27/01 - Debug force to One (inactive) +// c = 0.25f; + //PKMOD - Ergodic 10/13/02 - Add new animated gravity well model (resize) + size = size / 3.0; + + //PKMOD - Ergodic 03/26/01 - add dynamic lighting + dynamic_lumens = 50 + 125 * size; + if ( dynamic_lumens > 255 ) + dynamic_lumens = 255; + + trap_R_AddLightToScene(orb.origin, dynamic_lumens, dynamic_color[0], dynamic_color[1], dynamic_color[2] ); + + + VectorScale( axis[0], 2.5 * size * GWELL_ORB_RADIUS_3, orb.axis[0] ); + VectorScale( axis[1], 2.5 * size * GWELL_ORB_RADIUS_3, orb.axis[1] ); + VectorScale( axis[2], 2.5 * size * GWELL_ORB_RADIUS_3, orb.axis[2] ); + orb.nonNormalizedAxes = qtrue; + + if (t > GWELL_CONTRACTION_FADETIME_3) { + c = (float)(t - GWELL_CONTRACTION_FADETIME_3) / (float)(GWELL_CONTRACTION_ENDTIME_3 - GWELL_CONTRACTION_FADETIME_3); + } + else { + c = 0; + } + c *= 0xff; + orb.shaderRGBA[0] = 0xff - c; + orb.shaderRGBA[1] = 0xff - c; + orb.shaderRGBA[2] = 0xff - c; + orb.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &orb ); + + //PKMOD - Ergodic 10/17/02 - draw some particles... + VectorCopy(orb.origin, spark_origin); + spark_origin[2] += 200; + + //PKMOD - Ergodic 03/18/04 - reduce index max from 10 to 4 + for ( indx = 1; indx < 4; indx++ ) { + vel[0] = 10 * crandom(); + vel[1] = 10 * crandom(); + vel[2] = 10 * crandom(); + + duration = 3000; + + x_offset = (t - GWELL_CONTRACTION_STARTTIME_3) * crandom() / 5; + y_offset = (t - GWELL_CONTRACTION_STARTTIME_3) * crandom() / 5; + + speed = 10 * crandom(); + + //PKMOD - Ergodic 10/18/02 - draw super-sized particles... + CG_ParticleSparks2 (spark_origin, vel, duration, x_offset, y_offset, speed); + } + + //PKMOD - Ergodic 10/18/02 - debug, count the number particle calls (inactive) + //Com_Printf("CG_GravityWell_Expand\n"); + + return;// 03/25/01 - exit + } + + //PKMOD - Ergodic 01/04/02 - (inactive) debug, here is not part of gwell effect +// Com_Printf("CG_GravityWell_Expand - delta time>%d<\n", t); + +} + + +/* +================== +PKMOD - CG_Coord, Ergodic 01/21/01 display the Coord model +================== +*/ + +//PKMOD - Ergodic 01/21/01 - add event to display coordinate model for exploding shells debug +void CG_Coord( entityState_t *es ) { + localEntity_t *le; + float light; + vec3_t lightColor; + + + light = 100; + lightColor[0] = 1; + lightColor[1] = 1; + lightColor[2] = 0; + //PKMOD - Ergodic 01/22/01 - debug location + Com_Printf("CG_Coord: es->pos.trBase>%s<\n", CG_vtos(es->pos.trBase)); + + + le = CG_MakeExplosion( es->pos.trBase, 0, cgs.media.coordFlashModel, cgs.media.plasmaExplosionShader, 3000, qfalse ); +// le = CG_MakeExplosion( es->pos.trBase, es->angles, cgs.media.bulletFlashModel, cgs.media.plasmaExplosionShader, 3000, qfalse ); + le->light = light; + VectorCopy( lightColor, le->lightColor ); + +} + + +//============================================================================ + +/* +========================= +CG_AdjustPositionForMover + +Also called by client movement prediction code +========================= +*/ +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ) { + centity_t *cent; + vec3_t oldOrigin, origin, deltaOrigin; + vec3_t oldAngles, angles, deltaAngles; + + if ( moverNum <= 0 || moverNum >= ENTITYNUM_MAX_NORMAL ) { + VectorCopy( in, out ); + return; + } + + cent = &cg_entities[ moverNum ]; + //PKMOD - Ergodic 06/21/01 - mover is similar to zombie + if ( ( cent->currentState.eType != ET_MOVER ) && ( cent->currentState.eType != ET_ZOMBIE ) ) { + VectorCopy( in, out ); + return; + } + + BG_EvaluateTrajectory( ¢->currentState.pos, fromTime, oldOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, fromTime, oldAngles ); + + BG_EvaluateTrajectory( ¢->currentState.pos, toTime, origin ); + BG_EvaluateTrajectory( ¢->currentState.apos, toTime, angles ); + + VectorSubtract( origin, oldOrigin, deltaOrigin ); + VectorSubtract( angles, oldAngles, deltaAngles ); + + VectorAdd( in, deltaOrigin, out ); + + // FIXME: origin change when on a rotating object +} + + +/* +============================= +CG_InterpolateEntityPosition +============================= +*/ +static void CG_InterpolateEntityPosition( centity_t *cent ) { + vec3_t current, next; + float f; + + // it would be an internal error to find an entity that interpolates without + // a snapshot ahead of the current one + if ( cg.nextSnap == NULL ) { + CG_Error( "CG_InterpoateEntityPosition: cg.nextSnap == NULL" ); + } + + f = cg.frameInterpolation; + + // this will linearize a sine or parabolic curve, but it is important + // to not extrapolate player positions if more recent data is available + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.pos, cg.nextSnap->serverTime, next ); + + cent->lerpOrigin[0] = current[0] + f * ( next[0] - current[0] ); + cent->lerpOrigin[1] = current[1] + f * ( next[1] - current[1] ); + cent->lerpOrigin[2] = current[2] + f * ( next[2] - current[2] ); + + BG_EvaluateTrajectory( ¢->currentState.apos, cg.snap->serverTime, current ); + BG_EvaluateTrajectory( ¢->nextState.apos, cg.nextSnap->serverTime, next ); + + cent->lerpAngles[0] = LerpAngle( current[0], next[0], f ); + cent->lerpAngles[1] = LerpAngle( current[1], next[1], f ); + cent->lerpAngles[2] = LerpAngle( current[2], next[2], f ); + +} + +/* +=============== +CG_CalcEntityLerpPositions + +=============== +*/ +static void CG_CalcEntityLerpPositions( centity_t *cent ) { + + // if this player does not want to see extrapolated players + if ( !cg_smoothClients.integer ) { + // make sure the clients use TR_INTERPOLATE + if ( cent->currentState.number < MAX_CLIENTS ) { + cent->currentState.pos.trType = TR_INTERPOLATE; + cent->nextState.pos.trType = TR_INTERPOLATE; + } + } + + if ( cent->interpolate && cent->currentState.pos.trType == TR_INTERPOLATE ) { + CG_InterpolateEntityPosition( cent ); + return; + } + + // first see if we can interpolate between two snaps for + // linear extrapolated clients + if ( cent->interpolate && cent->currentState.pos.trType == TR_LINEAR_STOP && + cent->currentState.number < MAX_CLIENTS) { + CG_InterpolateEntityPosition( cent ); + return; + } + + //PKMOD - Ergodic 06/11/01 - if zombie mover... + if ( cent->currentState.generic1 && ( cent->currentState.eType == ET_MOVER ) ) { + //PKMOD - Ergodic 06/11/01 - for zombie + vec3_t hold_vec; + float hold_angle; + + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + //PKMOD - Ergodic 06/21/01 - rotate model 90 degrees "forward" +// cent->lerpAngles[0] += 90; + + hold_angle = (cent->lerpAngles[2] + 90.0) * M_PI / 180.0; + hold_vec[1] = (float) (cent->currentState.generic1 - 256) * cos( hold_angle ); + hold_vec[2] = (float) (cent->currentState.generic1 - 256) * sin( hold_angle ); + hold_vec[0] = 0; + + //PKMOD - Ergodic 06/11/01 - debug call +// Com_Printf("CG_CalcEntityLerpPositions - deltaTime>%f<, trDelta>%s<\n", 100 * deltaTime, CG_vtos(cent->currentState.pos.trDelta) ); + + VectorAdd( hold_vec, cent->currentState.pos.trBase, cent->lerpOrigin ); + + //PKMOD - Ergodic 06/11/01 - debug call +// Com_Printf("CG_CalcEntityLerpPositions - generic1>%d<, angle>%s<, location>%s<, hold>%s<\n", cent->currentState.generic1, CG_vtos(cent->lerpAngles), CG_vtos(cent->lerpOrigin), CG_vtos(hold_vec) ); + } + else { + // just use the current frame and evaluate as best we can + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + } + + // adjust for riding a mover if it wasn't rolled into the predicted + // player state + if ( cent != &cg.predictedPlayerEntity ) { + CG_AdjustPositionForMover( cent->lerpOrigin, cent->currentState.groundEntityNum, + cg.snap->serverTime, cg.time, cent->lerpOrigin ); + } +} + +/* +=============== +CG_TeamBase +=============== +*/ +static void CG_TeamBase( centity_t *cent ) { + refEntity_t model; +#ifdef MISSIONPACK + vec3_t angles; + int t, h; + float c; + + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF ) { +#else + if ( cgs.gametype == GT_CTF) { +#endif + // show the flag base + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + if ( cent->currentState.modelindex == TEAM_RED ) { + model.hModel = cgs.media.redFlagBaseModel; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + model.hModel = cgs.media.blueFlagBaseModel; + } + else { + model.hModel = cgs.media.neutralFlagBaseModel; + } + trap_R_AddRefEntityToScene( &model ); + } +#ifdef MISSIONPACK + else if ( cgs.gametype == GT_OBELISK ) { + // show the obelisk + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + + model.hModel = cgs.media.overloadBaseModel; + trap_R_AddRefEntityToScene( &model ); + // if hit + if ( cent->currentState.frame == 1) { + // show hit model + // modelindex2 is the health value of the obelisk + c = cent->currentState.modelindex2; + model.shaderRGBA[0] = 0xff; + model.shaderRGBA[1] = c; + model.shaderRGBA[2] = c; + model.shaderRGBA[3] = 0xff; + // + model.hModel = cgs.media.overloadEnergyModel; + trap_R_AddRefEntityToScene( &model ); + } + // if respawning + if ( cent->currentState.frame == 2) { + if ( !cent->miscTime ) { + cent->miscTime = cg.time; + } + t = cg.time - cent->miscTime; + h = (cg_obeliskRespawnDelay.integer - 5) * 1000; + // + if (t > h) { + c = (float) (t - h) / h; + if (c > 1) + c = 1; + } + else { + c = 0; + } + // show the lights + AnglesToAxis( cent->currentState.angles, model.axis ); + // + model.shaderRGBA[0] = c * 0xff; + model.shaderRGBA[1] = c * 0xff; + model.shaderRGBA[2] = c * 0xff; + model.shaderRGBA[3] = c * 0xff; + + model.hModel = cgs.media.overloadLightsModel; + trap_R_AddRefEntityToScene( &model ); + // show the target + if (t > h) { + if ( !cent->muzzleFlashTime ) { + trap_S_StartSound (cent->lerpOrigin, ENTITYNUM_NONE, CHAN_BODY, cgs.media.obeliskRespawnSound); + cent->muzzleFlashTime = 1; + } + VectorCopy(cent->currentState.angles, angles); + angles[YAW] += (float) 16 * acos(1-c) * 180 / M_PI; + AnglesToAxis( angles, model.axis ); + + VectorScale( model.axis[0], c, model.axis[0]); + VectorScale( model.axis[1], c, model.axis[1]); + VectorScale( model.axis[2], c, model.axis[2]); + + model.shaderRGBA[0] = 0xff; + model.shaderRGBA[1] = 0xff; + model.shaderRGBA[2] = 0xff; + model.shaderRGBA[3] = 0xff; + // + model.origin[2] += 56; + model.hModel = cgs.media.overloadTargetModel; + trap_R_AddRefEntityToScene( &model ); + } + else { + //FIXME: show animated smoke + } + } + else { + cent->miscTime = 0; + cent->muzzleFlashTime = 0; + // modelindex2 is the health value of the obelisk + c = cent->currentState.modelindex2; + model.shaderRGBA[0] = 0xff; + model.shaderRGBA[1] = c; + model.shaderRGBA[2] = c; + model.shaderRGBA[3] = 0xff; + // show the lights + model.hModel = cgs.media.overloadLightsModel; + trap_R_AddRefEntityToScene( &model ); + // show the target + model.origin[2] += 56; + model.hModel = cgs.media.overloadTargetModel; + trap_R_AddRefEntityToScene( &model ); + } + } + else if ( cgs.gametype == GT_HARVESTER ) { + // show harvester model + memset(&model, 0, sizeof(model)); + model.reType = RT_MODEL; + VectorCopy( cent->lerpOrigin, model.lightingOrigin ); + VectorCopy( cent->lerpOrigin, model.origin ); + AnglesToAxis( cent->currentState.angles, model.axis ); + + if ( cent->currentState.modelindex == TEAM_RED ) { + model.hModel = cgs.media.harvesterModel; + model.customSkin = cgs.media.harvesterRedSkin; + } + else if ( cent->currentState.modelindex == TEAM_BLUE ) { + model.hModel = cgs.media.harvesterModel; + model.customSkin = cgs.media.harvesterBlueSkin; + } + else { + model.hModel = cgs.media.harvesterNeutralModel; + model.customSkin = 0; + } + trap_R_AddRefEntityToScene( &model ); + } +#endif +} + +/* +=============== +CG_AddCEntity + +=============== +*/ +static void CG_AddCEntity( centity_t *cent ) { + // event-only entities will have been dealt with already + if ( cent->currentState.eType >= ET_EVENTS ) { + return; + } + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + +//PKMOD Ergodic debug 07/18/00 inactive +// if ( cent->currentState.constantLight ) { +// Com_Printf( "CG_AddCEntity constant light found\n" ); +// } + + // add automatic effects + CG_EntityEffects( cent ); + + switch ( cent->currentState.eType ) { + default: + CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + break; +//PKMOD - Ergodic 07/17/00 special spawn functions - do nothing + case ET_LIGHTNING_FX: + //PKMOD Ergodic debug 07/18/00 (inactive) +// Com_Printf( "ET_LIGHTNING_FX found\n" ); + break; + case ET_INVISIBLE: + case ET_PUSH_TRIGGER: + //PKMOD - Ergodic 11/15/00 add functionality to make trigger_push silent + case ET_QUIET_TRIGGER: + case ET_TELEPORT_TRIGGER: + break; + case ET_GENERAL: + CG_General( cent ); + break; + //PKMOD - Ergodic 08/02/01 - create a new entity type for door_trigger + // fixes the bug of dragon deploy hitting doors + case ET_DOOR_TRIGGER: + CG_General( cent ); + break; + //PKMOD - Ergodic 08/02/01 - create a new entity type for trigger_multiple + // fixes the bug of dragon deploy hitting doors + case ET_TRIGGER_MULTIPLE: + CG_General( cent ); + break; + case ET_PLAYER: + CG_Player( cent ); + break; + case ET_ITEM: + CG_Item( cent ); + break; + case ET_MISSILE: + CG_Missile( cent ); + break; + case ET_MOVER: + CG_Mover( cent ); + break; + case ET_BEAM: + CG_Beam( cent ); + break; + case ET_PORTAL: + CG_Portal( cent ); + break; + case ET_SPEAKER: + CG_Speaker( cent ); + break; + case ET_GRAPPLE: + CG_Grapple( cent ); + break; + case ET_BEARTRAP: + CG_BearTrap( cent ); + break; + case ET_GRAVITY_WELL: + //PKMOD - Ergodic 11/01/02 - This function call was moved from cg_event + // to fix the Invisible Gravity Well bug + //CG_GravityWellActivate( cent->lerpOrigin ); + //PKMOD - Ergodic 11/02/02 - Pass in the whole centity + //PKMOD - Ergodic 11/02/02 - Move function to cg_ents + CG_GravityWell_Activate( cent ); + break; + //PKMOD Ergodic - 07/12/2000, add chain lightning event type + case ET_CHAIN_LIGHTNING: + CG_ChainLightning( cent ); + break; + case ET_BEARTRAP_FOLLOW: + //PKMOD - Ergodic 06/30/00 do nothing since tracking is dealt with in cg_player +// CG_BearTrap_Follow( cent ); + break; + //PKMOD Ergodic - 08/03/2000, add nail event type + case ET_NAIL: + CG_Nail( cent ); + break; + //PKMOD Ergodic - 11/26/2000, add deploy autosentry entity type + case ET_AUTOSENTRY_DEPLOY: + CG_AutoSentry( cent ); + break; + //PKMOD Ergodic - 11/22/2000, add launch autosentry entity type + //PKMOD Ergodic - 11/26/2000, separate the autosentry launch/deploy entity types + case ET_AUTOSENTRY_LAUNCH: + cent->miscTime = 0; //force the time to zero -> deploy0 model + CG_AutoSentry( cent ); + break; + //PKMOD Ergodic - 12/02/2000, add base autosentry entity type + case ET_AUTOSENTRY_BASE: + CG_AutoSentry_Base( cent ); + break; + //PKMOD Ergodic - 12/02/2000, add turret autosentry entity type + case ET_AUTOSENTRY_TURRET: + CG_AutoSentry_Turret( cent ); + break; + //PKMOD - Ergodic 03/14/01 - add dragon deployable weapon fire + case ET_DRAGON_DEPLOY: + CG_Dragon_Deploy( cent ); + break; + //PKMOD - Ergodic 06/21/01 - zombie is similar to mover + case ET_ZOMBIE: + CG_Mover( cent ); + break; + case ET_TEAM: + CG_TeamBase( cent ); + break; + //PKMOD - Ergodic 06/13/02 - the Personal Sentry driver entity should be ignored + case ET_PERSONALSENTRY: + break; + + //PKMOD Ergodic - 08/20/03, add Shooter_lightning entity type to differentiate between + // shooter_Lightning(green) and CLG lightning(red) + case ET_SHOOTER_LIGHTNING: + CG_ShooterLightning( cent ); + break; + + } +} + +/* +=============== +CG_AddPacketEntities + +=============== +*/ +void CG_AddPacketEntities( void ) { + int num; + centity_t *cent; + playerState_t *ps; + + // set cg.frameInterpolation + if ( cg.nextSnap ) { + int delta; + + delta = (cg.nextSnap->serverTime - cg.snap->serverTime); + if ( delta == 0 ) { + cg.frameInterpolation = 0; + } else { + cg.frameInterpolation = (float)( cg.time - cg.snap->serverTime ) / delta; + } + } else { + cg.frameInterpolation = 0; // actually, it should never be used, because + // no entities should be marked as interpolating + } + + // the auto-rotating items will all have the same axis + cg.autoAngles[0] = 0; + cg.autoAngles[1] = ( cg.time & 2047 ) * 360 / 2048.0; + cg.autoAngles[2] = 0; + + cg.autoAnglesFast[0] = 0; + cg.autoAnglesFast[1] = ( cg.time & 1023 ) * 360 / 1024.0f; + cg.autoAnglesFast[2] = 0; + + //PKMOD - Ergodic 01/27/02 - add slower rotating items + cg.autoAnglesSlow[0] = 0; + cg.autoAnglesSlow[1] = ( cg.time & 4095 ) * 360 / 4096.0f; + cg.autoAnglesSlow[2] = 0; + + AnglesToAxis( cg.autoAngles, cg.autoAxis ); + AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); + //PKMOD - Ergodic 01/27/02 - add slower rotating items + AnglesToAxis( cg.autoAnglesSlow, cg.autoAxisSlow ); + + // generate and add the entity from the playerstate + ps = &cg.predictedPlayerState; + BG_PlayerStateToEntityState( ps, &cg.predictedPlayerEntity.currentState, qfalse ); + CG_AddCEntity( &cg.predictedPlayerEntity ); + + // lerp the non-predicted value for lightning gun origins + CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); + + // add each entity sent over by the server + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + CG_AddCEntity( cent ); + } +} + + + diff --git a/quake3/source/code/cgame/cg_event.c b/quake3/source/code/cgame/cg_event.c new file mode 100644 index 0000000..c6cfdd9 --- /dev/null +++ b/quake3/source/code/cgame/cg_event.c @@ -0,0 +1,2723 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_event.c -- handle entity events at snapshot or playerstate transitions + +#include "cg_local.h" + +// for the voice chats +#ifdef MISSIONPACK // bk001205 +#include "../../ui/menudef.h" +#endif +//========================================================================== + +/* +=================== +CG_PlaceString + +Also called by scoreboard drawing +=================== +*/ +const char *CG_PlaceString( int rank ) { + static char str[64]; + char *s, *t; + + if ( rank & RANK_TIED_FLAG ) { + rank &= ~RANK_TIED_FLAG; + t = "Tied for "; + } else { + t = ""; + } + + if ( rank == 1 ) { + s = S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue + } else if ( rank == 2 ) { + s = S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red + } else if ( rank == 3 ) { + s = S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow + } else if ( rank == 11 ) { + s = "11th"; + } else if ( rank == 12 ) { + s = "12th"; + } else if ( rank == 13 ) { + s = "13th"; + } else if ( rank % 10 == 1 ) { + s = va("%ist", rank); + } else if ( rank % 10 == 2 ) { + s = va("%ind", rank); + } else if ( rank % 10 == 3 ) { + s = va("%ird", rank); + } else { + s = va("%ith", rank); + } + + Com_sprintf( str, sizeof( str ), "%s%s", t, s ); + return str; +} + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) { + int mod; + int target, attacker; + char *message; + char *message2; + const char *targetInfo; + const char *attackerInfo; + //PKMOD - Ergodic 12/18/02 - expand target's name from 32 TO 64, To include "Private Bot" + //char targetName[32]; + char targetName[64]; + char attackerName[32]; + gender_t gender; + clientInfo_t *ci; + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + int rndmsg; + + target = ent->otherEntityNum; + attacker = ent->otherEntityNum2; + mod = ent->eventParm; + + if ( target < 0 || target >= MAX_CLIENTS ) { + CG_Error( "CG_Obituary: target out of range" ); + } + ci = &cgs.clientinfo[target]; + + if ( attacker < 0 || attacker >= MAX_CLIENTS ) { + attacker = ENTITYNUM_WORLD; + attackerInfo = NULL; + } else { + attackerInfo = CG_ConfigString( CS_PLAYERS + attacker ); + } + + targetInfo = CG_ConfigString( CS_PLAYERS + target ); + if ( !targetInfo ) { + return; + } + Q_strncpyz( targetName, Info_ValueForKey( targetInfo, "n" ), sizeof(targetName) - 2); + //pkmod - Ergodic 12/18/02 - add Private Bot Info if needed + if ( ent->time2 == 1 ) + strcat( targetName, "'s Private Bot" ); + + strcat( targetName, S_COLOR_WHITE ); + + message2 = ""; + + // check for single client messages + + switch( mod ) { + case MOD_SUICIDE: + message = "suicides"; + break; + case MOD_FALLING: + message = "cratered"; + break; + case MOD_CRUSH: + message = "was squished"; + break; + case MOD_WATER: + message = "sank like a rock"; + break; + case MOD_SLIME: + message = "melted"; + break; + case MOD_LAVA: + message = "does a back flip into the lava"; + break; + case MOD_TARGET_LASER: + message = "saw the light"; + break; + case MOD_TRIGGER_HURT: + message = "was in the wrong place"; + break; + //PKMOD - Ergodic 12/05/00 - add new types of PKA shooters + case MOD_SHOOTER_LIGHTNING: + rndmsg = rand() % 4; //Generate random numbers: {0,1,2,3} + switch (rndmsg) { + case 0: + message = "was zapped extra crispy"; + break; + case 1: + message = "was short-circuited"; + break; + case 2: + message = "was not properly grounded"; + break; + default: + message = "had a shocking experience"; + break; + } + default: + message = NULL; + break; + } + + //pkmod - Ergodic 12/18/02 - if suicide and not a Private Bot + if ( ( attacker == target ) && ( ent->time2 != 1 ) ) { + gender = ci->gender; + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + rndmsg = rand() % 3; //Generate random numbers: {0,1,2} + + switch (mod) { +#ifdef MISSIONPACK + case MOD_KAMIKAZE: + message = "goes out with a bang"; + break; +#endif + case MOD_GRENADE_SPLASH: + if ( gender == GENDER_FEMALE ) + message = "tripped on her own grenade"; + else if ( gender == GENDER_NEUTER ) + message = "tripped on its own grenade"; + else + message = "tripped on his own grenade"; + break; + case MOD_ROCKET_SPLASH: + if ( gender == GENDER_FEMALE ) + message = "blew herself up"; + else if ( gender == GENDER_NEUTER ) + message = "blew itself up"; + else + message = "blew himself up"; + break; + case MOD_PLASMA_SPLASH: + if ( gender == GENDER_FEMALE ) + message = "melted herself"; + else if ( gender == GENDER_NEUTER ) + message = "melted itself"; + else + message = "melted himself"; + break; + case MOD_BFG_SPLASH: + message = "should have used a smaller gun"; + break; + case MOD_GRAVITY: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "was sucked into her own well"; + break; + case 1: + message = "left this dimension through her own door"; + break; + default: + message = "now knows the fate of the universe"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "was sucked into its own well"; + break; + case 1: + message = "left this dimension through its own door"; + break; + default: + message = "now knows the fate of the universe"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "was sucked into his own well"; + break; + case 1: + message = "left this dimension through his own door"; + break; + default: + message = "now knows the fate of the universe"; + break; + } + } + break; + + case MOD_BEARTRAP: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "lost some limbs in her own beartrap"; + break; + case 1: + message = "was snared by her own beartrap"; + break; + default: + message = "should have jumped over her own beartrap"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "lost some limbs in its own beartrap"; + break; + case 1: + message = "was snared by its own beartrap"; + break; + default: + message = "should have jumped over its her own beartrap"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "lost some limbs in his own beartrap"; + break; + case 1: + message = "was snared by his own beartrap"; + break; + default: + message = "should have jumped over his own beartrap"; + break; + } + } + break; + + case MOD_SENTRY: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "forgot about her own sentry"; + break; + case 1: + message = "mistook her own sentry for an ATM"; + break; + default: + message = "was shot up by her own sentry"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "forgot about its own sentry"; + break; + case 1: + message = "mistook its own sentry for an ATM"; + break; + default: + message = "was shot up by its own sentry"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "forgot about his own sentry"; + break; + case 1: + message = "mistook his own sentry for an ATM"; + break; + default: + message = "was shot up by his own sentry"; + break; + } + } + break; + + //PKMOD - Ergodic 01/13/01 - exploding autosentry will produce splash damage + case MOD_SENTRY_SPLASH: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "was caught in her own sentry's detonation"; + break; + case 1: + message = "was shredded by her own sentry's shrapnel"; + break; + default: + message = "got to close to her little R2D2 buddy"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "was caught its her own sentry's detonation"; + break; + case 1: + message = "was shredded by its own sentry's shrapnel"; + break; + default: + message = "got to close to its little R2D2 buddy"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "was caught in his own sentry's detonation"; + break; + case 1: + message = "was shredded by his own sentry's shrapnel"; + break; + default: + message = "got to close to his little R2D2 buddy"; + break; + } + } + break; + + //PKMOD - Ergodic 01/15/01 - add Lightning Discharge in water + case MOD_LIGHTNING_WATER_DISCHARGE: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "dishonored herself by committing electrical suicide"; + break; + case 1: + message = "probably wet her bed many times in her youth"; + break; + default: + message = "was fried by her own juice"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "dishonored itself by committing electrical suicide"; + break; + case 1: + message = "probably wet its bed many times in its youth"; + break; + default: + message = "was fried by its own juice"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "dishonored himself by committing electrical suicide"; + break; + case 1: + message = "probably wet his bed many times in his youth"; + break; + default: + message = "was fried by his own juice"; + break; + } + } + break; + + //PKMOD - Ergodic 07/02/01 - add reverse damage on lightning from autosentry + case MOD_REVERSE_LIGHTNING: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "Shafted her own ignorant self"; + break; + case 1: + message = "had her own lightning reflected"; + break; + default: + message = "was juiced by her own hand"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "Shafted its own ignorant self"; + break; + case 1: + message = "had its own lightning reflected"; + break; + default: + message = "was juiced by its own hand"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "Shafted his own ignorant self"; + break; + case 1: + message = "had his own lightning reflected"; + break; + default: + message = "was juiced by his own hand"; + break; + } + } + break; + + //PKMOD - Ergodic 10/29/01 - add holdable radiation death + case MOD_RADIATION: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "was burned by her own radiation"; + break; + case 1: + message = "made her own internal organs glow"; + break; + default: + message = "stumbled into her own nuclear trap"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "was burned by its own radiation"; + break; + case 1: + message = "made its own internal parts glow"; + break; + default: + message = "stumbled into its own nuclear trap"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "was burned by his own radiation"; + break; + case 1: + message = "made his own internal organs glow"; + break; + default: + message = "stumbled into his own nuclear trap"; + break; + } + } + break; + + case MOD_EXPLODING_SHELLS: + //PKMOD Ergodic 06/16/00 Enable Multi-MOD messages + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "was ignited by her own shells"; + break; + case 1: + message = "was toasted by her own lava pellets"; + break; + default: + message = "was evaporated by her own exploding shells"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "was ignited by its own shells"; + break; + case 1: + message = "was toasted by its own lava pellets"; + break; + default: + message = "was evaporated by its own exploding shells"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "was ignited by his own shells"; + break; + case 1: + message = "was toasted by his own lava pellets"; + break; + default: + message = "was evaporated by his own exploding shells"; + break; + } + } + break; + + case MOD_NAIL: + //PKMOD Ergodic 08/01/00 Enable Multi-MOD messages + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "was burned by her own blue spike"; + break; + case 1: + message = "should not have touched her own blue stump"; + break; + default: + message = "got impaled on her own hot nail"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "was burned by its own blue spike"; + break; + case 1: + message = "should not have touched its own blue stump"; + break; + default: + message = "got impaled on its own hot nail"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "was burned by his own blue spike"; + break; + case 1: + message = "should not have touched his own blue stump"; + break; + default: + message = "got impaled on his own hot nail"; + break; + } + } + break; + +#ifdef MISSIONPACK + case MOD_PROXIMITY_MINE: + if( gender == GENDER_FEMALE ) { + message = "found her prox mine"; + } else if ( gender == GENDER_NEUTER ) { + message = "found it's prox mine"; + } else { + message = "found his prox mine"; + } + break; +#endif + + case MOD_CRUSH_CREDIT: + //PKMOD Ergodic 01/11/01 - Enable Multi-MOD messages for Activator of trap + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "squished her own dumb self"; + break; + case 1: + message = "is now 2D thanks to herself"; + break; + default: + message = "was flattened by her own trap"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "squished its own dumb self"; + break; + case 1: + message = "is now 2D thanks to itself"; + break; + default: + message = "was flattened by its own trap"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "squished his own dumb self"; + break; + case 1: + message = "is now 2D thanks to himself"; + break; + default: + message = "was flattened by his own trap"; + break; + } + } + break; + + //PKMOD - Ergodic 01/05/04 - add QUAD FART beans toot damage (self damage) + case MOD_QUADBEANS_BLAST: + if ( gender == GENDER_FEMALE ) { + switch (rndmsg) { + case 0: + message = "melted her lungs by using Quad Beans"; + break; + case 1: + message = "suffocated in her own enhanced fecal frenzy"; + break; + default: + message = "was kramered in her own caustic bio-gas"; + break; + } + } + else if ( gender == GENDER_NEUTER ) { + switch (rndmsg) { + case 0: + message = "melted its lungs by using Quad Beans"; + break; + case 1: + message = "suffocated in its own enhanced fecal frenzy"; + break; + default: + message = "was kramered in its own caustic bio-gas"; + break; + } + } + else { + switch (rndmsg) { + case 0: + message = "melted his lungs by using Quad Beans"; + break; + case 1: + message = "suffocated in his own enhanced fecal frenzy"; + break; + default: + message = "was kramered in his own caustic bio-gas"; + break; + } + } + break; + + default: + if ( gender == GENDER_FEMALE ) + message = "killed herself"; + else if ( gender == GENDER_NEUTER ) + message = "killed itself"; + else + message = "killed himself"; + break; + } + } + + if (message) { + CG_Printf( "%s %s.\n", targetName, message); + return; + } + + // check for kill messages from the current clientNum + //pkmod - Ergodic 12/18/02 - if suicide and not a Private Bot + //if ( attacker == cg.snap->ps.clientNum ) { + if ( ( attacker == cg.snap->ps.clientNum ) && ( ent->time2 != 1 ) ) { + char *s; + + if ( cgs.gametype < GT_TEAM ) { + s = va("You fragged %s\n%s place with %i", targetName, + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + cg.snap->ps.persistant[PERS_SCORE] ); + } else { + s = va("You fragged %s", targetName ); + } +#ifdef MISSIONPACK + if (!(cg_singlePlayerActive.integer && cg_cameraOrbit.integer)) { + CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } +#else + CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); +#endif + + // print the text message as well + } + + // check for double client messages + if ( !attackerInfo ) { + attacker = ENTITYNUM_WORLD; + strcpy( attackerName, "noname" ); + } else { + Q_strncpyz( attackerName, Info_ValueForKey( attackerInfo, "n" ), sizeof(attackerName) - 2); + strcat( attackerName, S_COLOR_WHITE ); + // check for kill messages about the current clientNum + if ( target == cg.snap->ps.clientNum ) { + Q_strncpyz( cg.killerName, attackerName, sizeof( cg.killerName ) ); + } + } + + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + rndmsg = rand() % 3; //Generate random numbers: {0,1,2} + + if ( attacker != ENTITYNUM_WORLD ) { + switch (mod) { + case MOD_GRAPPLE: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was caught by"; + break; + case 1: + message = "got"; + message2 = "'s point"; + break; + default: + message = "merged with"; + message2 = "'s spear"; + break; + } + break; + case MOD_GAUNTLET: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was touched by"; + break; + case 1: + message = "got too close to"; + message2 = "'s reach"; + break; + default: + message = "was ripped to pieces by"; + break; + } + break; + case MOD_MACHINEGUN: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was actually killed by"; + message2 = "'s wimpy default weapon"; + break; + case 1: + message = "received excessive rat-a-tap-tap from"; + break; + default: + message = "was slower on the draw than"; + break; + } + break; + case MOD_SHOTGUN: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "ate a whole lot of"; + message2 = "'s pellets"; + break; + case 1: + message = "was double-barreled by"; + break; + default: + message = "was blasted by"; + message2 = "'s shotgun"; + break; + } + break; + case MOD_GRENADE: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "caught"; + message2 = "'s pineapple toss"; + break; + case 1: + message = "triggered"; + message2 = "'s hot potato"; + break; + default: + message = "'s parts were scattered by"; + message2 = "'s lucky throw"; + break; + } + break; + case MOD_GRENADE_SPLASH: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "didn't run away quick enough from"; + message2 = "'s grenade"; + break; + case 1: + message = "was spammed by"; + break; + default: + message = "was perforated by"; + message2 = "'s shrapnel"; + break; + } + break; + case MOD_ROCKET: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was personally detonated"; + message2 = "'s rocket"; + break; + case 1: + message = "caught a strong-one from"; + break; + default: + message = "reached Valhalla on"; + message2 = "'s missile"; + break; + } + break; + case MOD_ROCKET_SPLASH: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "should have zagged from"; + message2 = "'s rocket"; + break; + case 1: + message = "almost got away from"; + message2 = "'s wrath missile"; + break; + default: + message = "was splashed down by"; + break; + } + break; + case MOD_PLASMA: + message = "was melted by"; + message2 = "'s plasmagun"; + break; + case MOD_PLASMA_SPLASH: + message = "was melted by"; + message2 = "'s plasmagun"; + break; + case MOD_RAILGUN: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was 'Dirty Harry`d' by"; + break; + case 1: + message = "was bored through by"; + message2 = "'s magnum slug"; + break; + default: + message = "got another hole from"; + message2 = "'s magnum"; + break; + } + break; + case MOD_LIGHTNING: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was shafted by"; + break; + case 1: + message = "was plugged into the wall socket by"; + break; + default: + message = "was turned extra-crispy by"; + message2 = "'s juice"; + break; + } + break; + case MOD_BFG: + case MOD_BFG_SPLASH: + message = "was blasted by"; + message2 = "'s BFG"; + break; + +#ifdef MISSIONPACK + case MOD_NAIL: + message = "was nailed by"; + break; + case MOD_CHAINGUN: + message = "got lead poisoning from"; + message2 = "'s Chaingun"; + break; + case MOD_PROXIMITY_MINE: + message = "was too close to"; + message2 = "'s Prox Mine"; + break; + case MOD_KAMIKAZE: + message = "falls to"; + message2 = "'s Kamikaze blast"; + break; + case MOD_JUICED: + message = "was juiced by"; + break; +#endif + + //PKMOD + /*PKMOD -Add Weapons. + WP_HARPOON, + WP_GRAVITY, + WP_SENTRY, + WP_BEARTRAP, + WP_CHAINLG, + WP_A2K, + WP_EMPNUKE, + WP_AIRFIST, + WP_NAILGUN, + PKMOD -Add Weapons. */ + +/*PKMOD - Ergodic 06/05/03 - remove dead code for CLG... + case MOD_HARPOON: + message = "penetrated by"; + break; +end 06/05/03 remove section*/ + + case MOD_GRAVITY: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was sucked into"; + message2 = "'s well"; + break; + case 1: + message = "left this dimension through"; + message2 = "'s door"; + break; + default: + message = "knows the fate of the universe thanks to"; + break; + } + break; + case MOD_SENTRY: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "mistook"; + message2 = "'s sentry for an ATM"; + break; + case 1: + message = "was shot up by"; + message2 = "'s sentry"; + break; + default: + message = "was mowed down by"; + message2 = "'s sentry"; + break; + } + break; + //PKMOD - Ergodic 01/13/01 - exploding autosentry will produce splash damage + case MOD_SENTRY_SPLASH: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was caught in"; + message2 = "'s detonating sentry"; + break; + case 1: + message = "was shredded by the shrapnel from"; + message2 = "'s sentry"; + break; + default: + message = "tried to play with"; + message2 = "'s little R2D2 buddy"; + break; + } + break; + case MOD_BEARTRAP: + //PKMOD Ergodic 06/12/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "lost some limbs in"; + message2 = "'s beartrap"; + break; + case 1: + message = "was snared by"; + message2 = "'s beartrap"; + break; + default: + message = "should have jumped over"; + message2 = "'s beartrap"; + break; + } + break; +/*PKMOD - Ergodic 06/05/03 - remove dead code for CLG... + case MOD_CHAINLG: + message = "was fried by"; + break; + case MOD_A2K: + message = "was obliterated by"; + break; + case MOD_EMPNUKE: + message = "was nuked by"; + break; +end 06/05/03 remove section*/ + + case MOD_AIRFIST: + switch (rndmsg) { + case 0: + message = "was blown by"; + message2 = "'s airfist"; + break; + case 1: + message = "was flattened by"; + message2 = "'s draft"; + break; + default: + message = "was punched out by"; + message2 = "'s wind"; + break; + } + break; + case MOD_EXPLODING_SHELLS: + //PKMOD Ergodic 06/16/00 Enable Multi-MOD messages + //PKMOD Ergodic 07/14/00 fixed grammar + switch (rndmsg) { + case 0: + message = "was ignited by"; + message2 = "'s shells"; + break; + case 1: + message = "was toasted by"; + message2 = "'s lava pellets"; + break; + default: + message = "was incinerated by"; + message2 = "'s exploding shells"; + break; + } + break; + case MOD_EXPLODING_SHELLS_SPLASH: + //PKMOD Ergodic 06/16/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "could not out run"; + message2 = "'s exploding shells"; + break; + case 1: + message = "was hot-waxed by"; + break; + default: + message = "felt"; + message2 = "'s napalm"; + break; + } + break; + case MOD_NAILGUN: + //PKMOD Ergodic 08/01/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was nailed by"; + break; + case 1: + message = "was pinned in place by"; + message2 = "'s spike"; + break; + default: + message = "got a new hole added by"; + message2 = "'s nailgun"; + break; + } + break; + case MOD_NAIL: + //PKMOD Ergodic 08/01/00 Enable Multi-MOD messages + switch (rndmsg) { + case 0: + message = "was burned by"; + message2 = "'s blue spike"; + break; + case 1: + message = "should not have touched"; + message2 = "'s blue stump"; + break; + default: + message = "got impaled on"; + message2 = "'s hot nail"; + break; + } + break; + case MOD_CRUSH_CREDIT: + //PKMOD Ergodic 01/11/01 - Enable Multi-MOD messages for Activator of trap + switch (rndmsg) { + case 0: + message = "was squished by"; + break; + case 1: + message = "is now '2D' thanks to"; + break; + default: + message = "was flattened by"; + message2 = "'s trap"; + break; + } + break; + + //PKMOD - Ergodic 01/15/01 - add Lightning Discharge in water + case MOD_LIGHTNING_WATER_DISCHARGE: + switch (rndmsg) { + case 0: + message = "was caught by"; + message2 = "'s electrical discharge"; + break; + case 1: + message = "needs to stay away from"; + message2 = "'s lame water trap"; + break; + default: + message = "was fried by"; + message2 = "'s suicide shaft"; + break; + } + break; + + //PKMOD - Ergodic 02/01/01 - add can of beans toot damage + case MOD_BEANS_BLAST: + switch (rndmsg) { + case 0: + message = "was engulfed by"; + message2 = "'s fecal plume"; + break; + case 1: + message = "was smothered thanks to"; + message2 = "'s Bean blast"; + break; + default: + message = "was poisoned by"; + message2 = "'s gas shroud"; + break; + } + break; + + //PKMOD - Ergodic 10/29/01 - add holdable radiation death + case MOD_RADIATION: + switch (rndmsg) { + case 0: + message = "was burned by"; + message2 = "'s radiation"; + break; + case 1: + message = "'s flesh was set ablaze by"; + message2 = "'s nuclear trap"; + break; + default: + message = "succumbed to radiation exposure from"; + break; + } + break; + + //PKMOD - Ergodic 08/02/02 - add holdable Personal Sentry death + case MOD_PERSONALSENTRY: + switch (rndmsg) { + case 0: + message = "ate some shoulder mounted love from"; + message2 = "'s auto cannon"; + break; + case 1: + message = "was chewed up by"; + message2 = "'s A.I. pet"; + break; + default: + message = "was hosed down by"; + message2 = "'s Personal Sentry"; + break; + } + break; + + //PKMOD - Ergodic 10/23/02 - Create new Means of Death for the Dragon Blade + case MOD_DRAGONBLADE: + switch (rndmsg) { + case 0: + message = "was bitten by the teeth of"; + message2 = "'s Dragon"; + break; + case 1: + message = "lost some chunks of flesh from"; + message2 = "'s Dragon Bite"; + break; + default: + message = "was carved into bits by"; + message2 = "'s Dragon Blade"; + break; + } + break; + + //PKMOD - Ergodic 01/05/04 - add QUAD FART beans toot damage + case MOD_QUADBEANS_BLAST: + switch (rndmsg) { + case 0: + message = "'s lungs were melted by"; + message2 = "'s Quaded Beans"; + break; + case 1: + message = "suffocated in"; + message2 = "'s enhanced fecal frenzy"; + break; + default: + message = "was kramered in"; + message2 = "'s caustic bio-gas"; + break; + } + break; + + + + //PKMOD + case MOD_TELEFRAG: + message = "tried to invade"; + message2 = "'s personal space"; + break; + default: + message = "was killed by"; + break; + } + + if (message) { + CG_Printf( "%s %s %s%s\n", + targetName, message, attackerName, message2); + return; + } + } + + // we don't know what it was + CG_Printf( "%s died.\n", targetName ); +} + +//========================================================================== + +/* +=============== +CG_UseItem +=============== +*/ +static void CG_UseItem( centity_t *cent ) { + clientInfo_t *ci; + int itemNum, clientNum; + gitem_t *item; + entityState_t *es; + + es = ¢->currentState; + + itemNum = (es->event & ~EV_EVENT_BITS) - EV_USE_ITEM0; + if ( itemNum < 0 || itemNum > HI_NUM_HOLDABLE ) { + itemNum = 0; + } + + // print a message if the local player + if ( es->number == cg.snap->ps.clientNum ) { + if ( !itemNum ) { + CG_CenterPrint( "No item to use", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } else { + //PKMOD - Ergodic 12/08/01 - give unique message for the PRIAVTE BOT + if ( itemNum == HI_BOTHEAD ) + CG_CenterPrint( "Activating Private Bot", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + else { + item = BG_FindItemForHoldable( itemNum ); + CG_CenterPrint( va("Use %s", item->pickup_name), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } + } + } + + switch ( itemNum ) { + default: + case HI_NONE: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); + break; + + case HI_TELEPORTER: + break; + + case HI_MEDKIT: + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + ci->medkitUsageTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.medkitSound ); + break; + + //PKMOD - Ergodic 10/13/01 - add new holdable code for RADIATE + case HI_RADIATE: + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + ci->medkitUsageTime = cg.time; + } + //PKMOD - Ergodic 08/02/02 - Holdable: radiate activation sound + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.pkaradiateactivationSound ); + break; + + //PKMOD - Ergodic 12/08/01 - add new holdable code for PRIVATE BOT + case HI_BOTHEAD: + break; + + //PKMOD - Ergodic 08/02/02 - add new holdable code for PERSONAL SENTRY (no sound) + case HI_PERSENTRY: + break; + +#ifdef MISSIONPACK + case HI_KAMIKAZE: + break; + + case HI_PORTAL: + break; + case HI_INVULNERABILITY: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useInvulnerabilitySound ); + break; +#endif + } + +} + + +//PKMOD - Ergodic 11/17/00 - code for removing item from client +static void CG_ItemRemove( int itemNum ) { + int hold_giTag; + int i; + qboolean holding_shotgun; + +// cg.itemPickup = 0; //was itemNum +// cg.itemPickupTime = cg.time; +// cg.itemPickupBlendTime = cg.time; + + // see if it should be the grabbed weapon + if ( bg_itemlist[itemNum].giType == IT_WEAPON ) { + hold_giTag = bg_itemlist[itemNum].giTag; //get the weapon tag + + cg.snap->ps.stats[ STAT_WEAPONS ] &= ~( 1 << hold_giTag ); //remove the weapon + + holding_shotgun = qfalse; + //if removing the exploding shotgun then also remove the non-exploding shotgun + if ( hold_giTag == WP_EXPLODING_SHELLS ) { + holding_shotgun = qtrue; + cg.snap->ps.stats[ STAT_WEAPONS ] &= ~( 1 << WP_SHOTGUN ); + } + + //if removing the shotgun then also remove the exploding shells shotgun + if ( hold_giTag == WP_SHOTGUN ) { + holding_shotgun = qtrue; + cg.snap->ps.stats[ STAT_WEAPONS ] &= ~( 1 << WP_EXPLODING_SHELLS ); + } + + // do we hold the weapon that is to be removed? + if ( ( cg.weaponSelect == hold_giTag ) || holding_shotgun ) { + switch ( hold_giTag ) { + case WP_BEARTRAP: + case WP_GRAVITY: + case WP_SENTRY: + case WP_BEANS: + cg.weaponSelect = 1; + break; + default: + for ( i = WP_GRAPPLING_HOOK - 1; i > 0 ; i-- ) { + //modelled after CG_WeaponSelectable( ) + //check ammo + if (i == WP_SHOTGUN) { + if ( (!cg.snap->ps.ammo[WP_SHOTGUN]) && (!cg.snap->ps.ammo[WP_EXPLODING_SHELLS]) ) { + continue; + } + } + else if ( !cg.snap->ps.ammo[i] ) { + continue; + } + //check weapon + if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { + continue; + } + + cg.weaponSelect = i; + break; + } + } + cg.weaponSelectTime = cg.time; + } + + } +} + +/* +================ +CG_ItemPickup + +A new item was picked up this frame +//PKMOD - Ergodic 07/06/00 - modified "autoswitch" logic for exploding shells and PKA Items +//PKMOD - Ergodic 07/06/00 (later in the day) - if pickup item is ammo_expshell and current +// weapon is the WP_SHOTGUN then switch to WP_EXPLODING_SHELLS +//PKMOD - Ergodic 07/06/00 handle case of picking up a "dropped" WP_EXPLODING_SHELLS when +// the current weapon is the WP_SHOTGUN then switch to WP_EXPLODING_SHELLS +================ +*/ +static void CG_ItemPickup( int itemNum ) { + int hold_giTag; + + cg.itemPickup = itemNum; + cg.itemPickupTime = cg.time; + cg.itemPickupBlendTime = cg.time; + // see if it should be the grabbed weapon + if ( bg_itemlist[itemNum].giType == IT_WEAPON ) { + hold_giTag = bg_itemlist[itemNum].giTag; + // select it immediately + if ( cg_autoswitch.integer && hold_giTag != WP_MACHINEGUN ) { + //PKMOD - Ergodic 07/06/00 - do not autoswitch to PKA Items + if ( hold_giTag == WP_GRAVITY ) + return; + if ( hold_giTag == WP_SENTRY ) + return; + if ( hold_giTag == WP_BEARTRAP ) + return; + if ( hold_giTag == WP_BEANS ) + return; + //PKMOD - Ergodic 07/06/00 - Logic for excploding shells + // if exploding shells in inventory then switch to exploding shells gun + if ( hold_giTag == WP_SHOTGUN ) { + if ( cg.snap->ps.ammo[WP_EXPLODING_SHELLS] ) + cg.weaponSelect = WP_EXPLODING_SHELLS; + else + cg.weaponSelect = WP_SHOTGUN; + + cg.weaponSelectTime = cg.time; + return; + } + + cg.weaponSelectTime = cg.time; + cg.weaponSelect = bg_itemlist[itemNum].giTag; + return; + } + + //PKMOD - Ergodic 07/06/00 handle case of picking up a "dropped" WP_EXPLODING_SHELLS when + // the current weapon is the WP_SHOTGUN then switch to WP_EXPLODING_SHELLS + if ( hold_giTag == WP_EXPLODING_SHELLS ) { + if ( cg.weaponSelect == WP_SHOTGUN ) { + cg.weaponSelect = WP_EXPLODING_SHELLS; + cg.weaponSelectTime = cg.time; + return; + } + } + + } + //case of picking up ammo_expshells 07/06/00 (later in the day) + else if ( ( bg_itemlist[itemNum].giType == IT_AMMO ) && bg_itemlist[itemNum].giTag == WP_EXPLODING_SHELLS) { + if ( cg.weaponSelect == WP_SHOTGUN ) { //is the shotgun the current model + //change weapon to exploding shells + cg.weaponSelect = WP_EXPLODING_SHELLS; + cg.weaponSelectTime = cg.time; + } + } +} + + +/* +================ +CG_PainEvent + +Also called by playerstate transition +================ +*/ +void CG_PainEvent( centity_t *cent, int health ) { + char *snd; + + // don't do more than two pain sounds a second + if ( cg.time - cent->pe.painTime < 500 ) { + return; + } + + if ( health < 25 ) { + snd = "*pain25_1.wav"; + } else if ( health < 50 ) { + snd = "*pain50_1.wav"; + } else if ( health < 75 ) { + snd = "*pain75_1.wav"; + } else { + snd = "*pain100_1.wav"; + } + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, snd ) ); + + // save pain time for programitic twitch animation + cent->pe.painTime = cg.time; + cent->pe.painDirection ^= 1; +} + + + +/* +============== +CG_EntityEvent + +An entity has an event value +also called by CG_CheckPlayerstateEvents +============== +*/ +#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} +void CG_EntityEvent( centity_t *cent, vec3_t position ) { + entityState_t *es; + int event; + vec3_t dir; + const char *s; + int clientNum; + clientInfo_t *ci; + + es = ¢->currentState; + event = es->event & ~EV_EVENT_BITS; + + if ( cg_debugEvents.integer ) { + CG_Printf( "ent:%3i event:%3i ", es->number, event ); + } + + if ( !event ) { + DEBUGNAME("ZEROEVENT"); + return; + } + + clientNum = es->clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + switch ( event ) { + // + // movement generated events + // + case EV_FOOTSTEP: + DEBUGNAME("EV_FOOTSTEP"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ ci->footsteps ][rand()&3] ); + } + break; + case EV_FOOTSTEP_METAL: + DEBUGNAME("EV_FOOTSTEP_METAL"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_METAL ][rand()&3] ); + } + break; + case EV_FOOTSPLASH: + DEBUGNAME("EV_FOOTSPLASH"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_FOOTWADE: + DEBUGNAME("EV_FOOTWADE"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + case EV_SWIM: + DEBUGNAME("EV_SWIM"); + if (cg_footsteps.integer) { + trap_S_StartSound (NULL, es->number, CHAN_BODY, + cgs.media.footsteps[ FOOTSTEP_SPLASH ][rand()&3] ); + } + break; + + + case EV_FALL_SHORT: + DEBUGNAME("EV_FALL_SHORT"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -8; + cg.landTime = cg.time; + } + break; + case EV_FALL_MEDIUM: + DEBUGNAME("EV_FALL_MEDIUM"); + // use normal pain sound + trap_S_StartSound( NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*pain100_1.wav" ) ); + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -16; + cg.landTime = cg.time; + } + break; + case EV_FALL_FAR: + DEBUGNAME("EV_FALL_FAR"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*fall1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + if ( clientNum == cg.predictedPlayerState.clientNum ) { + // smooth landing z changes + cg.landChange = -24; + cg.landTime = cg.time; + } + break; + + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: // smooth out step up transitions + DEBUGNAME("EV_STEP"); + { + float oldStep; + int delta; + int step; + + if ( clientNum != cg.predictedPlayerState.clientNum ) { + break; + } + // if we are interpolating, we don't need to smooth steps + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) || + cg_nopredict.integer || cg_synchronousClients.integer ) { + break; + } + // check for stepping up before a previous step is completed + delta = cg.time - cg.stepTime; + if (delta < STEP_TIME) { + oldStep = cg.stepChange * (STEP_TIME - delta) / STEP_TIME; + } else { + oldStep = 0; + } + + // add this amount + step = 4 * (event - EV_STEP_4 + 1 ); + cg.stepChange = oldStep + step; + if ( cg.stepChange > MAX_STEP_CHANGE ) { + cg.stepChange = MAX_STEP_CHANGE; + } + cg.stepTime = cg.time; + break; + } + + case EV_JUMP_PAD: + DEBUGNAME("EV_JUMP_PAD"); +// CG_Printf( "EV_JUMP_PAD w/effect #%i\n", es->eventParm ); + { + localEntity_t *smoke; + vec3_t up = {0, 0, 1}; + + + smoke = CG_SmokePuff( cent->lerpOrigin, up, + 32, + 1, 1, 1, 0.33f, + 1000, + cg.time, 0, + LEF_PUFF_DONT_SCALE, + cgs.media.smokePuffShader ); + } + + // boing sound at origin, jump sound on player + trap_S_StartSound ( cent->lerpOrigin, -1, CHAN_VOICE, cgs.media.jumpPadSound ); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + + case EV_JUMP: + DEBUGNAME("EV_JUMP"); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + break; + case EV_TAUNT: + DEBUGNAME("EV_TAUNT"); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*taunt.wav" ) ); + break; +#ifdef MISSIONPACK + case EV_TAUNT_YES: + DEBUGNAME("EV_TAUNT_YES"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_YES); + break; + case EV_TAUNT_NO: + DEBUGNAME("EV_TAUNT_NO"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_NO); + break; + case EV_TAUNT_FOLLOWME: + DEBUGNAME("EV_TAUNT_FOLLOWME"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_FOLLOWME); + break; + case EV_TAUNT_GETFLAG: + DEBUGNAME("EV_TAUNT_GETFLAG"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONGETFLAG); + break; + case EV_TAUNT_GUARDBASE: + DEBUGNAME("EV_TAUNT_GUARDBASE"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONDEFENSE); + break; + case EV_TAUNT_PATROL: + DEBUGNAME("EV_TAUNT_PATROL"); + CG_VoiceChatLocal(SAY_TEAM, qfalse, es->number, COLOR_CYAN, VOICECHAT_ONPATROL); + break; +#endif + case EV_WATER_TOUCH: + DEBUGNAME("EV_WATER_TOUCH"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrInSound ); + break; + case EV_WATER_LEAVE: + DEBUGNAME("EV_WATER_LEAVE"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrOutSound ); + break; + case EV_WATER_UNDER: + DEBUGNAME("EV_WATER_UNDER"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.watrUnSound ); + break; + case EV_WATER_CLEAR: + DEBUGNAME("EV_WATER_CLEAR"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, CG_CustomSound( es->number, "*gasp.wav" ) ); + break; + + case EV_ITEM_PICKUP: + DEBUGNAME("EV_ITEM_PICKUP"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + + //PKMOD - Ergodic 08/13/01 - Debug dragon infinite (inactive) +// if(cg_debugEvents.integer) +// Com_Printf("CG_EntityEvent - Pickup, Item>%d<, Weapon>%d<, BT_AMMO>%d<\n", item->giTag, cent->currentState.weapon, cg.snap->ps.ammo[WP_BEARTRAP] ); + + + // powerups and team items will have a separate global sound, this one + // will be played at prediction time + if ( item->giType == IT_POWERUP || item->giType == IT_TEAM) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.n_healthSound ); + } else if (item->giType == IT_PERSISTANT_POWERUP) { +#ifdef MISSIONPACK + switch (item->giTag ) { + case PW_SCOUT: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.scoutSound ); + break; + case PW_GUARD: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.guardSound ); + break; + case PW_DOUBLER: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.doublerSound ); + break; + case PW_AMMOREGEN: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.ammoregenSound ); + break; + } +#endif + } else { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + //PKMOD - Ergodic 12/05/01 - Radiate Item event + case EV_ITEM_RADIATE: + DEBUGNAME("EV_ITEM_RADIATE"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.pkaradiateitemSound ); + break; + + //PKMOD - Ergodic 12/05/01 - Radiate Player event + case EV_PLAYER_RADIATE: + DEBUGNAME("EV_PLAYER_RADIATE"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.pkaradiateplayerSound ); + break; + + case EV_GLOBAL_ITEM_PICKUP: + DEBUGNAME("EV_GLOBAL_ITEM_PICKUP"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + // powerup pickups are global + if( item->pickup_sound ) { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound, qfalse ) ); + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + // + // weapon events + // + case EV_NOAMMO: + DEBUGNAME("EV_NOAMMO"); +// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.noAmmoSound ); + if ( es->number == cg.snap->ps.clientNum ) { + CG_OutOfAmmoChange(); + } + break; + case EV_CHANGE_WEAPON: + DEBUGNAME("EV_CHANGE_WEAPON"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + //PKMOD - Ergodic 08/13/01 - Debug dragon infinite (inactive) +// if(cg_debugEvents.integer) +// Com_Printf("CG_EntityEvent - Change Weapon>%d<, Select>%d<, BT_AMMO>%d<, predicted BT_AMMO>%d<\n",cent->currentState.weapon, cg.weaponSelect, cg.snap->ps.ammo[WP_BEARTRAP], cg.predictedPlayerState.ammo[WP_BEARTRAP] ); + break; + case EV_FIRE_WEAPON: + DEBUGNAME("EV_FIRE_WEAPON"); + //PKMOD - Ergodic 08/13/01 - Debug dragon infinite (inactive) +// if(cg_debugEvents.integer) +// Com_Printf("CG_EntityEvent - Fire Weapon>%d<, Select>%d<, generic1>%d<, BT_AMMO>%d<, predicted BT_AMMO>%d<\n",cent->currentState.weapon, cg.weaponSelect, cent->currentState.generic1 & 15, cg.snap->ps.ammo[WP_BEARTRAP], cg.predictedPlayerState.ammo[WP_BEARTRAP] ); + CG_FireWeapon( cent ); + break; + + case EV_USE_ITEM0: + DEBUGNAME("EV_USE_ITEM0"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM1: + DEBUGNAME("EV_USE_ITEM1"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM2: + DEBUGNAME("EV_USE_ITEM2"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM3: + DEBUGNAME("EV_USE_ITEM3"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM4: + DEBUGNAME("EV_USE_ITEM4"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM5: + DEBUGNAME("EV_USE_ITEM5"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM6: + DEBUGNAME("EV_USE_ITEM6"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM7: + DEBUGNAME("EV_USE_ITEM7"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM8: + DEBUGNAME("EV_USE_ITEM8"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM9: + DEBUGNAME("EV_USE_ITEM9"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM10: + DEBUGNAME("EV_USE_ITEM10"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM11: + DEBUGNAME("EV_USE_ITEM11"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM12: + DEBUGNAME("EV_USE_ITEM12"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM13: + DEBUGNAME("EV_USE_ITEM13"); + CG_UseItem( cent ); + break; + case EV_USE_ITEM14: + DEBUGNAME("EV_USE_ITEM14"); + CG_UseItem( cent ); + break; + + //================================================================= + + // + // other events + // + case EV_PLAYER_TELEPORT_IN: + DEBUGNAME("EV_PLAYER_TELEPORT_IN"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); + CG_SpawnEffect( position); + break; + + case EV_PLAYER_TELEPORT_OUT: + DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); + CG_SpawnEffect( position); + break; + + case EV_ITEM_POP: + DEBUGNAME("EV_ITEM_POP"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + case EV_ITEM_RESPAWN: + DEBUGNAME("EV_ITEM_RESPAWN"); + cent->miscTime = cg.time; // scale up from this + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.respawnSound ); + break; + + case EV_GRENADE_BOUNCE: + DEBUGNAME("EV_GRENADE_BOUNCE"); + if ( rand() & 1 ) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb1aSound ); + } else { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.hgrenb2aSound ); + } + break; + +#ifdef MISSIONPACK + case EV_PROXIMITY_MINE_STICK: + DEBUGNAME("EV_PROXIMITY_MINE_STICK"); + if( es->eventParm & SURF_FLESH ) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimplSound ); + } else if( es->eventParm & SURF_METALSTEPS ) { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpmSound ); + } else { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbimpdSound ); + } + break; + + case EV_PROXIMITY_MINE_TRIGGER: + DEBUGNAME("EV_PROXIMITY_MINE_TRIGGER"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.wstbactvSound ); + break; + case EV_KAMIKAZE: + DEBUGNAME("EV_KAMIKAZE"); + CG_KamikazeEffect( cent->lerpOrigin ); + break; + case EV_OBELISKEXPLODE: + DEBUGNAME("EV_OBELISKEXPLODE"); + CG_ObeliskExplode( cent->lerpOrigin, es->eventParm ); + break; + case EV_OBELISKPAIN: + DEBUGNAME("EV_OBELISKPAIN"); + CG_ObeliskPain( cent->lerpOrigin ); + break; + case EV_INVUL_IMPACT: + DEBUGNAME("EV_INVUL_IMPACT"); + CG_InvulnerabilityImpact( cent->lerpOrigin, cent->currentState.angles ); + break; + case EV_JUICED: + DEBUGNAME("EV_JUICED"); + CG_InvulnerabilityJuiced( cent->lerpOrigin ); + break; + case EV_LIGHTNINGBOLT: + DEBUGNAME("EV_LIGHTNINGBOLT"); + CG_LightningBoltBeam(es->origin2, es->pos.trBase); + break; +#endif + case EV_SCOREPLUM: + DEBUGNAME("EV_SCOREPLUM"); + CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); + break; + + // + // missile impacts + // + case EV_MISSILE_HIT: + DEBUGNAME("EV_MISSILE_HIT"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum ); + break; + + case EV_MISSILE_MISS: + DEBUGNAME("EV_MISSILE_MISS"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT ); + break; + + case EV_MISSILE_MISS_METAL: + DEBUGNAME("EV_MISSILE_MISS_METAL"); + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, 0, position, dir, IMPACTSOUND_METAL ); + break; + + case EV_RAILTRAIL: + DEBUGNAME("EV_RAILTRAIL"); + cent->currentState.weapon = WP_RAILGUN; + // if the end was on a nomark surface, don't make an explosion + CG_RailTrail( ci, es->origin2, es->pos.trBase ); + if ( es->eventParm != 255 ) { + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall( es->weapon, es->clientNum, position, dir, IMPACTSOUND_DEFAULT ); + } + break; + + //PKMOD - Ergodic 12/14/00 - add autosentry code for greater tracability + case EV_BULLET_HIT_WALL: + DEBUGNAME("EV_BULLET_HIT_WALL"); + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, qfalse ); + break; + + //PKMOD - Ergodic 12/14/00 - add autosentry code for greater tracability + case EV_BULLET_HIT_FLESH: + DEBUGNAME("EV_BULLET_HIT_FLESH"); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, qfalse ); + break; + + case EV_SHOTGUN: + DEBUGNAME("EV_SHOTGUN"); + CG_ShotgunFire( es ); + break; + + case EV_GENERAL_SOUND: + DEBUGNAME("EV_GENERAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_SOUND: // play from the player's head so it never diminishes + DEBUGNAME("EV_GLOBAL_SOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, CG_CustomSound( es->number, s ) ); + } + break; + + case EV_GLOBAL_TEAM_SOUND: // play from the player's head so it never diminishes + { + DEBUGNAME("EV_GLOBAL_TEAM_SOUND"); + switch( es->eventParm ) { + case GTS_RED_CAPTURE: // CTF: red team captured the blue flag, 1FCTF: red team captured the neutral flag + if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) + CG_AddBufferedSound( cgs.media.captureYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.captureOpponentSound ); + break; + case GTS_BLUE_CAPTURE: // CTF: blue team captured the red flag, 1FCTF: blue team captured the neutral flag + if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) + CG_AddBufferedSound( cgs.media.captureYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.captureOpponentSound ); + break; + case GTS_RED_RETURN: // CTF: blue flag returned, 1FCTF: never used + if ( cgs.clientinfo[cg.clientNum].team == TEAM_RED ) + CG_AddBufferedSound( cgs.media.returnYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.returnOpponentSound ); + // + CG_AddBufferedSound( cgs.media.blueFlagReturnedSound ); + break; + case GTS_BLUE_RETURN: // CTF red flag returned, 1FCTF: neutral flag returned + if ( cgs.clientinfo[cg.clientNum].team == TEAM_BLUE ) + CG_AddBufferedSound( cgs.media.returnYourTeamSound ); + else + CG_AddBufferedSound( cgs.media.returnOpponentSound ); + // + CG_AddBufferedSound( cgs.media.redFlagReturnedSound ); + break; + + case GTS_RED_TAKEN: // CTF: red team took blue flag, 1FCTF: blue team took the neutral flag + // if this player picked up the flag then a sound is played in CG_CheckLocalSounds + if (cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + } + else { + if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); + } + else if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); + } + } + break; + case GTS_BLUE_TAKEN: // CTF: blue team took the red flag, 1FCTF red team took the neutral flag + // if this player picked up the flag then a sound is played in CG_CheckLocalSounds + if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + } + else { + if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.yourTeamTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.enemyTookYourFlagSound ); + } + else if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { +#ifdef MISSIONPACK + if (cgs.gametype == GT_1FCTF) + CG_AddBufferedSound( cgs.media.enemyTookTheFlagSound ); + else +#endif + CG_AddBufferedSound( cgs.media.yourTeamTookEnemyFlagSound ); + } + } + break; + case GTS_REDOBELISK_ATTACKED: // Overload: red obelisk is being attacked + if (cgs.clientinfo[cg.clientNum].team == TEAM_RED) { + CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); + } + break; + case GTS_BLUEOBELISK_ATTACKED: // Overload: blue obelisk is being attacked + if (cgs.clientinfo[cg.clientNum].team == TEAM_BLUE) { + CG_AddBufferedSound( cgs.media.yourBaseIsUnderAttackSound ); + } + break; + + case GTS_REDTEAM_SCORED: + CG_AddBufferedSound(cgs.media.redScoredSound); + break; + case GTS_BLUETEAM_SCORED: + CG_AddBufferedSound(cgs.media.blueScoredSound); + break; + case GTS_REDTEAM_TOOK_LEAD: + CG_AddBufferedSound(cgs.media.redLeadsSound); + break; + case GTS_BLUETEAM_TOOK_LEAD: + CG_AddBufferedSound(cgs.media.blueLeadsSound); + break; + case GTS_TEAMS_ARE_TIED: + CG_AddBufferedSound( cgs.media.teamsTiedSound ); + break; +#ifdef MISSIONPACK + case GTS_KAMIKAZE: + trap_S_StartLocalSound(cgs.media.kamikazeFarSound, CHAN_ANNOUNCER); + break; +#endif + default: + break; + } + break; + } + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + DEBUGNAME("EV_PAIN"); + if ( cent->currentState.number != cg.snap->ps.clientNum ) { + CG_PainEvent( cent, es->eventParm ); + } + break; + + case EV_DEATH1: + case EV_DEATH2: + case EV_DEATH3: + DEBUGNAME("EV_DEATHx"); + trap_S_StartSound( NULL, es->number, CHAN_VOICE, + CG_CustomSound( es->number, va("*death%i.wav", event - EV_DEATH1 + 1) ) ); + break; + + + case EV_OBITUARY: + DEBUGNAME("EV_OBITUARY"); + CG_Obituary( es ); + break; + + // + // powerup events + // + case EV_POWERUP_QUAD: + DEBUGNAME("EV_POWERUP_QUAD"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_QUAD; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.quadSound ); + break; + case EV_POWERUP_BATTLESUIT: + DEBUGNAME("EV_POWERUP_BATTLESUIT"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_BATTLESUIT; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.protectSound ); + break; + case EV_POWERUP_REGEN: + DEBUGNAME("EV_POWERUP_REGEN"); + if ( es->number == cg.snap->ps.clientNum ) { + cg.powerupActive = PW_REGEN; + cg.powerupTime = cg.time; + } + trap_S_StartSound (NULL, es->number, CHAN_ITEM, cgs.media.regenSound ); + break; + + case EV_GIB_PLAYER: + DEBUGNAME("EV_GIB_PLAYER"); + // don't play gib sound when using the kamikaze because it interferes + // with the kamikaze sound, downside is that the gib sound will also + // not be played when someone is gibbed while just carrying the kamikaze + //PKMOD Ergodic - 07/08/01, remove kamizaki entity flag + // if ( !(es->eFlags & EF_KAMIKAZE) ) { + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); + // } + CG_GibPlayer( cent->lerpOrigin ); + break; + + case EV_STOPLOOPINGSOUND: + DEBUGNAME("EV_STOPLOOPINGSOUND"); + trap_S_StopLoopingSound( es->number ); + es->loopSound = 0; + break; + + case EV_DEBUG_LINE: + DEBUGNAME("EV_DEBUG_LINE"); + CG_Beam( cent ); + break; + + // PKMOD - Ergodic 07/11/00 Events + case EV_GRAVITY_RELEASED: + DEBUGNAME("EV_GRAVITY_RELEASED"); + cent->miscTime = cg.time; // scale up from this + //PKMOD - Ergodic 07/12/00 change from CHAN_voice to CHAN_AUTO + trap_S_StartSound ( cent->lerpOrigin, -1, CHAN_AUTO, cgs.media.sfx_pkagravitylaunched ); + //PKMOD - Ergodic 11/01/02 - Move the call of this function to cg_ents + // to fix the Invisible Gravity Well bug + //CG_GravityWellActivate( cent->lerpOrigin ); + break; + + // + // PKMOD - Ergodic 05/26/00 Events + // + case EV_BEARTRAP_DIE: // PKMOD - Ergodic 05/26/00 Events + DEBUGNAME("EV_BEARTRAP_DIE"); + ByteToDir( es->eventParm, dir ); + CG_BearTrapDie( position ); + break; + + case EV_BEARTRAP_SNAP: // PKMOD - Ergodic 07/01/00 Events + DEBUGNAME("EV_BEARTRAP_SNAP"); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkabeartrapsnap ); + break; + + case EV_BEARTRAP_DROP: // PKMOD - Ergodic 08/07/00 Events + DEBUGNAME("EV_BEARTRAP_DROP"); + + //PKMOD - Ergodic 07/17/03 - use timer for driver of model animation + cent->miscTime = cg.time; // scale up from this + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkabeartrapdrop ); + break; + + case EV_CHAINLIGHTNING_STRIKE: // PKMOD - Ergodic 08/22/00 Events + DEBUGNAME("EV_CHAINLIGHTNING_STRIKE"); + //uses hack for target location stored in es.angles + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); + + if ( rand() % 2 ) //random number: { 0, 1 } + trap_S_StartSound (es->angles, es->number, CHAN_AUTO, cgs.media.sfx_chainlightningstrike1 ); + else + trap_S_StartSound (es->angles, es->number, CHAN_AUTO, cgs.media.sfx_chainlightningstrike2 ); + break; + + //PKMOD Ergodic 05/30/00 switch to gauntlet for PK Items noammo state + case EV_PKA_NOAMMO: + DEBUGNAME("EV_PKA_NOAMMO"); + if ( es->number == cg.snap->ps.clientNum ) { + CG_PKA_OutOfAmmoChange(); + } + break; + + //PKMOD Ergodic 07/19/00 display lightning flash + case EV_LIGHTNING_FX: + DEBUGNAME("EV_LIGHTNING_FX"); + CG_Lightning_FX( position, es->eventParm ); //flash location and flash constantLight + break; + + //PKMOD - Ergodic 09/06/00 gravity well item suck sounds from Mongusta + case EV_GRAVITYWELL_SUCK: // PKMOD - Ergodic 09/06/00 Events + DEBUGNAME("EV_GRAVITYWELL_SUCK"); + switch ( rand() % 3 ) { //random numbers: { 0, 1, 2 } + case 0: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkagravitywell_suck1 ); + break; + case 1: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkagravitywell_suck2 ); + break; + default: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkagravitywell_suck3 ); + break; + } + break; + + //PKMOD - Ergodic 11/16/00 - add target_remove code + case EV_ITEM_REMOVE: + DEBUGNAME("EV_ITEM_REMOVE"); + { + gitem_t *item; + int index; + //12/16/00 - add non compressed flag + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/items/lostitem.wav", qfalse ) ); + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemRemove( index ); + } + + } + break; + + //PKMOD - Ergodic 11/20/00 - add target_remove code for powerups + case EV_GLOBAL_ITEM_REMOVE: + DEBUGNAME("EV_GLOBAL_ITEM_REMOVE"); + { + gitem_t *item; + int index; + + index = es->eventParm; // player predicted + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + // powerup pickups are global + //12/16/00 - add non compressed flag + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/items/lostpowerup.wav", qfalse ) ); + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemRemove( index ); + } + } + break; + + case EV_AUTOSENTRY_DROP: // PKMOD - Ergodic 11/22/00 drop the sentry sound + DEBUGNAME("EV_AUTOSENTRY_DROP"); + //PKMOD - Ergodic 11/25 - use timer for driver of model animation + cent->miscTime = cg.time; // scale up from this + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkasentrydrop ); + break; + + case EV_AUTOSENTRY_DIE: // PKMOD - Ergodic 11/22/00 blow-up the sentry sound + DEBUGNAME("EV_AUTOSENTRY_DIE"); + ByteToDir( es->eventParm, dir ); + CG_AutoSentryDie( position ); + break; + + //PKMOD - Ergodic 12/06/00 - special lightning shooter event + case EV_SHOOTER_LIGHTNING: + DEBUGNAME("EV_SHOOTER_LIGHTNING"); +// ByteToDir( es->eventParm, dir ); +// CG_MissileHitWall( es->weapon, 0, position, dir ); + CG_ChainLightning( cent ); + break; + + //PKMOD - Ergodic 12/14/00 - add autosentry code for greater tracability + case EV_AUTOSENTRY_HIT_WALL: + DEBUGNAME("EV_BULLET_HIT_WALL"); + ByteToDir( es->eventParm, dir ); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qfalse, ENTITYNUM_WORLD, qtrue ); + break; + + //PKMOD - Ergodic 12/14/00 - add autosentry code for greater tracability + case EV_AUTOSENTRY_HIT_FLESH: + DEBUGNAME("EV_BULLET_HIT_FLESH"); + CG_Bullet( es->pos.trBase, es->otherEntityNum, dir, qtrue, es->eventParm, qtrue ); + break; + + //PKMOD - Ergodic 12/26/00 add Beans fart noises from Mongusta + //PKMOD - Ergodic 04/13/01 - move gas effects routine to cg_effects and add bubbles + //PKMOD - Ergodic 06/30/01 add two more fart sounds from original Q1 PK + case EV_BEANS_TOOT: // PKMOD - Ergodic 12/26/00 Events + DEBUGNAME("EV_BEANS_TOOT"); + + //add gas effects + CG_BeansToot( cent->lerpOrigin ); + + //add gas sounds + //PKMOD - Ergodic 06/30/01 add two more fart sounds from original Q1 PK (was rand() % 5) + switch ( rand() % 7 ) { //random numbers: { 0, 1, 2, 3, 4, 5, 6 } + case 0: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart1 ); + break; + case 1: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart2 ); + break; + case 2: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart3 ); + break; + case 3: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart4 ); + break; + case 4: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart5 ); + break; + case 5: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart6 ); + break; + default: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart7 ); + break; + } + + break; + + //PKMOD - Ergodic 01/13/01 - add autosentry fire sounds from mongusta + case EV_AUTOSENTRY_FIRE: + DEBUGNAME("EV_AUTOSENTRY_FIRE"); + //add sentry firing noise + switch ( rand() % 3 ) { //random numbers: { 0, 1, 2 } + case 0: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkasentry1 ); + break; + case 1: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkasentry2 ); + break; + default: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkasentry3 ); + break; + } + + break; + + //PKMOD - Ergodic 03/26/01 - add autosentry ping sound + case EV_AUTOSENTRY_PING: + DEBUGNAME("EV_AUTOSENTRY_PING"); + //add sentry firing noise + switch ( rand() % 3 ) { //random numbers: { 0, 1, 2 } + case 0: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkasentry_ping1); + break; + case 1: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkasentry_ping2 ); + break; + default: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkasentry_ping3 ); + break; + } + + break; + + + //PKMOD - Ergodic 01/15/01 - add Lightning Discharge in water + case EV_LIGHTNING_WATER_DISCHARGE: + DEBUGNAME("EV_LIGHTNING_WATER_DISCHARGE"); + CG_Lightning_Water_Discharge (position, es->eventParm); // eventParm is duration/size + break; + + //PKMOD - Ergodic 01/21/01 - add event to display coordinate model for exploding shells debug + case EV_COORD: + DEBUGNAME("EV_COORD"); + CG_Coord( es ); + break; + + //PKMOD - Ergodic 07/03/01 ChainLightning reflect sounds + case EV_CHAINLIGHTNING_REFLECT: // PKMOD - Ergodic 07/03/01 Events + DEBUGNAME("EV_CHAINLIGHTNING_REFLECT"); + //uses hack for target location stored in es.angles + if ( rand() % 2 ) //random number: { 0, 1 } + trap_S_StartSound (es->angles, es->number, CHAN_AUTO, cgs.media.sfx_chainlightningreflect1 ); + else + trap_S_StartSound (es->angles, es->number, CHAN_AUTO, cgs.media.sfx_chainlightningreflect2 ); + break; + + //PKMOD - Ergodic 01/07/02 - send message to client if not all Private Bot parts are held + case EV_INCOMPLETE_PRIVATEBOT: // PKMOD - Ergodic 01/07/02 Events + DEBUGNAME("EV_INCOMPLETE_PRIVATEBOT"); + + //PKMOD - Ergodic 03/17/04 - only send this message to the owner + if ( es->number == cg.snap->ps.clientNum ) { + CG_CenterPrint( "Incomplete Private Bot", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); + } + break; + + //PKMOD - Ergodic 03/18/02 - send message to client that no more Private Bots are available + case EV_NOAVAILABLE_PRIVATEBOTS: // PKMOD - Ergodic 03/18/02 Events + DEBUGNAME("EV_NOAVAILABLE_PRIVATEBOTS"); + switch ( rand() % 4 ) { //random numbers: { 0, 1, 2, 3 } + case 0: + CG_CenterPrint( "All Private Bots are active", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + break; + case 1: + CG_CenterPrint( "All Private Bots are deployed", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + break; + case 2: + CG_CenterPrint( "No Private Bots are left to assign", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + break; + default: + CG_CenterPrint( "All Private Bots are allocated", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + break; + } + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); + break; + + //PKMOD - Ergodic 02/07/02 - message client if Private Bot is completed + + case EV_COMPLETED_PRIVATEBOT: + DEBUGNAME("EV_COMPLETED_PRIVATEBOT"); + { + int index; + + index = es->eventParm; // player predicted + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.pkapribot_complete ); + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + //PKMOD - Ergodic 02/10/02 - send FRAG message to Private Bot's owner + case EV_PRIVATEBOT_FRAG: + DEBUGNAME("EV_PRIVATEBOT_FRAG"); + switch ( rand() % 2 ) { //random numbers: { 0, 1 } + case 0: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.pkapribot_frag1 ); + break; + default: + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.pkapribot_frag2 ); + break; + } + break; + + //PKMOD - Ergodic 06/09/02 - create the teleport flash for the personal sentry (teleport in sound) + case EV_TELE_IN_PERSONALSENTRY: + DEBUGNAME( "EV_TELE_IN_PERSONALSENTRY" ); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); + CG_PersentrySpawnEffect( position ); + break; + + //PKMOD - Ergodic 08/03/02 - create the teleport flash for the personal sentry (teleport out sound) + case EV_TELE_OUT_PERSONALSENTRY: + DEBUGNAME( "EV_TELE_OUT_PERSONALSENTRY" ); + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); + CG_PersentrySpawnEffect( position ); + break; + + //PKMOD - Ergodic 06/14/02 - create the firing sound for the personal sentry + //PKMOD - Ergodic 08/26/02 - add Personal Sentry fire sounds from StarDagger + case EV_FIRE_PERSONALSENTRY: + DEBUGNAME( "EV_FIRE_PERSONALSENTRY" ); + //add sentry firing noise + switch ( rand() % 3 ) { //random numbers: { 0, 1, 2 } + case 0: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.pkapersentry_fire1 ); + break; + case 1: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.pkapersentry_fire2 ); + break; + default: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.pkapersentry_fire3 ); + break; + } + + break; + + //PKMOD - Ergodic 11/21/03 - Play an earthquake sound + //PKMOD - Ergodic 12/07/03 - removed, code moved to global sound + //case EV_EARTHQUAKE: + // DEBUGNAME( "EV_EARTHQUAKE" ); + // trap_S_StartSound (NULL, ENTITYNUM_NONE, CHAN_AUTO, cgs.media.pkaearthquake ); + // break; + + //PKMOD - Ergodic 12/06/03 - play the charge up sound + case EV_CHAINLIGHTNING_CHARGE_UP: + DEBUGNAME("EV_CHAINLIGHTNING_CHARGE_UP"); + trap_S_StartSound(position, es->number, CHAN_AUTO, cgs.media.pkachargeup ); + break; + + //PKMOD - Ergodic 01/05/04 - add quad farting logic for differing CG graphic sequence + case EV_QUADBEANS_TOOT: + DEBUGNAME("EV_QUADBEANS_TOOT"); + + //add gas effects + CG_QuadBeansToot( cent->lerpOrigin ); + + //PKMOD - Ergodic 03/17/04 - add new quad farting sounds that occur very rarely + if ( ( rand() % 100 ) > 91 ) { + switch ( rand() % 3 ) { //random numbers: { 0, 1, 2 } + case 0: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkaquadfart1 ); + break; + case 1: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkaquadfart2 ); + break; + default: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkaquadfart3 ); + break; + } + } else { + switch ( rand() % 7 ) { //random numbers: { 0, 1, 2, 3, 4, 5, 6 } + case 0: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart1 ); + break; + case 1: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart2 ); + break; + case 2: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart3 ); + break; + case 3: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart4 ); + break; + case 4: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart5 ); + break; + case 5: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart6 ); + break; + default: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.sfx_pkafart7 ); + break; + } + } + + //PKMOD - Ergodic 01/14/04 - add gib effect at beans blast area + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); + CG_GibPlayer( cent->lerpOrigin ); + + break; + + + + default: + DEBUGNAME("UNKNOWN"); + CG_Error( "Unknown event: %i", event ); + break; + } + +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) { + +//PKMOD - Ergodic 09/30/01 - debug missing EV_LIGHTNING_FX event (inactive) +/* + entityState_t *es; + int event; + + es = ¢->currentState; + event = es->event & ~EV_EVENT_BITS; + if ( event == EV_LIGHTNING_FX ) + Com_Printf( "CG_CheckEvents detected!\n" ); + +*/ + + // check for event-only entities + if ( cent->currentState.eType > ET_EVENTS ) { + if ( cent->previousEvent ) { + return; // already fired + } + // if this is a player event set the entity number of the client entity number + if ( cent->currentState.eFlags & EF_PLAYER_EVENT ) { + cent->currentState.number = cent->currentState.otherEntityNum; + } + + cent->previousEvent = 1; + + cent->currentState.event = cent->currentState.eType - ET_EVENTS; + } else { + // check for events riding with another entity + if ( cent->currentState.event == cent->previousEvent ) { + return; + } + cent->previousEvent = cent->currentState.event; + if ( ( cent->currentState.event & ~EV_EVENT_BITS ) == 0 ) { + return; + } + } + + // calculate the position at exactly the frame time + BG_EvaluateTrajectory( ¢->currentState.pos, cg.snap->serverTime, cent->lerpOrigin ); + CG_SetEntitySoundPosition( cent ); + + CG_EntityEvent( cent, cent->lerpOrigin ); +} + diff --git a/quake3/source/code/cgame/cg_info.c b/quake3/source/code/cgame/cg_info.c new file mode 100644 index 0000000..a3124e9 --- /dev/null +++ b/quake3/source/code/cgame/cg_info.c @@ -0,0 +1,315 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_info.c -- display information while data is being loading + +#include "cg_local.h" + +#define MAX_LOADING_PLAYER_ICONS 16 + //PKMOD - Ergodic 02/04/01 - make 3 rows of registered icons +#define MAX_LOADING_ITEM_ICONS 39 //was 26 + +static int loadingPlayerIconCount; +static int loadingItemIconCount; +static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; +static qhandle_t loadingItemIcons[MAX_LOADING_ITEM_ICONS]; + + +/* +=================== +CG_DrawLoadingIcons +=================== +*/ +static void CG_DrawLoadingIcons( void ) { + int n; + int x, y; + + for( n = 0; n < loadingPlayerIconCount; n++ ) { + x = 16 + n * 78; + y = 324-40; + CG_DrawPic( x, y, 64, 64, loadingPlayerIcons[n] ); + } + +/* PKMOD - Ergodic 02/04/01 - original code + for( n = 0; n < loadingItemIconCount; n++ ) { + y = 400-40; + if( n >= 13 ) { + y += 40; + } + x = 16 + n % 13 * 48; + CG_DrawPic( x, y, 32, 32, loadingItemIcons[n] ); + } +*/ + //PKMOD - Ergodic 02/04/01 - make 3 row s of registered icons + for( n = 0; n < loadingItemIconCount; n++ ) { + y = 400-40; + if ( n >= 26 ) { + y += 80; + } + else { + if( n >= 13 ) { + y += 40; + } + } + x = 16 + n % 13 * 48; + CG_DrawPic( x, y, 32, 32, loadingItemIcons[n] ); + } + +} + + +/* +====================== +CG_LoadingString + +====================== +*/ +void CG_LoadingString( const char *s ) { + Q_strncpyz( cg.infoScreenText, s, sizeof( cg.infoScreenText ) ); + + trap_UpdateScreen(); +} + +/* +=================== +CG_LoadingItem +=================== +*/ +void CG_LoadingItem( int itemNum ) { + gitem_t *item; + + item = &bg_itemlist[itemNum]; + + if ( item->icon && loadingItemIconCount < MAX_LOADING_ITEM_ICONS ) { + loadingItemIcons[loadingItemIconCount++] = trap_R_RegisterShaderNoMip( item->icon ); + } + + CG_LoadingString( item->pickup_name ); +} + +/* +=================== +CG_LoadingClient +=================== +*/ +void CG_LoadingClient( int clientNum ) { + const char *info; + char *skin; + char personality[MAX_QPATH]; + char model[MAX_QPATH]; + char iconName[MAX_QPATH]; + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + + if ( loadingPlayerIconCount < MAX_LOADING_PLAYER_ICONS ) { + Q_strncpyz( model, Info_ValueForKey( info, "model" ), sizeof( model ) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } else { + skin = "default"; + } + + Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", model, skin ); + + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { + Com_sprintf( iconName, MAX_QPATH, "models/players/characters/%s/icon_%s.tga", model, skin ); + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + } + if ( !loadingPlayerIcons[loadingPlayerIconCount] ) { + Com_sprintf( iconName, MAX_QPATH, "models/players/%s/icon_%s.tga", DEFAULT_MODEL, "default" ); + loadingPlayerIcons[loadingPlayerIconCount] = trap_R_RegisterShaderNoMip( iconName ); + } + if ( loadingPlayerIcons[loadingPlayerIconCount] ) { + loadingPlayerIconCount++; + } + } + + Q_strncpyz( personality, Info_ValueForKey( info, "n" ), sizeof(personality) ); + Q_CleanStr( personality ); + + if( cgs.gametype == GT_SINGLE_PLAYER ) { + trap_S_RegisterSound( va( "sound/player/announce/%s.wav", personality ), qtrue ); + } + + CG_LoadingString( personality ); +} + + +/* +==================== +CG_DrawInformation + +Draw all the status / pacifier stuff during level loading +==================== +*/ +void CG_DrawInformation( void ) { + const char *s; + const char *info; + const char *sysInfo; + int y; + int value; + //PKMOD - Ergodic 11/07/00 - add logic to enable hub limits + int hold_hub_flag; + + qhandle_t levelshot; + qhandle_t detail; + char buf[1024]; + + info = CG_ConfigString( CS_SERVERINFO ); + sysInfo = CG_ConfigString( CS_SYSTEMINFO ); + + s = Info_ValueForKey( info, "mapname" ); + levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s.tga", s ) ); + if ( !levelshot ) { + levelshot = trap_R_RegisterShaderNoMip( "menu/art/unknownmap" ); + } + trap_R_SetColor( NULL ); + CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, levelshot ); + + // blend a detail texture over it + detail = trap_R_RegisterShader( "levelShotDetail" ); + trap_R_DrawStretchPic( 0, 0, cgs.glconfig.vidWidth, cgs.glconfig.vidHeight, 0, 0, 2.5, 2, detail ); + + // draw the icons of things as they are loaded + CG_DrawLoadingIcons(); + + // the first 150 rows are reserved for the client connection + // screen to write into + if ( cg.infoScreenText[0] ) { + UI_DrawProportionalString( 320, 128-32, va("Loading... %s", cg.infoScreenText), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + } else { + UI_DrawProportionalString( 320, 128-32, "Awaiting snapshot...", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + } + + // draw info string information + + y = 180-32; + + // don't print server lines if playing a local game + trap_Cvar_VariableStringBuffer( "sv_running", buf, sizeof( buf ) ); + if ( !atoi( buf ) ) { + // server hostname + Q_strncpyz(buf, Info_ValueForKey( info, "sv_hostname" ), 1024); + Q_CleanStr(buf); + UI_DrawProportionalString( 320, y, buf, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + + // pure server + s = Info_ValueForKey( sysInfo, "sv_pure" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( 320, y, "Pure Server", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // server-specific message of the day + s = CG_ConfigString( CS_MOTD ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // some extra space after hostname and motd + y += 10; + } + + // map-specific message (long map name) + s = CG_ConfigString( CS_MESSAGE ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // cheats warning + s = Info_ValueForKey( sysInfo, "sv_cheats" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( 320, y, "CHEATS ARE ENABLED", + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + // game type + switch ( cgs.gametype ) { + case GT_FFA: + s = "Free For All"; + break; + case GT_SINGLE_PLAYER: + s = "Single Player"; + break; + case GT_TOURNAMENT: + s = "Tournament"; + break; + case GT_TEAM: + s = "Team Deathmatch"; + break; + case GT_CTF: + s = "Capture The Flag"; + break; +#ifdef MISSIONPACK + case GT_1FCTF: + s = "One Flag CTF"; + break; + case GT_OBELISK: + s = "Overload"; + break; + case GT_HARVESTER: + s = "Harvester"; + break; +#endif + default: + s = "Unknown Gametype"; + break; + } + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + + y += PROP_HEIGHT; + + //PKMOD - Ergodic 11/07/00 - add logic to enable hub limits + hold_hub_flag = atoi( Info_ValueForKey( info, "hub_flag" ) ); + + if ( hold_hub_flag ) { + value = atoi( Info_ValueForKey( info, "hub_timelimit" ) ); + } + else { + value = atoi( Info_ValueForKey( info, "timelimit" ) ); + } + + if ( value ) { + UI_DrawProportionalString( 320, y, va( "timelimit %i", value ), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + + if (cgs.gametype != GT_CTF) { + //PKMOD - Ergodic 11/07/00 - add logic to enable hub limits + if ( hold_hub_flag ) { + value = atoi( Info_ValueForKey( info, "hub_fraglimit" ) ); + } + else { + value = atoi( Info_ValueForKey( info, "fraglimit" ) ); + } + + if ( value ) { + UI_DrawProportionalString( 320, y, va( "fraglimit %i", value ), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + } + + if (cgs.gametype >= GT_CTF) { + value = atoi( Info_ValueForKey( info, "capturelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "capturelimit %i", value ), + UI_CENTER|UI_SMALLFONT|UI_DROPSHADOW, colorWhite ); + y += PROP_HEIGHT; + } + } +} + diff --git a/quake3/source/code/cgame/cg_local.h b/quake3/source/code/cgame/cg_local.h new file mode 100644 index 0000000..01b82eb --- /dev/null +++ b/quake3/source/code/cgame/cg_local.h @@ -0,0 +1,2194 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#include "../game/q_shared.h" +#include "tr_types.h" +#include "../game/bg_public.h" +#include "cg_public.h" + + +// The entire cgame module is unloaded and reloaded on each level change, +// so there is NO persistant data between levels on the client side. +// If you absolutely need something stored, it can either be kept +// by the server in the server stored userinfos, or stashed in a cvar. + +#ifdef MISSIONPACK +#define CG_FONT_THRESHOLD 0.1 +#endif + +#define POWERUP_BLINKS 5 + +#define POWERUP_BLINK_TIME 1000 +#define FADE_TIME 200 +#define PULSE_TIME 200 +#define DAMAGE_DEFLECT_TIME 100 +#define DAMAGE_RETURN_TIME 400 +#define DAMAGE_TIME 500 +#define LAND_DEFLECT_TIME 150 +#define LAND_RETURN_TIME 300 +#define STEP_TIME 200 +#define DUCK_TIME 100 +#define PAIN_TWITCH_TIME 200 +#define WEAPON_SELECT_TIME 1400 +#define ITEM_SCALEUP_TIME 1000 +#define ZOOM_TIME 150 +#define ITEM_BLOB_TIME 200 +#define MUZZLE_FLASH_TIME 20 +#define SINK_TIME 1000 // time for fragments to sink into ground before going away +#define ATTACKER_HEAD_TIME 10000 +#define REWARD_TIME 3000 + +#define PULSE_SCALE 1.5 // amount to scale up the icons when activating + +#define MAX_STEP_CHANGE 32 + +#define MAX_VERTS_ON_POLY 10 +#define MAX_MARK_POLYS 256 + +#define STAT_MINUS 10 // num frame for '-' stats digit + +#define ICON_SIZE 48 +//PKMOD - Ergodic 05/11/01 - make a smaller icon size for display multiple holdables +#define ICON_SIZE_SMALL 32 + +#define CHAR_WIDTH 32 +#define CHAR_HEIGHT 48 +#define TEXT_ICON_SPACE 4 + +#define TEAMCHAT_WIDTH 80 +#define TEAMCHAT_HEIGHT 8 + +// very large characters +#define GIANT_WIDTH 32 +#define GIANT_HEIGHT 48 + +#define NUM_CROSSHAIRS 10 + +#define TEAM_OVERLAY_MAXNAME_WIDTH 12 +#define TEAM_OVERLAY_MAXLOCATION_WIDTH 16 + +#define DEFAULT_MODEL "sarge" +#ifdef MISSIONPACK +#define DEFAULT_TEAM_MODEL "james" +#define DEFAULT_TEAM_HEAD "*james" +#else +#define DEFAULT_TEAM_MODEL "sarge" +#define DEFAULT_TEAM_HEAD "sarge" +#endif + +#define DEFAULT_REDTEAM_NAME "Stroggs" +#define DEFAULT_BLUETEAM_NAME "Pagans" + +typedef enum { + FOOTSTEP_NORMAL, + FOOTSTEP_BOOT, + FOOTSTEP_FLESH, + FOOTSTEP_MECH, + FOOTSTEP_ENERGY, + FOOTSTEP_METAL, + FOOTSTEP_SPLASH, + + FOOTSTEP_TOTAL +} footstep_t; + +typedef enum { + IMPACTSOUND_DEFAULT, + IMPACTSOUND_METAL, + IMPACTSOUND_FLESH +} impactSound_t; + +//================================================= + +// player entities need to track more information +// than any other type of entity. + +// note that not every player entity is a client entity, +// because corpses after respawn are outside the normal +// client numbering range + +// when changing animation, set animationTime to frameTime + lerping time +// The current lerp will finish out, then it will lerp to the new animation +typedef struct { + int oldFrame; + int oldFrameTime; // time when ->oldFrame was exactly on + + int frame; + int frameTime; // time when ->frame will be exactly on + + float backlerp; + + float yawAngle; + qboolean yawing; + float pitchAngle; + qboolean pitching; + + int animationNumber; // may include ANIM_TOGGLEBIT + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact +} lerpFrame_t; + + +typedef struct { + lerpFrame_t legs, torso, flag; + int painTime; + int painDirection; // flip from 0 to 1 + int lightningFiring; + + // railgun trail spawning + vec3_t railgunImpact; + qboolean railgunFlash; + + // machinegun spinning + float barrelAngle; + int barrelTime; + qboolean barrelSpinning; +} playerEntity_t; + +//================================================= + + + +// centity_t have a direct corespondence with gentity_t in the game, but +// only the entityState_t is directly communicated to the cgame +typedef struct centity_s { + entityState_t currentState; // from cg.frame + entityState_t nextState; // from cg.nextFrame, if available + qboolean interpolate; // true if next is valid to interpolate to + qboolean currentValid; // true if cg.frame holds this entity + + int muzzleFlashTime; // move to playerEntity? + int previousEvent; + int teleportFlag; + + int trailTime; // so missile trails can handle dropped initial packets + int dustTrailTime; + int miscTime; + + int snapShotTime; // last time this entity was found in a snapshot + //PKMOD - Ergodic 10/20/01 - hold the radiate state timer + int PKA_RadiateTime; + //PKMOD - Ergodic 11/30/01 - hold the radiate infected state timer + int PKA_RadiateInfectTime; + //PKMOD - Ergodic 11/17/01 - add new custom shader for radiate effect on simple items + qhandle_t PKA_customShader; // use one image for the entire thing + + playerEntity_t pe; + + int errorTime; // decay the error from this time + vec3_t errorOrigin; + vec3_t errorAngles; + + qboolean extrapolated; // false if origin / angles is an interpolation + vec3_t rawOrigin; + vec3_t rawAngles; + + vec3_t beamEnd; + + // exact interpolated position of entity on this frame + vec3_t lerpOrigin; + vec3_t lerpAngles; +} centity_t; + + +//====================================================================== + +// local entities are created as a result of events or predicted actions, +// and live independantly from all server transmitted entities + +typedef struct markPoly_s { + struct markPoly_s *prevMark, *nextMark; + int time; + qhandle_t markShader; + qboolean alphaFade; // fade alpha instead of rgb + float color[4]; + poly_t poly; + polyVert_t verts[MAX_VERTS_ON_POLY]; +} markPoly_t; + + +typedef enum { + LE_MARK, + LE_EXPLOSION, + LE_SPRITE_EXPLOSION, + LE_FRAGMENT, + LE_MOVE_SCALE_FADE, + LE_FALL_SCALE_FADE, + LE_FADE_RGB, + LE_SCALE_FADE, + LE_SCOREPLUM, + LE_SCALED_SPRITE_EXPLOSION, //PKMOD - Ergodic 01/12/04 - add LE for quad beans + +#ifdef MISSIONPACK + LE_KAMIKAZE, + LE_INVULIMPACT, + LE_INVULJUICED, + LE_SHOWREFENTITY +#endif +} leType_t; + +typedef enum { + LEF_PUFF_DONT_SCALE = 0x0001, // do not scale size over time + LEF_TUMBLE = 0x0002, // tumble over time, used for ejecting shells + LEF_SOUND1 = 0x0004, // sound 1 for kamikaze + LEF_SOUND2 = 0x0008 // sound 2 for kamikaze +} leFlag_t; + +typedef enum { + LEMT_NONE, + LEMT_BURN, + LEMT_BLOOD +} leMarkType_t; // fragment local entities can leave marks on walls + +typedef enum { + LEBS_NONE, + LEBS_BLOOD, + LEBS_BRASS +} leBounceSoundType_t; // fragment local entities can make sounds on impacts + +typedef struct localEntity_s { + struct localEntity_s *prev, *next; + leType_t leType; + int leFlags; + + int startTime; + int endTime; + int fadeInTime; + + float lifeRate; // 1.0 / (endTime - startTime) + + trajectory_t pos; + trajectory_t angles; + + float bounceFactor; // 0.0 = no bounce, 1.0 = perfect + + float color[4]; + + float radius; + + float light; + vec3_t lightColor; + + leMarkType_t leMarkType; // mark to leave on fragment impact + leBounceSoundType_t leBounceSoundType; + + refEntity_t refEntity; +} localEntity_t; + +//====================================================================== + + +typedef struct { + int client; + int score; + int ping; + int time; + int scoreFlags; + int powerUps; + int accuracy; + int impressiveCount; + int excellentCount; + int guantletCount; + int defendCount; + int assistCount; + int captures; + qboolean perfect; + int team; + //PKMOD - Ergodic 02/28/04 - add PainKiller medal awards to ownerdraw + int painkillerCount; +} score_t; + +// each client has an associated clientInfo_t +// that contains media references necessary to present the +// client model and other color coded effects +// this is regenerated each time a client's configstring changes, +// usually as a result of a userinfo (name, model, etc) change +#define MAX_CUSTOM_SOUNDS 32 + +typedef struct { + qboolean infoValid; + + char name[MAX_QPATH]; + team_t team; + + int botSkill; // 0 = not bot, 1-5 = bot + //PKMOD - Ergodic 01/09/02 - add info to structure so Private Bot will not appear in scoreboard + int privateBot; // 0 = Not Private Bot, 1 = Private Bot + + vec3_t color1; + vec3_t color2; + + int score; // updated by score servercmds + int location; // location index for team mode + int health; // you only get this info about your teammates + int armor; + int curWeapon; + + int handicap; + int wins, losses; // in tourney mode + + int teamTask; // task in teamplay (offence/defence) + qboolean teamLeader; // true when this is a team leader + + int powerups; // so can display quad/flag status + + int medkitUsageTime; + int invulnerabilityStartTime; + int invulnerabilityStopTime; + + int breathPuffTime; + + // when clientinfo is changed, the loading of models/skins/sounds + // can be deferred until you are dead, to prevent hitches in + // gameplay + char modelName[MAX_QPATH]; + char skinName[MAX_QPATH]; + char headModelName[MAX_QPATH]; + char headSkinName[MAX_QPATH]; + char redTeam[MAX_TEAMNAME]; + char blueTeam[MAX_TEAMNAME]; + qboolean deferred; + + qboolean newAnims; // true if using the new mission pack animations + qboolean fixedlegs; // true if legs yaw is always the same as torso yaw + qboolean fixedtorso; // true if torso never changes yaw + + vec3_t headOffset; // move head in icon views + footstep_t footsteps; + gender_t gender; // from model + + qhandle_t legsModel; + qhandle_t legsSkin; + + qhandle_t torsoModel; + qhandle_t torsoSkin; + + qhandle_t headModel; + qhandle_t headSkin; + + qhandle_t modelIcon; + + animation_t animations[MAX_TOTALANIMATIONS]; + + sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; +} clientInfo_t; + + +// each WP_* weapon enum has an associated weaponInfo_t +// that contains media references necessary to present the +// weapon and its effects +typedef struct weaponInfo_s { + qboolean registered; + gitem_t *item; + + qhandle_t handsModel; // the hands don't actually draw, they just position the weapon + qhandle_t weaponModel; + qhandle_t barrelModel; + qhandle_t flashModel; + + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag + + float flashDlight; + vec3_t flashDlightColor; + sfxHandle_t flashSound[4]; // fast firing weapons randomly choose + + qhandle_t weaponIcon; + qhandle_t ammoIcon; + + qhandle_t ammoModel; + + qhandle_t missileModel; + sfxHandle_t missileSound; + void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + float missileDlight; + vec3_t missileDlightColor; + int missileRenderfx; + + void (*ejectBrassFunc)( centity_t * ); + + float trailRadius; + float wiTrailTime; + + sfxHandle_t readySound; + sfxHandle_t firingSound; + qboolean loopFireSound; +} weaponInfo_t; + + +// each IT_* item has an associated itemInfo_t +// that constains media references necessary to present the +// item and its effects +typedef struct { + qboolean registered; + qhandle_t models[MAX_ITEM_MODELS]; + qhandle_t icon; +} itemInfo_t; + + +typedef struct { + int itemNum; +} powerupInfo_t; + + +#define MAX_SKULLTRAIL 10 + +typedef struct { + vec3_t positions[MAX_SKULLTRAIL]; + int numpositions; +} skulltrail_t; + +//PKMOD - Ergodic 10/13/00 - add hubinfo fields +typedef struct { + char info[128]; +} hubInfoDisplay_t; + + +#define MAX_REWARDSTACK 10 +#define MAX_SOUNDBUFFER 20 + +//====================================================================== + +// all cg.stepTime, cg.duckTime, cg.landTime, etc are set to cg.time when the action +// occurs, and they will have visible effects for #define STEP_TIME or whatever msec after + +#define MAX_PREDICTED_EVENTS 16 + +typedef struct { + int clientFrame; // incremented each frame + + int clientNum; + + qboolean demoPlayback; + qboolean levelShot; // taking a level menu screenshot + int deferredPlayerLoading; + qboolean loading; // don't defer players at initial startup + qboolean intermissionStarted; // don't play voice rewards, because game will end shortly + + // there are only one or two snapshot_t that are relevent at a time + int latestSnapshotNum; // the number of snapshots the client system has received + int latestSnapshotTime; // the time from latestSnapshotNum, so we don't need to read the snapshot yet + + snapshot_t *snap; // cg.snap->serverTime <= cg.time + snapshot_t *nextSnap; // cg.nextSnap->serverTime > cg.time, or NULL + snapshot_t activeSnapshots[2]; + + float frameInterpolation; // (float)( cg.time - cg.frame->serverTime ) / (cg.nextFrame->serverTime - cg.frame->serverTime) + + qboolean thisFrameTeleport; + qboolean nextFrameTeleport; + + int frametime; // cg.time - cg.oldTime + + int time; // this is the time value that the client + // is rendering at. + int oldTime; // time at last frame, used for missile trails and prediction checking + + int physicsTime; // either cg.snap->time or cg.nextSnap->time + + int timelimitWarnings; // 5 min, 1 min, overtime + int fraglimitWarnings; + + qboolean mapRestart; // set on a map restart to set back the weapon + + qboolean renderingThirdPerson; // during deaths, chasecams, etc + + // prediction state + qboolean hyperspace; // true if prediction has hit a trigger_teleport + playerState_t predictedPlayerState; + centity_t predictedPlayerEntity; + qboolean validPPS; // clear until the first call to CG_PredictPlayerState + int predictedErrorTime; + vec3_t predictedError; + + int eventSequence; + int predictableEvents[MAX_PREDICTED_EVENTS]; + + float stepChange; // for stair up smoothing + int stepTime; + + float duckChange; // for duck viewheight smoothing + int duckTime; + + float landChange; // for landing hard + int landTime; + + // input state sent to server + int weaponSelect; + + // auto rotating items + vec3_t autoAngles; + vec3_t autoAxis[3]; + vec3_t autoAnglesFast; + vec3_t autoAxisFast[3]; + //PKMOD - Ergodic 01/27/02 - add slower rotating items + vec3_t autoAnglesSlow; + vec3_t autoAxisSlow[3]; + + // view rendering + refdef_t refdef; + vec3_t refdefViewAngles; // will be converted to refdef.viewaxis + + // zoom key + qboolean zoomed; + int zoomTime; + float zoomSensitivity; + + // information screen text during loading + char infoScreenText[MAX_STRING_CHARS]; + + // scoreboard + int scoresRequestTime; + int numScores; + int selectedScore; + int teamScores[2]; + score_t scores[MAX_CLIENTS]; + qboolean showScores; + qboolean scoreBoardShowing; + int scoreFadeTime; + char killerName[MAX_NAME_LENGTH]; + char spectatorList[MAX_STRING_CHARS]; // list of names + int spectatorLen; // length of list + float spectatorWidth; // width in device units + int spectatorTime; // next time to offset + int spectatorPaintX; // current paint x + int spectatorPaintX2; // current paint x + int spectatorOffset; // current offset from start + int spectatorPaintLen; // current offset from start + + // skull trails + skulltrail_t skulltrails[MAX_CLIENTS]; + + // centerprinting + int centerPrintTime; + int centerPrintCharWidth; + int centerPrintY; + char centerPrint[1024]; + int centerPrintLines; + + // low ammo warning state + int lowAmmoWarning; // 1 = low, 2 = empty + + // kill timers for carnage reward + int lastKillTime; + + // crosshair client ID + int crosshairClientNum; + int crosshairClientTime; + + // powerup active flashing + int powerupActive; + int powerupTime; + + // attacking player + int attackerTime; + int voiceTime; + + // reward medals + int rewardStack; + int rewardTime; + int rewardCount[MAX_REWARDSTACK]; + qhandle_t rewardShader[MAX_REWARDSTACK]; + qhandle_t rewardSound[MAX_REWARDSTACK]; + + // sound buffer mainly for announcer sounds + int soundBufferIn; + int soundBufferOut; + int soundTime; + qhandle_t soundBuffer[MAX_SOUNDBUFFER]; + + // for voice chat buffer + int voiceChatTime; + int voiceChatBufferIn; + int voiceChatBufferOut; + + // warmup countdown + int warmup; + int warmupCount; + + //========================== + + int itemPickup; + int itemPickupTime; + int itemPickupBlendTime; // the pulse around the crosshair is timed seperately + + int weaponSelectTime; + int weaponAnimation; + int weaponAnimationTime; + + // blend blobs + float damageTime; + float damageX, damageY, damageValue; + + // status bar head + float headYaw; + float headEndPitch; + float headEndYaw; + int headEndTime; + float headStartPitch; + float headStartYaw; + int headStartTime; + + // view movement + float v_dmg_time; + float v_dmg_pitch; + float v_dmg_roll; + + vec3_t kick_angles; // weapon kicks + vec3_t kick_origin; + + // temp working variables for player view + float bobfracsin; + int bobcycle; + float xyspeed; + int nextOrbitTime; + + //qboolean cameraMode; // if rendering from a loaded camera + + + // development tool + refEntity_t testModelEntity; + char testModelName[MAX_QPATH]; + qboolean testGun; + +} cg_t; + + +// all of the model, shader, and sound references that are +// loaded at gamestate time are stored in cgMedia_t +// Other media that can be tied to clients, weapons, or items are +// stored in the clientInfo_t, itemInfo_t, weaponInfo_t, and powerupInfo_t +typedef struct { + qhandle_t charsetShader; + qhandle_t charsetProp; + qhandle_t charsetPropGlow; + qhandle_t charsetPropB; + qhandle_t whiteShader; + + qhandle_t redCubeModel; + qhandle_t blueCubeModel; + qhandle_t redCubeIcon; + qhandle_t blueCubeIcon; + qhandle_t redFlagModel; + qhandle_t blueFlagModel; + qhandle_t neutralFlagModel; + qhandle_t redFlagShader[3]; + qhandle_t blueFlagShader[3]; + qhandle_t flagShader[4]; + + qhandle_t flagPoleModel; + qhandle_t flagFlapModel; + + qhandle_t redFlagFlapSkin; + qhandle_t blueFlagFlapSkin; + qhandle_t neutralFlagFlapSkin; + + qhandle_t redFlagBaseModel; + qhandle_t blueFlagBaseModel; + qhandle_t neutralFlagBaseModel; + +#ifdef MISSIONPACK + qhandle_t overloadBaseModel; + qhandle_t overloadTargetModel; + qhandle_t overloadLightsModel; + qhandle_t overloadEnergyModel; + + qhandle_t harvesterModel; + qhandle_t harvesterRedSkin; + qhandle_t harvesterBlueSkin; + qhandle_t harvesterNeutralModel; +#endif + + qhandle_t armorModel; + qhandle_t armorIcon; + + qhandle_t teamStatusBar; + + qhandle_t deferShader; + + // gib explosions + qhandle_t gibAbdomen; + qhandle_t gibArm; + qhandle_t gibChest; + qhandle_t gibFist; + qhandle_t gibFoot; + qhandle_t gibForearm; + qhandle_t gibIntestine; + qhandle_t gibLeg; + qhandle_t gibSkull; + qhandle_t gibBrain; + + qhandle_t smoke2; + + qhandle_t machinegunBrassModel; + qhandle_t shotgunBrassModel; + + qhandle_t railRingsShader; + qhandle_t railCoreShader; + + qhandle_t lightningShader; + + qhandle_t friendShader; + + qhandle_t balloonShader; + qhandle_t connectionShader; + + qhandle_t selectShader; + qhandle_t viewBloodShader; + qhandle_t tracerShader; + qhandle_t crosshairShader[NUM_CROSSHAIRS]; + qhandle_t lagometerShader; + qhandle_t backTileShader; + qhandle_t noammoShader; + + qhandle_t smokePuffShader; + qhandle_t smokePuffRageProShader; + qhandle_t shotgunSmokePuffShader; + qhandle_t plasmaBallShader; + qhandle_t waterBubbleShader; + qhandle_t bloodTrailShader; +#ifdef MISSIONPACK + qhandle_t nailPuffShader; + qhandle_t blueProxMine; +#endif + + qhandle_t numberShaders[11]; + + qhandle_t shadowMarkShader; + + qhandle_t botSkillShaders[5]; + + // wall mark shaders + qhandle_t wakeMarkShader; + qhandle_t bloodMarkShader; + qhandle_t bulletMarkShader; + qhandle_t burnMarkShader; + qhandle_t holeMarkShader; + qhandle_t energyMarkShader; + + // powerup shaders + qhandle_t quadShader; + qhandle_t redQuadShader; + qhandle_t quadWeaponShader; + qhandle_t invisShader; + qhandle_t regenShader; + qhandle_t battleSuitShader; + qhandle_t battleWeaponShader; + qhandle_t hastePuffShader; + qhandle_t redKamikazeShader; + qhandle_t blueKamikazeShader; + + // weapon effect models + qhandle_t bulletFlashModel; + qhandle_t ringFlashModel; + qhandle_t dishFlashModel; + qhandle_t lightningExplosionModel; + + // weapon effect shaders + qhandle_t railExplosionShader; + qhandle_t plasmaExplosionShader; + qhandle_t bulletExplosionShader; + qhandle_t rocketExplosionShader; + qhandle_t grenadeExplosionShader; + qhandle_t bfgExplosionShader; + qhandle_t bloodExplosionShader; + + // special effects models + qhandle_t teleportEffectModel; + qhandle_t teleportEffectShader; +#ifdef MISSIONPACK + qhandle_t kamikazeEffectModel; + qhandle_t kamikazeShockWave; + qhandle_t kamikazeHeadModel; + qhandle_t kamikazeHeadTrail; + qhandle_t guardPowerupModel; + qhandle_t scoutPowerupModel; + qhandle_t doublerPowerupModel; + qhandle_t ammoRegenPowerupModel; + qhandle_t invulnerabilityImpactModel; + qhandle_t invulnerabilityJuicedModel; + qhandle_t medkitUsageModel; + qhandle_t dustPuffShader; + qhandle_t heartShader; +#endif + qhandle_t invulnerabilityPowerupModel; + + // scoreboard headers + qhandle_t scoreboardName; + qhandle_t scoreboardPing; + qhandle_t scoreboardScore; + qhandle_t scoreboardTime; + + // medals shown during gameplay + qhandle_t medalImpressive; + qhandle_t medalExcellent; + qhandle_t medalGauntlet; + qhandle_t medalDefend; + qhandle_t medalAssist; + qhandle_t medalCapture; + + // sounds + sfxHandle_t quadSound; + sfxHandle_t tracerSound; + sfxHandle_t selectSound; + sfxHandle_t useNothingSound; + sfxHandle_t wearOffSound; + sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; + sfxHandle_t sfx_lghit1; + sfxHandle_t sfx_lghit2; + sfxHandle_t sfx_lghit3; + sfxHandle_t sfx_ric1; + sfxHandle_t sfx_ric2; + sfxHandle_t sfx_ric3; + sfxHandle_t sfx_railg; + sfxHandle_t sfx_rockexp; + sfxHandle_t sfx_plasmaexp; +#ifdef MISSIONPACK + sfxHandle_t sfx_proxexp; + sfxHandle_t sfx_nghit; + sfxHandle_t sfx_nghitflesh; + sfxHandle_t sfx_nghitmetal; + sfxHandle_t sfx_chghit; + sfxHandle_t sfx_chghitflesh; + sfxHandle_t sfx_chghitmetal; + sfxHandle_t kamikazeExplodeSound; + sfxHandle_t kamikazeImplodeSound; + sfxHandle_t kamikazeFarSound; + sfxHandle_t useInvulnerabilitySound; + sfxHandle_t invulnerabilityImpactSound1; + sfxHandle_t invulnerabilityImpactSound2; + sfxHandle_t invulnerabilityImpactSound3; + sfxHandle_t invulnerabilityJuicedSound; + sfxHandle_t obeliskHitSound1; + sfxHandle_t obeliskHitSound2; + sfxHandle_t obeliskHitSound3; + sfxHandle_t obeliskRespawnSound; + sfxHandle_t winnerSound; + sfxHandle_t loserSound; + sfxHandle_t youSuckSound; +#endif + sfxHandle_t gibSound; + sfxHandle_t gibBounce1Sound; + sfxHandle_t gibBounce2Sound; + sfxHandle_t gibBounce3Sound; + sfxHandle_t teleInSound; + sfxHandle_t teleOutSound; + sfxHandle_t noAmmoSound; + sfxHandle_t respawnSound; + sfxHandle_t talkSound; + sfxHandle_t landSound; + sfxHandle_t fallSound; + sfxHandle_t jumpPadSound; + + sfxHandle_t oneMinuteSound; + sfxHandle_t fiveMinuteSound; + sfxHandle_t suddenDeathSound; + + sfxHandle_t threeFragSound; + sfxHandle_t twoFragSound; + sfxHandle_t oneFragSound; + + sfxHandle_t hitSound; + sfxHandle_t hitSoundHighArmor; + sfxHandle_t hitSoundLowArmor; + sfxHandle_t hitTeamSound; + sfxHandle_t impressiveSound; + sfxHandle_t excellentSound; + sfxHandle_t deniedSound; + sfxHandle_t humiliationSound; + sfxHandle_t assistSound; + sfxHandle_t defendSound; + sfxHandle_t firstImpressiveSound; + sfxHandle_t firstExcellentSound; + sfxHandle_t firstHumiliationSound; + + sfxHandle_t takenLeadSound; + sfxHandle_t tiedLeadSound; + sfxHandle_t lostLeadSound; + + sfxHandle_t voteNow; + sfxHandle_t votePassed; + sfxHandle_t voteFailed; + + sfxHandle_t watrInSound; + sfxHandle_t watrOutSound; + sfxHandle_t watrUnSound; + + sfxHandle_t flightSound; + sfxHandle_t medkitSound; + + sfxHandle_t weaponHoverSound; + + // teamplay sounds + sfxHandle_t captureAwardSound; + sfxHandle_t redScoredSound; + sfxHandle_t blueScoredSound; + sfxHandle_t redLeadsSound; + sfxHandle_t blueLeadsSound; + sfxHandle_t teamsTiedSound; + + sfxHandle_t captureYourTeamSound; + sfxHandle_t captureOpponentSound; + sfxHandle_t returnYourTeamSound; + sfxHandle_t returnOpponentSound; + sfxHandle_t takenYourTeamSound; + sfxHandle_t takenOpponentSound; + + sfxHandle_t redFlagReturnedSound; + sfxHandle_t blueFlagReturnedSound; + sfxHandle_t neutralFlagReturnedSound; + sfxHandle_t enemyTookYourFlagSound; + sfxHandle_t enemyTookTheFlagSound; + sfxHandle_t yourTeamTookEnemyFlagSound; + sfxHandle_t yourTeamTookTheFlagSound; + sfxHandle_t youHaveFlagSound; + sfxHandle_t yourBaseIsUnderAttackSound; + sfxHandle_t holyShitSound; + + // tournament sounds + sfxHandle_t count3Sound; + sfxHandle_t count2Sound; + sfxHandle_t count1Sound; + sfxHandle_t countFightSound; + sfxHandle_t countPrepareSound; + +#ifdef MISSIONPACK + // new stuff + qhandle_t patrolShader; + qhandle_t assaultShader; + qhandle_t campShader; + qhandle_t followShader; + qhandle_t defendShader; + qhandle_t teamLeaderShader; + qhandle_t retrieveShader; + qhandle_t escortShader; + qhandle_t flagShaders[3]; + sfxHandle_t countPrepareTeamSound; + + sfxHandle_t ammoregenSound; + sfxHandle_t doublerSound; + sfxHandle_t guardSound; + sfxHandle_t scoutSound; +#endif + + //PKMOD - Ergodic 01/28/04 - Dynamic HUD activation: move flagShaders & heartShader to active code + qhandle_t flagShaders[3]; + qhandle_t heartShader; + + //PKMOD - Ergodic 01/30/04 - Dynamic HUD activation: move the following Shaders to active code + qhandle_t patrolShader; + qhandle_t assaultShader; + qhandle_t campShader; + qhandle_t followShader; + qhandle_t defendShader; + qhandle_t retrieveShader; + qhandle_t escortShader; + + qhandle_t cursor; + qhandle_t selectCursor; + qhandle_t sizeCursor; + + sfxHandle_t regenSound; + sfxHandle_t protectSound; + sfxHandle_t n_healthSound; + sfxHandle_t hgrenb1aSound; + sfxHandle_t hgrenb2aSound; + //PKMOD - Ergodic 12/19/00 - remove Team Arena proxmine sounds +// sfxHandle_t wstbimplSound; +// sfxHandle_t wstbimpmSound; +// sfxHandle_t wstbimpdSound; +// sfxHandle_t wstbactvSound; + + //PKMOD - Ergodic 05/21/00 add models and sounds and shaders + sfxHandle_t sfx_pkagravitylaunched; + qhandle_t pkabeartrapgib1; + qhandle_t pkabeartrapgib2; + qhandle_t pkabeartrapgib3; + qhandle_t pkabeartrapgib4; + qhandle_t pkabeartrap; //Ergodic 05/31/00 + qhandle_t pkabeartrapfollow; //Ergodic 06/11/00 + sfxHandle_t sfx_pkabeartrapdrop; + sfxHandle_t sfx_pkabeartrapbreakup; + sfxHandle_t sfx_pkabeartrapsnap; + + //PKMOD - Ergodic 03/23/01 - add team parameters + qhandle_t pkabeartrap_red; + qhandle_t pkabeartrap_blue; + + + //PKMOD - Ergodic 01/16/01 - add model for exploding shells weaponhit + qhandle_t explshellsFlashModel; + + qhandle_t shellsExplosionShader1; //Ergodic 06/18/00 (updated 01/16/01) + qhandle_t shellsExplosionShader2; //Ergodic 01/16/01 + qhandle_t shellsExplosionShader3; //Ergodic 01/16/01 + qhandle_t shellsExplosionShader4; //Ergodic 01/16/01 + qhandle_t shellsExplosionShader5; //Ergodic 01/16/01 + qhandle_t shellsExplosionShader6; //Ergodic 01/16/01 + + qhandle_t pkagravitywelluniverse; //Ergodic 03/18/01 + + //PKMOD - Ergodic 08/01/00 - nailgun models/shaders + qhandle_t nailImpactShader; + qhandle_t nailFlashModel; + qhandle_t nailMarkShader; + qhandle_t nail1; //Ergodic 08/03/00 static nail entity + + //PKMOD - Ergodic 08/08/00 PAINKILLER awarded after every 10 PKitem kills + qhandle_t medalPainKiller; + sfxHandle_t painkillerSound; + + //PKMOD - Ergodic 08/21/00 ChainLightning special shaders + qhandle_t chainlightningShader; + + //PKMOD - Ergodic 08/22/00 ChainLightning Gun player hit shader effect + qhandle_t clgplayerhitShader; + + //PKMOD - Ergodic 08/22/00 ChainLightning strike sounds + sfxHandle_t sfx_chainlightningstrike1; + sfxHandle_t sfx_chainlightningstrike2; + + //PKMOD - Ergodic 08/25/00 nailgun ricochet sounds + sfxHandle_t sfx_nailrico1; + sfxHandle_t sfx_nailrico2; + sfxHandle_t sfx_nailrico3; + sfxHandle_t sfx_nailrico4; + + //PKMOD - Ergodic 09/06/00 gravity well item suck sounds from Mongusta + sfxHandle_t sfx_pkagravitywell_suck1; + sfxHandle_t sfx_pkagravitywell_suck2; + sfxHandle_t sfx_pkagravitywell_suck3; + + //PKMOD - Ergodic 09/29/00 shaders for hub voting images + qhandle_t voting_levelshot_0; + qhandle_t voting_levelshot_1; + qhandle_t voting_levelshot_2; + qhandle_t voting_levelshot_3; + qhandle_t voting_levelshot_4; + qhandle_t voting_levelshot_5; + qhandle_t voting_levelshot_6; + qhandle_t voting_levelshot_7; + qhandle_t voting_levelshot_8; + qhandle_t voting_levelshot_9; + qhandle_t voting_levelshot_10; + qhandle_t voting_levelshot_11; + qhandle_t voting_levelshot_12; + qhandle_t voting_levelshot_13; + qhandle_t voting_levelshot_14; + qhandle_t voting_levelshot_15; + qhandle_t voting_levelshot_16; + qhandle_t voting_levelshot_17; + qhandle_t voting_levelshot_18; + qhandle_t voting_levelshot_19; + qhandle_t voting_levelshot_20; + qhandle_t voting_levelshot_21; + qhandle_t voting_levelshot_22; + qhandle_t voting_levelshot_23; + qhandle_t voting_levelshot_24; + + //PKMOD - Ergodic 10/03/00 dragon beam shader effect + qhandle_t dragonboltShader; + + //PKMOD - Ergodic 11/12/00 airfist flash model shader + qhandle_t airfistFlashShader; + + //PKMOD - Ergodic 11/16/00 update airfist flash model to correspond to airfist_level + qhandle_t airfist4FlashModel; + qhandle_t airfist3FlashModel; + qhandle_t airfist2FlashModel; + qhandle_t airfist1FlashModel; + qhandle_t airfist0FlashModel; + + //PKMOD - Ergodic 11/22/00 add autosentry handles + sfxHandle_t sfx_pkasentrydrop; + //PKMOD - Ergodic 11/27/00 autosentry pickup model will be pkasentry_deploy0 +// qhandle_t pkasentry_pickup; + //PKMOD - Ergodic 11/25/00 add autosentry deploy models + //PKMOD - Ergodic 05/31/02 - use autosentry animation model + /* + qhandle_t pkasentry_deploy0; + qhandle_t pkasentry_deploy1; + qhandle_t pkasentry_deploy2; + qhandle_t pkasentry_deploy3; + qhandle_t pkasentry_deploy4; + qhandle_t pkasentry_deploy5; + qhandle_t pkasentry_deploy6; + qhandle_t pkasentry_deploy7; + qhandle_t pkasentry_deploy8; + qhandle_t pkasentry_deploy9; + qhandle_t pkasentry_deploy10; + */ + //PKMOD - Ergodic 03/20/01 - add team parameters + //PKMOD - Ergodic 05/31/02 - use autosentry animation model + qhandle_t pkasentry_red; + qhandle_t pkasentry_blue; + //PKMOD - Ergodic 06/01/02 - add default autosentry model for dragon deploy + qhandle_t pkasentry; + /* + qhandle_t pkasentry_deploy0_red; + qhandle_t pkasentry_deploy1_red; + qhandle_t pkasentry_deploy2_red; + qhandle_t pkasentry_deploy3_red; + qhandle_t pkasentry_deploy4_red; + qhandle_t pkasentry_deploy5_red; + qhandle_t pkasentry_deploy6_red; + qhandle_t pkasentry_deploy7_red; + qhandle_t pkasentry_deploy8_red; + qhandle_t pkasentry_deploy9_red; + qhandle_t pkasentry_deploy10_red; + qhandle_t pkasentry_deploy0_blue; + qhandle_t pkasentry_deploy1_blue; + qhandle_t pkasentry_deploy2_blue; + qhandle_t pkasentry_deploy3_blue; + qhandle_t pkasentry_deploy4_blue; + qhandle_t pkasentry_deploy5_blue; + qhandle_t pkasentry_deploy6_blue; + qhandle_t pkasentry_deploy7_blue; + qhandle_t pkasentry_deploy8_blue; + qhandle_t pkasentry_deploy9_blue; + qhandle_t pkasentry_deploy10_blue; + */ + + //PKMOD - Ergodic 12/02/00 add split autosentry models + qhandle_t pkasentry_base; + //PKMOD - Ergodic 03/20/01 - add team parameters + qhandle_t pkasentry_base_red; + qhandle_t pkasentry_base_blue; + + //PKMOD - Ergodic 03/20/01 - add team parameters + qhandle_t pkasentry_turret; + qhandle_t pkasentry_turret_red; + qhandle_t pkasentry_turret_blue; + + //PKMOD - Ergodic 12/14/00 - add autosentry gib models + qhandle_t pkasentry_gib1; + qhandle_t pkasentry_gib2; + qhandle_t pkasentry_gib3; + qhandle_t pkasentry_gib4; + qhandle_t pkasentry_gib5; + qhandle_t pkasentry_gib6; + qhandle_t pkasentry_gib7; + qhandle_t pkasentry_gib8; + qhandle_t pkasentry_gib9; + qhandle_t pkasentry_gib10; + qhandle_t pkasentry_gib11; + qhandle_t pkasentry_gib12; + qhandle_t pkasentry_gib13; + qhandle_t pkasentry_gib14; + qhandle_t pkasentry_gib15; + qhandle_t pkasentry_gib16; + qhandle_t pkasentry_gib17; + + //PKMOD - Ergodic 12/26/00 add beans fart sounds + sfxHandle_t sfx_pkafart1; + sfxHandle_t sfx_pkafart2; + sfxHandle_t sfx_pkafart3; + sfxHandle_t sfx_pkafart4; + sfxHandle_t sfx_pkafart5; + //PKMOD - Ergodic 06/30/01 add two more fart sounds from original Q1 PK + sfxHandle_t sfx_pkafart6; + sfxHandle_t sfx_pkafart7; + + //PKMOD - Ergodic 06/30/01 add airfist sounds for all types of situations + sfxHandle_t sfx_pkaairfistfire; + sfxHandle_t sfx_pkaairfistwaterfire; + sfxHandle_t sfx_pkaairfistempty; + sfxHandle_t sfx_pkaairfistwaterempty; + + //PKMOD - Ergodic 12/27/00 add beans shader + qhandle_t pkafartPuffShader; + + //PKMOD - Ergodic 12/28/00 add flash model for autosentry + qhandle_t autosentryFlashModel; + + //PKMOD - Ergodic 01/13/01 - add autosentry fire sounds from mongusta + qhandle_t sfx_pkasentry1; + qhandle_t sfx_pkasentry2; + qhandle_t sfx_pkasentry3; + + //PKMOD - Ergodic 01/21/01 - add coordinate model for exploding shells debug + qhandle_t coordFlashModel; + + //PKMOD - Ergodic 03/26/01 - autosentry sonar ping sounds + sfxHandle_t sfx_pkasentry_ping1; + sfxHandle_t sfx_pkasentry_ping2; + sfxHandle_t sfx_pkasentry_ping3; + + //PKMOD - Ergodic 04/06/01 - add autosentry missile sprite + qhandle_t autosentryBallShader; + + //PKMOD - Ergodic 06/20/01 - add legs model for swinging zombie + qhandle_t pkazombie_legsModel; + qhandle_t pkazombie_legsSkin; + + //PKMOD - Ergodic 06/21/01 - add torso model for swinging zombie + qhandle_t pkazombie_torsoModel; + qhandle_t pkazombie_torsoSkin; + + //PKMOD - Ergodic 06/21/01 - add head model for swinging zombie + qhandle_t pkazombie_headModel; + qhandle_t pkazombie_headSkin; + + //PKMOD - Ergodic 07/03/01 ChainLightning reflect sounds + sfxHandle_t sfx_chainlightningreflect1; + sfxHandle_t sfx_chainlightningreflect2; + + //PKMOD - Ergodic 10/14/01 - add shaders for radiation holdable spark effect + qhandle_t radiate1Shader; + qhandle_t radiate2Shader; + qhandle_t radiate3Shader; + qhandle_t radiate4Shader; + qhandle_t radiate5Shader; + qhandle_t radiate6Shader; + + //PKMOD - Ergodic 11/17/01 - add icons for simple items that are radiated + qhandle_t radiate1SimpleIcon; + qhandle_t radiate2SimpleIcon; + qhandle_t radiate3SimpleIcon; + + //PKMOD - Ergodic 11/27/01 - add radition trail for infected players + qhandle_t radiationTrailShader; + + //PKMOD - Ergodic 12/03/01 - New Holdables - Private Bot pickup skins + qhandle_t privatebot_legsSkin; + qhandle_t privatebot_torsoSkin; + qhandle_t privatebot_headSkin; + + //PKMOD - Ergodic 12/05/01 - Holdable: radiate sounds + qhandle_t pkaradiatewarningSound; + qhandle_t pkaradiateitemSound; + qhandle_t pkaradiateplayerSound; + //PKMOD - Ergodic 08/02/02 - Holdable: radiate activation sound + sfxHandle_t pkaradiateactivationSound; + + //PKMOD - Ergodic 12/07/01 - Holdable: Private Bot HUD Icons + qhandle_t pkapribot_001Icon; + qhandle_t pkapribot_010Icon; + qhandle_t pkapribot_011Icon; + qhandle_t pkapribot_100Icon; + qhandle_t pkapribot_101Icon; + qhandle_t pkapribot_110Icon; + qhandle_t pkapribot_111Icon; + + //PKMOD - Ergodic 12/16/01 - add new model for repositioned deployed gauntlet blade +// qhandle_t pkagauntlet_bladeModel; + + //PKMOD - Ergodic 02/05/02 - add attack sounds for beartrap and autosentry + sfxHandle_t beartrap_attackSound; + sfxHandle_t autosentry_attackSound; + sfxHandle_t radiate_attackSound; + + //PKMOD - Ergodic 02/07/02 - add Private Bot completed sound + sfxHandle_t pkapribot_complete; + + //PKMOD - Ergodic 02/10/02 - send FRAG message to Private Bot's owner + sfxHandle_t pkapribot_frag1; + sfxHandle_t pkapribot_frag2; + + //PKMOD - Ergodic 02/14/02 - explosive shells hit sounds + sfxHandle_t sfx_expgunhit1; + sfxHandle_t sfx_expgunhit2; + sfxHandle_t sfx_expgunhit3; + //PKMOD - Ergodic 07/10/02 - add 2 more explosive shells hit sounds + sfxHandle_t sfx_expgunhit4; + sfxHandle_t sfx_expgunhit5; + + //PKMOD - Ergodic 05/07/02 - add active personal sentry model + qhandle_t persentry_active; + + //PKMOD - Ergodic 06/08/02 - add personal sentry teleport model + qhandle_t persentry_teleportEffectModel; + + //PKMOD - Ergodic 06/12/02 - add personal sentry missile shader + qhandle_t personalsentryBallShader; + + //PKMOD - Ergodic 08/02/02 - Holdable: Personal Sentry hover sound + qhandle_t pkapersentryhoverSound; + //PKMOD - Ergodic 08/26/02 - add Personal Sentry fire sounds from StarDagger + qhandle_t pkapersentry_fire1; + qhandle_t pkapersentry_fire2; + qhandle_t pkapersentry_fire3; + + //PKMOD - Ergodic 09/11/02 - add private bot field effect + qhandle_t privatebot_CueModel; + + //PKMOD - Ergodic 10/14/02 - Add the gravity well expanding wave + qhandle_t pkagravitywellwave; + + //PKMOD - Ergodic 10/18/02 - Add the gravity well spark + qhandle_t pkagravitywellspark; + + //PKMOD - Ergodic 07/18/03 - add invisible Beartrap Shaders + qhandle_t pkainvisbeartrap1; + qhandle_t pkainvisbeartrap2; + qhandle_t pkainvisbeartrap3; + qhandle_t pkainvisbeartrap4; + qhandle_t pkainvisbeartrap5; + qhandle_t pkainvisbeartrap6; + qhandle_t pkainvisbeartrap7; + qhandle_t pkainvisbeartrap8; + qhandle_t pkainvisbeartrap9; + qhandle_t pkainvisbeartrap10; + qhandle_t pkainvisbeartrap11; + qhandle_t pkainvisbeartrap12; + qhandle_t pkainvisbeartrap13; + qhandle_t pkainvisbeartrap14; + qhandle_t pkainvisbeartrap15; + qhandle_t pkainvisbeartrap16; + qhandle_t pkainvisbeartrap17; + qhandle_t pkainvisbeartrap18; + qhandle_t pkainvisbeartrap19; + qhandle_t pkainvisbeartrap20; + + //PKMOD - Ergodic 08/20/03 - add special shader for shooter lightning + qhandle_t shooterlightningShader; + + //PKMOD - Ergodic 09/18/03 - Beartrap Invisibility Spark sprite shaders + qhandle_t pkabeartrapspark1Shader; + qhandle_t pkabeartrapspark2Shader; + qhandle_t pkabeartrapspark3Shader; + qhandle_t pkabeartrapspark4Shader; + qhandle_t pkabeartrapspark5Shader; + + //PKMOD - Ergodic 11/21/03 - Earthquake sound for Gravity Well effect on out of reach players + //PKMOD - Ergodic 12/07/03 - removed, code moved to global sound + //qhandle_t pkaearthquake; + + //PKMOD - Ergodic 12/08/03 - Add chargeup sound for BearTrap, Autosentry invisibility + qhandle_t pkachargeup; + + //PKMOD - Ergodic 12/13/03 - add invisible Autosentry Shaders + qhandle_t pkainvisautosentry1; + qhandle_t pkainvisautosentry2; + qhandle_t pkainvisautosentry3; + qhandle_t pkainvisautosentry4; + qhandle_t pkainvisautosentry5; + qhandle_t pkainvisautosentry6; + qhandle_t pkainvisautosentry7; + qhandle_t pkainvisautosentry8; + qhandle_t pkainvisautosentry9; + qhandle_t pkainvisautosentry10; + qhandle_t pkainvisautosentry11; + qhandle_t pkainvisautosentry12; + qhandle_t pkainvisautosentry13; + qhandle_t pkainvisautosentry14; + qhandle_t pkainvisautosentry15; + qhandle_t pkainvisautosentry16; + qhandle_t pkainvisautosentry17; + qhandle_t pkainvisautosentry18; + qhandle_t pkainvisautosentry19; + qhandle_t pkainvisautosentry20; + + //PKMOD - Ergodic 01/07/04 - add quad farting logic for differing CG graphic sequence + qhandle_t pkaquadbeansShader; + qhandle_t pkaquadbeansModel; + + //PKMOD - Ergodic 03/17/04 - add quad beans fart sounds + sfxHandle_t sfx_pkaquadfart1; + sfxHandle_t sfx_pkaquadfart2; + sfxHandle_t sfx_pkaquadfart3; + +} cgMedia_t; + + +// The client game static (cgs) structure hold everything +// loaded or calculated from the gamestate. It will NOT +// be cleared when a tournement restart is done, allowing +// all clients to begin playing instantly +typedef struct { + gameState_t gameState; // gamestate from server + glconfig_t glconfig; // rendering configuration + float screenXScale; // derived from glconfig + float screenYScale; + float screenXBias; + + int serverCommandSequence; // reliable command stream counter + int processedSnapshotNum;// the number of snapshots cgame has requested + + qboolean localServer; // detected on startup by checking sv_running + + // parsed from serverinfo + gametype_t gametype; + int dmflags; + int teamflags; + int fraglimit; + int capturelimit; + int timelimit; + int maxclients; + char mapname[MAX_QPATH]; + char redTeam[MAX_QPATH]; + char blueTeam[MAX_QPATH]; + + int voteTime; + int voteYes; + int voteNo; + qboolean voteModified; // beep whenever changed + char voteString[MAX_STRING_TOKENS]; + + int teamVoteTime[2]; + int teamVoteYes[2]; + int teamVoteNo[2]; + qboolean teamVoteModified[2]; // beep whenever changed + char teamVoteString[2][MAX_STRING_TOKENS]; + + int levelStartTime; + + int scores1, scores2; // from configstrings + int redflag, blueflag; // flag status from configstrings + int flagStatus; + + qboolean newHud; + + // + // locally derived information from gamestate + // + qhandle_t gameModels[MAX_MODELS]; + sfxHandle_t gameSounds[MAX_SOUNDS]; + + int numInlineModels; + qhandle_t inlineDrawModel[MAX_MODELS]; + vec3_t inlineModelMidpoints[MAX_MODELS]; + + clientInfo_t clientinfo[MAX_CLIENTS]; + + // teamchat width is *3 because of embedded color codes + char teamChatMsgs[TEAMCHAT_HEIGHT][TEAMCHAT_WIDTH*3+1]; + int teamChatMsgTimes[TEAMCHAT_HEIGHT]; + int teamChatPos; + int teamLastChatPos; + + int cursorX; + int cursorY; + qboolean eventHandling; + qboolean mouseCaptured; + qboolean sizingHud; + void *capturedItem; + qhandle_t activeCursor; + + // orders + int currentOrder; + qboolean orderPending; + int orderTime; + int currentVoiceClient; + int acceptOrderTime; + int acceptTask; + int acceptLeader; + char acceptVoice[MAX_NAME_LENGTH]; + + // media + cgMedia_t media; + + //PKMOD - Ergodic 10/13/00 - add hubinfo fields + hubInfoDisplay_t hubInfoDisplay[3]; + +} cgs_t; + + +//PKMOD - Ergodic 05/11/01 - register holdables into their own array +// for optimization purposes +extern int cg_holdable[ HI_NUM_HOLDABLE ]; + +//============================================================================== + +extern cgs_t cgs; +extern cg_t cg; +extern centity_t cg_entities[MAX_GENTITIES]; +extern weaponInfo_t cg_weapons[MAX_WEAPONS]; +extern itemInfo_t cg_items[MAX_ITEMS]; +extern markPoly_t cg_markPolys[MAX_MARK_POLYS]; + +extern vmCvar_t cg_centertime; +extern vmCvar_t cg_runpitch; +extern vmCvar_t cg_runroll; +extern vmCvar_t cg_bobup; +extern vmCvar_t cg_bobpitch; +extern vmCvar_t cg_bobroll; +extern vmCvar_t cg_swingSpeed; +extern vmCvar_t cg_shadows; +extern vmCvar_t cg_gibs; +extern vmCvar_t cg_drawTimer; +extern vmCvar_t cg_drawFPS; +extern vmCvar_t cg_drawSnapshot; +extern vmCvar_t cg_draw3dIcons; +extern vmCvar_t cg_drawIcons; +extern vmCvar_t cg_drawAmmoWarning; +extern vmCvar_t cg_drawCrosshair; +extern vmCvar_t cg_drawCrosshairNames; +extern vmCvar_t cg_drawRewards; +extern vmCvar_t cg_drawTeamOverlay; +extern vmCvar_t cg_teamOverlayUserinfo; +extern vmCvar_t cg_crosshairX; +extern vmCvar_t cg_crosshairY; +extern vmCvar_t cg_crosshairSize; +extern vmCvar_t cg_crosshairHealth; +extern vmCvar_t cg_drawStatus; +extern vmCvar_t cg_draw2D; +extern vmCvar_t cg_animSpeed; +extern vmCvar_t cg_debugAnim; +extern vmCvar_t cg_debugPosition; +extern vmCvar_t cg_debugEvents; +extern vmCvar_t cg_railTrailTime; +extern vmCvar_t cg_errorDecay; +extern vmCvar_t cg_nopredict; +extern vmCvar_t cg_noPlayerAnims; +extern vmCvar_t cg_showmiss; +extern vmCvar_t cg_footsteps; +extern vmCvar_t cg_addMarks; +extern vmCvar_t cg_brassTime; +extern vmCvar_t cg_gun_frame; +extern vmCvar_t cg_gun_x; +extern vmCvar_t cg_gun_y; +extern vmCvar_t cg_gun_z; +extern vmCvar_t cg_drawGun; +extern vmCvar_t cg_viewsize; +extern vmCvar_t cg_tracerChance; +extern vmCvar_t cg_tracerWidth; +extern vmCvar_t cg_tracerLength; +extern vmCvar_t cg_autoswitch; +extern vmCvar_t cg_ignore; +extern vmCvar_t cg_simpleItems; +extern vmCvar_t cg_fov; +extern vmCvar_t cg_zoomFov; +extern vmCvar_t cg_thirdPersonRange; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_stereoSeparation; +extern vmCvar_t cg_lagometer; +extern vmCvar_t cg_drawAttacker; +extern vmCvar_t cg_synchronousClients; +extern vmCvar_t cg_teamChatTime; +extern vmCvar_t cg_teamChatHeight; +extern vmCvar_t cg_stats; +extern vmCvar_t cg_forceModel; +extern vmCvar_t cg_buildScript; +extern vmCvar_t cg_paused; +extern vmCvar_t cg_blood; +extern vmCvar_t cg_predictItems; +extern vmCvar_t cg_deferPlayers; +extern vmCvar_t cg_drawFriend; +extern vmCvar_t cg_teamChatsOnly; +extern vmCvar_t cg_noVoiceChats; +extern vmCvar_t cg_noVoiceText; +extern vmCvar_t cg_scorePlum; +extern vmCvar_t cg_smoothClients; +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; +//extern vmCvar_t cg_pmove_fixed; +extern vmCvar_t cg_cameraOrbit; +extern vmCvar_t cg_cameraOrbitDelay; +extern vmCvar_t cg_timescaleFadeEnd; +extern vmCvar_t cg_timescaleFadeSpeed; +extern vmCvar_t cg_timescale; +extern vmCvar_t cg_cameraMode; +extern vmCvar_t cg_smallFont; +extern vmCvar_t cg_bigFont; +extern vmCvar_t cg_noTaunt; +extern vmCvar_t cg_noProjectileTrail; +extern vmCvar_t cg_oldRail; +extern vmCvar_t cg_oldRocket; +extern vmCvar_t cg_oldPlasma; +extern vmCvar_t cg_trueLightning; +//PKMOD - Ergodic 08/16/03 - add cvar for PKA full weapon cycling +extern vmCvar_t cg_pkafullweaponcycling; + +#ifdef MISSIONPACK +extern vmCvar_t cg_redTeamName; +extern vmCvar_t cg_blueTeamName; +extern vmCvar_t cg_currentSelectedPlayer; +extern vmCvar_t cg_currentSelectedPlayerName; +extern vmCvar_t cg_singlePlayer; +extern vmCvar_t cg_enableDust; +extern vmCvar_t cg_enableBreath; +extern vmCvar_t cg_singlePlayerActive; +extern vmCvar_t cg_recordSPDemo; +extern vmCvar_t cg_recordSPDemoName; +extern vmCvar_t cg_obeliskRespawnDelay; +#endif + +//PKMOD - Ergodic 02/02/04 - Enable this code so that POSTGAME will show proper completion time +extern vmCvar_t cg_singlePlayerActive; +extern vmCvar_t cg_recordSPDemo; +extern vmCvar_t cg_recordSPDemoName; + +//PKMOD - Ergodic 01/17/04 - Enable HUD in PKA3.0 +extern vmCvar_t cg_redTeamName; +extern vmCvar_t cg_blueTeamName; +extern vmCvar_t cg_currentSelectedPlayer; +extern vmCvar_t cg_currentSelectedPlayerName; + + +// +// cg_main.c +// +const char *CG_ConfigString( int index ); +const char *CG_Argv( int arg ); + +void QDECL CG_Printf( const char *msg, ... ); +void QDECL CG_Error( const char *msg, ... ); + +void CG_StartMusic( void ); + +//PKMOD - Ergodic 10/14/00 - add post vote music +void CG_StartPostVoteMusic( const char *postvotemusic ); + +void CG_UpdateCvars( void ); + +int CG_CrosshairPlayer( void ); +int CG_LastAttacker( void ); +void CG_LoadMenus(const char *menuFile); +void CG_KeyEvent(int key, qboolean down); +void CG_MouseEvent(int x, int y); +void CG_EventHandling(int type); +void CG_RankRunFrame( void ); +void CG_SetScoreSelection(void *menu); +score_t *CG_GetSelectedScore(); +void CG_BuildSpectatorString(); + + +// +// cg_view.c +// +void CG_TestModel_f (void); +void CG_TestGun_f (void); +void CG_TestModelNextFrame_f (void); +void CG_TestModelPrevFrame_f (void); +void CG_TestModelNextSkin_f (void); +void CG_TestModelPrevSkin_f (void); +void CG_ZoomDown_f( void ); +void CG_ZoomUp_f( void ); +void CG_AddBufferedSound( sfxHandle_t sfx); + +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + + +// +// cg_drawtools.c +// +void CG_AdjustFrom640( float *x, float *y, float *w, float *h ); +void CG_FillRect( float x, float y, float width, float height, const float *color ); +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void CG_DrawString( float x, float y, const char *string, + float charWidth, float charHeight, const float *modulate ); + + +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ); +void CG_DrawBigString( int x, int y, const char *s, float alpha ); +void CG_DrawBigStringColor( int x, int y, const char *s, vec4_t color ); +void CG_DrawSmallString( int x, int y, const char *s, float alpha ); +void CG_DrawSmallStringColor( int x, int y, const char *s, vec4_t color ); + +int CG_DrawStrlen( const char *str ); + +float *CG_FadeColor( int startMsec, int totalMsec ); +float *CG_TeamColor( int team ); +void CG_TileClear( void ); +void CG_ColorForHealth( vec4_t hcolor ); +void CG_GetColorForHealth( int health, int armor, vec4_t hcolor ); + +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ); +void CG_DrawRect( float x, float y, float width, float height, float size, const float *color ); +void CG_DrawSides(float x, float y, float w, float h, float size); +void CG_DrawTopBottom(float x, float y, float w, float h, float size); + + +// +// cg_draw.c, cg_newDraw.c +// +extern int sortedTeamPlayers[TEAM_MAXOVERLAY]; +extern int numSortedTeamPlayers; +extern int drawTeamOverlayModificationCount; +extern char systemChat[256]; +extern char teamChat1[256]; +extern char teamChat2[256]; + +//PKMOD - Ergodic 10/13/00 - add hubinfo fields +extern int numHubInfoLines; + +void CG_AddLagometerFrameInfo( void ); +void CG_AddLagometerSnapshotInfo( snapshot_t *snap ); +void CG_CenterPrint( const char *str, int y, int charWidth ); +void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ); +void CG_DrawActive( stereoFrame_t stereoView ); +void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ); +void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ); +void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle); +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style); +int CG_Text_Width(const char *text, float scale, int limit); +int CG_Text_Height(const char *text, float scale, int limit); +void CG_SelectPrevPlayer(); +void CG_SelectNextPlayer(); +float CG_GetValue(int ownerDraw); +qboolean CG_OwnerDrawVisible(int flags); +void CG_RunMenuScript(char **args); +void CG_ShowResponseHead(); +void CG_SetPrintString(int type, const char *p); +void CG_InitTeamChat(); +void CG_GetTeamColor(vec4_t *color); +const char *CG_GetGameStatusText(); +const char *CG_GetKillerText(); +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ); +void CG_Text_PaintChar(float x, float y, float width, float height, float scale, float s, float t, float s2, float t2, qhandle_t hShader); +void CG_CheckOrderPending(); +const char *CG_GameTypeString(); +qboolean CG_YourTeamHasFlag(); +qboolean CG_OtherTeamHasFlag(); +qhandle_t CG_StatusHandle(int task); + + + +// +// cg_player.c +// +void CG_Player( centity_t *cent ); +void CG_ResetPlayerEntity( centity_t *cent ); +void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ); +void CG_NewClientInfo( int clientNum ); +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); + +// +// cg_predict.c +// +void CG_BuildSolidList( void ); +int CG_PointContents( const vec3_t point, int passEntityNum ); +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ); +void CG_PredictPlayerState( void ); +void CG_LoadDeferredPlayers( void ); + + +// +// cg_events.c +// +void CG_CheckEvents( centity_t *cent ); +const char *CG_PlaceString( int rank ); +void CG_EntityEvent( centity_t *cent, vec3_t position ); +void CG_PainEvent( centity_t *cent, int health ); + + +// +// cg_ents.c +// +void CG_SetEntitySoundPosition( centity_t *cent ); +void CG_AddPacketEntities( void ); +void CG_Beam( centity_t *cent ); +void CG_AdjustPositionForMover( const vec3_t in, int moverNum, int fromTime, int toTime, vec3_t out ); + +void CG_PositionEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); +void CG_PositionRotatedEntityOnTag( refEntity_t *entity, const refEntity_t *parent, + qhandle_t parentModel, char *tagName ); +//PKMOD - Ergodic 12/06/00 - added for special lightning shooter event +void CG_ChainLightning( centity_t *cent ); +//PKMOD - Ergodic 01/21/01 - add event to display coordinate model for exploding shells debug +void CG_Coord( entityState_t *es ); +//PKMOD - Ergodic - debug code +char *CG_vtos( const vec3_t v ); + +// +// cg_weapons.c +// +void CG_NextWeapon_f( void ); +void CG_PrevWeapon_f( void ); +void CG_Weapon_f( void ); + +void CG_RegisterWeapon( int weaponNum ); +void CG_RegisterItemVisuals( int itemNum ); + +void CG_FireWeapon( centity_t *cent ); +void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ); +void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ); +void CG_ShotgunFire( entityState_t *es ); +//void CG_Bullet( vec3_t origin, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum ); +//PKMOD - Ergodic 12/14/00 - add autosentry code for greater tracability +void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum, qboolean autosentry ); + +void CG_RailTrail( clientInfo_t *ci, vec3_t start, vec3_t end ); +void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ); +void CG_AddViewWeapon (playerState_t *ps); +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ); +void CG_DrawWeaponSelect( void ); + +void CG_OutOfAmmoChange( void ); // should this be in pmove? + +//PKMOD - Ergodic 07/02/00 PKA weapon commands +void CG_Weapon_GravityWell( void ); +void CG_Weapon_Sentry( void ); +void CG_Weapon_BearTrap( void ); +void CG_Weapon_Beans( void ); + +//PKMOD - Ergodic 07/12/00 PKA weapon commands +void CG_Weapon_Gauntlet( void ); +void CG_Weapon_MachineGun( void ); +void CG_Weapon_ShotGun( void ); +void CG_Weapon_AirFist( void ); +void CG_Weapon_NailGun( void ); +void CG_Weapon_GrenadeLauncher( void ); +void CG_Weapon_RocketLauncher( void ); +void CG_Weapon_LightningGun( void ); +void CG_Weapon_RailGun( void ); +void CG_Weapon_Harpoon( void ); + +//PKMOD - Ergodic 03/01/01 dragon deploy pka weapon +void CG_Weapon_DragonDeploy( void ); + +//PKMOD - Ergodic 04/04/01 - add last weapon command +void CG_LastWeapon_f( void ); + +// +// cg_marks.c +// +void CG_InitMarkPolys( void ); +void CG_AddMarks( void ); +void CG_ImpactMark( qhandle_t markShader, + const vec3_t origin, const vec3_t dir, + float orientation, + float r, float g, float b, float a, + qboolean alphaFade, + float radius, qboolean temporary ); + +// +// cg_localents.c +// +void CG_InitLocalEntities( void ); +localEntity_t *CG_AllocLocalEntity( void ); +void CG_AddLocalEntities( void ); + +// +// cg_effects.c +// +localEntity_t *CG_SmokePuff( const vec3_t p, + const vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ); +void CG_BubbleTrail( vec3_t start, vec3_t end, float spacing ); +void CG_SpawnEffect( vec3_t org ); +#ifdef MISSIONPACK +void CG_KamikazeEffect( vec3_t org ); +void CG_ObeliskExplode( vec3_t org, int entityNum ); +void CG_ObeliskPain( vec3_t org ); +void CG_InvulnerabilityImpact( vec3_t org, vec3_t angles ); +void CG_InvulnerabilityJuiced( vec3_t org ); +void CG_LightningBoltBeam( vec3_t start, vec3_t end ); +#endif +void CG_ScorePlum( int client, vec3_t org, int score ); + +void CG_GibPlayer( vec3_t playerOrigin ); +void CG_BigExplode( vec3_t playerOrigin ); + +void CG_Bleed( vec3_t origin, int entityNum ); + +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, qhandle_t shader, int msec, + qboolean isSprite ); + +//PKMOD - Ergodic 05/23/00 - 05/26/00 event/effect routines +void CG_BearTrapDie( vec3_t BearTrapOrigin ); +//PKMOD - Ergodic 06/30/00 - modify beartrap code to be called by cg_player +void CG_BearTraps_Follow( centity_t *cent ); +//PKMOD - Ergodic 07/01/00 - chain lightning gun stubs +void CG_ChainShaft( entityState_t *es ); +//PKMOD - Ergodic 07/19/00 - display lightning flash +void CG_Lightning_FX( vec3_t position, int constantlight ); +//PKMOD - Ergodic 11/22/00 - autosentry death routine +void CG_AutoSentryDie( vec3_t AutoSentryOrigin ); + +//PKMOD - Ergodic 01/15/01 - add Lightning Discharge in water +void CG_Lightning_Water_Discharge (vec3_t origin, int msec); + +//PKMOD - Ergodic 02/20/01 - Add expanding gravity well +//void CG_GravityWellActivate( vec3_t org ); +//PKMOD - Ergodic 11/02/02 - Pass in the whole centity +//PKMOD - Ergodic 11/02/02 - remove call +//CG_GravityWellActivate( centity_t *cent ); + +//PKMOD - Ergodic 04/13/01 - Renders fecal plumes or bubbles from eating beans +void CG_BeansToot( vec3_t origin ); + +//PKMOD - Ergodic 10/14/01 - add radiation sparking effect +void CG_Radiation( vec3_t start ); + +//PKMOD - Ergodic 11/30/01 - add radiation trail to infected player +//void CG_RadiationTrail0( const vec3_t p ); + +//PKMOD - Ergodic 12/01/01 - add new radiation trail function to infected player +void CG_RadiationTrail( const vec3_t p, + vec3_t vel, + float radius, + float r, float g, float b, float a, + float duration, + int startTime, + int fadeInTime, + int leFlags, + qhandle_t hShader ); + +//PKMOD - Ergodic 06/08/02 - add personal sentry teleport model +void CG_PersentrySpawnEffect( vec3_t org ); + +//PKMOD - Ergodic 01/05/04 - add quad farting logic for differing CG graphic sequence +void CG_QuadBeansToot( vec3_t origin ); + + +// +// cg_snapshot.c +// +void CG_ProcessSnapshots( void ); + +// +// cg_info.c +// +void CG_LoadingString( const char *s ); +void CG_LoadingItem( int itemNum ); +void CG_LoadingClient( int clientNum ); +void CG_DrawInformation( void ); + +// +// cg_scoreboard.c +// +qboolean CG_DrawOldScoreboard( void ); +void CG_DrawOldTourneyScoreboard( void ); + +// +// cg_consolecmds.c +// +qboolean CG_ConsoleCommand( void ); +void CG_InitConsoleCommands( void ); + +// +// cg_servercmds.c +// +void CG_ExecuteNewServerCommands( int latestSequence ); +void CG_ParseServerinfo( void ); +void CG_SetConfigValues( void ); +void CG_LoadVoiceChats( void ); +void CG_ShaderStateChanged(void); +void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ); +void CG_PlayBufferedVoiceChats( void ); + +// +// cg_playerstate.c +// +void CG_Respawn( void ); +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); +void CG_CheckChangedPredictableEvents( playerState_t *ps ); + +// +//PKMOD - Ergodic 05/22/00 Add CG functions +// +void CG_PKA_OutOfAmmoChange( void ); + + + +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// + +// print message on the local console +void trap_Print( const char *fmt ); + +// abort the game +void trap_Error( const char *fmt ); + +// milliseconds should only be used for performance tuning, never +// for anything game related. Get time from the CG_DrawActiveFrame parameter +int trap_Milliseconds( void ); + +// console variable interaction +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ); +void trap_Cvar_Update( vmCvar_t *vmCvar ); +void trap_Cvar_Set( const char *var_name, const char *value ); +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +// ServerCommand and ConsoleCommand parameter access +int trap_Argc( void ); +void trap_Argv( int n, char *buffer, int bufferLength ); +void trap_Args( char *buffer, int bufferLength ); + +// filesystem access +// returns length of file +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +void trap_FS_FCloseFile( fileHandle_t f ); +int trap_FS_Seek( fileHandle_t f, long offset, int origin ); // fsOrigin_t + +// add commands to the local console as if they were typed in +// for map changing, etc. The command is not executed immediately, +// but will be executed in order the next time console commands +// are processed +void trap_SendConsoleCommand( const char *text ); + +// register a command name so the console can perform command completion. +// FIXME: replace this with a normal console command "defineCommand"? +void trap_AddCommand( const char *cmdName ); + +// send a string to the server over the network +void trap_SendClientCommand( const char *s ); + +// force a screen update, only used during gamestate load +void trap_UpdateScreen( void ); + +// model collision +void trap_CM_LoadMap( const char *mapname ); +int trap_CM_NumInlineModels( void ); +clipHandle_t trap_CM_InlineModel( int index ); // 0 = world, 1+ = bmodels +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ); +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ); +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ); +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ); + +// Returns the projection of a polygon onto the solid brushes in the world +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ); + +// normal sounds will have their volume dynamically changed as their entity +// moves and the listener moves +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +void trap_S_StopLoopingSound(int entnum); + +// a local sound is always played full volume +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ); +void trap_S_ClearLoopingSounds( qboolean killall ); +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +// respatialize recalculates the volumes of sound as they should be heard by the +// given entityNum and position +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); // returns buzz if not found +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ); // empty name stops music +void trap_S_StopBackgroundTrack( void ); + + +void trap_R_LoadWorldMap( const char *mapname ); + +// all media should be registered during level startup to prevent +// hitches during gameplay +qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found + +// a scene is built up by calls to R_ClearScene and the various R_Add functions. +// Nothing is drawn until R_RenderScene is called. +void trap_R_ClearScene( void ); +void trap_R_AddRefEntityToScene( const refEntity_t *re ); + +// polys are intended for simple wall marks, not really for doing +// significant construction +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ); +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int numPolys ); +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ); +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ); +void trap_R_RenderScene( const refdef_t *fd ); +void trap_R_SetColor( const float *rgba ); // NULL = 1,1,1,1 +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ); +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ); +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ); +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +// The glconfig_t will not change during the life of a cgame. +// If it needs to change, the entire cgame will be restarted, because +// all the qhandle_t are then invalid. +void trap_GetGlconfig( glconfig_t *glconfig ); + +// the gamestate should be grabbed at startup, and whenever a +// configstring changes +void trap_GetGameState( gameState_t *gamestate ); + +// cgame will poll each frame to see if a newer snapshot has arrived +// that it is interested in. The time is returned seperately so that +// snapshot latency can be calculated. +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ); + +// a snapshot get can fail if the snapshot (or the entties it holds) is so +// old that it has fallen out of the client system queue +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ); + +// retrieve a text command from the server stream +// the current snapshot will hold the number of the most recent command +// qfalse can be returned if the client system handled the command +// argc() / argv() can be used to examine the parameters of the command +qboolean trap_GetServerCommand( int serverCommandNumber ); + +// returns the most recent command number that can be passed to GetUserCmd +// this will always be at least one higher than the number in the current +// snapshot, and it may be quite a few higher if it is a fast computer on +// a lagged connection +int trap_GetCurrentCmdNumber( void ); + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ); + +// used for the weapon select and zoom +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ); + +// aids for VM testing +void testPrintInt( char *string, int i ); +void testPrintFloat( char *string, float f ); + +int trap_MemoryRemaining( void ); +void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font); +qboolean trap_Key_IsDown( int keynum ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +int trap_Key_GetKey( const char *binding ); + + +typedef enum { + SYSTEM_PRINT, + CHAT_PRINT, + TEAMCHAT_PRINT +} q3print_t; // bk001201 - warning: useless keyword or type name in empty declaration + +//PKMOD - Ergodic 10/10/2000 - voting images' shader assignments +extern char cg_voting_shader_flag[]; + +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status trap_CIN_StopCinematic(int handle); +e_status trap_CIN_RunCinematic (int handle); +void trap_CIN_DrawCinematic (int handle); +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h); + +void trap_SnapVector( float *v ); + +qboolean trap_loadCamera(const char *name); +void trap_startCamera(int time); +qboolean trap_getCameraInfo(int time, vec3_t *origin, vec3_t *angles); + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ); + +void CG_ClearParticles (void); +void CG_AddParticles (void); +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum); +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent); +void CG_AddParticleShrapnel (localEntity_t *le); +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent); +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration); +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir); +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha); +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd); +extern qboolean initparticles; +int CG_NewParticleArea ( int num ); +//PKMOD - Ergodic 10/18/02 - draw super-sized particles... +void CG_ParticleSparks2 (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); +//PKMOD - Ergodic 07/20/03 - particles for beartrap +void CG_ParticleSparks3 (vec3_t org, vec3_t vel, int duration, float x, float y, float speed); + + diff --git a/quake3/source/code/cgame/cg_localents.c b/quake3/source/code/cgame/cg_localents.c new file mode 100644 index 0000000..1d60f3b --- /dev/null +++ b/quake3/source/code/cgame/cg_localents.c @@ -0,0 +1,932 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +// cg_localents.c -- every frame, generate renderer commands for locally +// processed entities, like smoke puffs, gibs, shells, etc. + +#include "cg_local.h" + +#define MAX_LOCAL_ENTITIES 512 +localEntity_t cg_localEntities[MAX_LOCAL_ENTITIES]; +localEntity_t cg_activeLocalEntities; // double linked list +localEntity_t *cg_freeLocalEntities; // single linked list + +/* +=================== +CG_InitLocalEntities + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitLocalEntities( void ) { + int i; + + memset( cg_localEntities, 0, sizeof( cg_localEntities ) ); + cg_activeLocalEntities.next = &cg_activeLocalEntities; + cg_activeLocalEntities.prev = &cg_activeLocalEntities; + cg_freeLocalEntities = cg_localEntities; + for ( i = 0 ; i < MAX_LOCAL_ENTITIES - 1 ; i++ ) { + cg_localEntities[i].next = &cg_localEntities[i+1]; + } +} + + +/* +================== +CG_FreeLocalEntity +================== +*/ +void CG_FreeLocalEntity( localEntity_t *le ) { + if ( !le->prev ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prev->next = le->next; + le->next->prev = le->prev; + + // the free list is only singly linked + le->next = cg_freeLocalEntities; + cg_freeLocalEntities = le; +} + +/* +=================== +CG_AllocLocalEntity + +Will allways succeed, even if it requires freeing an old active entity +=================== +*/ +localEntity_t *CG_AllocLocalEntity( void ) { + localEntity_t *le; + + if ( !cg_freeLocalEntities ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + CG_FreeLocalEntity( cg_activeLocalEntities.prev ); + } + + le = cg_freeLocalEntities; + cg_freeLocalEntities = cg_freeLocalEntities->next; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->next = cg_activeLocalEntities.next; + le->prev = &cg_activeLocalEntities; + cg_activeLocalEntities.next->prev = le; + cg_activeLocalEntities.next = le; + return le; +} + + +/* +==================================================================================== + +FRAGMENT PROCESSING + +A fragment localentity interacts with the environment in some way (hitting walls), +or generates more localentities along a trail. + +==================================================================================== +*/ + +/* +================ +CG_BloodTrail + +Leave expanding blood puffs behind gibs +================ +*/ +void CG_BloodTrail( localEntity_t *le ) { + int t; + int t2; + int step; + vec3_t newOrigin; + localEntity_t *blood; + + step = 150; + t = step * ( (cg.time - cg.frametime + step ) / step ); + t2 = step * ( cg.time / step ); + + for ( ; t <= t2; t += step ) { + BG_EvaluateTrajectory( &le->pos, t, newOrigin ); + + blood = CG_SmokePuff( newOrigin, vec3_origin, + 20, // radius + 1, 1, 1, 1, // color + 2000, // trailTime + t, // startTime + 0, // fadeInTime + 0, // flags + cgs.media.bloodTrailShader ); + // use the optimized version + blood->leType = LE_FALL_SCALE_FADE; + // drop a total of 40 units over its lifetime + blood->pos.trDelta[2] = 40; + } +} + + +/* +================ +CG_FragmentBounceMark +================ +*/ +void CG_FragmentBounceMark( localEntity_t *le, trace_t *trace ) { + int radius; + + if ( le->leMarkType == LEMT_BLOOD ) { + + radius = 16 + (rand()&31); + CG_ImpactMark( cgs.media.bloodMarkShader, trace->endpos, trace->plane.normal, random()*360, + 1,1,1,1, qtrue, radius, qfalse ); + } else if ( le->leMarkType == LEMT_BURN ) { + + radius = 8 + (rand()&15); + CG_ImpactMark( cgs.media.burnMarkShader, trace->endpos, trace->plane.normal, random()*360, + 1,1,1,1, qtrue, radius, qfalse ); + } + + + // don't allow a fragment to make multiple marks, or they + // pile up while settling + le->leMarkType = LEMT_NONE; +} + +/* +================ +CG_FragmentBounceSound +================ +*/ +void CG_FragmentBounceSound( localEntity_t *le, trace_t *trace ) { + if ( le->leBounceSoundType == LEBS_BLOOD ) { + // half the gibs will make splat sounds + if ( rand() & 1 ) { + int r = rand()&3; + sfxHandle_t s; + + if ( r == 0 ) { + s = cgs.media.gibBounce1Sound; + } else if ( r == 1 ) { + s = cgs.media.gibBounce2Sound; + } else { + s = cgs.media.gibBounce3Sound; + } + trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); + } + } else if ( le->leBounceSoundType == LEBS_BRASS ) { + + } + + // don't allow a fragment to make multiple bounce sounds, + // or it gets too noisy as they settle + le->leBounceSoundType = LEBS_NONE; +} + + +/* +================ +CG_ReflectVelocity +================ +*/ +void CG_ReflectVelocity( localEntity_t *le, trace_t *trace ) { + vec3_t velocity; + float dot; + int hitTime; + + // reflect the velocity on the trace plane + hitTime = cg.time - cg.frametime + cg.frametime * trace->fraction; + BG_EvaluateTrajectoryDelta( &le->pos, hitTime, velocity ); + dot = DotProduct( velocity, trace->plane.normal ); + VectorMA( velocity, -2*dot, trace->plane.normal, le->pos.trDelta ); + + VectorScale( le->pos.trDelta, le->bounceFactor, le->pos.trDelta ); + + VectorCopy( trace->endpos, le->pos.trBase ); + le->pos.trTime = cg.time; + + + // check for stop, making sure that even on low FPS systems it doesn't bobble + if ( trace->allsolid || + ( trace->plane.normal[2] > 0 && + ( le->pos.trDelta[2] < 40 || le->pos.trDelta[2] < -cg.frametime * le->pos.trDelta[2] ) ) ) { + le->pos.trType = TR_STATIONARY; + } else { + + } +} + +/* +================ +CG_AddFragment +================ +*/ +void CG_AddFragment( localEntity_t *le ) { + vec3_t newOrigin; + trace_t trace; + + if ( le->pos.trType == TR_STATIONARY ) { + // sink into the ground if near the removal time + int t; + float oldZ; + + t = le->endTime - cg.time; + if ( t < SINK_TIME ) { + // we must use an explicit lighting origin, otherwise the + // lighting would be lost as soon as the origin went + // into the ground + VectorCopy( le->refEntity.origin, le->refEntity.lightingOrigin ); + le->refEntity.renderfx |= RF_LIGHTING_ORIGIN; + oldZ = le->refEntity.origin[2]; + le->refEntity.origin[2] -= 16 * ( 1.0 - (float)t / SINK_TIME ); + trap_R_AddRefEntityToScene( &le->refEntity ); + le->refEntity.origin[2] = oldZ; + } else { + trap_R_AddRefEntityToScene( &le->refEntity ); + } + + return; + } + + // calculate new position + BG_EvaluateTrajectory( &le->pos, cg.time, newOrigin ); + + // trace a line from previous position to new position + CG_Trace( &trace, le->refEntity.origin, NULL, NULL, newOrigin, -1, CONTENTS_SOLID ); + if ( trace.fraction == 1.0 ) { + // still in free fall + VectorCopy( newOrigin, le->refEntity.origin ); + + if ( le->leFlags & LEF_TUMBLE ) { + vec3_t angles; + + BG_EvaluateTrajectory( &le->angles, cg.time, angles ); + AnglesToAxis( angles, le->refEntity.axis ); + } + + trap_R_AddRefEntityToScene( &le->refEntity ); + + // add a blood trail + if ( le->leBounceSoundType == LEBS_BLOOD ) { + CG_BloodTrail( le ); + } + + return; + } + + // if it is in a nodrop zone, remove it + // this keeps gibs from waiting at the bottom of pits of death + // and floating levels + if ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) { + CG_FreeLocalEntity( le ); + return; + } + + // leave a mark + CG_FragmentBounceMark( le, &trace ); + + // do a bouncy sound + CG_FragmentBounceSound( le, &trace ); + + // reflect the velocity on the trace plane + CG_ReflectVelocity( le, &trace ); + + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +/* +===================================================================== + +TRIVIAL LOCAL ENTITIES + +These only do simple scaling or modulation before passing to the renderer +===================================================================== +*/ + +/* +==================== +CG_AddFadeRGB +==================== +*/ +void CG_AddFadeRGB( localEntity_t *le ) { + refEntity_t *re; + float c; + + re = &le->refEntity; + + c = ( le->endTime - cg.time ) * le->lifeRate; + c *= 0xff; + + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + re->shaderRGBA[3] = le->color[3] * c; + + trap_R_AddRefEntityToScene( re ); +} + +/* +================== +CG_AddMoveScaleFade +================== +*/ +static void CG_AddMoveScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + if ( le->fadeInTime > le->startTime && cg.time < le->fadeInTime ) { + // fade / grow time + c = 1.0 - (float) ( le->fadeInTime - cg.time ) / ( le->fadeInTime - le->startTime ); + } + else { + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + } + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + if ( !( le->leFlags & LEF_PUFF_DONT_SCALE ) ) { + re->radius = le->radius * ( 1.0 - c ) + 8; + } + + BG_EvaluateTrajectory( &le->pos, cg.time, re->origin ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +=================== +CG_AddScaleFade + +For rocket smokes that hang in place, fade out, and are +removed if the view passes through them. +There are often many of these, so it needs to be simple. +=================== +*/ +static void CG_AddScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + re->radius = le->radius * ( 1.0 - c ) + 8; + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + +/* +================= +CG_AddFallScaleFade + +This is just an optimized CG_AddMoveScaleFade +For blood mists that drift down, fade out, and are +removed if the view passes through them. +There are often 100+ of these, so it needs to be simple. +================= +*/ +static void CG_AddFallScaleFade( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade time + c = ( le->endTime - cg.time ) * le->lifeRate; + + re->shaderRGBA[3] = 0xff * c * le->color[3]; + + re->origin[2] = le->pos.trBase[2] - ( 1.0 - c ) * le->pos.trDelta[2]; + + re->radius = le->radius * ( 1.0 - c ) + 16; + + //PKMOD - Ergodic 11/30/01 - debug radius (inactive) +// if ( (rand() % 101) > 95 ) +// Com_Printf("CG_AddFallScaleFade - re->radius>%f<, le->radius>%f<\n", re->radius, le->radius ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( re->origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < le->radius ) { + CG_FreeLocalEntity( le ); + return; + } + + trap_R_AddRefEntityToScene( re ); +} + + + +/* +================ +CG_AddExplosion +================ +*/ +static void CG_AddExplosion( localEntity_t *ex ) { + refEntity_t *ent; + + ent = &ex->refEntity; + + // add the entity + trap_R_AddRefEntityToScene(ent); + + // add the dlight + if ( ex->light ) { + float light; + + light = (float)( cg.time - ex->startTime ) / ( ex->endTime - ex->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = ex->light * light; + trap_R_AddLightToScene(ent->origin, light, ex->lightColor[0], ex->lightColor[1], ex->lightColor[2] ); + } +} + +/* +================ +CG_AddSpriteExplosion +================ +*/ +static void CG_AddSpriteExplosion( localEntity_t *le ) { + refEntity_t re; + float c; + + re = le->refEntity; + + c = ( le->endTime - cg.time ) / ( float ) ( le->endTime - le->startTime ); + if ( c > 1 ) { + c = 1.0; // can happen during connection problems + } + + re.shaderRGBA[0] = 0xff; + re.shaderRGBA[1] = 0xff; + re.shaderRGBA[2] = 0xff; + re.shaderRGBA[3] = 0xff * c * 0.33; + + re.reType = RT_SPRITE; + re.radius = 42 * ( 1.0 - c ) + 30; + + trap_R_AddRefEntityToScene( &re ); + + // add the dlight + if ( le->light ) { + float light; + + light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = le->light * light; + trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] ); + } +} + + +/* +================ +PKMOD - Ergodic 01/12/04 - for quad beans blast +CG_AddScaledSpriteExplosion + + scale explosion from 1 to 2 times the size +================ +*/ +static void CG_AddScaledSpriteExplosion( localEntity_t *le ) { + refEntity_t re; + float c; + +// vec3_t test, axis[3]; + + re = le->refEntity; + + c = (float) (1 + 3*( cg.time - le->startTime ) / 1100); + + if ( c > 4.0 ) { + c = 4.0; // can happen during connection problems + } + +// VectorClear( test ); +// AnglesToAxis( test, axis ); + + +// VectorScale( axis[0], c, re.axis[0] ); +// VectorScale( axis[1], c, re.axis[1] ); +// VectorScale( axis[2], c, re.axis[2] ); +// re.nonNormalizedAxes = qtrue; + + + re.shaderRGBA[0] = 0xff; + re.shaderRGBA[1] = 0xff; + re.shaderRGBA[2] = 0xff; + re.shaderRGBA[3] = 0xff * c * 0.33; + + re.reType = RT_SPRITE; + re.radius = 40 * c; + + trap_R_AddRefEntityToScene( &re ); + + // add the dlight + if ( le->light ) { + float light; + + light = (float)( cg.time - le->startTime ) / ( le->endTime - le->startTime ); + if ( light < 0.5 ) { + light = 1.0; + } else { + light = 1.0 - ( light - 0.5 ) * 2; + } + light = le->light * light; + trap_R_AddLightToScene(re.origin, light, le->lightColor[0], le->lightColor[1], le->lightColor[2] ); + } +} + +#ifdef MISSIONPACK +/* +==================== +CG_AddKamikaze +==================== +*/ +void CG_AddKamikaze( localEntity_t *le ) { + refEntity_t *re; + refEntity_t shockwave; + float c; + vec3_t test, axis[3]; + int t; + + re = &le->refEntity; + + t = cg.time - le->startTime; + VectorClear( test ); + AnglesToAxis( test, axis ); + + if (t > KAMI_SHOCKWAVE_STARTTIME && t < KAMI_SHOCKWAVE_ENDTIME) { + + if (!(le->leFlags & LEF_SOUND1)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeExplodeSound ); + trap_S_StartLocalSound(cgs.media.kamikazeExplodeSound, CHAN_AUTO); + le->leFlags |= LEF_SOUND1; + } + // 1st kamikaze shockwave + memset(&shockwave, 0, sizeof(shockwave)); + shockwave.hModel = cgs.media.kamikazeShockWave; + shockwave.reType = RT_MODEL; + shockwave.shaderTime = re->shaderTime; + VectorCopy(re->origin, shockwave.origin); + + c = (float)(t - KAMI_SHOCKWAVE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVE_STARTTIME); + VectorScale( axis[0], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); + VectorScale( axis[1], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); + VectorScale( axis[2], c * KAMI_SHOCKWAVE_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); + shockwave.nonNormalizedAxes = qtrue; + + if (t > KAMI_SHOCKWAVEFADE_STARTTIME) { + c = (float)(t - KAMI_SHOCKWAVEFADE_STARTTIME) / (float)(KAMI_SHOCKWAVE_ENDTIME - KAMI_SHOCKWAVEFADE_STARTTIME); + } + else { + c = 0; + } + c *= 0xff; + shockwave.shaderRGBA[0] = 0xff - c; + shockwave.shaderRGBA[1] = 0xff - c; + shockwave.shaderRGBA[2] = 0xff - c; + shockwave.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &shockwave ); + } + + if (t > KAMI_EXPLODE_STARTTIME && t < KAMI_IMPLODE_ENDTIME) { + // explosion and implosion + c = ( le->endTime - cg.time ) * le->lifeRate; + c *= 0xff; + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + re->shaderRGBA[3] = le->color[3] * c; + + if( t < KAMI_IMPLODE_STARTTIME ) { + c = (float)(t - KAMI_EXPLODE_STARTTIME) / (float)(KAMI_IMPLODE_STARTTIME - KAMI_EXPLODE_STARTTIME); + } + else { + if (!(le->leFlags & LEF_SOUND2)) { +// trap_S_StartSound (re->origin, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.kamikazeImplodeSound ); + trap_S_StartLocalSound(cgs.media.kamikazeImplodeSound, CHAN_AUTO); + le->leFlags |= LEF_SOUND2; + } + c = (float)(KAMI_IMPLODE_ENDTIME - t) / (float) (KAMI_IMPLODE_ENDTIME - KAMI_IMPLODE_STARTTIME); + } + VectorScale( axis[0], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[0] ); + VectorScale( axis[1], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[1] ); + VectorScale( axis[2], c * KAMI_BOOMSPHERE_MAXRADIUS / KAMI_BOOMSPHEREMODEL_RADIUS, re->axis[2] ); + re->nonNormalizedAxes = qtrue; + + trap_R_AddRefEntityToScene( re ); + // add the dlight + trap_R_AddLightToScene( re->origin, c * 1000.0, 1.0, 1.0, c ); + } + + if (t > KAMI_SHOCKWAVE2_STARTTIME && t < KAMI_SHOCKWAVE2_ENDTIME) { + // 2nd kamikaze shockwave + if (le->angles.trBase[0] == 0 && + le->angles.trBase[1] == 0 && + le->angles.trBase[2] == 0) { + le->angles.trBase[0] = random() * 360; + le->angles.trBase[1] = random() * 360; + le->angles.trBase[2] = random() * 360; + } + else { + c = 0; + } + memset(&shockwave, 0, sizeof(shockwave)); + shockwave.hModel = cgs.media.kamikazeShockWave; + shockwave.reType = RT_MODEL; + shockwave.shaderTime = re->shaderTime; + VectorCopy(re->origin, shockwave.origin); + + test[0] = le->angles.trBase[0]; + test[1] = le->angles.trBase[1]; + test[2] = le->angles.trBase[2]; + AnglesToAxis( test, axis ); + + c = (float)(t - KAMI_SHOCKWAVE2_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2_STARTTIME); + VectorScale( axis[0], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[0] ); + VectorScale( axis[1], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[1] ); + VectorScale( axis[2], c * KAMI_SHOCKWAVE2_MAXRADIUS / KAMI_SHOCKWAVEMODEL_RADIUS, shockwave.axis[2] ); + shockwave.nonNormalizedAxes = qtrue; + + if (t > KAMI_SHOCKWAVE2FADE_STARTTIME) { + c = (float)(t - KAMI_SHOCKWAVE2FADE_STARTTIME) / (float)(KAMI_SHOCKWAVE2_ENDTIME - KAMI_SHOCKWAVE2FADE_STARTTIME); + } + else { + c = 0; + } + c *= 0xff; + shockwave.shaderRGBA[0] = 0xff - c; + shockwave.shaderRGBA[1] = 0xff - c; + shockwave.shaderRGBA[2] = 0xff - c; + shockwave.shaderRGBA[3] = 0xff - c; + + trap_R_AddRefEntityToScene( &shockwave ); + } +} + + +/* +=================== +CG_AddInvulnerabilityImpact +=================== +*/ +void CG_AddInvulnerabilityImpact( localEntity_t *le ) { + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +/* +=================== +CG_AddInvulnerabilityJuiced +=================== +*/ +void CG_AddInvulnerabilityJuiced( localEntity_t *le ) { + int t; + + t = cg.time - le->startTime; + if ( t > 3000 ) { + le->refEntity.axis[0][0] = (float) 1.0 + 0.3 * (t - 3000) / 2000; + le->refEntity.axis[1][1] = (float) 1.0 + 0.3 * (t - 3000) / 2000; + le->refEntity.axis[2][2] = (float) 0.7 + 0.3 * (2000 - (t - 3000)) / 2000; + } + if ( t > 5000 ) { + le->endTime = 0; + CG_GibPlayer( le->refEntity.origin ); + } + else { + trap_R_AddRefEntityToScene( &le->refEntity ); + } +} + +/* +=================== +CG_AddRefEntity +=================== +*/ +void CG_AddRefEntity( localEntity_t *le ) { + if (le->endTime < cg.time) { + CG_FreeLocalEntity( le ); + return; + } + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +#endif +/* +=================== +CG_AddScorePlum +=================== +*/ +#define NUMBER_SIZE 8 + +void CG_AddScorePlum( localEntity_t *le ) { + refEntity_t *re; + vec3_t origin, delta, dir, vec, up = {0, 0, 1}; + float c, len; + int i, score, digits[10], numdigits, negative; + + re = &le->refEntity; + + c = ( le->endTime - cg.time ) * le->lifeRate; + + score = le->radius; + if (score < 0) { + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0x11; + re->shaderRGBA[2] = 0x11; + } + else { + re->shaderRGBA[0] = 0xff; + re->shaderRGBA[1] = 0xff; + re->shaderRGBA[2] = 0xff; + if (score >= 50) { + re->shaderRGBA[1] = 0; + } else if (score >= 20) { + re->shaderRGBA[0] = re->shaderRGBA[1] = 0; + } else if (score >= 10) { + re->shaderRGBA[2] = 0; + } else if (score >= 2) { + re->shaderRGBA[0] = re->shaderRGBA[2] = 0; + } + + } + if (c < 0.25) + re->shaderRGBA[3] = 0xff * 4 * c; + else + re->shaderRGBA[3] = 0xff; + + re->radius = NUMBER_SIZE / 2; + + VectorCopy(le->pos.trBase, origin); + origin[2] += 110 - c * 100; + + VectorSubtract(cg.refdef.vieworg, origin, dir); + CrossProduct(dir, up, vec); + VectorNormalize(vec); + + VectorMA(origin, -10 + 20 * sin(c * 2 * M_PI), vec, origin); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + VectorSubtract( origin, cg.refdef.vieworg, delta ); + len = VectorLength( delta ); + if ( len < 20 ) { + CG_FreeLocalEntity( le ); + return; + } + + negative = qfalse; + if (score < 0) { + negative = qtrue; + score = -score; + } + + for (numdigits = 0; !(numdigits && !score); numdigits++) { + digits[numdigits] = score % 10; + score = score / 10; + } + + if (negative) { + digits[numdigits] = 10; + numdigits++; + } + + for (i = 0; i < numdigits; i++) { + VectorMA(origin, (float) (((float) numdigits / 2) - i) * NUMBER_SIZE, vec, re->origin); + re->customShader = cgs.media.numberShaders[digits[numdigits-1-i]]; + trap_R_AddRefEntityToScene( re ); + } +} + + +//============================================================================== + +/* +=================== +CG_AddLocalEntities + +=================== +*/ +void CG_AddLocalEntities( void ) { + localEntity_t *le, *next; + + // walk the list backwards, so any new local entities generated + // (trails, marks, etc) will be present this frame + le = cg_activeLocalEntities.prev; + for ( ; le != &cg_activeLocalEntities ; le = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = le->prev; + + if ( cg.time >= le->endTime ) { + CG_FreeLocalEntity( le ); + continue; + } + switch ( le->leType ) { + default: + CG_Error( "Bad leType: %i", le->leType ); + break; + + case LE_MARK: + break; + + case LE_SPRITE_EXPLOSION: + CG_AddSpriteExplosion( le ); + break; + + case LE_EXPLOSION: + CG_AddExplosion( le ); + break; + + case LE_FRAGMENT: // gibs and brass + CG_AddFragment( le ); + break; + + case LE_MOVE_SCALE_FADE: // water bubbles + CG_AddMoveScaleFade( le ); + break; + + case LE_FADE_RGB: // teleporters, railtrails + CG_AddFadeRGB( le ); + break; + + case LE_FALL_SCALE_FADE: // gib blood trails + CG_AddFallScaleFade( le ); + break; + + case LE_SCALE_FADE: // rocket trails + CG_AddScaleFade( le ); + break; + + case LE_SCOREPLUM: + CG_AddScorePlum( le ); + break; + + //PKMOD - Ergodic 01/12/04 - add LE for quad beans + case LE_SCALED_SPRITE_EXPLOSION: + CG_AddScaledSpriteExplosion( le ); + break; + +#ifdef MISSIONPACK + case LE_KAMIKAZE: + CG_AddKamikaze( le ); + break; + case LE_INVULIMPACT: + CG_AddInvulnerabilityImpact( le ); + break; + case LE_INVULJUICED: + CG_AddInvulnerabilityJuiced( le ); + break; + case LE_SHOWREFENTITY: + CG_AddRefEntity( le ); + break; +#endif + } + + } +} + + + + diff --git a/quake3/source/code/cgame/cg_main.c b/quake3/source/code/cgame/cg_main.c new file mode 100644 index 0000000..0d90ddd --- /dev/null +++ b/quake3/source/code/cgame/cg_main.c @@ -0,0 +1,2545 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_main.c -- initialization and primary entry point for cgame +#include "cg_local.h" + +//PKMOD - Ergodic 01/17/04 - Enable HUD in PKA3.0 +//#ifdef MISSIONPACK +#include "../ui/ui_shared.h" +// display context for new ui stuff +displayContextDef_t cgDC; +//#endif + +int forceModelModificationCount = -1; + +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); +void CG_Shutdown( void ); + + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +int vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8, int arg9, int arg10, int arg11 ) { + + switch ( command ) { + case CG_INIT: + //PKMOD - Ergodic 10/31/01 - debug CG_INIT arguments (inactive) +// Com_Printf("vmMain - 0>%d<, 1>%d<, 2>%d<, 3>%d<, 4>%d<, 5>%d<, 6>%d<, 7>%d<, 8>%d<, 9>%d<, 10>%d<, 11>%d<\n", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 ); + CG_Init( arg0, arg1, arg2 ); + return 0; + case CG_SHUTDOWN: + CG_Shutdown(); + return 0; + case CG_CONSOLE_COMMAND: + return CG_ConsoleCommand(); + case CG_DRAW_ACTIVE_FRAME: + //PKMOD - Ergodic 10/31/01 - debug CG_DRAW_ACTIVE_FRAME arguments (inactive) +// Com_Printf("vmMain - 0>%d<, 1>%d<, 2>%d<, 3>%d<, 4>%d<, 5>%d<, 6>%d<, 7>%d<, 8>%d<, 9>%d<, 10>%d<, 11>%d<\n", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 ); + CG_DrawActiveFrame( arg0, arg1, arg2 ); + return 0; + case CG_CROSSHAIR_PLAYER: + return CG_CrosshairPlayer(); + case CG_LAST_ATTACKER: + return CG_LastAttacker(); + case CG_KEY_EVENT: + CG_KeyEvent(arg0, arg1); + return 0; + case CG_MOUSE_EVENT: +//PKMOD - Ergodic 01/17/04 - enable HUD code in PKA 3.0 +//#ifdef MISSIONPACK + cgDC.cursorx = cgs.cursorX; + cgDC.cursory = cgs.cursorY; +//#endif + CG_MouseEvent(arg0, arg1); + return 0; + case CG_EVENT_HANDLING: + CG_EventHandling(arg0); + return 0; + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + return -1; +} + + +cg_t cg; +cgs_t cgs; +centity_t cg_entities[MAX_GENTITIES]; +weaponInfo_t cg_weapons[MAX_WEAPONS]; +itemInfo_t cg_items[MAX_ITEMS]; + + +vmCvar_t cg_railTrailTime; +vmCvar_t cg_centertime; +vmCvar_t cg_runpitch; +vmCvar_t cg_runroll; +vmCvar_t cg_bobup; +vmCvar_t cg_bobpitch; +vmCvar_t cg_bobroll; +vmCvar_t cg_swingSpeed; +vmCvar_t cg_shadows; +vmCvar_t cg_gibs; +vmCvar_t cg_drawTimer; +vmCvar_t cg_drawFPS; +vmCvar_t cg_drawSnapshot; +vmCvar_t cg_draw3dIcons; +vmCvar_t cg_drawIcons; +vmCvar_t cg_drawAmmoWarning; +vmCvar_t cg_drawCrosshair; +vmCvar_t cg_drawCrosshairNames; +vmCvar_t cg_drawRewards; +vmCvar_t cg_crosshairSize; +vmCvar_t cg_crosshairX; +vmCvar_t cg_crosshairY; +vmCvar_t cg_crosshairHealth; +vmCvar_t cg_draw2D; +vmCvar_t cg_drawStatus; +vmCvar_t cg_animSpeed; +vmCvar_t cg_debugAnim; +vmCvar_t cg_debugPosition; +vmCvar_t cg_debugEvents; +vmCvar_t cg_errorDecay; +vmCvar_t cg_nopredict; +vmCvar_t cg_noPlayerAnims; +vmCvar_t cg_showmiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_addMarks; +vmCvar_t cg_brassTime; +vmCvar_t cg_viewsize; +vmCvar_t cg_drawGun; +vmCvar_t cg_gun_frame; +vmCvar_t cg_gun_x; +vmCvar_t cg_gun_y; +vmCvar_t cg_gun_z; +vmCvar_t cg_tracerChance; +vmCvar_t cg_tracerWidth; +vmCvar_t cg_tracerLength; +vmCvar_t cg_autoswitch; +vmCvar_t cg_ignore; +vmCvar_t cg_simpleItems; +vmCvar_t cg_fov; +vmCvar_t cg_zoomFov; +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_drawAttacker; +vmCvar_t cg_synchronousClients; +vmCvar_t cg_teamChatTime; +vmCvar_t cg_teamChatHeight; +vmCvar_t cg_stats; +vmCvar_t cg_buildScript; +vmCvar_t cg_forceModel; +vmCvar_t cg_paused; +vmCvar_t cg_blood; +vmCvar_t cg_predictItems; +vmCvar_t cg_deferPlayers; +vmCvar_t cg_drawTeamOverlay; +vmCvar_t cg_teamOverlayUserinfo; +vmCvar_t cg_drawFriend; +vmCvar_t cg_teamChatsOnly; +vmCvar_t cg_noVoiceChats; +vmCvar_t cg_noVoiceText; +vmCvar_t cg_hudFiles; +vmCvar_t cg_scorePlum; +vmCvar_t cg_smoothClients; +vmCvar_t pmove_fixed; +//vmCvar_t cg_pmove_fixed; +vmCvar_t pmove_msec; +vmCvar_t cg_pmove_msec; +vmCvar_t cg_cameraMode; +vmCvar_t cg_cameraOrbit; +vmCvar_t cg_cameraOrbitDelay; +vmCvar_t cg_timescaleFadeEnd; +vmCvar_t cg_timescaleFadeSpeed; +vmCvar_t cg_timescale; +vmCvar_t cg_smallFont; +vmCvar_t cg_bigFont; +vmCvar_t cg_noTaunt; +vmCvar_t cg_noProjectileTrail; +vmCvar_t cg_oldRail; +vmCvar_t cg_oldRocket; +vmCvar_t cg_oldPlasma; +vmCvar_t cg_trueLightning; +//PKMOD - Ergodic 08/17/2002 - add Client error for communication to UI module +vmCvar_t cl_pkaerror; +//PKMOD - Ergodic 08/16/03 - add cvar for PKA full weapon cycling +vmCvar_t cg_pkafullweaponcycling; + + + +#ifdef MISSIONPACK +vmCvar_t cg_redTeamName; +vmCvar_t cg_blueTeamName; +vmCvar_t cg_currentSelectedPlayer; +vmCvar_t cg_currentSelectedPlayerName; +vmCvar_t cg_singlePlayer; +vmCvar_t cg_enableDust; +vmCvar_t cg_enableBreath; +vmCvar_t cg_singlePlayerActive; +vmCvar_t cg_recordSPDemo; +vmCvar_t cg_recordSPDemoName; +vmCvar_t cg_obeliskRespawnDelay; +#endif + +//PKMOD - Ergodic 02/02/04 - Enable this code so that POSTGAME will show proper completion time +vmCvar_t cg_singlePlayerActive; +vmCvar_t cg_recordSPDemo; +vmCvar_t cg_recordSPDemoName; + + +//PKMOD - Ergodic 01/17/04 - Enable Hud Code +vmCvar_t cg_redTeamName; +vmCvar_t cg_blueTeamName; +vmCvar_t cg_currentSelectedPlayer; +vmCvar_t cg_currentSelectedPlayerName; + +//PKMOD - Ergodic 05/11/01 - register holdables into their own array +// for optimization purposes +int cg_holdable[ HI_NUM_HOLDABLE ]; + +typedef struct { + vmCvar_t *vmCvar; + char *cvarName; + char *defaultString; + int cvarFlags; +} cvarTable_t; + +static cvarTable_t cvarTable[] = { // bk001129 + { &cg_ignore, "cg_ignore", "0", 0 }, // used for debugging + { &cg_autoswitch, "cg_autoswitch", "1", CVAR_ARCHIVE }, + { &cg_drawGun, "cg_drawGun", "1", CVAR_ARCHIVE }, + { &cg_zoomFov, "cg_zoomfov", "22.5", CVAR_ARCHIVE }, + { &cg_fov, "cg_fov", "90", CVAR_ARCHIVE }, + { &cg_viewsize, "cg_viewsize", "100", CVAR_ARCHIVE }, + { &cg_stereoSeparation, "cg_stereoSeparation", "0.4", CVAR_ARCHIVE }, + { &cg_shadows, "cg_shadows", "1", CVAR_ARCHIVE }, + { &cg_gibs, "cg_gibs", "1", CVAR_ARCHIVE }, + { &cg_draw2D, "cg_draw2D", "1", CVAR_ARCHIVE }, + { &cg_drawStatus, "cg_drawStatus", "1", CVAR_ARCHIVE }, + { &cg_drawTimer, "cg_drawTimer", "0", CVAR_ARCHIVE }, + { &cg_drawFPS, "cg_drawFPS", "0", CVAR_ARCHIVE }, + { &cg_drawSnapshot, "cg_drawSnapshot", "0", CVAR_ARCHIVE }, + { &cg_draw3dIcons, "cg_draw3dIcons", "1", CVAR_ARCHIVE }, + { &cg_drawIcons, "cg_drawIcons", "1", CVAR_ARCHIVE }, + { &cg_drawAmmoWarning, "cg_drawAmmoWarning", "1", CVAR_ARCHIVE }, + { &cg_drawAttacker, "cg_drawAttacker", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "4", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, + { &cg_crosshairHealth, "cg_crosshairHealth", "1", CVAR_ARCHIVE }, + { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, + { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, + { &cg_brassTime, "cg_brassTime", "2500", CVAR_ARCHIVE }, + { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, + { &cg_lagometer, "cg_lagometer", "1", CVAR_ARCHIVE }, + { &cg_railTrailTime, "cg_railTrailTime", "400", CVAR_ARCHIVE }, + { &cg_gun_x, "cg_gunX", "0", CVAR_CHEAT }, + { &cg_gun_y, "cg_gunY", "0", CVAR_CHEAT }, + { &cg_gun_z, "cg_gunZ", "0", CVAR_CHEAT }, + { &cg_centertime, "cg_centertime", "3", CVAR_CHEAT }, + { &cg_runpitch, "cg_runpitch", "0.002", CVAR_ARCHIVE}, + { &cg_runroll, "cg_runroll", "0.005", CVAR_ARCHIVE }, + { &cg_bobup , "cg_bobup", "0.005", CVAR_CHEAT }, + { &cg_bobpitch, "cg_bobpitch", "0.002", CVAR_ARCHIVE }, + { &cg_bobroll, "cg_bobroll", "0.002", CVAR_ARCHIVE }, + { &cg_swingSpeed, "cg_swingSpeed", "0.3", CVAR_CHEAT }, + { &cg_animSpeed, "cg_animspeed", "1", CVAR_CHEAT }, + { &cg_debugAnim, "cg_debuganim", "0", CVAR_CHEAT }, + { &cg_debugPosition, "cg_debugposition", "0", CVAR_CHEAT }, + { &cg_debugEvents, "cg_debugevents", "0", CVAR_CHEAT }, + { &cg_errorDecay, "cg_errordecay", "100", 0 }, + { &cg_nopredict, "cg_nopredict", "0", 0 }, + { &cg_noPlayerAnims, "cg_noplayeranims", "0", CVAR_CHEAT }, + { &cg_showmiss, "cg_showmiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "1", CVAR_CHEAT }, + { &cg_tracerChance, "cg_tracerchance", "0.4", CVAR_CHEAT }, + { &cg_tracerWidth, "cg_tracerwidth", "1", CVAR_CHEAT }, + { &cg_tracerLength, "cg_tracerlength", "100", CVAR_CHEAT }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "40", CVAR_CHEAT }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPerson, "cg_thirdPerson", "0", 0 }, + { &cg_teamChatTime, "cg_teamChatTime", "3000", CVAR_ARCHIVE }, + { &cg_teamChatHeight, "cg_teamChatHeight", "0", CVAR_ARCHIVE }, + { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, + { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, +#ifdef MISSIONPACK + { &cg_deferPlayers, "cg_deferPlayers", "0", CVAR_ARCHIVE }, +#else + { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, +#endif + { &cg_drawTeamOverlay, "cg_drawTeamOverlay", "0", CVAR_ARCHIVE }, + { &cg_teamOverlayUserinfo, "teamoverlay", "0", CVAR_ROM | CVAR_USERINFO }, + { &cg_stats, "cg_stats", "0", 0 }, + { &cg_drawFriend, "cg_drawFriend", "1", CVAR_ARCHIVE }, + { &cg_teamChatsOnly, "cg_teamChatsOnly", "0", CVAR_ARCHIVE }, + { &cg_noVoiceChats, "cg_noVoiceChats", "0", CVAR_ARCHIVE }, + { &cg_noVoiceText, "cg_noVoiceText", "0", CVAR_ARCHIVE }, + // the following variables are created in other parts of the system, + // but we also reference them here + { &cg_buildScript, "com_buildScript", "0", 0 }, // force loading of all possible data amd error on failures + { &cg_paused, "cl_paused", "0", CVAR_ROM }, + { &cg_blood, "com_blood", "1", CVAR_ARCHIVE }, + { &cg_synchronousClients, "g_synchronousClients", "0", 0 }, // communicated by systeminfo +#ifdef MISSIONPACK + { &cg_redTeamName, "g_redteam", DEFAULT_REDTEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, + { &cg_blueTeamName, "g_blueteam", DEFAULT_BLUETEAM_NAME, CVAR_ARCHIVE | CVAR_SERVERINFO | CVAR_USERINFO }, + { &cg_currentSelectedPlayer, "cg_currentSelectedPlayer", "0", CVAR_ARCHIVE}, + { &cg_currentSelectedPlayerName, "cg_currentSelectedPlayerName", "", CVAR_ARCHIVE}, + { &cg_singlePlayer, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_enableDust, "g_enableDust", "0", CVAR_SERVERINFO}, + { &cg_enableBreath, "g_enableBreath", "0", CVAR_SERVERINFO}, + { &cg_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, + { &cg_recordSPDemoName, "ui_recordSPDemoName", "", CVAR_ARCHIVE}, + { &cg_obeliskRespawnDelay, "g_obeliskRespawnDelay", "10", CVAR_SERVERINFO}, + { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, +#endif + + //PKMOD - Ergodic 02/02/04 - Enable this code so that POSTGAME will show proper completion time + { &cg_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, + { &cg_recordSPDemoName, "ui_recordSPDemoName", "", CVAR_ARCHIVE}, + + //PKMOD - Ergodic 01/17/03 - Activate HUD for PKA3.0 + { &cg_hudFiles, "cg_hudFiles", "ui/hud.txt", CVAR_ARCHIVE}, + { &cg_cameraOrbit, "cg_cameraOrbit", "0", CVAR_CHEAT}, + { &cg_cameraOrbitDelay, "cg_cameraOrbitDelay", "50", CVAR_ARCHIVE}, + { &cg_timescaleFadeEnd, "cg_timescaleFadeEnd", "1", 0}, + { &cg_timescaleFadeSpeed, "cg_timescaleFadeSpeed", "0", 0}, + { &cg_timescale, "timescale", "1", 0}, + { &cg_scorePlum, "cg_scorePlums", "1", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_smoothClients, "cg_smoothClients", "0", CVAR_USERINFO | CVAR_ARCHIVE}, + { &cg_cameraMode, "com_cameraMode", "0", CVAR_CHEAT}, + + { &pmove_fixed, "pmove_fixed", "0", 0}, + { &pmove_msec, "pmove_msec", "8", 0}, + { &cg_noTaunt, "cg_noTaunt", "0", CVAR_ARCHIVE}, + { &cg_noProjectileTrail, "cg_noProjectileTrail", "0", CVAR_ARCHIVE}, + { &cg_smallFont, "ui_smallFont", "0.25", CVAR_ARCHIVE}, + { &cg_bigFont, "ui_bigFont", "0.4", CVAR_ARCHIVE}, + { &cg_oldRail, "cg_oldRail", "1", CVAR_ARCHIVE}, + { &cg_oldRocket, "cg_oldRocket", "1", CVAR_ARCHIVE}, + { &cg_oldPlasma, "cg_oldPlasma", "1", CVAR_ARCHIVE}, + { &cg_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE}, +// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } + //PKMOD - Ergodic 08/17/2002 - add Client error for communication to UI module + { &cl_pkaerror, "cl_pkaerror", "0", CVAR_ROM }, + //PKMOD - Ergodic 08/16/03 - add cvar for PKA full weapon cycling + { &cg_pkafullweaponcycling, "cg_pkafullweaponcycling", "1", CVAR_ARCHIVE }, +}; + +static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +//PKMOD - Ergodic 10/10/2000 - 25 voting images' shader assignments - set to zero +char cg_voting_shader_flag[] = { "0000000000000000000000000" }; + +/* +================= +CG_RegisterCvars +================= +*/ +void CG_RegisterCvars( void ) { + int i; + cvarTable_t *cv; + char var[MAX_TOKEN_CHARS]; + + for ( i = 0, cv = cvarTable ; i < cvarTableSize ; i++, cv++ ) { + trap_Cvar_Register( cv->vmCvar, cv->cvarName, + cv->defaultString, cv->cvarFlags ); + } + + // see if we are also running the server on this machine + trap_Cvar_VariableStringBuffer( "sv_running", var, sizeof( var ) ); + cgs.localServer = atoi( var ); + + forceModelModificationCount = cg_forceModel.modificationCount; + + trap_Cvar_Register(NULL, "model", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "headmodel", DEFAULT_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "team_model", DEFAULT_TEAM_MODEL, CVAR_USERINFO | CVAR_ARCHIVE ); + trap_Cvar_Register(NULL, "team_headmodel", DEFAULT_TEAM_HEAD, CVAR_USERINFO | CVAR_ARCHIVE ); +} + +/* +=================== +CG_ForceModelChange +=================== +*/ +static void CG_ForceModelChange( void ) { + int i; + + for (i=0 ; ivmCvar ); + } + + // check for modications here + + // If team overlay is on, ask for updates from the server. If its off, + // let the server know so we don't receive it + if ( drawTeamOverlayModificationCount != cg_drawTeamOverlay.modificationCount ) { + drawTeamOverlayModificationCount = cg_drawTeamOverlay.modificationCount; + + if ( cg_drawTeamOverlay.integer > 0 ) { + trap_Cvar_Set( "teamoverlay", "1" ); + } else { + trap_Cvar_Set( "teamoverlay", "0" ); + } + // FIXME E3 HACK + trap_Cvar_Set( "teamoverlay", "1" ); + } + + // if force model changed + if ( forceModelModificationCount != cg_forceModel.modificationCount ) { + forceModelModificationCount = cg_forceModel.modificationCount; + CG_ForceModelChange(); + } +} + +int CG_CrosshairPlayer( void ) { + if ( cg.time > ( cg.crosshairClientTime + 1000 ) ) { + return -1; + } + return cg.crosshairClientNum; +} + +int CG_LastAttacker( void ) { + if ( !cg.attackerTime ) { + return -1; + } + return cg.snap->ps.persistant[PERS_ATTACKER]; +} + +void QDECL CG_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Print( text ); +} + +void QDECL CG_Error( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + trap_Error( text ); +} + +#ifndef CGAME_HARD_LINKED +// this is only here so the functions in q_shared.c and bg_*.c can link (FIXME) + +void QDECL Com_Error( int level, const char *error, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, error); + vsprintf (text, error, argptr); + va_end (argptr); + + CG_Error( "%s", text); +} + +void QDECL Com_Printf( const char *msg, ... ) { + va_list argptr; + char text[1024]; + + va_start (argptr, msg); + vsprintf (text, msg, argptr); + va_end (argptr); + + CG_Printf ("%s", text); +} + +#endif + +/* +================ +CG_Argv +================ +*/ +const char *CG_Argv( int arg ) { + static char buffer[MAX_STRING_CHARS]; + + trap_Argv( arg, buffer, sizeof( buffer ) ); + + return buffer; +} + + +//======================================================================== + +/* +================= +CG_RegisterItemSounds + +The server says this item is used on this level +================= +*/ +static void CG_RegisterItemSounds( int itemNum ) { + gitem_t *item; + char data[MAX_QPATH]; + char *s, *start; + int len; + + item = &bg_itemlist[ itemNum ]; + + if( item->pickup_sound ) { + trap_S_RegisterSound( item->pickup_sound, qfalse ); + } + + // parse the space seperated precache string for other media + s = item->sounds; + if (!s || !s[0]) + return; + + while (*s) { + start = s; + while (*s && *s != ' ') { + s++; + } + + len = s-start; + if (len >= MAX_QPATH || len < 5) { + CG_Error( "PrecacheItem: %s has bad precache string", + item->classname); + return; + } + memcpy (data, start, len); + data[len] = 0; + if ( *s ) { + s++; + } + + if ( !strcmp(data+len-3, "wav" )) { + trap_S_RegisterSound( data, qfalse ); + } + } +} + + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +static void CG_RegisterSounds( void ) { + int i; + char items[MAX_ITEMS+1]; + char name[MAX_QPATH]; + const char *soundName; + + //PKMOD - Ergodic 07/30/01 - debug "bad magic" crash (inactive) +// int dbg1, dbg2; +// dbg1 = 1; +// dbg2 = 2; + + // voice commands +#ifdef MISSIONPACK + CG_LoadVoiceChats(); +#endif + + cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/feedback/1_minute.wav", qtrue ); + cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/feedback/5_minute.wav", qtrue ); + cgs.media.suddenDeathSound = trap_S_RegisterSound( "sound/feedback/sudden_death.wav", qtrue ); + cgs.media.oneFragSound = trap_S_RegisterSound( "sound/feedback/1_frag.wav", qtrue ); + cgs.media.twoFragSound = trap_S_RegisterSound( "sound/feedback/2_frags.wav", qtrue ); + cgs.media.threeFragSound = trap_S_RegisterSound( "sound/feedback/3_frags.wav", qtrue ); + cgs.media.count3Sound = trap_S_RegisterSound( "sound/feedback/three.wav", qtrue ); + cgs.media.count2Sound = trap_S_RegisterSound( "sound/feedback/two.wav", qtrue ); + cgs.media.count1Sound = trap_S_RegisterSound( "sound/feedback/one.wav", qtrue ); + cgs.media.countFightSound = trap_S_RegisterSound( "sound/feedback/fight.wav", qtrue ); + cgs.media.countPrepareSound = trap_S_RegisterSound( "sound/feedback/prepare.wav", qtrue ); +#ifdef MISSIONPACK + cgs.media.countPrepareTeamSound = trap_S_RegisterSound( "sound/feedback/prepare_team.wav", qtrue ); +#endif + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + + cgs.media.captureAwardSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); + cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/feedback/redleads.wav", qtrue ); + cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/feedback/blueleads.wav", qtrue ); + cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/feedback/teamstied.wav", qtrue ); + cgs.media.hitTeamSound = trap_S_RegisterSound( "sound/feedback/hit_teammate.wav", qtrue ); + + cgs.media.redScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_red_scores.wav", qtrue ); + cgs.media.blueScoredSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_scores.wav", qtrue ); + + cgs.media.captureYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav", qtrue ); + cgs.media.captureOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_opponent.wav", qtrue ); + + cgs.media.returnYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_yourteam.wav", qtrue ); + cgs.media.returnOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); + + cgs.media.takenYourTeamSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_yourteam.wav", qtrue ); + cgs.media.takenOpponentSound = trap_S_RegisterSound( "sound/teamplay/flagtaken_opponent.wav", qtrue ); + + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { + cgs.media.redFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_red_returned.wav", qtrue ); + cgs.media.blueFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/voc_blue_returned.wav", qtrue ); + cgs.media.enemyTookYourFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_flag.wav", qtrue ); + cgs.media.yourTeamTookEnemyFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_flag.wav", qtrue ); + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { + // FIXME: get a replacement for this sound ? + cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); + cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); + cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); + } + + if ( cgs.gametype == GT_1FCTF || cgs.gametype == GT_CTF || cg_buildScript.integer ) { + cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); + cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); + } + + if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { + cgs.media.yourBaseIsUnderAttackSound = trap_S_RegisterSound( "sound/teamplay/voc_base_attack.wav", qtrue ); + } +#else + cgs.media.youHaveFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_you_flag.wav", qtrue ); + cgs.media.holyShitSound = trap_S_RegisterSound("sound/feedback/voc_holyshit.wav", qtrue); + cgs.media.neutralFlagReturnedSound = trap_S_RegisterSound( "sound/teamplay/flagreturn_opponent.wav", qtrue ); + cgs.media.yourTeamTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_team_1flag.wav", qtrue ); + cgs.media.enemyTookTheFlagSound = trap_S_RegisterSound( "sound/teamplay/voc_enemy_1flag.wav", qtrue ); +#endif + } + + cgs.media.tracerSound = trap_S_RegisterSound( "sound/weapons/machinegun/buletby1.wav", qfalse ); + cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav", qfalse ); + cgs.media.wearOffSound = trap_S_RegisterSound( "sound/items/wearoff.wav", qfalse ); + cgs.media.useNothingSound = trap_S_RegisterSound( "sound/items/use_nothing.wav", qfalse ); + cgs.media.gibSound = trap_S_RegisterSound( "sound/player/gibsplt1.wav", qfalse ); + cgs.media.gibBounce1Sound = trap_S_RegisterSound( "sound/player/gibimp1.wav", qfalse ); + cgs.media.gibBounce2Sound = trap_S_RegisterSound( "sound/player/gibimp2.wav", qfalse ); + cgs.media.gibBounce3Sound = trap_S_RegisterSound( "sound/player/gibimp3.wav", qfalse ); + +#ifdef MISSIONPACK + cgs.media.useInvulnerabilitySound = trap_S_RegisterSound( "sound/items/invul_activate.wav", qfalse ); + cgs.media.invulnerabilityImpactSound1 = trap_S_RegisterSound( "sound/items/invul_impact_01.wav", qfalse ); + cgs.media.invulnerabilityImpactSound2 = trap_S_RegisterSound( "sound/items/invul_impact_02.wav", qfalse ); + cgs.media.invulnerabilityImpactSound3 = trap_S_RegisterSound( "sound/items/invul_impact_03.wav", qfalse ); + cgs.media.invulnerabilityJuicedSound = trap_S_RegisterSound( "sound/items/invul_juiced.wav", qfalse ); + cgs.media.obeliskHitSound1 = trap_S_RegisterSound( "sound/items/obelisk_hit_01.wav", qfalse ); + cgs.media.obeliskHitSound2 = trap_S_RegisterSound( "sound/items/obelisk_hit_02.wav", qfalse ); + cgs.media.obeliskHitSound3 = trap_S_RegisterSound( "sound/items/obelisk_hit_03.wav", qfalse ); + cgs.media.obeliskRespawnSound = trap_S_RegisterSound( "sound/items/obelisk_respawn.wav", qfalse ); + + cgs.media.ammoregenSound = trap_S_RegisterSound("sound/items/cl_ammoregen.wav", qfalse); + cgs.media.doublerSound = trap_S_RegisterSound("sound/items/cl_doubler.wav", qfalse); + cgs.media.guardSound = trap_S_RegisterSound("sound/items/cl_guard.wav", qfalse); + cgs.media.scoutSound = trap_S_RegisterSound("sound/items/cl_scout.wav", qfalse); +#endif + + cgs.media.teleInSound = trap_S_RegisterSound( "sound/world/telein.wav", qfalse ); + cgs.media.teleOutSound = trap_S_RegisterSound( "sound/world/teleout.wav", qfalse ); + cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav", qfalse ); + + cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav", qfalse ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav", qfalse ); + cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav", qfalse); + + cgs.media.hitSound = trap_S_RegisterSound( "sound/feedback/hit.wav", qfalse ); +#ifdef MISSIONPACK + cgs.media.hitSoundHighArmor = trap_S_RegisterSound( "sound/feedback/hithi.wav", qfalse ); + cgs.media.hitSoundLowArmor = trap_S_RegisterSound( "sound/feedback/hitlo.wav", qfalse ); +#endif + + cgs.media.impressiveSound = trap_S_RegisterSound( "sound/feedback/impressive.wav", qtrue ); + cgs.media.excellentSound = trap_S_RegisterSound( "sound/feedback/excellent.wav", qtrue ); + cgs.media.deniedSound = trap_S_RegisterSound( "sound/feedback/denied.wav", qtrue ); + cgs.media.humiliationSound = trap_S_RegisterSound( "sound/feedback/humiliation.wav", qtrue ); + cgs.media.assistSound = trap_S_RegisterSound( "sound/feedback/assist.wav", qtrue ); + cgs.media.defendSound = trap_S_RegisterSound( "sound/feedback/defense.wav", qtrue ); +#ifdef MISSIONPACK + cgs.media.firstImpressiveSound = trap_S_RegisterSound( "sound/feedback/first_impressive.wav", qtrue ); + cgs.media.firstExcellentSound = trap_S_RegisterSound( "sound/feedback/first_excellent.wav", qtrue ); + cgs.media.firstHumiliationSound = trap_S_RegisterSound( "sound/feedback/first_gauntlet.wav", qtrue ); +#endif + + cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/feedback/takenlead.wav", qtrue); + cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/feedback/tiedlead.wav", qtrue); + cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/feedback/lostlead.wav", qtrue); + +#ifdef MISSIONPACK + cgs.media.voteNow = trap_S_RegisterSound( "sound/feedback/vote_now.wav", qtrue); + cgs.media.votePassed = trap_S_RegisterSound( "sound/feedback/vote_passed.wav", qtrue); + cgs.media.voteFailed = trap_S_RegisterSound( "sound/feedback/vote_failed.wav", qtrue); +#endif + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav", qfalse); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav", qfalse); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav", qfalse); + + cgs.media.jumpPadSound = trap_S_RegisterSound ("sound/world/jumppad.wav", qfalse ); + + //PKMOD - Ergodic 08/08/00 PAINKILLER awarded after every 10 PKitem kills + //PKMOD - Ergodic 09/03/00 updated Painkiller wav file with sounds from Mongusta + cgs.media.painkillerSound = trap_S_RegisterSound( "sound/feedback/PainkillerAward2.wav", qfalse ); + + + for (i=0 ; i<4 ; i++) { + Com_sprintf (name, sizeof(name), "sound/player/footsteps/step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_NORMAL][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/boot%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_BOOT][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/flesh%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_FLESH][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/mech%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_MECH][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/energy%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_ENERGY][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/splash%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SPLASH][i] = trap_S_RegisterSound (name, qfalse); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/clank%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_METAL][i] = trap_S_RegisterSound (name, qfalse); + } + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS ) ); + + //PKMOD - Ergodic 07/30/01 - debug "bad magic" crash (inactive) +// Com_Printf( "CG_RegisterSounds - items>%s<\n\n", items ); +// Com_Printf( "CG_RegisterSounds - bg_numItems>%d<\n", bg_numItems ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { +// if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_RegisterItemSounds( i ); +// } + } + + //PKMOD - Ergodic 07/30/01 - debug "bad magic" crash (inactive) +// if ( dbg1 != dbg2 ) +// return; + + for ( i = 1 ; i < MAX_SOUNDS ; i++ ) { + soundName = CG_ConfigString( CS_SOUNDS+i ); + if ( !soundName[0] ) { + break; + } + if ( soundName[0] == '*' ) { + continue; // custom sound + } + cgs.gameSounds[i] = trap_S_RegisterSound( soundName, qfalse ); + + //PKMOD - Ergodic 11/19/02 - debug mover sound (inactive) + //Com_Printf( "CG_RegisterSounds - soundindex: %d - name >%s<\n", i, &soundName[0] ); + + } + + // FIXME: only needed with item + cgs.media.flightSound = trap_S_RegisterSound( "sound/items/flight.wav", qfalse ); + cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_medkit.wav", qfalse); + cgs.media.quadSound = trap_S_RegisterSound("sound/items/damage3.wav", qfalse); + cgs.media.sfx_ric1 = trap_S_RegisterSound ("sound/weapons/machinegun/ric1.wav", qfalse); + cgs.media.sfx_ric2 = trap_S_RegisterSound ("sound/weapons/machinegun/ric2.wav", qfalse); + cgs.media.sfx_ric3 = trap_S_RegisterSound ("sound/weapons/machinegun/ric3.wav", qfalse); + cgs.media.sfx_railg = trap_S_RegisterSound ("sound/weapons/railgun/railgf1a.wav", qfalse); + cgs.media.sfx_rockexp = trap_S_RegisterSound ("sound/weapons/rocket/rocklx1a.wav", qfalse); + cgs.media.sfx_plasmaexp = trap_S_RegisterSound ("sound/weapons/plasma/plasmx1a.wav", qfalse); +#ifdef MISSIONPACK + cgs.media.sfx_proxexp = trap_S_RegisterSound( "sound/weapons/proxmine/wstbexpl.wav" , qfalse); + cgs.media.sfx_nghit = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpd.wav" , qfalse); + cgs.media.sfx_nghitflesh = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpl.wav" , qfalse); + cgs.media.sfx_nghitmetal = trap_S_RegisterSound( "sound/weapons/nailgun/wnalimpm.wav", qfalse ); + cgs.media.sfx_chghit = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpd.wav", qfalse ); + cgs.media.sfx_chghitflesh = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpl.wav", qfalse ); + cgs.media.sfx_chghitmetal = trap_S_RegisterSound( "sound/weapons/vulcan/wvulimpm.wav", qfalse ); + cgs.media.weaponHoverSound = trap_S_RegisterSound( "sound/weapons/weapon_hover.wav", qfalse ); + cgs.media.kamikazeExplodeSound = trap_S_RegisterSound( "sound/items/kam_explode.wav", qfalse ); + cgs.media.kamikazeImplodeSound = trap_S_RegisterSound( "sound/items/kam_implode.wav", qfalse ); + cgs.media.kamikazeFarSound = trap_S_RegisterSound( "sound/items/kam_explode_far.wav", qfalse ); + cgs.media.winnerSound = trap_S_RegisterSound( "sound/feedback/voc_youwin.wav", qfalse ); + cgs.media.loserSound = trap_S_RegisterSound( "sound/feedback/voc_youlose.wav", qfalse ); + cgs.media.youSuckSound = trap_S_RegisterSound( "sound/misc/yousuck.wav", qfalse ); + + cgs.media.wstbimplSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpl.wav", qfalse); + cgs.media.wstbimpmSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpm.wav", qfalse); + cgs.media.wstbimpdSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpd.wav", qfalse); + cgs.media.wstbactvSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbactv.wav", qfalse); +#endif + + cgs.media.regenSound = trap_S_RegisterSound("sound/items/regen.wav", qfalse); + cgs.media.protectSound = trap_S_RegisterSound("sound/items/protect3.wav", qfalse); + cgs.media.n_healthSound = trap_S_RegisterSound("sound/items/n_health.wav", qfalse ); + cgs.media.hgrenb1aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb1a.wav", qfalse); + cgs.media.hgrenb2aSound = trap_S_RegisterSound("sound/weapons/grenade/hgrenb2a.wav", qfalse); + +#ifdef MISSIONPACK + trap_S_RegisterSound("sound/player/james/death1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/death2.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/death3.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/jump1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/pain25_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/pain75_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/pain100_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/falling1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/gasp.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/drown.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/fall1.wav", qfalse ); + trap_S_RegisterSound("sound/player/james/taunt.wav", qfalse ); + + trap_S_RegisterSound("sound/player/janet/death1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/death2.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/death3.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/jump1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/pain25_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/pain75_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/pain100_1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/falling1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/gasp.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/drown.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/fall1.wav", qfalse ); + trap_S_RegisterSound("sound/player/janet/taunt.wav", qfalse ); +#endif + //PKMOD - Ergodic 12/19/00 - remove Team Arena proxmine sounds +// cgs.media.wstbimplSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpl.wav", qfalse); +// cgs.media.wstbimpmSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpm.wav", qfalse); +// cgs.media.wstbimpdSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbimpd.wav", qfalse); +// cgs.media.wstbactvSound = trap_S_RegisterSound("sound/weapons/proxmine/wstbactv.wav", qfalse); + + //PKMOD - Ergodic 05/22/00 add pka sounds + //12/16/00 - add non compressed flag + cgs.media.sfx_pkagravitylaunched = trap_S_RegisterSound ("sound/weapons2/gwell/gravity_released.wav", qfalse); + cgs.media.sfx_pkabeartrapbreakup = trap_S_RegisterSound ("sound/weapons2/beartrap/beartrap_breakup.wav", qfalse); + cgs.media.sfx_pkabeartrapdrop = trap_S_RegisterSound ("sound/weapons2/beartrap/beartrap_drop.wav", qfalse); + cgs.media.sfx_pkabeartrapsnap = trap_S_RegisterSound ("sound/weapons2/beartrap/beartrap_snap.wav", qfalse); + + //PKMOD - Ergodic 03/23/01 - add team parameters + cgs.media.pkabeartrap_red = trap_R_RegisterModel( "models/weapons2/beartrap/beartrap_red.md3" ); + cgs.media.pkabeartrap_blue = trap_R_RegisterModel( "models/weapons2/beartrap/beartrap_blue.md3" ); + + //PKMOD - Ergodic 08/22/00 ChainLightning strike sounds + //12/16/00 - add non compressed flag + cgs.media.sfx_chainlightningstrike1 = trap_S_RegisterSound( "sound/weapons2/chainlightning/Chainlight spark.wav", qfalse ); + cgs.media.sfx_chainlightningstrike2 = trap_S_RegisterSound( "sound/weapons2/chainlightning/Chainlight spark#2.wav", qfalse ); + + //PKMOD - Ergodic 08/25/00 nailgun ricochet sounds + //12/16/00 - add non compressed flag + cgs.media.sfx_nailrico1 = trap_S_RegisterSound ("sound/weapons2/nailgun/nailrico1.wav", qfalse); + cgs.media.sfx_nailrico2 = trap_S_RegisterSound ("sound/weapons2/nailgun/nailrico2.wav", qfalse); + cgs.media.sfx_nailrico3 = trap_S_RegisterSound ("sound/weapons2/nailgun/nailrico3.wav", qfalse); + cgs.media.sfx_nailrico4 = trap_S_RegisterSound ("sound/weapons2/nailgun/nailrico4.wav", qfalse); + + //PKMOD - Ergodic 09/06/00 gravity well item suck sounds from Mongusta + //12/16/00 - add non compressed flag + cgs.media.sfx_pkagravitywell_suck1 = trap_S_RegisterSound ("sound/weapons2/gwell/Grav Well Suk#1.wav", qfalse); + cgs.media.sfx_pkagravitywell_suck2 = trap_S_RegisterSound ("sound/weapons2/gwell/Grav Well Suk#2.wav", qfalse); + cgs.media.sfx_pkagravitywell_suck3 = trap_S_RegisterSound ("sound/weapons2/gwell/Grav Well Suk#3.wav", qfalse); + + //PKMOD - Ergodic 11/22/00 autosentry sounds from Mongusta + //12/16/00 - add non compressed flag + cgs.media.sfx_pkasentrydrop = trap_S_RegisterSound ("sound/weapons2/autosentry/autosentrydeploy.wav", qfalse); + + //PKMOD - Ergodic 12/26/00 add beans fart sounds + cgs.media.sfx_pkafart1 = trap_S_RegisterSound ("sound/weapons2/beans/Fart Blast.wav", qfalse); + cgs.media.sfx_pkafart2 = trap_S_RegisterSound ("sound/weapons2/beans/Fart Short.wav", qfalse); + cgs.media.sfx_pkafart3 = trap_S_RegisterSound ("sound/weapons2/beans/Fart Squish.wav", qfalse); + cgs.media.sfx_pkafart4 = trap_S_RegisterSound ("sound/weapons2/beans/Fart Whistle.wav", qfalse); + cgs.media.sfx_pkafart5 = trap_S_RegisterSound ("sound/weapons2/beans/Fart Whooppee.wav", qfalse); + //PKMOD - Ergodic 06/30/01 add two more fart sounds from original Q1 PK + cgs.media.sfx_pkafart6 = trap_S_RegisterSound ("sound/weapons2/beans/Fart Q1PK_4.wav", qfalse); + cgs.media.sfx_pkafart7 = trap_S_RegisterSound ("sound/weapons2/beans/Fart Q1PK_5.wav", qfalse); + + //PKMOD - Ergodic 01/13/01 - add autosentry fire sounds from mongusta + cgs.media.sfx_pkasentry1 = trap_S_RegisterSound ("sound/weapons2/autosentry/autosentryshot1.wav", qfalse); + cgs.media.sfx_pkasentry2 = trap_S_RegisterSound ("sound/weapons2/autosentry/autosentryshot2.wav", qfalse); + cgs.media.sfx_pkasentry3 = trap_S_RegisterSound ("sound/weapons2/autosentry/autosentryshot3.wav", qfalse); + + //PKMOD - Ergodic 03/26/01 - autosentry sonar ping sounds + cgs.media.sfx_pkasentry_ping1 = trap_S_RegisterSound ("sound/weapons2/autosentry/autosentryping1.wav", qfalse); + cgs.media.sfx_pkasentry_ping2 = trap_S_RegisterSound ("sound/weapons2/autosentry/autosentryping2.wav", qfalse); + cgs.media.sfx_pkasentry_ping3 = trap_S_RegisterSound ("sound/weapons2/autosentry/autosentryping3.wav", qfalse); + + //PKMOD - Ergodic 06/20/01 - add legs model for swinging zombie + cgs.media.pkazombie_legsModel = trap_R_RegisterModel( "models/players/biker/lower.md3" ); + cgs.media.pkazombie_legsSkin = trap_R_RegisterSkin( "models/mapobjects/zombie/lower_zombie.skin" ); + + //PKMOD - Ergodic 06/21/01 - add torso model for swinging zombie + cgs.media.pkazombie_torsoModel = trap_R_RegisterModel( "models/players/biker/upper.md3" ); + cgs.media.pkazombie_torsoSkin = trap_R_RegisterSkin( "models/mapobjects/zombie/upper_zombie.skin" ); + + //PKMOD - Ergodic 06/21/01 - add head model for swinging zombie + cgs.media.pkazombie_headModel = trap_R_RegisterModel( "models/players/biker/head.md3" ); + cgs.media.pkazombie_headSkin = trap_R_RegisterSkin( "models/mapobjects/zombie/head_zombie.skin" ); + + //PKMOD - Ergodic 06/30/01 add airfist sounds for all types of situations + cgs.media.sfx_pkaairfistfire = trap_S_RegisterSound ("sound/weapons2/airfist/af_fire.wav", qfalse); + cgs.media.sfx_pkaairfistwaterfire = trap_S_RegisterSound ("sound/weapons2/airfist/af_wfire.wav", qfalse); + cgs.media.sfx_pkaairfistempty = trap_S_RegisterSound ("sound/weapons2/airfist/af_empty.wav", qfalse); + cgs.media.sfx_pkaairfistwaterempty = trap_S_RegisterSound ("sound/weapons2/airfist/af_wempty.wav", qfalse); + + //PKMOD - Ergodic 07/03/01 ChainLightning reflect sounds + cgs.media.sfx_chainlightningreflect1 = trap_S_RegisterSound( "sound/weapons2/chainlightning/Electricute2.wav", qfalse ); + cgs.media.sfx_chainlightningreflect2 = trap_S_RegisterSound( "sound/weapons2/chainlightning/Electricute3.wav", qfalse ); + + //PKMOD - Ergodic 12/03/01 - New Holdables - Private Bot pickup skins + cgs.media.privatebot_legsSkin = trap_R_RegisterSkin( "models/players/tankjr/lower_default.skin" ); + cgs.media.privatebot_torsoSkin = trap_R_RegisterSkin( "models/players/doom/upper_phobos.skin" ); + cgs.media.privatebot_headSkin = trap_R_RegisterSkin( "models/players/visor/head_painkiller.skin" ); + + //PKMOD - Ergodic 12/05/01 - Holdable: radiate sounds + cgs.media.pkaradiatewarningSound = trap_S_RegisterSound( "sound/items/radiatewarning.wav", qfalse ); + cgs.media.pkaradiateitemSound = trap_S_RegisterSound( "sound/items/radiateitem.wav", qfalse ); + cgs.media.pkaradiateplayerSound = trap_S_RegisterSound( "sound/items/radiateplayer.wav", qfalse ); + //PKMOD - Ergodic 08/02/02 - Holdable: radiate activation sound + cgs.media.pkaradiateactivationSound = trap_S_RegisterSound( "sound/items/radiateroar.wav", qfalse ); + + //PKMOD - Ergodic 12/07/01 - Holdable: Private Bot HUD Icons + cgs.media.pkapribot_001Icon = trap_R_RegisterShader( "icons/iconh_pribot_001" ); + cgs.media.pkapribot_010Icon = trap_R_RegisterShader( "icons/iconh_pribot_010" ); + cgs.media.pkapribot_011Icon = trap_R_RegisterShader( "icons/iconh_pribot_011" ); + cgs.media.pkapribot_100Icon = trap_R_RegisterShader( "icons/iconh_pribot_100" ); + cgs.media.pkapribot_101Icon = trap_R_RegisterShader( "icons/iconh_pribot_101" ); + cgs.media.pkapribot_110Icon = trap_R_RegisterShader( "icons/iconh_pribot_110" ); + cgs.media.pkapribot_111Icon = trap_R_RegisterShader( "icons/iconh_pribot_111" ); + + //PKMOD - Ergodic 12/16/01 - add new model for repositioned deployed gauntlet blade +// cgs.media.pkagauntlet_bladeModel = trap_R_RegisterModel( "models/weapons2/gauntlet/gauntlet_blade.md3" ); + + //PKMOD - Ergodic 02/06/02 - add attack sounds for beartrap and autosentry + cgs.media.beartrap_attackSound = trap_S_RegisterSound( "sound/feedback/attack_Trap.wav", qfalse ); + cgs.media.autosentry_attackSound = trap_S_RegisterSound( "sound/feedback/attack_Sentry.wav", qfalse ); + cgs.media.radiate_attackSound = trap_S_RegisterSound( "sound/feedback/attack_Radiate.wav", qfalse ); + + //PKMOD - Ergodic 02/07/02 - add Private Bot completed sound + cgs.media.pkapribot_complete = trap_S_RegisterSound( "sound/items/unit_complete.wav", qfalse ); + + //PKMOD - Ergodic 02/10/02 - send FRAG message to Private Bot's owner + cgs.media.pkapribot_frag1 = trap_S_RegisterSound( "sound/feedback/PB_frag_obtained.wav", qfalse ); + cgs.media.pkapribot_frag2 = trap_S_RegisterSound( "sound/feedback/PB_opposition_eliminated.wav", qfalse ); + + //PKMOD - Ergodic 02/14/02 - explosive shells hit sounds + cgs.media.sfx_expgunhit1 = trap_S_RegisterSound ("sound/weapons2/expgun/ESG_Hits1.wav", qfalse); + cgs.media.sfx_expgunhit2 = trap_S_RegisterSound ("sound/weapons2/expgun/ESG_Hits2.wav", qfalse); + cgs.media.sfx_expgunhit3 = trap_S_RegisterSound ("sound/weapons2/expgun/ESG_Hits3.wav", qfalse); + //PKMOD - Ergodic 07/10/02 - add 2 more explosive shells hit sounds + cgs.media.sfx_expgunhit4 = trap_S_RegisterSound ("sound/weapons2/expgun/ESG_Hits4.wav", qfalse); + cgs.media.sfx_expgunhit5 = trap_S_RegisterSound ("sound/weapons2/expgun/ESG_Hits5.wav", qfalse); + + //PKMOD - Ergodic 08/02/02 - Holdable: Personal Sentry hover sound + cgs.media.pkapersentryhoverSound = trap_S_RegisterSound( "sound/items/persentryhover.wav", qfalse ); + + //PKMOD - Ergodic 08/26/02 - add Personal Sentry fire sounds from StarDagger + cgs.media.pkapersentry_fire1 = trap_S_RegisterSound ("sound/weapons2/persentry/PersSentryshot1.wav", qfalse); + cgs.media.pkapersentry_fire2 = trap_S_RegisterSound ("sound/weapons2/persentry/PersSentryshot2.wav", qfalse); + cgs.media.pkapersentry_fire3 = trap_S_RegisterSound ("sound/weapons2/persentry/PersSentryshot3.wav", qfalse); + + //PKMOD - Ergodic 08/20/03 - add special shader for shooter lightning + cgs.media.shooterlightningShader = trap_R_RegisterShader( "shooterlightningBolt" ); + + //PKMOD - Ergodic 11/21/03 - Earthquake sound for Gravity Well effect on out of reach players + //PKMOD - Ergodic 12/06/03 - update Earthquake sound for Gravity Well effect on out of reach players + //PKMOD - Ergodic 12/07/03 - removed, code moved to global sound + //cgs.media.pkaearthquake = trap_S_RegisterSound( "sound/weapons2/gwell/earthquake3.wav", qfalse ); + + //PKMOD - Ergodic 12/08/03 - Add chargeup sound for BearTrap, Autosentry invisibility + cgs.media.pkachargeup = trap_S_RegisterSound( "sound/weapons/lightning/lg_charge.wav", qfalse ); + + //PKMOD - Ergodic 03/17/04 - add quad beans fart sounds + cgs.media.sfx_pkaquadfart1 = trap_S_RegisterSound ("sound/weapons2/beans/QFart_Boom1.wav", qfalse); + cgs.media.sfx_pkaquadfart2 = trap_S_RegisterSound ("sound/weapons2/beans/QFart_Duck1.wav", qfalse); + cgs.media.sfx_pkaquadfart3 = trap_S_RegisterSound ("sound/weapons2/beans/QFart_Frog1.wav", qfalse); + + +} + + +//=================================================================================== + + +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +static void CG_RegisterGraphics( void ) { + int i; + char items[MAX_ITEMS+1]; + static char *sb_nums[11] = { + "gfx/2d/numbers/zero_32b", + "gfx/2d/numbers/one_32b", + "gfx/2d/numbers/two_32b", + "gfx/2d/numbers/three_32b", + "gfx/2d/numbers/four_32b", + "gfx/2d/numbers/five_32b", + "gfx/2d/numbers/six_32b", + "gfx/2d/numbers/seven_32b", + "gfx/2d/numbers/eight_32b", + "gfx/2d/numbers/nine_32b", + "gfx/2d/numbers/minus_32b", + }; + + // clear any references to old media + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + trap_R_ClearScene(); + + CG_LoadingString( cgs.mapname ); + + trap_R_LoadWorldMap( cgs.mapname ); + + // precache status bar pics + CG_LoadingString( "game media" ); + + for ( i=0 ; i<11 ; i++) { + cgs.media.numberShaders[i] = trap_R_RegisterShader( sb_nums[i] ); + } + + cgs.media.botSkillShaders[0] = trap_R_RegisterShader( "menu/art/skill1.tga" ); + cgs.media.botSkillShaders[1] = trap_R_RegisterShader( "menu/art/skill2.tga" ); + cgs.media.botSkillShaders[2] = trap_R_RegisterShader( "menu/art/skill3.tga" ); + cgs.media.botSkillShaders[3] = trap_R_RegisterShader( "menu/art/skill4.tga" ); + cgs.media.botSkillShaders[4] = trap_R_RegisterShader( "menu/art/skill5.tga" ); + + cgs.media.viewBloodShader = trap_R_RegisterShader( "viewBloodBlend" ); + + cgs.media.deferShader = trap_R_RegisterShaderNoMip( "gfx/2d/defer.tga" ); + + cgs.media.scoreboardName = trap_R_RegisterShaderNoMip( "menu/tab/name.tga" ); + cgs.media.scoreboardPing = trap_R_RegisterShaderNoMip( "menu/tab/ping.tga" ); + cgs.media.scoreboardScore = trap_R_RegisterShaderNoMip( "menu/tab/score.tga" ); + cgs.media.scoreboardTime = trap_R_RegisterShaderNoMip( "menu/tab/time.tga" ); + + cgs.media.smokePuffShader = trap_R_RegisterShader( "smokePuff" ); + cgs.media.smokePuffRageProShader = trap_R_RegisterShader( "smokePuffRagePro" ); + cgs.media.shotgunSmokePuffShader = trap_R_RegisterShader( "shotgunSmokePuff" ); +#ifdef MISSIONPACK + cgs.media.nailPuffShader = trap_R_RegisterShader( "nailtrail" ); + cgs.media.blueProxMine = trap_R_RegisterModel( "models/weaphits/proxmineb.md3" ); +#endif + cgs.media.plasmaBallShader = trap_R_RegisterShader( "sprites/plasma1" ); + cgs.media.bloodTrailShader = trap_R_RegisterShader( "bloodTrail" ); + cgs.media.lagometerShader = trap_R_RegisterShader("lagometer" ); + cgs.media.connectionShader = trap_R_RegisterShader( "disconnected" ); + + cgs.media.waterBubbleShader = trap_R_RegisterShader( "waterBubble" ); + + cgs.media.tracerShader = trap_R_RegisterShader( "gfx/misc/tracer" ); + cgs.media.selectShader = trap_R_RegisterShader( "gfx/2d/select" ); + + for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { + cgs.media.crosshairShader[i] = trap_R_RegisterShader( va("gfx/2d/crosshair%c", 'a'+i) ); + } + + cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); + cgs.media.noammoShader = trap_R_RegisterShader( "icons/noammo" ); + + // powerup shaders + cgs.media.quadShader = trap_R_RegisterShader("powerups/quad" ); + cgs.media.quadWeaponShader = trap_R_RegisterShader("powerups/quadWeapon" ); + cgs.media.battleSuitShader = trap_R_RegisterShader("powerups/battleSuit" ); + cgs.media.battleWeaponShader = trap_R_RegisterShader("powerups/battleWeapon" ); + cgs.media.invisShader = trap_R_RegisterShader("powerups/invisibility" ); + cgs.media.regenShader = trap_R_RegisterShader("powerups/regen" ); + cgs.media.hastePuffShader = trap_R_RegisterShader("hasteSmokePuff" ); + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { +#else + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { +#endif + cgs.media.redCubeModel = trap_R_RegisterModel( "models/powerups/orb/r_orb.md3" ); + cgs.media.blueCubeModel = trap_R_RegisterModel( "models/powerups/orb/b_orb.md3" ); + cgs.media.redCubeIcon = trap_R_RegisterShader( "icons/skull_red" ); + cgs.media.blueCubeIcon = trap_R_RegisterShader( "icons/skull_blue" ); + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF || cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { +#else + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { +#endif + cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); + cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); + cgs.media.redFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_red1" ); + cgs.media.redFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); + cgs.media.redFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_red3" ); + cgs.media.blueFlagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_blu1" ); + cgs.media.blueFlagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); + cgs.media.blueFlagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu3" ); +#ifdef MISSIONPACK + cgs.media.flagPoleModel = trap_R_RegisterModel( "models/flag2/flagpole.md3" ); + cgs.media.flagFlapModel = trap_R_RegisterModel( "models/flag2/flagflap3.md3" ); + + cgs.media.redFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/red.skin" ); + cgs.media.blueFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/blue.skin" ); + cgs.media.neutralFlagFlapSkin = trap_R_RegisterSkin( "models/flag2/white.skin" ); + + cgs.media.redFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/red_base.md3" ); + cgs.media.blueFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/blue_base.md3" ); + cgs.media.neutralFlagBaseModel = trap_R_RegisterModel( "models/mapobjects/flagbase/ntrl_base.md3" ); +#endif + } + +#ifdef MISSIONPACK + if ( cgs.gametype == GT_1FCTF || cg_buildScript.integer ) { + cgs.media.neutralFlagModel = trap_R_RegisterModel( "models/flags/n_flag.md3" ); + cgs.media.flagShader[0] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral1" ); + cgs.media.flagShader[1] = trap_R_RegisterShaderNoMip( "icons/iconf_red2" ); + cgs.media.flagShader[2] = trap_R_RegisterShaderNoMip( "icons/iconf_blu2" ); + cgs.media.flagShader[3] = trap_R_RegisterShaderNoMip( "icons/iconf_neutral3" ); + } + + if ( cgs.gametype == GT_OBELISK || cg_buildScript.integer ) { + cgs.media.overloadBaseModel = trap_R_RegisterModel( "models/powerups/overload_base.md3" ); + cgs.media.overloadTargetModel = trap_R_RegisterModel( "models/powerups/overload_target.md3" ); + cgs.media.overloadLightsModel = trap_R_RegisterModel( "models/powerups/overload_lights.md3" ); + cgs.media.overloadEnergyModel = trap_R_RegisterModel( "models/powerups/overload_energy.md3" ); + } + + if ( cgs.gametype == GT_HARVESTER || cg_buildScript.integer ) { + cgs.media.harvesterModel = trap_R_RegisterModel( "models/powerups/harvester/harvester.md3" ); + cgs.media.harvesterRedSkin = trap_R_RegisterSkin( "models/powerups/harvester/red.skin" ); + cgs.media.harvesterBlueSkin = trap_R_RegisterSkin( "models/powerups/harvester/blue.skin" ); + cgs.media.harvesterNeutralModel = trap_R_RegisterModel( "models/powerups/obelisk/obelisk.md3" ); + } + + cgs.media.redKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikred" ); + cgs.media.dustPuffShader = trap_R_RegisterShader("hasteSmokePuff" ); +#endif + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + cgs.media.friendShader = trap_R_RegisterShader( "sprites/foe" ); + cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); + cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); +#ifdef MISSIONPACK + cgs.media.blueKamikazeShader = trap_R_RegisterShader( "models/weaphits/kamikblu" ); +#endif + } + + cgs.media.armorModel = trap_R_RegisterModel( "models/powerups/armor/armor_yel.md3" ); + cgs.media.armorIcon = trap_R_RegisterShaderNoMip( "icons/iconr_yellow" ); + + cgs.media.machinegunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/m_shell.md3" ); + cgs.media.shotgunBrassModel = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); + + cgs.media.gibAbdomen = trap_R_RegisterModel( "models/gibs/abdomen.md3" ); + cgs.media.gibArm = trap_R_RegisterModel( "models/gibs/arm.md3" ); + cgs.media.gibChest = trap_R_RegisterModel( "models/gibs/chest.md3" ); + cgs.media.gibFist = trap_R_RegisterModel( "models/gibs/fist.md3" ); + cgs.media.gibFoot = trap_R_RegisterModel( "models/gibs/foot.md3" ); + cgs.media.gibForearm = trap_R_RegisterModel( "models/gibs/forearm.md3" ); + cgs.media.gibIntestine = trap_R_RegisterModel( "models/gibs/intestine.md3" ); + cgs.media.gibLeg = trap_R_RegisterModel( "models/gibs/leg.md3" ); + cgs.media.gibSkull = trap_R_RegisterModel( "models/gibs/skull.md3" ); + cgs.media.gibBrain = trap_R_RegisterModel( "models/gibs/brain.md3" ); + + cgs.media.smoke2 = trap_R_RegisterModel( "models/weapons2/shells/s_shell.md3" ); + + cgs.media.balloonShader = trap_R_RegisterShader( "sprites/balloon3" ); + + cgs.media.bloodExplosionShader = trap_R_RegisterShader( "bloodExplosion" ); + + cgs.media.bulletFlashModel = trap_R_RegisterModel("models/weaphits/bullet.md3"); + cgs.media.ringFlashModel = trap_R_RegisterModel("models/weaphits/ring02.md3"); + cgs.media.dishFlashModel = trap_R_RegisterModel("models/weaphits/boom01.md3"); +#ifdef MISSIONPACK + cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/powerups/pop.md3" ); +#else + cgs.media.teleportEffectModel = trap_R_RegisterModel( "models/misc/telep.md3" ); + cgs.media.teleportEffectShader = trap_R_RegisterShader( "teleportEffect" ); +#endif +#ifdef MISSIONPACK + cgs.media.kamikazeEffectModel = trap_R_RegisterModel( "models/weaphits/kamboom2.md3" ); + cgs.media.kamikazeShockWave = trap_R_RegisterModel( "models/weaphits/kamwave.md3" ); + cgs.media.kamikazeHeadModel = trap_R_RegisterModel( "models/powerups/kamikazi.md3" ); + cgs.media.kamikazeHeadTrail = trap_R_RegisterModel( "models/powerups/trailtest.md3" ); + cgs.media.guardPowerupModel = trap_R_RegisterModel( "models/powerups/guard_player.md3" ); + cgs.media.scoutPowerupModel = trap_R_RegisterModel( "models/powerups/scout_player.md3" ); + cgs.media.doublerPowerupModel = trap_R_RegisterModel( "models/powerups/doubler_player.md3" ); + cgs.media.ammoRegenPowerupModel = trap_R_RegisterModel( "models/powerups/ammo_player.md3" ); + cgs.media.invulnerabilityImpactModel = trap_R_RegisterModel( "models/powerups/shield/impact.md3" ); + cgs.media.invulnerabilityJuicedModel = trap_R_RegisterModel( "models/powerups/shield/juicer.md3" ); + cgs.media.medkitUsageModel = trap_R_RegisterModel( "models/powerups/regen.md3" ); + cgs.media.heartShader = trap_R_RegisterShaderNoMip( "ui/assets/statusbar/selectedhealth.tga" ); + +#endif + + cgs.media.invulnerabilityPowerupModel = trap_R_RegisterModel( "models/powerups/shield/shield.md3" ); + cgs.media.medalImpressive = trap_R_RegisterShaderNoMip( "medal_impressive" ); + cgs.media.medalExcellent = trap_R_RegisterShaderNoMip( "medal_excellent" ); + cgs.media.medalGauntlet = trap_R_RegisterShaderNoMip( "medal_gauntlet" ); + cgs.media.medalDefend = trap_R_RegisterShaderNoMip( "medal_defend" ); + cgs.media.medalAssist = trap_R_RegisterShaderNoMip( "medal_assist" ); + cgs.media.medalCapture = trap_R_RegisterShaderNoMip( "medal_capture" ); + + //PKMOD - Ergodic 08/08/00 PAINKILLER awarded after every 10 PKitem kills + cgs.media.medalPainKiller = trap_R_RegisterShaderNoMip( "medal_painkiller" ); + + //PKMOD - Ergodic 08/22/00 ChainLightning Gun player hit shader effect + cgs.media.clgplayerhitShader = trap_R_RegisterShader("models/weaphits/clgplayerhit" ); + + //PKMOD - Ergodic 11/12/00 airfist flash model shader + cgs.media.airfistFlashShader = trap_R_RegisterShader("models/weapons2/airfist/airfist_flash" ); + + //PKMOD - Ergodic 11/16/00 update airfist flash model to correspond to airfist_level + cgs.media.airfist4FlashModel = trap_R_RegisterModel( "models/weapons2/airfist/airfist4_flash.md3" ); + cgs.media.airfist3FlashModel = trap_R_RegisterModel( "models/weapons2/airfist/airfist3_flash.md3" ); + cgs.media.airfist2FlashModel = trap_R_RegisterModel( "models/weapons2/airfist/airfist2_flash.md3" ); + cgs.media.airfist1FlashModel = trap_R_RegisterModel( "models/weapons2/airfist/airfist1_flash.md3" ); + cgs.media.airfist0FlashModel = trap_R_RegisterModel( "models/weapons2/airfist/airfist0_flash.md3" ); + + //PKMOD - Ergodic 12/27/00 add beans shader + cgs.media.pkafartPuffShader = trap_R_RegisterShader( "pkafartPuff" ); + + //PKMOD - Ergodic 12/28/00 add flash model for autosentry (same as machinegun) + cgs.media.autosentryFlashModel = trap_R_RegisterModel( "models/weapons2/machinegun/machinegun_flash.md3" ); + + //PKMOD - Ergodic 01/16/01 - add model for exploding shells weaponhit + cgs.media.explshellsFlashModel = trap_R_RegisterModel("models/weaphits/explboom/explboom.md3"); + + //PKMOD - Ergodic 01/16/01 - add multi-shaders for exploding shells + cgs.media.shellsExplosionShader1 = trap_R_RegisterShader("shellsexplosion1" ); + cgs.media.shellsExplosionShader2 = trap_R_RegisterShader("shellsexplosion2" ); + cgs.media.shellsExplosionShader3 = trap_R_RegisterShader("shellsexplosion3" ); + cgs.media.shellsExplosionShader4 = trap_R_RegisterShader("shellsexplosion4" ); + cgs.media.shellsExplosionShader5 = trap_R_RegisterShader("shellsexplosion5" ); + cgs.media.shellsExplosionShader6 = trap_R_RegisterShader("shellsexplosion6" ); + + //PKMOD - Ergodic 01/21/01 - add coordinate model for exploding shells debug + cgs.media.coordFlashModel = trap_R_RegisterModel("models/weapons2/explgun/coord.md3"); + + //PKMOD - Ergodic 04/06/01 - add autosentry missile sprite + cgs.media.autosentryBallShader = trap_R_RegisterShader( "sprites/autosentry/missile1" ); + + //PKMOD - Ergodic 10/14/01 - add shaders for radiation holdable spark effect + cgs.media.radiate1Shader = trap_R_RegisterShader( "radiate1Spark" ); + cgs.media.radiate2Shader = trap_R_RegisterShader( "radiate2Spark" ); + cgs.media.radiate3Shader = trap_R_RegisterShader( "radiate3Spark" ); + cgs.media.radiate4Shader = trap_R_RegisterShader( "radiate4Spark" ); + cgs.media.radiate5Shader = trap_R_RegisterShader( "radiate5Spark" ); + cgs.media.radiate6Shader = trap_R_RegisterShader( "radiate6Spark" ); + + //PKMOD - Ergodic 11/17/01 - add icons for simple items that are radiatedt + cgs.media.radiate1SimpleIcon = trap_R_RegisterShader( "radiate1SimpleIcon" ); + cgs.media.radiate2SimpleIcon = trap_R_RegisterShader( "radiate2SimpleIcon" ); + cgs.media.radiate3SimpleIcon = trap_R_RegisterShader( "radiate3SimpleIcon" ); + + //PKMOD - Ergodic 11/27/01 - add radition trail for infected players + cgs.media.radiationTrailShader = trap_R_RegisterShader( "radiationTrail" ); + + //PKMOD - Ergodic 05/07/02 - add active personal sentry model + cgs.media.persentry_active = trap_R_RegisterModel("models/powerups/holdable/persentry_active.md3"); + + //PKMOD - Ergodic 06/08/02 - add personal sentry teleport model + cgs.media.persentry_teleportEffectModel = trap_R_RegisterModel( "models/powerups/holdable/persentry_tele.md3" ); + + //PKMOD - Ergodic 06/12/02 - add personal sentry missile shader + cgs.media.personalsentryBallShader = trap_R_RegisterShader( "sprites/personalsentry/missile1" ); + + //PKMOD - Ergodic 09/11/02 - add private bot field effect + cgs.media.privatebot_CueModel = trap_R_RegisterModel( "models/powerups/holdable/pbcue.md3" ); + + + memset( cg_items, 0, sizeof( cg_items ) ); + memset( cg_weapons, 0, sizeof( cg_weapons ) ); + + // only register the items that the server says we need + strcpy( items, CG_ConfigString( CS_ITEMS) ); + + for ( i = 1 ; i < bg_numItems ; i++ ) { + if ( items[ i ] == '1' || cg_buildScript.integer ) { + CG_LoadingItem( i ); + CG_RegisterItemVisuals( i ); + } + } + + //PKMOD - Ergodic 05/11/01 - register holdables into their own array + // for optimization purposes + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( bg_itemlist[i].giType == IT_HOLDABLE ) { + if ( bg_itemlist[i].giTag < HI_NUM_HOLDABLE ) { + cg_holdable[ bg_itemlist[i].giTag ] = i; + } + else { + Com_Error( ERR_DROP, "HOLDABLE array index has been exceeded: %d at offset %d\n", bg_itemlist[i].giTag, i); + } + } + } + + // wall marks + cgs.media.bulletMarkShader = trap_R_RegisterShader( "gfx/damage/bullet_mrk" ); + cgs.media.burnMarkShader = trap_R_RegisterShader( "gfx/damage/burn_med_mrk" ); + cgs.media.holeMarkShader = trap_R_RegisterShader( "gfx/damage/hole_lg_mrk" ); + cgs.media.energyMarkShader = trap_R_RegisterShader( "gfx/damage/plasma_mrk" ); + cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); + cgs.media.bloodMarkShader = trap_R_RegisterShader( "bloodMark" ); + + // register the inline models + cgs.numInlineModels = trap_CM_NumInlineModels(); + for ( i = 1 ; i < cgs.numInlineModels ; i++ ) { + char name[10]; + vec3_t mins, maxs; + int j; + + Com_sprintf( name, sizeof(name), "*%i", i ); + cgs.inlineDrawModel[i] = trap_R_RegisterModel( name ); + trap_R_ModelBounds( cgs.inlineDrawModel[i], mins, maxs ); + for ( j = 0 ; j < 3 ; j++ ) { + cgs.inlineModelMidpoints[i][j] = mins[j] + 0.5 * ( maxs[j] - mins[j] ); + } + } + + // register all the server specified models + for (i=1 ; i= MAX_CONFIGSTRINGS ) { + CG_Error( "CG_ConfigString: bad index: %i", index ); + } + return cgs.gameState.stringData + cgs.gameState.stringOffsets[ index ]; +} + +//================================================================== + +//PKMOD - Ergodic 10/13/00 - add alternate music to hub +/* +====================== +CG_StartPostVoteMusic + +====================== +*/ +void CG_StartPostVoteMusic( const char *postvotemusic ) { + char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + //PKMOD - Ergodic 10/14/00 - debug inactive +// Com_Printf( "CG_StartPostVoteMusic - initiating\n" ); + + + //PKMOD - Ergodic 10/14/00 - only do alternate music if player has voted +// if ( cg.snap->ps.persistant[PERS_HUB_FLAG] != 1 ) +// return; + //PKMOD - Ergodic 12/16/00 - change PERS_HUB_FLAG to be first bit of PERS_PAINKILLER_COUNT + if ( !( cg.snap->ps.persistant[PERS_PAINKILLER_COUNT] & 1 ) ) { + return; + } + + + //PKMOD - Ergodic 10/14/00 - debug inactive +// Com_Printf( "CG_StartPostVoteMusic - setting music\n" ); + + // start the background music +// s = (char *)CG_ConfigString( CS_POSTVOTE_MUSIC ); +// s = (char *)CG_ConfigString( CS_MUSIC ); + + s = (char *)postvotemusic; + + //PKMOD - Ergodic 10/14/00 - debug inactive +// Com_Printf( "CG_StartPostVoteMusic - setting music>%s<\n", s ); + + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2 ); +} + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( void ) { + char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( &s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( &s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2 ); +} +//PKMOD - Ergodic 01/17/04 - enable HUD code in PKA 3.0 +//#ifdef MISSIONPACK +char *CG_GetMenuBuffer(const char *filename) { + int len; + fileHandle_t f; + static char buf[MAX_MENUFILE]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", filename ) ); + return NULL; + } + if ( len >= MAX_MENUFILE ) { + trap_Print( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", filename, len, MAX_MENUFILE ) ); + trap_FS_FCloseFile( f ); + return NULL; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + return buf; +} + +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +qboolean CG_Asset_Parse(int handle) { + pc_token_t token; + const char *tempStr; + + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + if (Q_stricmp(token.string, "{") != 0) { + return qfalse; + } + + while ( 1 ) { + if (!trap_PC_ReadToken(handle, &token)) + return qfalse; + + if (Q_stricmp(token.string, "}") == 0) { + return qtrue; + } + + // font + if (Q_stricmp(token.string, "font") == 0) { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.textFont); + continue; + } + + // smallFont + if (Q_stricmp(token.string, "smallFont") == 0) { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.smallFont); + continue; + } + + // font + if (Q_stricmp(token.string, "bigfont") == 0) { + int pointSize; + if (!PC_String_Parse(handle, &tempStr) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + cgDC.registerFont(tempStr, pointSize, &cgDC.Assets.bigFont); + continue; + } + + // gradientbar + if (Q_stricmp(token.string, "gradientbar") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(tempStr); + continue; + } + + // enterMenuSound + if (Q_stricmp(token.string, "menuEnterSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.menuEnterSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // exitMenuSound + if (Q_stricmp(token.string, "menuExitSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.menuExitSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // itemFocusSound + if (Q_stricmp(token.string, "itemFocusSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.itemFocusSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + // menuBuzzSound + if (Q_stricmp(token.string, "menuBuzzSound") == 0) { + if (!PC_String_Parse(handle, &tempStr)) { + return qfalse; + } + cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( tempStr, qfalse ); + continue; + } + + if (Q_stricmp(token.string, "cursor") == 0) { + if (!PC_String_Parse(handle, &cgDC.Assets.cursorStr)) { + return qfalse; + } + cgDC.Assets.cursor = trap_R_RegisterShaderNoMip( cgDC.Assets.cursorStr); + continue; + } + + if (Q_stricmp(token.string, "fadeClamp") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeClamp)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeCycle") == 0) { + if (!PC_Int_Parse(handle, &cgDC.Assets.fadeCycle)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "fadeAmount") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.fadeAmount)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowX") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowX)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowY") == 0) { + if (!PC_Float_Parse(handle, &cgDC.Assets.shadowY)) { + return qfalse; + } + continue; + } + + if (Q_stricmp(token.string, "shadowColor") == 0) { + if (!PC_Color_Parse(handle, &cgDC.Assets.shadowColor)) { + return qfalse; + } + cgDC.Assets.shadowFadeClamp = cgDC.Assets.shadowColor[3]; + continue; + } + } + return qfalse; // bk001204 - why not? +} + +void CG_ParseMenu(const char *menuFile) { + pc_token_t token; + int handle; + + handle = trap_PC_LoadSource(menuFile); + if (!handle) + handle = trap_PC_LoadSource("ui/testhud.menu"); + if (!handle) + return; + + while ( 1 ) { + if (!trap_PC_ReadToken( handle, &token )) { + break; + } + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if ( token.string[0] == '}' ) { + break; + } + + if (Q_stricmp(token.string, "assetGlobalDef") == 0) { + if (CG_Asset_Parse(handle)) { + continue; + } else { + break; + } + } + + + if (Q_stricmp(token.string, "menudef") == 0) { + // start a new menu + Menu_New(handle); + } + } + trap_PC_FreeSource(handle); +} + +qboolean CG_Load_Menu(char **p) { + char *token; + + token = COM_ParseExt(p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + while ( 1 ) { + + token = COM_ParseExt(p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + CG_ParseMenu(token); + } + return qfalse; +} + + + +void CG_LoadMenus(const char *menuFile) { + char *token; + char *p; + int len, start; + fileHandle_t f; + static char buf[MAX_MENUDEFFILE]; + + start = trap_Milliseconds(); + + len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + if ( !f ) { + trap_Error( va( S_COLOR_YELLOW "menu file not found: %s, using default\n", menuFile ) ); + len = trap_FS_FOpenFile( "ui/hud.txt", &f, FS_READ ); + if (!f) { + trap_Error( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); + } + } + + if ( len >= MAX_MENUDEFFILE ) { + trap_Error( va( S_COLOR_RED "menu file too large: %s is %i, max allowed is %i", menuFile, len, MAX_MENUDEFFILE ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + COM_Compress(buf); + + Menu_Reset(); + + p = buf; + + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if( !token || token[0] == 0 || token[0] == '}') { + break; + } + + //if ( Q_stricmp( token, "{" ) ) { + // Com_Printf( "Missing { in menu file\n" ); + // break; + //} + + //if ( menuCount == MAX_MENUS ) { + // Com_Printf( "Too many menus!\n" ); + // break; + //} + + if ( Q_stricmp( token, "}" ) == 0 ) { + break; + } + + if (Q_stricmp(token, "loadmenu") == 0) { + if (CG_Load_Menu(&p)) { + continue; + } else { + break; + } + } + } + + Com_Printf("UI menu load time = %d milli seconds\n", trap_Milliseconds() - start); + +} + + + +static qboolean CG_OwnerDrawHandleKey(int ownerDraw, int flags, float *special, int key) { + return qfalse; +} + + +static int CG_FeederCount(float feederID) { + int i, count; + count = 0; + if (feederID == FEEDER_REDTEAM_LIST) { + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == TEAM_RED) { + count++; + } + } + } else if (feederID == FEEDER_BLUETEAM_LIST) { + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == TEAM_BLUE) { + count++; + } + } + } else if (feederID == FEEDER_SCOREBOARD) { + return cg.numScores; + } + return count; +} + + +void CG_SetScoreSelection(void *p) { + menuDef_t *menu = (menuDef_t*)p; + playerState_t *ps = &cg.snap->ps; + int i, red, blue; + red = blue = 0; + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == TEAM_RED) { + red++; + } else if (cg.scores[i].team == TEAM_BLUE) { + blue++; + } + if (ps->clientNum == cg.scores[i].client) { + cg.selectedScore = i; + } + } + + if (menu == NULL) { + // just interested in setting the selected score + return; + } + + if ( cgs.gametype >= GT_TEAM ) { + int feeder = FEEDER_REDTEAM_LIST; + i = red; + if (cg.scores[cg.selectedScore].team == TEAM_BLUE) { + feeder = FEEDER_BLUETEAM_LIST; + i = blue; + } + Menu_SetFeederSelection(menu, feeder, i, NULL); + } else { + Menu_SetFeederSelection(menu, FEEDER_SCOREBOARD, cg.selectedScore, NULL); + } +} + +// FIXME: might need to cache this info +static clientInfo_t * CG_InfoFromScoreIndex(int index, int team, int *scoreIndex) { + int i, count; + if ( cgs.gametype >= GT_TEAM ) { + count = 0; + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == team) { + if (count == index) { + *scoreIndex = i; + return &cgs.clientinfo[cg.scores[i].client]; + } + count++; + } + } + } + *scoreIndex = index; + return &cgs.clientinfo[ cg.scores[index].client ]; +} + +static const char *CG_FeederItemText(float feederID, int index, int column, qhandle_t *handle) { + gitem_t *item; + int scoreIndex = 0; + clientInfo_t *info = NULL; + int team = -1; + score_t *sp = NULL; + + *handle = -1; + + if (feederID == FEEDER_REDTEAM_LIST) { + team = TEAM_RED; + } else if (feederID == FEEDER_BLUETEAM_LIST) { + team = TEAM_BLUE; + } + + info = CG_InfoFromScoreIndex(index, team, &scoreIndex); + sp = &cg.scores[scoreIndex]; + + if (info && info->infoValid) { + switch (column) { + case 0: + if ( info->powerups & ( 1 << PW_NEUTRALFLAG ) ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + *handle = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_REDFLAG ) ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + *handle = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_BLUEFLAG ) ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + *handle = cg_items[ ITEM_INDEX(item) ].icon; + } else { + if ( info->botSkill > 0 && info->botSkill <= 5 ) { + *handle = cgs.media.botSkillShaders[ info->botSkill - 1 ]; + } else if ( info->handicap < 100 ) { + return va("%i", info->handicap ); + } + } + break; + case 1: + if (team == -1) { + return ""; + } else { + *handle = CG_StatusHandle(info->teamTask); + } + break; + case 2: + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << sp->client ) ) { + return "Ready"; + } + if (team == -1) { + if (cgs.gametype == GT_TOURNAMENT) { + return va("%i/%i", info->wins, info->losses); + } else if (info->infoValid && info->team == TEAM_SPECTATOR ) { + return "Spectator"; + } else { + return ""; + } + } else { + if (info->teamLeader) { + return "Leader"; + } + } + break; + case 3: + return info->name; + break; + case 4: + return va("%i", info->score); + break; + case 5: + return va("%4i", sp->time); + break; + case 6: + if ( sp->ping == -1 ) { + return "connecting"; + } + return va("%4i", sp->ping); + break; + } + } + + return ""; +} + +static qhandle_t CG_FeederItemImage(float feederID, int index) { + return 0; +} + +static void CG_FeederSelection(float feederID, int index) { + if ( cgs.gametype >= GT_TEAM ) { + int i, count; + int team = (feederID == FEEDER_REDTEAM_LIST) ? TEAM_RED : TEAM_BLUE; + count = 0; + for (i = 0; i < cg.numScores; i++) { + if (cg.scores[i].team == team) { + if (index == count) { + cg.selectedScore = i; + } + count++; + } + } + } else { + cg.selectedScore = index; + } +} +//PKMOD - Ergodic 01/17/04 - enable HUD code in PKA 3.0 +//#endif + +//PKMOD - Ergodic 01/17/04 - Enable HUD in PKA3.0 +//#ifdef MISSIONPACK // bk001204 - only needed there +static float CG_Cvar_Get(const char *cvar) { + char buff[128]; + memset(buff, 0, sizeof(buff)); + trap_Cvar_VariableStringBuffer(cvar, buff, sizeof(buff)); + return atof(buff); +} +//#endif + +//PKMOD - Ergodic 01/17/04 - Enable the mission pack code so that HUD code will work +//#ifdef MISSIONPACK +void CG_Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style) { + CG_Text_Paint(x, y, scale, color, text, 0, limit, style); +} + +static int CG_OwnerDrawWidth(int ownerDraw, float scale) { + switch (ownerDraw) { + case CG_GAME_TYPE: + return CG_Text_Width(CG_GameTypeString(), scale, 0); + case CG_GAME_STATUS: + return CG_Text_Width(CG_GetGameStatusText(), scale, 0); + break; + case CG_KILLER: + return CG_Text_Width(CG_GetKillerText(), scale, 0); + break; + case CG_RED_NAME: + return CG_Text_Width(cg_redTeamName.string, scale, 0); + break; + case CG_BLUE_NAME: + return CG_Text_Width(cg_blueTeamName.string, scale, 0); + break; + + + } + return 0; +} + +static int CG_PlayCinematic(const char *name, float x, float y, float w, float h) { + return trap_CIN_PlayCinematic(name, x, y, w, h, CIN_loop); +} + +static void CG_StopCinematic(int handle) { + trap_CIN_StopCinematic(handle); +} + +static void CG_DrawCinematic(int handle, float x, float y, float w, float h) { + trap_CIN_SetExtents(handle, x, y, w, h); + trap_CIN_DrawCinematic(handle); +} + +static void CG_RunCinematicFrame(int handle) { + trap_CIN_RunCinematic(handle); +} + +/* +================= +CG_LoadHudMenu(); + +================= +*/ +void CG_LoadHudMenu() { + char buff[1024]; + const char *hudSet; + + cgDC.registerShaderNoMip = &trap_R_RegisterShaderNoMip; + cgDC.setColor = &trap_R_SetColor; + cgDC.drawHandlePic = &CG_DrawPic; + cgDC.drawStretchPic = &trap_R_DrawStretchPic; + cgDC.drawText = &CG_Text_Paint; + cgDC.textWidth = &CG_Text_Width; + cgDC.textHeight = &CG_Text_Height; + cgDC.registerModel = &trap_R_RegisterModel; + cgDC.modelBounds = &trap_R_ModelBounds; + cgDC.fillRect = &CG_FillRect; + cgDC.drawRect = &CG_DrawRect; + cgDC.drawSides = &CG_DrawSides; + cgDC.drawTopBottom = &CG_DrawTopBottom; + cgDC.clearScene = &trap_R_ClearScene; + cgDC.addRefEntityToScene = &trap_R_AddRefEntityToScene; + cgDC.renderScene = &trap_R_RenderScene; + cgDC.registerFont = &trap_R_RegisterFont; + cgDC.ownerDrawItem = &CG_OwnerDraw; + cgDC.getValue = &CG_GetValue; + cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; + cgDC.runScript = &CG_RunMenuScript; + cgDC.getTeamColor = &CG_GetTeamColor; + cgDC.setCVar = trap_Cvar_Set; + cgDC.getCVarString = trap_Cvar_VariableStringBuffer; + cgDC.getCVarValue = CG_Cvar_Get; + cgDC.drawTextWithCursor = &CG_Text_PaintWithCursor; + //cgDC.setOverstrikeMode = &trap_Key_SetOverstrikeMode; + //cgDC.getOverstrikeMode = &trap_Key_GetOverstrikeMode; + cgDC.startLocalSound = &trap_S_StartLocalSound; + cgDC.ownerDrawHandleKey = &CG_OwnerDrawHandleKey; + cgDC.feederCount = &CG_FeederCount; + cgDC.feederItemImage = &CG_FeederItemImage; + cgDC.feederItemText = &CG_FeederItemText; + cgDC.feederSelection = &CG_FeederSelection; + //cgDC.setBinding = &trap_Key_SetBinding; + //cgDC.getBindingBuf = &trap_Key_GetBindingBuf; + //cgDC.keynumToStringBuf = &trap_Key_KeynumToStringBuf; + //cgDC.executeText = &trap_Cmd_ExecuteText; + cgDC.Error = &Com_Error; + cgDC.Print = &Com_Printf; + cgDC.ownerDrawWidth = &CG_OwnerDrawWidth; + //cgDC.Pause = &CG_Pause; + cgDC.registerSound = &trap_S_RegisterSound; + cgDC.startBackgroundTrack = &trap_S_StartBackgroundTrack; + cgDC.stopBackgroundTrack = &trap_S_StopBackgroundTrack; + cgDC.playCinematic = &CG_PlayCinematic; + cgDC.stopCinematic = &CG_StopCinematic; + cgDC.drawCinematic = &CG_DrawCinematic; + cgDC.runCinematicFrame = &CG_RunCinematicFrame; + + Init_Display(&cgDC); + + Menu_Reset(); + + trap_Cvar_VariableStringBuffer("cg_hudFiles", buff, sizeof(buff)); + hudSet = buff; + if (hudSet[0] == '\0') { + hudSet = "ui/hud.txt"; + } + + CG_LoadMenus(hudSet); +} + +void CG_AssetCache() { + //if (Assets.textFont == NULL) { + // trap_R_RegisterFont("fonts/arial.ttf", 72, &Assets.textFont); + //} + //Assets.background = trap_R_RegisterShaderNoMip( ASSET_BACKGROUND ); + //Com_Printf("Menu Size: %i bytes\n", sizeof(Menus)); + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip( ASSET_GRADIENTBAR ); + cgDC.Assets.fxBasePic = trap_R_RegisterShaderNoMip( ART_FX_BASE ); + cgDC.Assets.fxPic[0] = trap_R_RegisterShaderNoMip( ART_FX_RED ); + cgDC.Assets.fxPic[1] = trap_R_RegisterShaderNoMip( ART_FX_YELLOW ); + cgDC.Assets.fxPic[2] = trap_R_RegisterShaderNoMip( ART_FX_GREEN ); + cgDC.Assets.fxPic[3] = trap_R_RegisterShaderNoMip( ART_FX_TEAL ); + cgDC.Assets.fxPic[4] = trap_R_RegisterShaderNoMip( ART_FX_BLUE ); + cgDC.Assets.fxPic[5] = trap_R_RegisterShaderNoMip( ART_FX_CYAN ); + cgDC.Assets.fxPic[6] = trap_R_RegisterShaderNoMip( ART_FX_WHITE ); + cgDC.Assets.scrollBar = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR ); + cgDC.Assets.scrollBarArrowDown = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWDOWN ); + cgDC.Assets.scrollBarArrowUp = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWUP ); + cgDC.Assets.scrollBarArrowLeft = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWLEFT ); + cgDC.Assets.scrollBarArrowRight = trap_R_RegisterShaderNoMip( ASSET_SCROLLBAR_ARROWRIGHT ); + cgDC.Assets.scrollBarThumb = trap_R_RegisterShaderNoMip( ASSET_SCROLL_THUMB ); + cgDC.Assets.sliderBar = trap_R_RegisterShaderNoMip( ASSET_SLIDER_BAR ); + cgDC.Assets.sliderThumb = trap_R_RegisterShaderNoMip( ASSET_SLIDER_THUMB ); +} + +//PKMOD - Ergodic 01/17/04 - Enable the mission pack code so that HUD code will work +//#endif + +/* +================= +CG_Init + +Called after every level change or subsystem restart +Will perform callbacks to make the loading info screen update. +================= +*/ +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) { + const char *s; + int version_test; //PKMOD - Ergodic 08/19/02 - Hold PKA version tests + + // clear everything + memset( &cgs, 0, sizeof( cgs ) ); + memset( &cg, 0, sizeof( cg ) ); + memset( cg_entities, 0, sizeof(cg_entities) ); + memset( cg_weapons, 0, sizeof(cg_weapons) ); + memset( cg_items, 0, sizeof(cg_items) ); + + cg.clientNum = clientNum; + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + // load a few needed things before we do any screen updates + cgs.media.charsetShader = trap_R_RegisterShader( "gfx/2d/bigchars" ); + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + cgs.media.charsetProp = trap_R_RegisterShaderNoMip( "menu/art/font1_prop.tga" ); + cgs.media.charsetPropGlow = trap_R_RegisterShaderNoMip( "menu/art/font1_prop_glo.tga" ); + cgs.media.charsetPropB = trap_R_RegisterShaderNoMip( "menu/art/font2_prop.tga" ); + + CG_RegisterCvars(); + + CG_InitConsoleCommands(); + + cg.weaponSelect = WP_MACHINEGUN; + + cgs.redflag = cgs.blueflag = -1; // For compatibily, default to unset for + cgs.flagStatus = -1; + // old servers + + // get the rendering configuration from the client system + trap_GetGlconfig( &cgs.glconfig ); + cgs.screenXScale = cgs.glconfig.vidWidth / 640.0; + cgs.screenYScale = cgs.glconfig.vidHeight / 480.0; + + // get the gamestate from the client system + trap_GetGameState( &cgs.gameState ); + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + //PKMOD - Ergodic 08/19/02 - give greater information when version tests fail + //< 0 string1 less than string2 + //= 0 string1 identical to string2 + //> 0 string1 greater than string2 + + + version_test = strcmp( s, GAME_VERSION ); + + if ( version_test ) { + if ( version_test < 0) { + //PKMOD - Ergodic 08/19/02 - set the CVAR + //Server version is less than Client Version + trap_Cvar_Set( "cl_pkaerror", va("QUAKE3 Client(%s)/Server(%s) game version mismatch!\n Client at a Higher Version than Server.\n Please try another PKA server", GAME_VERSION, s ) ); + CG_Error( "QUAKE3 Client(%s)/Server(%s) game version mismatch!\n Client at a Higher Version than Server.\n Please try another PKA server", GAME_VERSION, s ); + } + else { + //PKMOD - Ergodic 08/19/02 - set the CVAR + //Client version is less than Server Version + trap_Cvar_Set( "cl_pkaerror", va("QUAKE3 Client(%s)/Server(%s) game version mismatch!\n Server at a Higher Version than Client.\n Please upgrade at www.idsoftware.com", GAME_VERSION, s ) ); + CG_Error( "QUAKE3 Client(%s)/Server(%s) game mismatch!\n Server at a Higher Version than Client.\n Please upgrade at www.idsoftware.com", GAME_VERSION, s ); + } + } + + //PKMOD - Ergodic 02/01/01 - check the PKARENA game version + s = CG_ConfigString( CS_PKARENA_VERSION ); + + //PKMOD - Ergodic 08/19/02 - give greater information when version tests fail + version_test = strcmp( s, PKARENA_VERSION ); + if ( version_test ) { + if ( version_test < 0) { + //PKMOD - Ergodic 08/19/02 - set the CVAR + //Server version is less than Client Version + trap_Cvar_Set( "cl_pkaerror", va("PKARENA Client(%s)/Server(%s) game version mismatch!\n Client at a Higher Version than Server.\n Please try another PKA server", PKARENA_VERSION, s ) ); + CG_Error( "PKARENA Client(%s)/Server(%s) game version mismatch!\n Client at a Higher Version than Server.\n Please try another PKA server", PKARENA_VERSION, s ); + } + else { + //PKMOD - Ergodic 08/19/02 - set the CVAR + //Client version is less than Server Version + trap_Cvar_Set( "cl_pkaerror", va("PKARENA Client(%s)/Server(%s) game version mismatch!\n Server at a Higher Version than Client.\n Please upgrade at www.team-evolve.com", PKARENA_VERSION, s ) ); + CG_Error( "PKARENA Client(%s)/Server(%s) game version mismatch!\n Server at a Higher Version than Client.\n Please upgrade at www.team-evolve.com", PKARENA_VERSION, s ); + } + } + else { + //PKMOD - Ergodic 08/17/02 - clear the error CVAR + trap_Cvar_Set( "cl_pkaerror", "0" ); + } + + s = CG_ConfigString( CS_LEVEL_START_TIME ); + cgs.levelStartTime = atoi( s ); + + CG_ParseServerinfo(); + + // load the new map + CG_LoadingString( "collision map" ); + + trap_CM_LoadMap( cgs.mapname ); + +//PKMOD - Ergodic 01/31/03 - Activate HUD for PKA3.0 +//#ifdef MISSIONPACK + String_Init(); +//#endif + + cg.loading = qtrue; // force players to load instead of defer + + CG_LoadingString( "sounds" ); + + CG_RegisterSounds(); + + CG_LoadingString( "graphics" ); + + CG_RegisterGraphics(); + + CG_LoadingString( "clients" ); + + CG_RegisterClients(); // if low on memory, some clients will be deferred + +//PKMOD - Ergodic 03/12/04 - remove this to enable scollbars in scoreboard +//#ifdef MISSIONPACK + CG_AssetCache(); +//PKMOD - Ergodic 03/12/04 - remove this to enable scollbars in scoreboard +//#endif + //PKMOD - Ergodic 01/17/03 - Activate HUD for PKA3.0 + CG_LoadHudMenu(); // load new hud stuff + + cg.loading = qfalse; // future players will be deferred + + CG_InitLocalEntities(); + + CG_InitMarkPolys(); + + // remove the last loading update + cg.infoScreenText[0] = 0; + + // Make sure we have update values (scores) + CG_SetConfigValues(); + + CG_StartMusic(); + + CG_LoadingString( "" ); + +#ifdef MISSIONPACK + CG_InitTeamChat(); +#endif + + CG_ShaderStateChanged(); + + trap_S_ClearLoopingSounds( qtrue ); +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) { + // some mods may need to do cleanup work here, + // like closing files or archiving session data +} + + +/* +================== +CG_EventHandling +================== + type 0 - no event handling + 1 - team menu + 2 - hud editor + +*/ +//PKMOD - Ergodic 01/30/04 - Activate HUD - These functions will use TA definitions found in UI module + +/* PKMOD++++++++++ +#ifndef MISSIONPACK +void CG_EventHandling(int type) { +} + + + +void CG_KeyEvent(int key, qboolean down) { +} + +void CG_MouseEvent(int x, int y) { +} +#endif +*/ //PKMOD--------- \ No newline at end of file diff --git a/quake3/source/code/cgame/cg_marks.c b/quake3/source/code/cgame/cg_marks.c new file mode 100644 index 0000000..c1af668 --- /dev/null +++ b/quake3/source/code/cgame/cg_marks.c @@ -0,0 +1,2381 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_marks.c -- wall marks + +#include "cg_local.h" + +/* +=================================================================== + +MARK POLYS + +=================================================================== +*/ + + +markPoly_t cg_activeMarkPolys; // double linked list +markPoly_t *cg_freeMarkPolys; // single linked list +markPoly_t cg_markPolys[MAX_MARK_POLYS]; +static int markTotal; + +/* +=================== +CG_InitMarkPolys + +This is called at startup and for tournement restarts +=================== +*/ +void CG_InitMarkPolys( void ) { + int i; + + memset( cg_markPolys, 0, sizeof(cg_markPolys) ); + + cg_activeMarkPolys.nextMark = &cg_activeMarkPolys; + cg_activeMarkPolys.prevMark = &cg_activeMarkPolys; + cg_freeMarkPolys = cg_markPolys; + for ( i = 0 ; i < MAX_MARK_POLYS - 1 ; i++ ) { + cg_markPolys[i].nextMark = &cg_markPolys[i+1]; + } +} + + +/* +================== +CG_FreeMarkPoly +================== +*/ +void CG_FreeMarkPoly( markPoly_t *le ) { + if ( !le->prevMark ) { + CG_Error( "CG_FreeLocalEntity: not active" ); + } + + // remove from the doubly linked active list + le->prevMark->nextMark = le->nextMark; + le->nextMark->prevMark = le->prevMark; + + // the free list is only singly linked + le->nextMark = cg_freeMarkPolys; + cg_freeMarkPolys = le; +} + +/* +=================== +CG_AllocMark + +Will allways succeed, even if it requires freeing an old active mark +=================== +*/ +markPoly_t *CG_AllocMark( void ) { + markPoly_t *le; + int time; + + if ( !cg_freeMarkPolys ) { + // no free entities, so free the one at the end of the chain + // remove the oldest active entity + time = cg_activeMarkPolys.prevMark->time; + while (cg_activeMarkPolys.prevMark && time == cg_activeMarkPolys.prevMark->time) { + CG_FreeMarkPoly( cg_activeMarkPolys.prevMark ); + } + } + + le = cg_freeMarkPolys; + cg_freeMarkPolys = cg_freeMarkPolys->nextMark; + + memset( le, 0, sizeof( *le ) ); + + // link into the active list + le->nextMark = cg_activeMarkPolys.nextMark; + le->prevMark = &cg_activeMarkPolys; + cg_activeMarkPolys.nextMark->prevMark = le; + cg_activeMarkPolys.nextMark = le; + return le; +} + + + +/* +================= +CG_ImpactMark + +origin should be a point within a unit of the plane +dir should be the plane normal + +temporary marks will not be stored or randomly oriented, but immediately +passed to the renderer. +================= +*/ +#define MAX_MARK_FRAGMENTS 128 +#define MAX_MARK_POINTS 384 + +void CG_ImpactMark( qhandle_t markShader, const vec3_t origin, const vec3_t dir, + float orientation, float red, float green, float blue, float alpha, + qboolean alphaFade, float radius, qboolean temporary ) { + vec3_t axis[3]; + float texCoordScale; + vec3_t originalPoints[4]; + byte colors[4]; + int i, j; + int numFragments; + markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; + vec3_t markPoints[MAX_MARK_POINTS]; + vec3_t projection; + + if ( !cg_addMarks.integer ) { + return; + } + + if ( radius <= 0 ) { + CG_Error( "CG_ImpactMark called with <= 0 radius" ); + } + + //if ( markTotal >= MAX_MARK_POLYS ) { + // return; + //} + + // create the texture axis + VectorNormalize2( dir, axis[0] ); + PerpendicularVector( axis[1], axis[0] ); + RotatePointAroundVector( axis[2], axis[0], axis[1], orientation ); + CrossProduct( axis[0], axis[2], axis[1] ); + + texCoordScale = 0.5 * 1.0 / radius; + + // create the full polygon + for ( i = 0 ; i < 3 ; i++ ) { + originalPoints[0][i] = origin[i] - radius * axis[1][i] - radius * axis[2][i]; + originalPoints[1][i] = origin[i] + radius * axis[1][i] - radius * axis[2][i]; + originalPoints[2][i] = origin[i] + radius * axis[1][i] + radius * axis[2][i]; + originalPoints[3][i] = origin[i] - radius * axis[1][i] + radius * axis[2][i]; + } + + // get the fragments + VectorScale( dir, -20, projection ); + numFragments = trap_CM_MarkFragments( 4, (void *)originalPoints, + projection, MAX_MARK_POINTS, markPoints[0], + MAX_MARK_FRAGMENTS, markFragments ); + + colors[0] = red * 255; + colors[1] = green * 255; + colors[2] = blue * 255; + colors[3] = alpha * 255; + + for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) { + polyVert_t *v; + polyVert_t verts[MAX_VERTS_ON_POLY]; + markPoly_t *mark; + + // we have an upper limit on the complexity of polygons + // that we store persistantly + if ( mf->numPoints > MAX_VERTS_ON_POLY ) { + mf->numPoints = MAX_VERTS_ON_POLY; + } + for ( j = 0, v = verts ; j < mf->numPoints ; j++, v++ ) { + vec3_t delta; + + VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); + + VectorSubtract( v->xyz, origin, delta ); + v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * texCoordScale; + v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * texCoordScale; + *(int *)v->modulate = *(int *)colors; + } + + // if it is a temporary (shadow) mark, add it immediately and forget about it + if ( temporary ) { + trap_R_AddPolyToScene( markShader, mf->numPoints, verts ); + continue; + } + + // otherwise save it persistantly + mark = CG_AllocMark(); + mark->time = cg.time; + mark->alphaFade = alphaFade; + mark->markShader = markShader; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = red; + mark->color[1] = green; + mark->color[2] = blue; + mark->color[3] = alpha; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + markTotal++; + } +} + + +/* +=============== +CG_AddMarks +=============== +*/ +#define MARK_TOTAL_TIME 10000 +#define MARK_FADE_TIME 1000 + +void CG_AddMarks( void ) { + int j; + markPoly_t *mp, *next; + int t; + int fade; + + if ( !cg_addMarks.integer ) { + return; + } + + mp = cg_activeMarkPolys.nextMark; + for ( ; mp != &cg_activeMarkPolys ; mp = next ) { + // grab next now, so if the local entity is freed we + // still have it + next = mp->nextMark; + + // see if it is time to completely remove it + if ( cg.time > mp->time + MARK_TOTAL_TIME ) { + CG_FreeMarkPoly( mp ); + continue; + } + + // fade out the energy bursts + if ( mp->markShader == cgs.media.energyMarkShader ) { + + fade = 450 - 450 * ( (cg.time - mp->time ) / 3000.0 ); + if ( fade < 255 ) { + if ( fade < 0 ) { + fade = 0; + } + if ( mp->verts[0].modulate[0] != 0 ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + } + + // fade all marks out with time + t = mp->time + MARK_TOTAL_TIME - cg.time; + if ( t < MARK_FADE_TIME ) { + fade = 255 * t / MARK_FADE_TIME; + if ( mp->alphaFade ) { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[3] = fade; + } + } else { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * fade; + mp->verts[j].modulate[1] = mp->color[1] * fade; + mp->verts[j].modulate[2] = mp->color[2] * fade; + } + } + } + + + trap_R_AddPolyToScene( mp->markShader, mp->poly.numVerts, mp->verts ); + } +} + +// cg_particles.c + +#define BLOODRED 2 +#define EMISIVEFADE 3 +#define GREY75 4 + +typedef struct particle_s +{ + struct particle_s *next; + + float time; + float endtime; + + vec3_t org; + vec3_t vel; + vec3_t accel; + int color; + float colorvel; + float alpha; + float alphavel; + int type; + qhandle_t pshader; + + float height; + float width; + + float endheight; + float endwidth; + + float start; + float end; + + float startfade; + qboolean rotate; + int snum; + + qboolean link; + + // Ridah + int shaderAnim; + int roll; + + int accumroll; + +} cparticle_t; + +typedef enum +{ + P_NONE, + P_WEATHER, + P_FLAT, + P_SMOKE, + P_ROTATE, + P_WEATHER_TURBULENT, + P_ANIM, // Ridah + P_BAT, + P_BLEED, + P_FLAT_SCALEUP, + P_FLAT_SCALEUP_FADE, + P_WEATHER_FLURRY, + P_SMOKE_IMPACT, + P_BUBBLE, + P_BUBBLE_TURBULENT, + P_SPRITE +} particle_type_t; + +#define MAX_SHADER_ANIMS 32 +#define MAX_SHADER_ANIM_FRAMES 64 + +static char *shaderAnimNames[MAX_SHADER_ANIMS] = { + "explode1", + NULL +}; +static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; +static int shaderAnimCounts[MAX_SHADER_ANIMS] = { + 23 +}; +static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { + 1.0f +}; +static int numShaderAnims; +// done. + +#define PARTICLE_GRAVITY 40 +//PKMOD - Ergodic 03/18/04 - change max particles from 1024 to 1536 +#define MAX_PARTICLES 1536 + +cparticle_t *active_particles, *free_particles; +cparticle_t particles[MAX_PARTICLES]; +int cl_numparticles = MAX_PARTICLES; + +qboolean initparticles = qfalse; +vec3_t pvforward, pvright, pvup; +vec3_t rforward, rright, rup; + +float oldtime; + +/* +=============== +CL_ClearParticles +=============== +*/ +void CG_ClearParticles (void) +{ + int i; + + memset( particles, 0, sizeof(particles) ); + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + {// create a front facing polygon + + if (p->type != P_WEATHER_FLURRY) + { + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + if (org[2] > p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + p->org[2] = ( p->start + crandom () * 4 ); + + + if (p->type == P_BUBBLE_TURBULENT) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + } + } + else + { + if (org[2] < p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + while (p->org[2] < p->end) + { + p->org[2] += (p->start - p->end); + } + + + if (p->type == P_WEATHER_TURBULENT) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + } + } + + + // Rafael snow pvs check + if (!p->link) + return; + + p->alpha = 1; + } + + // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp + if (Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + // done. + + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, -p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255 * p->alpha; + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy( point, TRIverts[0].xyz ); + TRIverts[0].st[0] = 1; + TRIverts[0].st[1] = 0; + TRIverts[0].modulate[0] = 255; + TRIverts[0].modulate[1] = 255; + TRIverts[0].modulate[2] = 255; + TRIverts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + VectorCopy (point, TRIverts[1].xyz); + TRIverts[1].st[0] = 0; + TRIverts[1].st[1] = 0; + TRIverts[1].modulate[0] = 255; + TRIverts[1].modulate[1] = 255; + TRIverts[1].modulate[2] = 255; + TRIverts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + VectorCopy (point, TRIverts[2].xyz); + TRIverts[2].st[0] = 0; + TRIverts[2].st[1] = 1; + TRIverts[2].modulate[0] = 255; + TRIverts[2].modulate[1] = 255; + TRIverts[2].modulate[2] = 255; + TRIverts[2].modulate[3] = 255 * p->alpha; + } + + } + else if (p->type == P_SPRITE) + { + vec3_t rr, ru; + vec3_t rotate_ang; + + VectorSet (color, 1.0, 1.0, 0.5); + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, pvup, point); + VectorMA (point, -width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, pvup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, pvup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) + {// create a front rotating facing polygon + + if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + + if (p->color == BLOODRED) + VectorSet (color, 0.22f, 0.0f, 0.0f); + else if (p->color == GREY75) + { + float len; + float greyit; + float val; + len = Distance (cg.snap->ps.origin, org); + if (!len) + len = 1; + + val = 4096/len; + greyit = 0.25 * val; + if (greyit > 0.5) + greyit = 0.5; + + VectorSet (color, greyit, greyit, greyit); + } + else + VectorSet (color, 1.0, 1.0, 1.0); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + if (cg.time > p->startfade) + { + invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); + + if (p->color == EMISIVEFADE) + { + float fval; + fval = (invratio * invratio); + if (fval < 0) + fval = 0; + VectorSet (color, fval , fval , fval ); + } + invratio *= p->alpha; + } + else + invratio = 1 * p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + invratio = 1; + + if (invratio > 1) + invratio = 1; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->type != P_SMOKE_IMPACT) + { + vec3_t temp; + + vectoangles (rforward, temp); + p->accumroll += p->roll; + temp[ROLL] += p->accumroll * 0.1; + AngleVectors ( temp, NULL, rright2, rup2); + } + else + { + VectorCopy (rright, rright2); + VectorCopy (rup, rup2); + } + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, -p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, p->height, pvup, point); + VectorMA (point, p->width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, p->height, pvup, point); + VectorMA (point, -p->width, pvright, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255 * invratio; + + } + else if (p->type == P_BLEED) + { + vec3_t rr, ru; + vec3_t rotate_ang; + float alpha; + + alpha = p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + alpha = 1; + + if (p->roll) + { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + else + { + VectorCopy (pvup, ru); + VectorCopy (pvright, rr); + } + + VectorMA (org, -p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 111; + verts[0].modulate[1] = 19; + verts[0].modulate[2] = 9; + verts[0].modulate[3] = 255 * alpha; + + VectorMA (org, -p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 111; + verts[1].modulate[1] = 19; + verts[1].modulate[2] = 9; + verts[1].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 111; + verts[2].modulate[1] = 19; + verts[2].modulate[2] = 9; + verts[2].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 111; + verts[3].modulate[1] = 19; + verts[3].modulate[2] = 9; + verts[3].modulate[3] = 255 * alpha; + + } + else if (p->type == P_FLAT_SCALEUP) + { + float width, height; + float sinR, cosR; + + if (p->color == BLOODRED) + VectorSet (color, 1, 1, 1); + else + VectorSet (color, 0.5, 0.5, 0.5); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (width > p->endwidth) + width = p->endwidth; + + if (height > p->endheight) + height = p->endheight; + + sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); + cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= sinR; + verts[0].xyz[1] -= cosR; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= cosR; + verts[1].xyz[1] += sinR; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += sinR; + verts[2].xyz[1] += cosR; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += cosR; + verts[3].xyz[1] -= sinR; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255; + } + else if (p->type == P_FLAT) + { + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= p->height; + verts[0].xyz[1] -= p->width; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= p->height; + verts[1].xyz[1] += p->width; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += p->height; + verts[2].xyz[1] += p->width; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += p->height; + verts[3].xyz[1] -= p->width; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + } + // Ridah + else if (p->type == P_ANIM) { + vec3_t rr, ru; + vec3_t rotate_ang; + int i, j; + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + if (ratio >= 1.0f) { + ratio = 0.9999f; + } + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + // if we are "inside" this sprite, don't draw + if (Distance( cg.snap->ps.origin, org ) < width/1.5) { + return; + } + + i = p->shaderAnim; + j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); + p->pshader = shaderAnims[i][j]; + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, pvup, point); + VectorMA (point, -width, pvright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, pvup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, pvright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, pvup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + // done. + + if (!p->pshader) { +// (SA) temp commented out for DM +// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); + return; + } + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) + trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); + else + trap_R_AddPolyToScene( p->pshader, 4, verts ); + +} + +// Ridah, made this static so it doesn't interfere with other files +static float roll = 0.0; + +/* +=============== +CG_AddParticles +=============== +*/ +void CG_AddParticles (void) +{ + cparticle_t *p, *next; + float alpha; + float time, time2; + vec3_t org; + int color; + cparticle_t *active, *tail; + int type; + vec3_t rotate_ang; + + if (!initparticles) + CG_ClearParticles (); + + VectorCopy( cg.refdef.viewaxis[0], pvforward ); + VectorCopy( cg.refdef.viewaxis[1], pvright ); + VectorCopy( cg.refdef.viewaxis[2], pvup ); + + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + roll += ((cg.time - oldtime) * 0.1) ; + rotate_ang[ROLL] += (roll*0.9); + AngleVectors ( rotate_ang, rforward, rright, rup); + + oldtime = cg.time; + + active = NULL; + tail = NULL; + + for (p=active_particles ; p ; p=next) + { + + next = p->next; + + time = (cg.time - p->time)*0.001; + + alpha = p->alpha + time*p->alphavel; + if (alpha <= 0) + { // faded out + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + + } + + if (p->type == P_WEATHER_FLURRY) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + } + + + if (p->type == P_FLAT_SCALEUP_FADE) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + } + + if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { + // temporary sprite + CG_AddParticleToScene (p, p->org, alpha); + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + p->next = NULL; + if (!tail) + active = tail = p; + else + { + tail->next = p; + tail = p; + } + + if (alpha > 1.0) + alpha = 1; + + color = p->color; + + time2 = time*time; + + org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; + org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; + org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; + + type = p->type; + + CG_AddParticleToScene (p, org, alpha); + } + + active_particles = active; +} + +/* +====================== +CG_AddParticles +====================== +*/ +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + qboolean turb = qtrue; + + if (!pshader) + CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.90f; + p->alphavel = 0; + + p->start = cent->currentState.origin2[0]; + p->end = cent->currentState.origin2[1]; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->pshader = pshader; + + if (rand()%100 > 90) + { + p->height = 32; + p->width = 32; + p->alpha = 0.10f; + } + else + { + p->height = 1; + p->width = 1; + } + + p->vel[2] = -20; + + p->type = P_WEATHER_FLURRY; + + if (turb) + p->vel[2] = -10; + + VectorCopy(cent->currentState.origin, p->org); + + p->org[0] = p->org[0]; + p->org[1] = p->org[1]; + p->org[2] = p->org[2]; + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); + p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); + p->vel[2] += cent->currentState.angles[2]; + + if (turb) + { + p->accel[0] = crandom () * 16; + p->accel[1] = crandom () * 16; + } + +} + +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + p->height = 1; + p->width = 1; + + p->vel[2] = -50; + + if (turb) + { + p->type = P_WEATHER_TURBULENT; + p->vel[2] = -50 * 1.3; + } + else + { + p->type = P_WEATHER; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + float randsize; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + + randsize = 1 + (crandom() * 0.5); + + p->height = randsize; + p->width = randsize; + + p->vel[2] = 50 + ( crandom() * 10 ); + + if (turb) + { + p->type = P_BUBBLE_TURBULENT; + p->vel[2] = 50 * 1.3; + } + else + { + p->type = P_BUBBLE; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) +{ + + // using cent->density = enttime + // cent->frame = startfade + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSmoke == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; + p->pshader = pshader; + p->rotate = qfalse; + p->height = 8; + p->width = 8; + p->endheight = 32; + p->endwidth = 32; + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org); + + p->vel[0] = p->vel[1] = 0; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[2] = 5; + + if (cent->currentState.frame == 1)// reverse gravity + p->vel[2] *= -1; + + p->roll = 8 + (crandom() * 4); +} + + +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) +{ + + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 1.0; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -60; + p->vel[2] += -20; + +} + +/* +====================== +CG_ParticleExplosion +====================== +*/ + +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) +{ + cparticle_t *p; + int anim; + + if (animStr < (char *)10) + CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); + + // find the animation string + for (anim=0; shaderAnimNames[anim]; anim++) { + if (!Q_stricmp( animStr, shaderAnimNames[anim] )) + break; + } + if (!shaderAnimNames[anim]) { + CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); + return; + } + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 0.5; + p->alphavel = 0; + + if (duration < 0) { + duration *= -1; + p->roll = 0; + } else { + p->roll = crandom()*179; + } + + p->shaderAnim = anim; + + p->width = sizeStart; + p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction + + p->endheight = sizeEnd; + p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; + + p->endtime = cg.time + duration; + + p->type = P_ANIM; + + VectorCopy( origin, p->org ); + VectorCopy( vel, p->vel ); + VectorClear( p->accel ); + +} + +// Rafael Shrapnel +void CG_AddParticleShrapnel (localEntity_t *le) +{ + return; +} +// done. + +int CG_NewParticleArea (int num) +{ + // const char *str; + char *str; + char *token; + int type; + vec3_t origin, origin2; + int i; + float range = 0; + int turb; + int numparticles; + int snum; + + str = (char *) CG_ConfigString (num); + if (!str[0]) + return (0); + + // returns type 128 64 or 32 + token = COM_Parse (&str); + type = atoi (token); + + if (type == 1) + range = 128; + else if (type == 2) + range = 64; + else if (type == 3) + range = 32; + else if (type == 0) + range = 256; + else if (type == 4) + range = 8; + else if (type == 5) + range = 16; + else if (type == 6) + range = 32; + else if (type == 7) + range = 64; + + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin[i] = atof (token); + } + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin2[i] = atof (token); + } + + token = COM_Parse (&str); + numparticles = atoi (token); + + token = COM_Parse (&str); + turb = atoi (token); + + token = COM_Parse (&str); + snum = atoi (token); + + for (i=0; i= 4) + CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + else + CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + } + + return (1); +} + +void CG_SnowLink (centity_t *cent, qboolean particleOn) +{ + cparticle_t *p, *next; + int id; + + id = cent->currentState.frame; + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) + { + if (p->snum == id) + { + if (particleOn) + p->link = qtrue; + else + p->link = qfalse; + } + } + + } +} + +void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 0.25; + p->alphavel = 0; + p->roll = crandom()*179; + + p->pshader = pshader; + + p->endtime = cg.time + 1000; + p->startfade = cg.time + 100; + + p->width = rand()%4 + 8; + p->height = rand()%4 + 8; + + p->endheight = p->height *2; + p->endwidth = p->width * 2; + + p->endtime = cg.time + 500; + + p->type = P_SMOKE_IMPACT; + + VectorCopy( origin, p->org ); + VectorSet(p->vel, 0, 0, 20); + VectorSet(p->accel, 0, 0, 20); + + p->rotate = qtrue; +} + +void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + if (fleshEntityNum) + p->startfade = cg.time; + else + p->startfade = cg.time + 100; + + p->width = 4; + p->height = 4; + + p->endheight = 4+rand()%3; + p->endwidth = p->endheight; + + p->type = P_SMOKE; + + VectorCopy( start, p->org ); + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -20; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + p->alpha = 0.75; + +} + +void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + int time; + int time2; + float ratio; + + float duration = 1500; + + time = cg.time; + time2 = cg.time + cent->currentState.time; + + ratio =(float)1 - ((float)time / (float)time2); + + if (!pshader) + CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + p->startfade = p->endtime; + + p->width = 1; + p->height = 3; + + p->endheight = 3; + p->endwidth = 1; + + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org ); + + p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); + p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); + p->vel[2] = (cent->currentState.origin2[2]); + + p->snum = 1.0f; + + VectorClear( p->accel ); + + p->accel[2] = -20; + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + + +void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + if (cent->currentState.angles2[2]) + p->endtime = cg.time + cent->currentState.angles2[2]; + else + p->endtime = cg.time + 60000; + + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) + { + p->width = cent->currentState.angles2[0]; + p->height = cent->currentState.angles2[0]; + + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + } + else + { + p->width = 8; + p->height = 8; + + p->endheight = 16; + p->endwidth = 16; + } + + p->type = P_FLAT_SCALEUP; + + p->snum = 1.0; + + VectorCopy(cent->currentState.origin, p->org ); + + p->org[2]+= 0.55 + (crandom() * 0.5); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + +void CG_OilSlickRemove (centity_t *cent) +{ + cparticle_t *p, *next; + int id; + + id = 1.0f; + + if (!id) + CG_Printf ("CG_OilSlickRevove NULL id\n"); + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_FLAT_SCALEUP) + { + if (p->snum == id) + { + p->endtime = cg.time + 100; + p->startfade = p->endtime; + p->type = P_FLAT_SCALEUP_FADE; + + } + } + + } +} + +qboolean ValidBloodPool (vec3_t start) +{ +#define EXTRUDE_DIST 0.5 + + vec3_t angles; + vec3_t right, up; + vec3_t this_pos, x_pos, center_pos, end_pos; + float x, y; + float fwidth, fheight; + trace_t trace; + vec3_t normal; + + fwidth = 16; + fheight = 16; + + VectorSet (normal, 0, 0, 1); + + vectoangles (normal, angles); + AngleVectors (angles, NULL, right, up); + + VectorMA (start, EXTRUDE_DIST, normal, center_pos); + + for (x= -fwidth/2; xendpos, start); + legit = ValidBloodPool (start); + + if (!legit) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + 3000; + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + rndSize = 0.4 + random()*0.6; + + p->width = 8*rndSize; + p->height = 8*rndSize; + + p->endheight = 16*rndSize; + p->endwidth = 16*rndSize; + + p->type = P_FLAT_SCALEUP; + + VectorCopy(start, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + p->color = BLOODRED; +} + +#define NORMALSIZE 16 +#define LARGESIZE 32 + +void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + length = VectorLength (dir); + vectoangles (dir, angles); + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors (angles, forward, NULL, NULL); + AngleVectorsForward( angles, forward ); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + p->endtime = cg.time + 350 + (crandom() * 100); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + p->endheight = LARGESIZE; + p->endwidth = LARGESIZE; + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -1; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +{ + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 0.4f; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->org[0] += (crandom() * x); + p->org[1] += (crandom() * y); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += (crandom() * 4); + p->vel[1] += (crandom() * 4); + p->vel[2] += (20 + (crandom() * 10)) * speed; + + p->accel[0] = crandom () * 4; + p->accel[1] = crandom () * 4; + +} + +//PKMOD - Ergodic 01/18/02 - re-sized sparks - now even bigger! +void CG_ParticleSparks2 (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +{ + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 0.4f; + p->alphavel = 0; + + p->height = 3.0; + p->width = 3.0; + p->endheight = 3.0; + p->endwidth = 3.0; + + p->pshader = cgs.media.pkagravitywellspark; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->org[0] += (crandom() * x); + p->org[1] += (crandom() * y); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += (crandom() * 4); + p->vel[1] += (crandom() * 4); + p->vel[2] += (20 + (crandom() * 10)) * speed; + + p->accel[0] = crandom () * 4; + p->accel[1] = crandom () * 4; + +} + + +//PKMOD - Ergodic 07/20/03 - particles for beartrap +void CG_ParticleSparks3 (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +{ + cparticle_t *p; + float ransize; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + //PKMOD - Ergodic 09/22/03 - change color + p->color = EMISIVEFADE; +// p->color = GREY75; + p->alpha = 0.4f; + p->alphavel = 0; + + //PKMOD - Ergodic 10/15/03 - vary the size + ransize = 2.0 + 1.25 * random(); + p->height = ransize; + p->width = ransize; + p->endheight = 2.0; + p->endwidth = 2.0; + + switch ( rand() % 5 ) { //random numbers: { 0, 1, 2, 3, 4 } + case 0: + p->pshader = cgs.media.pkabeartrapspark1Shader; + break; + case 1: + p->pshader = cgs.media.pkabeartrapspark2Shader; + break; + case 2: + p->pshader = cgs.media.pkabeartrapspark3Shader; + break; + case 3: + p->pshader = cgs.media.pkabeartrapspark4Shader; + break; + default: + p->pshader = cgs.media.pkabeartrapspark5Shader; + break; + } + + //PKMOD - Ergodic 09/22/03 - change type + //p->type = P_SMOKE; + p->type = P_SMOKE_IMPACT; + + VectorCopy(org, p->org); + + p->org[0] += (crandom() * x); + p->org[1] += (crandom() * y); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += (crandom() * 4); + p->vel[1] += (crandom() * 4); + p->vel[2] += (20 + (crandom() * 10)) * speed; + + p->accel[0] = crandom () * 4; + p->accel[1] = crandom () * 4; + +} + +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + VectorNegate (dir, dir); + length = VectorLength (dir); + vectoangles (dir, angles); + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors (angles, forward, NULL, NULL); + AngleVectorsForward( angles, forward ); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 5.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + // RF, stay around for long enough to expand and dissipate naturally + if (length) + p->endtime = cg.time + 4500 + (crandom() * 3500); + else + p->endtime = cg.time + 750 + (crandom() * 500); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + + // RF, expand while falling + p->endheight = LARGESIZE*3.0; + p->endwidth = LARGESIZE*3.0; + + if (!length) + { + p->width *= 0.2f; + p->height *= 0.2f; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } + + p->type = P_SMOKE; + + VectorCopy( point, p->org ); + + p->vel[0] = crandom()*6; + p->vel[1] = crandom()*6; + p->vel[2] = random()*20; + + // RF, add some gravity/randomness + p->accel[0] = crandom()*3; + p->accel[1] = crandom()*3; + p->accel[2] = -PARTICLE_GRAVITY*0.4; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = rand()%179; + + p->pshader = pshader; + + if (duration > 0) + p->endtime = cg.time + duration; + else + p->endtime = duration; + + p->startfade = cg.time; + + p->width = size; + p->height = size; + + p->endheight = size; + p->endwidth = size; + + p->type = P_SPRITE; + + VectorCopy( origin, p->org ); + + p->rotate = qfalse; +} + diff --git a/quake3/source/code/cgame/cg_newdraw.c b/quake3/source/code/cgame/cg_newdraw.c new file mode 100644 index 0000000..f8d38cc --- /dev/null +++ b/quake3/source/code/cgame/cg_newdraw.c @@ -0,0 +1,2236 @@ + +//PKMOD - Ergodic 01/17/04 - Enable Hud Code +//#ifndef MISSIONPACK // bk001204 +//#error This file not be used for classic Q3A. +//#endif + +#include "cg_local.h" +#include "../ui/ui_shared.h" + +extern displayContextDef_t cgDC; + + +// set in CG_ParseTeamInfo + +//static int sortedTeamPlayers[TEAM_MAXOVERLAY]; +//static int numSortedTeamPlayers; +int drawTeamOverlayModificationCount = -1; + +//static char systemChat[256]; +//static char teamChat1[256]; +//static char teamChat2[256]; + +void CG_InitTeamChat() { + memset(teamChat1, 0, sizeof(teamChat1)); + memset(teamChat2, 0, sizeof(teamChat2)); + memset(systemChat, 0, sizeof(systemChat)); +} + +void CG_SetPrintString(int type, const char *p) { + if (type == SYSTEM_PRINT) { + strcpy(systemChat, p); + } else { + strcpy(teamChat2, teamChat1); + strcpy(teamChat1, p); + } +} + +void CG_CheckOrderPending() { + if (cgs.gametype < GT_CTF) { + return; + } + if (cgs.orderPending) { + //clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; + const char *p1, *p2, *b; + p1 = p2 = b = NULL; + switch (cgs.currentOrder) { + case TEAMTASK_OFFENSE: + p1 = VOICECHAT_ONOFFENSE; + p2 = VOICECHAT_OFFENSE; + b = "+button7; wait; -button7"; + break; + case TEAMTASK_DEFENSE: + p1 = VOICECHAT_ONDEFENSE; + p2 = VOICECHAT_DEFEND; + b = "+button8; wait; -button8"; + break; + case TEAMTASK_PATROL: + p1 = VOICECHAT_ONPATROL; + p2 = VOICECHAT_PATROL; + b = "+button9; wait; -button9"; + break; + case TEAMTASK_FOLLOW: + p1 = VOICECHAT_ONFOLLOW; + p2 = VOICECHAT_FOLLOWME; + b = "+button10; wait; -button10"; + break; + case TEAMTASK_CAMP: + p1 = VOICECHAT_ONCAMPING; + p2 = VOICECHAT_CAMP; + break; + case TEAMTASK_RETRIEVE: + p1 = VOICECHAT_ONGETFLAG; + p2 = VOICECHAT_RETURNFLAG; + break; + case TEAMTASK_ESCORT: + p1 = VOICECHAT_ONFOLLOWCARRIER; + p2 = VOICECHAT_FOLLOWFLAGCARRIER; + break; + } + + if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { + // to everyone + trap_SendConsoleCommand(va("cmd vsay_team %s\n", p2)); + } else { + // for the player self + if (sortedTeamPlayers[cg_currentSelectedPlayer.integer] == cg.snap->ps.clientNum && p1) { + trap_SendConsoleCommand(va("teamtask %i\n", cgs.currentOrder)); + //trap_SendConsoleCommand(va("cmd say_team %s\n", p2)); + trap_SendConsoleCommand(va("cmd vsay_team %s\n", p1)); + } else if (p2) { + //trap_SendConsoleCommand(va("cmd say_team %s, %s\n", ci->name,p)); + trap_SendConsoleCommand(va("cmd vtell %d %s\n", sortedTeamPlayers[cg_currentSelectedPlayer.integer], p2)); + } + } + if (b) { + trap_SendConsoleCommand(b); + } + cgs.orderPending = qfalse; + } +} + +//PKMOD - Ergodic 02/08/04 - get maximum weapon ammo +// NOTES - these value should be in sync with void Add_Ammo in g_items.c + +int CG_GetMaxAmmo( int weapon) { + switch( weapon ) { + case WP_GAUNTLET: //weapon 1 + return (-1); + break; + case WP_MACHINEGUN: //weapon 2 + return (200); + break; + case WP_SHOTGUN: //weapon 3 + return (200); + break; + case WP_AIRFIST : //weapon 4 + return (4); + break; + case WP_NAILGUN : //weapon 5 + return (200); + break; + case WP_GRENADE_LAUNCHER: //weapon 6 + return (200); + break; + case WP_ROCKET_LAUNCHER: //weapon 7 + return (200); + break; + case WP_LIGHTNING: //weapon 8 + return (200); + break; + case WP_RAILGUN: //weapon 9 + return (200); + break; + case WP_GRAPPLING_HOOK: //weapon 10 + return (-1); + break; + case WP_GRAVITY: //weapon 11 + return (1); + break; + case WP_SENTRY: //weapon 12 + return (3); + break; + case WP_BEARTRAP: //weapon 13 + return (3); + break; + case WP_BEANS: //weapon 14 + return (1); + break; + case WP_EXPLODING_SHELLS: //weapon 15 + return (10); + break; + + default: + return (1); //this should never happen + break; + } +} + + +static void CG_SetSelectedPlayerName() { + if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { + clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[cg_currentSelectedPlayer.integer]; + if (ci) { + trap_Cvar_Set("cg_selectedPlayerName", ci->name); + trap_Cvar_Set("cg_selectedPlayer", va("%d", sortedTeamPlayers[cg_currentSelectedPlayer.integer])); + cgs.currentOrder = ci->teamTask; + } + } else { + trap_Cvar_Set("cg_selectedPlayerName", "Everyone"); + } +} +int CG_GetSelectedPlayer() { + if (cg_currentSelectedPlayer.integer < 0 || cg_currentSelectedPlayer.integer >= numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer = 0; + } + return cg_currentSelectedPlayer.integer; +} + +void CG_SelectNextPlayer() { + CG_CheckOrderPending(); + if (cg_currentSelectedPlayer.integer >= 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer++; + } else { + cg_currentSelectedPlayer.integer = 0; + } + CG_SetSelectedPlayerName(); +} + +void CG_SelectPrevPlayer() { + CG_CheckOrderPending(); + if (cg_currentSelectedPlayer.integer > 0 && cg_currentSelectedPlayer.integer < numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer--; + } else { + cg_currentSelectedPlayer.integer = numSortedTeamPlayers; + } + CG_SetSelectedPlayerName(); +} + + +static void CG_DrawPlayerArmorIcon( rectDef_t *rect, qboolean draw2D ) { + centity_t *cent; + playerState_t *ps; + vec3_t angles; + vec3_t origin; + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( draw2D || ( !cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses + CG_DrawPic( rect->x, rect->y + rect->h/2 + 1, rect->w, rect->h, cgs.media.armorIcon ); + } else if (cg_draw3dIcons.integer) { + VectorClear( angles ); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cgs.media.armorModel, 0, origin, angles ); + } + +} + +static void CG_DrawPlayerArmorValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + char num[16]; + int value; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + value = ps->stats[STAT_ARMOR]; + + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + +#ifndef MISSIONPACK // bk001206 +static float healthColors[4][4] = { +// { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 }, {0.5, 0.5, 0.5, 1} }; + // bk0101016 - float const + { 1.0f, 0.69f, 0.0f, 1.0f } , // normal + { 1.0f, 0.2f, 0.2f, 1.0f }, // low health + { 0.5f, 0.5f, 0.5f, 1.0f}, // weapon firing + { 1.0f, 1.0f, 1.0f, 1.0f } }; // health > 100 +#endif + +static void CG_DrawPlayerAmmoIcon( rectDef_t *rect, qboolean draw2D ) { + centity_t *cent; + playerState_t *ps; + vec3_t angles; + vec3_t origin; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( draw2D || (!cg_draw3dIcons.integer && cg_drawIcons.integer) ) { // bk001206 - parentheses + qhandle_t icon; + icon = cg_weapons[ cg.predictedPlayerState.weapon ].ammoIcon; + if ( icon ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, icon ); + } + } else if (cg_draw3dIcons.integer) { + if ( cent->currentState.weapon && cg_weapons[ cent->currentState.weapon ].ammoModel ) { + VectorClear( angles ); + origin[0] = 70; + origin[1] = 0; + origin[2] = 0; + angles[YAW] = 90 + 20 * sin( cg.time / 1000.0 ); + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, cg_weapons[ cent->currentState.weapon ].ammoModel, 0, origin, angles ); + } + } +} + +static void CG_DrawPlayerAmmoValue(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + char num[16]; + int value; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( cent->currentState.weapon ) { + value = ps->ammo[cent->currentState.weapon]; + if ( value > -1 ) { + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } + } + } + +} + +//PKMOD - ergodic 02/07/04 - add horizontal meters for ammo +//Meter: Horizontal Right to Left fill +static void CG_DrawPlayerAmmoMeter_HR2L(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + float value; + float rectx; + float rectw; + int max_value; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( cent->currentState.weapon ) { + max_value = CG_GetMaxAmmo( cent->currentState.weapon ); + if ( max_value < 0 ) + value = 1; + else { + //force a float value + value = ps->ammo[cent->currentState.weapon]; + value = value / max_value; + } + + if (shader) { + rectx = rect->x + rect->w * ( 1 - value ); + rectw = rect->w * value; + trap_R_SetColor( color ); + CG_DrawPic(rectx, rect->y, rectw, rect->h, shader); + trap_R_SetColor( NULL ); + } + } +} + +//PKMOD - ergodic 02/07/04 - add horizontal meters for ammo +//Meter: Horizontal Left to Right fill +static void CG_DrawPlayerAmmoMeter_HL2R(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + float value; + float rectw; + int max_value; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + if ( cent->currentState.weapon ) { + max_value = CG_GetMaxAmmo( cent->currentState.weapon ); + if ( max_value < 0 ) + value = 1; + else { + //force a float value + value = ps->ammo[cent->currentState.weapon]; + value = value / max_value; + } + + if (shader) { + rectw = rect->w * value; + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rectw, rect->h, shader); + trap_R_SetColor( NULL ); + } + } +} + +//PKMOD - ergodic 02/08/04 - add horizontal meters for armor +//Meter: Horizontal Right to Left fill +static void CG_DrawPlayerArmorMeter_HR2L(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + float value; + float rectx; + float rectw; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + //force a float value + value = ps->stats[STAT_ARMOR]; + value = value / 200; + + if (shader) { + rectx = rect->x + rect->w * ( 1 - value ); + rectw = rect->w * value; + trap_R_SetColor( color ); + CG_DrawPic(rectx, rect->y, rectw, rect->h, shader); + trap_R_SetColor( NULL ); + } +} + +//PKMOD - ergodic 02/07/04 - add horizontal meters for armor +//Meter: Horizontal Left to Right fill +static void CG_DrawPlayerArmorMeter_HL2R(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + float value; + float rectw; + centity_t *cent; + playerState_t *ps; + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + //force a float value + value = ps->stats[STAT_ARMOR]; + value = value / 200; + + if (shader) { + rectw = rect->w * value; + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rectw, rect->h, shader); + trap_R_SetColor( NULL ); + } +} + + +static void CG_DrawPlayerHead(rectDef_t *rect, qboolean draw2D) { + vec3_t angles; + float size, stretch; + float frac; + float x = rect->x; + + VectorClear( angles ); + + if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { + frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; + size = rect->w * 1.25 * ( 1.5 - frac * 0.5 ); + + stretch = size - rect->w * 1.25; + // kick in the direction of damage + x -= stretch * 0.5 + cg.damageX * stretch * 0.5; + + cg.headStartYaw = 180 + cg.damageX * 45; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + + cg.headStartTime = cg.time; + cg.headEndTime = cg.time + 100 + random() * 2000; + } else { + if ( cg.time >= cg.headEndTime ) { + // select a new head angle + cg.headStartYaw = cg.headEndYaw; + cg.headStartPitch = cg.headEndPitch; + cg.headStartTime = cg.headEndTime; + cg.headEndTime = cg.time + 100 + random() * 2000; + + cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); + cg.headEndPitch = 5 * cos( crandom()*M_PI ); + } + + size = rect->w * 1.25; + } + + // if the server was frozen for a while we may have a bad head start time + if ( cg.headStartTime > cg.time ) { + cg.headStartTime = cg.time; + } + + frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); + frac = frac * frac * ( 3 - 2 * frac ); + angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; + angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; + + CG_DrawHead( x, rect->y, rect->w, rect->h, cg.snap->ps.clientNum, angles ); +} + +static void CG_DrawSelectedPlayerHealth( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + clientInfo_t *ci; + int value; + char num[16]; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", ci->health); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } + } +} + +static void CG_DrawSelectedPlayerArmor( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + clientInfo_t *ci; + int value; + char num[16]; + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + if (ci->armor > 0) { + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", ci->armor); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } + } + } +} + +qhandle_t CG_StatusHandle(int task) { + qhandle_t h = cgs.media.assaultShader; + switch (task) { + case TEAMTASK_OFFENSE : + h = cgs.media.assaultShader; + break; + case TEAMTASK_DEFENSE : + h = cgs.media.defendShader; + break; + case TEAMTASK_PATROL : + h = cgs.media.patrolShader; + break; + case TEAMTASK_FOLLOW : + h = cgs.media.followShader; + break; + case TEAMTASK_CAMP : + h = cgs.media.campShader; + break; + case TEAMTASK_RETRIEVE : + h = cgs.media.retrieveShader; + break; + case TEAMTASK_ESCORT : + h = cgs.media.escortShader; + break; + default : + h = cgs.media.assaultShader; + break; + } + return h; +} + +static void CG_DrawSelectedPlayerStatus( rectDef_t *rect ) { + clientInfo_t *ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + qhandle_t h; + if (cgs.orderPending) { + // blink the icon + if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { + return; + } + h = CG_StatusHandle(cgs.currentOrder); + } else { + h = CG_StatusHandle(ci->teamTask); + } + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h ); + } +} + + +static void CG_DrawPlayerStatus( rectDef_t *rect ) { + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + if (ci) { + qhandle_t h = CG_StatusHandle(ci->teamTask); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, h); + } +} + + +static void CG_DrawSelectedPlayerName( rectDef_t *rect, float scale, vec4_t color, qboolean voice, int textStyle) { + clientInfo_t *ci; + ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); + if (ci) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, ci->name, 0, 0, textStyle); + } +} + +static void CG_DrawSelectedPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + clientInfo_t *ci; + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) { + p = "unknown"; + } + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); + } +} + +static void CG_DrawPlayerLocation( rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + if (ci) { + const char *p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) { + p = "unknown"; + } + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, p, 0, 0, textStyle); + } +} + + + +static void CG_DrawSelectedPlayerWeapon( rectDef_t *rect ) { + clientInfo_t *ci; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader); + } + } +} + +//PKMOD - Ergodic 02/08/04 - add relative score value to the HUD's gauges +static void CG_DrawPlayerRelativeScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + char num[16]; + int value; + + + if ( cg.snap->ps.persistant[PERS_SCORE] == cgs.scores1 ) { + //PKMOD - Ergodic 02/09/04 - debug wackie scores when respawn into game (inactive) + //Com_Printf( "RelativeScore - if " ); + //check if there is a scond place score... + if ( cgs.scores2 == SCORE_NOT_PRESENT ) + value = cg.snap->ps.persistant[PERS_SCORE]; + else + value = cg.snap->ps.persistant[PERS_SCORE] - cgs.scores2; + } else { + //PKMOD - Ergodic 02/09/04 - debug wackie scores when respawn into game (inactive) + //Com_Printf( "RelativeScore - else " ); + value = cg.snap->ps.persistant[PERS_SCORE] - cgs.scores1; + } + + //PKMOD - Ergodic 02/09/04 - debug wackie scores when respawn into game (inactive) + //Com_Printf( "- pers_score>%d<, score1>%d<, score2>%d<, value>%d<\n", cg.snap->ps.persistant[PERS_SCORE], cgs.scores1, cgs.scores2, value); + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + +static void CG_DrawPlayerScore( rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + char num[16]; + int value = cg.snap->ps.persistant[PERS_SCORE]; + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + +static void CG_DrawPlayerItem( rectDef_t *rect, float scale, qboolean draw2D) { + //int value; //PKMOD - Ergodic 02/01/04 - remove original code + //vec3_t origin, angles; //PKMOD - Ergodic 02/01/04 - remove original code + int i; + int holdable_index; + int y; + +//>>>>>> + // value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; +// CG_RegisterItemVisuals( value ); + + //MOdify the code to handle more than one holdable in possession + if ( cg.snap->ps.stats[STAT_HOLDABLE_ITEM] ) { + //PKMOD - Ergodic 10/13/01 - move holdable icons higher up on the screen + // change start height from 1/2 to 1/4 from top of screen + // y = (SCREEN_HEIGHT-ICON_SIZE_SMALL)/2; + y = (SCREEN_HEIGHT-ICON_SIZE_SMALL)/4; + holdable_index = cg.snap->ps.stats[STAT_ACTIVE_HOLDABLE]; + for (i = 1; i < HI_NUM_HOLDABLE; i++) { + if ( cg.snap->ps.stats[STAT_HOLDABLE_ITEM] & ( 1 << holdable_index ) ) { + //PKMOD - Ergodic 12/07/01 - display Private Bot pieces as only one Icon + if ( ( holdable_index < HI_BOTLEGS ) || ( holdable_index > HI_BOTHEAD )) { + //only display valid holdables + if ( holdable_index < HI_NUM_HOLDABLE ) { + CG_RegisterItemVisuals( cg_holdable[holdable_index] ); + CG_DrawPic( 640-ICON_SIZE, y, ICON_SIZE_SMALL, ICON_SIZE_SMALL, cg_items[ cg_holdable[holdable_index] ].icon ); + } + //get next y offset + y += 1.2 * ICON_SIZE_SMALL; + } + } + //PKMOD - Ergodic 12/07/01 - now at end of Private Bot pieces list - then display + if ( holdable_index == HI_BOTHEAD ) { + //PKMOD - Ergodic 12/07/01 - hold private bot parts + int pribot_parts; + qhandle_t pribotIcon; + + pribot_parts = cg.snap->ps.stats[STAT_HOLDABLE_ITEM] & ( 7 << HI_BOTLEGS ); + if ( pribot_parts ) { + //encoding bits (LEGS TORSO HEAD) + switch ( pribot_parts >> HI_BOTLEGS ) { + case 1: + pribotIcon = cgs.media.pkapribot_100Icon; + break; + case 2: + pribotIcon = cgs.media.pkapribot_010Icon; + break; + case 3: + pribotIcon = cgs.media.pkapribot_110Icon; + break; + case 4: + pribotIcon = cgs.media.pkapribot_001Icon; + break; + case 5: + pribotIcon = cgs.media.pkapribot_101Icon; + break; + case 6: + pribotIcon = cgs.media.pkapribot_011Icon; + break; + case 7: + pribotIcon = cgs.media.pkapribot_111Icon; + break; + } + CG_DrawPic( 640-ICON_SIZE, y, ICON_SIZE_SMALL, ICON_SIZE_SMALL, pribotIcon ); + //get next y offset + y += 1.2 * ICON_SIZE_SMALL; + } + } + + //set next index, wrap around if at the end of the list + holdable_index = holdable_index + 1; + if ( holdable_index >= HI_NUM_HOLDABLE ) + holdable_index = 1; + + } + } + +//>>>>>> +/* PKMOD - Ergodic 02/01/04 - remove original code + value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; + if ( value ) { + CG_RegisterItemVisuals( value ); + + if (qtrue) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); + } else { + VectorClear( angles ); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + CG_Draw3DModel(rect->x, rect->y, rect->w, rect->h, cg_items[ value ].models[0], 0, origin, angles ); + } + } +*/ + +} + + +static void CG_DrawSelectedPlayerPowerup( rectDef_t *rect, qboolean draw2D ) { + clientInfo_t *ci; + int j; + float x, y; + + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + if (ci) { + x = rect->x; + y = rect->y; + + for (j = 0; j < PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + gitem_t *item; + item = BG_FindItemForPowerup( j ); + if (item) { + CG_DrawPic( x, y, rect->w, rect->h, trap_R_RegisterShader( item->icon ) ); + x += 3; + y += 3; + return; + } + } + } + + } +} + + +static void CG_DrawSelectedPlayerHead( rectDef_t *rect, qboolean draw2D, qboolean voice ) { + clipHandle_t cm; + clientInfo_t *ci; + float len; + vec3_t origin; + vec3_t mins, maxs, angles; + + + ci = cgs.clientinfo + ((voice) ? cgs.currentVoiceClient : sortedTeamPlayers[CG_GetSelectedPlayer()]); + + if (ci) { + if ( cg_draw3dIcons.integer ) { + cm = ci->headModel; + if ( !cm ) { + return; + } + + // offset the origin y and z to center the head + trap_R_ModelBounds( cm, mins, maxs ); + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + + // calculate distance so the head nearly fills the box + // assume heads are taller than wide + len = 0.7 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; // len / tan( fov/2 ) + + // allow per-model tweaking + VectorAdd( origin, ci->headOffset, origin ); + + angles[PITCH] = 0; + angles[YAW] = 180; + angles[ROLL] = 0; + + CG_Draw3DModel( rect->x, rect->y, rect->w, rect->h, ci->headModel, ci->headSkin, origin, angles ); + } else if ( cg_drawIcons.integer ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, ci->modelIcon ); + } + + // if they are deferred, draw a cross out + if ( ci->deferred ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.deferShader ); + } + } + +} + + +static void CG_DrawPlayerHealth(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + playerState_t *ps; + int value; + char num[16]; + + ps = &cg.snap->ps; + + value = ps->stats[STAT_HEALTH]; + + if (shader) { + trap_R_SetColor( color ); + CG_DrawPic(rect->x, rect->y, rect->w, rect->h, shader); + trap_R_SetColor( NULL ); + } else { + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h, scale, color, num, 0, 0, textStyle); + } +} + + +static void CG_DrawRedScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + int value; + char num[16]; + if ( cgs.scores1 == SCORE_NOT_PRESENT ) { + Com_sprintf (num, sizeof(num), "-"); + } + else { + Com_sprintf (num, sizeof(num), "%i", cgs.scores1); + } + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); +} + +static void CG_DrawBlueScore(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + int value; + char num[16]; + + if ( cgs.scores2 == SCORE_NOT_PRESENT ) { + Com_sprintf (num, sizeof(num), "-"); + } + else { + Com_sprintf (num, sizeof(num), "%i", cgs.scores2); + } + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + rect->w - value, rect->y + rect->h, scale, color, num, 0, 0, textStyle); +} + +// FIXME: team name support +static void CG_DrawRedName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_redTeamName.string , 0, 0, textStyle); +} + +static void CG_DrawBlueName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cg_blueTeamName.string, 0, 0, textStyle); +} + +static void CG_DrawBlueFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); + return; + } + } +} + +static void CG_DrawBlueFlagStatus(rectDef_t *rect, qhandle_t shader) { + if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { + if (cgs.gametype == GT_HARVESTER) { + vec4_t color = {0, 0, 1, 1}; + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.blueCubeIcon ); + trap_R_SetColor(NULL); + } + return; + } + if (shader) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + } else { + gitem_t *item = BG_FindItemForPowerup( PW_BLUEFLAG ); + if (item) { + vec4_t color = {0, 0, 1, 1}; + trap_R_SetColor(color); + if( cgs.blueflag >= 0 && cgs.blueflag <= 2 ) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.blueflag] ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); + } + trap_R_SetColor(NULL); + } + } +} + +static void CG_DrawBlueFlagHead(rectDef_t *rect) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_RED && cgs.clientinfo[i].powerups & ( 1<< PW_BLUEFLAG )) { + vec3_t angles; + VectorClear( angles ); + angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; + CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); + return; + } + } +} + +static void CG_DrawRedFlagName(rectDef_t *rect, float scale, vec4_t color, int textStyle ) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, cgs.clientinfo[i].name, 0, 0, textStyle); + return; + } + } +} + +static void CG_DrawRedFlagStatus(rectDef_t *rect, qhandle_t shader) { + if (cgs.gametype != GT_CTF && cgs.gametype != GT_1FCTF) { + if (cgs.gametype == GT_HARVESTER) { + vec4_t color = {1, 0, 0, 1}; + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.redCubeIcon ); + trap_R_SetColor(NULL); + } + return; + } + if (shader) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + } else { + gitem_t *item = BG_FindItemForPowerup( PW_REDFLAG ); + if (item) { + vec4_t color = {1, 0, 0, 1}; + trap_R_SetColor(color); + if( cgs.redflag >= 0 && cgs.redflag <= 2) { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[cgs.redflag] ); + } else { + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[0] ); + } + trap_R_SetColor(NULL); + } + } +} + +static void CG_DrawRedFlagHead(rectDef_t *rect) { + int i; + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_BLUE && cgs.clientinfo[i].powerups & ( 1<< PW_REDFLAG )) { + vec3_t angles; + VectorClear( angles ); + angles[YAW] = 180 + 20 * sin( cg.time / 650.0 );; + CG_DrawHead( rect->x, rect->y, rect->w, rect->h, 0,angles ); + return; + } + } +} + +static void CG_HarvesterSkulls(rectDef_t *rect, float scale, vec4_t color, qboolean force2D, int textStyle ) { + char num[16]; + vec3_t origin, angles; + qhandle_t handle; + int value = cg.snap->ps.generic1; + + if (cgs.gametype != GT_HARVESTER) { + return; + } + + if( value > 99 ) { + value = 99; + } + + Com_sprintf (num, sizeof(num), "%i", value); + value = CG_Text_Width(num, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value), rect->y + rect->h, scale, color, num, 0, 0, textStyle); + + if (cg_drawIcons.integer) { + if (!force2D && cg_draw3dIcons.integer) { + VectorClear(angles); + origin[0] = 90; + origin[1] = 0; + origin[2] = -10; + angles[YAW] = ( cg.time & 2047 ) * 360 / 2048.0; + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeModel; + } else { + handle = cgs.media.blueCubeModel; + } + CG_Draw3DModel( rect->x, rect->y, 35, 35, handle, 0, origin, angles ); + } else { + if( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) { + handle = cgs.media.redCubeIcon; + } else { + handle = cgs.media.blueCubeIcon; + } + CG_DrawPic( rect->x + 3, rect->y + 16, 20, 20, handle ); + } + } +} + +static void CG_OneFlagStatus(rectDef_t *rect) { + if (cgs.gametype != GT_1FCTF) { + return; + } else { + gitem_t *item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + if (item) { + if( cgs.flagStatus >= 0 && cgs.flagStatus <= 4 ) { + vec4_t color = {1, 1, 1, 1}; + int index = 0; + if (cgs.flagStatus == FLAG_TAKEN_RED) { + color[1] = color[2] = 0; + index = 1; + } else if (cgs.flagStatus == FLAG_TAKEN_BLUE) { + color[0] = color[1] = 0; + index = 1; + } else if (cgs.flagStatus == FLAG_DROPPED) { + index = 2; + } + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cgs.media.flagShaders[index] ); + } + } + } +} + + +//PKMOD - Ergodic 01/28/04 - Dynamic HUD activation: Ignore this code since PKA does not have TA type of powerups +/* +static void CG_DrawCTFPowerUp(rectDef_t *rect) { + int value; + + if (cgs.gametype < GT_CTF) { + return; + } + value = cg.snap->ps.stats[STAT_PERSISTANT_POWERUP]; + if ( value ) { + CG_RegisterItemVisuals( value ); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, cg_items[ value ].icon ); + } +} +*/ + + +static void CG_DrawTeamColor(rectDef_t *rect, vec4_t color) { + CG_DrawTeamBackground(rect->x, rect->y, rect->w, rect->h, color[3], cg.snap->ps.persistant[PERS_TEAM]); +} + +static void CG_DrawAreaPowerUp(rectDef_t *rect, int align, float special, float scale, vec4_t color) { + char num[16]; + int sorted[MAX_POWERUPS]; + int sortedTime[MAX_POWERUPS]; + int i, j, k; + int active; + playerState_t *ps; + int t; + gitem_t *item; + float f; + rectDef_t r2; + float *inc; + r2.x = rect->x; + r2.y = rect->y; + r2.w = rect->w; + r2.h = rect->h; + + inc = (align == HUD_VERTICAL) ? &r2.y : &r2.x; + + ps = &cg.snap->ps; + + if ( ps->stats[STAT_HEALTH] <= 0 ) { + return; + } + + // sort the list by time remaining + active = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( !ps->powerups[ i ] ) { + continue; + } + t = ps->powerups[ i ] - cg.time; + // ZOID--don't draw if the power up has unlimited time (999 seconds) + // This is true of the CTF flags + if ( t <= 0 || t >= 999000) { + continue; + } + + // insert into the list + for ( j = 0 ; j < active ; j++ ) { + if ( sortedTime[j] >= t ) { + for ( k = active - 1 ; k >= j ; k-- ) { + sorted[k+1] = sorted[k]; + sortedTime[k+1] = sortedTime[k]; + } + break; + } + } + sorted[j] = i; + sortedTime[j] = t; + active++; + } + + // draw the icons and timers + for ( i = 0 ; i < active ; i++ ) { + + //PKMOD - Ergodic - 02/25/04 - add cases for HOLDABLE POWERUPs + if ( sorted[i] == PW_RADIATE ) { + item = BG_FindItemForHoldable( HI_RADIATE ); + } else if ( sorted[i] == PW_PERSENTRY ) { + item = BG_FindItemForHoldable( HI_PERSENTRY ); + } else + item = BG_FindItemForPowerup( sorted[i] ); + + if (item) { + t = ps->powerups[ sorted[i] ]; + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + trap_R_SetColor( NULL ); + } else { + vec4_t modulate; + + f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; + f -= (int)f; + modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; + trap_R_SetColor( modulate ); + } + + CG_DrawPic( r2.x, r2.y, r2.w * .75, r2.h, trap_R_RegisterShader( item->icon ) ); + + Com_sprintf (num, sizeof(num), "%i", sortedTime[i] / 1000); + CG_Text_Paint(r2.x + (r2.w * .75) + 3 , r2.y + r2.h, scale, color, num, 0, 0, 0); + *inc += r2.w + special; + } + + } + trap_R_SetColor( NULL ); + +} + +float CG_GetValue(int ownerDraw) { + centity_t *cent; + clientInfo_t *ci; + playerState_t *ps; + + //PKMOD - ergodic 02/07/04 - for use in meteres +// int max_value; + + + cent = &cg_entities[cg.snap->ps.clientNum]; + ps = &cg.snap->ps; + + switch (ownerDraw) { + case CG_SELECTEDPLAYER_ARMOR: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->armor; + break; + case CG_SELECTEDPLAYER_HEALTH: + ci = cgs.clientinfo + sortedTeamPlayers[CG_GetSelectedPlayer()]; + return ci->health; + break; + case CG_PLAYER_ARMOR_VALUE: + return ps->stats[STAT_ARMOR]; + break; + case CG_PLAYER_AMMO_VALUE: + if ( cent->currentState.weapon ) { + return ps->ammo[cent->currentState.weapon]; + } + break; + case CG_PLAYER_SCORE: + return cg.snap->ps.persistant[PERS_SCORE]; + break; + case CG_PLAYER_HEALTH: + return ps->stats[STAT_HEALTH]; + break; + case CG_RED_SCORE: + return cgs.scores1; + break; + case CG_BLUE_SCORE: + return cgs.scores2; + break; + + //PKMOD - ergodic 02/07/04 - add horizontal meters for ammo + case CG_PLAYER_AMMO_METER_HR2L: //Meter: Horizontal Right to Left fill + if ( cent->currentState.weapon ) { + //PKMOD - ergodic 02/09/04 - turn off addColorRange functionality for several PKA weapons + switch ( cent->currentState.weapon ) { + case WP_GAUNTLET: + return 100; + break; + case WP_AIRFIST: + return 100; + break; + case WP_GRAPPLING_HOOK: + return 100; + break; + case WP_GRAVITY: + return 100; + break; + case WP_SENTRY: + return 100; + break; + case WP_BEARTRAP: + return 100; + break; + case WP_BEANS: + return 100; + break; + default: + return ps->ammo[cent->currentState.weapon]; + break; + } + } + break; + case CG_PLAYER_AMMO_METER_HL2R: //Meter: Horizontal Left to Right fill + if ( cent->currentState.weapon ) { + //PKMOD - ergodic 02/09/04 - turn off addColorRange functionality for several PKA weapons + switch ( cent->currentState.weapon ) { + case WP_GAUNTLET: + return 100; + break; + case WP_AIRFIST: + return 100; + break; + case WP_GRAPPLING_HOOK: + return 100; + break; + case WP_GRAVITY: + return 100; + break; + case WP_SENTRY: + return 100; + break; + case WP_BEARTRAP: + return 100; + break; + case WP_BEANS: + return 100; + break; + default: + return ps->ammo[cent->currentState.weapon]; + break; + } + } + break; + + //PKMOD - Ergodic 02/08/04 - add relative score value to the HUD's gauges + case CG_PLAYER_RELATIVE_SCORE: + if ( cg.snap->ps.persistant[PERS_SCORE] == cgs.scores1 ) + return ( cg.snap->ps.persistant[PERS_SCORE] - cgs.scores2 ); + else + return ( cg.snap->ps.persistant[PERS_SCORE] - cgs.scores1 ); + break; + + + //PKMOD - ergodic 02/08/04 - add horizontal meters for armor + case CG_PLAYER_ARMOR_METER_HR2L: //Meter: Horizontal Right to Left fill + return ps->stats[STAT_ARMOR]; + break; + case CG_PLAYER_ARMOR_METER_HL2R: //Meter: Horizontal Left to Right fill + return ps->stats[STAT_ARMOR]; + break; + + + default: + break; + } + return -1; +} + +qboolean CG_OtherTeamHasFlag() { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + if (cgs.gametype == GT_1FCTF) { + if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_BLUE) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_RED) { + return qtrue; + } else { + return qfalse; + } + } else { + if (team == TEAM_RED && cgs.redflag == FLAG_TAKEN) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.blueflag == FLAG_TAKEN) { + return qtrue; + } else { + return qfalse; + } + } + } + return qfalse; +} + +qboolean CG_YourTeamHasFlag() { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_1FCTF) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + if (cgs.gametype == GT_1FCTF) { + if (team == TEAM_RED && cgs.flagStatus == FLAG_TAKEN_RED) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.flagStatus == FLAG_TAKEN_BLUE) { + return qtrue; + } else { + return qfalse; + } + } else { + if (team == TEAM_RED && cgs.blueflag == FLAG_TAKEN) { + return qtrue; + } else if (team == TEAM_BLUE && cgs.redflag == FLAG_TAKEN) { + return qtrue; + } else { + return qfalse; + } + } + } + return qfalse; +} + +// THINKABOUTME: should these be exclusive or inclusive.. +// +qboolean CG_OwnerDrawVisible(int flags) { + + if (flags & CG_SHOW_TEAMINFO) { + return (cg_currentSelectedPlayer.integer == numSortedTeamPlayers); + } + + if (flags & CG_SHOW_NOTEAMINFO) { + return !(cg_currentSelectedPlayer.integer == numSortedTeamPlayers); + } + + if (flags & CG_SHOW_OTHERTEAMHASFLAG) { + return CG_OtherTeamHasFlag(); + } + + if (flags & CG_SHOW_YOURTEAMHASENEMYFLAG) { + return CG_YourTeamHasFlag(); + } + + if (flags & (CG_SHOW_BLUE_TEAM_HAS_REDFLAG | CG_SHOW_RED_TEAM_HAS_BLUEFLAG)) { + if (flags & CG_SHOW_BLUE_TEAM_HAS_REDFLAG && (cgs.redflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_RED)) { + return qtrue; + } else if (flags & CG_SHOW_RED_TEAM_HAS_BLUEFLAG && (cgs.blueflag == FLAG_TAKEN || cgs.flagStatus == FLAG_TAKEN_BLUE)) { + return qtrue; + } + return qfalse; + } + + if (flags & CG_SHOW_ANYTEAMGAME) { + if( cgs.gametype >= GT_TEAM) { + return qtrue; + } + } + + if (flags & CG_SHOW_ANYNONTEAMGAME) { + if( cgs.gametype < GT_TEAM) { + return qtrue; + } + } + + if (flags & CG_SHOW_HARVESTER) { + if( cgs.gametype == GT_HARVESTER ) { + return qtrue; + } else { + return qfalse; + } + } + + if (flags & CG_SHOW_ONEFLAG) { + if( cgs.gametype == GT_1FCTF ) { + return qtrue; + } else { + return qfalse; + } + } + + if (flags & CG_SHOW_CTF) { + if( cgs.gametype == GT_CTF ) { + return qtrue; + } + } + + if (flags & CG_SHOW_OBELISK) { + if( cgs.gametype == GT_OBELISK ) { + return qtrue; + } else { + return qfalse; + } + } + + if (flags & CG_SHOW_HEALTHCRITICAL) { + if (cg.snap->ps.stats[STAT_HEALTH] < 25) { + return qtrue; + } + } + + if (flags & CG_SHOW_HEALTHOK) { + if (cg.snap->ps.stats[STAT_HEALTH] >= 25) { + return qtrue; + } + } + + if (flags & CG_SHOW_SINGLEPLAYER) { + if( cgs.gametype == GT_SINGLE_PLAYER ) { + return qtrue; + } + } + + if (flags & CG_SHOW_TOURNAMENT) { + if( cgs.gametype == GT_TOURNAMENT ) { + return qtrue; + } + } + + if (flags & CG_SHOW_DURINGINCOMINGVOICE) { + } + + if (flags & CG_SHOW_IF_PLAYER_HAS_FLAG) { + if (cg.snap->ps.powerups[PW_REDFLAG] || cg.snap->ps.powerups[PW_BLUEFLAG] || cg.snap->ps.powerups[PW_NEUTRALFLAG]) { + return qtrue; + } + } + return qfalse; +} + + + +static void CG_DrawPlayerHasFlag(rectDef_t *rect, qboolean force2D) { + int adj = (force2D) ? 0 : 2; + if( cg.predictedPlayerState.powerups[PW_REDFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_RED, force2D); + } else if( cg.predictedPlayerState.powerups[PW_BLUEFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_BLUE, force2D); + } else if( cg.predictedPlayerState.powerups[PW_NEUTRALFLAG] ) { + CG_DrawFlagModel( rect->x + adj, rect->y + adj, rect->w - adj, rect->h - adj, TEAM_FREE, force2D); + } +} + +static void CG_DrawAreaSystemChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, systemChat, 0, 0, 0); +} + +static void CG_DrawAreaTeamChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color,teamChat1, 0, 0, 0); +} + +static void CG_DrawAreaChat(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, teamChat2, 0, 0, 0); +} + +const char *CG_GetKillerText() { + const char *s = ""; + if ( cg.killerName[0] ) { + s = va("Fragged by %s", cg.killerName ); + } + return s; +} + + +static void CG_DrawKiller(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + // fragged by ... line + if ( cg.killerName[0] ) { + int x = rect->x + rect->w / 2; + CG_Text_Paint(x - CG_Text_Width(CG_GetKillerText(), scale, 0) / 2, rect->y + rect->h, scale, color, CG_GetKillerText(), 0, 0, textStyle); + } + +} + + +static void CG_DrawCapFragLimit(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + int limit = (cgs.gametype >= GT_CTF) ? cgs.capturelimit : cgs.fraglimit; + CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", limit),0, 0, textStyle); +} + +static void CG_Draw1stPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + if (cgs.scores1 != SCORE_NOT_PRESENT) { + CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores1),0, 0, textStyle); + } +} + +static void CG_Draw2ndPlace(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle) { + if (cgs.scores2 != SCORE_NOT_PRESENT) { + CG_Text_Paint(rect->x, rect->y, scale, color, va("%2i", cgs.scores2),0, 0, textStyle); + } +} + +const char *CG_GetGameStatusText() { + const char *s = ""; + if ( cgs.gametype < GT_TEAM) { + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + s = va("%s place with %i",CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ),cg.snap->ps.persistant[PERS_SCORE] ); + } + } else { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va("Teams are tied at %i", cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va("Red leads Blue, %i to %i", cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va("Blue leads Red, %i to %i", cg.teamScores[1], cg.teamScores[0] ); + } + } + return s; +} + +static void CG_DrawGameStatus(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GetGameStatusText(), 0, 0, textStyle); +} + +const char *CG_GameTypeString() { + if ( cgs.gametype == GT_FFA ) { + return "Free For All"; + } else if ( cgs.gametype == GT_TEAM ) { + return "Team Deathmatch"; + } else if ( cgs.gametype == GT_CTF ) { + return "Capture the Flag"; + } else if ( cgs.gametype == GT_1FCTF ) { + return "One Flag CTF"; + } else if ( cgs.gametype == GT_OBELISK ) { + return "Overload"; + } else if ( cgs.gametype == GT_HARVESTER ) { + return "Harvester"; + } + return ""; +} +static void CG_DrawGameType(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader, int textStyle ) { + CG_Text_Paint(rect->x, rect->y + rect->h, scale, color, CG_GameTypeString(), 0, 0, textStyle); +} + +static void CG_Text_Paint_Limit(float *maxX, float x, float y, float scale, vec4_t color, const char* text, float adjust, int limit) { + int len, count; + vec4_t newColor; + glyphInfo_t *glyph; + if (text) { +// TTimo: FIXME +// const unsigned char *s = text; // bk001206 - unsigned + const char *s = text; + float max = *maxX; + float useScale; + fontInfo_t *font = &cgDC.Assets.textFont; + if (scale <= cg_smallFont.value) { + font = &cgDC.Assets.smallFont; + } else if (scale > cg_bigFont.value) { + font = &cgDC.Assets.bigFont; + } + useScale = scale * font->glyphScale; + trap_R_SetColor( color ); + len = strlen(text); + if (limit > 0 && len > limit) { + len = limit; + } + count = 0; + while (s && *s && count < len) { + glyph = &font->glyphs[(int)*s]; // TTimo: FIXME: getting nasty warnings without the cast, hopefully this doesn't break the VM build + if ( Q_IsColorString( s ) ) { + memcpy( newColor, g_color_table[ColorIndex(*(s+1))], sizeof( newColor ) ); + newColor[3] = color[3]; + trap_R_SetColor( newColor ); + s += 2; + continue; + } else { + float yadj = useScale * glyph->top; + if (CG_Text_Width(s, useScale, 1) + x > max) { + *maxX = 0; + break; + } + CG_Text_PaintChar(x, y - yadj, + glyph->imageWidth, + glyph->imageHeight, + useScale, + glyph->s, + glyph->t, + glyph->s2, + glyph->t2, + glyph->glyph); + x += (glyph->xSkip * useScale) + adjust; + *maxX = x; + count++; + s++; + } + } + trap_R_SetColor( NULL ); + } + +} + + + +#define PIC_WIDTH 12 + +void CG_DrawNewTeamInfo(rectDef_t *rect, float text_x, float text_y, float scale, vec4_t color, qhandle_t shader) { + int xx; + float y; + int i, j, len, count; + const char *p; + vec4_t hcolor; + float pwidth, lwidth, maxx, leftOver; + clientInfo_t *ci; + gitem_t *item; + qhandle_t h; + + // max player name width + pwidth = 0; + count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + len = CG_Text_Width( ci->name, scale, 0); + if (len > pwidth) + pwidth = len; + } + } + + // max location name width + lwidth = 0; + for (i = 1; i < MAX_LOCATIONS; i++) { + p = CG_ConfigString(CS_LOCATIONS + i); + if (p && *p) { + len = CG_Text_Width(p, scale, 0); + if (len > lwidth) + lwidth = len; + } + } + + y = rect->y; + + for (i = 0; i < count; i++) { + ci = cgs.clientinfo + sortedTeamPlayers[i]; + if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { + + xx = rect->x + 1; + for (j = 0; j <= PW_NUM_POWERUPS; j++) { + if (ci->powerups & (1 << j)) { + + item = BG_FindItemForPowerup( j ); + + if (item) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, trap_R_RegisterShader( item->icon ) ); + xx += PIC_WIDTH; + } + } + } + + // FIXME: max of 3 powerups shown properly + xx = rect->x + (PIC_WIDTH * 3) + 2; + + CG_GetColorForHealth( ci->health, ci->armor, hcolor ); + trap_R_SetColor(hcolor); + CG_DrawPic( xx, y + 1, PIC_WIDTH - 2, PIC_WIDTH - 2, cgs.media.heartShader ); + + //Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); + //CG_Text_Paint(xx, y + text_y, scale, hcolor, st, 0, 0); + + // draw weapon icon + xx += PIC_WIDTH + 1; + +// weapon used is not that useful, use the space for task +#if 0 + if ( cg_weapons[ci->curWeapon].weaponIcon ) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, cgs.media.deferShader ); + } +#endif + + trap_R_SetColor(NULL); + if (cgs.orderPending) { + // blink the icon + if ( cg.time > cgs.orderTime - 2500 && (cg.time >> 9 ) & 1 ) { + h = 0; + } else { + h = CG_StatusHandle(cgs.currentOrder); + } + } else { + h = CG_StatusHandle(ci->teamTask); + } + + if (h) { + CG_DrawPic( xx, y, PIC_WIDTH, PIC_WIDTH, h); + } + + xx += PIC_WIDTH + 1; + + leftOver = rect->w - xx; + maxx = xx + leftOver / 3; + + + + CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, ci->name, 0, 0); + + p = CG_ConfigString(CS_LOCATIONS + ci->location); + if (!p || !*p) { + p = "unknown"; + } + + xx += leftOver / 3 + 2; + maxx = rect->w - 4; + + CG_Text_Paint_Limit(&maxx, xx, y + text_y, scale, color, p, 0, 0); + y += text_y + 2; + if ( y + text_y + 2 > rect->y + rect->h ) { + break; + } + + } + } +} + + +void CG_DrawTeamSpectators(rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + if (cg.spectatorLen) { + float maxX; + + if (cg.spectatorWidth == -1) { + cg.spectatorWidth = 0; + cg.spectatorPaintX = rect->x + 1; + cg.spectatorPaintX2 = -1; + } + + if (cg.spectatorOffset > cg.spectatorLen) { + cg.spectatorOffset = 0; + cg.spectatorPaintX = rect->x + 1; + cg.spectatorPaintX2 = -1; + } + + if (cg.time > cg.spectatorTime) { + cg.spectatorTime = cg.time + 10; + if (cg.spectatorPaintX <= rect->x + 2) { + if (cg.spectatorOffset < cg.spectatorLen) { + cg.spectatorPaintX += CG_Text_Width(&cg.spectatorList[cg.spectatorOffset], scale, 1) - 1; + cg.spectatorOffset++; + } else { + cg.spectatorOffset = 0; + if (cg.spectatorPaintX2 >= 0) { + cg.spectatorPaintX = cg.spectatorPaintX2; + } else { + cg.spectatorPaintX = rect->x + rect->w - 2; + } + cg.spectatorPaintX2 = -1; + } + } else { + cg.spectatorPaintX--; + if (cg.spectatorPaintX2 >= 0) { + cg.spectatorPaintX2--; + } + } + } + + maxX = rect->x + rect->w - 2; + CG_Text_Paint_Limit(&maxX, cg.spectatorPaintX, rect->y + rect->h - 3, scale, color, &cg.spectatorList[cg.spectatorOffset], 0, 0); + if (cg.spectatorPaintX2 >= 0) { + float maxX2 = rect->x + rect->w - 2; + CG_Text_Paint_Limit(&maxX2, cg.spectatorPaintX2, rect->y + rect->h - 3, scale, color, cg.spectatorList, 0, cg.spectatorOffset); + } + if (cg.spectatorOffset && maxX > 0) { + // if we have an offset ( we are skipping the first part of the string ) and we fit the string + if (cg.spectatorPaintX2 == -1) { + cg.spectatorPaintX2 = rect->x + rect->w - 2; + } + } else { + cg.spectatorPaintX2 = -1; + } + + } +} + + + +void CG_DrawMedal(int ownerDraw, rectDef_t *rect, float scale, vec4_t color, qhandle_t shader) { + score_t *score = &cg.scores[cg.selectedScore]; + float value = 0; + char *text = NULL; + color[3] = 0.25; + + switch (ownerDraw) { + case CG_ACCURACY: + value = score->accuracy; + break; + case CG_ASSISTS: + value = score->assistCount; + break; + case CG_DEFEND: + value = score->defendCount; + break; + case CG_EXCELLENT: + value = score->excellentCount; + break; + case CG_IMPRESSIVE: + value = score->impressiveCount; + break; + case CG_PERFECT: + value = score->perfect; + break; + case CG_GAUNTLET: + value = score->guantletCount; + break; + case CG_CAPTURES: + value = score->captures; + break; + //PKMOD - Ergodic 02/28/04 - add PainKiller medal awards to ownerdraw + case CG_PAINKILLER: + value = score->painkillerCount; + break; + } + + if (value > 0) { + if (ownerDraw != CG_PERFECT) { + if (ownerDraw == CG_ACCURACY) { + text = va("%i%%", (int)value); + if (value > 50) { + color[3] = 1.0; + } + } else { + text = va("%i", (int)value); + color[3] = 1.0; + } + } else { + if (value) { + color[3] = 1.0; + } + text = "Wow"; + } + } + + trap_R_SetColor(color); + CG_DrawPic( rect->x, rect->y, rect->w, rect->h, shader ); + + if (text) { + color[3] = 1.0; + value = CG_Text_Width(text, scale, 0); + CG_Text_Paint(rect->x + (rect->w - value) / 2, rect->y + rect->h + 10 , scale, color, text, 0, 0, 0); + } + trap_R_SetColor(NULL); + +} + + +// +void CG_OwnerDraw(float x, float y, float w, float h, float text_x, float text_y, int ownerDraw, int ownerDrawFlags, int align, float special, float scale, vec4_t color, qhandle_t shader, int textStyle) { + rectDef_t rect; + + if ( cg_drawStatus.integer == 0 ) { + return; + } + + //if (ownerDrawFlags != 0 && !CG_OwnerDrawVisible(ownerDrawFlags)) { + // return; + //} + + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + switch (ownerDraw) { + case CG_PLAYER_ARMOR_ICON: + CG_DrawPlayerArmorIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_ARMOR_ICON2D: + CG_DrawPlayerArmorIcon(&rect, qtrue); + break; + case CG_PLAYER_ARMOR_VALUE: + CG_DrawPlayerArmorValue(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_AMMO_ICON: + CG_DrawPlayerAmmoIcon(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_AMMO_ICON2D: + CG_DrawPlayerAmmoIcon(&rect, qtrue); + break; + case CG_PLAYER_AMMO_VALUE: + CG_DrawPlayerAmmoValue(&rect, scale, color, shader, textStyle); + break; + + //PKMOD - Ergodic 02/07/04 - add horizontal meters for ammo + case CG_PLAYER_AMMO_METER_HR2L: //Meter: Horizontal Right to Left fill + CG_DrawPlayerAmmoMeter_HR2L(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_AMMO_METER_HL2R: //Meter: Horizontal Left to Right fill + CG_DrawPlayerAmmoMeter_HL2R(&rect, scale, color, shader, textStyle); + break; + + //PKMOD - Ergodic 02/08/04 - add horizontal meters for armor + case CG_PLAYER_ARMOR_METER_HR2L: //Meter: Horizontal Right to Left fill + CG_DrawPlayerArmorMeter_HR2L(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_ARMOR_METER_HL2R: //Meter: Horizontal Left to Right fill + CG_DrawPlayerArmorMeter_HL2R(&rect, scale, color, shader, textStyle); + break; + + //PKMOD - Ergodic 02/08/04 - add relative score value to the HUD's gauges + case CG_PLAYER_RELATIVE_SCORE: + CG_DrawPlayerRelativeScore(&rect, scale, color, shader, textStyle); + break; + + case CG_SELECTEDPLAYER_HEAD: + CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qfalse); + break; + case CG_VOICE_HEAD: + CG_DrawSelectedPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY, qtrue); + break; + case CG_VOICE_NAME: + CG_DrawSelectedPlayerName(&rect, scale, color, qtrue, textStyle); + break; + case CG_SELECTEDPLAYER_STATUS: + CG_DrawSelectedPlayerStatus(&rect); + break; + case CG_SELECTEDPLAYER_ARMOR: + CG_DrawSelectedPlayerArmor(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_HEALTH: + CG_DrawSelectedPlayerHealth(&rect, scale, color, shader, textStyle); + break; + case CG_SELECTEDPLAYER_NAME: + CG_DrawSelectedPlayerName(&rect, scale, color, qfalse, textStyle); + break; + case CG_SELECTEDPLAYER_LOCATION: + CG_DrawSelectedPlayerLocation(&rect, scale, color, textStyle); + break; + case CG_SELECTEDPLAYER_WEAPON: + CG_DrawSelectedPlayerWeapon(&rect); + break; + case CG_SELECTEDPLAYER_POWERUP: + CG_DrawSelectedPlayerPowerup(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_HEAD: + CG_DrawPlayerHead(&rect, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_ITEM: + CG_DrawPlayerItem(&rect, scale, ownerDrawFlags & CG_SHOW_2DONLY); + break; + case CG_PLAYER_SCORE: + CG_DrawPlayerScore(&rect, scale, color, shader, textStyle); + break; + case CG_PLAYER_HEALTH: + CG_DrawPlayerHealth(&rect, scale, color, shader, textStyle); + break; + case CG_RED_SCORE: + CG_DrawRedScore(&rect, scale, color, shader, textStyle); + break; + case CG_BLUE_SCORE: + CG_DrawBlueScore(&rect, scale, color, shader, textStyle); + break; + case CG_RED_NAME: + CG_DrawRedName(&rect, scale, color, textStyle); + break; + case CG_BLUE_NAME: + CG_DrawBlueName(&rect, scale, color, textStyle); + break; + case CG_BLUE_FLAGHEAD: + CG_DrawBlueFlagHead(&rect); + break; + case CG_BLUE_FLAGSTATUS: + CG_DrawBlueFlagStatus(&rect, shader); + break; + case CG_BLUE_FLAGNAME: + CG_DrawBlueFlagName(&rect, scale, color, textStyle); + break; + case CG_RED_FLAGHEAD: + CG_DrawRedFlagHead(&rect); + break; + case CG_RED_FLAGSTATUS: + CG_DrawRedFlagStatus(&rect, shader); + break; + case CG_RED_FLAGNAME: + CG_DrawRedFlagName(&rect, scale, color, textStyle); + break; + case CG_HARVESTER_SKULLS: + CG_HarvesterSkulls(&rect, scale, color, qfalse, textStyle); + break; + case CG_HARVESTER_SKULLS2D: + CG_HarvesterSkulls(&rect, scale, color, qtrue, textStyle); + break; + case CG_ONEFLAG_STATUS: + CG_OneFlagStatus(&rect); + break; + case CG_PLAYER_LOCATION: + CG_DrawPlayerLocation(&rect, scale, color, textStyle); + break; + case CG_TEAM_COLOR: + CG_DrawTeamColor(&rect, color); + break; + case CG_CTF_POWERUP: + //PKMOD - Ergodic 01/28/04 - Dynamic HUD activation: Ignore this code since PKA does not have TA type of powerups + //CG_DrawCTFPowerUp(&rect); + break; + case CG_AREA_POWERUP: + CG_DrawAreaPowerUp(&rect, align, special, scale, color); + break; + case CG_PLAYER_STATUS: + CG_DrawPlayerStatus(&rect); + break; + case CG_PLAYER_HASFLAG: + CG_DrawPlayerHasFlag(&rect, qfalse); + break; + case CG_PLAYER_HASFLAG2D: + CG_DrawPlayerHasFlag(&rect, qtrue); + break; + case CG_AREA_SYSTEMCHAT: + CG_DrawAreaSystemChat(&rect, scale, color, shader); + break; + case CG_AREA_TEAMCHAT: + CG_DrawAreaTeamChat(&rect, scale, color, shader); + break; + case CG_AREA_CHAT: + CG_DrawAreaChat(&rect, scale, color, shader); + break; + case CG_GAME_TYPE: + CG_DrawGameType(&rect, scale, color, shader, textStyle); + break; + case CG_GAME_STATUS: + CG_DrawGameStatus(&rect, scale, color, shader, textStyle); + break; + case CG_KILLER: + CG_DrawKiller(&rect, scale, color, shader, textStyle); + break; + case CG_ACCURACY: + case CG_ASSISTS: + case CG_DEFEND: + case CG_EXCELLENT: + case CG_IMPRESSIVE: + case CG_PERFECT: + case CG_GAUNTLET: + case CG_CAPTURES: + //PKMOD - Ergodic 02/28/04 - add PainKiller medal awards to ownerdraw + case CG_PAINKILLER: + CG_DrawMedal(ownerDraw, &rect, scale, color, shader); + break; + case CG_SPECTATORS: + CG_DrawTeamSpectators(&rect, scale, color, shader); + break; + case CG_TEAMINFO: + if (cg_currentSelectedPlayer.integer == numSortedTeamPlayers) { + CG_DrawNewTeamInfo(&rect, text_x, text_y, scale, color, shader); + } + break; + case CG_CAPFRAGLIMIT: + CG_DrawCapFragLimit(&rect, scale, color, shader, textStyle); + break; + case CG_1STPLACE: + CG_Draw1stPlace(&rect, scale, color, shader, textStyle); + break; + case CG_2NDPLACE: + CG_Draw2ndPlace(&rect, scale, color, shader, textStyle); + break; + default: + break; + } +} + +void CG_MouseEvent(int x, int y) { + int n; + + if ( (cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_SPECTATOR) && cg.showScores == qfalse) { + trap_Key_SetCatcher(0); + return; + } + + cgs.cursorX+= x; + if (cgs.cursorX < 0) + cgs.cursorX = 0; + else if (cgs.cursorX > 640) + cgs.cursorX = 640; + + cgs.cursorY += y; + if (cgs.cursorY < 0) + cgs.cursorY = 0; + else if (cgs.cursorY > 480) + cgs.cursorY = 480; + + n = Display_CursorType(cgs.cursorX, cgs.cursorY); + cgs.activeCursor = 0; + if (n == CURSOR_ARROW) { + cgs.activeCursor = cgs.media.selectCursor; + } else if (n == CURSOR_SIZER) { + cgs.activeCursor = cgs.media.sizeCursor; + } + + if (cgs.capturedItem) { + Display_MouseMove(cgs.capturedItem, x, y); + } else { + Display_MouseMove(NULL, cgs.cursorX, cgs.cursorY); + } + +} + +/* +================== +CG_HideTeamMenus +================== + +*/ +void CG_HideTeamMenu() { + Menus_CloseByName("teamMenu"); + Menus_CloseByName("getMenu"); +} + +/* +================== +CG_ShowTeamMenus +================== + +*/ +void CG_ShowTeamMenu() { + Menus_OpenByName("teamMenu"); +} + + + + +/* +================== +CG_EventHandling +================== + type 0 - no event handling + 1 - team menu + 2 - hud editor + +*/ +void CG_EventHandling(int type) { + cgs.eventHandling = type; + if (type == CGAME_EVENT_NONE) { + CG_HideTeamMenu(); + } else if (type == CGAME_EVENT_TEAMMENU) { + //CG_ShowTeamMenu(); + } else if (type == CGAME_EVENT_SCOREBOARD) { + } + +} + + + +void CG_KeyEvent(int key, qboolean down) { + + if (!down) { + return; + } + + if ( cg.predictedPlayerState.pm_type == PM_NORMAL || (cg.predictedPlayerState.pm_type == PM_SPECTATOR && cg.showScores == qfalse)) { + CG_EventHandling(CGAME_EVENT_NONE); + trap_Key_SetCatcher(0); + return; + } + + //if (key == trap_Key_GetKey("teamMenu") || !Display_CaptureItem(cgs.cursorX, cgs.cursorY)) { + // if we see this then we should always be visible + // CG_EventHandling(CGAME_EVENT_NONE); + // trap_Key_SetCatcher(0); + //} + + + + Display_HandleKey(key, down, cgs.cursorX, cgs.cursorY); + + if (cgs.capturedItem) { + cgs.capturedItem = NULL; + } else { + if (key == K_MOUSE2 && down) { + cgs.capturedItem = Display_CaptureItem(cgs.cursorX, cgs.cursorY); + } + } +} + +int CG_ClientNumFromName(const char *p) { + int i; + for (i = 0; i < cgs.maxclients; i++) { + if (cgs.clientinfo[i].infoValid && Q_stricmp(cgs.clientinfo[i].name, p) == 0) { + return i; + } + } + return -1; +} + +void CG_ShowResponseHead() { + Menus_OpenByName("voiceMenu"); + trap_Cvar_Set("cl_conXOffset", "72"); + cg.voiceTime = cg.time; +} + +void CG_RunMenuScript(char **args) { +} + + +void CG_GetTeamColor(vec4_t *color) { + if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED) { + (*color)[0] = 1.0f; + (*color)[3] = 0.25f; + (*color)[1] = (*color)[2] = 0.0f; + } else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE) { + (*color)[0] = (*color)[1] = 0.0f; + (*color)[2] = 1.0f; + (*color)[3] = 0.25f; + } else { + (*color)[0] = (*color)[2] = 0.0f; + (*color)[1] = 0.17f; + (*color)[3] = 0.25f; + } +} + diff --git a/quake3/source/code/cgame/cg_particles.c b/quake3/source/code/cgame/cg_particles.c new file mode 100644 index 0000000..16767c8 --- /dev/null +++ b/quake3/source/code/cgame/cg_particles.c @@ -0,0 +1,1997 @@ +// Rafael particles +// cg_particles.c + +#include "cg_local.h" + +#define BLOODRED 2 +#define EMISIVEFADE 3 +#define GREY75 4 + +typedef struct particle_s +{ + struct particle_s *next; + + float time; + float endtime; + + vec3_t org; + vec3_t vel; + vec3_t accel; + int color; + float colorvel; + float alpha; + float alphavel; + int type; + qhandle_t pshader; + + float height; + float width; + + float endheight; + float endwidth; + + float start; + float end; + + float startfade; + qboolean rotate; + int snum; + + qboolean link; + + // Ridah + int shaderAnim; + int roll; + + int accumroll; + +} cparticle_t; + +typedef enum +{ + P_NONE, + P_WEATHER, + P_FLAT, + P_SMOKE, + P_ROTATE, + P_WEATHER_TURBULENT, + P_ANIM, // Ridah + P_BAT, + P_BLEED, + P_FLAT_SCALEUP, + P_FLAT_SCALEUP_FADE, + P_WEATHER_FLURRY, + P_SMOKE_IMPACT, + P_BUBBLE, + P_BUBBLE_TURBULENT, + P_SPRITE +} particle_type_t; + +#define MAX_SHADER_ANIMS 32 +#define MAX_SHADER_ANIM_FRAMES 64 + +static char *shaderAnimNames[MAX_SHADER_ANIMS] = { + "explode1", + "blacksmokeanim", + "twiltb2", + "expblue", + "blacksmokeanimb", // uses 'explode1' sequence + "blood", + NULL +}; +static qhandle_t shaderAnims[MAX_SHADER_ANIMS][MAX_SHADER_ANIM_FRAMES]; +static int shaderAnimCounts[MAX_SHADER_ANIMS] = { + 23, + 25, + 45, + 25, + 23, + 5, +}; +static float shaderAnimSTRatio[MAX_SHADER_ANIMS] = { + 1.405f, + 1.0f, + 1.0f, + 1.0f, + 1.0f, + 1.0f, +}; +static int numShaderAnims; +// done. + +#define PARTICLE_GRAVITY 40 +#define MAX_PARTICLES 1024 * 8 + +cparticle_t *active_particles, *free_particles; +cparticle_t particles[MAX_PARTICLES]; +int cl_numparticles = MAX_PARTICLES; + +qboolean initparticles = qfalse; +vec3_t vforward, vright, vup; +vec3_t rforward, rright, rup; + +float oldtime; + +/* +=============== +CL_ClearParticles +=============== +*/ +void CG_ClearParticles (void) +{ + int i; + + memset( particles, 0, sizeof(particles) ); + + free_particles = &particles[0]; + active_particles = NULL; + + for (i=0 ;itype == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY + || p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + {// create a front facing polygon + + if (p->type != P_WEATHER_FLURRY) + { + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + if (org[2] > p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + p->org[2] = ( p->start + crandom () * 4 ); + + + if (p->type == P_BUBBLE_TURBULENT) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + } + } + else + { + if (org[2] < p->end) + { + p->time = cg.time; + VectorCopy (org, p->org); // Ridah, fixes rare snow flakes that flicker on the ground + + while (p->org[2] < p->end) + { + p->org[2] += (p->start - p->end); + } + + + if (p->type == P_WEATHER_TURBULENT) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + } + } + + + // Rafael snow pvs check + if (!p->link) + return; + + p->alpha = 1; + } + + // Ridah, had to do this or MAX_POLYS is being exceeded in village1.bsp + if (Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + // done. + + if (p->type == P_BUBBLE || p->type == P_BUBBLE_TURBULENT) + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, -p->height, vup, point); + VectorMA (point, p->width, vright, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, p->width, vright, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255 * p->alpha; + } + else + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy( point, TRIverts[0].xyz ); + TRIverts[0].st[0] = 1; + TRIverts[0].st[1] = 0; + TRIverts[0].modulate[0] = 255; + TRIverts[0].modulate[1] = 255; + TRIverts[0].modulate[2] = 255; + TRIverts[0].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + VectorCopy (point, TRIverts[1].xyz); + TRIverts[1].st[0] = 0; + TRIverts[1].st[1] = 0; + TRIverts[1].modulate[0] = 255; + TRIverts[1].modulate[1] = 255; + TRIverts[1].modulate[2] = 255; + TRIverts[1].modulate[3] = 255 * p->alpha; + + VectorMA (org, p->height, vup, point); + VectorMA (point, p->width, vright, point); + VectorCopy (point, TRIverts[2].xyz); + TRIverts[2].st[0] = 0; + TRIverts[2].st[1] = 1; + TRIverts[2].modulate[0] = 255; + TRIverts[2].modulate[1] = 255; + TRIverts[2].modulate[2] = 255; + TRIverts[2].modulate[3] = 255 * p->alpha; + } + + } + else if (p->type == P_SPRITE) + { + vec3_t rr, ru; + vec3_t rotate_ang; + + VectorSet (color, 1.0, 1.0, 1.0); + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, vup, point); + VectorMA (point, -width, vright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, vup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, vright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, vup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + else if (p->type == P_SMOKE || p->type == P_SMOKE_IMPACT) + {// create a front rotating facing polygon + + if ( p->type == P_SMOKE_IMPACT && Distance( cg.snap->ps.origin, org ) > 1024) { + return; + } + + if (p->color == BLOODRED) + VectorSet (color, 0.22f, 0.0f, 0.0f); + else if (p->color == GREY75) + { + float len; + float greyit; + float val; + len = Distance (cg.snap->ps.origin, org); + if (!len) + len = 1; + + val = 4096/len; + greyit = 0.25 * val; + if (greyit > 0.5) + greyit = 0.5; + + VectorSet (color, greyit, greyit, greyit); + } + else + VectorSet (color, 1.0, 1.0, 1.0); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + if (cg.time > p->startfade) + { + invratio = 1 - ( (cg.time - p->startfade) / (p->endtime - p->startfade) ); + + if (p->color == EMISIVEFADE) + { + float fval; + fval = (invratio * invratio); + if (fval < 0) + fval = 0; + VectorSet (color, fval , fval , fval ); + } + invratio *= p->alpha; + } + else + invratio = 1 * p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + invratio = 1; + + if (invratio > 1) + invratio = 1; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (p->type != P_SMOKE_IMPACT) + { + vec3_t temp; + + vectoangles (rforward, temp); + p->accumroll += p->roll; + temp[ROLL] += p->accumroll * 0.1; + AngleVectors ( temp, NULL, rright2, rup2); + } + else + { + VectorCopy (rright, rright2); + VectorCopy (rup, rup2); + } + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, -p->width, vright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, -height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, -p->height, vup, point); + VectorMA (point, p->width, vright, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, width, rright2, point); + } + else + { + VectorMA (org, p->height, vup, point); + VectorMA (point, p->width, vright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255 * invratio; + + if (p->rotate) + { + VectorMA (org, height, rup2, point); + VectorMA (point, -width, rright2, point); + } + else + { + VectorMA (org, p->height, vup, point); + VectorMA (point, -p->width, vright, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255 * invratio; + + } + else if (p->type == P_BLEED) + { + vec3_t rr, ru; + vec3_t rotate_ang; + float alpha; + + alpha = p->alpha; + + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) + alpha = 1; + + if (p->roll) + { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + else + { + VectorCopy (vup, ru); + VectorCopy (vright, rr); + } + + VectorMA (org, -p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 111; + verts[0].modulate[1] = 19; + verts[0].modulate[2] = 9; + verts[0].modulate[3] = 255 * alpha; + + VectorMA (org, -p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 111; + verts[1].modulate[1] = 19; + verts[1].modulate[2] = 9; + verts[1].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, p->width, rr, point); + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 111; + verts[2].modulate[1] = 19; + verts[2].modulate[2] = 9; + verts[2].modulate[3] = 255 * alpha; + + VectorMA (org, p->height, ru, point); + VectorMA (point, -p->width, rr, point); + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 111; + verts[3].modulate[1] = 19; + verts[3].modulate[2] = 9; + verts[3].modulate[3] = 255 * alpha; + + } + else if (p->type == P_FLAT_SCALEUP) + { + float width, height; + float sinR, cosR; + + if (p->color == BLOODRED) + VectorSet (color, 1, 1, 1); + else + VectorSet (color, 0.5, 0.5, 0.5); + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + if (width > p->endwidth) + width = p->endwidth; + + if (height > p->endheight) + height = p->endheight; + + sinR = height * sin(DEG2RAD(p->roll)) * sqrt(2); + cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2); + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= sinR; + verts[0].xyz[1] -= cosR; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255 * color[0]; + verts[0].modulate[1] = 255 * color[1]; + verts[0].modulate[2] = 255 * color[2]; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= cosR; + verts[1].xyz[1] += sinR; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255 * color[0]; + verts[1].modulate[1] = 255 * color[1]; + verts[1].modulate[2] = 255 * color[2]; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += sinR; + verts[2].xyz[1] += cosR; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255 * color[0]; + verts[2].modulate[1] = 255 * color[1]; + verts[2].modulate[2] = 255 * color[2]; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += cosR; + verts[3].xyz[1] -= sinR; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255 * color[0]; + verts[3].modulate[1] = 255 * color[1]; + verts[3].modulate[2] = 255 * color[2]; + verts[3].modulate[3] = 255; + } + else if (p->type == P_FLAT) + { + + VectorCopy (org, verts[0].xyz); + verts[0].xyz[0] -= p->height; + verts[0].xyz[1] -= p->width; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy (org, verts[1].xyz); + verts[1].xyz[0] -= p->height; + verts[1].xyz[1] += p->width; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy (org, verts[2].xyz); + verts[2].xyz[0] += p->height; + verts[2].xyz[1] += p->width; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy (org, verts[3].xyz); + verts[3].xyz[0] += p->height; + verts[3].xyz[1] -= p->width; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + } + // Ridah + else if (p->type == P_ANIM) { + vec3_t rr, ru; + vec3_t rotate_ang; + int i, j; + + time = cg.time - p->time; + time2 = p->endtime - p->time; + ratio = time / time2; + if (ratio >= 1.0f) { + ratio = 0.9999f; + } + + width = p->width + ( ratio * ( p->endwidth - p->width) ); + height = p->height + ( ratio * ( p->endheight - p->height) ); + + // if we are "inside" this sprite, don't draw + if (Distance( cg.snap->ps.origin, org ) < width/1.5) { + return; + } + + i = p->shaderAnim; + j = (int)floor(ratio * shaderAnimCounts[p->shaderAnim]); + p->pshader = shaderAnims[i][j]; + + if (p->roll) { + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + rotate_ang[ROLL] += p->roll; + AngleVectors ( rotate_ang, NULL, rr, ru); + } + + if (p->roll) { + VectorMA (org, -height, ru, point); + VectorMA (point, -width, rr, point); + } else { + VectorMA (org, -height, vup, point); + VectorMA (point, -width, vright, point); + } + VectorCopy (point, verts[0].xyz); + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*height, ru, point); + } else { + VectorMA (point, 2*height, vup, point); + } + VectorCopy (point, verts[1].xyz); + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, 2*width, rr, point); + } else { + VectorMA (point, 2*width, vright, point); + } + VectorCopy (point, verts[2].xyz); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + if (p->roll) { + VectorMA (point, -2*height, ru, point); + } else { + VectorMA (point, -2*height, vup, point); + } + VectorCopy (point, verts[3].xyz); + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + } + // done. + + if (!p->pshader) { +// (SA) temp commented out for DM +// CG_Printf ("CG_AddParticleToScene type %d p->pshader == ZERO\n", p->type); + return; + } + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT || p->type == P_WEATHER_FLURRY) + trap_R_AddPolyToScene( p->pshader, 3, TRIverts ); + else + trap_R_AddPolyToScene( p->pshader, 4, verts ); + +} + +// Ridah, made this static so it doesn't interfere with other files +static float roll = 0.0; + +/* +=============== +CG_AddParticles +=============== +*/ +void CG_AddParticles (void) +{ + cparticle_t *p, *next; + float alpha; + float time, time2; + vec3_t org; + int color; + cparticle_t *active, *tail; + int type; + vec3_t rotate_ang; + + if (!initparticles) + CG_ClearParticles (); + + VectorCopy( cg.refdef.viewaxis[0], vforward ); + VectorCopy( cg.refdef.viewaxis[1], vright ); + VectorCopy( cg.refdef.viewaxis[2], vup ); + + vectoangles( cg.refdef.viewaxis[0], rotate_ang ); + roll += ((cg.time - oldtime) * 0.1) ; + rotate_ang[ROLL] += (roll*0.9); + AngleVectors ( rotate_ang, rforward, rright, rup); + + oldtime = cg.time; + + active = NULL; + tail = NULL; + + for (p=active_particles ; p ; p=next) + { + + next = p->next; + + time = (cg.time - p->time)*0.001; + + alpha = p->alpha + time*p->alphavel; + if (alpha <= 0) + { // faded out + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + if (p->type == P_SMOKE || p->type == P_ANIM || p->type == P_BLEED || p->type == P_SMOKE_IMPACT) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + + } + + if (p->type == P_WEATHER_FLURRY) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + + continue; + } + } + + + if (p->type == P_FLAT_SCALEUP_FADE) + { + if (cg.time > p->endtime) + { + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + } + + if ((p->type == P_BAT || p->type == P_SPRITE) && p->endtime < 0) { + // temporary sprite + CG_AddParticleToScene (p, p->org, alpha); + p->next = free_particles; + free_particles = p; + p->type = 0; + p->color = 0; + p->alpha = 0; + continue; + } + + p->next = NULL; + if (!tail) + active = tail = p; + else + { + tail->next = p; + tail = p; + } + + if (alpha > 1.0) + alpha = 1; + + color = p->color; + + time2 = time*time; + + org[0] = p->org[0] + p->vel[0]*time + p->accel[0]*time2; + org[1] = p->org[1] + p->vel[1]*time + p->accel[1]*time2; + org[2] = p->org[2] + p->vel[2]*time + p->accel[2]*time2; + + type = p->type; + + CG_AddParticleToScene (p, org, alpha); + } + + active_particles = active; +} + +/* +====================== +CG_AddParticles +====================== +*/ +void CG_ParticleSnowFlurry (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + qboolean turb = qtrue; + + if (!pshader) + CG_Printf ("CG_ParticleSnowFlurry pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.90f; + p->alphavel = 0; + + p->start = cent->currentState.origin2[0]; + p->end = cent->currentState.origin2[1]; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->pshader = pshader; + + if (rand()%100 > 90) + { + p->height = 32; + p->width = 32; + p->alpha = 0.10f; + } + else + { + p->height = 1; + p->width = 1; + } + + p->vel[2] = -20; + + p->type = P_WEATHER_FLURRY; + + if (turb) + p->vel[2] = -10; + + VectorCopy(cent->currentState.origin, p->org); + + p->org[0] = p->org[0]; + p->org[1] = p->org[1]; + p->org[2] = p->org[2]; + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += cent->currentState.angles[0] * 32 + (crandom() * 16); + p->vel[1] += cent->currentState.angles[1] * 32 + (crandom() * 16); + p->vel[2] += cent->currentState.angles[2]; + + if (turb) + { + p->accel[0] = crandom () * 16; + p->accel[1] = crandom () * 16; + } + +} + +void CG_ParticleSnow (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + p->height = 1; + p->width = 1; + + p->vel[2] = -50; + + if (turb) + { + p->type = P_WEATHER_TURBULENT; + p->vel[2] = -50 * 1.3; + } + else + { + p->type = P_WEATHER; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 16; + p->vel[1] = crandom() * 16; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleBubble (qhandle_t pshader, vec3_t origin, vec3_t origin2, int turb, float range, int snum) +{ + cparticle_t *p; + float randsize; + + if (!pshader) + CG_Printf ("CG_ParticleSnow pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->color = 0; + p->alpha = 0.40f; + p->alphavel = 0; + p->start = origin[2]; + p->end = origin2[2]; + p->pshader = pshader; + + randsize = 1 + (crandom() * 0.5); + + p->height = randsize; + p->width = randsize; + + p->vel[2] = 50 + ( crandom() * 10 ); + + if (turb) + { + p->type = P_BUBBLE_TURBULENT; + p->vel[2] = 50 * 1.3; + } + else + { + p->type = P_BUBBLE; + } + + VectorCopy(origin, p->org); + + p->org[0] = p->org[0] + ( crandom() * range); + p->org[1] = p->org[1] + ( crandom() * range); + p->org[2] = p->org[2] + ( crandom() * (p->start - p->end)); + + p->vel[0] = p->vel[1] = 0; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + if (turb) + { + p->vel[0] = crandom() * 4; + p->vel[1] = crandom() * 4; + } + + // Rafael snow pvs check + p->snum = snum; + p->link = qtrue; + +} + +void CG_ParticleSmoke (qhandle_t pshader, centity_t *cent) +{ + + // using cent->density = enttime + // cent->frame = startfade + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleSmoke == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + cent->currentState.time; + p->startfade = cg.time + cent->currentState.time2; + + p->color = 0; + p->alpha = 1.0; + p->alphavel = 0; + p->start = cent->currentState.origin[2]; + p->end = cent->currentState.origin2[2]; + p->pshader = pshader; + p->rotate = qfalse; + p->height = 8; + p->width = 8; + p->endheight = 32; + p->endwidth = 32; + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org); + + p->vel[0] = p->vel[1] = 0; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[2] = 5; + + if (cent->currentState.frame == 1)// reverse gravity + p->vel[2] *= -1; + + p->roll = 8 + (crandom() * 4); +} + + +void CG_ParticleBulletDebris (vec3_t org, vec3_t vel, int duration) +{ + + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 1.0; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->accel[2] = -60; + p->vel[2] += -20; + +} + +/* +====================== +CG_ParticleExplosion +====================== +*/ + +void CG_ParticleExplosion (char *animStr, vec3_t origin, vec3_t vel, int duration, int sizeStart, int sizeEnd) +{ + cparticle_t *p; + int anim; + + if (animStr < (char *)10) + CG_Error( "CG_ParticleExplosion: animStr is probably an index rather than a string" ); + + // find the animation string + for (anim=0; shaderAnimNames[anim]; anim++) { + if (!stricmp( animStr, shaderAnimNames[anim] )) + break; + } + if (!shaderAnimNames[anim]) { + CG_Error("CG_ParticleExplosion: unknown animation string: %s\n", animStr); + return; + } + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + + if (duration < 0) { + duration *= -1; + p->roll = 0; + } else { + p->roll = crandom()*179; + } + + p->shaderAnim = anim; + + p->width = sizeStart; + p->height = sizeStart*shaderAnimSTRatio[anim]; // for sprites that are stretch in either direction + + p->endheight = sizeEnd; + p->endwidth = sizeEnd*shaderAnimSTRatio[anim]; + + p->endtime = cg.time + duration; + + p->type = P_ANIM; + + VectorCopy( origin, p->org ); + VectorCopy( vel, p->vel ); + VectorClear( p->accel ); + +} + +// Rafael Shrapnel +void CG_AddParticleShrapnel (localEntity_t *le) +{ + return; +} +// done. + +int CG_NewParticleArea (int num) +{ + // const char *str; + char *str; + char *token; + int type; + vec3_t origin, origin2; + int i; + float range = 0; + int turb; + int numparticles; + int snum; + + str = (char *) CG_ConfigString (num); + if (!str[0]) + return (0); + + // returns type 128 64 or 32 + token = COM_Parse (&str); + type = atoi (token); + + if (type == 1) + range = 128; + else if (type == 2) + range = 64; + else if (type == 3) + range = 32; + else if (type == 0) + range = 256; + else if (type == 4) + range = 8; + else if (type == 5) + range = 16; + else if (type == 6) + range = 32; + else if (type == 7) + range = 64; + + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin[i] = atof (token); + } + + for (i=0; i<3; i++) + { + token = COM_Parse (&str); + origin2[i] = atof (token); + } + + token = COM_Parse (&str); + numparticles = atoi (token); + + token = COM_Parse (&str); + turb = atoi (token); + + token = COM_Parse (&str); + snum = atoi (token); + + for (i=0; i= 4) + CG_ParticleBubble (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + else + CG_ParticleSnow (cgs.media.waterBubbleShader, origin, origin2, turb, range, snum); + } + + return (1); +} + +void CG_SnowLink (centity_t *cent, qboolean particleOn) +{ + cparticle_t *p, *next; + int id; + + id = cent->currentState.frame; + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_WEATHER || p->type == P_WEATHER_TURBULENT) + { + if (p->snum == id) + { + if (particleOn) + p->link = qtrue; + else + p->link = qfalse; + } + } + + } +} + +void CG_ParticleImpactSmokePuff (qhandle_t pshader, vec3_t origin) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 0.25; + p->alphavel = 0; + p->roll = crandom()*179; + + p->pshader = pshader; + + p->endtime = cg.time + 1000; + p->startfade = cg.time + 100; + + p->width = rand()%4 + 8; + p->height = rand()%4 + 8; + + p->endheight = p->height *2; + p->endwidth = p->width * 2; + + p->endtime = cg.time + 500; + + p->type = P_SMOKE_IMPACT; + + VectorCopy( origin, p->org ); + VectorSet(p->vel, 0, 0, 20); + VectorSet(p->accel, 0, 0, 20); + + p->rotate = qtrue; +} + +void CG_Particle_Bleed (qhandle_t pshader, vec3_t start, vec3_t dir, int fleshEntityNum, int duration) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_Bleed pshader == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + if (fleshEntityNum) + p->startfade = cg.time; + else + p->startfade = cg.time + 100; + + p->width = 4; + p->height = 4; + + p->endheight = 4+rand()%3; + p->endwidth = p->endheight; + + p->type = P_SMOKE; + + VectorCopy( start, p->org ); + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -20; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + p->alpha = 0.75; + +} + +void CG_Particle_OilParticle (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + int time; + int time2; + float ratio; + + float duration = 1500; + + time = cg.time; + time2 = cg.time + cent->currentState.time; + + ratio =(float)1 - ((float)time / (float)time2); + + if (!pshader) + CG_Printf ("CG_Particle_OilParticle == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + p->endtime = cg.time + duration; + + p->startfade = p->endtime; + + p->width = 1; + p->height = 3; + + p->endheight = 3; + p->endwidth = 1; + + p->type = P_SMOKE; + + VectorCopy(cent->currentState.origin, p->org ); + + p->vel[0] = (cent->currentState.origin2[0] * (16 * ratio)); + p->vel[1] = (cent->currentState.origin2[1] * (16 * ratio)); + p->vel[2] = (cent->currentState.origin2[2]); + + p->snum = 1.0f; + + VectorClear( p->accel ); + + p->accel[2] = -20; + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + + +void CG_Particle_OilSlick (qhandle_t pshader, centity_t *cent) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_Particle_OilSlick == ZERO!\n"); + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + if (cent->currentState.angles2[2]) + p->endtime = cg.time + cent->currentState.angles2[2]; + else + p->endtime = cg.time + 60000; + + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + if (cent->currentState.angles2[0] || cent->currentState.angles2[1]) + { + p->width = cent->currentState.angles2[0]; + p->height = cent->currentState.angles2[0]; + + p->endheight = cent->currentState.angles2[1]; + p->endwidth = cent->currentState.angles2[1]; + } + else + { + p->width = 8; + p->height = 8; + + p->endheight = 16; + p->endwidth = 16; + } + + p->type = P_FLAT_SCALEUP; + + p->snum = 1.0; + + VectorCopy(cent->currentState.origin, p->org ); + + p->org[2]+= 0.55 + (crandom() * 0.5); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + +} + +void CG_OilSlickRemove (centity_t *cent) +{ + cparticle_t *p, *next; + int id; + + id = 1.0f; + + if (!id) + CG_Printf ("CG_OilSlickRevove NULL id\n"); + + for (p=active_particles ; p ; p=next) + { + next = p->next; + + if (p->type == P_FLAT_SCALEUP) + { + if (p->snum == id) + { + p->endtime = cg.time + 100; + p->startfade = p->endtime; + p->type = P_FLAT_SCALEUP_FADE; + + } + } + + } +} + +qboolean ValidBloodPool (vec3_t start) +{ +#define EXTRUDE_DIST 0.5 + + vec3_t angles; + vec3_t right, up; + vec3_t this_pos, x_pos, center_pos, end_pos; + float x, y; + float fwidth, fheight; + trace_t trace; + vec3_t normal; + + fwidth = 16; + fheight = 16; + + VectorSet (normal, 0, 0, 1); + + vectoangles (normal, angles); + AngleVectors (angles, NULL, right, up); + + VectorMA (start, EXTRUDE_DIST, normal, center_pos); + + for (x= -fwidth/2; xendpos, start); + legit = ValidBloodPool (start); + + if (!legit) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + 3000; + p->startfade = p->endtime; + + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = pshader; + + rndSize = 0.4 + random()*0.6; + + p->width = 8*rndSize; + p->height = 8*rndSize; + + p->endheight = 16*rndSize; + p->endwidth = 16*rndSize; + + p->type = P_FLAT_SCALEUP; + + VectorCopy(start, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = 0; + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + p->color = BLOODRED; +} + +#define NORMALSIZE 16 +#define LARGESIZE 32 + +void CG_ParticleBloodCloud (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + p->endtime = cg.time + 350 + (crandom() * 100); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + p->endheight = LARGESIZE; + p->endwidth = LARGESIZE; + + p->type = P_SMOKE; + + VectorCopy( origin, p->org ); + + p->vel[0] = 0; + p->vel[1] = 0; + p->vel[2] = -1; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->color = BLOODRED; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleSparks (vec3_t org, vec3_t vel, int duration, float x, float y, float speed) +{ + cparticle_t *p; + + if (!free_particles) + return; + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + + p->endtime = cg.time + duration; + p->startfade = cg.time + duration/2; + + p->color = EMISIVEFADE; + p->alpha = 0.4f; + p->alphavel = 0; + + p->height = 0.5; + p->width = 0.5; + p->endheight = 0.5; + p->endwidth = 0.5; + + p->pshader = cgs.media.tracerShader; + + p->type = P_SMOKE; + + VectorCopy(org, p->org); + + p->org[0] += (crandom() * x); + p->org[1] += (crandom() * y); + + p->vel[0] = vel[0]; + p->vel[1] = vel[1]; + p->vel[2] = vel[2]; + + p->accel[0] = p->accel[1] = p->accel[2] = 0; + + p->vel[0] += (crandom() * 4); + p->vel[1] += (crandom() * 4); + p->vel[2] += (20 + (crandom() * 10)) * speed; + + p->accel[0] = crandom () * 4; + p->accel[1] = crandom () * 4; + +} + +void CG_ParticleDust (centity_t *cent, vec3_t origin, vec3_t dir) +{ + float length; + float dist; + float crittersize; + vec3_t angles, forward; + vec3_t point; + cparticle_t *p; + int i; + + dist = 0; + + VectorNegate (dir, dir); + length = VectorLength (dir); + vectoangles (dir, angles); + AngleVectors (angles, forward, NULL, NULL); + + crittersize = LARGESIZE; + + if (length) + dist = length / crittersize; + + if (dist < 1) + dist = 1; + + VectorCopy (origin, point); + + for (i=0; inext; + p->next = active_particles; + active_particles = p; + + p->time = cg.time; + p->alpha = 5.0; + p->alphavel = 0; + p->roll = 0; + + p->pshader = cgs.media.smokePuffShader; + + // RF, stay around for long enough to expand and dissipate naturally + if (length) + p->endtime = cg.time + 4500 + (crandom() * 3500); + else + p->endtime = cg.time + 750 + (crandom() * 500); + + p->startfade = cg.time; + + p->width = LARGESIZE; + p->height = LARGESIZE; + + // RF, expand while falling + p->endheight = LARGESIZE*3.0; + p->endwidth = LARGESIZE*3.0; + + if (!length) + { + p->width *= 0.2f; + p->height *= 0.2f; + + p->endheight = NORMALSIZE; + p->endwidth = NORMALSIZE; + } + + p->type = P_SMOKE; + + VectorCopy( point, p->org ); + + p->vel[0] = crandom()*6; + p->vel[1] = crandom()*6; + p->vel[2] = random()*20; + + // RF, add some gravity/randomness + p->accel[0] = crandom()*3; + p->accel[1] = crandom()*3; + p->accel[2] = -PARTICLE_GRAVITY*0.4; + + VectorClear( p->accel ); + + p->rotate = qfalse; + + p->roll = rand()%179; + + p->alpha = 0.75; + + } + + +} + +void CG_ParticleMisc (qhandle_t pshader, vec3_t origin, int size, int duration, float alpha) +{ + cparticle_t *p; + + if (!pshader) + CG_Printf ("CG_ParticleImpactSmokePuff pshader == ZERO!\n"); + + if (!free_particles) + return; + + p = free_particles; + free_particles = p->next; + p->next = active_particles; + active_particles = p; + p->time = cg.time; + p->alpha = 1.0; + p->alphavel = 0; + p->roll = rand()%179; + + p->pshader = pshader; + + if (duration > 0) + p->endtime = cg.time + duration; + else + p->endtime = duration; + + p->startfade = cg.time; + + p->width = size; + p->height = size; + + p->endheight = size; + p->endwidth = size; + + p->type = P_SPRITE; + + VectorCopy( origin, p->org ); + + p->rotate = qfalse; +} diff --git a/quake3/source/code/cgame/cg_players.c b/quake3/source/code/cgame/cg_players.c new file mode 100644 index 0000000..4383a50 --- /dev/null +++ b/quake3/source/code/cgame/cg_players.c @@ -0,0 +1,2772 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_players.c -- handle the media and animation for player entities +#include "cg_local.h" + +char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { + "*death1.wav", + "*death2.wav", + "*death3.wav", + "*jump1.wav", + "*pain25_1.wav", + "*pain50_1.wav", + "*pain75_1.wav", + "*pain100_1.wav", + "*falling1.wav", + "*gasp.wav", + "*drown.wav", + "*fall1.wav", + "*taunt.wav" +}; + + +/* +================ +CG_CustomSound + +================ +*/ +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { + clientInfo_t *ci; + int i; + + if ( soundName[0] != '*' ) { + return trap_S_RegisterSound( soundName, qfalse ); + } + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i] ; i++ ) { + if ( !strcmp( soundName, cg_customSoundNames[i] ) ) { + return ci->sounds[i]; + } + } + + CG_Error( "Unknown custom sound: %s", soundName ); + return 0; +} + + + +/* +============================================================================= + +CLIENT INFO + +============================================================================= +*/ + +/* +====================== +CG_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players/visor/animation.cfg, etc +====================== +*/ +static qboolean CG_ParseAnimationFile( const char *filename, clientInfo_t *ci ) { + char *text_p, *prev; + int len; + int i; + char *token; + float fps; + int skip; + char text[20000]; + fileHandle_t f; + animation_t *animations; + + animations = ci->animations; + + // load the file + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( len <= 0 ) { + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) { + CG_Printf( "File %s too long\n", filename ); + return qfalse; + } + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + skip = 0; // quite the compiler warning + + ci->footsteps = FOOTSTEP_NORMAL; + VectorClear( ci->headOffset ); + ci->gender = GENDER_MALE; + ci->fixedlegs = qfalse; + ci->fixedtorso = qfalse; + + // read optional parameters + while ( 1 ) { + prev = text_p; // so we can unget + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "footsteps" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( !Q_stricmp( token, "default" ) || !Q_stricmp( token, "normal" ) ) { + ci->footsteps = FOOTSTEP_NORMAL; + } else if ( !Q_stricmp( token, "boot" ) ) { + ci->footsteps = FOOTSTEP_BOOT; + } else if ( !Q_stricmp( token, "flesh" ) ) { + ci->footsteps = FOOTSTEP_FLESH; + } else if ( !Q_stricmp( token, "mech" ) ) { + ci->footsteps = FOOTSTEP_MECH; + } else if ( !Q_stricmp( token, "energy" ) ) { + ci->footsteps = FOOTSTEP_ENERGY; + } else { + CG_Printf( "Bad footsteps parm in %s: %s\n", filename, token ); + } + continue; + } else if ( !Q_stricmp( token, "headoffset" ) ) { + for ( i = 0 ; i < 3 ; i++ ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + ci->headOffset[i] = atof( token ); + } + continue; + } else if ( !Q_stricmp( token, "sex" ) ) { + token = COM_Parse( &text_p ); + if ( !token ) { + break; + } + if ( token[0] == 'f' || token[0] == 'F' ) { + ci->gender = GENDER_FEMALE; + } else if ( token[0] == 'n' || token[0] == 'N' ) { + ci->gender = GENDER_NEUTER; + } else { + ci->gender = GENDER_MALE; + } + continue; + } else if ( !Q_stricmp( token, "fixedlegs" ) ) { + ci->fixedlegs = qtrue; + continue; + } else if ( !Q_stricmp( token, "fixedtorso" ) ) { + ci->fixedtorso = qtrue; + continue; + } + + // if it is a number, start parsing animations + if ( token[0] >= '0' && token[0] <= '9' ) { + text_p = prev; // unget the token + break; + } + Com_Printf( "unknown token '%s' is %s\n", token, filename ); + } + + // read information for each frame + for ( i = 0 ; i < MAX_ANIMATIONS ; i++ ) { + + token = COM_Parse( &text_p ); + if ( !*token ) { + if( i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE ) { + animations[i].firstFrame = animations[TORSO_GESTURE].firstFrame; + animations[i].frameLerp = animations[TORSO_GESTURE].frameLerp; + animations[i].initialLerp = animations[TORSO_GESTURE].initialLerp; + animations[i].loopFrames = animations[TORSO_GESTURE].loopFrames; + animations[i].numFrames = animations[TORSO_GESTURE].numFrames; + animations[i].reversed = qfalse; + animations[i].flipflop = qfalse; + continue; + } + break; + } + animations[i].firstFrame = atoi( token ); + // leg only frames are adjusted to not count the upper body only frames + if ( i == LEGS_WALKCR ) { + skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame; + } + if ( i >= LEGS_WALKCR && i0) { + return qtrue; + } + return qfalse; +} + +/* +========================== +CG_FindClientModelFile +========================== +*/ +static qboolean CG_FindClientModelFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext ) { + char *team, *charactersFolder; + int i; + + if ( cgs.gametype >= GT_TEAM ) { + switch ( ci->team ) { + case TEAM_BLUE: { + team = "blue"; + break; + } + default: { + team = "red"; + break; + } + } + } + else { + team = "default"; + } + charactersFolder = ""; + while(1) { + for ( i = 0; i < 2; i++ ) { + if ( i == 0 && teamName && *teamName ) { + // "models/players/characters/james/stroggs/lower_lily_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext ); + } + else { + // "models/players/characters/james/lower_lily_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext ); + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( cgs.gametype >= GT_TEAM ) { + if ( i == 0 && teamName && *teamName ) { + // "models/players/characters/james/stroggs/lower_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext ); + } + else { + // "models/players/characters/james/lower_red.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext ); + } + } + else { + if ( i == 0 && teamName && *teamName ) { + // "models/players/characters/james/stroggs/lower_lily.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext ); + } + else { + // "models/players/characters/james/lower_lily.skin" + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext ); + } + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( !teamName || !*teamName ) { + break; + } + } + // if tried the heads folder first + if ( charactersFolder[0] ) { + break; + } + charactersFolder = "characters/"; + } + + return qfalse; +} + +/* +========================== +CG_FindClientHeadFile +========================== +*/ +static qboolean CG_FindClientHeadFile( char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext ) { + char *team, *headsFolder; + int i; + + if ( cgs.gametype >= GT_TEAM ) { + switch ( ci->team ) { + case TEAM_BLUE: { + team = "blue"; + break; + } + default: { + team = "red"; + break; + } + } + } + else { + team = "default"; + } + + if ( headModelName[0] == '*' ) { + headsFolder = "heads/"; + headModelName++; + } + else { + headsFolder = ""; + } + while(1) { + for ( i = 0; i < 2; i++ ) { + if ( i == 0 && teamName && *teamName ) { + Com_sprintf( filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext ); + } + else { + Com_sprintf( filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext ); + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( cgs.gametype >= GT_TEAM ) { + if ( i == 0 && teamName && *teamName ) { + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext ); + } + else { + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext ); + } + } + else { + if ( i == 0 && teamName && *teamName ) { + Com_sprintf( filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext ); + } + else { + Com_sprintf( filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext ); + } + } + if ( CG_FileExists( filename ) ) { + return qtrue; + } + if ( !teamName || !*teamName ) { + break; + } + } + // if tried the heads folder first + if ( headsFolder[0] ) { + break; + } + headsFolder = "heads/"; + } + + return qfalse; +} + +/* +========================== +CG_RegisterClientSkin +========================== +*/ +static qboolean CG_RegisterClientSkin( clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName ) { + char filename[MAX_QPATH]; + + /* + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%slower_%s.skin", modelName, teamName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + if (!ci->legsSkin) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%slower_%s.skin", modelName, teamName, skinName ); + ci->legsSkin = trap_R_RegisterSkin( filename ); + if (!ci->legsSkin) { + Com_Printf( "Leg skin load failure: %s\n", filename ); + } + } + + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/%supper_%s.skin", modelName, teamName, skinName ); + ci->torsoSkin = trap_R_RegisterSkin( filename ); + if (!ci->torsoSkin) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/%supper_%s.skin", modelName, teamName, skinName ); + ci->torsoSkin = trap_R_RegisterSkin( filename ); + if (!ci->torsoSkin) { + Com_Printf( "Torso skin load failure: %s\n", filename ); + } + } + */ + if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "lower", "skin" ) ) { + ci->legsSkin = trap_R_RegisterSkin( filename ); + } + if (!ci->legsSkin) { + Com_Printf( "Leg skin load failure: %s\n", filename ); + } + + if ( CG_FindClientModelFile( filename, sizeof(filename), ci, teamName, modelName, skinName, "upper", "skin" ) ) { + ci->torsoSkin = trap_R_RegisterSkin( filename ); + } + if (!ci->torsoSkin) { + Com_Printf( "Torso skin load failure: %s\n", filename ); + } + + if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headModelName, headSkinName, "head", "skin" ) ) { + ci->headSkin = trap_R_RegisterSkin( filename ); + } + if (!ci->headSkin) { + Com_Printf( "Head skin load failure: %s\n", filename ); + } + + // if any skins failed to load + if ( !ci->legsSkin || !ci->torsoSkin || !ci->headSkin ) { + return qfalse; + } + return qtrue; +} + +/* +========================== +CG_RegisterClientModelname +========================== +*/ +static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName ) { + char filename[MAX_QPATH*2]; + const char *headName; + char newTeamName[MAX_QPATH*2]; + + if ( headModelName[0] == '\0' ) { + headName = modelName; + } + else { + headName = headModelName; + } + Com_sprintf( filename, sizeof( filename ), "models/players/%s/lower.md3", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + if ( !ci->legsModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/lower.md3", modelName ); + ci->legsModel = trap_R_RegisterModel( filename ); + if ( !ci->legsModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + Com_sprintf( filename, sizeof( filename ), "models/players/%s/upper.md3", modelName ); + ci->torsoModel = trap_R_RegisterModel( filename ); + if ( !ci->torsoModel ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/upper.md3", modelName ); + ci->torsoModel = trap_R_RegisterModel( filename ); + if ( !ci->torsoModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + } + + if( headName[0] == '*' ) { + Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1] ); + } + else { + Com_sprintf( filename, sizeof( filename ), "models/players/%s/head.md3", headName ); + } + ci->headModel = trap_R_RegisterModel( filename ); + // if the head model could not be found and we didn't load from the heads folder try to load from there + if ( !ci->headModel && headName[0] != '*' ) { + Com_sprintf( filename, sizeof( filename ), "models/players/heads/%s/%s.md3", headModelName, headModelName ); + ci->headModel = trap_R_RegisterModel( filename ); + } + if ( !ci->headModel ) { + Com_Printf( "Failed to load model file %s\n", filename ); + return qfalse; + } + + // if any skins failed to load, return failure + if ( !CG_RegisterClientSkin( ci, teamName, modelName, skinName, headName, headSkinName ) ) { + if ( teamName && *teamName) { + Com_Printf( "(CG 1) Failed to load skin file: %s : %s : %s, %s : %s\n", teamName, modelName, skinName, headName, headSkinName ); + if( ci->team == TEAM_BLUE ) { + Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_BLUETEAM_NAME); + } + else { + Com_sprintf(newTeamName, sizeof(newTeamName), "%s/", DEFAULT_REDTEAM_NAME); + } + if ( !CG_RegisterClientSkin( ci, newTeamName, modelName, skinName, headName, headSkinName ) ) { + Com_Printf( "(CG 2) Failed to load skin file: %s : %s : %s, %s : %s\n", newTeamName, modelName, skinName, headName, headSkinName ); + return qfalse; + } + } else { + Com_Printf( "(cg 3) Failed to load skin file: %s : %s, %s : %s\n", modelName, skinName, headName, headSkinName ); + return qfalse; + } + } + + // load the animations + Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", modelName ); + if ( !CG_ParseAnimationFile( filename, ci ) ) { + Com_sprintf( filename, sizeof( filename ), "models/players/characters/%s/animation.cfg", modelName ); + if ( !CG_ParseAnimationFile( filename, ci ) ) { + Com_Printf( "Failed to load animation file %s\n", filename ); + return qfalse; + } + } + + if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "skin" ) ) { + ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); + } + else if ( CG_FindClientHeadFile( filename, sizeof(filename), ci, teamName, headName, headSkinName, "icon", "tga" ) ) { + ci->modelIcon = trap_R_RegisterShaderNoMip( filename ); + } + + if ( !ci->modelIcon ) { + return qfalse; + } + + return qtrue; +} + +/* +==================== +CG_ColorFromString +==================== +*/ +static void CG_ColorFromString( const char *v, vec3_t color ) { + int val; + + VectorClear( color ); + + val = atoi( v ); + + if ( val < 1 || val > 7 ) { + VectorSet( color, 1, 1, 1 ); + return; + } + + if ( val & 1 ) { + color[2] = 1.0f; + } + if ( val & 2 ) { + color[1] = 1.0f; + } + if ( val & 4 ) { + color[0] = 1.0f; + } +} + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits. +This will usually be deferred to a safe time +=================== +*/ +static void CG_LoadClientInfo( clientInfo_t *ci ) { + const char *dir, *fallback; + int i, modelloaded; + const char *s; + int clientNum; + char teamname[MAX_QPATH]; + + teamname[0] = 0; +#ifdef MISSIONPACK + if( cgs.gametype >= GT_TEAM) { + if( ci->team == TEAM_BLUE ) { + Q_strncpyz(teamname, cg_blueTeamName.string, sizeof(teamname) ); + } else { + Q_strncpyz(teamname, cg_redTeamName.string, sizeof(teamname) ); + } + } + if( teamname[0] ) { + strcat( teamname, "/" ); + } +#endif + modelloaded = qtrue; + + if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ) ) { + if ( cg_buildScript.integer ) { + CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); + } + + // fall back to default team name + if( cgs.gametype >= GT_TEAM) { + // keep skin name + if( ci->team == TEAM_BLUE ) { + Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof(teamname) ); + } else { + Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof(teamname) ); + } + if ( !CG_RegisterClientModelname( ci, DEFAULT_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname ) ) { + CG_Error( "DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName ); + } + } else { + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname ) ) { + CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); + } + } + modelloaded = qfalse; + } + + ci->newAnims = qfalse; + if ( ci->torsoModel ) { + orientation_t tag; + // if the torso model has the "tag_flag" + if ( trap_R_LerpTag( &tag, ci->torsoModel, 0, 0, 1, "tag_flag" ) ) { + ci->newAnims = qtrue; + } + } + + // sounds + dir = ci->modelName; + fallback = (cgs.gametype >= GT_TEAM) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL; + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) { + s = cg_customSoundNames[i]; + if ( !s ) { + break; + } + ci->sounds[i] = 0; + // if the model didn't load use the sounds of the default model + if (modelloaded) { + ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", dir, s + 1), qfalse ); + } + if ( !ci->sounds[i] ) { + ci->sounds[i] = trap_S_RegisterSound( va("sound/player/%s/%s", fallback, s + 1), qfalse ); + } + } + + ci->deferred = qfalse; + + // reset any existing players and bodies, because they might be in bad + // frames for this new model + clientNum = ci - cgs.clientinfo; + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) { + if ( cg_entities[i].currentState.clientNum == clientNum + && cg_entities[i].currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( &cg_entities[i] ); + } + } +} + +/* +====================== +CG_CopyClientInfoModel +====================== +*/ +static void CG_CopyClientInfoModel( clientInfo_t *from, clientInfo_t *to ) { + VectorCopy( from->headOffset, to->headOffset ); + to->footsteps = from->footsteps; + to->gender = from->gender; + + to->legsModel = from->legsModel; + to->legsSkin = from->legsSkin; + to->torsoModel = from->torsoModel; + to->torsoSkin = from->torsoSkin; + to->headModel = from->headModel; + to->headSkin = from->headSkin; + to->modelIcon = from->modelIcon; + + to->newAnims = from->newAnims; + + memcpy( to->animations, from->animations, sizeof( to->animations ) ); + memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); +} + +/* +====================== +CG_ScanForExistingClientInfo +====================== +*/ +static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci ) { + int i; + clientInfo_t *match; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + if ( match->deferred ) { + continue; + } + if ( !Q_stricmp( ci->modelName, match->modelName ) + && !Q_stricmp( ci->skinName, match->skinName ) + && !Q_stricmp( ci->headModelName, match->headModelName ) + && !Q_stricmp( ci->headSkinName, match->headSkinName ) + && !Q_stricmp( ci->blueTeam, match->blueTeam ) + && !Q_stricmp( ci->redTeam, match->redTeam ) + && (cgs.gametype < GT_TEAM || ci->team == match->team) ) { + // this clientinfo is identical, so use it's handles + + ci->deferred = qfalse; + + CG_CopyClientInfoModel( match, ci ); + + return qtrue; + } + } + + // nothing matches, so defer the load + return qfalse; +} + +/* +====================== +CG_SetDeferredClientInfo + +We aren't going to load it now, so grab some other +client's info to use until we have some spare time. +====================== +*/ +static void CG_SetDeferredClientInfo( clientInfo_t *ci ) { + int i; + clientInfo_t *match; + + // if someone else is already the same models and skins we + // can just load the client info + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid || match->deferred ) { + continue; + } + if ( Q_stricmp( ci->skinName, match->skinName ) || + Q_stricmp( ci->modelName, match->modelName ) || +// Q_stricmp( ci->headModelName, match->headModelName ) || +// Q_stricmp( ci->headSkinName, match->headSkinName ) || + (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { + continue; + } + // just load the real info cause it uses the same models and skins + CG_LoadClientInfo( ci ); + return; + } + + // if we are in teamplay, only grab a model if the skin is correct + if ( cgs.gametype >= GT_TEAM ) { + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid || match->deferred ) { + continue; + } + if ( Q_stricmp( ci->skinName, match->skinName ) || + (cgs.gametype >= GT_TEAM && ci->team != match->team) ) { + continue; + } + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + // load the full model, because we don't ever want to show + // an improper team skin. This will cause a hitch for the first + // player, when the second enters. Combat shouldn't be going on + // yet, so it shouldn't matter + CG_LoadClientInfo( ci ); + return; + } + + // find the first valid clientinfo and grab its stuff + for ( i = 0 ; i < cgs.maxclients ; i++ ) { + match = &cgs.clientinfo[ i ]; + if ( !match->infoValid ) { + continue; + } + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + + // we should never get here... + CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); + + CG_LoadClientInfo( ci ); +} + + +/* +====================== +CG_NewClientInfo +====================== +*/ +void CG_NewClientInfo( int clientNum ) { + clientInfo_t *ci; + clientInfo_t newInfo; + const char *configstring; + const char *v; + char *slash; + + ci = &cgs.clientinfo[clientNum]; + + configstring = CG_ConfigString( clientNum + CS_PLAYERS ); + if ( !configstring[0] ) { + memset( ci, 0, sizeof( *ci ) ); + return; // player just left + } + + //PKMOD - Ergodic 01/09/02 - debug bot's userinfo (inactive) +// Com_Printf("CG_NewClientInfo - configstring>%s<\n", configstring); + + // build into a temp buffer so the defer checks can use + // the old value + memset( &newInfo, 0, sizeof( newInfo ) ); + + // isolate the player's name + v = Info_ValueForKey(configstring, "n"); + Q_strncpyz( newInfo.name, v, sizeof( newInfo.name ) ); + + // colors + v = Info_ValueForKey( configstring, "c1" ); + CG_ColorFromString( v, newInfo.color1 ); + + v = Info_ValueForKey( configstring, "c2" ); + CG_ColorFromString( v, newInfo.color2 ); + + // bot skill + v = Info_ValueForKey( configstring, "skill" ); + newInfo.botSkill = atoi( v ); + + //PKMOD - Ergodic 01/09/02 - add info to structure so Private Bot will not appear in scoreboard + // Private Bot + v = Info_ValueForKey( configstring, "pb" ); + //PKMOD - Ergodic 01/06/02 - debug bot's userinfo (inactive) +// Com_Printf("CG_NewClientInfo - privateBot>%s<\n", v ); + + if ( atoi( v ) == 1 ) + newInfo.privateBot = qtrue; + else + newInfo.privateBot = qfalse; + + // handicap + v = Info_ValueForKey( configstring, "hc" ); + newInfo.handicap = atoi( v ); + + // wins + v = Info_ValueForKey( configstring, "w" ); + newInfo.wins = atoi( v ); + + // losses + v = Info_ValueForKey( configstring, "l" ); + newInfo.losses = atoi( v ); + + // team + v = Info_ValueForKey( configstring, "t" ); + newInfo.team = atoi( v ); + + // team task + v = Info_ValueForKey( configstring, "tt" ); + newInfo.teamTask = atoi(v); + + // team leader + v = Info_ValueForKey( configstring, "tl" ); + newInfo.teamLeader = atoi(v); + + v = Info_ValueForKey( configstring, "g_redteam" ); + Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME); + + v = Info_ValueForKey( configstring, "g_blueteam" ); + Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME); + + // model + v = Info_ValueForKey( configstring, "model" ); + if ( cg_forceModel.integer ) { + // forcemodel makes everyone use a single model + // to prevent load hitches + char modelStr[MAX_QPATH]; + char *skin; + + if( cgs.gametype >= GT_TEAM ) { + Q_strncpyz( newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.modelName ) ); + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + } else { + trap_Cvar_VariableStringBuffer( "model", modelStr, sizeof( modelStr ) ); + if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { + skin = "default"; + } else { + *skin++ = 0; + } + + Q_strncpyz( newInfo.skinName, skin, sizeof( newInfo.skinName ) ); + Q_strncpyz( newInfo.modelName, modelStr, sizeof( newInfo.modelName ) ); + } + + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name + slash = strchr( v, '/' ); + if ( slash ) { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + } + } + } else { + Q_strncpyz( newInfo.modelName, v, sizeof( newInfo.modelName ) ); + + slash = strchr( newInfo.modelName, '/' ); + if ( !slash ) { + // modelName didn not include a skin name + Q_strncpyz( newInfo.skinName, "default", sizeof( newInfo.skinName ) ); + } else { + Q_strncpyz( newInfo.skinName, slash + 1, sizeof( newInfo.skinName ) ); + // truncate modelName + *slash = 0; + } + } + + // head model + v = Info_ValueForKey( configstring, "hmodel" ); + if ( cg_forceModel.integer ) { + // forcemodel makes everyone use a single model + // to prevent load hitches + char modelStr[MAX_QPATH]; + char *skin; + + if( cgs.gametype >= GT_TEAM ) { + Q_strncpyz( newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof( newInfo.headModelName ) ); + Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); + } else { + trap_Cvar_VariableStringBuffer( "headmodel", modelStr, sizeof( modelStr ) ); + if ( ( skin = strchr( modelStr, '/' ) ) == NULL) { + skin = "default"; + } else { + *skin++ = 0; + } + + Q_strncpyz( newInfo.headSkinName, skin, sizeof( newInfo.headSkinName ) ); + Q_strncpyz( newInfo.headModelName, modelStr, sizeof( newInfo.headModelName ) ); + } + + if ( cgs.gametype >= GT_TEAM ) { + // keep skin name + slash = strchr( v, '/' ); + if ( slash ) { + Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); + } + } + } else { + Q_strncpyz( newInfo.headModelName, v, sizeof( newInfo.headModelName ) ); + + slash = strchr( newInfo.headModelName, '/' ); + if ( !slash ) { + // modelName didn not include a skin name + Q_strncpyz( newInfo.headSkinName, "default", sizeof( newInfo.headSkinName ) ); + } else { + Q_strncpyz( newInfo.headSkinName, slash + 1, sizeof( newInfo.headSkinName ) ); + // truncate modelName + *slash = 0; + } + } + + // scan for an existing clientinfo that matches this modelname + // so we can avoid loading checks if possible + if ( !CG_ScanForExistingClientInfo( &newInfo ) ) { + qboolean forceDefer; + + forceDefer = trap_MemoryRemaining() < 4000000; + + // if we are defering loads, just have it pick the first valid + if ( forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading ) ) { + // keep whatever they had if it won't violate team skins + CG_SetDeferredClientInfo( &newInfo ); + // if we are low on memory, leave them with this model + if ( forceDefer ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + newInfo.deferred = qfalse; + } + } else { + CG_LoadClientInfo( &newInfo ); + } + } + + // replace whatever was there with the new one + newInfo.infoValid = qtrue; + *ci = newInfo; +} + + + +/* +====================== +CG_LoadDeferredPlayers + +Called each frame when a player is dead +and the scoreboard is up +so deferred players can be loaded +====================== +*/ +void CG_LoadDeferredPlayers( void ) { + int i; + clientInfo_t *ci; + + // scan for a deferred player to load + for ( i = 0, ci = cgs.clientinfo ; i < cgs.maxclients ; i++, ci++ ) { + if ( ci->infoValid && ci->deferred ) { + // if we are low on memory, leave it deferred + if ( trap_MemoryRemaining() < 4000000 ) { + CG_Printf( "Memory is low. Using deferred model.\n" ); + ci->deferred = qfalse; + continue; + } + CG_LoadClientInfo( ci ); +// break; + } + } +} + +/* +============================================================================= + +PLAYER ANIMATION + +============================================================================= +*/ + + +/* +=============== +CG_SetLerpFrameAnimation + +may include ANIM_TOGGLEBIT +=============== +*/ +static void CG_SetLerpFrameAnimation( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation ) { + animation_t *anim; + + lf->animationNumber = newAnimation; + newAnimation &= ~ANIM_TOGGLEBIT; + + if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { + CG_Error( "Bad animation number: %i", newAnimation ); + } + + anim = &ci->animations[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + anim->initialLerp; + + if ( cg_debugAnim.integer ) { + CG_Printf( "Anim: %i\n", newAnimation ); + } +} + +/* +=============== +CG_RunLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +static void CG_RunLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale ) { + int f, numFrames; + animation_t *anim; + + // debugging tool to get no animations + if ( cg_animSpeed.integer == 0 ) { + lf->oldFrame = lf->frame = lf->backlerp = 0; + return; + } + + // see if the animation sequence is switching + if ( newAnimation != lf->animationNumber || !lf->animation ) { + CG_SetLerpFrameAnimation( ci, lf, newAnimation ); + } + + // if we have passed the current frame, move it to + // oldFrame and calculate a new frame + if ( cg.time >= lf->frameTime ) { + lf->oldFrame = lf->frame; + lf->oldFrameTime = lf->frameTime; + + // get the next frame based on the animation + anim = lf->animation; + if ( !anim->frameLerp ) { + return; // shouldn't happen + } + if ( cg.time < lf->animationTime ) { + lf->frameTime = lf->animationTime; // initial lerp + } else { + lf->frameTime = lf->oldFrameTime + anim->frameLerp; + } + f = ( lf->frameTime - lf->animationTime ) / anim->frameLerp; + f *= speedScale; // adjust for haste, etc + + numFrames = anim->numFrames; + if (anim->flipflop) { + numFrames *= 2; + } + if ( f >= numFrames ) { + f -= numFrames; + if ( anim->loopFrames ) { + f %= anim->loopFrames; + f += anim->numFrames - anim->loopFrames; + } else { + f = numFrames - 1; + // the animation is stuck at the end, so it + // can immediately transition to another sequence + lf->frameTime = cg.time; + } + } + if ( anim->reversed ) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - f; + } + else if (anim->flipflop && f>=anim->numFrames) { + lf->frame = anim->firstFrame + anim->numFrames - 1 - (f%anim->numFrames); + } + else { + lf->frame = anim->firstFrame + f; + } + if ( cg.time > lf->frameTime ) { + lf->frameTime = cg.time; + if ( cg_debugAnim.integer ) { + CG_Printf( "Clamp lf->frameTime\n"); + } + } + } + + if ( lf->frameTime > cg.time + 200 ) { + lf->frameTime = cg.time; + } + + if ( lf->oldFrameTime > cg.time ) { + lf->oldFrameTime = cg.time; + } + // calculate current lerp value + if ( lf->frameTime == lf->oldFrameTime ) { + lf->backlerp = 0; + } else { + lf->backlerp = 1.0 - (float)( cg.time - lf->oldFrameTime ) / ( lf->frameTime - lf->oldFrameTime ); + } +} + + +/* +=============== +CG_ClearLerpFrame +=============== +*/ +static void CG_ClearLerpFrame( clientInfo_t *ci, lerpFrame_t *lf, int animationNumber ) { + lf->frameTime = lf->oldFrameTime = cg.time; + CG_SetLerpFrameAnimation( ci, lf, animationNumber ); + lf->oldFrame = lf->frame = lf->animation->firstFrame; +} + + +/* +=============== +CG_PlayerAnimation +=============== +*/ +static void CG_PlayerAnimation( centity_t *cent, int *legsOld, int *legs, float *legsBackLerp, + int *torsoOld, int *torso, float *torsoBackLerp ) { + clientInfo_t *ci; + int clientNum; + float speedScale; + + clientNum = cent->currentState.clientNum; + + if ( cg_noPlayerAnims.integer ) { + *legsOld = *legs = *torsoOld = *torso = 0; + return; + } + + if ( cent->currentState.powerups & ( 1 << PW_HASTE ) ) { + speedScale = 1.5; + } else { + speedScale = 1; + } + + ci = &cgs.clientinfo[ clientNum ]; + + // do the shuffle turn frames locally + if ( cent->pe.legs.yawing && ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) == LEGS_IDLE ) { + CG_RunLerpFrame( ci, ¢->pe.legs, LEGS_TURN, speedScale ); + } else { + CG_RunLerpFrame( ci, ¢->pe.legs, cent->currentState.legsAnim, speedScale ); + } + + *legsOld = cent->pe.legs.oldFrame; + *legs = cent->pe.legs.frame; + *legsBackLerp = cent->pe.legs.backlerp; + + CG_RunLerpFrame( ci, ¢->pe.torso, cent->currentState.torsoAnim, speedScale ); + + *torsoOld = cent->pe.torso.oldFrame; + *torso = cent->pe.torso.frame; + *torsoBackLerp = cent->pe.torso.backlerp; +} + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +/* +================== +CG_SwingAngles +================== +*/ +static void CG_SwingAngles( float destination, float swingTolerance, float clampTolerance, + float speed, float *angle, qboolean *swinging ) { + float swing; + float move; + float scale; + + if ( !*swinging ) { + // see if a swing should be started + swing = AngleSubtract( *angle, destination ); + if ( swing > swingTolerance || swing < -swingTolerance ) { + *swinging = qtrue; + } + } + + if ( !*swinging ) { + return; + } + + // modify the speed depending on the delta + // so it doesn't seem so linear + swing = AngleSubtract( destination, *angle ); + scale = fabs( swing ); + if ( scale < swingTolerance * 0.5 ) { + scale = 0.5; + } else if ( scale < swingTolerance ) { + scale = 1.0; + } else { + scale = 2.0; + } + + // swing towards the destination angle + if ( swing >= 0 ) { + move = cg.frametime * scale * speed; + if ( move >= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } else if ( swing < 0 ) { + move = cg.frametime * scale * -speed; + if ( move <= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } + + // clamp to no more than tolerance + swing = AngleSubtract( destination, *angle ); + if ( swing > clampTolerance ) { + *angle = AngleMod( destination - (clampTolerance - 1) ); + } else if ( swing < -clampTolerance ) { + *angle = AngleMod( destination + (clampTolerance - 1) ); + } +} + +/* +================= +CG_AddPainTwitch +================= +*/ +static void CG_AddPainTwitch( centity_t *cent, vec3_t torsoAngles ) { + int t; + float f; + + t = cg.time - cent->pe.painTime; + if ( t >= PAIN_TWITCH_TIME ) { + return; + } + + f = 1.0 - (float)t / PAIN_TWITCH_TIME; + + if ( cent->pe.painDirection ) { + torsoAngles[ROLL] += 20 * f; + } else { + torsoAngles[ROLL] -= 20 * f; + } +} + + +/* +=============== +CG_PlayerAngles + +Handles seperate torso motion + + legs pivot based on direction of movement + + head always looks exactly at cent->lerpAngles + + if motion < 20 degrees, show in head only + if < 45 degrees, also show in torso +=============== +*/ +static void CG_PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3] ) { + vec3_t legsAngles, torsoAngles, headAngles; + float dest; + static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; + vec3_t velocity; + float speed; + int dir, clientNum; + clientInfo_t *ci; + + VectorCopy( cent->lerpAngles, headAngles ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + + // --------- yaw ------------- + + // allow yaw to drift a bit + if ( ( cent->currentState.legsAnim & ~ANIM_TOGGLEBIT ) != LEGS_IDLE + || ( cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT ) != TORSO_STAND ) { + // if not standing still, always point all in the same direction + cent->pe.torso.yawing = qtrue; // always center + cent->pe.torso.pitching = qtrue; // always center + cent->pe.legs.yawing = qtrue; // always center + } + + //PKMOD Ergodic 07/07/01 - debug new time2 encoding scheme (inactive) +// Com_Printf("CG_PlayerAngles - angles2[YAW]>%f<, time2>%d<\n", cent->currentState.angles2[YAW], ( cent->currentState.time2 >> 7 ) & 7 ); + + // adjust legs for movement dir + if ( cent->currentState.eFlags & EF_DEAD ) { + // don't let dead bodies twitch + dir = 0; + } else { + //PKMOD - Ergodic 07/07/01 - movementDir is now packed into time2 +// dir = cent->currentState.angles2[YAW]; + dir = ( cent->currentState.time2 >> 7 ) & 7; + if ( dir < 0 || dir > 7 ) { + CG_Error( "Bad player movement angle" ); + } + } + legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ]; + torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ]; + + // torso + CG_SwingAngles( torsoAngles[YAW], 25, 90, cg_swingSpeed.value, ¢->pe.torso.yawAngle, ¢->pe.torso.yawing ); + CG_SwingAngles( legsAngles[YAW], 40, 90, cg_swingSpeed.value, ¢->pe.legs.yawAngle, ¢->pe.legs.yawing ); + + torsoAngles[YAW] = cent->pe.torso.yawAngle; + legsAngles[YAW] = cent->pe.legs.yawAngle; + + + // --------- pitch ------------- + + // only show a fraction of the pitch angle in the torso + if ( headAngles[PITCH] > 180 ) { + dest = (-360 + headAngles[PITCH]) * 0.75f; + } else { + dest = headAngles[PITCH] * 0.75f; + } + CG_SwingAngles( dest, 15, 30, 0.1f, ¢->pe.torso.pitchAngle, ¢->pe.torso.pitching ); + torsoAngles[PITCH] = cent->pe.torso.pitchAngle; + + // + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + if ( ci->fixedtorso ) { + torsoAngles[PITCH] = 0.0f; + } + } + + // --------- roll ------------- + + + // lean towards the direction of travel + VectorCopy( cent->currentState.pos.trDelta, velocity ); + speed = VectorNormalize( velocity ); + if ( speed ) { + vec3_t axis[3]; + float side; + + speed *= 0.05f; + + AnglesToAxis( legsAngles, axis ); + side = speed * DotProduct( velocity, axis[1] ); + legsAngles[ROLL] -= side; + + side = speed * DotProduct( velocity, axis[0] ); + legsAngles[PITCH] += side; + } + + // + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + if ( ci->fixedlegs ) { + legsAngles[YAW] = torsoAngles[YAW]; + legsAngles[PITCH] = 0.0f; + legsAngles[ROLL] = 0.0f; + } + } + + // pain twitch + CG_AddPainTwitch( cent, torsoAngles ); + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + AnglesToAxis( legsAngles, legs ); + AnglesToAxis( torsoAngles, torso ); + AnglesToAxis( headAngles, head ); +} + + +//========================================================================== + +/* +=============== +CG_HasteTrail +=============== +*/ +static void CG_HasteTrail( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin; + int anim; + + if ( cent->trailTime > cg.time ) { + return; + } + anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; + if ( anim != LEGS_RUN && anim != LEGS_BACK ) { + return; + } + + cent->trailTime += 100; + if ( cent->trailTime < cg.time ) { + cent->trailTime = cg.time; + } + + VectorCopy( cent->lerpOrigin, origin ); + origin[2] -= 16; + + smoke = CG_SmokePuff( origin, vec3_origin, + 8, + 1, 1, 1, 1, + 500, + cg.time, + 0, + 0, + cgs.media.hastePuffShader ); + + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; +} + +#ifdef MISSIONPACK +/* +=============== +CG_BreathPuffs +=============== +*/ +static void CG_BreathPuffs( centity_t *cent, refEntity_t *head) { + clientInfo_t *ci; + vec3_t up, origin; + int contents; + + ci = &cgs.clientinfo[ cent->currentState.number ]; + + if (!cg_enableBreath.integer) { + return; + } + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) { + return; + } + if ( cent->currentState.eFlags & EF_DEAD ) { + return; + } + contents = trap_CM_PointContents( head->origin, 0 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + if ( ci->breathPuffTime > cg.time ) { + return; + } + + VectorSet( up, 0, 0, 8 ); + VectorMA(head->origin, 8, head->axis[0], origin); + VectorMA(origin, -4, head->axis[2], origin); + CG_SmokePuff( origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); + ci->breathPuffTime = cg.time + 2000; +} + +/* +=============== +CG_DustTrail +=============== +*/ +static void CG_DustTrail( centity_t *cent ) { + int anim; + localEntity_t *dust; + vec3_t end, vel; + trace_t tr; + + if (!cg_enableDust.integer) + return; + + if ( cent->dustTrailTime > cg.time ) { + return; + } + + anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT; + if ( anim != LEGS_LANDB && anim != LEGS_LAND ) { + return; + } + + cent->dustTrailTime += 40; + if ( cent->dustTrailTime < cg.time ) { + cent->dustTrailTime = cg.time; + } + + VectorCopy(cent->currentState.pos.trBase, end); + end[2] -= 64; + CG_Trace( &tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID ); + + if ( !(tr.surfaceFlags & SURF_DUST) ) + return; + + VectorCopy( cent->currentState.pos.trBase, end ); + end[2] -= 16; + + VectorSet(vel, 0, 0, -30); + dust = CG_SmokePuff( end, vel, + 24, + .8f, .8f, 0.7f, 0.33f, + 500, + cg.time, + 0, + 0, + cgs.media.dustPuffShader ); +} + +#endif + +/* +=============== +CG_TrailItem +=============== +*/ +static void CG_TrailItem( centity_t *cent, qhandle_t hModel ) { + refEntity_t ent; + vec3_t angles; + vec3_t axis[3]; + + VectorCopy( cent->lerpAngles, angles ); + angles[PITCH] = 0; + angles[ROLL] = 0; + AnglesToAxis( angles, axis ); + + memset( &ent, 0, sizeof( ent ) ); + VectorMA( cent->lerpOrigin, -16, axis[0], ent.origin ); + ent.origin[2] += 16; + angles[YAW] += 90; + AnglesToAxis( angles, ent.axis ); + + ent.hModel = hModel; + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_PlayerFlag +=============== +*/ +static void CG_PlayerFlag( centity_t *cent, qhandle_t hSkin, refEntity_t *torso ) { + clientInfo_t *ci; + refEntity_t pole; + refEntity_t flag; + vec3_t angles, dir; + int legsAnim, flagAnim, updateangles; + float angle, d; + + // show the flag pole model + memset( &pole, 0, sizeof(pole) ); + pole.hModel = cgs.media.flagPoleModel; + VectorCopy( torso->lightingOrigin, pole.lightingOrigin ); + pole.shadowPlane = torso->shadowPlane; + pole.renderfx = torso->renderfx; + CG_PositionEntityOnTag( &pole, torso, torso->hModel, "tag_flag" ); + trap_R_AddRefEntityToScene( &pole ); + + // show the flag model + memset( &flag, 0, sizeof(flag) ); + flag.hModel = cgs.media.flagFlapModel; + flag.customSkin = hSkin; + VectorCopy( torso->lightingOrigin, flag.lightingOrigin ); + flag.shadowPlane = torso->shadowPlane; + flag.renderfx = torso->renderfx; + + VectorClear(angles); + + updateangles = qfalse; + legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + if( legsAnim == LEGS_IDLE || legsAnim == LEGS_IDLECR ) { + flagAnim = FLAG_STAND; + } else if ( legsAnim == LEGS_WALK || legsAnim == LEGS_WALKCR ) { + flagAnim = FLAG_STAND; + updateangles = qtrue; + } else { + flagAnim = FLAG_RUN; + updateangles = qtrue; + } + + if ( updateangles ) { + + VectorCopy( cent->currentState.pos.trDelta, dir ); + // add gravity + dir[2] += 100; + VectorNormalize( dir ); + d = DotProduct(pole.axis[2], dir); + // if there is anough movement orthogonal to the flag pole + if (fabs(d) < 0.9) { + // + d = DotProduct(pole.axis[0], dir); + if (d > 1.0f) { + d = 1.0f; + } + else if (d < -1.0f) { + d = -1.0f; + } + angle = acos(d); + + d = DotProduct(pole.axis[1], dir); + if (d < 0) { + angles[YAW] = 360 - angle * 180 / M_PI; + } + else { + angles[YAW] = angle * 180 / M_PI; + } + if (angles[YAW] < 0) + angles[YAW] += 360; + if (angles[YAW] > 360) + angles[YAW] -= 360; + + //vectoangles( cent->currentState.pos.trDelta, tmpangles ); + //angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle; + // change the yaw angle + CG_SwingAngles( angles[YAW], 25, 90, 0.15f, ¢->pe.flag.yawAngle, ¢->pe.flag.yawing ); + } + + /* + d = DotProduct(pole.axis[2], dir); + angle = Q_acos(d); + + d = DotProduct(pole.axis[1], dir); + if (d < 0) { + angle = 360 - angle * 180 / M_PI; + } + else { + angle = angle * 180 / M_PI; + } + if (angle > 340 && angle < 20) { + flagAnim = FLAG_RUNUP; + } + if (angle > 160 && angle < 200) { + flagAnim = FLAG_RUNDOWN; + } + */ + } + + // set the yaw angle + angles[YAW] = cent->pe.flag.yawAngle; + // lerp the flag animation frames + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + CG_RunLerpFrame( ci, ¢->pe.flag, flagAnim, 1 ); + flag.oldframe = cent->pe.flag.oldFrame; + flag.frame = cent->pe.flag.frame; + flag.backlerp = cent->pe.flag.backlerp; + + AnglesToAxis( angles, flag.axis ); + CG_PositionRotatedEntityOnTag( &flag, &pole, pole.hModel, "tag_flag" ); + + trap_R_AddRefEntityToScene( &flag ); +} + + +#ifdef MISSIONPACK // bk001204 +/* +=============== +CG_PlayerTokens +=============== +*/ +static void CG_PlayerTokens( centity_t *cent, int renderfx ) { + int tokens, i, j; + float angle; + refEntity_t ent; + vec3_t dir, origin; + skulltrail_t *trail; + trail = &cg.skulltrails[cent->currentState.number]; + tokens = cent->currentState.generic1; + if ( !tokens ) { + trail->numpositions = 0; + return; + } + + if ( tokens > MAX_SKULLTRAIL ) { + tokens = MAX_SKULLTRAIL; + } + + // add skulls if there are more than last time + for (i = 0; i < tokens - trail->numpositions; i++) { + for (j = trail->numpositions; j > 0; j--) { + VectorCopy(trail->positions[j-1], trail->positions[j]); + } + VectorCopy(cent->lerpOrigin, trail->positions[0]); + } + trail->numpositions = tokens; + + // move all the skulls along the trail + VectorCopy(cent->lerpOrigin, origin); + for (i = 0; i < trail->numpositions; i++) { + VectorSubtract(trail->positions[i], origin, dir); + if (VectorNormalize(dir) > 30) { + VectorMA(origin, 30, dir, trail->positions[i]); + } + VectorCopy(trail->positions[i], origin); + } + + memset( &ent, 0, sizeof( ent ) ); + if( cgs.clientinfo[ cent->currentState.clientNum ].team == TEAM_BLUE ) { + ent.hModel = cgs.media.redCubeModel; + } else { + ent.hModel = cgs.media.blueCubeModel; + } + ent.renderfx = renderfx; + + VectorCopy(cent->lerpOrigin, origin); + for (i = 0; i < trail->numpositions; i++) { + VectorSubtract(origin, trail->positions[i], ent.axis[0]); + ent.axis[0][2] = 0; + VectorNormalize(ent.axis[0]); + VectorSet(ent.axis[2], 0, 0, 1); + CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); + + VectorCopy(trail->positions[i], ent.origin); + angle = (((cg.time + 500 * MAX_SKULLTRAIL - 500 * i) / 16) & 255) * (M_PI * 2) / 255; + ent.origin[2] += sin(angle) * 10; + trap_R_AddRefEntityToScene( &ent ); + VectorCopy(trail->positions[i], origin); + } +} +#endif + + +/* +=============== +CG_PlayerPowerups +=============== +*/ +static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { + int powerups; + clientInfo_t *ci; + + powerups = cent->currentState.powerups; + + //PKMOD - Ergodic 10/18/01 - debug modelindex2 usage for spare bits (inactive) +// if ( (rand() % 101) > 95 ) +// Com_Printf("CG_PlayerPowerups - modelindex2>%d<\n", cent->currentState.modelindex2); + + if ( !powerups ) { + return; + } + + // quad gives a dlight + if ( powerups & ( 1 << PW_QUAD ) ) { + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1 ); + } + + //PKMOD - Ergodic 12/05/01 - add radiate waring sound + if ( powerups & ( 1 << PW_RADIATE ) ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.pkaradiatewarningSound ); + } + + //PKMOD - Ergodic 08/02/02 - add Personal Sentry hover sound + if ( powerups & ( 1 << PW_PERSENTRY ) ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.pkapersentryhoverSound ); + } + + // flight plays a looped sound + if ( powerups & ( 1 << PW_FLIGHT ) ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound ); + } + + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + // redflag + if ( powerups & ( 1 << PW_REDFLAG ) ) { + //PKMOD - Ergodic 08/29/01 - if not carrying the red flag + if ( ( cent->currentState.generic1 & 15 ) != PW_REDFLAG ) { + if (ci->newAnims) { + CG_PlayerFlag( cent, cgs.media.redFlagFlapSkin, torso ); + } + else { + CG_TrailItem( cent, cgs.media.redFlagModel ); + } + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); + } + } + + // blueflag + if ( powerups & ( 1 << PW_BLUEFLAG ) ) { + //PKMOD - Ergodic 08/29/01 - if not carrying the blue flag + if ( ( cent->currentState.generic1 & 15 ) != PW_BLUEFLAG ) { + if (ci->newAnims){ + CG_PlayerFlag( cent, cgs.media.blueFlagFlapSkin, torso ); + } + else { + CG_TrailItem( cent, cgs.media.blueFlagModel ); + } + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); + + } + } + + // neutralflag + if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if (ci->newAnims) { + CG_PlayerFlag( cent, cgs.media.neutralFlagFlapSkin, torso ); + } + else { + CG_TrailItem( cent, cgs.media.neutralFlagModel ); + } + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0 ); + } + + // haste leaves smoke trails + if ( powerups & ( 1 << PW_HASTE ) ) { + CG_HasteTrail( cent ); + } +} + + +/* +=============== +CG_PlayerFloatSprite + +Float a sprite over the player's head +=============== +*/ +static void CG_PlayerFloatSprite( centity_t *cent, qhandle_t shader ) { + int rf; + refEntity_t ent; + + if ( cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson ) { + rf = RF_THIRD_PERSON; // only show in mirrors + } else { + rf = 0; + } + + memset( &ent, 0, sizeof( ent ) ); + VectorCopy( cent->lerpOrigin, ent.origin ); + ent.origin[2] += 48; + ent.reType = RT_SPRITE; + ent.customShader = shader; + ent.radius = 10; + ent.renderfx = rf; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + + + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) { + int team; + + if ( cent->currentState.eFlags & EF_CONNECTION ) { + CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); + return; + } + + if ( cent->currentState.eFlags & EF_TALK ) { + CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_IMPRESSIVE ) { + CG_PlayerFloatSprite( cent, cgs.media.medalImpressive ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_EXCELLENT ) { + CG_PlayerFloatSprite( cent, cgs.media.medalExcellent ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_GAUNTLET ) { + CG_PlayerFloatSprite( cent, cgs.media.medalGauntlet ); + return; + } + + //PKMOD - Ergodic 08/08/00 PAINKILLER awarded after every 10 PKitem kills + if ( cent->currentState.eFlags & EF_AWARD_PAINKILLER ) { + CG_PlayerFloatSprite( cent, cgs.media.medalPainKiller ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_DEFEND ) { + CG_PlayerFloatSprite( cent, cgs.media.medalDefend ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_ASSIST ) { + CG_PlayerFloatSprite( cent, cgs.media.medalAssist ); + return; + } + + if ( cent->currentState.eFlags & EF_AWARD_CAP ) { + CG_PlayerFloatSprite( cent, cgs.media.medalCapture ); + return; + } + + team = cgs.clientinfo[ cent->currentState.clientNum ].team; + if ( !(cent->currentState.eFlags & EF_DEAD) && + cg.snap->ps.persistant[PERS_TEAM] == team && + cgs.gametype >= GT_TEAM) { + if (cg_drawFriend.integer) { + CG_PlayerFloatSprite( cent, cgs.media.friendShader ); + } + return; + } +} + +/* +=============== +CG_PlayerShadow + +Returns the Z component of the surface being shadowed + + should it return a full plane instead of a Z? +=============== +*/ +#define SHADOW_DISTANCE 128 +static qboolean CG_PlayerShadow( centity_t *cent, float *shadowPlane ) { + vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2}; + trace_t trace; + float alpha; + + *shadowPlane = 0; + + if ( cg_shadows.integer == 0 ) { + return qfalse; + } + + // no shadows when invisible + if ( cent->currentState.powerups & ( 1 << PW_INVIS ) ) { + return qfalse; + } + + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + end[2] -= SHADOW_DISTANCE; + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) { + return qfalse; + } + + *shadowPlane = trace.endpos[2] + 1; + + if ( cg_shadows.integer != 1 ) { // no mark for stencil or projection shadows + return qtrue; + } + + // fade the shadow out with height + alpha = 1.0 - trace.fraction; + + // bk0101022 - hack / FPE - bogus planes? + //assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) + + // add the mark as a temporary, so it goes directly to the renderer + // without taking a spot in the cg_marks array + CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, + cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, 24, qtrue ); + + return qtrue; +} + + +/* +=============== +CG_PlayerSplash + +Draw a mark at the water surface +=============== +*/ +static void CG_PlayerSplash( centity_t *cent ) { + vec3_t start, end; + trace_t trace; + int contents; + polyVert_t verts[4]; + + if ( !cg_shadows.integer ) { + return; + } + + VectorCopy( cent->lerpOrigin, end ); + end[2] -= 24; + + // if the feet aren't in liquid, don't make a mark + // this won't handle moving water brushes, but they wouldn't draw right anyway... + contents = trap_CM_PointContents( end, 0 ); + if ( !( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) ) { + return; + } + + VectorCopy( cent->lerpOrigin, start ); + start[2] += 32; + + // if the head isn't out of liquid, don't make a mark + contents = trap_CM_PointContents( start, 0 ); + if ( contents & ( CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + return; + } + + // trace down to find the surface + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ); + + if ( trace.fraction == 1.0 ) { + return; + } + + // create a mark polygon + VectorCopy( trace.endpos, verts[0].xyz ); + verts[0].xyz[0] -= 32; + verts[0].xyz[1] -= 32; + verts[0].st[0] = 0; + verts[0].st[1] = 0; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[1].xyz ); + verts[1].xyz[0] -= 32; + verts[1].xyz[1] += 32; + verts[1].st[0] = 0; + verts[1].st[1] = 1; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[2].xyz ); + verts[2].xyz[0] += 32; + verts[2].xyz[1] += 32; + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorCopy( trace.endpos, verts[3].xyz ); + verts[3].xyz[0] += 32; + verts[3].xyz[1] -= 32; + verts[3].st[0] = 1; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.wakeMarkShader, 4, verts ); +} + + + +/* +=============== +CG_AddRefEntityWithPowerups + +Adds a piece with modifications or duplications for powerups +Also called by CG_Missile for quad rockets, but nobody can tell... +=============== +*/ +void CG_AddRefEntityWithPowerups( refEntity_t *ent, entityState_t *state, int team ) { + + //PKMOD - Ergodic 08/22/00 - add logic for chainlightning player hit effect + if ( state->powerups & ( 1 << PW_CLGPLAYERHIT ) ) { + trap_R_AddRefEntityToScene( ent ); //add raw player "piece" image + ent->customShader = cgs.media.clgplayerhitShader; + trap_R_AddRefEntityToScene( ent ); //add player "piece" image with shader effect + } else { + if ( state->powerups & ( 1 << PW_INVIS ) ) { + ent->customShader = cgs.media.invisShader; + /* + if ( state->eFlags & EF_KAMIKAZE ) { + if (team == TEAM_BLUE) + ent->customShader = cgs.media.blueKamikazeShader; + else + ent->customShader = cgs.media.redKamikazeShader; + trap_R_AddRefEntityToScene( ent ); + } + else {*/ + trap_R_AddRefEntityToScene( ent ); + //} + + } else { + trap_R_AddRefEntityToScene( ent ); + + if ( state->powerups & ( 1 << PW_QUAD ) ) + { + if (team == TEAM_RED) + ent->customShader = cgs.media.redQuadShader; + else + ent->customShader = cgs.media.quadShader; + trap_R_AddRefEntityToScene( ent ); + } + if ( state->powerups & ( 1 << PW_REGEN ) ) { + if ( ( ( cg.time / 100 ) % 10 ) == 1 ) { + ent->customShader = cgs.media.regenShader; + trap_R_AddRefEntityToScene( ent ); + } + } + if ( state->powerups & ( 1 << PW_BATTLESUIT ) ) { + ent->customShader = cgs.media.battleSuitShader; + trap_R_AddRefEntityToScene( ent ); + } + } + } +} + +/* +================= +CG_LightVerts +================= +*/ +int CG_LightVerts( vec3_t normal, int numVerts, polyVert_t *verts ) +{ + int i, j; + float incoming; + vec3_t ambientLight; + vec3_t lightDir; + vec3_t directedLight; + + trap_R_LightForPoint( verts[0].xyz, ambientLight, directedLight, lightDir ); + + for (i = 0; i < numVerts; i++) { + incoming = DotProduct (normal, lightDir); + if ( incoming <= 0 ) { + verts[i].modulate[0] = ambientLight[0]; + verts[i].modulate[1] = ambientLight[1]; + verts[i].modulate[2] = ambientLight[2]; + verts[i].modulate[3] = 255; + continue; + } + j = ( ambientLight[0] + incoming * directedLight[0] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[0] = j; + + j = ( ambientLight[1] + incoming * directedLight[1] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[1] = j; + + j = ( ambientLight[2] + incoming * directedLight[2] ); + if ( j > 255 ) { + j = 255; + } + verts[i].modulate[2] = j; + + verts[i].modulate[3] = 255; + } + return qtrue; +} + +/* +=============== +CG_Player +=============== +*/ +void CG_Player( centity_t *cent ) { + clientInfo_t *ci; + refEntity_t legs; + refEntity_t torso; + refEntity_t head; + int clientNum; + int renderfx; + qboolean shadow; + float shadowPlane; + //PKMOD - Ergodic 05/07/02 - add active personal sentry model + refEntity_t persentry; + int holdtime; + +#ifdef MISSIONPACK + refEntity_t skull; + refEntity_t powerup; + int t; + float c; + float angle; + vec3_t dir, angles; +#endif + + + //PKMOD - Ergodic 11/30/01 - debug modelindex2 usage for spare bits (inactive) +// if ( (rand() % 101) > 95 ) +// Com_Printf("CG_Player - modelindex2>%d<\n", cent->currentState.modelindex2); + + + // the client number is stored in clientNum. It can't be derived + // from the entity number, because a single client may have + // multiple corpses on the level using the same clientinfo + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity"); + } + ci = &cgs.clientinfo[ clientNum ]; + + //PKMOD - Ergodic 09/11/02 - display the Private Bot field effect if ent is a Private Bot and visible + if ( ci->privateBot ) { + if ( ! (cent->currentState.powerups & ( 1 << PW_INVIS ) ) ) { + refEntity_t pbfield; + + //PKMOD - Ergodic 09/11/02 - debug pb field effect (inactive) +// Com_Printf( "CG_Player - adding PB field\n" ); + + memset( &pbfield, 0, sizeof(pbfield) ); + VectorCopy( cent->lerpOrigin, pbfield.origin ); + AnglesToAxis( cent->lerpAngles, pbfield.axis ); + + pbfield.hModel = cgs.media.privatebot_CueModel; + trap_R_AddRefEntityToScene( &pbfield ); + } + } + + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if ( !ci->infoValid ) { + return; + } + + // get the player model information + renderfx = 0; + if ( cent->currentState.number == cg.snap->ps.clientNum) { + if (!cg.renderingThirdPerson) { + renderfx = RF_THIRD_PERSON; // only draw in mirrors + } else { + if (cg_cameraMode.integer) { + return; + } + } + } + + memset( &legs, 0, sizeof(legs) ); + memset( &torso, 0, sizeof(torso) ); + memset( &head, 0, sizeof(head) ); + + // get the rotation information + CG_PlayerAngles( cent, legs.axis, torso.axis, head.axis ); + + // get the animation state (after rotation, to allow feet shuffle) + CG_PlayerAnimation( cent, &legs.oldframe, &legs.frame, &legs.backlerp, + &torso.oldframe, &torso.frame, &torso.backlerp ); + +//PKMOD - Ergodic 11/13/00 - use time2 integer to store beartraps +//PKMOD - Ergodic 07/07/01 - use new packing scheme to encode # of beartraps + if ( cent->currentState.time2 & 3 ) + CG_BearTraps_Follow( cent ); + + //PKMOD - Ergodic 10/24/01 - add logic for radiation effect + if ( cent->currentState.powerups & ( 1 << PW_RADIATE ) ) { + if ( cent->PKA_RadiateTime < cg.time ) { + //PKMOD - Ergodic 11/24/01 - increase spark height + vec3_t sparkheight; + VectorCopy( cent->lerpOrigin, sparkheight ); + sparkheight[2] += DEFAULT_VIEWHEIGHT; + + CG_Radiation( sparkheight ); + cent->PKA_RadiateTime = cg.time + 1000; //every 1 seconds + } + } + + //PKMOD - Ergodic 11/30/01 - add logic for infected radiated player effect + if ( cent->currentState.modelindex2 & ( 1 << PKA_IRRADIATED ) ) { + + if ( cent->PKA_RadiateInfectTime < cg.time ) { + //PKMOD - Ergodic 11/24/01 - increase spark height + vec3_t sparkheight; + //PKMOD - Ergodic 12/01/01 - add a random velocity + vec3_t vel; + + VectorCopy( cent->lerpOrigin, sparkheight ); + sparkheight[2] += DEFAULT_VIEWHEIGHT + 5 - 2 * ( rand() % 5 ); + + vel[0] = 40 - 2 * ( rand() % 40 ); + vel[1] = 40 - 2 * ( rand() % 40 ); + vel[2] = 40 - 2 * ( rand() % 40 ); + + +// CG_RadiationTrail( sparkheight ); + + CG_RadiationTrail( sparkheight, vel, + 1, // radius + 1, 1, 1, 1, // color + 500, // trailTime + cg.time, // startTime + 0, //12/16/00 - add fadeInTime + 0, // flags + cgs.media.radiationTrailShader ); + +// cent->PKA_RadiateInfectTime = cg.time + 200 + rand() % 250; //every .2 seconds plus a random bit + cent->PKA_RadiateInfectTime = cg.time + 100 + rand() % 50; //every .1 seconds plus a random bit + } + } + + + // add the talk balloon or disconnect icon + CG_PlayerSprites( cent ); + + // add the shadow + shadow = CG_PlayerShadow( cent, &shadowPlane ); + + // add a water splash if partially in and out of water + CG_PlayerSplash( cent ); + + if ( cg_shadows.integer == 3 && shadow ) { + renderfx |= RF_SHADOW_PLANE; + } + renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all +#ifdef MISSIONPACK + if( cgs.gametype == GT_HARVESTER ) { + CG_PlayerTokens( cent, renderfx ); + } +#endif + // + // add the legs + // + legs.hModel = ci->legsModel; + legs.customSkin = ci->legsSkin; + + VectorCopy( cent->lerpOrigin, legs.origin ); + + VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); + legs.shadowPlane = shadowPlane; + legs.renderfx = renderfx; + VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all + + CG_AddRefEntityWithPowerups( &legs, ¢->currentState, ci->team ); + + // if the model failed, allow the default nullmodel to be displayed + if (!legs.hModel) { + return; + } + + // + // add the torso + // + torso.hModel = ci->torsoModel; + if (!torso.hModel) { + return; + } + + torso.customSkin = ci->torsoSkin; + + VectorCopy( cent->lerpOrigin, torso.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &torso, &legs, ci->legsModel, "tag_torso"); + + torso.shadowPlane = shadowPlane; + torso.renderfx = renderfx; + + CG_AddRefEntityWithPowerups( &torso, ¢->currentState, ci->team ); + +#ifdef MISSIONPACK + if ( cent->currentState.eFlags & EF_KAMIKAZE ) { + + memset( &skull, 0, sizeof(skull) ); + + VectorCopy( cent->lerpOrigin, skull.lightingOrigin ); + skull.shadowPlane = shadowPlane; + skull.renderfx = renderfx; + + if ( cent->currentState.eFlags & EF_DEAD ) { + // one skull bobbing above the dead body + angle = ((cg.time / 7) & 255) * (M_PI * 2) / 255; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; + dir[2] = 15 + sin(angle) * 8; + VectorAdd(torso.origin, dir, skull.origin); + + dir[2] = 0; + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + } + else { + // three skulls spinning around the player + angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255; + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 20; + VectorAdd(torso.origin, dir, skull.origin); + + angles[0] = sin(angle) * 30; + angles[1] = (angle * 180 / M_PI) + 90; + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + AnglesToAxis( angles, skull.axis ); + + /* + dir[2] = 0; + VectorInverse(dir); + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + */ + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + // flip the trail because this skull is spinning in the other direction + VectorInverse(skull.axis[1]); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + + angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255 + M_PI; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + dir[2] = cos(angle) * 20; + VectorAdd(torso.origin, dir, skull.origin); + + angles[0] = cos(angle - 0.5 * M_PI) * 30; + angles[1] = 360 - (angle * 180 / M_PI); + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + AnglesToAxis( angles, skull.axis ); + + /* + dir[2] = 0; + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + */ + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + + angle = ((cg.time / 3) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI; + if (angle > M_PI * 2) + angle -= (float)M_PI * 2; + dir[0] = sin(angle) * 20; + dir[1] = cos(angle) * 20; + dir[2] = 0; + VectorAdd(torso.origin, dir, skull.origin); + + VectorCopy(dir, skull.axis[1]); + VectorNormalize(skull.axis[1]); + VectorSet(skull.axis[2], 0, 0, 1); + CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]); + + skull.hModel = cgs.media.kamikazeHeadModel; + trap_R_AddRefEntityToScene( &skull ); + skull.hModel = cgs.media.kamikazeHeadTrail; + trap_R_AddRefEntityToScene( &skull ); + } + } + + if ( cent->currentState.powerups & ( 1 << PW_GUARD ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.guardPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_SCOUT ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.scoutPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_DOUBLER ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.doublerPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_AMMOREGEN ) ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.ammoRegenPowerupModel; + powerup.frame = 0; + powerup.oldframe = 0; + powerup.customSkin = 0; + trap_R_AddRefEntityToScene( &powerup ); + } + if ( cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) { + if ( !ci->invulnerabilityStartTime ) { + ci->invulnerabilityStartTime = cg.time; + } + ci->invulnerabilityStopTime = cg.time; + } + else { + ci->invulnerabilityStartTime = 0; + } + if ( (cent->currentState.powerups & ( 1 << PW_INVULNERABILITY ) ) || + cg.time - ci->invulnerabilityStopTime < 250 ) { + + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.invulnerabilityPowerupModel; + powerup.customSkin = 0; + // always draw + powerup.renderfx &= ~RF_THIRD_PERSON; + VectorCopy(cent->lerpOrigin, powerup.origin); + + if ( cg.time - ci->invulnerabilityStartTime < 250 ) { + c = (float) (cg.time - ci->invulnerabilityStartTime) / 250; + } + else if (cg.time - ci->invulnerabilityStopTime < 250 ) { + c = (float) (250 - (cg.time - ci->invulnerabilityStopTime)) / 250; + } + else { + c = 1; + } + VectorSet( powerup.axis[0], c, 0, 0 ); + VectorSet( powerup.axis[1], 0, c, 0 ); + VectorSet( powerup.axis[2], 0, 0, c ); + trap_R_AddRefEntityToScene( &powerup ); + } + + t = cg.time - ci->medkitUsageTime; + if ( ci->medkitUsageTime && t < 500 ) { + memcpy(&powerup, &torso, sizeof(torso)); + powerup.hModel = cgs.media.medkitUsageModel; + powerup.customSkin = 0; + // always draw + powerup.renderfx &= ~RF_THIRD_PERSON; + VectorClear(angles); + AnglesToAxis(angles, powerup.axis); + VectorCopy(cent->lerpOrigin, powerup.origin); + powerup.origin[2] += -24 + (float) t * 80 / 500; + if ( t > 400 ) { + c = (float) (t - 1000) * 0xff / 100; + powerup.shaderRGBA[0] = 0xff - c; + powerup.shaderRGBA[1] = 0xff - c; + powerup.shaderRGBA[2] = 0xff - c; + powerup.shaderRGBA[3] = 0xff - c; + } + else { + powerup.shaderRGBA[0] = 0xff; + powerup.shaderRGBA[1] = 0xff; + powerup.shaderRGBA[2] = 0xff; + powerup.shaderRGBA[3] = 0xff; + } + trap_R_AddRefEntityToScene( &powerup ); + } +#endif // MISSIONPACK + + // + // add the head + // + head.hModel = ci->headModel; + if (!head.hModel) { + return; + } + head.customSkin = ci->headSkin; + + VectorCopy( cent->lerpOrigin, head.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &head, &torso, ci->torsoModel, "tag_head"); + + head.shadowPlane = shadowPlane; + head.renderfx = renderfx; + + CG_AddRefEntityWithPowerups( &head, ¢->currentState, ci->team ); + + //PKMOD - Ergodic 05/07/02 - if Personal Sentry is active on player + if ( cent->currentState.powerups & ( 1 << PW_PERSENTRY ) ) { +// if ( cent->currentState.modelindex2 & ( 1 << PKA_PERSENTRY_ACTIVE ) ) { + + //PKMOD - Ergodic 05/07/02 - debug persentry model (inactive) +// Com_Printf( "CG_Player - attaching persentry\n" ); + + memcpy(&persentry, &torso, sizeof(torso)); + persentry.hModel = cgs.media.persentry_active; + holdtime = cg.time / 50; // sec * ( 20f/sec / 1000 ) + persentry.frame = holdtime % 149; + persentry.oldframe = 0; + persentry.customSkin = 0; + trap_R_AddRefEntityToScene( &persentry ); + +//>>>>>>>>>>>> +/* + memset( &persentry, 0, sizeof(persentry) ); + + persentry.hModel = cgs.media.persentry_active; + +// torso.customSkin = ci->torsoSkin; + + VectorCopy( cent->lerpOrigin, persentry.lightingOrigin ); + + CG_PositionRotatedEntityOnTag( &persentry, &legs, ci->legsModel, "tag_persentry"); + + persentry.shadowPlane = shadowPlane; + persentry.renderfx = renderfx; + + CG_AddRefEntityWithPowerups( &persentry, ¢->currentState, ci->team ); + +//>>>>>>>>>>>> +*/ + + } + + +#ifdef MISSIONPACK + CG_BreathPuffs(cent, &head); + + CG_DustTrail(cent); +#endif + + // + // add the gun / barrel / flash + // + CG_AddPlayerWeapon( &torso, NULL, cent, ci->team ); + + // add powerups floating behind the player + CG_PlayerPowerups( cent, &torso ); +} + + +//===================================================================== + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) { + cent->errorTime = -99999; // guarantee no error decay added + cent->extrapolated = qfalse; + + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.legs, cent->currentState.legsAnim ); + CG_ClearLerpFrame( &cgs.clientinfo[ cent->currentState.clientNum ], ¢->pe.torso, cent->currentState.torsoAnim ); + + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + + VectorCopy( cent->lerpOrigin, cent->rawOrigin ); + VectorCopy( cent->lerpAngles, cent->rawAngles ); + + memset( ¢->pe.legs, 0, sizeof( cent->pe.legs ) ); + cent->pe.legs.yawAngle = cent->rawAngles[YAW]; + cent->pe.legs.yawing = qfalse; + cent->pe.legs.pitchAngle = 0; + cent->pe.legs.pitching = qfalse; + + memset( ¢->pe.torso, 0, sizeof( cent->pe.legs ) ); + cent->pe.torso.yawAngle = cent->rawAngles[YAW]; + cent->pe.torso.yawing = qfalse; + cent->pe.torso.pitchAngle = cent->rawAngles[PITCH]; + cent->pe.torso.pitching = qfalse; + + if ( cg_debugPosition.integer ) { + CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); + } +} + diff --git a/quake3/source/code/cgame/cg_playerstate.c b/quake3/source/code/cgame/cg_playerstate.c new file mode 100644 index 0000000..9077729 --- /dev/null +++ b/quake3/source/code/cgame/cg_playerstate.c @@ -0,0 +1,556 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_playerstate.c -- this file acts on changes in a new playerState_t +// With normal play, this will be done after local prediction, but when +// following another player or playing back a demo, it will be checked +// when the snapshot transitions like all the other entities + +#include "cg_local.h" + +/* +============== +CG_CheckAmmo + +If the ammo has gone low enough to generate the warning, play a sound +============== +*/ +void CG_CheckAmmo( void ) { + int i; + int total; + int previous; + int weapons; + + // see about how many seconds of ammo we have remaining + weapons = cg.snap->ps.stats[ STAT_WEAPONS ]; + total = 0; + for ( i = WP_MACHINEGUN ; i < WP_NUM_WEAPONS ; i++ ) { + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + switch ( i ) { + case WP_ROCKET_LAUNCHER: + case WP_GRENADE_LAUNCHER: + case WP_RAILGUN: + case WP_SHOTGUN: +#ifdef MISSIONPACK + case WP_PROX_LAUNCHER: +#endif + total += cg.snap->ps.ammo[i] * 1000; + break; + default: + total += cg.snap->ps.ammo[i] * 200; + break; + } + if ( total >= 5000 ) { + cg.lowAmmoWarning = 0; + return; + } + } + + previous = cg.lowAmmoWarning; + + if ( total == 0 ) { + cg.lowAmmoWarning = 2; + } else { + cg.lowAmmoWarning = 1; + } + + // play a sound on transitions + if ( cg.lowAmmoWarning != previous ) { + trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); + } +} + +/* +============== +CG_DamageFeedback +============== +*/ +void CG_DamageFeedback( int yawByte, int pitchByte, int damage ) { + float left, front, up; + float kick; + int health; + float scale; + vec3_t dir; + vec3_t angles; + float dist; + float yaw, pitch; + + // show the attacking player's head and name in corner + cg.attackerTime = cg.time; + + // the lower on health you are, the greater the view kick will be + health = cg.snap->ps.stats[STAT_HEALTH]; + if ( health < 40 ) { + scale = 1; + } else { + scale = 40.0 / health; + } + kick = damage * scale; + + if (kick < 5) + kick = 5; + if (kick > 10) + kick = 10; + + // if yaw and pitch are both 255, make the damage always centered (falling, etc) + if ( yawByte == 255 && pitchByte == 255 ) { + cg.damageX = 0; + cg.damageY = 0; + cg.v_dmg_roll = 0; + cg.v_dmg_pitch = -kick; + } else { + // positional + pitch = pitchByte / 255.0 * 360; + yaw = yawByte / 255.0 * 360; + + angles[PITCH] = pitch; + angles[YAW] = yaw; + angles[ROLL] = 0; + + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors( angles, dir, NULL, NULL ); + AngleVectorsForward( angles, dir ); + VectorSubtract( vec3_origin, dir, dir ); + + front = DotProduct (dir, cg.refdef.viewaxis[0] ); + left = DotProduct (dir, cg.refdef.viewaxis[1] ); + up = DotProduct (dir, cg.refdef.viewaxis[2] ); + + dir[0] = front; + dir[1] = left; + dir[2] = 0; + dist = VectorLength( dir ); + if ( dist < 0.1 ) { + dist = 0.1f; + } + + cg.v_dmg_roll = kick * left; + + cg.v_dmg_pitch = -kick * front; + + if ( front <= 0.1 ) { + front = 0.1f; + } + cg.damageX = -left / front; + cg.damageY = up / dist; + } + + // clamp the position + if ( cg.damageX > 1.0 ) { + cg.damageX = 1.0; + } + if ( cg.damageX < - 1.0 ) { + cg.damageX = -1.0; + } + + if ( cg.damageY > 1.0 ) { + cg.damageY = 1.0; + } + if ( cg.damageY < - 1.0 ) { + cg.damageY = -1.0; + } + + // don't let the screen flashes vary as much + if ( kick > 10 ) { + kick = 10; + } + cg.damageValue = kick; + cg.v_dmg_time = cg.time + DAMAGE_TIME; + cg.damageTime = cg.snap->serverTime; +} + + + + +/* +================ +CG_Respawn + +A respawn happened this snapshot +================ +*/ +void CG_Respawn( void ) { + // no error decay on player movement + cg.thisFrameTeleport = qtrue; + + // display weapons available + cg.weaponSelectTime = cg.time; + + // select the weapon the server says we are using + cg.weaponSelect = cg.snap->ps.weapon; + + //PKMOD - Ergodic 04/04/01, holds last weapon +// cg.weaponLast = cg.snap->ps.stats[ STAT_LAST_WEAPON ]; + + //PKMOD - Ergodic 04/04/01 - debug (inactive) +// Com_Printf( "CG_Respawn - current>%d<, last>%d<\n", cg.weaponSelect, cg.weaponLast ); + +} + +extern char *eventnames[]; + +/* +============== +CG_CheckPlayerstateEvents +============== +*/ +void CG_CheckPlayerstateEvents( playerState_t *ps, playerState_t *ops ) { + int i; + int event; + centity_t *cent; + + if ( ps->externalEvent && ps->externalEvent != ops->externalEvent ) { + cent = &cg_entities[ ps->clientNum ]; + cent->currentState.event = ps->externalEvent; + cent->currentState.eventParm = ps->externalEventParm; + CG_EntityEvent( cent, cent->lerpOrigin ); + } + + cent = &cg.predictedPlayerEntity; // cg_entities[ ps->clientNum ]; + // go through the predictable events buffer + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // if we have a new predictable event + if ( i >= ops->eventSequence + // or the server told us to play another event instead of a predicted event we already issued + // or something the server told us changed our prediction causing a different event + || (i > ops->eventSequence - MAX_PS_EVENTS && ps->events[i & (MAX_PS_EVENTS-1)] != ops->events[i & (MAX_PS_EVENTS-1)]) ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + cg.eventSequence++; + } + } +} + +/* +================== +CG_CheckChangedPredictableEvents +================== +*/ +void CG_CheckChangedPredictableEvents( playerState_t *ps ) { + int i; + int event; + centity_t *cent; + + cent = &cg.predictedPlayerEntity; + for ( i = ps->eventSequence - MAX_PS_EVENTS ; i < ps->eventSequence ; i++ ) { + // + if (i >= cg.eventSequence) { + continue; + } + // if this event is not further back in than the maximum predictable events we remember + if (i > cg.eventSequence - MAX_PREDICTED_EVENTS) { + // if the new playerstate event is different from a previously predicted one + if ( ps->events[i & (MAX_PS_EVENTS-1)] != cg.predictableEvents[i & (MAX_PREDICTED_EVENTS-1) ] ) { + + event = ps->events[ i & (MAX_PS_EVENTS-1) ]; + cent->currentState.event = event; + cent->currentState.eventParm = ps->eventParms[ i & (MAX_PS_EVENTS-1) ]; + CG_EntityEvent( cent, cent->lerpOrigin ); + + cg.predictableEvents[ i & (MAX_PREDICTED_EVENTS-1) ] = event; + + if ( cg_showmiss.integer ) { + CG_Printf("WARNING: changed predicted event\n"); + } + } + } + } +} + +/* +================== +pushReward +================== +*/ +static void pushReward(sfxHandle_t sfx, qhandle_t shader, int rewardCount) { + if (cg.rewardStack < (MAX_REWARDSTACK-1)) { + cg.rewardStack++; + cg.rewardSound[cg.rewardStack] = sfx; + cg.rewardShader[cg.rewardStack] = shader; + cg.rewardCount[cg.rewardStack] = rewardCount; + } +} + +/* +================== +CG_CheckLocalSounds +================== +*/ +void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { + int highScore, health, armor, reward; + sfxHandle_t sfx; + int hold_stat_bits; //PKMOD - Ergodic 02/05/02 - hold the ATTACK bits for the proper sounds + + // don't play the sounds if the player just changed teams + if ( ps->persistant[PERS_TEAM] != ops->persistant[PERS_TEAM] ) { + return; + } + + // hit changes + if ( ps->persistant[PERS_HITS] > ops->persistant[PERS_HITS] ) { + armor = ps->persistant[PERS_ATTACKEE_ARMOR] & 0xff; + health = ps->persistant[PERS_ATTACKEE_ARMOR] >> 8; +#ifdef MISSIONPACK + if (armor > 50 ) { + trap_S_StartLocalSound( cgs.media.hitSoundHighArmor, CHAN_LOCAL_SOUND ); + } else if (armor || health > 100) { + trap_S_StartLocalSound( cgs.media.hitSoundLowArmor, CHAN_LOCAL_SOUND ); + } else { + trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + } +#else + //PKMOD - Ergodic 02/05/02 - decode the ATTACK bits for the proper sounds + hold_stat_bits = ps->stats[STAT_PKA_BITS] & PKA_BITS_SENTRYATTACK; + switch ( hold_stat_bits ) { + case PKA_BITS_BEARTRAPATTACK: + trap_S_StartLocalSound( cgs.media.beartrap_attackSound, CHAN_LOCAL_SOUND ); + break; + case PKA_BITS_SENTRYATTACK: + trap_S_StartLocalSound( cgs.media.autosentry_attackSound, CHAN_LOCAL_SOUND ); + break; + case PKA_BITS_RADIATEATTACK: + trap_S_StartLocalSound( cgs.media.radiate_attackSound, CHAN_LOCAL_SOUND ); + break; + default: + trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + break; + } + +#endif + } else if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { + trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); + } + + // health changes of more than -1 should make pain sounds + if ( ps->stats[STAT_HEALTH] < ops->stats[STAT_HEALTH] - 1 ) { + if ( ps->stats[STAT_HEALTH] > 0 ) { + CG_PainEvent( &cg.predictedPlayerEntity, ps->stats[STAT_HEALTH] ); + } + } + + + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + // reward sounds + reward = qfalse; + if (ps->persistant[PERS_CAPTURES] != ops->persistant[PERS_CAPTURES]) { + pushReward(cgs.media.captureAwardSound, cgs.media.medalCapture, ps->persistant[PERS_CAPTURES]); + reward = qtrue; + //Com_Printf("capture\n"); + } + if (ps->persistant[PERS_IMPRESSIVE_COUNT] != ops->persistant[PERS_IMPRESSIVE_COUNT]) { +#ifdef MISSIONPACK + if (ps->persistant[PERS_IMPRESSIVE_COUNT] == 1) { + sfx = cgs.media.firstImpressiveSound; + } else { + sfx = cgs.media.impressiveSound; + } +#else + sfx = cgs.media.impressiveSound; +#endif + pushReward(sfx, cgs.media.medalImpressive, ps->persistant[PERS_IMPRESSIVE_COUNT]); + reward = qtrue; + //Com_Printf("impressive\n"); + } + if (ps->persistant[PERS_EXCELLENT_COUNT] != ops->persistant[PERS_EXCELLENT_COUNT]) { +#ifdef MISSIONPACK + if (ps->persistant[PERS_EXCELLENT_COUNT] == 1) { + sfx = cgs.media.firstExcellentSound; + } else { + sfx = cgs.media.excellentSound; + } +#else + sfx = cgs.media.excellentSound; +#endif + pushReward(sfx, cgs.media.medalExcellent, ps->persistant[PERS_EXCELLENT_COUNT]); + reward = qtrue; + //Com_Printf("excellent\n"); + } + if (ps->persistant[PERS_GAUNTLET_FRAG_COUNT] != ops->persistant[PERS_GAUNTLET_FRAG_COUNT]) { +#ifdef MISSIONPACK + if (ops->persistant[PERS_GAUNTLET_FRAG_COUNT] == 1) { + sfx = cgs.media.firstHumiliationSound; + } else { + sfx = cgs.media.humiliationSound; + } +#else + sfx = cgs.media.humiliationSound; +#endif + pushReward(sfx, cgs.media.medalGauntlet, ps->persistant[PERS_GAUNTLET_FRAG_COUNT]); + reward = qtrue; + //Com_Printf("guantlet frag\n"); + } + if (ps->persistant[PERS_DEFEND_COUNT] != ops->persistant[PERS_DEFEND_COUNT]) { + pushReward(cgs.media.defendSound, cgs.media.medalDefend, ps->persistant[PERS_DEFEND_COUNT]); + reward = qtrue; + //Com_Printf("defend\n"); + } + if (ps->persistant[PERS_ASSIST_COUNT] != ops->persistant[PERS_ASSIST_COUNT]) { + pushReward(cgs.media.assistSound, cgs.media.medalAssist, ps->persistant[PERS_ASSIST_COUNT]); + reward = qtrue; + //Com_Printf("assist\n"); + } + //PKMOD - Ergodic 08/08/00 PAINKILLER awarded after every 10 PKitem kills + //PKMOD - Ergodic 12/16/00 - shift off add HUB_FLAG (first bit of PERS_PAINKILLER_COUNT) + if ( ( (ps->persistant[PERS_PAINKILLER_COUNT] >> 1) / 10 ) != ( (ops->persistant[PERS_PAINKILLER_COUNT] >> 1) / 10 )) { + //PKMOD - Ergodic 12/26/00 fixed typo on painkiller announcement sound + sfx = cgs.media.painkillerSound; + pushReward(sfx, cgs.media.medalPainKiller, (ps->persistant[PERS_PAINKILLER_COUNT] >> 1) / 10); + reward = qtrue; + //Com_Printf("painkiller\n"); + } + + //PKMOD OLD 1.17 codebase + //PKMOD - Ergodic 08/08/00 PAINKILLER awarded after every 10 PKitem kills +// case REWARD_PAINKILLER: +// trap_S_StartLocalSound( cgs.media.painkillerSound, CHAN_ANNOUNCER ); +// cg.rewardTime = cg.time; +// cg.rewardShader = cgs.media.medalPainKiller; +// cg.rewardCount = ps->persistant[PERS_PAINKILLER_COUNT] / 10; +// break; + + + // if any of the player event bits changed + if (ps->persistant[PERS_PLAYEREVENTS] != ops->persistant[PERS_PLAYEREVENTS]) { + if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_DENIEDREWARD)) { + trap_S_StartLocalSound( cgs.media.deniedSound, CHAN_ANNOUNCER ); + } + else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_GAUNTLETREWARD)) { + trap_S_StartLocalSound( cgs.media.humiliationSound, CHAN_ANNOUNCER ); + } + else if ((ps->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT) != + (ops->persistant[PERS_PLAYEREVENTS] & PLAYEREVENT_HOLYSHIT)) { + trap_S_StartLocalSound( cgs.media.holyShitSound, CHAN_ANNOUNCER ); + } + reward = qtrue; + } + + // check for flag pickup + if ( cgs.gametype >= GT_TEAM ) { + if ((ps->powerups[PW_REDFLAG] != ops->powerups[PW_REDFLAG] && ps->powerups[PW_REDFLAG]) || + (ps->powerups[PW_BLUEFLAG] != ops->powerups[PW_BLUEFLAG] && ps->powerups[PW_BLUEFLAG]) || + (ps->powerups[PW_NEUTRALFLAG] != ops->powerups[PW_NEUTRALFLAG] && ps->powerups[PW_NEUTRALFLAG]) ) + { + trap_S_StartLocalSound( cgs.media.youHaveFlagSound, CHAN_ANNOUNCER ); + } + } + + // lead changes + if (!reward) { + // + if ( !cg.warmup ) { + // never play lead changes during warmup + if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { + //PKMOD - Ergodic 01/20/02 - don't play sounds when comparing with private bots + //PKMOD - Ergodic 02/05/02 - change STAT_PKA_BITS settings from enum type to definition + if ( !( ( ops->stats[STAT_PKA_BITS] & PKA_BITS_PRIVATEBOT ) || ( ps->stats[STAT_PKA_BITS] & PKA_BITS_PRIVATEBOT ) ) ) { + if ( cgs.gametype < GT_TEAM) { + if ( ps->persistant[PERS_RANK] == 0 ) { + CG_AddBufferedSound(cgs.media.takenLeadSound); + } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { + CG_AddBufferedSound(cgs.media.tiedLeadSound); + } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { + CG_AddBufferedSound(cgs.media.lostLeadSound); + } + } + } + } + } + } + + // timelimit warnings + if ( cgs.timelimit > 0 ) { + int msec; + + msec = cg.time - cgs.levelStartTime; + if ( !( cg.timelimitWarnings & 4 ) && msec > ( cgs.timelimit * 60 + 2 ) * 1000 ) { + cg.timelimitWarnings |= 1 | 2 | 4; + trap_S_StartLocalSound( cgs.media.suddenDeathSound, CHAN_ANNOUNCER ); + } + else if ( !( cg.timelimitWarnings & 2 ) && msec > (cgs.timelimit - 1) * 60 * 1000 ) { + cg.timelimitWarnings |= 1 | 2; + trap_S_StartLocalSound( cgs.media.oneMinuteSound, CHAN_ANNOUNCER ); + } + else if ( cgs.timelimit > 5 && !( cg.timelimitWarnings & 1 ) && msec > (cgs.timelimit - 5) * 60 * 1000 ) { + cg.timelimitWarnings |= 1; + trap_S_StartLocalSound( cgs.media.fiveMinuteSound, CHAN_ANNOUNCER ); + } + } + + // fraglimit warnings + if ( cgs.fraglimit > 0 && cgs.gametype < GT_CTF) { + highScore = cgs.scores1; + if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { + cg.fraglimitWarnings |= 1 | 2 | 4; + CG_AddBufferedSound(cgs.media.oneFragSound); + } + else if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { + cg.fraglimitWarnings |= 1 | 2; + CG_AddBufferedSound(cgs.media.twoFragSound); + } + else if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { + cg.fraglimitWarnings |= 1; + CG_AddBufferedSound(cgs.media.threeFragSound); + } + } +} + +/* +=============== +CG_TransitionPlayerState + +=============== +*/ +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ) { + // check for changing follow mode + if ( ps->clientNum != ops->clientNum ) { + cg.thisFrameTeleport = qtrue; + // make sure we don't get any unwanted transition effects + *ops = *ps; + } + + // damage events (player is getting wounded) + if ( ps->damageEvent != ops->damageEvent && ps->damageCount ) { + CG_DamageFeedback( ps->damageYaw, ps->damagePitch, ps->damageCount ); + } + + // respawning + if ( ps->persistant[PERS_SPAWN_COUNT] != ops->persistant[PERS_SPAWN_COUNT] ) { + CG_Respawn(); + } + + if ( cg.mapRestart ) { + CG_Respawn(); + cg.mapRestart = qfalse; + } + + if ( cg.snap->ps.pm_type != PM_INTERMISSION + && ps->persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + CG_CheckLocalSounds( ps, ops ); + } + + // check for going low on ammo + CG_CheckAmmo(); + + // run events + CG_CheckPlayerstateEvents( ps, ops ); + + // smooth the ducking viewheight change + if ( ps->viewheight != ops->viewheight ) { + cg.duckChange = ps->viewheight - ops->viewheight; + cg.duckTime = cg.time; + } +} + diff --git a/quake3/source/code/cgame/cg_predict.c b/quake3/source/code/cgame/cg_predict.c new file mode 100644 index 0000000..221469b --- /dev/null +++ b/quake3/source/code/cgame/cg_predict.c @@ -0,0 +1,643 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_predict.c -- this file generates cg.predictedPlayerState by either +// interpolating between snapshots from the server or locally predicting +// ahead the client's movement. +// It also handles local physics interaction, like fragments bouncing off walls + +#include "cg_local.h" + +static pmove_t cg_pmove; + +static int cg_numSolidEntities; +static centity_t *cg_solidEntities[MAX_ENTITIES_IN_SNAPSHOT]; +static int cg_numTriggerEntities; +static centity_t *cg_triggerEntities[MAX_ENTITIES_IN_SNAPSHOT]; + +/* +==================== +CG_BuildSolidList + +When a new cg.snap has been set, this function builds a sublist +of the entities that are actually solid, to make for more +efficient collision detection +==================== +*/ +void CG_BuildSolidList( void ) { + int i; + centity_t *cent; + snapshot_t *snap; + entityState_t *ent; + + cg_numSolidEntities = 0; + cg_numTriggerEntities = 0; + + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + snap = cg.nextSnap; + } else { + snap = cg.snap; + } + + for ( i = 0 ; i < snap->numEntities ; i++ ) { + cent = &cg_entities[ snap->entities[ i ].number ]; + ent = ¢->currentState; + + //PKMOD Ergodic debug 07/04/01 (inactive) +// if ( ent->eType == ET_ZOMBIE ) { +// Com_Printf( "CG_BuildSolidList et_zombie found\n" ); +// } + + + //PKMOD Ergodic debug 09/30/01 (inactive) +// if ( ent->eType == ET_LIGHTNING_FX ) { +// Com_Printf( "CG_BuildSolidList ET_LIGHTNING_FX found\n" ); +// } + + + //PKMOD - Ergodic. 05/30/00 modify for beartrap + //PKMOD - Ergodic 11/15/00 add functionality to make trigger_push silent + if ( ent->eType == ET_BEARTRAP || ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER || ent->eType == ET_QUIET_TRIGGER) { + cg_triggerEntities[cg_numTriggerEntities] = cent; + cg_numTriggerEntities++; + continue; + } + + if ( cent->nextState.solid ) { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + continue; + } + } +} + +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +static void CG_ClipMoveToEntities ( const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask, trace_t *tr ) { + int i, x, zd, zu; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + vec3_t bmins, bmaxs; + vec3_t origin, angles; + centity_t *cent; + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + ent = ¢->currentState; + + if ( ent->number == skipNumber ) { + continue; + } + + if ( ent->solid == SOLID_BMODEL ) { + // special value for bmodel + cmodel = trap_CM_InlineModel( ent->modelindex ); + VectorCopy( cent->lerpAngles, angles ); + BG_EvaluateTrajectory( ¢->currentState.pos, cg.physicsTime, origin ); + } else { + // encoded bbox + x = (ent->solid & 255); + zd = ((ent->solid>>8) & 255); + zu = ((ent->solid>>16) & 255) - 32; + + bmins[0] = bmins[1] = -x; + bmaxs[0] = bmaxs[1] = x; + bmins[2] = -zd; + bmaxs[2] = zu; + + cmodel = trap_CM_TempBoxModel( bmins, bmaxs ); + VectorCopy( vec3_origin, angles ); + VectorCopy( cent->lerpOrigin, origin ); + } + + + trap_CM_TransformedBoxTrace ( &trace, start, end, + mins, maxs, cmodel, mask, origin, angles); + + if (trace.allsolid || trace.fraction < tr->fraction) { + trace.entityNum = ent->number; + *tr = trace; + } else if (trace.startsolid) { + tr->startsolid = qtrue; + } + if ( tr->allsolid ) { + return; + } + } +} + +/* +================ +CG_Trace +================ +*/ +void CG_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) { + + trace_t t; + + trap_CM_BoxTrace ( &t, start, end, mins, maxs, 0, mask); + t.entityNum = t.fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; + // check all other solid models + CG_ClipMoveToEntities (start, mins, maxs, end, skipNumber, mask, &t); + + *result = t; +} + +/* +================ +CG_PointContents +================ +*/ +int CG_PointContents( const vec3_t point, int passEntityNum ) { + int i; + entityState_t *ent; + centity_t *cent; + clipHandle_t cmodel; + int contents; + + contents = trap_CM_PointContents (point, 0); + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + + ent = ¢->currentState; + + if ( ent->number == passEntityNum ) { + continue; + } + + if (ent->solid != SOLID_BMODEL) { // special value for bmodel + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + contents |= trap_CM_TransformedPointContents( point, cmodel, ent->origin, ent->angles ); + } + + return contents; +} + + +/* +======================== +CG_InterpolatePlayerState + +Generates cg.predictedPlayerState by interpolating between +cg.snap->player_state and cg.nextFrame->player_state +======================== +*/ +static void CG_InterpolatePlayerState( qboolean grabAngles ) { + float f; + int i; + playerState_t *out; + snapshot_t *prev, *next; + + out = &cg.predictedPlayerState; + prev = cg.snap; + next = cg.nextSnap; + + *out = cg.snap->ps; + + // if we are still allowing local input, short circuit the view angles + if ( grabAngles ) { + usercmd_t cmd; + int cmdNum; + + cmdNum = trap_GetCurrentCmdNumber(); + trap_GetUserCmd( cmdNum, &cmd ); + + PM_UpdateViewAngles( out, &cmd ); + } + + // if the next frame is a teleport, we can't lerp to it + if ( cg.nextFrameTeleport ) { + return; + } + + if ( !next || next->serverTime <= prev->serverTime ) { + return; + } + + f = (float)( cg.time - prev->serverTime ) / ( next->serverTime - prev->serverTime ); + + i = next->ps.bobCycle; + if ( i < prev->ps.bobCycle ) { + i += 256; // handle wraparound + } + out->bobCycle = prev->ps.bobCycle + f * ( i - prev->ps.bobCycle ); + + for ( i = 0 ; i < 3 ; i++ ) { + out->origin[i] = prev->ps.origin[i] + f * (next->ps.origin[i] - prev->ps.origin[i] ); + if ( !grabAngles ) { + out->viewangles[i] = LerpAngle( + prev->ps.viewangles[i], next->ps.viewangles[i], f ); + } + out->velocity[i] = prev->ps.velocity[i] + + f * (next->ps.velocity[i] - prev->ps.velocity[i] ); + } + +} + +/* +=================== +CG_TouchItem +//PKMOD - Ergodic 09/26/00 - don't register the touching of the voting_image entity. +// The voting_image is not a pickup item +=================== +*/ +static void CG_TouchItem( centity_t *cent ) { + gitem_t *item; + + if ( !cg_predictItems.integer ) { + return; + } + if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) { + return; + } + + // never pick an item up twice in a prediction + if ( cent->miscTime == cg.time ) { + return; + } + + if ( !BG_CanItemBeGrabbed( cgs.gametype, ¢->currentState, &cg.predictedPlayerState ) ) { + return; // can't hold it + } + + item = &bg_itemlist[ cent->currentState.modelindex ]; + + //PKMOD - Ergodic 09/26/00 - don't pickup the voting_image + if ( item->giType == IT_VOTING ) { + //PKMOD - Ergodic 09/28/00 - debug inactive +// Com_Printf("CG_TouchItem - voting entity index >%d<\n", cent->currentState.otherEntityNum ); + return; + } + + // Special case for flags. + // We don't predict touching our own flag +#ifdef MISSIONPACK + if( cgs.gametype == GT_1FCTF ) { + if( item->giTag != PW_NEUTRALFLAG ) { + return; + } + } + if( cgs.gametype == GT_CTF || cgs.gametype == GT_HARVESTER ) { +#else + if( cgs.gametype == GT_CTF ) { +#endif + if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_RED && + item->giTag == PW_REDFLAG) + return; + if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_BLUE && + item->giTag == PW_BLUEFLAG) + return; + } + + // grab it + BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.modelindex , &cg.predictedPlayerState); + + // remove it from the frame so it won't be drawn + cent->currentState.eFlags |= EF_NODRAW; + + // don't touch it again this prediction + cent->miscTime = cg.time; + + // if its a weapon, give them some predicted ammo so the autoswitch will work + if ( item->giType == IT_WEAPON ) { + cg.predictedPlayerState.stats[ STAT_WEAPONS ] |= 1 << item->giTag; + if ( !cg.predictedPlayerState.ammo[ item->giTag ] ) { + cg.predictedPlayerState.ammo[ item->giTag ] = 1; + } + } +} + + +/* +========================= +CG_TouchTriggerPrediction + +Predict push triggers and items +========================= +*/ +static void CG_TouchTriggerPrediction( void ) { + int i; + trace_t trace; + entityState_t *ent; + clipHandle_t cmodel; + centity_t *cent; + qboolean spectator; + + // dead clients don't activate triggers + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + spectator = ( cg.predictedPlayerState.pm_type == PM_SPECTATOR ); + + if ( cg.predictedPlayerState.pm_type != PM_NORMAL && !spectator ) { + return; + } + + for ( i = 0 ; i < cg_numTriggerEntities ; i++ ) { + cent = cg_triggerEntities[ i ]; + ent = ¢->currentState; + + if ( ent->eType == ET_ITEM && !spectator ) { + CG_TouchItem( cent ); + continue; + } + +//PKMOD - Ergodic. 05/30/00 modify for beartrap + if ( ent->eType == ET_BEARTRAP && !spectator ) { +//PKMOD - Ergodic 06/03/00 don't fire up cg_touchitem for launched beartraps +// CG_TouchItem( cent ); + continue; + } + + + if ( ent->solid != SOLID_BMODEL ) { + continue; + } + + cmodel = trap_CM_InlineModel( ent->modelindex ); + if ( !cmodel ) { + continue; + } + + trap_CM_BoxTrace( &trace, cg.predictedPlayerState.origin, cg.predictedPlayerState.origin, + cg_pmove.mins, cg_pmove.maxs, cmodel, -1 ); + + if ( !trace.startsolid ) { + continue; + } + + if ( ent->eType == ET_TELEPORT_TRIGGER ) { + cg.hyperspace = qtrue; + } else if ( ( ent->eType == ET_PUSH_TRIGGER ) || ( ent->eType == ET_TELEPORT_TRIGGER ) || ( ent->eType == ET_QUIET_TRIGGER ) ){ + BG_TouchJumpPad( &cg.predictedPlayerState, ent ); + } + } + + // if we didn't touch a jump pad this pmove frame + if ( cg.predictedPlayerState.jumppad_frame != cg.predictedPlayerState.pmove_framecount ) { + cg.predictedPlayerState.jumppad_frame = 0; + cg.predictedPlayerState.jumppad_ent = 0; + } +} + + + +/* +================= +CG_PredictPlayerState + +Generates cg.predictedPlayerState for the current cg.time +cg.predictedPlayerState is guaranteed to be valid after exiting. + +For demo playback, this will be an interpolation between two valid +playerState_t. + +For normal gameplay, it will be the result of predicted usercmd_t on +top of the most recent playerState_t received from the server. + +Each new snapshot will usually have one or more new usercmd over the last, +but we simulate all unacknowledged commands each time, not just the new ones. +This means that on an internet connection, quite a few pmoves may be issued +each frame. + +OPTIMIZE: don't re-simulate unless the newly arrived snapshot playerState_t +differs from the predicted one. Would require saving all intermediate +playerState_t during prediction. + +We detect prediction errors and allow them to be decayed off over several frames +to ease the jerk. +================= +*/ +void CG_PredictPlayerState( void ) { + int cmdNum, current; + playerState_t oldPlayerState; + qboolean moved; + usercmd_t oldestCmd; + usercmd_t latestCmd; + + cg.hyperspace = qfalse; // will be set if touching a trigger_teleport + + // if this is the first frame we must guarantee + // predictedPlayerState is valid even if there is some + // other error condition + if ( !cg.validPPS ) { + cg.validPPS = qtrue; + cg.predictedPlayerState = cg.snap->ps; + } + + + // demo playback just copies the moves + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) { + CG_InterpolatePlayerState( qfalse ); + return; + } + + // non-predicting local movement will grab the latest angles + if ( cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_InterpolatePlayerState( qtrue ); + return; + } + + // prepare for pmove + cg_pmove.ps = &cg.predictedPlayerState; + cg_pmove.trace = CG_Trace; + cg_pmove.pointcontents = CG_PointContents; + if ( cg_pmove.ps->pm_type == PM_DEAD ) { + cg_pmove.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else { + cg_pmove.tracemask = MASK_PLAYERSOLID; + } + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + cg_pmove.tracemask &= ~CONTENTS_BODY; // spectators can fly through bodies + } + cg_pmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; + + // save the state before the pmove so we can detect transitions + oldPlayerState = cg.predictedPlayerState; + + current = trap_GetCurrentCmdNumber(); + + // if we don't have the commands right after the snapshot, we + // can't accurately predict a current position, so just freeze at + // the last good position we had + cmdNum = current - CMD_BACKUP + 1; + trap_GetUserCmd( cmdNum, &oldestCmd ); + if ( oldestCmd.serverTime > cg.snap->ps.commandTime + && oldestCmd.serverTime < cg.time ) { // special check for map_restart + if ( cg_showmiss.integer ) { + CG_Printf ("exceeded PACKET_BACKUP on commands\n"); + } + return; + } + + // get the latest command so we can know which commands are from previous map_restarts + trap_GetUserCmd( current, &latestCmd ); + + // get the most recent information we have, even if + // the server time is beyond our current cg.time, + // because predicted player positions are going to + // be ahead of everything else anyway + if ( cg.nextSnap && !cg.nextFrameTeleport && !cg.thisFrameTeleport ) { + cg.predictedPlayerState = cg.nextSnap->ps; + cg.physicsTime = cg.nextSnap->serverTime; + } else { + cg.predictedPlayerState = cg.snap->ps; + cg.physicsTime = cg.snap->serverTime; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) { + trap_Cvar_Set("pmove_msec", "33"); + } + + cg_pmove.pmove_fixed = pmove_fixed.integer;// | cg_pmove_fixed.integer; + cg_pmove.pmove_msec = pmove_msec.integer; + + // run cmds + moved = qfalse; + for ( cmdNum = current - CMD_BACKUP + 1 ; cmdNum <= current ; cmdNum++ ) { + // get the command + trap_GetUserCmd( cmdNum, &cg_pmove.cmd ); + + if ( cg_pmove.pmove_fixed ) { + PM_UpdateViewAngles( cg_pmove.ps, &cg_pmove.cmd ); + } + + // don't do anything if the time is before the snapshot player time + if ( cg_pmove.cmd.serverTime <= cg.predictedPlayerState.commandTime ) { + continue; + } + + // don't do anything if the command was from a previous map_restart + if ( cg_pmove.cmd.serverTime > latestCmd.serverTime ) { + continue; + } + + // check for a prediction error from last frame + // on a lan, this will often be the exact value + // from the snapshot, but on a wan we will have + // to predict several commands to get to the point + // we want to compare + if ( cg.predictedPlayerState.commandTime == oldPlayerState.commandTime ) { + vec3_t delta; + float len; + + if ( cg.thisFrameTeleport ) { + // a teleport will not cause an error decay + VectorClear( cg.predictedError ); + if ( cg_showmiss.integer ) { + CG_Printf( "PredictionTeleport\n" ); + } + cg.thisFrameTeleport = qfalse; + } else { + vec3_t adjusted; + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); + + if ( cg_showmiss.integer ) { + if (!VectorCompare( oldPlayerState.origin, adjusted )) { + CG_Printf("prediction error\n"); + } + } + VectorSubtract( oldPlayerState.origin, adjusted, delta ); + len = VectorLength( delta ); + if ( len > 0.1 ) { + if ( cg_showmiss.integer ) { + CG_Printf("Prediction miss: %f\n", len); + } + if ( cg_errorDecay.integer ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f < 0 ) { + f = 0; + } + if ( f > 0 && cg_showmiss.integer ) { + CG_Printf("Double prediction decay: %f\n", f); + } + VectorScale( cg.predictedError, f, cg.predictedError ); + } else { + VectorClear( cg.predictedError ); + } + VectorAdd( delta, cg.predictedError, cg.predictedError ); + cg.predictedErrorTime = cg.oldTime; + } + } + } + + // don't predict gauntlet firing, which is only supposed to happen + // when it actually inflicts damage + cg_pmove.gauntletHit = qfalse; + + if ( cg_pmove.pmove_fixed ) { + cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + } + + //PKMOD - Ergodic 04/05/01 - debug (inactive) +// Com_Printf( "CG_PredictPlayerState - calling pmove\n" ); + + Pmove (&cg_pmove); + + moved = qtrue; + + // add push trigger movement effects + CG_TouchTriggerPrediction(); + + // check for predictable events that changed from previous predictions + //CG_CheckChangedPredictableEvents(&cg.predictedPlayerState); + } + + if ( cg_showmiss.integer > 1 ) { + CG_Printf( "[%i : %i] ", cg_pmove.cmd.serverTime, cg.time ); + } + + if ( !moved ) { + if ( cg_showmiss.integer ) { + CG_Printf( "not moved\n" ); + } + return; + } + + // adjust for the movement of the groundentity + CG_AdjustPositionForMover( cg.predictedPlayerState.origin, + cg.predictedPlayerState.groundEntityNum, + cg.physicsTime, cg.time, cg.predictedPlayerState.origin ); + + if ( cg_showmiss.integer ) { + if (cg.predictedPlayerState.eventSequence > oldPlayerState.eventSequence + MAX_PS_EVENTS) { + CG_Printf("WARNING: dropped event\n"); + } + } + + // fire events and other transition triggered things + CG_TransitionPlayerState( &cg.predictedPlayerState, &oldPlayerState ); + + if ( cg_showmiss.integer ) { + if (cg.eventSequence > cg.predictedPlayerState.eventSequence) { + CG_Printf("WARNING: double event\n"); + cg.eventSequence = cg.predictedPlayerState.eventSequence; + } + } +} + + diff --git a/quake3/source/code/cgame/cg_public.h b/quake3/source/code/cgame/cg_public.h new file mode 100644 index 0000000..2aaef36 --- /dev/null +++ b/quake3/source/code/cgame/cg_public.h @@ -0,0 +1,218 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + + +#define CMD_BACKUP 64 +#define CMD_MASK (CMD_BACKUP - 1) +// allow a lot of command backups for very fast systems +// multiple commands may be combined into a single packet, so this +// needs to be larger than PACKET_BACKUP + + +#define MAX_ENTITIES_IN_SNAPSHOT 256 + +// snapshots are a view of the server at a given time + +// Snapshots are generated at regular time intervals by the server, +// but they may not be sent if a client's rate level is exceeded, or +// they may be dropped by the network. +typedef struct { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; + + int serverTime; // server time the message is valid for (in msec) + + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot + + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current +} snapshot_t; + +enum { + CGAME_EVENT_NONE, + CGAME_EVENT_TEAMMENU, + CGAME_EVENT_SCOREBOARD, + CGAME_EVENT_EDITHUD +}; + + +/* +================================================================== + +functions imported from the main executable + +================================================================== +*/ + +#define CGAME_IMPORT_API_VERSION 4 + +typedef enum { + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_CLEARSCENE, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDLIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, + CG_R_REGISTERFONT, + CG_KEY_ISDOWN, + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, + CG_PC_ADD_GLOBAL_DEFINE, + CG_PC_LOAD_SOURCE, + CG_PC_FREE_SOURCE, + CG_PC_READ_TOKEN, + CG_PC_SOURCE_FILE_AND_LINE, + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_REMOVECOMMAND, + CG_R_LIGHTFORPOINT, + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + CG_R_REMAP_SHADER, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + + CG_CM_TEMPCAPSULEMODEL, + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_R_ADDADDITIVELIGHTTOSCENE, + CG_GET_ENTITY_TOKEN, + CG_R_ADDPOLYSTOSCENE, + CG_R_INPVS, + // 1.32 + CG_FS_SEEK, + +/* + CG_LOADCAMERA, + CG_STARTCAMERA, + CG_GETCAMERAINFO, +*/ + + CG_MEMSET = 100, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_FLOOR, + CG_CEIL, + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_ACOS +} cgameImport_t; + + +/* +================================================================== + +functions exported to the main executable + +================================================================== +*/ + +typedef enum { + CG_INIT, +// void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, +// void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, +// qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, +// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, +// int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, +// int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, +// void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, +// void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING +// void (*CG_EventHandling)(int type); +} cgameExport_t; + +//---------------------------------------------- diff --git a/quake3/source/code/cgame/cg_scoreboard.c b/quake3/source/code/cgame/cg_scoreboard.c new file mode 100644 index 0000000..ed2c660 --- /dev/null +++ b/quake3/source/code/cgame/cg_scoreboard.c @@ -0,0 +1,531 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_scoreboard -- draw the scoreboard on top of the game screen +#include "cg_local.h" + + +#define SCOREBOARD_X (0) + +#define SB_HEADER 86 +#define SB_TOP (SB_HEADER+32) + +// Where the status bar starts, so we don't overwrite it +#define SB_STATUSBAR 420 + +#define SB_NORMAL_HEIGHT 40 +#define SB_INTER_HEIGHT 16 // interleaved height + +#define SB_MAXCLIENTS_NORMAL ((SB_STATUSBAR - SB_TOP) / SB_NORMAL_HEIGHT) +#define SB_MAXCLIENTS_INTER ((SB_STATUSBAR - SB_TOP) / SB_INTER_HEIGHT - 1) + +// Used when interleaved + + + +#define SB_LEFT_BOTICON_X (SCOREBOARD_X+0) +#define SB_LEFT_HEAD_X (SCOREBOARD_X+32) +#define SB_RIGHT_BOTICON_X (SCOREBOARD_X+64) +#define SB_RIGHT_HEAD_X (SCOREBOARD_X+96) +// Normal +#define SB_BOTICON_X (SCOREBOARD_X+32) +#define SB_HEAD_X (SCOREBOARD_X+64) + +#define SB_SCORELINE_X 112 + +#define SB_RATING_WIDTH (6 * BIGCHAR_WIDTH) // width 6 +#define SB_SCORE_X (SB_SCORELINE_X + BIGCHAR_WIDTH) // width 6 +#define SB_RATING_X (SB_SCORELINE_X + 6 * BIGCHAR_WIDTH) // width 6 +#define SB_PING_X (SB_SCORELINE_X + 12 * BIGCHAR_WIDTH + 8) // width 5 +#define SB_TIME_X (SB_SCORELINE_X + 17 * BIGCHAR_WIDTH + 8) // width 5 +#define SB_NAME_X (SB_SCORELINE_X + 22 * BIGCHAR_WIDTH) // width 15 + +// The new and improved score board +// +// In cases where the number of clients is high, the score board heads are interleaved +// here's the layout + +// +// 0 32 80 112 144 240 320 400 <-- pixel position +// bot head bot head score ping time name +// +// wins/losses are drawn on bot icon now + +static qboolean localClient; // true if local client has been displayed + + + /* +================= +CG_DrawScoreboard +================= +*/ +static void CG_DrawClientScore( int y, score_t *score, float *color, float fade, qboolean largeFormat ) { + char string[1024]; + vec3_t headAngles; + clientInfo_t *ci; + int iconx, headx; + + if ( score->client < 0 || score->client >= cgs.maxclients ) { + Com_Printf( "Bad score->client: %i\n", score->client ); + return; + } + + ci = &cgs.clientinfo[score->client]; + + iconx = SB_BOTICON_X + (SB_RATING_WIDTH / 2); + headx = SB_HEAD_X + (SB_RATING_WIDTH / 2); + + // draw the handicap or bot skill marker (unless player has flag) + if ( ci->powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if( largeFormat ) { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_FREE, qfalse ); + } + else { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_FREE, qfalse ); + } + } else if ( ci->powerups & ( 1 << PW_REDFLAG ) ) { + if( largeFormat ) { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_RED, qfalse ); + } + else { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_RED, qfalse ); + } + } else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) { + if( largeFormat ) { + CG_DrawFlagModel( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, TEAM_BLUE, qfalse ); + } + else { + CG_DrawFlagModel( iconx, y, 16, 16, TEAM_BLUE, qfalse ); + } + } else { + if ( ci->botSkill > 0 && ci->botSkill <= 5 ) { + if ( cg_drawIcons.integer ) { + if( largeFormat ) { + CG_DrawPic( iconx, y - ( 32 - BIGCHAR_HEIGHT ) / 2, 32, 32, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); + } + else { + CG_DrawPic( iconx, y, 16, 16, cgs.media.botSkillShaders[ ci->botSkill - 1 ] ); + } + } + } else if ( ci->handicap < 100 ) { + Com_sprintf( string, sizeof( string ), "%i", ci->handicap ); + if ( cgs.gametype == GT_TOURNAMENT ) + CG_DrawSmallStringColor( iconx, y - SMALLCHAR_HEIGHT/2, string, color ); + else + CG_DrawSmallStringColor( iconx, y, string, color ); + } + + // draw the wins / losses + if ( cgs.gametype == GT_TOURNAMENT ) { + Com_sprintf( string, sizeof( string ), "%i/%i", ci->wins, ci->losses ); + if( ci->handicap < 100 && !ci->botSkill ) { + CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, string, color ); + } + else { + CG_DrawSmallStringColor( iconx, y, string, color ); + } + } + } + + // draw the face + VectorClear( headAngles ); + headAngles[YAW] = 180; + if( largeFormat ) { + CG_DrawHead( headx, y - ( ICON_SIZE - BIGCHAR_HEIGHT ) / 2, ICON_SIZE, ICON_SIZE, + score->client, headAngles ); + } + else { + CG_DrawHead( headx, y, 16, 16, score->client, headAngles ); + } + +#ifdef MISSIONPACK + // draw the team task + if ( ci->teamTask != TEAMTASK_NONE ) { + if ( ci->teamTask == TEAMTASK_OFFENSE ) { + CG_DrawPic( headx + 48, y, 16, 16, cgs.media.assaultShader ); + } + else if ( ci->teamTask == TEAMTASK_DEFENSE ) { + CG_DrawPic( headx + 48, y, 16, 16, cgs.media.defendShader ); + } + } +#endif + // draw the score line + if ( score->ping == -1 ) { + Com_sprintf(string, sizeof(string), + " connecting %s", ci->name); + } else if ( ci->team == TEAM_SPECTATOR ) { + Com_sprintf(string, sizeof(string), + " SPECT %3i %4i %s", score->ping, score->time, ci->name); + } else { + Com_sprintf(string, sizeof(string), + "%5i %4i %4i %s", score->score, score->ping, score->time, ci->name); + } + + // highlight your position + if ( score->client == cg.snap->ps.clientNum ) { + float hcolor[4]; + int rank; + + localClient = qtrue; + + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR + || cgs.gametype >= GT_TEAM ) { + rank = -1; + } else { + rank = cg.snap->ps.persistant[PERS_RANK] & ~RANK_TIED_FLAG; + } + if ( rank == 0 ) { + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7f; + } else if ( rank == 1 ) { + hcolor[0] = 0.7f; + hcolor[1] = 0; + hcolor[2] = 0; + } else if ( rank == 2 ) { + hcolor[0] = 0.7f; + hcolor[1] = 0.7f; + hcolor[2] = 0; + } else { + hcolor[0] = 0.7f; + hcolor[1] = 0.7f; + hcolor[2] = 0.7f; + } + + hcolor[3] = fade * 0.7; + CG_FillRect( SB_SCORELINE_X + BIGCHAR_WIDTH + (SB_RATING_WIDTH / 2), y, + 640 - SB_SCORELINE_X - BIGCHAR_WIDTH, BIGCHAR_HEIGHT+1, hcolor ); + } + + CG_DrawBigString( SB_SCORELINE_X + (SB_RATING_WIDTH / 2), y, string, fade ); + + // add the "ready" marker for intermission exiting + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) { + CG_DrawBigStringColor( iconx, y, "READY", color ); + } +} + +/* +================= +CG_TeamScoreboard +================= +*/ +static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight ) { + int i; + score_t *score; + float color[4]; + int count; + clientInfo_t *ci; + + color[0] = color[1] = color[2] = 1.0; + color[3] = fade; + + count = 0; + for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) { + score = &cg.scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team != ci->team ) { + continue; + } + + //PKMOD - Ergodic 01/09/02 - add info to structure so Private Bot will not appear in scoreboard + if ( ci->privateBot ) { + continue; + } + + //PKMOD - Ergodic 01/09/02 - debug private bot on scoreboard (inactive) +// Com_Printf("CG_TeamScoreboard\n" ); + + CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); + + count++; + } + + return count; +} + +/* +================= +CG_DrawScoreboard + +Draw the normal in-game scoreboard +================= +*/ +qboolean CG_DrawOldScoreboard( void ) { + int x, y, w, i, n1, n2; + float fade; + float *fadeColor; + char *s; + int maxClients; + int lineHeight; + int topBorderSize, bottomBorderSize; + + // don't draw amuthing if the menu or console is up + if ( cg_paused.integer ) { + cg.deferredPlayerLoading = 0; + return qfalse; + } + + if ( cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + cg.deferredPlayerLoading = 0; + return qfalse; + } + + // don't draw scoreboard during death while warmup up + if ( cg.warmup && !cg.showScores ) { + return qfalse; + } + + if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || + cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + fade = 1.0; + fadeColor = colorWhite; + } else { + fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); + + if ( !fadeColor ) { + // next time scoreboard comes up, don't print killer + cg.deferredPlayerLoading = 0; + cg.killerName[0] = 0; + return qfalse; + } + fade = *fadeColor; + } + + + // fragged by ... line + if ( cg.killerName[0] ) { + s = va("Fragged by %s", cg.killerName ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + } + + // current rank + if ( cgs.gametype < GT_TEAM) { + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) { + s = va("%s place with %i", + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + cg.snap->ps.persistant[PERS_SCORE] ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 60; + CG_DrawBigString( x, y, s, fade ); + } + } else { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va("Teams are tied at %i", cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va("Red leads %i to %i",cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va("Blue leads %i to %i",cg.teamScores[1], cg.teamScores[0] ); + } + + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 60; + CG_DrawBigString( x, y, s, fade ); + } + + // scoreboard + y = SB_HEADER; + + CG_DrawPic( SB_SCORE_X + (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardScore ); + CG_DrawPic( SB_PING_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardPing ); + CG_DrawPic( SB_TIME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardTime ); + CG_DrawPic( SB_NAME_X - (SB_RATING_WIDTH / 2), y, 64, 32, cgs.media.scoreboardName ); + + y = SB_TOP; + + // If there are more than SB_MAXCLIENTS_NORMAL, use the interleaved scores + if ( cg.numScores > SB_MAXCLIENTS_NORMAL ) { + maxClients = SB_MAXCLIENTS_INTER; + lineHeight = SB_INTER_HEIGHT; + topBorderSize = 8; + bottomBorderSize = 16; + } else { + maxClients = SB_MAXCLIENTS_NORMAL; + lineHeight = SB_NORMAL_HEIGHT; + topBorderSize = 16; + bottomBorderSize = 16; + } + + localClient = qfalse; + + if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // + y += lineHeight/2; + + if ( cg.teamScores[0] >= cg.teamScores[1] ) { + n1 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n1; + n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n2; + } else { + n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n1; + n2 = CG_TeamScoreboard( y, TEAM_RED, fade, maxClients, lineHeight ); + CG_DrawTeamBackground( 0, y - topBorderSize, 640, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + maxClients -= n2; + } + n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + + } else { + // + // free for all scoreboard + // + n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + } + + if (!localClient) { + // draw local client at the bottom + for ( i = 0 ; i < cg.numScores ; i++ ) { + if ( cg.scores[i].client == cg.snap->ps.clientNum ) { + + //PKMOD - Ergodic 01/09/02 - add info to structure so Private Bot will not appear in scoreboard + if ( cgs.clientinfo[i].privateBot ) { + continue; + } + + //PKMOD - Ergodic 01/09/02 - debug private bot on scoreboard (inactive) +// Com_Printf("CG_DrawOldScoreboard\n" ); + + CG_DrawClientScore( y, &cg.scores[i], fadeColor, fade, lineHeight == SB_NORMAL_HEIGHT ); + break; + } + } + } + + // load any models that have been deferred + if ( ++cg.deferredPlayerLoading > 10 ) { + CG_LoadDeferredPlayers(); + } + + return qtrue; +} + +//================================================================================ + +/* +================ +CG_CenterGiantLine +================ +*/ +static void CG_CenterGiantLine( float y, const char *string ) { + float x; + vec4_t color; + + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + x = 0.5 * ( 640 - GIANT_WIDTH * CG_DrawStrlen( string ) ); + + CG_DrawStringExt( x, y, string, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); +} + +/* +================= +CG_DrawTourneyScoreboard + +Draw the oversize scoreboard for tournements +================= +*/ +void CG_DrawOldTourneyScoreboard( void ) { + const char *s; + vec4_t color; + int min, tens, ones; + clientInfo_t *ci; + int y; + int i; + + // request more scores regularly + if ( cg.scoresRequestTime + 2000 < cg.time ) { + cg.scoresRequestTime = cg.time; + trap_SendClientCommand( "score" ); + } + + color[0] = 1; + color[1] = 1; + color[2] = 1; + color[3] = 1; + + // draw the dialog background + color[0] = color[1] = color[2] = 0; + color[3] = 1; + CG_FillRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, color ); + + // print the mesage of the day + s = CG_ConfigString( CS_MOTD ); + if ( !s[0] ) { + s = "Scoreboard"; + } + + // print optional title + CG_CenterGiantLine( 8, s ); + + // print server time + ones = cg.time / 1000; + min = ones / 60; + ones %= 60; + tens = ones / 10; + ones %= 10; + s = va("%i:%i%i", min, tens, ones ); + + CG_CenterGiantLine( 64, s ); + + + // print the two scores + + y = 160; + if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // + CG_DrawStringExt( 8, y, "Red Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + s = va("%i", cg.teamScores[0] ); + CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + + y += 64; + + CG_DrawStringExt( 8, y, "Blue Team", color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + s = va("%i", cg.teamScores[1] ); + CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + } else { + // + // free for all scoreboard + // + for ( i = 0 ; i < MAX_CLIENTS ; i++ ) { + ci = &cgs.clientinfo[i]; + if ( !ci->infoValid ) { + continue; + } + if ( ci->team != TEAM_FREE ) { + continue; + } + + CG_DrawStringExt( 8, y, ci->name, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + s = va("%i", ci->score ); + CG_DrawStringExt( 632 - GIANT_WIDTH * strlen(s), y, s, color, qtrue, qtrue, GIANT_WIDTH, GIANT_HEIGHT, 0 ); + y += 64; + } + } + + +} + diff --git a/quake3/source/code/cgame/cg_servercmds.c b/quake3/source/code/cgame/cg_servercmds.c new file mode 100644 index 0000000..70c2a6d --- /dev/null +++ b/quake3/source/code/cgame/cg_servercmds.c @@ -0,0 +1,1200 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_servercmds.c -- reliably sequenced text commands sent by the server +// these are processed at snapshot transition time, so there will definately +// be a valid snapshot this frame + +#include "cg_local.h" +#include "../../ui/menudef.h" // bk001205 - for Q3_ui as well + +typedef struct { + const char *order; + int taskNum; +} orderTask_t; + +static const orderTask_t validOrders[] = { + { VOICECHAT_GETFLAG, TEAMTASK_OFFENSE }, + { VOICECHAT_OFFENSE, TEAMTASK_OFFENSE }, + { VOICECHAT_DEFEND, TEAMTASK_DEFENSE }, + { VOICECHAT_DEFENDFLAG, TEAMTASK_DEFENSE }, + { VOICECHAT_PATROL, TEAMTASK_PATROL }, + { VOICECHAT_CAMP, TEAMTASK_CAMP }, + { VOICECHAT_FOLLOWME, TEAMTASK_FOLLOW }, + { VOICECHAT_RETURNFLAG, TEAMTASK_RETRIEVE }, + { VOICECHAT_FOLLOWFLAGCARRIER, TEAMTASK_ESCORT } +}; + +static const int numValidOrders = sizeof(validOrders) / sizeof(orderTask_t); + +#ifdef MISSIONPACK // bk001204 +static int CG_ValidOrder(const char *p) { + int i; + for (i = 0; i < numValidOrders; i++) { + if (Q_stricmp(p, validOrders[i].order) == 0) { + return validOrders[i].taskNum; + } + } + return -1; +} +#endif + +/* +================= +CG_ParseScores + +================= +*/ +static void CG_ParseScores( void ) { + int i, powerups; + + cg.numScores = atoi( CG_Argv( 1 ) ); + if ( cg.numScores > MAX_CLIENTS ) { + cg.numScores = MAX_CLIENTS; + } + + cg.teamScores[0] = atoi( CG_Argv( 2 ) ); + cg.teamScores[1] = atoi( CG_Argv( 3 ) ); + + memset( cg.scores, 0, sizeof( cg.scores ) ); + for ( i = 0 ; i < cg.numScores ; i++ ) { + // + //PKMOD - Ergodic 03/16/04 - fix painkiller medal error: change form of index from "i * 14 + n" to "i * 15 + n" + cg.scores[i].client = atoi( CG_Argv( i * 15 + 4 ) ); + cg.scores[i].score = atoi( CG_Argv( i * 15 + 5 ) ); + cg.scores[i].ping = atoi( CG_Argv( i * 15 + 6 ) ); + cg.scores[i].time = atoi( CG_Argv( i * 15 + 7 ) ); + cg.scores[i].scoreFlags = atoi( CG_Argv( i * 15 + 8 ) ); + powerups = atoi( CG_Argv( i * 15 + 9 ) ); + cg.scores[i].accuracy = atoi(CG_Argv(i * 15 + 10)); + cg.scores[i].impressiveCount = atoi(CG_Argv(i * 15 + 11)); + cg.scores[i].excellentCount = atoi(CG_Argv(i * 15 + 12)); + cg.scores[i].guantletCount = atoi(CG_Argv(i * 15 + 13)); + cg.scores[i].defendCount = atoi(CG_Argv(i * 15 + 14)); + cg.scores[i].assistCount = atoi(CG_Argv(i * 15 + 15)); + cg.scores[i].perfect = atoi(CG_Argv(i * 15 + 16)); + cg.scores[i].captures = atoi(CG_Argv(i * 15 + 17)); + //PKMOD - Ergodic 02/28/04 - add PainKiller medal awards to ownerdraw + cg.scores[i].painkillerCount = atoi(CG_Argv(i * 15 + 18));; + + if ( cg.scores[i].client < 0 || cg.scores[i].client >= MAX_CLIENTS ) { + cg.scores[i].client = 0; + } + cgs.clientinfo[ cg.scores[i].client ].score = cg.scores[i].score; + cgs.clientinfo[ cg.scores[i].client ].powerups = powerups; + + cg.scores[i].team = cgs.clientinfo[cg.scores[i].client].team; + } +//PKMOD - Ergodic 02/23/04 - enable HUD based scoreboard +//#ifdef MISSIONPACK + CG_SetScoreSelection(NULL); +//#endif + +} + +//PKMOD - Ergodic 10/13/00 - store hubinfo into cg fields +/* +================= +CG_ParseHubInfo + +================= +*/ +static void CG_ParseHubInfo( void ) { + int i; + + numHubInfoLines = atoi( CG_Argv( 1 ) ); + + //PKMOD - Ergodic 10/13/00 - debug inactive +// Com_Printf("CG_ParseHubInfo: numHubInfoLines >%d<\n", numHubInfoLines ); + + + //PKMOD - Ergodic 11/01/00 - change format to include a spacer line and timelimit + for ( i = 0 ; i < numHubInfoLines - 1; i++ ) { + if ( atoi( CG_Argv( i * 2 + 3 ) ) > 9 ) { + strcpy( cgs.hubInfoDisplay[i].info, " " ); + } + else { + strcpy( cgs.hubInfoDisplay[i].info, " " ); + } + //PKMOD - Ergodic 10/13/00 - debug inactive +// Com_Printf("CG_ParseHubInfo: 1 numHubInfoLines[%d]>%s<\n", i, cgs.hubInfoDisplay[i].info ); + + strcat( cgs.hubInfoDisplay[i].info, CG_Argv( i * 2 + 3 ) ); + //PKMOD - Ergodic 10/13/00 - debug inactive +// Com_Printf("CG_ParseHubInfo: 2 numHubInfoLines[%d]>%s<\n", i, cgs.hubInfoDisplay[i].info ); + + strcat( cgs.hubInfoDisplay[i].info, " " ); + //PKMOD - Ergodic 10/13/00 - debug inactive +// Com_Printf("CG_ParseHubInfo: 3 numHubInfoLines[%d]>%s<\n", i, cgs.hubInfoDisplay[i].info ); + + strcat( cgs.hubInfoDisplay[i].info, CG_Argv( i * 2 + 4 ) ); + //PKMOD - Ergodic 10/13/00 - debug inactive +// Com_Printf("CG_ParseHubInfo: 4 numHubInfoLines[%d]>%s<\n", i, cgs.hubInfoDisplay[i].info ); + } + + + //PKMOD - Ergodic 11/01/00 - get the time remaining + strcpy( cgs.hubInfoDisplay[i].info, " " ); + strcat( cgs.hubInfoDisplay[i].info, CG_Argv( i * 2 + 3 ) ); + strcat( cgs.hubInfoDisplay[i].info, " " ); + strcat( cgs.hubInfoDisplay[i].info, CG_Argv( i * 2 + 4 ) ); + +} + +/* +================= +CG_ParseTeamInfo + +================= +*/ +static void CG_ParseTeamInfo( void ) { + int i; + int client; + + numSortedTeamPlayers = atoi( CG_Argv( 1 ) ); + + for ( i = 0 ; i < numSortedTeamPlayers ; i++ ) { + client = atoi( CG_Argv( i * 6 + 2 ) ); + + sortedTeamPlayers[i] = client; + + cgs.clientinfo[ client ].location = atoi( CG_Argv( i * 6 + 3 ) ); + cgs.clientinfo[ client ].health = atoi( CG_Argv( i * 6 + 4 ) ); + cgs.clientinfo[ client ].armor = atoi( CG_Argv( i * 6 + 5 ) ); + cgs.clientinfo[ client ].curWeapon = atoi( CG_Argv( i * 6 + 6 ) ); + cgs.clientinfo[ client ].powerups = atoi( CG_Argv( i * 6 + 7 ) ); + } +} + + +/* +================ +CG_ParseServerinfo + +This is called explicitly when the gamestate is first received, +and whenever the server updates any serverinfo flagged cvars +================ +*/ +void CG_ParseServerinfo( void ) { + const char *info; + int hold_hub_flag; + char *mapname; + + info = CG_ConfigString( CS_SERVERINFO ); + cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); + trap_Cvar_Set("g_gametype", va("%i", cgs.gametype)); + cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); + cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); + + //PKMOD - Ergodic 11/07/00 - add logic to enable hub limits + hold_hub_flag = atoi( Info_ValueForKey( info, "hub_flag" ) ); + if ( hold_hub_flag ) { + cgs.fraglimit = atoi( Info_ValueForKey( info, "hub_fraglimit" ) ); + cgs.timelimit = atoi( Info_ValueForKey( info, "hub_timelimit" ) ); + } + else { + cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); + cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); + } + + cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); + cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ) + MAX_PRIVATE_BOTS; + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cgs.mapname, sizeof( cgs.mapname ), "maps/%s.bsp", mapname ); + Q_strncpyz( cgs.redTeam, Info_ValueForKey( info, "g_redTeam" ), sizeof(cgs.redTeam) ); + trap_Cvar_Set("g_redTeam", cgs.redTeam); + Q_strncpyz( cgs.blueTeam, Info_ValueForKey( info, "g_blueTeam" ), sizeof(cgs.blueTeam) ); + trap_Cvar_Set("g_blueTeam", cgs.blueTeam); +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) { + const char *info; + int warmup; + + info = CG_ConfigString( CS_WARMUP ); + + warmup = atoi( info ); + cg.warmupCount = -1; + + if ( warmup == 0 && cg.warmup ) { + + } else if ( warmup > 0 && cg.warmup <= 0 ) { +#ifdef MISSIONPACK + if (cgs.gametype >= GT_CTF && cgs.gametype <= GT_HARVESTER) { + trap_S_StartLocalSound( cgs.media.countPrepareTeamSound, CHAN_ANNOUNCER ); + } else +#endif + { + trap_S_StartLocalSound( cgs.media.countPrepareSound, CHAN_ANNOUNCER ); + } + } + + cg.warmup = warmup; +} + +/* +================ +CG_SetConfigValues + +Called on load to set the initial values from configure strings +================ +*/ +void CG_SetConfigValues( void ) { + const char *s; + + cgs.scores1 = atoi( CG_ConfigString( CS_SCORES1 ) ); + cgs.scores2 = atoi( CG_ConfigString( CS_SCORES2 ) ); + cgs.levelStartTime = atoi( CG_ConfigString( CS_LEVEL_START_TIME ) ); + if( cgs.gametype == GT_CTF ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.redflag = s[0] - '0'; + cgs.blueflag = s[1] - '0'; + } +#ifdef MISSIONPACK + else if( cgs.gametype == GT_1FCTF ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.flagStatus = s[0] - '0'; + } +#endif + cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); +} + +/* +===================== +CG_ShaderStateChanged +===================== +*/ +void CG_ShaderStateChanged(void) { + char originalShader[MAX_QPATH]; + char newShader[MAX_QPATH]; + char timeOffset[16]; + const char *o; + char *n,*t; + + o = CG_ConfigString( CS_SHADERSTATE ); + while (o && *o) { + n = strstr(o, "="); + if (n && *n) { + strncpy(originalShader, o, n-o); + originalShader[n-o] = 0; + n++; + t = strstr(n, ":"); + if (t && *t) { + strncpy(newShader, n, t-n); + newShader[t-n] = 0; + } else { + break; + } + t++; + o = strstr(t, "@"); + if (o) { + strncpy(timeOffset, t, o-t); + timeOffset[o-t] = 0; + o++; + trap_R_RemapShader( originalShader, newShader, timeOffset ); + } + } else { + break; + } + } +} + +/* +================ +CG_ConfigStringModified + +================ +*/ +static void CG_ConfigStringModified( void ) { + const char *str; + int num; + + num = atoi( CG_Argv( 1 ) ); + + // get the gamestate from the client system, which will have the + // new configstring already integrated + trap_GetGameState( &cgs.gameState ); + + // look up the individual string that was modified + str = CG_ConfigString( num ); + + //PKMOD - Ergodic 10/14/00 - debug inactive +// Com_Printf( "CG_ConfigStringModified - num>%d<, str>%s<\n", num, str ); + + // do something with it if necessary + if ( num == CS_MUSIC ) { + CG_StartMusic(); + //PKMOD - Ergodic 10/14/00 - add alternate music to hub + } else if ( num == CS_POSTVOTE_MUSIC ) { + CG_StartPostVoteMusic( str ); + + } else if ( num == CS_SERVERINFO ) { + CG_ParseServerinfo(); + } else if ( num == CS_WARMUP ) { + CG_ParseWarmup(); + } else if ( num == CS_SCORES1 ) { + cgs.scores1 = atoi( str ); + } else if ( num == CS_SCORES2 ) { + cgs.scores2 = atoi( str ); + } else if ( num == CS_LEVEL_START_TIME ) { + cgs.levelStartTime = atoi( str ); + } else if ( num == CS_VOTE_TIME ) { + cgs.voteTime = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_YES ) { + cgs.voteYes = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_NO ) { + cgs.voteNo = atoi( str ); + cgs.voteModified = qtrue; + } else if ( num == CS_VOTE_STRING ) { + Q_strncpyz( cgs.voteString, str, sizeof( cgs.voteString ) ); +#ifdef MISSIONPACK + trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); +#endif //MISSIONPACK + } else if ( num >= CS_TEAMVOTE_TIME && num <= CS_TEAMVOTE_TIME + 1) { + cgs.teamVoteTime[num-CS_TEAMVOTE_TIME] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_TIME] = qtrue; + } else if ( num >= CS_TEAMVOTE_YES && num <= CS_TEAMVOTE_YES + 1) { + cgs.teamVoteYes[num-CS_TEAMVOTE_YES] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_YES] = qtrue; + } else if ( num >= CS_TEAMVOTE_NO && num <= CS_TEAMVOTE_NO + 1) { + cgs.teamVoteNo[num-CS_TEAMVOTE_NO] = atoi( str ); + cgs.teamVoteModified[num-CS_TEAMVOTE_NO] = qtrue; + } else if ( num >= CS_TEAMVOTE_STRING && num <= CS_TEAMVOTE_STRING + 1) { + Q_strncpyz( cgs.teamVoteString[num-CS_TEAMVOTE_STRING], str, sizeof( cgs.teamVoteString ) ); +#ifdef MISSIONPACK + trap_S_StartLocalSound( cgs.media.voteNow, CHAN_ANNOUNCER ); +#endif + } else if ( num == CS_INTERMISSION ) { + cg.intermissionStarted = atoi( str ); + } else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) { + cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( str ); +//PKMOD - Ergodic 01/04/02 - Add bug fix from [TH]DemENtoR that was in the following post: +// http://www.quake3world.com/ubb/Forum4/HTML/005944.html +// } else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_MODELS ) { + } else if ( num >= CS_SOUNDS && num < CS_SOUNDS+MAX_SOUNDS ) { + if ( str[0] != '*' ) { // player specific sounds don't register here + cgs.gameSounds[ num-CS_SOUNDS] = trap_S_RegisterSound( str, qfalse ); + } + } else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) { + CG_NewClientInfo( num - CS_PLAYERS ); + CG_BuildSpectatorString(); + } else if ( num == CS_FLAGSTATUS ) { + if( cgs.gametype == GT_CTF ) { + // format is rb where its red/blue, 0 is at base, 1 is taken, 2 is dropped + cgs.redflag = str[0] - '0'; + cgs.blueflag = str[1] - '0'; + } +#ifdef MISSIONPACK + else if( cgs.gametype == GT_1FCTF ) { + cgs.flagStatus = str[0] - '0'; + } +#endif + } + else if ( num == CS_SHADERSTATE ) { + CG_ShaderStateChanged(); + } + +} + + +/* +======================= +CG_AddToTeamChat + +======================= +*/ +static void CG_AddToTeamChat( const char *str ) { + int len; + char *p, *ls; + int lastcolor; + int chatHeight; + + if (cg_teamChatHeight.integer < TEAMCHAT_HEIGHT) { + chatHeight = cg_teamChatHeight.integer; + } else { + chatHeight = TEAMCHAT_HEIGHT; + } + + if (chatHeight <= 0 || cg_teamChatTime.integer <= 0) { + // team chat disabled, dump into normal chat + cgs.teamChatPos = cgs.teamLastChatPos = 0; + return; + } + + len = 0; + + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + + lastcolor = '7'; + + ls = NULL; + while (*str) { + if (len > TEAMCHAT_WIDTH - 1) { + if (ls) { + str -= (p - ls); + str++; + p -= (p - ls); + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + + cgs.teamChatPos++; + p = cgs.teamChatMsgs[cgs.teamChatPos % chatHeight]; + *p = 0; + *p++ = Q_COLOR_ESCAPE; + *p++ = lastcolor; + len = 0; + ls = NULL; + } + + if ( Q_IsColorString( str ) ) { + *p++ = *str++; + lastcolor = *str; + *p++ = *str++; + continue; + } + if (*str == ' ') { + ls = p; + } + *p++ = *str++; + len++; + } + *p = 0; + + cgs.teamChatMsgTimes[cgs.teamChatPos % chatHeight] = cg.time; + cgs.teamChatPos++; + + if (cgs.teamChatPos - cgs.teamLastChatPos > chatHeight) + cgs.teamLastChatPos = cgs.teamChatPos - chatHeight; +} + +/* +=============== +CG_MapRestart + +The server has issued a map_restart, so the next snapshot +is completely new and should not be interpolated to. + +A tournement restart will clear everything, but doesn't +require a reload of all the media +=============== +*/ +static void CG_MapRestart( void ) { + if ( cg_showmiss.integer ) { + CG_Printf( "CG_MapRestart\n" ); + } + + CG_InitLocalEntities(); + CG_InitMarkPolys(); + CG_ClearParticles (); + + // make sure the "3 frags left" warnings play again + cg.fraglimitWarnings = 0; + + cg.timelimitWarnings = 0; + + cg.intermissionStarted = qfalse; + + cgs.voteTime = 0; + + cg.mapRestart = qtrue; + + CG_StartMusic(); + + trap_S_ClearLoopingSounds(qtrue); + + // we really should clear more parts of cg here and stop sounds + + // play the "fight" sound if this is a restart without warmup + if ( cg.warmup == 0 /* && cgs.gametype == GT_TOURNAMENT */) { + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + CG_CenterPrint( "FIGHT!", 120, GIANTCHAR_WIDTH*2 ); + } +//PKMOD - Ergodic 02/02/04 - Enable this code so that POSTGAME will show proper completion time +//#ifdef MISSIONPACK + if (cg_singlePlayerActive.integer) { + trap_Cvar_Set("ui_matchStartTime", va("%i", cg.time)); + if (cg_recordSPDemo.integer && cg_recordSPDemoName.string && *cg_recordSPDemoName.string) { + trap_SendConsoleCommand(va("set g_synchronousclients 1 ; record %s \n", cg_recordSPDemoName.string)); + } + } +//#endif + trap_Cvar_Set("cg_thirdPerson", "0"); +} + +#define MAX_VOICEFILESIZE 16384 +#define MAX_VOICEFILES 8 +#define MAX_VOICECHATS 64 +#define MAX_VOICESOUNDS 64 +#define MAX_CHATSIZE 64 +#define MAX_HEADMODELS 64 + +typedef struct voiceChat_s +{ + char id[64]; + int numSounds; + sfxHandle_t sounds[MAX_VOICESOUNDS]; + char chats[MAX_VOICESOUNDS][MAX_CHATSIZE]; +} voiceChat_t; + +typedef struct voiceChatList_s +{ + char name[64]; + int gender; + int numVoiceChats; + voiceChat_t voiceChats[MAX_VOICECHATS]; +} voiceChatList_t; + +typedef struct headModelVoiceChat_s +{ + char headmodel[64]; + int voiceChatNum; +} headModelVoiceChat_t; + +voiceChatList_t voiceChatLists[MAX_VOICEFILES]; +headModelVoiceChat_t headModelVoiceChat[MAX_HEADMODELS]; + +/* +================= +CG_ParseVoiceChats +================= +*/ +int CG_ParseVoiceChats( const char *filename, voiceChatList_t *voiceChatList, int maxVoiceChats ) { + int len, i; + fileHandle_t f; + char buf[MAX_VOICEFILESIZE]; + char **p, *ptr; + char *token; + voiceChat_t *voiceChats; + qboolean compress; + sfxHandle_t sound; + + compress = qtrue; + if (cg_buildScript.integer) { + compress = qfalse; + } + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Print( va( S_COLOR_RED "voice chat file not found: %s\n", filename ) ); + return qfalse; + } + if ( len >= MAX_VOICEFILESIZE ) { + trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); + trap_FS_FCloseFile( f ); + return qfalse; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ptr = buf; + p = &ptr; + + Com_sprintf(voiceChatList->name, sizeof(voiceChatList->name), "%s", filename); + voiceChats = voiceChatList->voiceChats; + for ( i = 0; i < maxVoiceChats; i++ ) { + voiceChats[i].id[0] = 0; + } + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + if (!Q_stricmp(token, "female")) { + voiceChatList->gender = GENDER_FEMALE; + } + else if (!Q_stricmp(token, "male")) { + voiceChatList->gender = GENDER_MALE; + } + else if (!Q_stricmp(token, "neuter")) { + voiceChatList->gender = GENDER_NEUTER; + } + else { + trap_Print( va( S_COLOR_RED "expected gender not found in voice chat file: %s\n", filename ) ); + return qfalse; + } + + voiceChatList->numVoiceChats = 0; + while ( 1 ) { + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + Com_sprintf(voiceChats[voiceChatList->numVoiceChats].id, sizeof( voiceChats[voiceChatList->numVoiceChats].id ), "%s", token); + token = COM_ParseExt(p, qtrue); + if (Q_stricmp(token, "{")) { + trap_Print( va( S_COLOR_RED "expected { found %s in voice chat file: %s\n", token, filename ) ); + return qfalse; + } + voiceChats[voiceChatList->numVoiceChats].numSounds = 0; + while(1) { + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + if (!Q_stricmp(token, "}")) + break; + sound = trap_S_RegisterSound( token, compress ); + voiceChats[voiceChatList->numVoiceChats].sounds[voiceChats[voiceChatList->numVoiceChats].numSounds] = sound; + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return qtrue; + } + Com_sprintf(voiceChats[voiceChatList->numVoiceChats].chats[ + voiceChats[voiceChatList->numVoiceChats].numSounds], MAX_CHATSIZE, "%s", token); + if (sound) + voiceChats[voiceChatList->numVoiceChats].numSounds++; + if (voiceChats[voiceChatList->numVoiceChats].numSounds >= MAX_VOICESOUNDS) + break; + } + voiceChatList->numVoiceChats++; + if (voiceChatList->numVoiceChats >= maxVoiceChats) + return qtrue; + } + return qtrue; +} + +/* +================= +CG_LoadVoiceChats +================= +*/ +void CG_LoadVoiceChats( void ) { + int size; + + size = trap_MemoryRemaining(); + CG_ParseVoiceChats( "scripts/female1.voice", &voiceChatLists[0], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/female2.voice", &voiceChatLists[1], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/female3.voice", &voiceChatLists[2], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male1.voice", &voiceChatLists[3], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male2.voice", &voiceChatLists[4], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male3.voice", &voiceChatLists[5], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male4.voice", &voiceChatLists[6], MAX_VOICECHATS ); + CG_ParseVoiceChats( "scripts/male5.voice", &voiceChatLists[7], MAX_VOICECHATS ); + CG_Printf("voice chat memory size = %d\n", size - trap_MemoryRemaining()); +} + +/* +================= +CG_HeadModelVoiceChats +================= +*/ +int CG_HeadModelVoiceChats( char *filename ) { + int len, i; + fileHandle_t f; + char buf[MAX_VOICEFILESIZE]; + char **p, *ptr; + char *token; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + //trap_Print( va( "voice chat file not found: %s\n", filename ) ); + return -1; + } + if ( len >= MAX_VOICEFILESIZE ) { + trap_Print( va( S_COLOR_RED "voice chat file too large: %s is %i, max allowed is %i", filename, len, MAX_VOICEFILESIZE ) ); + trap_FS_FCloseFile( f ); + return -1; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + ptr = buf; + p = &ptr; + + token = COM_ParseExt(p, qtrue); + if (!token || token[0] == 0) { + return -1; + } + + for ( i = 0; i < MAX_VOICEFILES; i++ ) { + if ( !Q_stricmp(token, voiceChatLists[i].name) ) { + return i; + } + } + + //FIXME: maybe try to load the .voice file which name is stored in token? + + return -1; +} + + +/* +================= +CG_GetVoiceChat +================= +*/ +int CG_GetVoiceChat( voiceChatList_t *voiceChatList, const char *id, sfxHandle_t *snd, char **chat) { + int i, rnd; + + for ( i = 0; i < voiceChatList->numVoiceChats; i++ ) { + if ( !Q_stricmp( id, voiceChatList->voiceChats[i].id ) ) { + rnd = random() * voiceChatList->voiceChats[i].numSounds; + *snd = voiceChatList->voiceChats[i].sounds[rnd]; + *chat = voiceChatList->voiceChats[i].chats[rnd]; + return qtrue; + } + } + return qfalse; +} + +/* +================= +CG_VoiceChatListForClient +================= +*/ +voiceChatList_t *CG_VoiceChatListForClient( int clientNum ) { + clientInfo_t *ci; + int voiceChatNum, i, j, k, gender; + char filename[MAX_QPATH], headModelName[MAX_QPATH]; + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + for ( k = 0; k < 2; k++ ) { + if ( k == 0 ) { + if (ci->headModelName[0] == '*') { + Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName+1, ci->headSkinName ); + } + else { + Com_sprintf( headModelName, sizeof(headModelName), "%s/%s", ci->headModelName, ci->headSkinName ); + } + } + else { + if (ci->headModelName[0] == '*') { + Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName+1 ); + } + else { + Com_sprintf( headModelName, sizeof(headModelName), "%s", ci->headModelName ); + } + } + // find the voice file for the head model the client uses + for ( i = 0; i < MAX_HEADMODELS; i++ ) { + if (!Q_stricmp(headModelVoiceChat[i].headmodel, headModelName)) { + break; + } + } + if (i < MAX_HEADMODELS) { + return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; + } + // find a .vc file + for ( i = 0; i < MAX_HEADMODELS; i++ ) { + if (!strlen(headModelVoiceChat[i].headmodel)) { + Com_sprintf(filename, sizeof(filename), "scripts/%s.vc", headModelName); + voiceChatNum = CG_HeadModelVoiceChats(filename); + if (voiceChatNum == -1) + break; + Com_sprintf(headModelVoiceChat[i].headmodel, sizeof ( headModelVoiceChat[i].headmodel ), + "%s", headModelName); + headModelVoiceChat[i].voiceChatNum = voiceChatNum; + return &voiceChatLists[headModelVoiceChat[i].voiceChatNum]; + } + } + } + gender = ci->gender; + for (k = 0; k < 2; k++) { + // just pick the first with the right gender + for ( i = 0; i < MAX_VOICEFILES; i++ ) { + if (strlen(voiceChatLists[i].name)) { + if (voiceChatLists[i].gender == gender) { + // store this head model with voice chat for future reference + for ( j = 0; j < MAX_HEADMODELS; j++ ) { + if (!strlen(headModelVoiceChat[j].headmodel)) { + Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), + "%s", headModelName); + headModelVoiceChat[j].voiceChatNum = i; + break; + } + } + return &voiceChatLists[i]; + } + } + } + // fall back to male gender because we don't have neuter in the mission pack + if (gender == GENDER_MALE) + break; + gender = GENDER_MALE; + } + // store this head model with voice chat for future reference + for ( j = 0; j < MAX_HEADMODELS; j++ ) { + if (!strlen(headModelVoiceChat[j].headmodel)) { + Com_sprintf(headModelVoiceChat[j].headmodel, sizeof ( headModelVoiceChat[j].headmodel ), + "%s", headModelName); + headModelVoiceChat[j].voiceChatNum = 0; + break; + } + } + // just return the first voice chat list + return &voiceChatLists[0]; +} + +#define MAX_VOICECHATBUFFER 32 + +typedef struct bufferedVoiceChat_s +{ + int clientNum; + sfxHandle_t snd; + int voiceOnly; + char cmd[MAX_SAY_TEXT]; + char message[MAX_SAY_TEXT]; +} bufferedVoiceChat_t; + +bufferedVoiceChat_t voiceChatBuffer[MAX_VOICECHATBUFFER]; + +/* +================= +CG_PlayVoiceChat +================= +*/ +void CG_PlayVoiceChat( bufferedVoiceChat_t *vchat ) { +#ifdef MISSIONPACK + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + if ( !cg_noVoiceChats.integer ) { + trap_S_StartLocalSound( vchat->snd, CHAN_VOICE); + if (vchat->clientNum != cg.snap->ps.clientNum) { + int orderTask = CG_ValidOrder(vchat->cmd); + if (orderTask > 0) { + cgs.acceptOrderTime = cg.time + 5000; + Q_strncpyz(cgs.acceptVoice, vchat->cmd, sizeof(cgs.acceptVoice)); + cgs.acceptTask = orderTask; + cgs.acceptLeader = vchat->clientNum; + } + // see if this was an order + CG_ShowResponseHead(); + } + } + if (!vchat->voiceOnly && !cg_noVoiceText.integer) { + CG_AddToTeamChat( vchat->message ); + CG_Printf( "%s\n", vchat->message ); + } + voiceChatBuffer[cg.voiceChatBufferOut].snd = 0; +#endif +} + +/* +===================== +CG_PlayBufferedVoieChats +===================== +*/ +void CG_PlayBufferedVoiceChats( void ) { +#ifdef MISSIONPACK + if ( cg.voiceChatTime < cg.time ) { + if (cg.voiceChatBufferOut != cg.voiceChatBufferIn && voiceChatBuffer[cg.voiceChatBufferOut].snd) { + // + CG_PlayVoiceChat(&voiceChatBuffer[cg.voiceChatBufferOut]); + // + cg.voiceChatBufferOut = (cg.voiceChatBufferOut + 1) % MAX_VOICECHATBUFFER; + cg.voiceChatTime = cg.time + 1000; + } + } +#endif +} + +/* +===================== +CG_AddBufferedVoiceChat +===================== +*/ +void CG_AddBufferedVoiceChat( bufferedVoiceChat_t *vchat ) { +#ifdef MISSIONPACK + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + memcpy(&voiceChatBuffer[cg.voiceChatBufferIn], vchat, sizeof(bufferedVoiceChat_t)); + cg.voiceChatBufferIn = (cg.voiceChatBufferIn + 1) % MAX_VOICECHATBUFFER; + if (cg.voiceChatBufferIn == cg.voiceChatBufferOut) { + CG_PlayVoiceChat( &voiceChatBuffer[cg.voiceChatBufferOut] ); + cg.voiceChatBufferOut++; + } +#endif +} + +/* +================= +CG_VoiceChatLocal +================= +*/ +void CG_VoiceChatLocal( int mode, qboolean voiceOnly, int clientNum, int color, const char *cmd ) { +#ifdef MISSIONPACK + char *chat; + voiceChatList_t *voiceChatList; + clientInfo_t *ci; + sfxHandle_t snd; + bufferedVoiceChat_t vchat; + + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + clientNum = 0; + } + ci = &cgs.clientinfo[ clientNum ]; + + cgs.currentVoiceClient = clientNum; + + voiceChatList = CG_VoiceChatListForClient( clientNum ); + + if ( CG_GetVoiceChat( voiceChatList, cmd, &snd, &chat ) ) { + // + if ( mode == SAY_TEAM || !cg_teamChatsOnly.integer ) { + vchat.clientNum = clientNum; + vchat.snd = snd; + vchat.voiceOnly = voiceOnly; + Q_strncpyz(vchat.cmd, cmd, sizeof(vchat.cmd)); + if ( mode == SAY_TELL ) { + Com_sprintf(vchat.message, sizeof(vchat.message), "[%s]: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + else if ( mode == SAY_TEAM ) { + Com_sprintf(vchat.message, sizeof(vchat.message), "(%s): %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + else { + Com_sprintf(vchat.message, sizeof(vchat.message), "%s: %c%c%s", ci->name, Q_COLOR_ESCAPE, color, chat); + } + CG_AddBufferedVoiceChat(&vchat); + } + } +#endif +} + +/* +================= +CG_VoiceChat +================= +*/ +void CG_VoiceChat( int mode ) { +#ifdef MISSIONPACK + const char *cmd; + int clientNum, color; + qboolean voiceOnly; + + voiceOnly = atoi(CG_Argv(1)); + clientNum = atoi(CG_Argv(2)); + color = atoi(CG_Argv(3)); + cmd = CG_Argv(4); + + if (cg_noTaunt.integer != 0) { + if (!strcmp(cmd, VOICECHAT_KILLINSULT) || !strcmp(cmd, VOICECHAT_TAUNT) || \ + !strcmp(cmd, VOICECHAT_DEATHINSULT) || !strcmp(cmd, VOICECHAT_KILLGAUNTLET) || \ + !strcmp(cmd, VOICECHAT_PRAISE)) { + return; + } + } + + CG_VoiceChatLocal( mode, voiceOnly, clientNum, color, cmd ); +#endif +} + +/* +================= +CG_RemoveChatEscapeChar +================= +*/ +static void CG_RemoveChatEscapeChar( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (text[i] == '\x19') + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +static void CG_ServerCommand( void ) { + const char *cmd; + char text[MAX_SAY_TEXT]; + + cmd = CG_Argv(0); + + if ( !cmd[0] ) { + // server claimed the command + return; + } + + if ( !strcmp( cmd, "cp" ) ) { + CG_CenterPrint( CG_Argv(1), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + return; + } + + if ( !strcmp( cmd, "cs" ) ) { + CG_ConfigStringModified(); + return; + } + + if ( !strcmp( cmd, "print" ) ) { + CG_Printf( "%s", CG_Argv(1) ); +#ifdef MISSIONPACK + cmd = CG_Argv(1); // yes, this is obviously a hack, but so is the way we hear about + // votes passing or failing + if ( !Q_stricmpn( cmd, "vote failed", 11 ) || !Q_stricmpn( cmd, "team vote failed", 16 )) { + trap_S_StartLocalSound( cgs.media.voteFailed, CHAN_ANNOUNCER ); + } else if ( !Q_stricmpn( cmd, "vote passed", 11 ) || !Q_stricmpn( cmd, "team vote passed", 16 ) ) { + trap_S_StartLocalSound( cgs.media.votePassed, CHAN_ANNOUNCER ); + } +#endif + return; + } + + if ( !strcmp( cmd, "chat" ) ) { + if ( !cg_teamChatsOnly.integer ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_Printf( "%s\n", text ); + } + return; + } + + if ( !strcmp( cmd, "tchat" ) ) { + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + CG_RemoveChatEscapeChar( text ); + CG_AddToTeamChat( text ); + CG_Printf( "%s\n", text ); + return; + } + if ( !strcmp( cmd, "vchat" ) ) { + CG_VoiceChat( SAY_ALL ); + return; + } + + if ( !strcmp( cmd, "vtchat" ) ) { + CG_VoiceChat( SAY_TEAM ); + return; + } + + if ( !strcmp( cmd, "vtell" ) ) { + CG_VoiceChat( SAY_TELL ); + return; + } + + if ( !strcmp( cmd, "scores" ) ) { + CG_ParseScores(); + return; + } + + if ( !strcmp( cmd, "tinfo" ) ) { + CG_ParseTeamInfo(); + return; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + CG_MapRestart(); + return; + } + + if ( Q_stricmp (cmd, "remapShader") == 0 ) { + if (trap_Argc() == 4) { + trap_R_RemapShader(CG_Argv(1), CG_Argv(2), CG_Argv(3)); + } + } + + // loaddeferred can be both a servercmd and a consolecmd + if ( !strcmp( cmd, "loaddefered" ) ) { // FIXME: spelled wrong, but not changing for demo + CG_LoadDeferredPlayers(); + return; + } + + // clientLevelShot is sent before taking a special screenshot for + // the menu system during development + if ( !strcmp( cmd, "clientLevelShot" ) ) { + cg.levelShot = qtrue; + return; + } + + //PKMOD - Ergodic 10/13/00 - add hubinfo command for voting + if ( !strcmp( cmd, "hubinfo_pka" ) ) { + CG_ParseHubInfo(); + return; + } + + //PKMOD - Ergodic 12/10/03 - add hubalternates server command + // This code is hit on client/server games + // and this code is hit on SP i.e.: /map hub_30 games + // and this code is hit on selected SinglePlayer games + if ( !strcmp( cmd, "cghubalternates" ) ) { + //PKMOD - Ergodic 03/29/04 - call will now take parameters form the server + char a1[MAX_HUB_DISPLAY_NAME]; + char a2[MAX_HUB_DISPLAY_NAME]; + char a3[MAX_HUB_DISPLAY_NAME]; + char a4[MAX_HUB_DISPLAY_NAME]; + + //PKMOD - Ergodic 03/29/04 - debug cghubalternates call (inactive) + //Com_Printf( "In CGame CG_ServerCommand - sending console command: hubalternates\n" ); + + + Q_strncpyz(a1, CG_Argv(1), sizeof(a1)); + Q_strncpyz(a2, CG_Argv(2), sizeof(a2)); + Q_strncpyz(a3, CG_Argv(3), sizeof(a3)); + Q_strncpyz(a4, CG_Argv(4), sizeof(a4)); + + //PKMOD - Ergodic 03/29/04 - debug cghubalternates call (inactive) + //Com_Printf( "In CGame CG_ServerCommand - sending console command: hubalternates\n" ); + //PKMOD - Ergodic 03/29/04 - debug hubalternates info (inactive) + //Com_Printf("CG_ServerCommand arg1>%s<\n", a1 ); + //Com_Printf("CG_ServerCommand arg2>%s<\n", a2 ); + //Com_Printf("CG_ServerCommand arg3>%s<\n", a3 ); + //Com_Printf("CG_ServerCommand arg4>%s<\n", a4 ); + + //PKMOD - Ergodic 03/29/04 - set the ui cvars + trap_Cvar_Set( "ui_HubAltDisp1", a1 ); + trap_Cvar_Set( "ui_HubAltDisp2", a2 ); + trap_Cvar_Set( "ui_HubAltDisp3", a3 ); + trap_Cvar_Set( "ui_HubAltDisp4", a4 ); + + trap_SendConsoleCommand( "hubalternates\n" ); + return; + } + + CG_Printf( "Unknown client game command: %s\n", cmd ); +} + + +/* +==================== +CG_ExecuteNewServerCommands + +Execute all of the server commands that were received along +with this this snapshot. +==================== +*/ +void CG_ExecuteNewServerCommands( int latestSequence ) { + while ( cgs.serverCommandSequence < latestSequence ) { + if ( trap_GetServerCommand( ++cgs.serverCommandSequence ) ) { + CG_ServerCommand(); + } + } +} diff --git a/quake3/source/code/cgame/cg_snapshot.c b/quake3/source/code/cgame/cg_snapshot.c new file mode 100644 index 0000000..bf90b43 --- /dev/null +++ b/quake3/source/code/cgame/cg_snapshot.c @@ -0,0 +1,402 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_snapshot.c -- things that happen on snapshot transition, +// not necessarily every single rendered frame + +#include "cg_local.h" + + + +/* +================== +CG_ResetEntity +================== +*/ +static void CG_ResetEntity( centity_t *cent ) { + // if the previous snapshot this entity was updated in is at least + // an event window back in time then we can reset the previous event + if ( cent->snapShotTime < cg.time - EVENT_VALID_MSEC ) { + cent->previousEvent = 0; + } + + cent->trailTime = cg.snap->serverTime; + + VectorCopy (cent->currentState.origin, cent->lerpOrigin); + VectorCopy (cent->currentState.angles, cent->lerpAngles); + if ( cent->currentState.eType == ET_PLAYER ) { + CG_ResetPlayerEntity( cent ); + } +} + +//PKMOD - Ergodic 07/04/01 - debug position +extern char *CG_vtos( const vec3_t v ); + +/* +=============== +CG_TransitionEntity + +cent->nextState is moved to cent->currentState and events are fired +=============== +*/ +static void CG_TransitionEntity( centity_t *cent ) { + cent->currentState = cent->nextState; + cent->currentValid = qtrue; + + //PKMOD Ergodic debug 07/19/00 (inactive) +// if ( cent->currentState.eType == ET_LIGHTNING_FX ) { +// Com_Printf("CG_TransitionEntity - ET_LIGHTNING_FX found\n"); +// } + + //PKMOD Ergodic debug 07/04/01 (inactive) +// if ( cent->currentState.eType == ET_ZOMBIE ) { +// Com_Printf("CG_TransitionEntity - ET_ZOMBIE found at >%s<\n", CG_vtos(cent->lerpOrigin) ) ; +// } + + // reset if the entity wasn't in the last frame or was teleported + if ( !cent->interpolate ) { + CG_ResetEntity( cent ); + } + + // clear the next state. if will be set by the next CG_SetNextSnap + cent->interpolate = qfalse; + + // check for events + CG_CheckEvents( cent ); +} + + +/* +================== +CG_SetInitialSnapshot + +This will only happen on the very first snapshot, or +on tourney restarts. All other times will use +CG_TransitionSnapshot instead. + +FIXME: Also called by map_restart? +================== +*/ +void CG_SetInitialSnapshot( snapshot_t *snap ) { + int i; + centity_t *cent; + entityState_t *state; + + cg.snap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].currentState, qfalse ); + + // sort out solid entities + CG_BuildSolidList(); + + CG_ExecuteNewServerCommands( snap->serverCommandSequence ); + + // set our local weapon selection pointer to + // what the server has indicated the current weapon is + CG_Respawn(); + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + state = &cg.snap->entities[ i ]; + cent = &cg_entities[ state->number ]; + + memcpy(¢->currentState, state, sizeof(entityState_t)); + //cent->currentState = *state; + cent->interpolate = qfalse; + cent->currentValid = qtrue; + + //PKMOD Ergodic debug 07/19/00 (inactive) +// if ( cent->currentState.eType == ET_LIGHTNING_FX ) { +// Com_Printf("CG_SetInitialSnapshot - ET_LIGHTNING_FX found\n"); +// } + + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +static void CG_TransitionSnapshot( void ) { + centity_t *cent; + snapshot_t *oldFrame; + int i; + + if ( !cg.snap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.snap" ); + } + if ( !cg.nextSnap ) { + CG_Error( "CG_TransitionSnapshot: NULL cg.nextSnap" ); + } + + // execute any server string commands before transitioning entities + CG_ExecuteNewServerCommands( cg.nextSnap->serverCommandSequence ); + + // if we had a map_restart, set everthing with initial + if ( !cg.snap ) { + } + + // clear the currentValid flag for all entities in the existing snapshot + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + cent->currentValid = qfalse; + } + + // move nextSnap to snap and do the transitions + oldFrame = cg.snap; + cg.snap = cg.nextSnap; + + BG_PlayerStateToEntityState( &cg.snap->ps, &cg_entities[ cg.snap->ps.clientNum ].currentState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qfalse; + + for ( i = 0 ; i < cg.snap->numEntities ; i++ ) { + cent = &cg_entities[ cg.snap->entities[ i ].number ]; + CG_TransitionEntity( cent ); + + // remember time of snapshot this entity was last updated in + cent->snapShotTime = cg.snap->serverTime; + } + + cg.nextSnap = NULL; + + // check for playerstate transition events + if ( oldFrame ) { + playerState_t *ops, *ps; + + ops = &oldFrame->ps; + ps = &cg.snap->ps; + // teleporting checks are irrespective of prediction + if ( ( ps->eFlags ^ ops->eFlags ) & EF_TELEPORT_BIT ) { + cg.thisFrameTeleport = qtrue; // will be cleared by prediction code + } + + // if we are not doing client side movement prediction for any + // reason, then the client events and view changes will be issued now + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) + || cg_nopredict.integer || cg_synchronousClients.integer ) { + CG_TransitionPlayerState( ps, ops ); + } + } + +} + + +/* +=================== +CG_SetNextSnap + +A new snapshot has just been read in from the client system. +=================== +*/ +static void CG_SetNextSnap( snapshot_t *snap ) { + int num; + entityState_t *es; + centity_t *cent; + + cg.nextSnap = snap; + + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); + cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; + + // check for extrapolation errors + for ( num = 0 ; num < snap->numEntities ; num++ ) { + es = &snap->entities[num]; + cent = &cg_entities[ es->number ]; + + memcpy(¢->nextState, es, sizeof(entityState_t)); + //cent->nextState = *es; + + // if this frame is a teleport, or the entity wasn't in the + // previous frame, don't interpolate + if ( !cent->currentValid || ( ( cent->currentState.eFlags ^ es->eFlags ) & EF_TELEPORT_BIT ) ) { + cent->interpolate = qfalse; + } else { + cent->interpolate = qtrue; + } + } + + // if the next frame is a teleport for the playerstate, we + // can't interpolate during demos + if ( cg.snap && ( ( snap->ps.eFlags ^ cg.snap->ps.eFlags ) & EF_TELEPORT_BIT ) ) { + cg.nextFrameTeleport = qtrue; + } else { + cg.nextFrameTeleport = qfalse; + } + + // if changing follow mode, don't interpolate + if ( cg.nextSnap->ps.clientNum != cg.snap->ps.clientNum ) { + cg.nextFrameTeleport = qtrue; + } + + // if changing server restarts, don't interpolate + if ( ( cg.nextSnap->snapFlags ^ cg.snap->snapFlags ) & SNAPFLAG_SERVERCOUNT ) { + cg.nextFrameTeleport = qtrue; + } + + // sort out solid entities + CG_BuildSolidList(); +} + + +/* +======================== +CG_ReadNextSnapshot + +This is the only place new snapshots are requested +This may increment cgs.processedSnapshotNum multiple +times if the client system fails to return a +valid snapshot. +======================== +*/ +static snapshot_t *CG_ReadNextSnapshot( void ) { + qboolean r; + snapshot_t *dest; + + if ( cg.latestSnapshotNum > cgs.processedSnapshotNum + 1000 ) { + CG_Printf( "WARNING: CG_ReadNextSnapshot: way out of range, %i > %i", + cg.latestSnapshotNum, cgs.processedSnapshotNum ); + } + + while ( cgs.processedSnapshotNum < cg.latestSnapshotNum ) { + // decide which of the two slots to load it into + if ( cg.snap == &cg.activeSnapshots[0] ) { + dest = &cg.activeSnapshots[1]; + } else { + dest = &cg.activeSnapshots[0]; + } + + // try to read the snapshot from the client system + cgs.processedSnapshotNum++; + r = trap_GetSnapshot( cgs.processedSnapshotNum, dest ); + + // FIXME: why would trap_GetSnapshot return a snapshot with the same server time + if ( cg.snap && r && dest->serverTime == cg.snap->serverTime ) { + //continue; + } + + // if it succeeded, return + if ( r ) { + CG_AddLagometerSnapshotInfo( dest ); + return dest; + } + + // a GetSnapshot will return failure if the snapshot + // never arrived, or is so old that its entities + // have been shoved off the end of the circular + // buffer in the client system. + + // record as a dropped packet + CG_AddLagometerSnapshotInfo( NULL ); + + // If there are additional snapshots, continue trying to + // read them. + } + + // nothing left to read + return NULL; +} + + +/* +============ +CG_ProcessSnapshots + +We are trying to set up a renderable view, so determine +what the simulated time is, and try to get snapshots +both before and after that time if available. + +If we don't have a valid cg.snap after exiting this function, +then a 3D game view cannot be rendered. This should only happen +right after the initial connection. After cg.snap has been valid +once, it will never turn invalid. + +Even if cg.snap is valid, cg.nextSnap may not be, if the snapshot +hasn't arrived yet (it becomes an extrapolating situation instead +of an interpolating one) + +============ +*/ +void CG_ProcessSnapshots( void ) { + snapshot_t *snap; + int n; + + // see what the latest snapshot the client system has is + trap_GetCurrentSnapshotNumber( &n, &cg.latestSnapshotTime ); + if ( n != cg.latestSnapshotNum ) { + if ( n < cg.latestSnapshotNum ) { + // this should never happen + CG_Error( "CG_ProcessSnapshots: n < cg.latestSnapshotNum" ); + } + cg.latestSnapshotNum = n; + } + + // If we have yet to receive a snapshot, check for it. + // Once we have gotten the first snapshot, cg.snap will + // always have valid data for the rest of the game + while ( !cg.snap ) { + snap = CG_ReadNextSnapshot(); + if ( !snap ) { + // we can't continue until we get a snapshot + return; + } + + // set our weapon selection to what + // the playerstate is currently using + if ( !( snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_SetInitialSnapshot( snap ); + } + } + + // loop until we either have a valid nextSnap with a serverTime + // greater than cg.time to interpolate towards, or we run + // out of available snapshots + do { + // if we don't have a nextframe, try and read a new one in + if ( !cg.nextSnap ) { + snap = CG_ReadNextSnapshot(); + + // if we still don't have a nextframe, we will just have to + // extrapolate + if ( !snap ) { + break; + } + + CG_SetNextSnap( snap ); + + + // if time went backwards, we have a level restart + if ( cg.nextSnap->serverTime < cg.snap->serverTime ) { + CG_Error( "CG_ProcessSnapshots: Server time went backwards" ); + } + } + + // if our time is < nextFrame's, we have a nice interpolating state + if ( cg.time >= cg.snap->serverTime && cg.time < cg.nextSnap->serverTime ) { + break; + } + + // we have passed the transition from nextFrame to frame + CG_TransitionSnapshot(); + } while ( 1 ); + + // assert our valid conditions upon exiting + if ( cg.snap == NULL ) { + CG_Error( "CG_ProcessSnapshots: cg.snap == NULL" ); + } + if ( cg.time < cg.snap->serverTime ) { + // this can happen right after a vid_restart + cg.time = cg.snap->serverTime; + } + if ( cg.nextSnap != NULL && cg.nextSnap->serverTime <= cg.time ) { + CG_Error( "CG_ProcessSnapshots: cg.nextSnap->serverTime <= cg.time" ); + } + +} + diff --git a/quake3/source/code/cgame/cg_syscalls.asm b/quake3/source/code/cgame/cg_syscalls.asm new file mode 100644 index 0000000..8363fe3 --- /dev/null +++ b/quake3/source/code/cgame/cg_syscalls.asm @@ -0,0 +1,106 @@ +code + +equ trap_Print -1 +equ trap_Error -2 +equ trap_Milliseconds -3 +equ trap_Cvar_Register -4 +equ trap_Cvar_Update -5 +equ trap_Cvar_Set -6 +equ trap_Cvar_VariableStringBuffer -7 +equ trap_Argc -8 +equ trap_Argv -9 +equ trap_Args -10 +equ trap_FS_FOpenFile -11 +equ trap_FS_Read -12 +equ trap_FS_Write -13 +equ trap_FS_FCloseFile -14 +equ trap_SendConsoleCommand -15 +equ trap_AddCommand -16 +equ trap_SendClientCommand -17 +equ trap_UpdateScreen -18 +equ trap_CM_LoadMap -19 +equ trap_CM_NumInlineModels -20 +equ trap_CM_InlineModel -21 +equ trap_CM_LoadModel -22 +equ trap_CM_TempBoxModel -23 +equ trap_CM_PointContents -24 +equ trap_CM_TransformedPointContents -25 +equ trap_CM_BoxTrace -26 +equ trap_CM_TransformedBoxTrace -27 +equ trap_CM_MarkFragments -28 +equ trap_S_StartSound -29 +equ trap_S_StartLocalSound -30 +equ trap_S_ClearLoopingSounds -31 +equ trap_S_AddLoopingSound -32 +equ trap_S_UpdateEntityPosition -33 +equ trap_S_Respatialize -34 +equ trap_S_RegisterSound -35 +equ trap_S_StartBackgroundTrack -36 +equ trap_R_LoadWorldMap -37 +equ trap_R_RegisterModel -38 +equ trap_R_RegisterSkin -39 +equ trap_R_RegisterShader -40 +equ trap_R_ClearScene -41 +equ trap_R_AddRefEntityToScene -42 +equ trap_R_AddPolyToScene -43 +equ trap_R_AddLightToScene -44 +equ trap_R_RenderScene -45 +equ trap_R_SetColor -46 +equ trap_R_DrawStretchPic -47 +equ trap_R_ModelBounds -48 +equ trap_R_LerpTag -49 +equ trap_GetGlconfig -50 +equ trap_GetGameState -51 +equ trap_GetCurrentSnapshotNumber -52 +equ trap_GetSnapshot -53 +equ trap_GetServerCommand -54 +equ trap_GetCurrentCmdNumber -55 +equ trap_GetUserCmd -56 +equ trap_SetUserCmdValue -57 +equ trap_R_RegisterShaderNoMip -58 +equ trap_MemoryRemaining -59 +equ trap_R_RegisterFont -60 +equ trap_Key_IsDown -61 +equ trap_Key_GetCatcher -62 +equ trap_Key_SetCatcher -63 +equ trap_Key_GetKey -64 +equ trap_PC_AddGlobalDefine -65 +equ trap_PC_LoadSource -66 +equ trap_PC_FreeSource -67 +equ trap_PC_ReadToken -68 +equ trap_PC_SourceFileAndLine -69 +equ trap_S_StopBackgroundTrack -70 +equ trap_RealTime -71 +equ trap_SnapVector -72 +equ trap_RemoveCommand -73 +equ trap_R_LightForPoint -74 +equ trap_CIN_PlayCinematic -75 +equ trap_CIN_StopCinematic -76 +equ trap_CIN_RunCinematic -77 +equ trap_CIN_DrawCinematic -78 +equ trap_CIN_SetExtents -79 +equ trap_R_RemapShader -80 +equ trap_S_AddRealLoopingSound -81 +equ trap_S_StopLoopingSound -82 +equ trap_CM_TempCapsuleModel -83 +equ trap_CM_CapsuleTrace -84 +equ trap_CM_TransformedCapsuleTrace -85 +equ trap_R_AddAdditiveLightToScene -86 +equ trap_GetEntityToken -87 +equ trap_R_AddPolysToScene -88 +equ trap_R_inPVS -89 +equ trap_FS_Seek -90 + +equ memset -101 +equ memcpy -102 +equ strncpy -103 +equ sin -104 +equ cos -105 +equ atan2 -106 +equ sqrt -107 +equ floor -108 +equ ceil -109 +equ testPrintInt -110 +equ testPrintFloat -111 +equ acos -112 + diff --git a/quake3/source/code/cgame/cg_syscalls.c b/quake3/source/code/cgame/cg_syscalls.c new file mode 100644 index 0000000..102fcc4 --- /dev/null +++ b/quake3/source/code/cgame/cg_syscalls.c @@ -0,0 +1,426 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_syscalls.c -- this file is only included when building a dll +// cg_syscalls.asm is included instead when building a qvm +#ifdef Q3_VM +#error "Do not use in VM build" +#endif + + +#include "cg_local.h" + +static int (QDECL *syscall)( int arg, ... ) = (int (QDECL *)( int, ...))-1; + + +void dllEntry( int (QDECL *syscallptr)( int arg,... ) ) { + syscall = syscallptr; +} + + +int PASSFLOAT( float x ) { + float floatTemp; + floatTemp = x; + return *(int *)&floatTemp; +} + +void trap_Print( const char *fmt ) { + syscall( CG_PRINT, fmt ); +} + +void trap_Error( const char *fmt ) { + syscall( CG_ERROR, fmt ); +} + +int trap_Milliseconds( void ) { + return syscall( CG_MILLISECONDS ); +} + +void trap_Cvar_Register( vmCvar_t *vmCvar, const char *varName, const char *defaultValue, int flags ) { + syscall( CG_CVAR_REGISTER, vmCvar, varName, defaultValue, flags ); +} + +void trap_Cvar_Update( vmCvar_t *vmCvar ) { + syscall( CG_CVAR_UPDATE, vmCvar ); +} + +void trap_Cvar_Set( const char *var_name, const char *value ) { + syscall( CG_CVAR_SET, var_name, value ); +} + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { + syscall( CG_CVAR_VARIABLESTRINGBUFFER, var_name, buffer, bufsize ); +} + +int trap_Argc( void ) { + return syscall( CG_ARGC ); +} + +void trap_Argv( int n, char *buffer, int bufferLength ) { + syscall( CG_ARGV, n, buffer, bufferLength ); +} + +void trap_Args( char *buffer, int bufferLength ) { + syscall( CG_ARGS, buffer, bufferLength ); +} + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ) { + return syscall( CG_FS_FOPENFILE, qpath, f, mode ); +} + +void trap_FS_Read( void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_READ, buffer, len, f ); +} + +void trap_FS_Write( const void *buffer, int len, fileHandle_t f ) { + syscall( CG_FS_WRITE, buffer, len, f ); +} + +void trap_FS_FCloseFile( fileHandle_t f ) { + syscall( CG_FS_FCLOSEFILE, f ); +} + +int trap_FS_Seek( fileHandle_t f, long offset, int origin ) { + return syscall( CG_FS_SEEK, f, offset, origin ); +} + +void trap_SendConsoleCommand( const char *text ) { + syscall( CG_SENDCONSOLECOMMAND, text ); +} + +void trap_AddCommand( const char *cmdName ) { + syscall( CG_ADDCOMMAND, cmdName ); +} + +void trap_RemoveCommand( const char *cmdName ) { + syscall( CG_REMOVECOMMAND, cmdName ); +} + +void trap_SendClientCommand( const char *s ) { + syscall( CG_SENDCLIENTCOMMAND, s ); +} + +void trap_UpdateScreen( void ) { + syscall( CG_UPDATESCREEN ); +} + +void trap_CM_LoadMap( const char *mapname ) { + syscall( CG_CM_LOADMAP, mapname ); +} + +int trap_CM_NumInlineModels( void ) { + return syscall( CG_CM_NUMINLINEMODELS ); +} + +clipHandle_t trap_CM_InlineModel( int index ) { + return syscall( CG_CM_INLINEMODEL, index ); +} + +clipHandle_t trap_CM_TempBoxModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPBOXMODEL, mins, maxs ); +} + +clipHandle_t trap_CM_TempCapsuleModel( const vec3_t mins, const vec3_t maxs ) { + return syscall( CG_CM_TEMPCAPSULEMODEL, mins, maxs ); +} + +int trap_CM_PointContents( const vec3_t p, clipHandle_t model ) { + return syscall( CG_CM_POINTCONTENTS, p, model ); +} + +int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { + return syscall( CG_CM_TRANSFORMEDPOINTCONTENTS, p, model, origin, angles ); +} + +void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_BOXTRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_CapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask ) { + syscall( CG_CM_CAPSULETRACE, results, start, end, mins, maxs, model, brushmask ); +} + +void trap_CM_TransformedBoxTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDBOXTRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +void trap_CM_TransformedCapsuleTrace( trace_t *results, const vec3_t start, const vec3_t end, + const vec3_t mins, const vec3_t maxs, + clipHandle_t model, int brushmask, + const vec3_t origin, const vec3_t angles ) { + syscall( CG_CM_TRANSFORMEDCAPSULETRACE, results, start, end, mins, maxs, model, brushmask, origin, angles ); +} + +int trap_CM_MarkFragments( int numPoints, const vec3_t *points, + const vec3_t projection, + int maxPoints, vec3_t pointBuffer, + int maxFragments, markFragment_t *fragmentBuffer ) { + return syscall( CG_CM_MARKFRAGMENTS, numPoints, points, projection, maxPoints, pointBuffer, maxFragments, fragmentBuffer ); +} + +void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) { + syscall( CG_S_STARTSOUND, origin, entityNum, entchannel, sfx ); +} + +void trap_S_StartLocalSound( sfxHandle_t sfx, int channelNum ) { + syscall( CG_S_STARTLOCALSOUND, sfx, channelNum ); +} + +void trap_S_ClearLoopingSounds( qboolean killall ) { + syscall( CG_S_CLEARLOOPINGSOUNDS, killall ); +} + +void trap_S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ) { + syscall( CG_S_ADDREALLOOPINGSOUND, entityNum, origin, velocity, sfx ); +} + +void trap_S_StopLoopingSound( int entityNum ) { + syscall( CG_S_STOPLOOPINGSOUND, entityNum ); +} + +void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); +} + +void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { + syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ) { + return syscall( CG_S_REGISTERSOUND, sample, compressed ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop ) { + syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop ); +} + +void trap_R_LoadWorldMap( const char *mapname ) { + syscall( CG_R_LOADWORLDMAP, mapname ); +} + +qhandle_t trap_R_RegisterModel( const char *name ) { + return syscall( CG_R_REGISTERMODEL, name ); +} + +qhandle_t trap_R_RegisterSkin( const char *name ) { + return syscall( CG_R_REGISTERSKIN, name ); +} + +qhandle_t trap_R_RegisterShader( const char *name ) { + return syscall( CG_R_REGISTERSHADER, name ); +} + +qhandle_t trap_R_RegisterShaderNoMip( const char *name ) { + return syscall( CG_R_REGISTERSHADERNOMIP, name ); +} + +void trap_R_RegisterFont(const char *fontName, int pointSize, fontInfo_t *font) { + syscall(CG_R_REGISTERFONT, fontName, pointSize, font ); +} + +void trap_R_ClearScene( void ) { + syscall( CG_R_CLEARSCENE ); +} + +void trap_R_AddRefEntityToScene( const refEntity_t *re ) { + syscall( CG_R_ADDREFENTITYTOSCENE, re ); +} + +void trap_R_AddPolyToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts ) { + syscall( CG_R_ADDPOLYTOSCENE, hShader, numVerts, verts ); +} + +void trap_R_AddPolysToScene( qhandle_t hShader , int numVerts, const polyVert_t *verts, int num ) { + syscall( CG_R_ADDPOLYSTOSCENE, hShader, numVerts, verts, num ); +} + +int trap_R_LightForPoint( vec3_t point, vec3_t ambientLight, vec3_t directedLight, vec3_t lightDir ) { + return syscall( CG_R_LIGHTFORPOINT, point, ambientLight, directedLight, lightDir ); +} + +void trap_R_AddLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDLIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_AddAdditiveLightToScene( const vec3_t org, float intensity, float r, float g, float b ) { + syscall( CG_R_ADDADDITIVELIGHTTOSCENE, org, PASSFLOAT(intensity), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b) ); +} + +void trap_R_RenderScene( const refdef_t *fd ) { + syscall( CG_R_RENDERSCENE, fd ); +} + +void trap_R_SetColor( const float *rgba ) { + syscall( CG_R_SETCOLOR, rgba ); +} + +void trap_R_DrawStretchPic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2, qhandle_t hShader ) { + syscall( CG_R_DRAWSTRETCHPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), hShader ); +} + +void trap_R_ModelBounds( clipHandle_t model, vec3_t mins, vec3_t maxs ) { + syscall( CG_R_MODELBOUNDS, model, mins, maxs ); +} + +int trap_R_LerpTag( orientation_t *tag, clipHandle_t mod, int startFrame, int endFrame, + float frac, const char *tagName ) { + return syscall( CG_R_LERPTAG, tag, mod, startFrame, endFrame, PASSFLOAT(frac), tagName ); +} + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) { + syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_GetGlconfig( glconfig_t *glconfig ) { + syscall( CG_GETGLCONFIG, glconfig ); +} + +void trap_GetGameState( gameState_t *gamestate ) { + syscall( CG_GETGAMESTATE, gamestate ); +} + +void trap_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + syscall( CG_GETCURRENTSNAPSHOTNUMBER, snapshotNumber, serverTime ); +} + +qboolean trap_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + return syscall( CG_GETSNAPSHOT, snapshotNumber, snapshot ); +} + +qboolean trap_GetServerCommand( int serverCommandNumber ) { + return syscall( CG_GETSERVERCOMMAND, serverCommandNumber ); +} + +int trap_GetCurrentCmdNumber( void ) { + return syscall( CG_GETCURRENTCMDNUMBER ); +} + +qboolean trap_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + return syscall( CG_GETUSERCMD, cmdNumber, ucmd ); +} + +void trap_SetUserCmdValue( int stateValue, float sensitivityScale ) { + syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale) ); +} + +void testPrintInt( char *string, int i ) { + syscall( CG_TESTPRINTINT, string, i ); +} + +void testPrintFloat( char *string, float f ) { + syscall( CG_TESTPRINTFLOAT, string, PASSFLOAT(f) ); +} + +int trap_MemoryRemaining( void ) { + return syscall( CG_MEMORY_REMAINING ); +} + +qboolean trap_Key_IsDown( int keynum ) { + return syscall( CG_KEY_ISDOWN, keynum ); +} + +int trap_Key_GetCatcher( void ) { + return syscall( CG_KEY_GETCATCHER ); +} + +void trap_Key_SetCatcher( int catcher ) { + syscall( CG_KEY_SETCATCHER, catcher ); +} + +int trap_Key_GetKey( const char *binding ) { + return syscall( CG_KEY_GETKEY, binding ); +} + +int trap_PC_AddGlobalDefine( char *define ) { + return syscall( CG_PC_ADD_GLOBAL_DEFINE, define ); +} + +int trap_PC_LoadSource( const char *filename ) { + return syscall( CG_PC_LOAD_SOURCE, filename ); +} + +int trap_PC_FreeSource( int handle ) { + return syscall( CG_PC_FREE_SOURCE, handle ); +} + +int trap_PC_ReadToken( int handle, pc_token_t *pc_token ) { + return syscall( CG_PC_READ_TOKEN, handle, pc_token ); +} + +int trap_PC_SourceFileAndLine( int handle, char *filename, int *line ) { + return syscall( CG_PC_SOURCE_FILE_AND_LINE, handle, filename, line ); +} + +void trap_S_StopBackgroundTrack( void ) { + syscall( CG_S_STOPBACKGROUNDTRACK ); +} + +int trap_RealTime(qtime_t *qtime) { + return syscall( CG_REAL_TIME, qtime ); +} + +void trap_SnapVector( float *v ) { + syscall( CG_SNAPVECTOR, v ); +} + +// this returns a handle. arg0 is the name in the format "idlogo.roq", set arg1 to NULL, alteredstates to qfalse (do not alter gamestate) +int trap_CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits) { + return syscall(CG_CIN_PLAYCINEMATIC, arg0, xpos, ypos, width, height, bits); +} + +// stops playing the cinematic and ends it. should always return FMV_EOF +// cinematics must be stopped in reverse order of when they are started +e_status trap_CIN_StopCinematic(int handle) { + return syscall(CG_CIN_STOPCINEMATIC, handle); +} + + +// will run a frame of the cinematic but will not draw it. Will return FMV_EOF if the end of the cinematic has been reached. +e_status trap_CIN_RunCinematic (int handle) { + return syscall(CG_CIN_RUNCINEMATIC, handle); +} + + +// draws the current frame +void trap_CIN_DrawCinematic (int handle) { + syscall(CG_CIN_DRAWCINEMATIC, handle); +} + + +// allows you to resize the animation dynamically +void trap_CIN_SetExtents (int handle, int x, int y, int w, int h) { + syscall(CG_CIN_SETEXTENTS, handle, x, y, w, h); +} + +/* +qboolean trap_loadCamera( const char *name ) { + return syscall( CG_LOADCAMERA, name ); +} + +void trap_startCamera(int time) { + syscall(CG_STARTCAMERA, time); +} + +qboolean trap_getCameraInfo( int time, vec3_t *origin, vec3_t *angles) { + return syscall( CG_GETCAMERAINFO, time, origin, angles ); +} +*/ + +qboolean trap_GetEntityToken( char *buffer, int bufferSize ) { + return syscall( CG_GET_ENTITY_TOKEN, buffer, bufferSize ); +} + +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2 ) { + return syscall( CG_R_INPVS, p1, p2 ); +} diff --git a/quake3/source/code/cgame/cg_view.c b/quake3/source/code/cgame/cg_view.c new file mode 100644 index 0000000..6f9e425 --- /dev/null +++ b/quake3/source/code/cgame/cg_view.c @@ -0,0 +1,863 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_view.c -- setup all the parameters (position, angle, etc) +// for a 3D rendering +#include "cg_local.h" + + +/* +============================================================================= + + MODEL TESTING + +The viewthing and gun positioning tools from Q2 have been integrated and +enhanced into a single model testing facility. + +Model viewing can begin with either "testmodel " or "testgun ". + +The names must be the full pathname after the basedir, like +"models/weapons/v_launch/tris.md3" or "players/male/tris.md3" + +Testmodel will create a fake entity 100 units in front of the current view +position, directly facing the viewer. It will remain immobile, so you can +move around it to view it from different angles. + +Testgun will cause the model to follow the player around and supress the real +view weapon model. The default frame 0 of most guns is completely off screen, +so you will probably have to cycle a couple frames to see it. + +"nextframe", "prevframe", "nextskin", and "prevskin" commands will change the +frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in +q3default.cfg. + +If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let +you adjust the positioning. + +Note that none of the model testing features update while the game is paused, so +it may be convenient to test with deathmatch set to 1 so that bringing down the +console doesn't pause the game. + +============================================================================= +*/ + +/* +================= +CG_TestModel_f + +Creates an entity in front of the current position, which +can then be moved around +================= +*/ +void CG_TestModel_f (void) { + vec3_t angles; + + memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) ); + if ( trap_Argc() < 2 ) { + return; + } + + Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH ); + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + + if ( trap_Argc() == 3 ) { + cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) ); + cg.testModelEntity.frame = 1; + cg.testModelEntity.oldframe = 0; + } + if (! cg.testModelEntity.hModel ) { + CG_Printf( "Can't register model\n" ); + return; + } + + VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin ); + + angles[PITCH] = 0; + angles[YAW] = 180 + cg.refdefViewAngles[1]; + angles[ROLL] = 0; + + AnglesToAxis( angles, cg.testModelEntity.axis ); + cg.testGun = qfalse; +} + +/* +================= +CG_TestGun_f + +Replaces the current view weapon with the given model +================= +*/ +void CG_TestGun_f (void) { + CG_TestModel_f(); + cg.testGun = qtrue; + cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON; +} + + +void CG_TestModelNextFrame_f (void) { + cg.testModelEntity.frame++; + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelPrevFrame_f (void) { + cg.testModelEntity.frame--; + if ( cg.testModelEntity.frame < 0 ) { + cg.testModelEntity.frame = 0; + } + CG_Printf( "frame %i\n", cg.testModelEntity.frame ); +} + +void CG_TestModelNextSkin_f (void) { + cg.testModelEntity.skinNum++; + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +void CG_TestModelPrevSkin_f (void) { + cg.testModelEntity.skinNum--; + if ( cg.testModelEntity.skinNum < 0 ) { + cg.testModelEntity.skinNum = 0; + } + CG_Printf( "skin %i\n", cg.testModelEntity.skinNum ); +} + +static void CG_AddTestModel (void) { + int i; + + // re-register the model, because the level may have changed + cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName ); + if (! cg.testModelEntity.hModel ) { + CG_Printf ("Can't register model\n"); + return; + } + + // if testing a gun, set the origin reletive to the view origin + if ( cg.testGun ) { + VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin ); + VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] ); + VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] ); + VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] ); + + // allow the position to be adjusted + for (i=0 ; i<3 ; i++) { + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value; + cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value; + } + } + + trap_R_AddRefEntityToScene( &cg.testModelEntity ); +} + + + +//============================================================================ + + +/* +================= +CG_CalcVrect + +Sets the coordinates of the rendered window +================= +*/ +static void CG_CalcVrect (void) { + int size; + + // the intermission should allways be full screen + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + size = 100; + } else { + // bound normal viewsize + if (cg_viewsize.integer < 30) { + trap_Cvar_Set ("cg_viewsize","30"); + size = 30; + } else if (cg_viewsize.integer > 100) { + trap_Cvar_Set ("cg_viewsize","100"); + size = 100; + } else { + size = cg_viewsize.integer; + } + + } + cg.refdef.width = cgs.glconfig.vidWidth*size/100; + cg.refdef.width &= ~1; + + cg.refdef.height = cgs.glconfig.vidHeight*size/100; + cg.refdef.height &= ~1; + + cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width)/2; + cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height)/2; +} + +//============================================================================== + + +/* +=============== +CG_OffsetThirdPersonView + +=============== +*/ +#define FOCUS_DISTANCE 512 +static void CG_OffsetThirdPersonView( void ) { + vec3_t forward, right, up; + vec3_t view; + vec3_t focusAngles; + trace_t trace; + static vec3_t mins = { -4, -4, -4 }; + static vec3_t maxs = { 4, 4, 4 }; + vec3_t focusPoint; + float focusDist; + float forwardScale, sideScale; + + cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight; + + VectorCopy( cg.refdefViewAngles, focusAngles ); + + // if dead, look at killer + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + + if ( focusAngles[PITCH] > 45 ) { + focusAngles[PITCH] = 45; // don't go too far overhead + } + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors( focusAngles, forward, NULL, NULL ); + AngleVectorsForward( focusAngles, forward ); + + VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + + VectorCopy( cg.refdef.vieworg, view ); + + view[2] += 8; + + cg.refdefViewAngles[PITCH] *= 0.5; + + AngleVectors( cg.refdefViewAngles, forward, right, up ); + + forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI ); + sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI ); + VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view ); + VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view ); + + // trace a ray from the origin to the viewpoint to make sure the view isn't + // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything + + if (!cg_cameraMode.integer) { + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + + if ( trace.fraction != 1.0 ) { + VectorCopy( trace.endpos, view ); + view[2] += (1.0 - trace.fraction) * 32; + // try another trace to this position, because a tunnel may have the ceiling + // close enogh that this is poking out + + CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID ); + VectorCopy( trace.endpos, view ); + } + } + + + VectorCopy( view, cg.refdef.vieworg ); + + // select pitch to look at focus point from vieword + VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint ); + focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] ); + if ( focusDist < 1 ) { + focusDist = 1; // should never happen + } + cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); + cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value; +} + + +// this causes a compiler bug on mac MrC compiler +static void CG_StepOffset( void ) { + int timeDelta; + + // smooth out stair climbing + timeDelta = cg.time - cg.stepTime; + if ( timeDelta < STEP_TIME ) { + cg.refdef.vieworg[2] -= cg.stepChange + * (STEP_TIME - timeDelta) / STEP_TIME; + } +} + +/* +=============== +CG_OffsetFirstPersonView + +=============== +*/ +static void CG_OffsetFirstPersonView( void ) { + float *origin; + float *angles; + float bob; + float ratio; + float delta; + float speed; + float f; + vec3_t predictedVelocity; + int timeDelta; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + return; + } + + origin = cg.refdef.vieworg; + angles = cg.refdefViewAngles; + + // if dead, fix the angle and don't add any kick + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { + angles[ROLL] = 40; + angles[PITCH] = -15; + angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + origin[2] += cg.predictedPlayerState.viewheight; + return; + } + + // add angles based on weapon kick + VectorAdd (angles, cg.kick_angles, angles); + + // add angles based on damage kick + if ( cg.damageTime ) { + ratio = cg.time - cg.damageTime; + if ( ratio < DAMAGE_DEFLECT_TIME ) { + ratio /= DAMAGE_DEFLECT_TIME; + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } else { + ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME; + if ( ratio > 0 ) { + angles[PITCH] += ratio * cg.v_dmg_pitch; + angles[ROLL] += ratio * cg.v_dmg_roll; + } + } + } + + // add pitch based on fall kick +#if 0 + ratio = ( cg.time - cg.landTime) / FALL_TIME; + if (ratio < 0) + ratio = 0; + angles[PITCH] += ratio * cg.fall_value; +#endif + + // add angles based on velocity + VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity ); + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]); + angles[PITCH] += delta * cg_runpitch.value; + + delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]); + angles[ROLL] -= delta * cg_runroll.value; + + // add angles based on bob + + // make sure the bob is visible even at low speeds + speed = cg.xyspeed > 200 ? cg.xyspeed : 200; + + delta = cg.bobfracsin * cg_bobpitch.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching + angles[PITCH] += delta; + delta = cg.bobfracsin * cg_bobroll.value * speed; + if (cg.predictedPlayerState.pm_flags & PMF_DUCKED) + delta *= 3; // crouching accentuates roll + if (cg.bobcycle & 1) + delta = -delta; + angles[ROLL] += delta; + +//=================================== + + // add view height + origin[2] += cg.predictedPlayerState.viewheight; + + // smooth out duck height changes + timeDelta = cg.time - cg.duckTime; + if ( timeDelta < DUCK_TIME) { + cg.refdef.vieworg[2] -= cg.duckChange + * (DUCK_TIME - timeDelta) / DUCK_TIME; + } + + // add bob height + bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value; + if (bob > 6) { + bob = 6; + } + + origin[2] += bob; + + + // add fall height + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) { + f = delta / LAND_DEFLECT_TIME; + cg.refdef.vieworg[2] += cg.landChange * f; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + delta -= LAND_DEFLECT_TIME; + f = 1.0 - ( delta / LAND_RETURN_TIME ); + cg.refdef.vieworg[2] += cg.landChange * f; + } + + // add step offset + CG_StepOffset(); + + // add kick offset + + VectorAdd (origin, cg.kick_origin, origin); + + // pivot the eye based on a neck length +#if 0 + { +#define NECK_LENGTH 8 + vec3_t forward, up; + + cg.refdef.vieworg[2] -= NECK_LENGTH; + AngleVectors( cg.refdefViewAngles, forward, NULL, up ); + VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); + } +#endif +} + +//====================================================================== + +void CG_ZoomDown_f( void ) { + if ( cg.zoomed ) { + return; + } + cg.zoomed = qtrue; + cg.zoomTime = cg.time; +} + +void CG_ZoomUp_f( void ) { + if ( !cg.zoomed ) { + return; + } + cg.zoomed = qfalse; + cg.zoomTime = cg.time; +} + + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 + +static int CG_CalcFov( void ) { + float x; + float phase; + float v; + int contents; + float fov_x, fov_y; + float zoomFov; + float f; + int inwater; + + if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + // if in intermission, use a fixed value + fov_x = 90; + } else { + // user selectable + if ( cgs.dmflags & DF_FIXED_FOV ) { + // dmflag to prevent wide fov for all clients + fov_x = 90; + } else { + fov_x = cg_fov.value; + if ( fov_x < 1 ) { + fov_x = 1; + } else if ( fov_x > 160 ) { + fov_x = 160; + } + } + + // account for zooms + zoomFov = cg_zoomFov.value; + if ( zoomFov < 1 ) { + zoomFov = 1; + } else if ( zoomFov > 160 ) { + zoomFov = 160; + } + + if ( cg.zoomed ) { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = zoomFov; + } else { + fov_x = fov_x + f * ( zoomFov - fov_x ); + } + } else { + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME; + if ( f > 1.0 ) { + fov_x = fov_x; + } else { + fov_x = zoomFov + f * ( fov_x - zoomFov ); + } + } + } + + x = cg.refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg.refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // warp if underwater + contents = CG_PointContents( cg.refdef.vieworg, -1 ); + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){ + phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + v = WAVE_AMPLITUDE * sin( phase ); + fov_x += v; + fov_y -= v; + inwater = qtrue; + } + else { + inwater = qfalse; + } + + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + if ( !cg.zoomed ) { + cg.zoomSensitivity = 1; + } else { + cg.zoomSensitivity = cg.refdef.fov_y / 75.0; + } + + return inwater; +} + + + +/* +=============== +CG_DamageBlendBlob + +=============== +*/ +static void CG_DamageBlendBlob( void ) { + int t; + int maxTime; + refEntity_t ent; + + if ( !cg.damageValue ) { + return; + } + + //if (cg.cameraMode) { + // return; + //} + + // ragePro systems can't fade blends, so don't obscure the screen + if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) { + return; + } + + maxTime = DAMAGE_TIME; + t = cg.time - cg.damageTime; + if ( t <= 0 || t >= maxTime ) { + return; + } + + + memset( &ent, 0, sizeof( ent ) ); + ent.reType = RT_SPRITE; + ent.renderfx = RF_FIRST_PERSON; + + VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin ); + VectorMA( ent.origin, cg.damageX * -8, cg.refdef.viewaxis[1], ent.origin ); + VectorMA( ent.origin, cg.damageY * 8, cg.refdef.viewaxis[2], ent.origin ); + + ent.radius = cg.damageValue * 3; + ent.customShader = cgs.media.viewBloodShader; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 200 * ( 1.0 - ((float)t / maxTime) ); + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_CalcViewValues + +Sets cg.refdef view values +=============== +*/ +static int CG_CalcViewValues( void ) { + playerState_t *ps; + + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + + // strings for in game rendering + // Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) ); + // Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) ); + + // calculate size of 3D view + CG_CalcVrect(); + + ps = &cg.predictedPlayerState; +/* + if (cg.cameraMode) { + vec3_t origin, angles; + if (trap_getCameraInfo(cg.time, &origin, &angles)) { + VectorCopy(origin, cg.refdef.vieworg); + angles[ROLL] = 0; + VectorCopy(angles, cg.refdefViewAngles); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } else { + cg.cameraMode = qfalse; + } + } +*/ + // intermission view + if ( ps->pm_type == PM_INTERMISSION ) { + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + return CG_CalcFov(); + } + + cg.bobcycle = ( ps->bobCycle & 128 ) >> 7; + cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) ); + cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] + + ps->velocity[1] * ps->velocity[1] ); + + + VectorCopy( ps->origin, cg.refdef.vieworg ); + VectorCopy( ps->viewangles, cg.refdefViewAngles ); + + if (cg_cameraOrbit.integer) { + if (cg.time > cg.nextOrbitTime) { + cg.nextOrbitTime = cg.time + cg_cameraOrbitDelay.integer; + cg_thirdPersonAngle.value += cg_cameraOrbit.value; + } + } + // add error decay + if ( cg_errorDecay.value > 0 ) { + int t; + float f; + + t = cg.time - cg.predictedErrorTime; + f = ( cg_errorDecay.value - t ) / cg_errorDecay.value; + if ( f > 0 && f < 1 ) { + VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg ); + } else { + cg.predictedErrorTime = 0; + } + } + + if ( cg.renderingThirdPerson ) { + // back away from character + CG_OffsetThirdPersonView(); + } else { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView(); + } + + // position eye reletive to origin + AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis ); + + if ( cg.hyperspace ) { + cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE; + } + + // field of view + return CG_CalcFov(); +} + + +/* +===================== +CG_PowerupTimerSounds +===================== +*/ +static void CG_PowerupTimerSounds( void ) { + int i; + int t; + + // powerup timers going away + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + //PKMOD - Ergodic 06/03/01 - add bean powerup timer for armor countdown immunity, + // ignore the beans powerup + if ( i == PW_BEANS ) + continue; + + t = cg.snap->ps.powerups[i]; + if ( t <= cg.time ) { + continue; + } + if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { + continue; + } + if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) { + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound ); + } + } +} + +/* +===================== +CG_AddBufferedSound +===================== +*/ +void CG_AddBufferedSound( sfxHandle_t sfx ) { + if ( !sfx ) + return; + cg.soundBuffer[cg.soundBufferIn] = sfx; + cg.soundBufferIn = (cg.soundBufferIn + 1) % MAX_SOUNDBUFFER; + if (cg.soundBufferIn == cg.soundBufferOut) { + cg.soundBufferOut++; + } +} + +/* +===================== +CG_PlayBufferedSounds +===================== +*/ +static void CG_PlayBufferedSounds( void ) { + if ( cg.soundTime < cg.time ) { + if (cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[cg.soundBufferOut]) { + trap_S_StartLocalSound(cg.soundBuffer[cg.soundBufferOut], CHAN_ANNOUNCER); + cg.soundBuffer[cg.soundBufferOut] = 0; + cg.soundBufferOut = (cg.soundBufferOut + 1) % MAX_SOUNDBUFFER; + cg.soundTime = cg.time + 750; + } + } +} + +//========================================================================= + +/* +================= +CG_DrawActiveFrame + +Generates and draws a game scene and status information at the given time. +================= +*/ +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { + int inwater; + + cg.time = serverTime; + cg.demoPlayback = demoPlayback; + + // update cvars + CG_UpdateCvars(); + + // if we are only updating the screen as a loading + // pacifier, don't even try to read snapshots + if ( cg.infoScreenText[0] != 0 ) { + CG_DrawInformation(); + return; + } + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds(qfalse); + + // clear all the render lists + trap_R_ClearScene(); + + // set up cg.snap and possibly cg.nextSnap + CG_ProcessSnapshots(); + + // if we haven't received any snapshots yet, all + // we can draw is the information screen + if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) { + CG_DrawInformation(); + return; + } + + // let the client system know what our weapon and zoom settings are + trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity ); + + // this counter will be bumped for every valid scene we generate + cg.clientFrame++; + + // update cg.predictedPlayerState + CG_PredictPlayerState(); + + // decide on third person view + cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0); + + // build cg.refdef + inwater = CG_CalcViewValues(); + + // first person blend blobs, done after AnglesToAxis + if ( !cg.renderingThirdPerson ) { + CG_DamageBlendBlob(); + } + + // build the render lists + if ( !cg.hyperspace ) { + CG_AddPacketEntities(); // adter calcViewValues, so predicted player state is correct + CG_AddMarks(); + CG_AddParticles (); + CG_AddLocalEntities(); + } + CG_AddViewWeapon( &cg.predictedPlayerState ); + + // add buffered sounds + CG_PlayBufferedSounds(); + + // play buffered voice chats + CG_PlayBufferedVoiceChats(); + + // finish up the rest of the refdef + if ( cg.testModelEntity.hModel ) { + CG_AddTestModel(); + } + cg.refdef.time = cg.time; + memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) ); + + // warning sounds when powerup is wearing off + CG_PowerupTimerSounds(); + + // update audio positions + trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater ); + + // make sure the lagometerSample and frame timing isn't done twice when in stereo + if ( stereoView != STEREO_RIGHT ) { + cg.frametime = cg.time - cg.oldTime; + if ( cg.frametime < 0 ) { + cg.frametime = 0; + } + cg.oldTime = cg.time; + CG_AddLagometerFrameInfo(); + } + if (cg_timescale.value != cg_timescaleFadeEnd.value) { + if (cg_timescale.value < cg_timescaleFadeEnd.value) { + cg_timescale.value += cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; + if (cg_timescale.value > cg_timescaleFadeEnd.value) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + else { + cg_timescale.value -= cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000; + if (cg_timescale.value < cg_timescaleFadeEnd.value) + cg_timescale.value = cg_timescaleFadeEnd.value; + } + if (cg_timescaleFadeSpeed.value) { + trap_Cvar_Set("timescale", va("%f", cg_timescale.value)); + } + } + + // actually issue the rendering calls + CG_DrawActive( stereoView ); + + if ( cg_stats.integer ) { + CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); + } + + +} + diff --git a/quake3/source/code/cgame/cg_weapons.c b/quake3/source/code/cgame/cg_weapons.c new file mode 100644 index 0000000..50d461b --- /dev/null +++ b/quake3/source/code/cgame/cg_weapons.c @@ -0,0 +1,3472 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_weapons.c -- events and effects dealing with weapons +#include "cg_local.h" + +/* +========================== +CG_MachineGunEjectBrass +========================== +*/ +static void CG_MachineGunEjectBrass( centity_t *cent ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity, xvelocity; + vec3_t offset, xoffset; + float waterScale = 1.0f; + vec3_t v[3]; + + if ( cg_brassTime.integer <= 0 ) { + return; + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = 0; + velocity[1] = -50 + 40 * crandom(); + velocity[2] = 100 + 50 * crandom(); + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + cg_brassTime.integer + ( cg_brassTime.integer / 4 ) * random(); + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time - (rand()&15); + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 8; + offset[1] = -4; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, re->origin ); + + VectorCopy( re->origin, le->pos.trBase ); + + if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10f; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->hModel = cgs.media.machinegunBrassModel; + + le->bounceFactor = 0.4 * waterScale; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand()&31; + le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand()&31; + le->angles.trDelta[0] = 2; + le->angles.trDelta[1] = 1; + le->angles.trDelta[2] = 0; + + le->leFlags = LEF_TUMBLE; + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; +} + +/* +========================== +CG_ShotgunEjectBrass +========================== +*/ +static void CG_ShotgunEjectBrass( centity_t *cent ) { + localEntity_t *le; + refEntity_t *re; + vec3_t velocity, xvelocity; + vec3_t offset, xoffset; + vec3_t v[3]; + int i; + + if ( cg_brassTime.integer <= 0 ) { + return; + } + + for ( i = 0; i < 2; i++ ) { + float waterScale = 1.0f; + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = 60 + 60 * crandom(); + if ( i == 0 ) { + velocity[1] = 40 + 10 * crandom(); + } else { + velocity[1] = -40 + 10 * crandom(); + } + velocity[2] = 100 + 50 * crandom(); + + le->leType = LE_FRAGMENT; + le->startTime = cg.time; + le->endTime = le->startTime + cg_brassTime.integer*3 + cg_brassTime.integer * random(); + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 8; + offset[1] = 0; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, re->origin ); + VectorCopy( re->origin, le->pos.trBase ); + if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10f; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->hModel = cgs.media.shotgunBrassModel; + le->bounceFactor = 0.3f; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand()&31; + le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand()&31; + le->angles.trDelta[0] = 1; + le->angles.trDelta[1] = 0.5; + le->angles.trDelta[2] = 0; + + le->leFlags = LEF_TUMBLE; + le->leBounceSoundType = LEBS_BRASS; + le->leMarkType = LEMT_NONE; + } +} + + +#ifdef MISSIONPACK +/* +========================== +CG_NailgunEjectBrass +========================== +*/ +static void CG_NailgunEjectBrass( centity_t *cent ) { + localEntity_t *smoke; + vec3_t origin; + vec3_t v[3]; + vec3_t offset; + vec3_t xoffset; + vec3_t up; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 0; + offset[1] = -12; + offset[2] = 24; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + VectorAdd( cent->lerpOrigin, xoffset, origin ); + + VectorSet( up, 0, 0, 64 ); + + smoke = CG_SmokePuff( origin, up, 32, 1, 1, 1, 0.33f, 700, cg.time, 0, 0, cgs.media.smokePuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; +} +#endif + + +/* +========================== +CG_RailTrail +========================== +*/ +void CG_RailTrail (clientInfo_t *ci, vec3_t start, vec3_t end) { + vec3_t axis[36], move, move2, next_move, vec, temp; + float len; + int i, j, skip; + + localEntity_t *le; + refEntity_t *re; + +#define RADIUS 4 +#define ROTATION 1 +#define SPACING 5 + + start[2] -= 4; + VectorCopy (start, move); + VectorSubtract (end, start, vec); + len = VectorNormalize (vec); + PerpendicularVector(temp, vec); + for (i = 0 ; i < 36; i++) { + RotatePointAroundVector(axis[i], vec, temp, i * 10);//banshee 2.4 was 10 + } + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + le->leType = LE_FADE_RGB; + le->startTime = cg.time; + le->endTime = cg.time + cg_railTrailTime.value; + le->lifeRate = 1.0 / (le->endTime - le->startTime); + + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_RAIL_CORE; + re->customShader = cgs.media.railCoreShader; + + VectorCopy(start, re->origin); + VectorCopy(end, re->oldorigin); + + re->shaderRGBA[0] = ci->color1[0] * 255; + re->shaderRGBA[1] = ci->color1[1] * 255; + re->shaderRGBA[2] = ci->color1[2] * 255; + re->shaderRGBA[3] = 255; + + le->color[0] = ci->color1[0] * 0.75; + le->color[1] = ci->color1[1] * 0.75; + le->color[2] = ci->color1[2] * 0.75; + le->color[3] = 1.0f; + + AxisClear( re->axis ); + + VectorMA(move, 20, vec, move); + VectorCopy(move, next_move); + VectorScale (vec, SPACING, vec); + + if (cg_oldRail.integer != 0) { + // nudge down a bit so it isn't exactly in center + re->origin[2] -= 8; + re->oldorigin[2] -= 8; + return; + } + skip = -1; + + j = 18; + for (i = 0; i < len; i += SPACING) { + if (i != skip) { + skip = i + SPACING; + le = CG_AllocLocalEntity(); + re = &le->refEntity; + le->leFlags = LEF_PUFF_DONT_SCALE; + le->leType = LE_MOVE_SCALE_FADE; + le->startTime = cg.time; + le->endTime = cg.time + (i>>1) + 600; + le->lifeRate = 1.0 / (le->endTime - le->startTime); + + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_SPRITE; + re->radius = 1.1f; + re->customShader = cgs.media.railRingsShader; + + re->shaderRGBA[0] = ci->color2[0] * 255; + re->shaderRGBA[1] = ci->color2[1] * 255; + re->shaderRGBA[2] = ci->color2[2] * 255; + re->shaderRGBA[3] = 255; + + le->color[0] = ci->color2[0] * 0.75; + le->color[1] = ci->color2[1] * 0.75; + le->color[2] = ci->color2[2] * 0.75; + le->color[3] = 1.0f; + + le->pos.trType = TR_LINEAR; + le->pos.trTime = cg.time; + + VectorCopy( move, move2); + VectorMA(move2, RADIUS , axis[j], move2); + VectorCopy(move2, le->pos.trBase); + + le->pos.trDelta[0] = axis[j][0]*6; + le->pos.trDelta[1] = axis[j][1]*6; + le->pos.trDelta[2] = axis[j][2]*6; + } + + VectorAdd (move, vec, move); + + j = j + ROTATION < 36 ? j + ROTATION : (j + ROTATION) % 36; + } +} + +/* +========================== +CG_RocketTrail +========================== +*/ +static void CG_RocketTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int t; + int startTime, contents; + int lastContents; + entityState_t *es; + vec3_t up; + localEntity_t *smoke; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + up[0] = 0; + up[1] = 0; + up[2] = 0; + + step = 50; + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + // if object (e.g. grenade) is stationary, don't toss up smoke + if ( es->pos.trType == TR_STATIONARY ) { + ent->trailTime = cg.time; + return; + } + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 8 ); + } + return; + } + + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + + smoke = CG_SmokePuff( lastPos, up, + wi->trailRadius, + 1, 1, 1, 0.33f, + wi->wiTrailTime, + t, + 0, + 0, + cgs.media.smokePuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; + } + +} + +#ifdef MISSIONPACK +/* +========================== +CG_NailTrail +========================== +*/ +static void CG_NailTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int t; + int startTime, contents; + int lastContents; + entityState_t *es; + vec3_t up; + localEntity_t *smoke; + + if ( cg_noProjectileTrail.integer ) { + return; + } + + up[0] = 0; + up[1] = 0; + up[2] = 0; + + step = 50; + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + // if object (e.g. grenade) is stationary, don't toss up smoke + if ( es->pos.trType == TR_STATIONARY ) { + ent->trailTime = cg.time; + return; + } + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 8 ); + } + return; + } + + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + + smoke = CG_SmokePuff( lastPos, up, + wi->trailRadius, + 1, 1, 1, 0.33f, + wi->wiTrailTime, + t, + 0, + 0, + cgs.media.nailPuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; + } + +} +#endif + +/* +========================== +CG_NailTrail +========================== +*/ +static void CG_PlasmaTrail( centity_t *cent, const weaponInfo_t *wi ) { + localEntity_t *le; + refEntity_t *re; + entityState_t *es; + vec3_t velocity, xvelocity, origin; + vec3_t offset, xoffset; + vec3_t v[3]; + int t, startTime, step; + + float waterScale = 1.0f; + + if ( cg_noProjectileTrail.integer || cg_oldPlasma.integer ) { + return; + } + + step = 50; + + es = ¢->currentState; + startTime = cent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + velocity[0] = 60 - 120 * crandom(); + velocity[1] = 40 - 80 * crandom(); + velocity[2] = 100 - 200 * crandom(); + + le->leType = LE_MOVE_SCALE_FADE; + le->leFlags = LEF_TUMBLE; + le->leBounceSoundType = LEBS_NONE; + le->leMarkType = LEMT_NONE; + + le->startTime = cg.time; + le->endTime = le->startTime + 600; + + le->pos.trType = TR_GRAVITY; + le->pos.trTime = cg.time; + + AnglesToAxis( cent->lerpAngles, v ); + + offset[0] = 2; + offset[1] = 2; + offset[2] = 2; + + xoffset[0] = offset[0] * v[0][0] + offset[1] * v[1][0] + offset[2] * v[2][0]; + xoffset[1] = offset[0] * v[0][1] + offset[1] * v[1][1] + offset[2] * v[2][1]; + xoffset[2] = offset[0] * v[0][2] + offset[1] * v[1][2] + offset[2] * v[2][2]; + + VectorAdd( origin, xoffset, re->origin ); + VectorCopy( re->origin, le->pos.trBase ); + + if ( CG_PointContents( re->origin, -1 ) & CONTENTS_WATER ) { + waterScale = 0.10f; + } + + xvelocity[0] = velocity[0] * v[0][0] + velocity[1] * v[1][0] + velocity[2] * v[2][0]; + xvelocity[1] = velocity[0] * v[0][1] + velocity[1] * v[1][1] + velocity[2] * v[2][1]; + xvelocity[2] = velocity[0] * v[0][2] + velocity[1] * v[1][2] + velocity[2] * v[2][2]; + VectorScale( xvelocity, waterScale, le->pos.trDelta ); + + AxisCopy( axisDefault, re->axis ); + re->shaderTime = cg.time / 1000.0f; + re->reType = RT_SPRITE; + re->radius = 0.25f; + re->customShader = cgs.media.railRingsShader; + le->bounceFactor = 0.3f; + + re->shaderRGBA[0] = wi->flashDlightColor[0] * 63; + re->shaderRGBA[1] = wi->flashDlightColor[1] * 63; + re->shaderRGBA[2] = wi->flashDlightColor[2] * 63; + re->shaderRGBA[3] = 63; + + le->color[0] = wi->flashDlightColor[0] * 0.2; + le->color[1] = wi->flashDlightColor[1] * 0.2; + le->color[2] = wi->flashDlightColor[2] * 0.2; + le->color[3] = 0.25f; + + le->angles.trType = TR_LINEAR; + le->angles.trTime = cg.time; + le->angles.trBase[0] = rand()&31; + le->angles.trBase[1] = rand()&31; + le->angles.trBase[2] = rand()&31; + le->angles.trDelta[0] = 1; + le->angles.trDelta[1] = 0.5; + le->angles.trDelta[2] = 0; + +} + +//PKMOD - Ergodic 03/10/01 - debug dragon return (trail appears behind player) +/* +============= +VectorToString PKMOD REMOVE THIS LATER + +This is just a convenience function +for printing vectors +============= +*/ +char *vtos( const 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; +} + +/* +========================== +CG_GrappleTrail +========================== +*/ +void CG_GrappleTrail( centity_t *ent, const weaponInfo_t *wi ) { + vec3_t origin; + entityState_t *es; + vec3_t forward, up; + refEntity_t beam; + + //PKMOD - Ergodic 03/10/01 - debug dragon return (trail appears behind player) (inactive) +// int dragon_distance; + + es = &ent->currentState; + + //PKMOD - Ergodic 03/10/01 - set tongue's location in the old origin for cgame hack +// BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + ent->trailTime = cg.time; + + memset( &beam, 0, sizeof( beam ) ); + //FIXME adjust for muzzle position + VectorCopy ( cg_entities[ ent->currentState.otherEntityNum ].lerpOrigin, beam.origin ); + beam.origin[2] += 26; + AngleVectors( cg_entities[ ent->currentState.otherEntityNum ].lerpAngles, forward, NULL, up ); + VectorMA( beam.origin, -6, up, beam.origin ); + //PKMOD - Ergodic 03/10/01 - set tongue's location in the old origin for cgame hack +// VectorCopy( origin, beam.oldorigin ); + + //PKMOD - Ergodic 03/11/01 - if dragon return origin is set then use it, else use launch value + // This will imply that dragon will not work correctly at coordinates (0,0,0) + // fix in the patch + if ( VectorLength( es->origin2 ) > 1 ) { + VectorCopy( es->origin2, beam.oldorigin ); + } + else { + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + VectorCopy( origin, beam.oldorigin ); + } + + //PKMOD - Ergodic 03/10/01 - debug dragon return (trail appears behind player) (inactive) +// dragon_distance = Distance( beam.origin, beam.oldorigin ); +// Com_Printf( "CG_GrappleTrail - origin>%s<, old>%s<, dist>%d<\n", vtos(beam.origin), vtos(beam.oldorigin), dragon_distance ); + + + if (Distance( beam.origin, beam.oldorigin ) < 64 ) + return; // Don't draw if close + + //PKMOD - Ergodic 10/03/00 change flash to railgun style to make a tighter beam + beam.reType = RT_RAIL_CORE; +// beam.reType = RT_LIGHTNING; + //PKMOD - Ergodic 10/03/00 dragon beam shader effect + beam.customShader = cgs.media.dragonboltShader; +// beam.customShader = cgs.media.lightningShader; + + AxisClear( beam.axis ); + beam.shaderRGBA[0] = 0xff; + beam.shaderRGBA[1] = 0xff; + beam.shaderRGBA[2] = 0xff; + beam.shaderRGBA[3] = 0xff; + trap_R_AddRefEntityToScene( &beam ); +} + +/* +========================== +CG_GrenadeTrail +========================== +*/ +static void CG_GrenadeTrail( centity_t *ent, const weaponInfo_t *wi ) { + CG_RocketTrail( ent, wi ); +} + +/* +========================== +//PKMOD - Ergodic 01/11/01 - add a nail trail that is "slimmed" down +CG_PKANailTrail +========================== +*/ +static void PKANailTrail( centity_t *ent, const weaponInfo_t *wi ) { + int step; + vec3_t origin, lastPos; + int t; + int startTime, contents; + int lastContents; + entityState_t *es; + vec3_t up; + localEntity_t *smoke; + + up[0] = 0; + up[1] = 0; + up[2] = 0; + + step = 200; //was 50 + + es = &ent->currentState; + startTime = ent->trailTime; + t = step * ( (startTime + step) / step ); + + BG_EvaluateTrajectory( &es->pos, cg.time, origin ); + contents = CG_PointContents( origin, -1 ); + + BG_EvaluateTrajectory( &es->pos, ent->trailTime, lastPos ); + lastContents = CG_PointContents( lastPos, -1 ); + + ent->trailTime = cg.time; + + if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ) { + if ( contents & lastContents & CONTENTS_WATER ) { + CG_BubbleTrail( lastPos, origin, 48 ); //was 8 + } + return; + } + + for ( ; t <= ent->trailTime ; t += step ) { + BG_EvaluateTrajectory( &es->pos, t, lastPos ); + + smoke = CG_SmokePuff( lastPos, up, + wi->trailRadius, + 1, 1, 1, 0.33f, + wi->wiTrailTime, + t, + 0, + 0, + cgs.media.smokePuffShader ); + // use the optimized local entity add + smoke->leType = LE_SCALE_FADE; + } + +} + +/* +================= +CG_RegisterWeapon + +The server says this item is used on this level +================= +*/ +void CG_RegisterWeapon( int weaponNum ) { + weaponInfo_t *weaponInfo; + gitem_t *item, *ammo; + char path[MAX_QPATH]; + vec3_t mins, maxs; + int i; + + weaponInfo = &cg_weapons[weaponNum]; + + if ( weaponNum == 0 ) { + return; + } + + if ( weaponInfo->registered ) { + return; + } + + memset( weaponInfo, 0, sizeof( *weaponInfo ) ); + weaponInfo->registered = qtrue; + + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) { + if ( item->giType == IT_WEAPON && item->giTag == weaponNum ) { + weaponInfo->item = item; + break; + } + } + if ( !item->classname ) { + CG_Error( "Couldn't find weapon %i", weaponNum ); + } + CG_RegisterItemVisuals( item - bg_itemlist ); + + // load cmodel before model so filecache works + weaponInfo->weaponModel = trap_R_RegisterModel( item->world_model[0] ); + + // calc midpoint for rotation + trap_R_ModelBounds( weaponInfo->weaponModel, mins, maxs ); + for ( i = 0 ; i < 3 ; i++ ) { + weaponInfo->weaponMidpoint[i] = mins[i] + 0.5 * ( maxs[i] - mins[i] ); + } + + weaponInfo->weaponIcon = trap_R_RegisterShader( item->icon ); + weaponInfo->ammoIcon = trap_R_RegisterShader( item->icon ); + + for ( ammo = bg_itemlist + 1 ; ammo->classname ; ammo++ ) { + if ( ammo->giType == IT_AMMO && ammo->giTag == weaponNum ) { + break; + } + } + if ( ammo->classname && ammo->world_model[0] ) { + weaponInfo->ammoModel = trap_R_RegisterModel( ammo->world_model[0] ); + } + + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_flash.md3" ); + weaponInfo->flashModel = trap_R_RegisterModel( path ); + + //PKMOD - Ergodic 03/04/01 - add lightning gun to barrel list + //PKMOD - Ergodic 03/27/01 - remove lightning gun to barrel list + //PKMOD - Ergodic 05/15/03 - re-add lightning gun to barrel list for Uber's clg model +// if ( weaponNum == WP_MACHINEGUN || weaponNum == WP_GAUNTLET || weaponNum == WP_BFG ) { + if ( weaponNum == WP_MACHINEGUN || weaponNum == WP_GAUNTLET || weaponNum == WP_LIGHTNING ) { + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_barrel.md3" ); + weaponInfo->barrelModel = trap_R_RegisterModel( path ); + } + + strcpy( path, item->world_model[0] ); + COM_StripExtension( path, path ); + strcat( path, "_hand.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( path ); + + if ( !weaponInfo->handsModel ) { + weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); + } + + weaponInfo->loopFireSound = qfalse; + + switch ( weaponNum ) { + case WP_GAUNTLET: + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/melee/fstrun.wav", qfalse ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav", qfalse ); + break; + + case WP_LIGHTNING: + //PKMOD - Ergodic 12/16/00 - add "f" to force a float declaration + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + //PKMOD - Ergodic 09/06/00 add new staionary sound from Mongusta + //12/16/00 - add non compressed flag + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons2/chainlightning/Chainlight Stat#6a.wav", qfalse ); + + //12/16/00 - add non compressed flag + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav", qfalse ); + + //PKMOD - Ergodic 08/22/00 add multiple fire sounds from Mongusta + //12/16/00 - add non compressed flag + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons2/chainlightning/Chainlight Fire#2.wav", qfalse ); + weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons2/chainlightning/Chainlight Fire#3.wav", qfalse ); + weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons2/chainlightning/Chainlight Fire#5.wav", qfalse ); + + //PKMOD - Ergodic 08/21/00 set the shader to chainlightning + cgs.media.chainlightningShader = trap_R_RegisterShader( "chainlightningBolt" ); + //PKMOD - Ergodic 08/21/00 set the map hit model to correct color + cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/clghit1.md3" ); + //12/16/00 - add non compressed flag + cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav", qfalse ); + cgs.media.sfx_lghit2 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav", qfalse ); + cgs.media.sfx_lghit3 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav", qfalse ); + + break; + + //PKMOD - Ergodic 10/03/00 hand models + //PKMOD - Ergodic 10/03/00 set the shader to the dragon + case WP_GRAPPLING_HOOK: + //PKMOD - Ergodic 12/16/00 - add "f" to force a float declaration + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/dragon/tongue.md3" ); + weaponInfo->weaponModel = trap_R_RegisterModel( "models/weapons2/dragon/dragonhold.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/dragon/dragonhold_hand.md3" ); + + //PKMOD - Ergodic 02/14/02 - use the plasmagun flyby sound for the moving tongue + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse ); + + //PKMOD - Ergodic 02/14/02 - hack - store the gauntlet blade in the flashsound[3] + // Note: this sound will not be hard as a flashsound but will be called in + // cg_ents.c(CG_Dragon_Deploy) when gauntletblade is being deployed. + weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons2/gauntlet/Bladewhirl.wav", qfalse ); + + //PKMOD - Ergodic 10/03/00 set the beam shader to the dragon + cgs.media.dragonboltShader = trap_R_RegisterShader( "DragonBolt" ); + weaponInfo->missileTrailFunc = CG_GrappleTrail; + weaponInfo->missileDlight = 200; + weaponInfo->wiTrailTime = 2000; + weaponInfo->trailRadius = 64; + //PKMOD - Ergodic 12/16/00 - add "f" to force a float declaration + MAKERGB( weaponInfo->missileDlightColor, 1.0f, 1.0f, 0.5f ); + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 0.5f ); + //12/16/00 - add non compressed flag + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav", qfalse ); + //PKMOD - Ergodic 10/03/00 set the dragon fire sound + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons2/dragon/gpulling.wav", qfalse ); + break; + +#ifdef MISSIONPACK + case WP_CHAINGUN: + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/vulcan/wvulfire.wav", qfalse ); + weaponInfo->loopFireSound = qtrue; + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf1b.wav", qfalse ); + weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf2b.wav", qfalse ); + weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf3b.wav", qfalse ); + weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/vulcan/vulcanf4b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); + break; +#endif + + case WP_MACHINEGUN: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf1b.wav", qfalse ); + weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf2b.wav", qfalse ); + weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf3b.wav", qfalse ); + weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons/machinegun/machgf4b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + cgs.media.bulletExplosionShader = trap_R_RegisterShader( "bulletExplosion" ); + break; + + case WP_SHOTGUN: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass; + break; + + case WP_ROCKET_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/rocket/rocket.md3" ); + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse ); + weaponInfo->missileTrailFunc = CG_RocketTrail; + weaponInfo->missileDlight = 200; + weaponInfo->wiTrailTime = 2000; + weaponInfo->trailRadius = 64; + + MAKERGB( weaponInfo->missileDlightColor, 1, 0.75f, 0 ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); + cgs.media.rocketExplosionShader = trap_R_RegisterShader( "rocketExplosion" ); + break; + +#ifdef MISSIONPACK + case WP_PROX_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/proxmine.md3" ); + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->wiTrailTime = 700; + weaponInfo->trailRadius = 32; + MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/proxmine/wstbfire.wav", qfalse ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; +#endif + + case WP_GRENADE_LAUNCHER: + weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" ); + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->wiTrailTime = 700; + weaponInfo->trailRadius = 32; + MAKERGB( weaponInfo->flashDlightColor, 1, 0.70f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav", qfalse ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; + +#ifdef MISSIONPACK + case WP_NAILGUN: + weaponInfo->ejectBrassFunc = CG_NailgunEjectBrass; + weaponInfo->missileTrailFunc = CG_NailTrail; +// weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/nailgun/wnalflit.wav", qfalse ); + weaponInfo->trailRadius = 16; + weaponInfo->wiTrailTime = 250; + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/nail.md3" ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.75f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/nailgun/wnalfire.wav", qfalse ); + break; +#endif + + case WP_PLASMAGUN: +// weaponInfo->missileModel = cgs.media.invulnerabilityPowerupModel; + weaponInfo->missileTrailFunc = CG_PlasmaTrail; + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/plasma/lasfly.wav", qfalse ); + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/plasma/hyprbf1a.wav", qfalse ); + cgs.media.plasmaExplosionShader = trap_R_RegisterShader( "plasmaExplosion" ); + cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); + break; + + case WP_RAILGUN: + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/railgun/rg_hum.wav", qfalse ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.5f, 0 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/railgun/railgf1a.wav", qfalse ); + cgs.media.railExplosionShader = trap_R_RegisterShader( "railExplosion" ); + cgs.media.railRingsShader = trap_R_RegisterShader( "railDisc" ); + cgs.media.railCoreShader = trap_R_RegisterShader( "railCore" ); + break; + + case WP_BFG: + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/bfg/bfg_hum.wav", qfalse ); + MAKERGB( weaponInfo->flashDlightColor, 1, 0.7f, 1 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bfg/bfg_fire.wav", qfalse ); + cgs.media.bfgExplosionShader = trap_R_RegisterShader( "bfgExplosion" ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/weaphits/bfg.md3" ); + //12/16/00 - add non compressed flag + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/rockfly.wav", qfalse + ); + break; + + //PKMOD + /*PKMOD -Add Weapons. + WP_HARPOON, + WP_GRAVITY, + WP_SENTRY, + WP_BEARTRAP, + WP_CHAINLG, + WP_A2K, + WP_EMPNUKE, + WP_AIRFIST, + WP_NAILGUN, + PKMOD -Add Weapons. */ +//PKMOD Ergodic 05/17/00 multi-model hack + case WP_GRAVITY: + //PKMOD - Ergodic 03/17/01 new PKA gravity well weapon to the "hold" model + weaponInfo->weaponModel = trap_R_RegisterModel( "models/weapons2/gwell/gwphold.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/gwell/gwphold_hand.md3" ); + //12/16/00 - add non compressed flag + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons2/gwell/gravity_carry.wav", qfalse ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/gwell/gwp.md3" ); + weaponInfo->wiTrailTime = 700; + weaponInfo->trailRadius = 32; + //PKMOD - Ergodic 07/11/00 - launch sound done in EV_GRAVITY_RELEASED +// weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons2/gwell/gravity_released.wav" ); + //PKMOD - Ergodic 12/16/00 - add "f" to force a float declaration + MAKERGB( weaponInfo->flashDlightColor, 1.0f, 0.7f, 0.5f ); +//PKMOD - Ergodic 05/17/00 - make launch sound same as player jump sound (effort to throw heavy item) +//PKMOD - Ergodic 05/17/00 - don't register sound since it will be programatically controlled +// weaponInfo->flashSound[0] = CG_CustomSound( es->number, "*jump1.wav" ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; +// case WP_GRAVITY_LAUNCHED: +// weaponInfo->missileModel = trap_R_RegisterModel( "models/ammo/grenade1.md3" ); +// weaponInfo->wiTrailTime = 700; +// weaponInfo->trailRadius = 32; +// MAKERGB( weaponInfo->flashDlightColor, 1, 0.7, 0.5 ); +//PKMOD - Ergodic 05/17/00 - make launch sound same as player jump sound (effort to throw heavy item) +//PKMOD - Ergodic 05/17/00 - don't register sound since it will be programatically controlled +// cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); +// weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons2/gwell/gravity_released.wav" ); +// break; + case WP_SENTRY: + weaponInfo->weaponModel = trap_R_RegisterModel( "models/weapons2/autosentry/autosentry_hold.md3" ); + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->wiTrailTime = 700; + weaponInfo->trailRadius = 32; + //PKMOD - Ergodic 12/16/00 - add "f" to force a float declaration + MAKERGB( weaponInfo->flashDlightColor, 1.0f, 0.7f, 0.5f ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; + case WP_BEARTRAP: + weaponInfo->weaponModel = trap_R_RegisterModel( "models/weapons2/beartrap/bearhold.md3" ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/beartrap/beartrap.md3" ); + weaponInfo->missileTrailFunc = CG_GrenadeTrail; + weaponInfo->wiTrailTime = 700; + weaponInfo->trailRadius = 32; + //PKMOD - Ergodic 12/16/00 - add "f" to force a float declaration + MAKERGB( weaponInfo->flashDlightColor, 1.0f, 0.7f, 0.5f ); +//PKMOD - Ergodic 05/27/00 - don't register sound since it will be programatically controlled +// weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/grenade/grenlf1a.wav" ); + cgs.media.grenadeExplosionShader = trap_R_RegisterShader( "grenadeExplosion" ); + break; +// case WP_CHAINLG: +// MAKERGB( weaponInfo->flashDlightColor, 0.6, 0.6, 1 ); +// weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons/melee/fsthum.wav" ); +// weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/lightning/lg_hum.wav" ); +// +// weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/lightning/lg_fire.wav" ); +// cgs.media.lightningShader = trap_R_RegisterShader( "lightningBolt" ); +// cgs.media.lightningExplosionModel = trap_R_RegisterModel( "models/weaphits/crackle.md3" ); +// cgs.media.sfx_lghit1 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit.wav" ); +// cgs.media.sfx_lghit2 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit2.wav" ); +// cgs.media.sfx_lghit3 = trap_S_RegisterSound( "sound/weapons/lightning/lg_hit3.wav" ); +// +// break; +//PKMOD - Ergodic 05/17/00 modify airfist registration parameters + case WP_AIRFIST: + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/airfist/airfist.md3" ); + //PKMOD - Ergodic 12/16/00 - add "f" to force a float declaration + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + //12/16/00 - add non compressed flag + //PKMOD - Ergodic 07/01/01 - sound will be added programmatically later in this module +// weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons2/airfist/agfire22.wav", qfalse ); + + break; + case WP_NAILGUN: +//PKMOD - Ergodic 07/28/00 add nailgun model + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/nailgun/nail.md3" ); + //PKMOD - Ergodic 12/16/00 - add "f" to force a float declaration + MAKERGB( weaponInfo->flashDlightColor, 1.0f, 1.0f, 0.1f ); +//PKMOD - Ergodic 08/05/00 changed the weapon sounds to mongusta's files + //12/16/00 - add non compressed flag + weaponInfo->readySound = trap_S_RegisterSound( "sound/weapons2/nailgun/nailgun_carry.wav", qfalse ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons2/nailgun/nailgunf1.wav", qfalse ); + weaponInfo->flashSound[1] = trap_S_RegisterSound( "sound/weapons2/nailgun/nailgunf2.wav", qfalse ); + weaponInfo->flashSound[2] = trap_S_RegisterSound( "sound/weapons2/nailgun/nailgunf3.wav", qfalse ); + weaponInfo->flashSound[3] = trap_S_RegisterSound( "sound/weapons2/nailgun/nailgunf4.wav", qfalse ); +// weaponInfo->ejectBrassFunc = CG_MachineGunEjectBrass; + //PKMOD - Ergodic 08/01/00 set nailgun impact + cgs.media.nailImpactShader = trap_R_RegisterShader( "nailImpact" ); +//PKMOD - Ergodic 01/11/01 add trail parameters + weaponInfo->missileTrailFunc = PKANailTrail; + weaponInfo->wiTrailTime = 400; + weaponInfo->trailRadius = 8; + + break; + case WP_EXPLODING_SHELLS: + //PKMOD - Ergodic 06/18/00 install custom shaders + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 0 ); + //12/16/00 - add non compressed flag + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/shotgun/sshotf1b.wav", qfalse ); + weaponInfo->ejectBrassFunc = CG_ShotgunEjectBrass; + break; + case WP_BEANS: + weaponInfo->weaponModel = trap_R_RegisterModel( "models/weapons2/beans/beanshold.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/beans/beanshold_hand.md3" ); + //PKMOD - Ergodic 12/16/00 - add "f" to force a float declaration + MAKERGB( weaponInfo->flashDlightColor, 1.0f, 0.7f, 0.5f ); + break; + + //PKMOD + + default: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav", qfalse ); + break; + } +} + +/* +================= +CG_RegisterItemVisuals + +The server says this item is used on this level +================= +*/ +void CG_RegisterItemVisuals( int itemNum ) { + itemInfo_t *itemInfo; + gitem_t *item; + + if ( itemNum < 0 || itemNum >= bg_numItems ) { + CG_Error( "CG_RegisterItemVisuals: itemNum %d out of range [0-%d]", itemNum, bg_numItems-1 ); + } + + itemInfo = &cg_items[ itemNum ]; + if ( itemInfo->registered ) { + return; + } + + item = &bg_itemlist[ itemNum ]; + + memset( itemInfo, 0, sizeof( &itemInfo ) ); + itemInfo->registered = qtrue; + + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); + + //PKMOD - Ergodic 03/05/01 - CLG has different pickup model than handhold model (inactive) + //PKMOD - Ergodic 03/27/01 - code was inactivated due to CLG will not have a rotating barrel +// if ( ( item->giTag == WP_LIGHTNING ) && ( item->giType == IT_WEAPON ) ) +// itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); + + + itemInfo->icon = trap_R_RegisterShader( item->icon ); + + if ( item->giType == IT_WEAPON ) { + CG_RegisterWeapon( item->giTag ); + } + + // + // powerups have an accompanying ring or sphere + // + if ( item->giType == IT_POWERUP || item->giType == IT_HEALTH || + item->giType == IT_ARMOR || item->giType == IT_HOLDABLE ) { + if ( item->world_model[1] ) { + itemInfo->models[1] = trap_R_RegisterModel( item->world_model[1] ); + } + } +} + + +/* +======================================================================================== + +VIEW WEAPON + +======================================================================================== +*/ + +/* +================= +CG_MapTorsoToWeaponFrame + +================= +*/ +static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame ) { + + // change weapon + if ( frame >= ci->animations[TORSO_DROP].firstFrame + && frame < ci->animations[TORSO_DROP].firstFrame + 9 ) { + return frame - ci->animations[TORSO_DROP].firstFrame + 6; + } + + // stand attack + if ( frame >= ci->animations[TORSO_ATTACK].firstFrame + && frame < ci->animations[TORSO_ATTACK].firstFrame + 6 ) { + return 1 + frame - ci->animations[TORSO_ATTACK].firstFrame; + } + + // stand attack 2 + if ( frame >= ci->animations[TORSO_ATTACK2].firstFrame + && frame < ci->animations[TORSO_ATTACK2].firstFrame + 6 ) { + return 1 + frame - ci->animations[TORSO_ATTACK2].firstFrame; + } + + return 0; +} + + +/* +============== +CG_CalculateWeaponPosition +============== +*/ +static void CG_CalculateWeaponPosition( vec3_t origin, vec3_t angles ) { + float scale; + int delta; + float fracsin; + + VectorCopy( cg.refdef.vieworg, origin ); + VectorCopy( cg.refdefViewAngles, angles ); + + // on odd legs, invert some angles + if ( cg.bobcycle & 1 ) { + scale = -cg.xyspeed; + } else { + scale = cg.xyspeed; + } + + // gun angles from bobbing + angles[ROLL] += scale * cg.bobfracsin * 0.005; + angles[YAW] += scale * cg.bobfracsin * 0.01; + angles[PITCH] += cg.xyspeed * cg.bobfracsin * 0.005; + + // drop the weapon when landing + delta = cg.time - cg.landTime; + if ( delta < LAND_DEFLECT_TIME ) { + origin[2] += cg.landChange*0.25 * delta / LAND_DEFLECT_TIME; + } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) { + origin[2] += cg.landChange*0.25 * + (LAND_DEFLECT_TIME + LAND_RETURN_TIME - delta) / LAND_RETURN_TIME; + } + +#if 0 + // drop the weapon when stair climbing + delta = cg.time - cg.stepTime; + if ( delta < STEP_TIME/2 ) { + origin[2] -= cg.stepChange*0.25 * delta / (STEP_TIME/2); + } else if ( delta < STEP_TIME ) { + origin[2] -= cg.stepChange*0.25 * (STEP_TIME - delta) / (STEP_TIME/2); + } +#endif + + // idle drift + scale = cg.xyspeed + 40; + fracsin = sin( cg.time * 0.001 ); + angles[ROLL] += scale * fracsin * 0.01; + angles[YAW] += scale * fracsin * 0.01; + angles[PITCH] += scale * fracsin * 0.01; +} + + +/* +=============== +CG_LightningBolt + +Origin will be the exact tag point, which is slightly +different than the muzzle point used for determining hits. +The cent should be the non-predicted cent if it is from the player, +so the endpoint will reflect the simulated strike (lagging the predicted +angle) +=============== +*/ +static void CG_LightningBolt( centity_t *cent, vec3_t origin ) { + trace_t trace; + refEntity_t beam; + vec3_t forward; + vec3_t muzzlePoint, endPoint; +// vec3_t holdv; + int shaftee; //12/15/03 hold number of player being directly shafted + qboolean straightbeam = qtrue; + + //PKMOD - Ergodic 07/06/01 - debug (inactive) +// Com_Printf("CG_LightningBolt\n"); + + if ( cent->currentState.weapon != WP_LIGHTNING ) { + //PKMOD - Ergodic 12/06/00 - debug inactive +// Com_Printf("CG_LightningBolt - nop exit\n"); + return; + } + + //PKMOD - Ergodic 07/06/01 - debug lightning strike (inactive) +// if ( cent->currentState.eFlags & EF_LIGHTNINGSTRIKE ) +// Com_Printf("CG_LightningBolt - EF_LIGHTNINGSTRIKE is set\n"); +// else +// Com_Printf("CG_LightningBolt - EF_LIGHTNINGSTRIKE inactive\n"); + + memset( &beam, 0, sizeof( beam ) ); + + //PKMOD - Ergodic 07/08/01 - only display shaft if NOT striking player + //PKMOD - Ergodic 12/15/03 - change logic to use location of CLG struck player + //if ( cent->currentState.eFlags & EF_LIGHTNINGSTRIKE ) + // return; + if ( cent->currentState.eFlags & EF_LIGHTNINGSTRIKE ) { + shaftee = cent->currentState.otherEntityNum2; + //minimally validate client + if ( shaftee >= 0 && shaftee < MAX_CLIENTS ) { + centity_t *target; + + target = &cg_entities[ shaftee ]; + + //PKMOD - Ergodic 12/15/03 - debug lightning strike (inactive) + //Com_Printf("CG_LightningBolt - inside valid client, shaftee>%d<, org>%s<, targ>%s<\n", shaftee, CG_vtos( origin), CG_vtos(target->lerpOrigin) ); + + VectorCopy( target->lerpOrigin, beam.oldorigin ); + beam.oldorigin[2] += DEFAULT_VIEWHEIGHT; //raise the impact point up + + VectorCopy( origin, beam.origin ); //CLG Muzzle Tag location + + straightbeam = qfalse; + + } + } + + if ( straightbeam ) { //here if we are not using lockon shaft beam + // CPMA "true" lightning + if ((cent->currentState.number == cg.predictedPlayerState.clientNum) && (cg_trueLightning.value != 0)) { + vec3_t angle; + int i; + + for (i = 0; i < 3; i++) { + float a = cent->lerpAngles[i] - cg.refdefViewAngles[i]; + if (a > 180) { + a -= 360; + } + if (a < -180) { + a += 360; + } + + angle[i] = cg.refdefViewAngles[i] + a * (1.0 - cg_trueLightning.value); + if (angle[i] < 0) { + angle[i] += 360; + } + if (angle[i] > 360) { + angle[i] -= 360; + } + } + + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors(angle, forward, NULL, NULL ); + AngleVectorsForward( angle, forward ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); +// VectorCopy(cg.refdef.vieworg, muzzlePoint ); + } else { + // !CPMA + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + AngleVectorsForward( cent->lerpAngles, forward ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); + } + + //PKMOD - Ergodic 09/10/00 - remove original aiming vector aiming + //PKMOD - Ergodic 09/20/00 - reset original aiming vector aiming + //PKMOD - Ergodic 07/07/01 - if player strike then set the beam aim, co-opt field: angles2 +// VectorCopy( cent->currentState.angles2, forward ); + //PKMOD - Ergodic 07/07/01 - debug angles2 +// Com_Printf("CG_LightningBolt: angles2>%s<\n", vtos( cent->currentState.angles2 ) ); + //PKMOD - Ergodic 07/08/01 - reset original aiming vector aiming + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + AngleVectorsForward( cent->lerpAngles, forward ); + + //PKMOD - Ergodic 09/10/00 - set the aim, co-opt field: apos.trDelta + // AngleVectors( cent->currentState.apos.trDelta, cent->currentState.apos.trDelta forward, NULL, NULL ); + //PKMOD - Ergodic 09/20/00 - undo apos aiming +// VectorCopy( cent->currentState.apos.trDelta, holdv ); + + + //PKMOD - Ergodic 09/20/00 - undo apos aiming +// VectorCopy( cent->currentState.apos.trDelta, forward ); + + // FIXME: crouch + muzzlePoint[2] += DEFAULT_VIEWHEIGHT; + + //PKMOD - Ergodic 01/16/01 - don't draw shaft if doing a Lightning Discharge in water + if (trap_CM_PointContents (muzzlePoint, 0) & MASK_WATER) + return; + + VectorMA( muzzlePoint, 14, forward, muzzlePoint ); + + // project forward by the lightning range + VectorMA( muzzlePoint, LIGHTNING_RANGE, forward, endPoint ); + + // see if it hit a wall + CG_Trace( &trace, muzzlePoint, vec3_origin, vec3_origin, endPoint, + cent->currentState.number, MASK_SHOT ); + + // this is the endpoint + VectorCopy( trace.endpos, beam.oldorigin ); + + // use the provided origin, even though it may be slightly + // different than the muzzle origin + VectorCopy( origin, beam.origin ); + } + +//PKMOD - Ergodic 08/21/00 change flash to railgun style to make a tighter beam + beam.reType = RT_RAIL_CORE; +// beam.reType = RT_LIGHTNING; +//PKMOD - Ergodic 08/21/00 set the shader to chainlightning + beam.customShader = cgs.media.chainlightningShader; + trap_R_AddRefEntityToScene( &beam ); + + if ( straightbeam ) { //here if we are not using lockon shaft beam + // add the impact flare if it hit something + if ( trace.fraction < 1.0 ) { + vec3_t angles; + vec3_t dir; + + VectorSubtract( beam.oldorigin, beam.origin, dir ); + VectorNormalize( dir ); + + memset( &beam, 0, sizeof( beam ) ); + beam.hModel = cgs.media.lightningExplosionModel; + + VectorMA( trace.endpos, -16, dir, beam.origin ); + + // make a random orientation + angles[0] = rand() % 360; + angles[1] = rand() % 360; + angles[2] = rand() % 360; + AnglesToAxis( angles, beam.axis ); + trap_R_AddRefEntityToScene( &beam ); + } + } +} + + +/* +=============== +CG_SpawnRailTrail + +Origin will be the exact tag point, which is slightly +different than the muzzle point used for determining hits. +=============== +*/ +static void CG_SpawnRailTrail( centity_t *cent, vec3_t origin ) { + clientInfo_t *ci; + + if ( cent->currentState.weapon != WP_RAILGUN ) { + return; + } + if ( !cent->pe.railgunFlash ) { + return; + } + cent->pe.railgunFlash = qtrue; + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + CG_RailTrail( ci, origin, cent->pe.railgunImpact ); +} + + +/* +====================== +CG_MachinegunSpinAngle +====================== +*/ +#define SPIN_SPEED 0.9 +#define COAST_TIME 1000 +static float CG_MachinegunSpinAngle( centity_t *cent ) { + int delta; + float angle; + float speed; + + delta = cg.time - cent->pe.barrelTime; + if ( cent->pe.barrelSpinning ) { + angle = cent->pe.barrelAngle + delta * SPIN_SPEED; + } else { + if ( delta > COAST_TIME ) { + delta = COAST_TIME; + } + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = cent->pe.barrelAngle + delta * speed; + } + + //PKMOD - Ergodic 12/19/01 - aways spin the gauntlet on the dragon + if ( cent->pe.barrelSpinning == !(cent->currentState.eFlags & EF_FIRING) ) { + cent->pe.barrelTime = cg.time; + cent->pe.barrelAngle = AngleMod( angle ); + cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); +#ifdef MISSIONPACK + if ( cent->currentState.weapon == WP_CHAINGUN && !cent->pe.barrelSpinning ) { + trap_S_StartSound( NULL, cent->currentState.number, CHAN_WEAPON, trap_S_RegisterSound( "sound/weapons/vulcan/wvulwind.wav", qfalse ) ); + } +#endif + } + + return angle; +} + + +/* +====================== +CG_MachinegunSpinAngle + +PKMOD - Ergodic 12/19/01 - aways spin the gauntlet blade on the dragon + ====================== +*/ +static float CG_GauntletSpinAngle( centity_t *cent ) { + int delta; + float angle; + float speed; + + delta = cg.time - cent->pe.barrelTime; + if ( cent->pe.barrelSpinning ) { + angle = cent->pe.barrelAngle + delta * SPIN_SPEED; + } else { + if ( delta > COAST_TIME ) { + delta = COAST_TIME; + } + + speed = 0.5 * ( SPIN_SPEED + (float)( COAST_TIME - delta ) / COAST_TIME ); + angle = cent->pe.barrelAngle + delta * speed; + } + + cent->pe.barrelTime = cg.time; + cent->pe.barrelAngle = AngleMod( angle ); + cent->pe.barrelSpinning = !!(cent->currentState.eFlags & EF_FIRING); + + return angle; +} + + +/* +======================== +CG_AddWeaponWithPowerups +======================== +*/ +static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { + // add powerup effects + if ( powerups & ( 1 << PW_INVIS ) ) { + gun->customShader = cgs.media.invisShader; + trap_R_AddRefEntityToScene( gun ); + } else { + trap_R_AddRefEntityToScene( gun ); + + if ( powerups & ( 1 << PW_BATTLESUIT ) ) { + gun->customShader = cgs.media.battleWeaponShader; + trap_R_AddRefEntityToScene( gun ); + } + if ( powerups & ( 1 << PW_QUAD ) ) { + gun->customShader = cgs.media.quadWeaponShader; + trap_R_AddRefEntityToScene( gun ); + } + } +} + + +/* +============= +CG_AddPlayerWeapon + +Used for both the view weapon (ps is valid) and the world modelother character models (ps is NULL) +The main player will have this called for BOTH cases, so effects like light and +sound should only be done on the world model case. +============= +*/ +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team ) { + refEntity_t gun; + refEntity_t barrel; + refEntity_t flash; + vec3_t angles; + weapon_t weaponNum; + weaponInfo_t *weapon; + centity_t *nonPredictedCent; +// int col; + + weaponNum = cent->currentState.weapon; + + //PKMOD - Ergodic 07/05/01 - debug (inactive) +// if ( rand() % 100 > 97 ) +// Com_Printf( "CG_AddPlayerWeapon - generic1:%d\n", cent->currentState.generic1 ); + + CG_RegisterWeapon( weaponNum ); + weapon = &cg_weapons[weaponNum]; + + // add the weapon + memset( &gun, 0, sizeof( gun ) ); + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); + gun.shadowPlane = parent->shadowPlane; + gun.renderfx = parent->renderfx; + + // set custom shading for railgun refire rate + if ( ps ) { + if ( cg.predictedPlayerState.weapon == WP_RAILGUN + && cg.predictedPlayerState.weaponstate == WEAPON_FIRING ) { + float f; + + f = (float)cg.predictedPlayerState.weaponTime / 1500; + gun.shaderRGBA[1] = 0; + gun.shaderRGBA[0] = + gun.shaderRGBA[2] = 255 * ( 1.0 - f ); + } else { + gun.shaderRGBA[0] = 255; + gun.shaderRGBA[1] = 255; + gun.shaderRGBA[2] = 255; + gun.shaderRGBA[3] = 255; + } + } + + gun.hModel = weapon->weaponModel; + if (!gun.hModel) { + return; + } + + if ( !ps ) { + // add weapon ready sound + cent->pe.lightningFiring = qfalse; + if ( ( cent->currentState.eFlags & EF_FIRING ) && weapon->firingSound ) { + // lightning gun and guantlet make a different sound when fire is held down + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->firingSound ); + cent->pe.lightningFiring = qtrue; + } else if ( weapon->readySound ) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, weapon->readySound ); + } + } + + CG_PositionEntityOnTag( &gun, parent, parent->hModel, "tag_weapon"); + + CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); + + //PKMOD - Ergodic 03/01/01 - Dragon alternate fire + //PKMOD - Ergodic 03/02/01 - modify logic to be based on weaponNum and not ammo + //PKMOD - Ergodic 03/13/01 - if current weapon is not the dragon then don't use the alternate weapon + if ( weaponNum == WP_GRAPPLING_HOOK ) { + //PKMOD - Ergodic 07/05/01 - use cent and not cg.snap so deploy weapon will appear + // on multiplayer entities + if ( cent->currentState.generic1 ) { + refEntity_t gun2; + weaponInfo_t *weapon2; + weapon_t deployNum; + + //PKMOD - Ergodic 03/14/01 - mask off the weapon bits (0 - 15) + //deployNum = cg.snap->ps.generic1 & 15; + //PKMOD - Ergodic 07/05/01 - use "ps" and not "cg" so deploy weapon + // will appear on multiplayer models and not just local client + deployNum = cent->currentState.generic1 & 15; + + // add the weapon + memset( &gun2, 0, sizeof( gun2 ) ); + VectorCopy( parent->lightingOrigin, gun2.lightingOrigin ); + gun2.shadowPlane = parent->shadowPlane; + gun2.renderfx = parent->renderfx; + + //PKMOD - Ergodic 12/15/01 - debug deploy gauntlet (inactive) +// Com_Printf( "CG_AddPlayerWeapon - deploying>%d<\n", deployNum ); + + + //PKMOD - Ergodic 08/29/01 - move code to include Flag as deploy weapon + //set the deploy model + if ( ( deployNum == PW_REDFLAG ) || ( deployNum == PW_BLUEFLAG ) ) { + if ( deployNum == PW_REDFLAG ) + gun2.hModel = cgs.media.redFlagModel; + else + gun2.hModel = cgs.media.blueFlagModel; + } + else { //NOT A FLAG: deploy a PA WEAPON + CG_RegisterWeapon( WP_GRAPPLING_HOOK ); + weapon2 = &cg_weapons[ deployNum ]; + //PKMOD - Ergodic 12/16/01 - show the barrel if depoying the gauntlet + if ( deployNum == WP_GAUNTLET ) { + //PKMOD - Ergodic 12/16/01 - select the barrel model + gun2.hModel = weapon2->barrelModel; + //PKMOD - Ergodic 12/16/01 - make gauntlet blade rotate + angles[YAW] = 0; // was CG_MachinegunSpinAngle( cent ); + angles[PITCH] = 0; //was -90 + angles[ROLL] = CG_GauntletSpinAngle( cent ); + AnglesToAxis( angles, gun2.axis ); + } + else + gun2.hModel = weapon2->weaponModel; + } + + if (!gun2.hModel) { + return; + } + //PKMOD - Ergodic 03/01/01 - debug inactive + // Com_Printf( "CG_AddPlayerWeapon - attaching beartrap to dragon\n" ); + + //PKMOD - Ergodic 12/16/01 - rotate the gauntlet_blade + if ( deployNum == WP_GAUNTLET ) { + //PKMOD - Ergodic 01/26/02 - only display gauntlet if NOT firing + if ( !( cent->currentState.eFlags & EF_FIRING ) ) { + CG_PositionRotatedEntityOnTag( &gun2, &gun, gun.hModel, "tag_gauntlet" ); + CG_AddWeaponWithPowerups( &gun2, cent->currentState.powerups ); + } + } + else { + CG_PositionEntityOnTag( &gun2, &gun, gun.hModel, "tag_tongue" ); + CG_AddWeaponWithPowerups( &gun2, cent->currentState.powerups ); + } + } + } + + + // add the spinning barrel + if ( weapon->barrelModel ) { + memset( &barrel, 0, sizeof( barrel ) ); + VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = parent->shadowPlane; + barrel.renderfx = parent->renderfx; + + barrel.hModel = weapon->barrelModel; + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = CG_MachinegunSpinAngle( cent ); + AnglesToAxis( angles, barrel.axis ); + + CG_PositionRotatedEntityOnTag( &barrel, &gun, weapon->weaponModel, "tag_barrel" ); + + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); + } + + // make sure we aren't looking at cg.predictedPlayerEntity for LG + nonPredictedCent = &cg_entities[cent->currentState.clientNum]; + + // if the index of the nonPredictedCent is not the same as the clientNum + // then this is a fake player (like on teh single player podiums), so + // go ahead and use the cent + if( ( nonPredictedCent - cg_entities ) != cent->currentState.clientNum ) { + nonPredictedCent = cent; + } + + // add the flash + if ( ( weaponNum == WP_LIGHTNING || weaponNum == WP_GAUNTLET || weaponNum == WP_GRAPPLING_HOOK ) + && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) + { + // continuous flash + } else { + // impulse flash + if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME && !cent->pe.railgunFlash ) { + return; + } + } + + memset( &flash, 0, sizeof( flash ) ); + VectorCopy( parent->lightingOrigin, flash.lightingOrigin ); + flash.shadowPlane = parent->shadowPlane; + flash.renderfx = parent->renderfx; + + flash.hModel = weapon->flashModel; + if (!flash.hModel) { + return; + } + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = crandom() * 10; + AnglesToAxis( angles, flash.axis ); + + // colorize the railgun blast + if ( weaponNum == WP_RAILGUN ) { + clientInfo_t *ci; + + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + flash.shaderRGBA[0] = 255 * ci->color1[0]; + flash.shaderRGBA[1] = 255 * ci->color1[1]; + flash.shaderRGBA[2] = 255 * ci->color1[2]; + } + + CG_PositionRotatedEntityOnTag( &flash, &gun, weapon->weaponModel, "tag_flash"); + trap_R_AddRefEntityToScene( &flash ); + + if ( ps || cg.renderingThirdPerson || + cent->currentState.number != cg.predictedPlayerState.clientNum ) { + + // add lightning bolt + CG_LightningBolt( nonPredictedCent, flash.origin ); + + // add rail trail + CG_SpawnRailTrail( cent, flash.origin ); + + if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { + trap_R_AddLightToScene( flash.origin, 300 + (rand()&31), weapon->flashDlightColor[0], + weapon->flashDlightColor[1], weapon->flashDlightColor[2] ); + } + } +} + +/* +============== +CG_AddViewWeapon + +Add the weapon, and flash for the player's view +============== +*/ +void CG_AddViewWeapon( playerState_t *ps ) { + refEntity_t hand; + centity_t *cent; + clientInfo_t *ci; + float fovOffset; + vec3_t angles; + weaponInfo_t *weapon; + + if ( ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + if ( ps->pm_type == PM_INTERMISSION ) { + return; + } + + // no gun if in third person view or a camera is active + //if ( cg.renderingThirdPerson || cg.cameraMode) { + if ( cg.renderingThirdPerson ) { + return; + } + + + // allow the gun to be completely removed + if ( !cg_drawGun.integer ) { + vec3_t origin; + + if ( cg.predictedPlayerState.eFlags & EF_FIRING ) { + // special hack for lightning gun... + VectorCopy( cg.refdef.vieworg, origin ); + VectorMA( origin, -8, cg.refdef.viewaxis[2], origin ); + CG_LightningBolt( &cg_entities[ps->clientNum], origin ); + } + return; + } + + // don't draw if testing a gun model + if ( cg.testGun ) { + return; + } + + // drop gun lower at higher fov + if ( cg_fov.integer > 90 ) { + fovOffset = -0.2 * ( cg_fov.integer - 90 ); + } else { + fovOffset = 0; + } + + cent = &cg.predictedPlayerEntity; // &cg_entities[cg.snap->ps.clientNum]; + CG_RegisterWeapon( ps->weapon ); + weapon = &cg_weapons[ ps->weapon ]; + + memset (&hand, 0, sizeof(hand)); + + // set up gun position + CG_CalculateWeaponPosition( hand.origin, angles ); + + VectorMA( hand.origin, cg_gun_x.value, cg.refdef.viewaxis[0], hand.origin ); + VectorMA( hand.origin, cg_gun_y.value, cg.refdef.viewaxis[1], hand.origin ); + VectorMA( hand.origin, (cg_gun_z.value+fovOffset), cg.refdef.viewaxis[2], hand.origin ); + + AnglesToAxis( angles, hand.axis ); + + // map torso animations to weapon animations + if ( cg_gun_frame.integer ) { + // development tool + hand.frame = hand.oldframe = cg_gun_frame.integer; + hand.backlerp = 0; + } else { + // get clientinfo for animation map + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame ); + hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame ); + hand.backlerp = cent->pe.torso.backlerp; + } + + hand.hModel = weapon->handsModel; + hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON | RF_MINLIGHT; + + // add everything onto the hand + CG_AddPlayerWeapon( &hand, ps, &cg.predictedPlayerEntity, ps->persistant[PERS_TEAM] ); +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ + +/* +=================== +CG_DrawWeaponSelect +=================== +*/ +void CG_DrawWeaponSelect( void ) { + int i; + int bits; + int count; + int x, y, w; + char *name; + float *color; + + // don't display if dead + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + return; + } + + color = CG_FadeColor( cg.weaponSelectTime, WEAPON_SELECT_TIME ); + if ( !color ) { + return; + } + trap_R_SetColor( color ); + + // showing weapon select clears pickup item display, but not the blend blob + cg.itemPickupTime = 0; + + // count the number of weapons owned + bits = cg.snap->ps.stats[ STAT_WEAPONS ]; + count = 0; + for ( i = 1 ; i < 16 ; i++ ) { + if ( bits & ( 1 << i ) ) { + count++; + } + } + + x = 320 - count * 20; + y = 380; + + for ( i = 1 ; i < 16 ; i++ ) { + if ( !( bits & ( 1 << i ) ) ) { + continue; + } + + CG_RegisterWeapon( i ); + + // draw weapon icon + CG_DrawPic( x, y, 32, 32, cg_weapons[i].weaponIcon ); + + // draw selection marker + if ( i == cg.weaponSelect ) { + CG_DrawPic( x-4, y-4, 40, 40, cgs.media.selectShader ); + } + + // no ammo cross on top + if ( !cg.snap->ps.ammo[ i ] ) { + CG_DrawPic( x, y, 32, 32, cgs.media.noammoShader ); + } + + x += 40; + } + + // draw the selected name + if ( cg_weapons[ cg.weaponSelect ].item ) { + name = cg_weapons[ cg.weaponSelect ].item->pickup_name; + if ( name ) { + w = CG_DrawStrlen( name ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + CG_DrawBigStringColor(x, y - 22, name, color); + } + } + + trap_R_SetColor( NULL ); +} + + +/* +=============== +CG_WeaponSelectable +=============== +*/ +static qboolean CG_WeaponSelectable( int i ) { + //PKMOD - Ergodic 06/14/00 handle weapon WP_SHOTGUN, shotgun/exploding shells weapon selection + if (i == WP_SHOTGUN) { + if ( (!cg.snap->ps.ammo[WP_SHOTGUN]) && (!cg.snap->ps.ammo[WP_EXPLODING_SHELLS]) ) { + return qfalse; + } + } +//PKMOD - Ergodic 05/18/01 - Airfist has displayable ammo corresponding to airfist_level +// airfist may have "useable" zero ammo + else if ( ( !cg.snap->ps.ammo[i] ) && ( i != WP_AIRFIST ) ) { + return qfalse; + } + + if ( ! (cg.snap->ps.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { + return qfalse; + } + + return qtrue; +} + +/* +=============== +CG_NextWeapon_f +=============== +*/ +void CG_NextWeapon_f( void ) { + int i; + int original; + //PKMOD - Ergodic 08/16/03 - PKA full weapon cycling + int max_weapon; + + //PKMOD - Ergodic 08/16/03 - PKA full weapon cycling + if ( cg_pkafullweaponcycling.integer ) + max_weapon = WP_EXPLODING_SHELLS - 1; + else + max_weapon = WP_GRAPPLING_HOOK; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + +//PMMOD - Ergodic 06/10/00 For this type of cycling to work +// must follow weapon_t in bg_public.h for special +// PK Weapons. Assume grapple is the highest regular weapon. + for ( i = 0 ; i < WP_NUM_WEAPONS; i++ ) { + if (cg.weaponSelect == WP_EXPLODING_SHELLS) { + cg.weaponSelect = WP_SHOTGUN + 1; + } + else { + cg.weaponSelect++; + //PKMOD - Ergodic 08/16/03 - PKA full weapon cycling + // if ( cg.weaponSelect > WP_GRAPPLING_HOOK ) { + if ( cg.weaponSelect > max_weapon ) { + cg.weaponSelect = 0; + } + } + //PKMOD - Ergodic 08/18/03 - don't cycle to Gauntlet using "normal" weapon cycling + if ( cg_pkafullweaponcycling.integer != 1 ) + if ( cg.weaponSelect == WP_GAUNTLET ) { + continue; // never cycle to gauntlet + } + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + //PKMOD - Ergodic 06/14/00 handle weapon WP_SHOTGUN, shotgun/exploding shells weapon selection + if (cg.weaponSelect == WP_SHOTGUN) { + if ( cg.snap->ps.ammo[WP_EXPLODING_SHELLS] ) { + cg.weaponSelect = WP_EXPLODING_SHELLS; + } + + } //end if (cg.weaponSelect==WP_SHOTGUN), weapon WP_SHOTGUN selection + + break; + } + } + + //PKMOD - Ergodic 04/03/01 - code to fix selecting empty ammo weapon + if ( i == WP_NUM_WEAPONS ) { + cg.weaponSelect = original; + } + +} + +/* +=============== +CG_PrevWeapon_f +=============== +*/ +void CG_PrevWeapon_f( void ) { + int i; + int original; + //PKMOD - Ergodic 08/18/03 - PKA full weapon cycling + int max_weapon; + + //PKMOD - Ergodic 08/18/03 - PKA full weapon cycling + if ( cg_pkafullweaponcycling.integer ) + max_weapon = WP_EXPLODING_SHELLS - 1; + else + max_weapon = WP_GRAPPLING_HOOK; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + +//PMMOD - Ergodic 06/10/00 For this type of cycling to work +// must follow weapon_t in bg_public.h for special +// PK Weapons. Assume grapple is the highest regular weapon. + for ( i = 0 ; i < WP_NUM_WEAPONS; i++ ) { + if (cg.weaponSelect == WP_EXPLODING_SHELLS) { + cg.weaponSelect = WP_SHOTGUN - 1; + } + else { + cg.weaponSelect--; + //PMMOD - Ergodic 07/26/01 - fix pka hold cycling + //PKMOD - Ergodic 08/18/03 - PKA full weapon cycling + //if ( cg.weaponSelect > WP_GRAPPLING_HOOK ) { + // cg.weaponSelect = WP_GRAPPLING_HOOK; + //} + //else if ( cg.weaponSelect < 0 ) { + // cg.weaponSelect = WP_GRAPPLING_HOOK; + //} + if ( cg.weaponSelect > max_weapon ) { + cg.weaponSelect = max_weapon; + } + else if ( cg.weaponSelect < 0 ) { + cg.weaponSelect = max_weapon; + } + } + + //PKMOD - Ergodic 08/18/03 - don't cycle to Gauntlet using "normal" weapon cycling + if ( cg_pkafullweaponcycling.integer != 1 ) + if ( cg.weaponSelect == WP_GAUNTLET ) { + continue; // never cycle to gauntlet + } + + //PKMOD - Ergodic 04/03/01 - debug "BT & MG bug" (inactive) +// Com_Printf("CG_PrevWeapon_f - cg.weaponSelect>%d<, i>%d<\n", cg.weaponSelect, i ); + + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + //PKMOD - Ergodic 06/14/00 handle weapon WP_SHOTGUN, shotgun/exploding shells weapon selection + if (cg.weaponSelect == WP_SHOTGUN) { + if ( cg.snap->ps.ammo[WP_EXPLODING_SHELLS] ) { + cg.weaponSelect = WP_EXPLODING_SHELLS; + } + + } //end if (cg.weaponSelect==WP_SHOTGUN), weapon WP_SHOTGUN selection + + break; + } + } + + //PKMOD - Ergodic 04/03/01 - code to fix (BT & MG bug) +// if ( i == WP_GRAPPLING_HOOK ) { + if ( i == WP_NUM_WEAPONS ) { + cg.weaponSelect = original; + } +} + +/* +=============== +CG_Weapon_f +=============== +*/ +void CG_Weapon_f( void ) { + int num; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + num = atoi( CG_Argv( 1 ) ); + + if ( num < 1 || num > 15 ) { + return; + } + + cg.weaponSelectTime = cg.time; + + //PKMOD - Ergodic 03/12/02 - check for auto-deploy + if ( cg.weaponSelect == num ) { + //PKMOD - Ergodic 10/23/02 - add reset dragon after holding dragon deploy and press "weapon 10" + if ( (( num >= WP_GRAVITY ) && ( num <= WP_BEANS )) || ( num == WP_GAUNTLET ) || ( num == WP_GRAPPLING_HOOK ) ) + CG_Weapon_DragonDeploy(); + return; + } + + //PKMOD - Ergodic 06/09/00 handle weapon 1 pka weapons cycling + //PKMOD - Ergodic 03/12/02 - turn wepon 1 cycling off + /*---------------begin: weapon 1 cycling ---------------- + if (num == 1) { + original = cg.weaponSelect; + cg.weaponSelect = 1; +//PMMOD - Ergodic 06/09/00 For this type of cycling to work +// must follow weapon_t in bg_public.h for special +// PK Weapons. This code will cycle from Gauntlet, +// Gravity Well, AutoSentry, BearTrap, and Beans. + if ( (original == 1) || ((original >= WP_GRAVITY) && (original <= WP_BEANS)) ) { + if ( original == 1 ) { + original = WP_GRAVITY - 1; + } + + for ( i = (original + 1) ; i <= WP_BEANS ; i++ ) { + if ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << i) ) { + cg.weaponSelect = i; // found the next weapon 1 selection + break; + } + } + } + + return; // always return from here on weapon 1 selection + } //end if (num==1), weapon 1 cycling + ---------------end: weapon 1 cycling ----------------*/ + + //PKMOD - Ergodic 06/14/00 handle weapon WP_SHOTGUN, shotgun/exploding shells weapon selection + if (num == WP_SHOTGUN) { + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { + return; // don't have the shotgun weapon + } + //we have the shotgun + if ( !cg.snap->ps.ammo[WP_EXPLODING_SHELLS] ) { + cg.weaponSelect = num; + } + else { + cg.weaponSelect = WP_EXPLODING_SHELLS; + } + + return; // always return from here on weapon WP_SHOTGUN selection + } //end if (num==WP_SHOTGUN), weapon WP_SHOTGUN selection + + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) { + return; // don't have the weapon + } + + //PKMOD - Ergodic 06/28/01 - turn off dragon deploy if enabled and selecting dragon + if ( ( num == WP_GRAPPLING_HOOK ) && ( ( cg.snap->ps.generic1 & 15 ) > 0 ) ) { + char command[128]; + + Com_sprintf( command, 128, "gdeploy 0" ); + trap_SendClientCommand( command ); + } + + cg.weaponSelect = num; +} + +/* +=================== +CG_OutOfAmmoChange + +The current weapon has just run out of ammo +=================== +*/ +void CG_OutOfAmmoChange( void ) { + int i; + + cg.weaponSelectTime = cg.time; + + //PKMOD - Ergodic 07/06/00 handle case of out exploding shells ammo + // test wp_shotgun for selectablility first + if ( cg.weaponSelect == WP_EXPLODING_SHELLS ) { + if ( CG_WeaponSelectable( WP_SHOTGUN ) ) { + cg.weaponSelect = WP_SHOTGUN; + return; + } + } + +//PKMOD - Ergodic 06/10/00 For this type of cycling to work +// must follow weapon_t in bg_public.h for special +// PK Weapons. Assume grapple is the highest regular weapon. + for ( i = WP_GRAPPLING_HOOK - 1; i > 0 ; i-- ) { + if ( CG_WeaponSelectable( i ) ) { + cg.weaponSelect = i; + break; + } + } +} + +//PKMOD - Ergodic 07/02/00 weapon commands +/* +=============== +CG_CMD_Weapon_Select - select the specified weapon +=============== +*/ +void CG_CMD_Weapon_Select( int weapon_num ) { + //PKMOD - Ergodic 04/04/01 - save last weapon (04/05/01 - code done in bg_pmove) +// int original_weapon; + + //PKMOD - Ergodic 07/25/01 - debug airfist select (inactive) +// Com_Printf( "CG_CMD_Weapon_Select - weapon_num:%d, stat:%d ammo:%d\n", weapon_num, cg.snap->ps.stats[STAT_WEAPONS], cg.snap->ps.stats[STAT_WEAPONS], cg.snap->ps.ammo[weapon_num] ); + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + +//PKMOD - Ergodic 12/25/00 move to later down in the function +// cg.weaponSelectTime = cg.time; + + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << weapon_num ) ) ) { + return; // don't have the weapon + } + + //PKMOD - Ergodic 06/28/01 - debug (inactive) +// Com_Printf( "CG_CMD_Weapon_Select - weapon_num:%d, ps.generic1:%d, predictedPlayerState.generic1:%d\n", weapon_num, cg.snap->ps.generic1, cg.predictedPlayerState.generic1 ); + + //PKMOD - Ergodic 06/28/01 - turn off dragon deploy if enabled and selecting dragon + if ( ( weapon_num == WP_GRAPPLING_HOOK ) && ( ( cg.snap->ps.generic1 & 15 ) > 0 ) ) { + char command[128]; + + Com_sprintf( command, 128, "gdeploy 0" ); + trap_SendClientCommand( command ); + cg.weaponSelectTime = cg.time; + cg.weaponSelect = weapon_num; + return; + } + + //PKMOD - Ergodic 04/04/01 - save last weapon (04/05/01 - code done in bg_pmove) +// original_weapon = cg.weaponSelect; + + //PKMOD - Ergodic 12/25/00 add logic to not switch if out of ammo + //PKMOD - Ergodic 06/14/00 handle weapon WP_SHOTGUN, shotgun/exploding shells weapon selection + if (weapon_num == WP_SHOTGUN) { + //do we have exploding shells? + if ( cg.snap->ps.ammo[WP_EXPLODING_SHELLS] > 0 ) { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = WP_EXPLODING_SHELLS; + + //PKMOD - Ergodic 04/04/01 - save last weapon if weapon number has changed + //PKMOD - Ergodic 04/05/01 - code done in bg_pmove +// if ( original_weapon != cg.weaponSelect ) +// cg.weaponLast = original_weapon; + + return; + } + //do we have regular shells? + if ( cg.snap->ps.ammo[WP_SHOTGUN] > 0 ) { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = WP_SHOTGUN; + + //PKMOD - Ergodic 04/04/01 - save last weapon if weapon number has changed + //PKMOD - Ergodic 04/05/01 - code done in bg_pmove +// if ( original_weapon != cg.weaponSelect ) +// cg.weaponLast = original_weapon; + } + + } //end if (num==WP_SHOTGUN), weapon WP_SHOTGUN selection + else { + //all other weapons +//PKMOD - Ergodic 05/18/01 - Airfist has displayable ammo corresponding to airfist_level +// airfist may have "useable" zero ammo +//PKMOD - Ergodic 07/25/01 - fix airfist selection +// if ( ( cg.snap->ps.ammo[weapon_num] != 0 ) && ( weapon_num != WP_AIRFIST ) ) { + if ( ( cg.snap->ps.ammo[weapon_num] != 0 ) || ( weapon_num == WP_AIRFIST ) ) { + cg.weaponSelectTime = cg.time; + cg.weaponSelect = weapon_num; + + //PKMOD - Ergodic 04/04/01 - save last weapon if weapon number has changed + //PKMOD - Ergodic 04/05/01 - code done in bg_pmove +// if ( original_weapon != cg.weaponSelect ) +// cg.weaponLast = original_weapon; + } + } +} + +/* +=============== +CG_LastWeapon_f - select the last wepon held +PKMOD - Ergodic 04/04/01 +=============== +*/ +void CG_LastWeapon_f( void ) { + + //PKMOD - Ergodic 04/04/01 - debug (inactive) +// Com_Printf( "CG_LastWeapon_f - current>%d<, last>%d<\n", cg.weaponSelect, cg.snap->ps.stats[ STAT_LAST_WEAPON ] ); + + CG_CMD_Weapon_Select( cg.snap->ps.stats[ STAT_LAST_WEAPON ] ); + +} + + +/* +=============== +CG_Weapon_Gauntlet - select the Gauntlet +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_Gauntlet( void ) { + CG_CMD_Weapon_Select( WP_GAUNTLET ); +} + +/* +=============== +CG_Weapon_MachineGun - select the MachineGun +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_MachineGun( void ) { + CG_CMD_Weapon_Select( WP_MACHINEGUN ); +} + +/* +=============== +CG_Weapon_ShotGun - select the ShotGun +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_ShotGun( void ) { + CG_CMD_Weapon_Select( WP_SHOTGUN ); +} + +/* +=============== +CG_Weapon_AirFist - select the AirFist +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_AirFist( void ) { + CG_CMD_Weapon_Select( WP_AIRFIST ); +} + +/* +=============== +CG_Weapon_NailGun - select the NailGun +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_NailGun( void ) { + CG_CMD_Weapon_Select( WP_NAILGUN ); +} + +/* +=============== +CG_Weapon_GrenadeLauncher - select the Grenade Launcher +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_GrenadeLauncher( void ) { + CG_CMD_Weapon_Select( WP_GRENADE_LAUNCHER ); +} + +/* +=============== +CG_Weapon_RocketLauncher - select the Rocket Launcher +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_RocketLauncher( void ) { + CG_CMD_Weapon_Select( WP_ROCKET_LAUNCHER ); +} + +/* +=============== +CG_Weapon_LightningGun - select the LightningGun +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_LightningGun( void ) { + CG_CMD_Weapon_Select( WP_LIGHTNING ); +} + +/* +=============== +CG_Weapon_RailGun - select the RailGun +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_RailGun( void ) { + CG_CMD_Weapon_Select( WP_RAILGUN ); +} + +/* +=============== +CG_Weapon_Harpoon - select the Harpoon +PKMOD - Ergodic 07/12/00 +=============== +*/ +void CG_Weapon_Harpoon( void ) { + CG_CMD_Weapon_Select( WP_GRAPPLING_HOOK ); +} + +/* +=============== +CG_Weapon_GravityWell - select the gravity well +PKMOD - Ergodic 07/02/00 +=============== +*/ +void CG_Weapon_GravityWell( void ) { + CG_CMD_Weapon_Select( WP_GRAVITY ); +} + +/* +=============== +CG_Weapon_Sentry - select the autosentry +PKMOD - Ergodic 07/02/00 +=============== +*/ +void CG_Weapon_Sentry( void ) { + CG_CMD_Weapon_Select( WP_SENTRY ); +} +/* +=============== +CG_Weapon_Beans - select the beartrap +PKMOD - Ergodic 07/02/00 +=============== +*/ +void CG_Weapon_BearTrap( void ) { + CG_CMD_Weapon_Select( WP_BEARTRAP ); +} + +/* +=============== +CG_Weapon_Beans - select the can of pork-n-beans +PKMOD - Ergodic 07/02/00 +=============== +*/ +void CG_Weapon_Beans( void ) { + CG_CMD_Weapon_Select( WP_BEANS ); +} + + +//PKMOD - Ergodic 05/20/00 switch to gauntlet +/* +=================== +CG_PKA_OutOfAmmoChange + +The current pka weapon has just run out of ammo +=================== +*/ +void CG_PKA_OutOfAmmoChange( void ) { + + //PKMOD - Ergodic 08/24/01 - debug removal of machinegun when testing infinite ammo bug (inactive) +// Com_Printf("CG_PKA_OutOfAmmoChange - removing weapon>%d<\n", cg.weaponSelect ); + + //PKMOD - Ergodic 06/27/00 physically remove the weapon from inventory + //PKMOD - Ergodic 03/02/01 - Does this work in the game code (I think not)? + cg.snap->ps.stats[STAT_WEAPONS] &= ~( 1 << cg.weaponSelect ); + + cg.weaponSelectTime = cg.time; + cg.weaponSelect = 1; +} + + +//PKMOD - Ergodic 03/01/01 dragon deploy pka weapon +/* +=================== +CG_Weapon_DragonDeploy + +Mount the current PKA weapon on the dragon + + if dragon's ammo = -1 then no weapon is being deployed + else weapon number (- ammo) == the weapon_t enum +=================== +*/ +void CG_Weapon_DragonDeploy( void ){ + char command[128]; + int deployNumber; + + //PKMOD - Ergodic 08/29/01 - debug (inactive) +// Com_Printf( "CG_Weapon_DragonDeploy - ps.generic1:%d, predictedPlayerState.generic1:%d\n", cg.snap->ps.generic1, cg.predictedPlayerState.generic1 ); + + //PKMOD - Ergodic 06/28/01 - turn off dragon deploy if enabled + if ( ( cg.weaponSelect == WP_GRAPPLING_HOOK ) && ( ( cg.snap->ps.generic1 & 15 ) > 0 ) ) { + Com_sprintf( command, 128, "gdeploy 0" ); + trap_SendClientCommand( command ); + //set the weapon to the dragon + cg.weaponSelect = WP_GRAPPLING_HOOK; + cg.weaponSelectTime = cg.time; + return; + } + + //check if player has a dragon in the inventory + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << WP_GRAPPLING_HOOK ) ) ) { + return; // don't have the weapon + } + + //PKMOD - Ergodic 05/08/01 - change logic structure to "if" from "switch" + //PKMOD - Ergodic 08/29/01 - cchange logic to include flag carrying + //PKMOD - Ergodic 12/15/01 - add gauntlet to deployable weapon list + //PKMOD - Ergodic 03/12/02 - optimize selction logic + if ( (( cg.weaponSelect >= WP_GRAVITY ) && ( cg.weaponSelect <= WP_BEANS ) ) || + ( cg.weaponSelect == WP_GAUNTLET ) ) { + deployNumber = cg.weaponSelect; + + //PKMOD - Ergodic 12/15/01 - only check the ammo if not the gauntlet + if ( deployNumber != WP_GAUNTLET ) { + + //PKMOD - Ergodic 07/22/01 - check if player has ammo - to fix infinite ammo bug + if ( cg.snap->ps.ammo[ cg.weaponSelect ] < 1 ) { + return; // don't have the required ammo + } + } + } + else { + //PKMOD - Ergodic 08/29/01 - check if player is carrying the flag + if ( cg.snap->ps.powerups[PW_BLUEFLAG] ) + deployNumber = PW_BLUEFLAG; + else if ( cg.snap->ps.powerups[PW_REDFLAG] ) + deployNumber = PW_REDFLAG; + else + return; //exit since current weapon or flag can not be deployed + + } + + //++++ + //here if weapon can be deployed + //++++ + + //PKMOD - Ergodic 03/01/01 - debug (inactive) +// Com_Printf( "CG_Weapon_DragonDeploy - valid deployable weapon\n" ); + + + //here if weapon can be deployed + + //set the dragon ammo to be the negative of the weapon number +// cg.predictedPlayerState.ammo[WP_GRAPPLING_HOOK] = 0 - cg.weaponSelect; + + //PKMOD - Ergodic 07/22/01 - debug infinite ammo (inactive) +// Com_Printf( "CG_Weapon_DragonDeploy - cg.weaponSelect:%d, ammo:%d\n", cg.weaponSelect, cg.snap->ps.ammo[ cg.weaponSelect ] ); + + //PKMOD - Ergodic 08/13/01 - Debug dragon infinite (inactive) +// Com_Printf("CG_Weapon_DragonDeploy - weaponSelect>%d<, ammo>%d<\n", cg.weaponSelect, cg.snap->ps.ammo[cg.weaponSelect] ); + + //PKMOD - Ergodic 03/03/01 - set generic1 to the alternate weapon +// cg.snap->ps.generic1 = cg.weaponSelect; +// cg.predictedPlayerState.generic1 = cg.weaponSelect; + + //PKMOD - Ergodic 02/20/04 - debug infinite flag bug (inactive) + //Com_Printf( "CG_Weapon_DragonDeploy - deployNumber>%d<\n", deployNumber ); + + + + Com_sprintf( command, 128, "gdeploy %i", deployNumber ); + trap_SendClientCommand( command ); + + //PKMOD - Ergodic 03/02/01 - debug (inactive) +// Com_Printf( "CG_Weapon_DragonDeploy - setting weapon to %d\n", cg.weaponSelect ); + + //set the weapon to the dragon + cg.weaponSelect = WP_GRAPPLING_HOOK; + cg.weaponSelectTime = cg.time; +} + + +/* +=================================================================================================== + +WEAPON EVENTS + +=================================================================================================== +*/ + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event +================ +*/ +void CG_FireWeapon( centity_t *cent ) { + entityState_t *ent; + int c; + weaponInfo_t *weap; + + + //PKMOD - Ergodic 10/14/01 - debug radiation effect (inactive) + //PKMOD - Ergodic 10/20/01 - update to punctuate the radiation effect (inactive) +// if ( cent->PKA_RadiateTime < cg.time ) { +// CG_Radiation( cg.snap->ps.origin ); +// cent->PKA_RadiateTime = cg.time + 1000; //every 1 seconds +// } + + //PKMOD - Ergodic 08/21/01 - debug infinite ammo bug (inactive) +// Com_Printf("CG_FireWeapon - firing weapon\n" ); + + ent = ¢->currentState; + if ( ent->weapon == WP_NONE ) { + return; + } + if ( ent->weapon >= WP_NUM_WEAPONS ) { + CG_Error( "CG_FireWeapon: ent->weapon >= WP_NUM_WEAPONS" ); + return; + } + weap = &cg_weapons[ ent->weapon ]; + + // mark the entity as muzzle flashing, so when it is added it will + // append the flash to the weapon model + //PKMOD - Ergodic 11/12/00 add 200 msecs to the airfist flashtime + //PKMOD - Ergodic 11/16/00 add multiple flash models depending on airfist_level + if ( ent->weapon == WP_AIRFIST ) { + trace_t tr; + int sourceContentType; + vec3_t af_forward; + vec3_t af_end; + vec3_t af_muzzle; + + cent->muzzleFlashTime = cg.time + 200; + //PKMOD - Ergodic 07/07/01 - use new packing scheme to encode airfist level + switch ( ( cent->currentState.time2 >> 4 ) & 7) { + case 4: + weap->flashModel = cgs.media.airfist4FlashModel; + break; + case 3: + weap->flashModel = cgs.media.airfist3FlashModel; + break; + case 2: + weap->flashModel = cgs.media.airfist2FlashModel; + break; + case 1: + weap->flashModel = cgs.media.airfist1FlashModel; + break; + default: + weap->flashModel = cgs.media.airfist0FlashModel; + break; + } + + //PKMOD - Ergodic 07/01/01 - determine which airfist sound to play + VectorCopy( cg.snap->ps.origin, af_muzzle ); + af_muzzle[2] += cg.snap->ps.viewheight; + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors( cg.snap->ps.viewangles, af_forward, NULL, NULL ); + AngleVectorsForward( cg.snap->ps.viewangles, af_forward ); + VectorMA( af_muzzle, 14, af_forward, af_muzzle ); + VectorMA( af_muzzle, 10, af_forward, af_end ); + + CG_Trace( &tr, af_muzzle, NULL, NULL, af_end, cg.predictedPlayerState.clientNum, MASK_SHOT ); + sourceContentType = trap_CM_PointContents( af_muzzle, 0 ); + + // FIXME: should probably move this cruft into CG_BubbleTrail + if ( sourceContentType & CONTENTS_WATER ) { + if ( cg.snap->ps.ammo[ WP_AIRFIST ] ) + weap->flashSound[0] = cgs.media.sfx_pkaairfistwaterfire; + else + weap->flashSound[0] = cgs.media.sfx_pkaairfistwaterempty; + } + else { + if ( cg.snap->ps.ammo[ WP_AIRFIST ] ) + weap->flashSound[0] = cgs.media.sfx_pkaairfistfire; + else + weap->flashSound[0] = cgs.media.sfx_pkaairfistempty; + } + + + } + else + cent->muzzleFlashTime = cg.time; + + // lightning gun only does this this on initial press + if ( ent->weapon == WP_LIGHTNING ) { + if ( cent->pe.lightningFiring ) { + return; + } + } + + // play quad sound if needed + if ( cent->currentState.powerups & ( 1 << PW_QUAD ) ) { + trap_S_StartSound (NULL, cent->currentState.number, CHAN_ITEM, cgs.media.quadSound ); + } + + // play a sound + for ( c = 0 ; c < 4 ; c++ ) { + if ( !weap->flashSound[c] ) { + break; + } + } + if ( c > 0 ) { + c = rand() % c; + if ( weap->flashSound[c] ) + { + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->flashSound[c] ); + } + } + + // do brass ejection + if ( weap->ejectBrassFunc && cg_brassTime.integer > 0 ) { + weap->ejectBrassFunc( cent ); + } +} + + +/* +================= +CG_MissileHitWall + +Caused by an EV_MISSILE_MISS event, or directly by local bullet tracing +================= +*/ +void CG_MissileHitWall( int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType ) { + qhandle_t mod; + qhandle_t mark; + qhandle_t shader; + sfxHandle_t sfx; + float radius; + float light; + vec3_t lightColor; + localEntity_t *le; + int r; + qboolean alphaFade; + qboolean isSprite; + int duration; + vec3_t sprOrg; + vec3_t sprVel; + + mark = 0; + radius = 32; + sfx = 0; + mod = 0; + shader = 0; + light = 0; + lightColor[0] = 1; + lightColor[1] = 1; + lightColor[2] = 0; + + // set defaults + isSprite = qfalse; + duration = 600; + + //PKMOD - Ergodic 02/14/02 - debug weapon number (inactive) +// Com_Printf( "CG_MissileHitWall - weapon>%d<\n", weapon ); + + switch ( weapon ) { + default: +#ifdef MISSIONPACK + case WP_NAILGUN: + if( soundType == IMPACTSOUND_FLESH ) { + sfx = cgs.media.sfx_nghitflesh; + } else if( soundType == IMPACTSOUND_METAL ) { + sfx = cgs.media.sfx_nghitmetal; + } else { + sfx = cgs.media.sfx_nghit; + } + mark = cgs.media.holeMarkShader; + radius = 12; + break; +#endif + case WP_LIGHTNING: + // no explosion at LG impact, it is added with the beam + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.sfx_lghit2; + } else if ( r == 2 ) { + sfx = cgs.media.sfx_lghit1; + } else { + sfx = cgs.media.sfx_lghit3; + } + mark = cgs.media.holeMarkShader; + radius = 12; + break; +#ifdef MISSIONPACK + case WP_PROX_LAUNCHER: + mod = cgs.media.dishFlashModel; + shader = cgs.media.grenadeExplosionShader; + sfx = cgs.media.sfx_proxexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + break; +#endif + case WP_GRENADE_LAUNCHER: + mod = cgs.media.dishFlashModel; + shader = cgs.media.grenadeExplosionShader; + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + break; + case WP_ROCKET_LAUNCHER: + mod = cgs.media.dishFlashModel; + shader = cgs.media.rocketExplosionShader; + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 64; + light = 300; + isSprite = qtrue; + duration = 1000; + lightColor[0] = 1; + lightColor[1] = 0.75; + lightColor[2] = 0.0; + if (cg_oldRocket.integer == 0) { + // explosion sprite animation + VectorMA( origin, 24, dir, sprOrg ); + VectorScale( dir, 64, sprVel ); + + CG_ParticleExplosion( "explode1", sprOrg, sprVel, 1400, 20, 30 ); + } + break; + case WP_RAILGUN: + mod = cgs.media.ringFlashModel; + shader = cgs.media.railExplosionShader; + sfx = cgs.media.sfx_plasmaexp; + mark = cgs.media.energyMarkShader; + radius = 24; + break; + case WP_PLASMAGUN: + mod = cgs.media.ringFlashModel; + shader = cgs.media.plasmaExplosionShader; + sfx = cgs.media.sfx_plasmaexp; + mark = cgs.media.energyMarkShader; + radius = 16; + break; + case WP_BFG: + mod = cgs.media.dishFlashModel; + shader = cgs.media.bfgExplosionShader; + sfx = cgs.media.sfx_rockexp; + mark = cgs.media.burnMarkShader; + radius = 32; + isSprite = qtrue; + break; + case WP_SHOTGUN: + mod = cgs.media.bulletFlashModel; + shader = cgs.media.bulletExplosionShader; + mark = cgs.media.bulletMarkShader; + sfx = 0; + radius = 4; + break; + +#ifdef MISSIONPACK + case WP_CHAINGUN: + mod = cgs.media.bulletFlashModel; + if( soundType == IMPACTSOUND_FLESH ) { + sfx = cgs.media.sfx_chghitflesh; + } else if( soundType == IMPACTSOUND_METAL ) { + sfx = cgs.media.sfx_chghitmetal; + } else { + sfx = cgs.media.sfx_chghit; + } + mark = cgs.media.bulletMarkShader; + + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.sfx_ric1; + } else if ( r == 2 ) { + sfx = cgs.media.sfx_ric2; + } else { + sfx = cgs.media.sfx_ric3; + } + + radius = 8; + break; +#endif + + case WP_MACHINEGUN: + mod = cgs.media.bulletFlashModel; + shader = cgs.media.bulletExplosionShader; + mark = cgs.media.bulletMarkShader; + + r = rand() & 3; + if ( r == 0 ) { + sfx = cgs.media.sfx_ric1; + } else if ( r == 1 ) { + sfx = cgs.media.sfx_ric2; + } else { + sfx = cgs.media.sfx_ric3; + } + + radius = 8; + break; + //PKMOD + /*PKMOD -Add Weapons. + WP_HARPOON, + WP_GRAVITY, + WP_SENTRY, + WP_BEARTRAP, + WP_CHAINLG, + WP_A2K, + WP_EMPNUKE, + WP_AIRFIST, + WP_NAILGUN, + PKMOD -Add Weapons. */ + case WP_GRAVITY: + //PKMOD Need shaders and SFX for this soon +// mark = cgs.media.burnMarkShader; +// mod = cgs.media.pkagravitylaunched; +// shader = cgs.media.rocketExplosionShader; +// sfx = cgs.media.sfx_pkagravitylaunched; + + +// mod = cgs.media.dishFlashModel; +// shader = cgs.media.grenadeExplosionShader; +// sfx = cgs.media.sfx_rockexp; +// mark = cgs.media.burnMarkShader; +// radius = 64; +// light = 300; +// isSprite = qtrue; + break; + case WP_SENTRY: + mod = cgs.media.bulletFlashModel; + shader = cgs.media.bulletExplosionShader; + mark = cgs.media.bulletMarkShader; + sfx = 0; + radius = 4; + break; + case WP_BEARTRAP: + mod = cgs.media.bulletFlashModel; + shader = cgs.media.bulletExplosionShader; + mark = cgs.media.bulletMarkShader; + sfx = cgs.media.sfx_pkabeartrapdrop; + radius = 4; + break; + case WP_CHAINLG: + //PKMOD rearrange for chain effect + // no explosion at lg impact, + r = rand() & 3; + if ( r < 2 ) { + sfx = cgs.media.sfx_lghit2; + } else if ( r == 2 ) { + sfx = cgs.media.sfx_lghit1; + } else { + sfx = cgs.media.sfx_lghit3; + } + mark = cgs.media.holeMarkShader; + radius = 12; + break; + case WP_AIRFIST: + //PKMOD probably remove mark altogether. Maybe add air mark + mark = cgs.media.holeMarkShader; + radius = 12; + break; + case WP_NAILGUN: + //PKMOD - Ergodic 08/01/00 set nailgun up correctly + mod = cgs.media.bulletFlashModel; + shader = cgs.media.nailImpactShader; + mark = cgs.media.nailMarkShader; + + //PKMOD - Ergodic 08/25/00 add new nailgun ricochet sounds + r = rand() & 63; //bias against sound #2 + if ( r == 0 ) { + sfx = cgs.media.sfx_nailrico2; + } else if ( r < 21 ) { + sfx = cgs.media.sfx_nailrico1; + } else if ( r < 41 ) { + sfx = cgs.media.sfx_nailrico3; + } else { + sfx = cgs.media.sfx_nailrico4; + } + + radius = 8; + break; +// case WP_NAILGUN + 128: +// //PKMOD - Ergodic 08/11/00 set nailgun hitting player +// mod = cgs.media.bulletFlashModel; +// shader = cgs.media.nailImpactShader; +// mark = cgs.media.nailMarkShader; +// +// r = rand() & 3; +// if ( r < 2 ) { +// sfx = cgs.media.sfx_ric1; +// } else if ( r == 2 ) { +// sfx = cgs.media.sfx_ric2; +// } else { +// sfx = cgs.media.sfx_ric3; +// } +// duration = 250; +// radius = 8; +// break; + case WP_EXPLODING_SHELLS: + mod = cgs.media.explshellsFlashModel; + + //PKMOD - Ergodic 01/16/01 - add multi-shaders for exploding shells + switch ( rand() % 6 ) { //random numbers: { 0, 1, 2, 3, 4, 5 } + case 0: + shader = cgs.media.shellsExplosionShader1; + break; + case 1: + shader = cgs.media.shellsExplosionShader2; + break; + case 2: + shader = cgs.media.shellsExplosionShader3; + break; + case 3: + shader = cgs.media.shellsExplosionShader4; + break; + case 4: + shader = cgs.media.shellsExplosionShader5; + break; + default: + shader = cgs.media.shellsExplosionShader6; + break; + } + + //PKMOD - Ergodic 02/14/02 - explosive shells hit sounds + //PKMOD - Ergodic 07/10/02 - add 2 more explosive shells hit sounds + switch ( rand() % 4 ) { //cases: 0, 1, 2, 3, 4 + case 0: + sfx = cgs.media.sfx_expgunhit1; + break; + case 1: + sfx = cgs.media.sfx_expgunhit2; + break; + case 2: + sfx = cgs.media.sfx_expgunhit3; + break; + case 3: + sfx = cgs.media.sfx_expgunhit4; + break; + default: + sfx = cgs.media.sfx_expgunhit5; + break; + } + + mark = cgs.media.burnMarkShader; + radius = 16; + light = 25; + //PKMOD - Ergodic 01/16/01 not a sprite anymore because sprite size seems too big +// isSprite = qtrue; + duration = 700; + lightColor[0] = 1; + lightColor[1] = 0.75; + lightColor[2] = 0.0; + break; + + } + //PKMOD + + if ( sfx ) { + trap_S_StartSound( origin, ENTITYNUM_WORLD, CHAN_AUTO, sfx ); + } + + // + // create the explosion + // + if ( mod ) { + le = CG_MakeExplosion( origin, dir, + mod, shader, + duration, isSprite ); + le->light = light; + VectorCopy( lightColor, le->lightColor ); + if ( weapon == WP_RAILGUN ) { + // colorize with client color + VectorCopy( cgs.clientinfo[clientNum].color1, le->color ); + } + } + + // + // impact mark + // + alphaFade = (mark == cgs.media.energyMarkShader); // plasma fades alpha, all others fade color + if ( weapon == WP_RAILGUN ) { + float *color; + + // colorize with client color + color = cgs.clientinfo[clientNum].color2; + CG_ImpactMark( mark, origin, dir, random()*360, color[0],color[1], color[2],1, alphaFade, radius, qfalse ); + } else { + CG_ImpactMark( mark, origin, dir, random()*360, 1,1,1,1, alphaFade, radius, qfalse ); + } +} + + +/* +================= +CG_MissileHitPlayer +================= +*/ +void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum ) { + CG_Bleed( origin, entityNum ); + + // some weapons will make an explosion with the blood, while + // others will just make the blood + switch ( weapon ) { +// //PKMOD - Ergodic 08/11/00 - add impact on player from nail +// commentary - this code does not work visually, due to player movements +// - and will be removed +// case WP_NAILGUN: +// CG_MissileHitWall( weapon + 128, 0, origin, dir ); //Weapon hack + case WP_GRENADE_LAUNCHER: + case WP_ROCKET_LAUNCHER: + CG_MissileHitWall( weapon, 0, origin, dir, IMPACTSOUND_FLESH ); + break; + default: + break; + } +} + + + +/* +============================================================================ + +SHOTGUN TRACING + +============================================================================ +*/ + +/* +================ +CG_ShotgunPellet +================ +*/ +//PKMOD - Ergodic 06/18/00 change the call parameters to add in weapon argument +static void CG_ShotgunPellet( vec3_t start, vec3_t end, int skipNum, int weapon ) { + trace_t tr; + int sourceContentType, destContentType; + + CG_Trace( &tr, start, NULL, NULL, end, skipNum, MASK_SHOT ); + + sourceContentType = trap_CM_PointContents( start, 0 ); + destContentType = trap_CM_PointContents( tr.endpos, 0 ); + + // FIXME: should probably move this cruft into CG_BubbleTrail + if ( sourceContentType == destContentType ) { + if ( sourceContentType & CONTENTS_WATER ) { + CG_BubbleTrail( start, tr.endpos, 32 ); + } + } else if ( sourceContentType & CONTENTS_WATER ) { + trace_t trace; + + trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( start, trace.endpos, 32 ); + } else if ( destContentType & CONTENTS_WATER ) { + trace_t trace; + + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( tr.endpos, trace.endpos, 32 ); + } + + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + return; + } + + //PKMOD - Ergodic 06/18/00 change the call argument from WP_SHOTGUN to weapon + if ( cg_entities[tr.entityNum].currentState.eType == ET_PLAYER ) { + CG_MissileHitPlayer( weapon, tr.endpos, tr.plane.normal, tr.entityNum ); + } else { + if ( tr.surfaceFlags & SURF_NOIMPACT ) { + // SURF_NOIMPACT will not make a flame puff or a mark + return; + } + if ( tr.surfaceFlags & SURF_METALSTEPS ) { + //PKMOD - Ergodic 06/18/00 change the call argument from WP_SHOTGUN to weapon + //PKMOD - Ergodic 01/16/01 - fixed missing exploding shells graphic + CG_MissileHitWall( weapon, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_METAL ); + } else { + //PKMOD - Ergodic 06/18/00 change the call argument from WP_SHOTGUN to weapon + //PKMOD - Ergodic 01/16/01 - fixed missing exploding shells graphic + CG_MissileHitWall( weapon, 0, tr.endpos, tr.plane.normal, IMPACTSOUND_DEFAULT ); + } + } +} + +/* +================ +CG_ShotgunPattern + +Perform the same traces the server did to locate the +hit splashes +================ +*/ +//PKMOD - Ergodic 06/18/00 change the call parameters to add in weapon argument +//PKMOD - Ergodic 10/31/02 - Q3A Ver 1.32 change the call parameters to add in seed +static void CG_ShotgunPattern( vec3_t origin, vec3_t origin2, int seed, int otherEntNum, int weapon ) { + int i; + float r, u; + vec3_t end; + vec3_t forward, right, up; + //PKMOD - Ergodic 01/16/01 make the exploding shells tighter in spread + int spread; + int count; + //PKMOD - Ergodic 01/20/01 - debug shotgun origin +// vec3_t backward; + //PKMOD - Ergodic 01/22/01 - optimize the end location + vec3_t hold_end; + + // derive the right and up vectors from the forward vector, because + // the client won't have any other information + VectorNormalize2( origin2, forward ); + PerpendicularVector( right, forward ); + CrossProduct( forward, right, up ); + //PKMOD - Ergodic 01/16/01 make the exploding shells tighter in spread + //PKMOD - Ergodic 01/18/01 make the pellet counts equal + if ( weapon == WP_EXPLODING_SHELLS ) { + spread = DEFAULT_SHOTGUN_SPREAD * 12; + count = DEFAULT_SHOTGUN_COUNT; + VectorMA( origin, 8192 * 12, forward, hold_end); + } + else { + spread = DEFAULT_SHOTGUN_SPREAD * 16; //default spread + count = DEFAULT_SHOTGUN_COUNT; + VectorMA( origin, 8192 * 16, forward, hold_end); + } + + //PKMOD - Ergodic 01/20/01 - debug shotgun origin +// VectorSet(backward, 0,0,0); +// VectorMA(backward, -1, forward, backward); +// CG_MissileHitWall( weapon, 0, origin, backward, IMPACTSOUND_METAL ); + + // generate the "random" spread pattern + for ( i = 0 ; i < count ; i++ ) { + //PKMOD - Ergodic Debug - only 1 pellet +// for ( i = 0 ; i < 1 ; i++ ) { + //PKMOD - Ergodic 01/16/01 make the exploding shells tighter in spread + r = Q_crandom( &seed ) * spread; + u = Q_crandom( &seed ) * spread; + + VectorCopy( hold_end, end ); + VectorMA (end, r, right, end); + VectorMA (end, u, up, end); + + CG_ShotgunPellet( origin, end, otherEntNum, weapon ); + } +} + +/* +============== +CG_ShotgunFire +============== +*/ +void CG_ShotgunFire( entityState_t *es ) { + vec3_t v; + int contents; + + VectorSubtract( es->origin2, es->pos.trBase, v ); + VectorNormalize( v ); + VectorScale( v, 32, v ); + VectorAdd( es->pos.trBase, v, v ); + if ( cgs.glconfig.hardwareType != GLHW_RAGEPRO ) { + // ragepro can't alpha fade, so don't even bother with smoke + vec3_t up; + + contents = trap_CM_PointContents( es->pos.trBase, 0 ); + if ( !( contents & CONTENTS_WATER ) ) { + VectorSet( up, 0, 0, 8 ); + CG_SmokePuff( v, up, 32, 1, 1, 1, 0.33f, 900, cg.time, 0, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader ); + } + } + +//PKMOD - Ergodic 06/18/00 change the call parameters to add in weapon argument +//PKMOD - Ergodic 10/31/02 - Q3A Ver 1.32 change the call parameters to add in es->eventParm as the seed + CG_ShotgunPattern( es->pos.trBase, es->origin2, es->eventParm, es->otherEntityNum, es->weapon ); +} + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + + +/* +=============== +CG_Tracer +=============== +*/ +//PKMOD - Ergodic 03/26/01 - add autosentry into CG_Tracer, autosentrys produce +// larger traces +void CG_Tracer( vec3_t source, vec3_t dest, qboolean autosentry ) { + vec3_t forward, right; + polyVert_t verts[4]; + vec3_t line; + float len, begin, end; + vec3_t start, finish; + vec3_t midpoint; + //PKMOD - Ergodic 03/26/01 - add autosentry tracer hold values + float pkatracerLength, pkatracerWidth; + + if ( autosentry ) { + pkatracerLength = 150.0f; //default = 100.0 + pkatracerWidth = 2.5f; //default = 1.0 + } + else { + pkatracerLength = cg_tracerLength.value; + pkatracerWidth = cg_tracerWidth.value; + } + + // tracer + VectorSubtract( dest, source, forward ); + len = VectorNormalize( forward ); + + // start at least a little ways from the muzzle + if ( len < 100 ) { + return; + } + begin = 50 + random() * (len - 60); + end = begin + pkatracerLength; + if ( end > len ) { + end = len; + } + VectorMA( source, begin, forward, start ); + VectorMA( source, end, forward, finish ); + + line[0] = DotProduct( forward, cg.refdef.viewaxis[1] ); + line[1] = DotProduct( forward, cg.refdef.viewaxis[2] ); + + VectorScale( cg.refdef.viewaxis[1], line[1], right ); + VectorMA( right, -line[0], cg.refdef.viewaxis[2], right ); + VectorNormalize( right ); + + VectorMA( finish, pkatracerWidth, right, verts[0].xyz ); + verts[0].st[0] = 0; + verts[0].st[1] = 1; + verts[0].modulate[0] = 255; + verts[0].modulate[1] = 255; + verts[0].modulate[2] = 255; + verts[0].modulate[3] = 255; + + VectorMA( finish, -pkatracerWidth, right, verts[1].xyz ); + verts[1].st[0] = 1; + verts[1].st[1] = 0; + verts[1].modulate[0] = 255; + verts[1].modulate[1] = 255; + verts[1].modulate[2] = 255; + verts[1].modulate[3] = 255; + + VectorMA( start, -pkatracerWidth, right, verts[2].xyz ); + verts[2].st[0] = 1; + verts[2].st[1] = 1; + verts[2].modulate[0] = 255; + verts[2].modulate[1] = 255; + verts[2].modulate[2] = 255; + verts[2].modulate[3] = 255; + + VectorMA( start, pkatracerWidth, right, verts[3].xyz ); + verts[3].st[0] = 0; + verts[3].st[1] = 0; + verts[3].modulate[0] = 255; + verts[3].modulate[1] = 255; + verts[3].modulate[2] = 255; + verts[3].modulate[3] = 255; + + trap_R_AddPolyToScene( cgs.media.tracerShader, 4, verts ); + + midpoint[0] = ( start[0] + finish[0] ) * 0.5; + midpoint[1] = ( start[1] + finish[1] ) * 0.5; + midpoint[2] = ( start[2] + finish[2] ) * 0.5; + + // add the tracer sound + trap_S_StartSound( midpoint, ENTITYNUM_WORLD, CHAN_AUTO, cgs.media.tracerSound ); + +} + + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ +static qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { + vec3_t forward; + centity_t *cent; + int anim; + + if ( entityNum == cg.snap->ps.clientNum ) { + VectorCopy( cg.snap->ps.origin, muzzle ); + muzzle[2] += cg.snap->ps.viewheight; + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors( cg.snap->ps.viewangles, forward, NULL, NULL ); + AngleVectorsForward( cg.snap->ps.viewangles, forward ); + VectorMA( muzzle, 14, forward, muzzle ); + return qtrue; + } + + cent = &cg_entities[entityNum]; + if ( !cent->currentValid ) { + return qfalse; + } + + VectorCopy( cent->currentState.pos.trBase, muzzle ); + + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); + AngleVectorsForward( cent->currentState.apos.trBase, forward ); + anim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT; + if ( anim == LEGS_WALKCR || anim == LEGS_IDLECR ) { + muzzle[2] += CROUCH_VIEWHEIGHT; + } else { + muzzle[2] += DEFAULT_VIEWHEIGHT; + } + + VectorMA( muzzle, 14, forward, muzzle ); + + return qtrue; + +} + +/* +====================== +CG_Bullet + +Renders bullet effects. +====================== +*/ +//PKMOD - Ergodic 12/14/00 - add autosentry code for greater tracability +void CG_Bullet( vec3_t end, int sourceEntityNum, vec3_t normal, qboolean flesh, int fleshEntityNum, qboolean autosentry ) { + trace_t trace; + int sourceContentType, destContentType; + vec3_t start; + + // if the shooter is currently valid, calc a source point and possibly + // do trail effects + if ( sourceEntityNum >= 0 && cg_tracerChance.value > 0 ) { + if ( CG_CalcMuzzlePoint( sourceEntityNum, start ) ) { + sourceContentType = trap_CM_PointContents( start, 0 ); + destContentType = trap_CM_PointContents( end, 0 ); + + // do a complete bubble trail if necessary + if ( ( sourceContentType == destContentType ) && ( sourceContentType & CONTENTS_WATER ) ) { + CG_BubbleTrail( start, end, 32 ); + } + // bubble trail from water into air + else if ( ( sourceContentType & CONTENTS_WATER ) ) { + trap_CM_BoxTrace( &trace, end, start, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( start, trace.endpos, 32 ); + } + // bubble trail from air into water + else if ( ( destContentType & CONTENTS_WATER ) ) { + trap_CM_BoxTrace( &trace, start, end, NULL, NULL, 0, CONTENTS_WATER ); + CG_BubbleTrail( end, trace.endpos, 32 ); + } + + // draw a tracer + //PKMOD - Ergodic 12/14/00 - add autosentry code for greater tracability + if ( autosentry ) { + if ( random() < 0.90 ) { + //PKMOD - Ergodic 03/26/01 - add autosentry into CG_Tracer + CG_Tracer( start, end, autosentry ); + } + } + else if ( random() < cg_tracerChance.value ) { + //PKMOD - Ergodic 03/26/01 - add autosentry into CG_Tracer + CG_Tracer( start, end, autosentry ); + } + } + } + + // impact splash and mark + if ( flesh ) { + CG_Bleed( end, fleshEntityNum ); + } else { + CG_MissileHitWall( WP_MACHINEGUN, 0, end, normal, IMPACTSOUND_DEFAULT ); + } + +} + + diff --git a/quake3/source/code/cgame/cgame.bat b/quake3/source/code/cgame/cgame.bat new file mode 100644 index 0000000..21090cd --- /dev/null +++ b/quake3/source/code/cgame/cgame.bat @@ -0,0 +1,68 @@ +set LIBRARY= +set INCLUDE= + +mkdir vm +cd vm + +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../../game/bg_misc.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../../game/bg_pmove.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../../game/bg_slidemove.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../../game/bg_lib.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../../game/q_math.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../../game/q_shared.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_consolecmds.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_draw.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_drawtools.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_effects.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_ents.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_event.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_info.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_localents.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_main.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_marks.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_players.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_playerstate.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_predict.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_scoreboard.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_servercmds.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_snapshot.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_view.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_weapons.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../../ui/ui_shared.c +@if errorlevel 1 goto quit +lcc -DQ3_VM -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui ../cg_newdraw.c +@if errorlevel 1 goto quit + +q3asm -f ../cgame + +rem copy the qvm to a staging area... +rem copy \quake3\baseq3\vm\cgame.qvm c:\workmb\pkarena31\pkarena0\vm\cgame.qvm + +echo "Compilation was successful!" + +:quit +cd .. diff --git a/quake3/source/code/cgame/cgame.def b/quake3/source/code/cgame/cgame.def new file mode 100644 index 0000000..01861ba --- /dev/null +++ b/quake3/source/code/cgame/cgame.def @@ -0,0 +1,3 @@ +EXPORTS + vmMain + dllEntry diff --git a/quake3/source/code/cgame/cgame.dsp b/quake3/source/code/cgame/cgame.dsp new file mode 100644 index 0000000..f7fc37a --- /dev/null +++ b/quake3/source/code/cgame/cgame.dsp @@ -0,0 +1,344 @@ +# Microsoft Developer Studio Project File - Name="cgame" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 5.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=cgame - Win32 Debug TA +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "cgame.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "cgame.mak" CFG="cgame - Win32 Debug TA" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cgame - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cgame - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cgame - Win32 Release TA" (based on\ + "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cgame - Win32 Debug TA" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP Scc_ProjName ""$/MissionPack/code/cgame", NPAAAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cgame - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /G6 /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL" +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL" +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /machine:I386 /out:"../Release/cgamex86.dll" +# SUBTRACT LINK32 /debug + +!ELSEIF "$(CFG)" == "cgame - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /G5 /MTd /W3 /GX /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /ZI /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL" +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL" +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /debug /machine:I386 /out:"../Debug/cgamex86.dll" +# SUBTRACT LINK32 /profile /nodefaultlib + +!ELSEIF "$(CFG)" == "cgame - Win32 Release TA" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "cgame___Win32_Release_TA" +# PROP BASE Intermediate_Dir "cgame___Win32_Release_TA" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release_TA" +# PROP Intermediate_Dir "Release_TA" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G6 /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /G6 /W4 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "MISSIONPACK" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL" +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o /win32 "NUL" +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /machine:I386 /out:"../Release/cgamex86.dll" +# SUBTRACT BASE LINK32 /debug +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /machine:I386 /out:"../Release_TA/cgamex86.dll" +# SUBTRACT LINK32 /debug + +!ELSEIF "$(CFG)" == "cgame - Win32 Debug TA" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "cgame___Win32_Debug_TA" +# PROP BASE Intermediate_Dir "cgame___Win32_Debug_TA" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_TA" +# PROP Intermediate_Dir "Debug_TA" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MTd /W3 /GX /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /ZI /c +# ADD CPP /nologo /G5 /MTd /W3 /GX /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /YX /FD /ZI /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL" +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o /win32 "NUL" +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /debug /machine:I386 /out:"..\Debug/cgamex86.dll" +# SUBTRACT BASE LINK32 /profile /nodefaultlib +# ADD LINK32 /nologo /base:"0x30000000" /subsystem:windows /dll /map /debug /machine:I386 /def:".\cgame.def" /out:"..\Debug_TA\cgamex86.dll" +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "cgame - Win32 Release" +# Name "cgame - Win32 Debug" +# Name "cgame - Win32 Release TA" +# Name "cgame - Win32 Debug TA" +# Begin Group "Source Files" + +# PROP Default_Filter "c" +# Begin Source File + +SOURCE=..\game\bg_lib.c + +!IF "$(CFG)" == "cgame - Win32 Release" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "cgame - Win32 Debug" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "cgame - Win32 Release TA" + +# PROP BASE Exclude_From_Build 1 +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "cgame - Win32 Debug TA" + +# PROP BASE Exclude_From_Build 1 +# PROP Exclude_From_Build 1 + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\game\bg_misc.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_pmove.c +# End Source File +# Begin Source File + +SOURCE=..\game\bg_slidemove.c +# End Source File +# Begin Source File + +SOURCE=.\cg_consolecmds.c +# End Source File +# Begin Source File + +SOURCE=.\cg_draw.c +# End Source File +# Begin Source File + +SOURCE=.\cg_drawtools.c +# End Source File +# Begin Source File + +SOURCE=.\cg_effects.c +# End Source File +# Begin Source File + +SOURCE=.\cg_ents.c +# End Source File +# Begin Source File + +SOURCE=.\cg_event.c +# End Source File +# Begin Source File + +SOURCE=.\cg_info.c +# End Source File +# Begin Source File + +SOURCE=.\cg_localents.c +# End Source File +# Begin Source File + +SOURCE=.\cg_main.c +# End Source File +# Begin Source File + +SOURCE=.\cg_marks.c +# End Source File +# Begin Source File + +SOURCE=.\cg_newDraw.c + +!IF "$(CFG)" == "cgame - Win32 Release" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "cgame - Win32 Debug" + +# PROP Exclude_From_Build 1 + +!ELSEIF "$(CFG)" == "cgame - Win32 Release TA" + +!ELSEIF "$(CFG)" == "cgame - Win32 Debug TA" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=.\cg_players.c +# End Source File +# Begin Source File + +SOURCE=.\cg_playerstate.c +# End Source File +# Begin Source File + +SOURCE=.\cg_predict.c +# End Source File +# Begin Source File + +SOURCE=.\cg_scoreboard.c +# End Source File +# Begin Source File + +SOURCE=.\cg_servercmds.c +# End Source File +# Begin Source File + +SOURCE=.\cg_snapshot.c +# End Source File +# Begin Source File + +SOURCE=.\cg_syscalls.c +# End Source File +# Begin Source File + +SOURCE=.\cg_view.c +# End Source File +# Begin Source File + +SOURCE=.\cg_weapons.c +# End Source File +# Begin Source File + +SOURCE=..\game\q_math.c +# End Source File +# Begin Source File + +SOURCE=..\game\q_shared.c +# End Source File +# Begin Source File + +SOURCE=..\ui\ui_shared.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=..\game\bg_public.h +# End Source File +# Begin Source File + +SOURCE=.\cg_local.h +# End Source File +# Begin Source File + +SOURCE=.\cg_public.h +# End Source File +# Begin Source File + +SOURCE=..\game\q_shared.h +# End Source File +# Begin Source File + +SOURCE=..\game\surfaceflags.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\cgame.def + +!IF "$(CFG)" == "cgame - Win32 Release" + +!ELSEIF "$(CFG)" == "cgame - Win32 Debug" + +!ELSEIF "$(CFG)" == "cgame - Win32 Release TA" + +!ELSEIF "$(CFG)" == "cgame - Win32 Debug TA" + +# PROP Exclude_From_Build 1 + +!ENDIF + +# End Source File +# End Target +# End Project diff --git a/quake3/source/code/cgame/cgame.plg b/quake3/source/code/cgame/cgame.plg new file mode 100644 index 0000000..96155c2 --- /dev/null +++ b/quake3/source/code/cgame/cgame.plg @@ -0,0 +1,16 @@ +--------------------Configuration: cgame - Win32 Debug TA-------------------- +Begining build with project "C:\quake3\source\code\cgame\cgame.dsp", with item "cg_servercmds.c" +Active configuration is Win32 (x86) Dynamic-Link Library (based on Win32 (x86) Dynamic-Link Library) + + +Creating temp file "C:\DOCUME~1\brights\LOCALS~1\Temp\RSP81.tmp" with contents +Creating command line "cl.exe @C:\DOCUME~1\brights\LOCALS~1\Temp\RSP81.tmp" +Compiling... +Command line warning D4002 : ignoring unknown option '/ZI' +cg_servercmds.c + + + +cg_servercmds.obj - 0 error(s), 1 warning(s) diff --git a/quake3/source/code/cgame/cgame.q3asm b/quake3/source/code/cgame/cgame.q3asm new file mode 100644 index 0000000..bec4c4e --- /dev/null +++ b/quake3/source/code/cgame/cgame.q3asm @@ -0,0 +1,28 @@ +-o "\quake3\baseq3\vm\cgame" +cg_main +..\cg_syscalls +cg_consolecmds +cg_draw +cg_drawtools +cg_effects +cg_ents +cg_event +cg_info +cg_localents +cg_marks +cg_players +cg_playerstate +cg_predict +cg_scoreboard +cg_servercmds +cg_snapshot +cg_view +cg_weapons +bg_slidemove +bg_pmove +bg_lib +bg_misc +q_math +q_shared +ui_shared +cg_newdraw diff --git a/quake3/source/code/cgame/cgame_ta.bat b/quake3/source/code/cgame/cgame_ta.bat new file mode 100644 index 0000000..266e017 --- /dev/null +++ b/quake3/source/code/cgame/cgame_ta.bat @@ -0,0 +1,65 @@ +rem make sure we have a safe environement +set LIBRARY= +set INCLUDE= + +mkdir vm +cd vm +set cc=lcc -DQ3_VM -DMISSIONPACK -DCGAME -S -Wf-target=bytecode -Wf-g -I..\..\cgame -I..\..\game -I..\..\ui %1 + +%cc% ../../game/bg_misc.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_pmove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_slidemove.c +@if errorlevel 1 goto quit +%cc% ../../game/bg_lib.c +@if errorlevel 1 goto quit +%cc% ../../game/q_math.c +@if errorlevel 1 goto quit +%cc% ../../game/q_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_consolecmds.c +@if errorlevel 1 goto quit +%cc% ../cg_draw.c +@if errorlevel 1 goto quit +%cc% ../cg_drawtools.c +@if errorlevel 1 goto quit +%cc% ../cg_effects.c +@if errorlevel 1 goto quit +%cc% ../cg_ents.c +@if errorlevel 1 goto quit +%cc% ../cg_event.c +@if errorlevel 1 goto quit +%cc% ../cg_info.c +@if errorlevel 1 goto quit +%cc% ../cg_localents.c +@if errorlevel 1 goto quit +%cc% ../cg_main.c +@if errorlevel 1 goto quit +%cc% ../cg_marks.c +@if errorlevel 1 goto quit +%cc% ../cg_players.c +@if errorlevel 1 goto quit +%cc% ../cg_playerstate.c +@if errorlevel 1 goto quit +%cc% ../cg_predict.c +@if errorlevel 1 goto quit +%cc% ../cg_scoreboard.c +@if errorlevel 1 goto quit +%cc% ../cg_servercmds.c +@if errorlevel 1 goto quit +%cc% ../cg_snapshot.c +@if errorlevel 1 goto quit +%cc% ../cg_view.c +@if errorlevel 1 goto quit +%cc% ../cg_weapons.c +@if errorlevel 1 goto quit +%cc% ../../ui/ui_shared.c +@if errorlevel 1 goto quit +%cc% ../cg_newdraw.c +@if errorlevel 1 goto quit + + +q3asm -f ../cgame_ta +:quit +cd .. diff --git a/quake3/source/code/cgame/cgame_ta.q3asm b/quake3/source/code/cgame/cgame_ta.q3asm new file mode 100644 index 0000000..8b14ed9 --- /dev/null +++ b/quake3/source/code/cgame/cgame_ta.q3asm @@ -0,0 +1,28 @@ +-o "\quake3\missionpack\vm\cgame" +cg_main +..\cg_syscalls +cg_consolecmds +cg_draw +cg_drawtools +cg_effects +cg_ents +cg_event +cg_info +cg_localents +cg_marks +cg_players +cg_playerstate +cg_predict +cg_scoreboard +cg_servercmds +cg_snapshot +cg_view +cg_weapons +bg_slidemove +bg_pmove +bg_lib +bg_misc +q_math +q_shared +ui_shared +cg_newdraw diff --git a/quake3/source/code/cgame/tr_types.h b/quake3/source/code/cgame/tr_types.h new file mode 100644 index 0000000..a771145 --- /dev/null +++ b/quake3/source/code/cgame/tr_types.h @@ -0,0 +1,209 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#ifndef __TR_TYPES_H +#define __TR_TYPES_H + + +#define MAX_DLIGHTS 32 // can't be increased, because bit flags are used on surfaces +#define MAX_ENTITIES 1023 // can't be increased without changing drawsurf bit packing + +// renderfx flags +#define RF_MINLIGHT 1 // allways have some light (viewmodel, some items) +#define RF_THIRD_PERSON 2 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 4 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 8 // for view weapon Z crunching +#define RF_NOSHADOW 64 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 128 // use refEntity->lightingOrigin instead of refEntity->origin + // for lighting. This allows entities to sink into the floor + // with their origin going solid, and allows all parts of a + // player to get the same lighting +#define RF_SHADOW_PLANE 256 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 512 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count + +// refdef flags +#define RDF_NOWORLDMODEL 1 // used for player configuration screen +#define RDF_HYPERSPACE 4 // teleportation effect + +typedef struct { + vec3_t xyz; + float st[2]; + byte modulate[4]; +} polyVert_t; + +typedef struct poly_s { + qhandle_t hShader; + int numVerts; + polyVert_t *verts; +} poly_t; + +typedef enum { + RT_MODEL, + RT_POLY, + RT_SPRITE, + RT_BEAM, + RT_RAIL_CORE, + RT_RAIL_RINGS, + RT_LIGHTNING, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +typedef struct { + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t lightingOrigin; // so multi-part models can be lit identically (RF_LIGHTING_ORIGIN) + float shadowPlane; // projection shadows go here, stencils go slightly lower + + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + float origin[3]; // also used as MODEL_BEAM's "from" + int frame; // also used as MODEL_BEAM's diameter + + // previous data for frame interpolation + float oldorigin[3]; // also used as MODEL_BEAM's "to" + int oldframe; + float backlerp; // 0.0 = current, 1.0 = old + + // texturing + int skinNum; // inline skin index + qhandle_t customSkin; // NULL for default skin + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + float shaderTexCoord[2]; // texture coordinates used by tcMod entity modifiers + float shaderTime; // subtracted from refdef time to control effect start times + + // extra sprite information + float radius; + float rotation; +} refEntity_t; + + +#define MAX_RENDER_STRINGS 8 +#define MAX_RENDER_STRING_LENGTH 32 + +typedef struct { + int x, y, width, height; + float fov_x, fov_y; + vec3_t vieworg; + vec3_t viewaxis[3]; // transformation matrix + + // time in milliseconds for shader effects and other time dependent rendering issues + int time; + + int rdflags; // RDF_NOWORLDMODEL, etc + + // 1 bits will prevent the associated area from rendering at all + byte areamask[MAX_MAP_AREA_BYTES]; + + // text messages for deform text shaders + char text[MAX_RENDER_STRINGS][MAX_RENDER_STRING_LENGTH]; +} refdef_t; + + +typedef enum { + STEREO_CENTER, + STEREO_LEFT, + STEREO_RIGHT +} stereoFrame_t; + + +/* +** glconfig_t +** +** Contains variables specific to the OpenGL configuration +** being run right now. These are constant once the OpenGL +** subsystem is initialized. +*/ +typedef enum { + TC_NONE, + TC_S3TC +} textureCompression_t; + +typedef enum { + GLDRV_ICD, // driver is integrated with window system + // WARNING: there are tests that check for + // > GLDRV_ICD for minidriverness, so this + // should always be the lowest value in this + // enum set + GLDRV_STANDALONE, // driver is a non-3Dfx standalone driver + GLDRV_VOODOO // driver is a 3Dfx standalone driver +} glDriverType_t; + +typedef enum { + GLHW_GENERIC, // where everthing works the way it should + GLHW_3DFX_2D3D, // Voodoo Banshee or Voodoo3, relevant since if this is + // the hardware type then there can NOT exist a secondary + // display adapter + GLHW_RIVA128, // where you can't interpolate alpha + GLHW_RAGEPRO, // where you can't modulate alpha on alpha textures + GLHW_PERMEDIA2 // where you don't have src*dst +} glHardwareType_t; + +typedef struct { + char renderer_string[MAX_STRING_CHARS]; + char vendor_string[MAX_STRING_CHARS]; + char version_string[MAX_STRING_CHARS]; + char extensions_string[BIG_INFO_STRING]; + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + + int colorBits, depthBits, stencilBits; + + glDriverType_t driverType; + glHardwareType_t hardwareType; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + + int vidWidth, vidHeight; + // aspect is the screen's physical width / height, which may be different + // than scrWidth / scrHeight if the pixels are non-square + // normal screens should be 4/3, but wide aspect monitors may be 16/9 + float windowAspect; + + int displayFrequency; + + // synonymous with "does rendering consume the entire screen?", therefore + // a Voodoo or Voodoo2 will have this set to TRUE, as will a Win32 ICD that + // used CDS. + qboolean isFullscreen; + qboolean stereoEnabled; + qboolean smpActive; // dual processor +} glconfig_t; + +// FIXME: VM should be OS agnostic .. in theory + +/* +#ifdef Q3_VM + +#define _3DFX_DRIVER_NAME "Voodoo" +#define OPENGL_DRIVER_NAME "Default" + +#elif defined(_WIN32) +*/ + +#if defined(Q3_VM) || defined(_WIN32) + +#define _3DFX_DRIVER_NAME "3dfxvgl" +#define OPENGL_DRIVER_NAME "opengl32" + +#else + +#define _3DFX_DRIVER_NAME "libMesaVoodooGL.so" +// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 +#define OPENGL_DRIVER_NAME "libGL.so.1" + +#endif // !defined _WIN32 + +#endif // __TR_TYPES_H diff --git a/quake3/source/code/game/Debug_TA/ai_chat.obj b/quake3/source/code/game/Debug_TA/ai_chat.obj new file mode 100644 index 0000000..e9203ed Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_chat.obj differ diff --git a/quake3/source/code/game/Debug_TA/ai_chat.sbr b/quake3/source/code/game/Debug_TA/ai_chat.sbr new file mode 100644 index 0000000..763e206 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_chat.sbr differ diff --git a/quake3/source/code/game/Debug_TA/ai_cmd.obj b/quake3/source/code/game/Debug_TA/ai_cmd.obj new file mode 100644 index 0000000..919123b Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_cmd.obj differ diff --git a/quake3/source/code/game/Debug_TA/ai_cmd.sbr b/quake3/source/code/game/Debug_TA/ai_cmd.sbr new file mode 100644 index 0000000..d77ba57 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_cmd.sbr differ diff --git a/quake3/source/code/game/Debug_TA/ai_dmnet.obj b/quake3/source/code/game/Debug_TA/ai_dmnet.obj new file mode 100644 index 0000000..8fe2a10 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_dmnet.obj differ diff --git a/quake3/source/code/game/Debug_TA/ai_dmnet.sbr b/quake3/source/code/game/Debug_TA/ai_dmnet.sbr new file mode 100644 index 0000000..7c5d730 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_dmnet.sbr differ diff --git a/quake3/source/code/game/Debug_TA/ai_dmq3.obj b/quake3/source/code/game/Debug_TA/ai_dmq3.obj new file mode 100644 index 0000000..8b1925e Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_dmq3.obj differ diff --git a/quake3/source/code/game/Debug_TA/ai_dmq3.sbr b/quake3/source/code/game/Debug_TA/ai_dmq3.sbr new file mode 100644 index 0000000..f75c96b Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_dmq3.sbr differ diff --git a/quake3/source/code/game/Debug_TA/ai_main.obj b/quake3/source/code/game/Debug_TA/ai_main.obj new file mode 100644 index 0000000..d7d3036 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_main.obj differ diff --git a/quake3/source/code/game/Debug_TA/ai_main.sbr b/quake3/source/code/game/Debug_TA/ai_main.sbr new file mode 100644 index 0000000..e31cb72 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_main.sbr differ diff --git a/quake3/source/code/game/Debug_TA/ai_team.obj b/quake3/source/code/game/Debug_TA/ai_team.obj new file mode 100644 index 0000000..738bec2 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_team.obj differ diff --git a/quake3/source/code/game/Debug_TA/ai_team.sbr b/quake3/source/code/game/Debug_TA/ai_team.sbr new file mode 100644 index 0000000..6e90e9e Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_team.sbr differ diff --git a/quake3/source/code/game/Debug_TA/ai_vcmd.obj b/quake3/source/code/game/Debug_TA/ai_vcmd.obj new file mode 100644 index 0000000..3e0cc8b Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_vcmd.obj differ diff --git a/quake3/source/code/game/Debug_TA/ai_vcmd.sbr b/quake3/source/code/game/Debug_TA/ai_vcmd.sbr new file mode 100644 index 0000000..d53b05f Binary files /dev/null and b/quake3/source/code/game/Debug_TA/ai_vcmd.sbr differ diff --git a/quake3/source/code/game/Debug_TA/bg_misc.obj b/quake3/source/code/game/Debug_TA/bg_misc.obj new file mode 100644 index 0000000..6d153bf Binary files /dev/null and b/quake3/source/code/game/Debug_TA/bg_misc.obj differ diff --git a/quake3/source/code/game/Debug_TA/bg_misc.sbr b/quake3/source/code/game/Debug_TA/bg_misc.sbr new file mode 100644 index 0000000..52ae04f Binary files /dev/null and b/quake3/source/code/game/Debug_TA/bg_misc.sbr differ diff --git a/quake3/source/code/game/Debug_TA/bg_pmove.obj b/quake3/source/code/game/Debug_TA/bg_pmove.obj new file mode 100644 index 0000000..b6488d6 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/bg_pmove.obj differ diff --git a/quake3/source/code/game/Debug_TA/bg_pmove.sbr b/quake3/source/code/game/Debug_TA/bg_pmove.sbr new file mode 100644 index 0000000..1d62a1b Binary files /dev/null and b/quake3/source/code/game/Debug_TA/bg_pmove.sbr differ diff --git a/quake3/source/code/game/Debug_TA/bg_slidemove.obj b/quake3/source/code/game/Debug_TA/bg_slidemove.obj new file mode 100644 index 0000000..2a470ee Binary files /dev/null and b/quake3/source/code/game/Debug_TA/bg_slidemove.obj differ diff --git a/quake3/source/code/game/Debug_TA/bg_slidemove.sbr b/quake3/source/code/game/Debug_TA/bg_slidemove.sbr new file mode 100644 index 0000000..4cc81b4 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/bg_slidemove.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_active.obj b/quake3/source/code/game/Debug_TA/g_active.obj new file mode 100644 index 0000000..4168148 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_active.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_active.sbr b/quake3/source/code/game/Debug_TA/g_active.sbr new file mode 100644 index 0000000..6b92aea Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_active.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_arenas.obj b/quake3/source/code/game/Debug_TA/g_arenas.obj new file mode 100644 index 0000000..776dede Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_arenas.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_arenas.sbr b/quake3/source/code/game/Debug_TA/g_arenas.sbr new file mode 100644 index 0000000..ce89f71 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_arenas.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_bot.obj b/quake3/source/code/game/Debug_TA/g_bot.obj new file mode 100644 index 0000000..b4799ba Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_bot.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_bot.sbr b/quake3/source/code/game/Debug_TA/g_bot.sbr new file mode 100644 index 0000000..8d1acaa Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_bot.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_client.obj b/quake3/source/code/game/Debug_TA/g_client.obj new file mode 100644 index 0000000..81593f9 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_client.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_client.sbr b/quake3/source/code/game/Debug_TA/g_client.sbr new file mode 100644 index 0000000..233dba1 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_client.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_cmds.obj b/quake3/source/code/game/Debug_TA/g_cmds.obj new file mode 100644 index 0000000..b3a3d93 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_cmds.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_cmds.sbr b/quake3/source/code/game/Debug_TA/g_cmds.sbr new file mode 100644 index 0000000..377b5b3 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_cmds.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_combat.obj b/quake3/source/code/game/Debug_TA/g_combat.obj new file mode 100644 index 0000000..29339d9 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_combat.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_combat.sbr b/quake3/source/code/game/Debug_TA/g_combat.sbr new file mode 100644 index 0000000..d6f1510 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_combat.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_items.obj b/quake3/source/code/game/Debug_TA/g_items.obj new file mode 100644 index 0000000..f91aefe Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_items.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_items.sbr b/quake3/source/code/game/Debug_TA/g_items.sbr new file mode 100644 index 0000000..6754cec Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_items.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_main.obj b/quake3/source/code/game/Debug_TA/g_main.obj new file mode 100644 index 0000000..9eb3023 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_main.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_main.sbr b/quake3/source/code/game/Debug_TA/g_main.sbr new file mode 100644 index 0000000..0cdbd2e Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_main.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_mem.obj b/quake3/source/code/game/Debug_TA/g_mem.obj new file mode 100644 index 0000000..881558c Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_mem.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_mem.sbr b/quake3/source/code/game/Debug_TA/g_mem.sbr new file mode 100644 index 0000000..85c7a45 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_mem.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_misc.obj b/quake3/source/code/game/Debug_TA/g_misc.obj new file mode 100644 index 0000000..92f354d Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_misc.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_misc.sbr b/quake3/source/code/game/Debug_TA/g_misc.sbr new file mode 100644 index 0000000..ee36aa0 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_misc.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_missile.obj b/quake3/source/code/game/Debug_TA/g_missile.obj new file mode 100644 index 0000000..cfdd8e7 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_missile.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_missile.sbr b/quake3/source/code/game/Debug_TA/g_missile.sbr new file mode 100644 index 0000000..d75f4a3 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_missile.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_mover.obj b/quake3/source/code/game/Debug_TA/g_mover.obj new file mode 100644 index 0000000..b242f03 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_mover.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_mover.sbr b/quake3/source/code/game/Debug_TA/g_mover.sbr new file mode 100644 index 0000000..4ff84ea Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_mover.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_session.obj b/quake3/source/code/game/Debug_TA/g_session.obj new file mode 100644 index 0000000..e1b35df Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_session.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_session.sbr b/quake3/source/code/game/Debug_TA/g_session.sbr new file mode 100644 index 0000000..db85647 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_session.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_spawn.obj b/quake3/source/code/game/Debug_TA/g_spawn.obj new file mode 100644 index 0000000..a0a133d Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_spawn.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_spawn.sbr b/quake3/source/code/game/Debug_TA/g_spawn.sbr new file mode 100644 index 0000000..28272ad Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_spawn.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_svcmds.obj b/quake3/source/code/game/Debug_TA/g_svcmds.obj new file mode 100644 index 0000000..ade0679 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_svcmds.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_svcmds.sbr b/quake3/source/code/game/Debug_TA/g_svcmds.sbr new file mode 100644 index 0000000..9de3ece Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_svcmds.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_syscalls.obj b/quake3/source/code/game/Debug_TA/g_syscalls.obj new file mode 100644 index 0000000..862d96d Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_syscalls.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_syscalls.sbr b/quake3/source/code/game/Debug_TA/g_syscalls.sbr new file mode 100644 index 0000000..8c75a47 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_syscalls.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_target.obj b/quake3/source/code/game/Debug_TA/g_target.obj new file mode 100644 index 0000000..667797d Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_target.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_target.sbr b/quake3/source/code/game/Debug_TA/g_target.sbr new file mode 100644 index 0000000..4dbb262 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_target.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_team.obj b/quake3/source/code/game/Debug_TA/g_team.obj new file mode 100644 index 0000000..3a8dd9c Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_team.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_team.sbr b/quake3/source/code/game/Debug_TA/g_team.sbr new file mode 100644 index 0000000..0c450d6 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_team.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_trigger.obj b/quake3/source/code/game/Debug_TA/g_trigger.obj new file mode 100644 index 0000000..0816eda Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_trigger.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_trigger.sbr b/quake3/source/code/game/Debug_TA/g_trigger.sbr new file mode 100644 index 0000000..2082ed7 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_trigger.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_utils.obj b/quake3/source/code/game/Debug_TA/g_utils.obj new file mode 100644 index 0000000..a3f0f2b Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_utils.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_utils.sbr b/quake3/source/code/game/Debug_TA/g_utils.sbr new file mode 100644 index 0000000..2296ba3 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_utils.sbr differ diff --git a/quake3/source/code/game/Debug_TA/g_weapon.obj b/quake3/source/code/game/Debug_TA/g_weapon.obj new file mode 100644 index 0000000..d2dd02f Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_weapon.obj differ diff --git a/quake3/source/code/game/Debug_TA/g_weapon.sbr b/quake3/source/code/game/Debug_TA/g_weapon.sbr new file mode 100644 index 0000000..22f7a6b Binary files /dev/null and b/quake3/source/code/game/Debug_TA/g_weapon.sbr differ diff --git a/quake3/source/code/game/Debug_TA/game.bsc b/quake3/source/code/game/Debug_TA/game.bsc new file mode 100644 index 0000000..46200f5 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/game.bsc differ diff --git a/quake3/source/code/game/Debug_TA/game.pch b/quake3/source/code/game/Debug_TA/game.pch new file mode 100644 index 0000000..07c80c8 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/game.pch differ diff --git a/quake3/source/code/game/Debug_TA/q_math.obj b/quake3/source/code/game/Debug_TA/q_math.obj new file mode 100644 index 0000000..4a50b90 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/q_math.obj differ diff --git a/quake3/source/code/game/Debug_TA/q_math.sbr b/quake3/source/code/game/Debug_TA/q_math.sbr new file mode 100644 index 0000000..e69de29 diff --git a/quake3/source/code/game/Debug_TA/q_shared.obj b/quake3/source/code/game/Debug_TA/q_shared.obj new file mode 100644 index 0000000..ef325b4 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/q_shared.obj differ diff --git a/quake3/source/code/game/Debug_TA/q_shared.sbr b/quake3/source/code/game/Debug_TA/q_shared.sbr new file mode 100644 index 0000000..e69de29 diff --git a/quake3/source/code/game/Debug_TA/qagamex86.exp b/quake3/source/code/game/Debug_TA/qagamex86.exp new file mode 100644 index 0000000..dedc04c Binary files /dev/null and b/quake3/source/code/game/Debug_TA/qagamex86.exp differ diff --git a/quake3/source/code/game/Debug_TA/qagamex86.lib b/quake3/source/code/game/Debug_TA/qagamex86.lib new file mode 100644 index 0000000..ae876fb Binary files /dev/null and b/quake3/source/code/game/Debug_TA/qagamex86.lib differ diff --git a/quake3/source/code/game/Debug_TA/qagamex86.map b/quake3/source/code/game/Debug_TA/qagamex86.map new file mode 100644 index 0000000..f492bff --- /dev/null +++ b/quake3/source/code/game/Debug_TA/qagamex86.map @@ -0,0 +1,2862 @@ + qagamex86 + + Timestamp is 402d0b32 (Fri Feb 13 12:36:50 2004) + + Preferred load address is 20000000 + + Start Length Name Class + 0001:00000000 0006d1beH .text CODE + 0002:00000000 000024c1H .rdata DATA + 0002:000024d0 0000005aH .edata DATA + 0003:00000000 00000004H .CRT$XCA DATA + 0003:00000004 00000004H .CRT$XCZ DATA + 0003:00000008 00000004H .CRT$XIA DATA + 0003:0000000c 00000004H .CRT$XIC DATA + 0003:00000010 00000004H .CRT$XIZ DATA + 0003:00000014 00000004H .CRT$XPA DATA + 0003:00000018 00000004H .CRT$XPX DATA + 0003:0000001c 00000004H .CRT$XPZ DATA + 0003:00000020 00000004H .CRT$XTA DATA + 0003:00000024 00000004H .CRT$XTZ DATA + 0003:00000030 00010344H .data DATA + 0003:00010378 0015a188H .bss DATA + 0004:00000000 00000014H .idata$2 DATA + 0004:00000014 00000014H .idata$3 DATA + 0004:00000028 00000124H .idata$4 DATA + 0004:0000014c 00000124H .idata$5 DATA + 0004:00000270 0000051cH .idata$6 DATA + + Address Publics by Value Rva+Base Lib:Object + + 0001:00000000 _BotNumActivePlayers 20001000 f ai_chat.obj + 0001:00000109 _BotIsFirstInRankings 20001109 f ai_chat.obj + 0001:0000022a _BotIsLastInRankings 2000122a f ai_chat.obj + 0001:0000034b _BotFirstClientInRankings 2000134b f ai_chat.obj + 0001:000004a0 _BotLastClientInRankings 200014a0 f ai_chat.obj + 0001:000005f5 _BotRandomOpponentName 200015f5 f ai_chat.obj + 0001:000007f1 _PrivateBotOwner 200017f1 f ai_chat.obj + 0001:00000862 _BotMapTitle 20001862 f ai_chat.obj + 0001:000008b3 _BotWeaponNameForMeansOfDeath 200018b3 f ai_chat.obj + 0001:000009f9 _BotRandomWeaponName 200019f9 f ai_chat.obj + 0001:00000a9a _BotVisibleEnemies 20001a9a f ai_chat.obj + 0001:00000bda _BotValidChatPosition 20001bda f ai_chat.obj + 0001:00000db9 _BotChat_EnterGame 20001db9 f ai_chat.obj + 0001:00000efd _BotChat_ExitGame 20001efd f ai_chat.obj + 0001:0000102a _BotChat_StartLevel 2000202a f ai_chat.obj + 0001:00001175 _BotChat_EndLevel 20002175 f ai_chat.obj + 0001:000013b4 _BotChat_Death 200023b4 f ai_chat.obj + 0001:00001953 _BotChat_Kill 20002953 f ai_chat.obj + 0001:00001c33 _BotChat_EnemySuicide 20002c33 f ai_chat.obj + 0001:00001d89 _BotChat_HitTalking 20002d89 f ai_chat.obj + 0001:00001f38 _BotChat_HitNoDeath 20002f38 f ai_chat.obj + 0001:00002149 _BotChat_HitNoKill 20003149 f ai_chat.obj + 0001:00002304 _BotChat_Random 20003304 f ai_chat.obj + 0001:000025bc _BotChatTime 200035bc f ai_chat.obj + 0001:000025e8 _BotChatTest 200035e8 f ai_chat.obj + 0001:000033a0 _BotPrintTeamGoal 200043a0 f ai_cmd.obj + 0001:000035c0 _BotGetItemTeamGoal 200045c0 f ai_cmd.obj + 0001:0000361e _BotGetMessageTeamGoal 2000461e f ai_cmd.obj + 0001:0000367e _BotGetTime 2000467e f ai_cmd.obj + 0001:000037a0 _FindClientByName 200047a0 f ai_cmd.obj + 0001:000038aa _FindEnemyByName 200048aa f ai_cmd.obj + 0001:000039e9 _NumPlayersOnSameTeam 200049e9 f ai_cmd.obj + 0001:00003ab7 _BotGetPatrolWaypoints 20004ab7 f ai_cmd.obj + 0001:00003d3d _BotAddressedToBot 20004d3d f ai_cmd.obj + 0001:00003fbb _BotGPSToPosition 20004fbb f ai_cmd.obj + 0001:000040b6 _BotMatch_HelpAccompany 200050b6 f ai_cmd.obj + 0001:000045d7 _BotMatch_DefendKeyArea 200055d7 f ai_cmd.obj + 0001:00004754 _BotMatch_GetItem 20005754 f ai_cmd.obj + 0001:000048ea _BotMatch_Camp 200058ea f ai_cmd.obj + 0001:00004d23 _BotMatch_Patrol 20005d23 f ai_cmd.obj + 0001:00004ebe _BotMatch_GetFlag 20005ebe f ai_cmd.obj + 0001:00004ff7 _BotMatch_AttackEnemyBase 20005ff7 f ai_cmd.obj + 0001:00005115 _BotMatch_RushBase 20006115 f ai_cmd.obj + 0001:0000522d _BotMatch_TaskPreference 2000622d f ai_cmd.obj + 0001:000053d8 _BotMatch_ReturnFlag 200063d8 f ai_cmd.obj + 0001:000054d7 _BotMatch_JoinSubteam 200064d7 f ai_cmd.obj + 0001:000055ae _BotMatch_LeaveSubteam 200065ae f ai_cmd.obj + 0001:0000566f _BotMatch_WhichTeam 2000666f f ai_cmd.obj + 0001:000056f7 _BotMatch_CheckPoint 200066f7 f ai_cmd.obj + 0001:00005986 _BotMatch_FormationSpace 20006986 f ai_cmd.obj + 0001:00005a5b _BotMatch_Dismiss 20006a5b f ai_cmd.obj + 0001:00005b72 _BotMatch_Suicide 20006b72 f ai_cmd.obj + 0001:00005c09 _BotMatch_StartTeamLeaderShip 20006c09 f ai_cmd.obj + 0001:00005ccb _BotMatch_StopTeamLeaderShip 20006ccb f ai_cmd.obj + 0001:00005dae _BotMatch_WhoIsTeamLeader 20006dae f ai_cmd.obj + 0001:00005e11 _BotMatch_WhatAreYouDoing 20006e11 f ai_cmd.obj + 0001:00006110 _BotMatch_WhatIsMyCommand 20007110 f ai_cmd.obj + 0001:00006157 _BotNearestVisibleItem 20007157 f ai_cmd.obj + 0001:000062ce _BotMatch_WhereAreYou 200072ce f ai_cmd.obj + 0001:00006640 _BotMatch_LeadTheWay 20007640 f ai_cmd.obj + 0001:0000691e _BotMatch_Kill 2000791e f ai_cmd.obj + 0001:00006ab8 _BotMatch_CTF 20007ab8 f ai_cmd.obj + 0001:00006c69 _BotMatch_EnterGame 20007c69 f ai_cmd.obj + 0001:00006caa _BotMatch_NewLeader 20007caa f ai_cmd.obj + 0001:00006d04 _BotMatchMessage 20007d04 f ai_cmd.obj + 0001:000070e0 _BotResetNodeSwitches 200080e0 f ai_dmnet.obj + 0001:000070ef _BotDumpNodeSwitches 200080ef f ai_dmnet.obj + 0001:00007176 _BotRecordNodeSwitch 20008176 f ai_dmnet.obj + 0001:00007206 _BotGetAirGoal 20008206 f ai_dmnet.obj + 0001:000073a3 _BotGoForAir 200083a3 f ai_dmnet.obj + 0001:0000748e _BotNearbyGoal 2000848e f ai_dmnet.obj + 0001:0000753d _BotReachedGoal 2000853d f ai_dmnet.obj + 0001:000076ef _BotGetItemLongTermGoal 200086ef f ai_dmnet.obj + 0001:0000782b _BotGetLongTermGoal 2000882b f ai_dmnet.obj + 0001:0000966c _BotLongTermGoal 2000a66c f ai_dmnet.obj + 0001:00009b70 _AIEnter_Intermission 2000ab70 f ai_dmnet.obj + 0001:00009bd6 _AINode_Intermission 2000abd6 f ai_dmnet.obj + 0001:00009c43 _AIEnter_Observer 2000ac43 f ai_dmnet.obj + 0001:00009c7b _AINode_Observer 2000ac7b f ai_dmnet.obj + 0001:00009ca6 _AIEnter_Stand 2000aca6 f ai_dmnet.obj + 0001:00009ce7 _AINode_Stand 2000ace7 f ai_dmnet.obj + 0001:00009e01 _AIEnter_Respawn 2000ae01 f ai_dmnet.obj + 0001:00009f01 _AINode_Respawn 2000af01 f ai_dmnet.obj + 0001:0000a001 _BotSelectActivateWeapon 2000b001 f ai_dmnet.obj + 0001:0000a0a4 _BotClearPath 2000b0a4 f ai_dmnet.obj + 0001:0000a3c0 _AIEnter_Seek_ActivateEntity 2000b3c0 f ai_dmnet.obj + 0001:0000a3ec _AINode_Seek_ActivateEntity 2000b3ec f ai_dmnet.obj + 0001:0000ac88 _AIEnter_Seek_NBG 2000bc88 f ai_dmnet.obj + 0001:0000ad0c _AINode_Seek_NBG 2000bd0c f ai_dmnet.obj + 0001:0000b1b6 _AIEnter_Seek_LTG 2000c1b6 f ai_dmnet.obj + 0001:0000b23a _AINode_Seek_LTG 2000c23a f ai_dmnet.obj + 0001:0000b8a4 _AIEnter_Battle_Fight 2000c8a4 f ai_dmnet.obj + 0001:0000b8e2 _AIEnter_Battle_SuicidalFight 2000c8e2 f ai_dmnet.obj + 0001:0000b934 _AINode_Battle_Fight 2000c934 f ai_dmnet.obj + 0001:0000becf _AIEnter_Battle_Chase 2000cecf f ai_dmnet.obj + 0001:0000bf09 _AINode_Battle_Chase 2000cf09 f ai_dmnet.obj + 0001:0000c451 _AIEnter_Battle_Retreat 2000d451 f ai_dmnet.obj + 0001:0000c47d _AINode_Battle_Retreat 2000d47d f ai_dmnet.obj + 0001:0000cacb _AIEnter_Battle_NBG 2000dacb f ai_dmnet.obj + 0001:0000caf7 _AINode_Battle_NBG 2000daf7 f ai_dmnet.obj + 0001:0000d030 _BotSetUserInfo 2000e030 f ai_dmq3.obj + 0001:0000d094 _BotCTFCarryingFlag 2000e094 f ai_dmq3.obj + 0001:0000d0ce _BotTeam 2000e0ce f ai_dmq3.obj + 0001:0000d166 _BotOppositeTeam 2000e166 f ai_dmq3.obj + 0001:0000d19b _BotEnemyFlag 2000e19b f ai_dmq3.obj + 0001:0000d1bd _BotTeamFlag 2000e1bd f ai_dmq3.obj + 0001:0000d1df _EntityIsDead 2000e1df f ai_dmq3.obj + 0001:0000d226 _EntityCarriesFlag 2000e226 f ai_dmq3.obj + 0001:0000d25a _EntityIsInvisible 2000e25a f ai_dmq3.obj + 0001:0000d289 _EntityIsShooting 2000e289 f ai_dmq3.obj + 0001:0000d2a7 _EntityIsChatting 2000e2a7 f ai_dmq3.obj + 0001:0000d2c5 _EntityHasQuad 2000e2c5 f ai_dmq3.obj + 0001:0000d2e0 _BotRememberLastOrderedTask 2000e2e0 f ai_dmq3.obj + 0001:0000d347 _BotSetTeamStatus 2000e347 f ai_dmq3.obj + 0001:0000d34c _BotSetLastOrderedTask 2000e34c f ai_dmq3.obj + 0001:0000d4fc _BotRefuseOrder 2000e4fc f ai_dmq3.obj + 0001:0000d57d _BotCTFSeekGoals 2000e57d f ai_dmq3.obj + 0001:0000e10c _BotCTFRetreatGoals 2000f10c f ai_dmq3.obj + 0001:0000e190 _BotTeamGoals 2000f190 f ai_dmq3.obj + 0001:0000e1d4 _BotPointAreaNum 2000f1d4 f ai_dmq3.obj + 0001:0000e246 _ClientName 2000f246 f ai_dmq3.obj + 0001:0000e2d8 _ClientSkin 2000f2d8 f ai_dmq3.obj + 0001:0000e35a _ClientFromName 2000f35a f ai_dmq3.obj + 0001:0000e41b _ClientOnSameTeamFromName 2000f41b f ai_dmq3.obj + 0001:0000e4fa _stristr 2000f4fa f ai_dmq3.obj + 0001:0000e58a _EasyClientName 2000f58a f ai_dmq3.obj + 0001:0000e813 _BotSynonymContext 2000f813 f ai_dmq3.obj + 0001:0000e853 _BotEatBeans 2000f853 f ai_dmq3.obj + 0001:0000e8e6 _BotChooseWeapon 2000f8e6 f ai_dmq3.obj + 0001:0000e980 _BotSetupForMovement 2000f980 f ai_dmq3.obj + 0001:0000eadb _BotCheckItemPickup 2000fadb f ai_dmq3.obj + 0001:0000eae0 _BotUpdateInventory 2000fae0 f ai_dmq3.obj + 0001:0000ef46 _BotUpdateBattleInventory 2000ff46 f ai_dmq3.obj + 0001:0000efd1 _BotBattleUseItems 2000ffd1 f ai_dmq3.obj + 0001:0000f0b0 _BotSetTeleportTime 200100b0 f ai_dmq3.obj + 0001:0000f0e9 _BotIsDead 200100e9 f ai_dmq3.obj + 0001:0000f0fc _BotIsObserver 200100fc f ai_dmq3.obj + 0001:0000f164 _BotIntermission 20010164 f ai_dmq3.obj + 0001:0000f1a1 _BotInLavaOrSlime 200101a1 f ai_dmq3.obj + 0001:0000f1ea _BotCreateWayPoint 200101ea f ai_dmq3.obj + 0001:0000f2df _BotFindWayPoint 200102df f ai_dmq3.obj + 0001:0000f31e _BotFreeWaypoints 2001031e f ai_dmq3.obj + 0001:0000f354 _BotInitWaypoints 20010354 f ai_dmq3.obj + 0001:0000f3a5 _TeamPlayIsOn 200103a5 f ai_dmq3.obj + 0001:0000f3b6 _BotAggression 200103b6 f ai_dmq3.obj + 0001:0000f5b0 _BotFeelingBad 200105b0 f ai_dmq3.obj + 0001:0000f60b _BotWantsToRetreat 2001060b f ai_dmq3.obj + 0001:0000f6a9 _BotWantsToChase 200106a9 f ai_dmq3.obj + 0001:0000f738 _BotWantsToHelp 20010738 f ai_dmq3.obj + 0001:0000f742 _BotCanAndWantsToRocketJump 20010742 f ai_dmq3.obj + 0001:0000f7ed _BotHasPersistantPowerupAndWeapon 200107ed f ai_dmq3.obj + 0001:0000f8c1 _BotGoCamp 200108c1 f ai_dmq3.obj + 0001:0000f9b7 _BotWantsToCamp 200109b7 f ai_dmq3.obj + 0001:0000fbec _BotDontAvoid 20010bec f ai_dmq3.obj + 0001:0000fc40 _BotGoForPowerups 20010c40 f ai_dmq3.obj + 0001:0000fca7 _BotRoamGoal 20010ca7 f ai_dmq3.obj + 0001:0001001e _BotAttackMove 2001101e f ai_dmq3.obj + 0001:00010818 _BotSameTeam 20011818 f ai_dmq3.obj + 0001:000109aa _InFieldOfVision 200119aa f ai_dmq3.obj + 0001:00010aac _BotEntityVisible 20011aac f ai_dmq3.obj + 0001:000110af _BotFindEnemy 200120af f ai_dmq3.obj + 0001:0001160f _BotTeamFlagCarrierVisible 2001260f f ai_dmq3.obj + 0001:0001170a _BotTeamFlagCarrier 2001270a f ai_dmq3.obj + 0001:000117b6 _BotEnemyFlagCarrierVisible 200127b6 f ai_dmq3.obj + 0001:000118b1 _BotVisibleTeamMatesAndEnemies 200128b1 f ai_dmq3.obj + 0001:00011a3f _BotAimAtEnemy 20012a3f f ai_dmq3.obj + 0001:00012f04 _BotCheckAttack 20013f04 f ai_dmq3.obj + 0001:000134c1 _BotMapScripts 200144c1 f ai_dmq3.obj + 0001:00013911 _BotSetMovedir 20014911 f ai_dmq3.obj + 0001:000139dd _BotModelMinsMaxs 200149dd f ai_dmq3.obj + 0001:00013b50 _BotFuncButtonActivateGoal 20014b50 f ai_dmq3.obj + 0001:000147b3 _BotFuncDoorActivateGoal 200157b3 f ai_dmq3.obj + 0001:00014985 _BotTriggerMultipleActivateGoal 20015985 f ai_dmq3.obj + 0001:00014c93 _BotPopFromActivateGoalStack 20015c93 f ai_dmq3.obj + 0001:00014cfa _BotPushOntoActivateGoalStack 20015cfa f ai_dmq3.obj + 0001:00014e06 _BotClearActivateGoalStack 20015e06 f ai_dmq3.obj + 0001:00014e25 _BotEnableActivateGoalAreas 20015e25 f ai_dmq3.obj + 0001:00014e8e _BotIsGoingToActivateEntity 20015e8e f ai_dmq3.obj + 0001:00014f5a _BotGetActivateGoal 20015f5a f ai_dmq3.obj + 0001:0001588c _BotGoForActivateGoal 2001688c f ai_dmq3.obj + 0001:00015940 _BotPrintActivateGoalInfo 20016940 f ai_dmq3.obj + 0001:00015a3e _BotRandomMove 20016a3e f ai_dmq3.obj + 0001:00015ac6 _BotAIBlocked 20016ac6 f ai_dmq3.obj + 0001:00015f08 _BotAIPredictObstacles 20016f08 f ai_dmq3.obj + 0001:000160a0 _BotCheckConsoleMessages 200170a0 f ai_dmq3.obj + 0001:000164ca _BotCheckForGrenades 200174ca f ai_dmq3.obj + 0001:00016506 _BotCheckForBeartraps 20017506 f ai_dmq3.obj + 0001:00016615 _BotCheckForAutosentrys 20017615 f ai_dmq3.obj + 0001:00016725 _BotCheckForGravityWells 20017725 f ai_dmq3.obj + 0001:0001679c _BotCheckEvents 2001779c f ai_dmq3.obj + 0001:00016c49 _BotCheckSnapshot 20017c49 f ai_dmq3.obj + 0001:00016d51 _BotCheckAir 20017d51 f ai_dmq3.obj + 0001:00016d8b _BotAlternateRoute 20017d8b f ai_dmq3.obj + 0001:00016e30 _BotGetAlternateRouteGoal 20017e30 f ai_dmq3.obj + 0001:00016f6c _BotSetupAlternativeRouteGoals 20017f6c f ai_dmq3.obj + 0001:00016f86 _BotDeathmatchAI 20017f86 f ai_dmq3.obj + 0001:00017351 _BotSetEntityNumForGoalWithModel 20018351 f ai_dmq3.obj + 0001:00017424 _BotSetEntityNumForGoal 20018424 f ai_dmq3.obj + 0001:000174dd _BotGoalForBSPEntity 200184dd f ai_dmq3.obj + 0001:00017672 _BotSetupDeathmatchAI 20018672 f ai_dmq3.obj + 0001:00017876 _BotShutdownDeathmatchAI 20018876 f ai_dmq3.obj + 0001:00017890 _BotAI_Print 20018890 f ai_main.obj + 0001:00017987 _BotAI_Trace 20018987 f ai_main.obj + 0001:00017a50 _BotAI_GetClientState 20018a50 f ai_main.obj + 0001:00017aa9 _BotAI_GetEntityState 20018aa9 f ai_main.obj + 0001:00017b23 _BotAI_GetSnapshotEntity 20018b23 f ai_main.obj + 0001:00017b72 _BotAI_BotInitialChat 20018b72 f ai_main.obj + 0001:00017c32 _BotTestAAS 20018c32 f ai_main.obj + 0001:00017d05 _BotReportStatus 20018d05 f ai_main.obj + 0001:000180c6 _BotTeamplayReport 200190c6 f ai_main.obj + 0001:000182d9 _BotSetInfoConfigString 200192d9 f ai_main.obj + 0001:00018681 _BotUpdateInfoConfigStrings 20019681 f ai_main.obj + 0001:00018760 _BotInterbreedBots 20019760 f ai_main.obj + 0001:00018911 _BotWriteInterbreeded 20019911 f ai_main.obj + 0001:000189cf _BotInterbreedEndMatch 200199cf f ai_main.obj + 0001:00018a46 _BotInterbreeding 20019a46 f ai_main.obj + 0001:00018b69 _BotEntityInfo 20019b69 f ai_main.obj + 0001:00018b7e _NumBots 20019b7e f ai_main.obj + 0001:00018b88 _BotTeamLeader 20019b88 f ai_main.obj + 0001:00018bd3 _AngleDifference 20019bd3 f ai_main.obj + 0001:00018c2e _BotChangeViewAngle 20019c2e f ai_main.obj + 0001:00018d07 _BotChangeViewAngles 20019d07 f ai_main.obj + 0001:00019055 _BotInputToUserCommand 2001a055 f ai_main.obj + 0001:0001945d _BotUpdateInput 2001a45d f ai_main.obj + 0001:0001958e _BotAIRegularUpdate 2001a58e f ai_main.obj + 0001:000195bd _RemoveColorEscapeSequences 2001a5bd f ai_main.obj + 0001:00019664 _BotAI 2001a664 f ai_main.obj + 0001:00019a7a _BotScheduleBotThink 2001aa7a f ai_main.obj + 0001:00019ae9 _BotWriteSessionData 2001aae9 f ai_main.obj + 0001:00019c01 _BotReadSessionData 2001ac01 f ai_main.obj + 0001:00019cfc _BotAISetupClient 2001acfc f ai_main.obj + 0001:0001a113 _BotAIShutdownClient 2001b113 f ai_main.obj + 0001:0001a234 _BotResetState 2001b234 f ai_main.obj + 0001:0001a479 _BotAILoadMap 2001b479 f ai_main.obj + 0001:0001a535 _BotAIStartFrame 2001b535 f ai_main.obj + 0001:0001abc1 _BotInitLibrary 2001bbc1 f ai_main.obj + 0001:0001b0c5 _BotAISetup 2001c0c5 f ai_main.obj + 0001:0001b249 _BotAIShutdown 2001c249 f ai_main.obj + 0001:0001b390 _BotValidTeamLeader 2001c390 f ai_team.obj + 0001:0001b3ce _BotNumTeamMates 2001c3ce f ai_team.obj + 0001:0001b4ee _BotClientTravelTimeToGoal 2001c4ee f ai_team.obj + 0001:0001b54c _BotSortTeamMatesByBaseTravelTime 2001c54c f ai_team.obj + 0001:0001b79a _BotSetTeamMateTaskPreference 2001c79a f ai_team.obj + 0001:0001b7de _BotGetTeamMateTaskPreference 2001c7de f ai_team.obj + 0001:0001b839 _BotSortTeamMatesByTaskPreference 2001c839 f ai_team.obj + 0001:0001b9b3 _BotSayTeamOrderAlways 2001c9b3 f ai_team.obj + 0001:0001ba5d _BotSayTeamOrder 2001ca5d f ai_team.obj + 0001:0001ba72 _BotVoiceChat 2001ca72 f ai_team.obj + 0001:0001ba77 _BotVoiceChatOnly 2001ca77 f ai_team.obj + 0001:0001ba7c _BotSayVoiceTeamOrder 2001ca7c f ai_team.obj + 0001:0001ba81 _BotCTFOrders_BothFlagsNotAtBase 2001ca81 f ai_team.obj + 0001:0001bfb4 _BotCTFOrders_FlagNotAtBase 2001cfb4 f ai_team.obj + 0001:0001c6d1 _BotCTFOrders_EnemyFlagNotAtBase 2001d6d1 f ai_team.obj + 0001:0001cb74 _BotCTFOrders_BothFlagsAtBase 2001db74 f ai_team.obj + 0001:0001d28c _BotCTFOrders 2001e28c f ai_team.obj + 0001:0001d335 _BotCreateGroup 2001e335 f ai_team.obj + 0001:0001d3e1 _BotTeamOrders 2001e3e1 f ai_team.obj + 0001:0001d62a _FindHumanTeamLeader 2001e62a f ai_team.obj + 0001:0001d6df _BotTeamAI 2001e6df f ai_team.obj + 0001:0001dbd0 _BotVoiceChat_GetFlag 2001ebd0 f ai_vcmd.obj + 0001:0001dcba _BotVoiceChat_Offense 2001ecba f ai_vcmd.obj + 0001:0001dd8a _BotVoiceChat_Defend 2001ed8a f ai_vcmd.obj + 0001:0001dea2 _BotVoiceChat_DefendFlag 2001eea2 f ai_vcmd.obj + 0001:0001debb _BotVoiceChat_Patrol 2001eebb f ai_vcmd.obj + 0001:0001df49 _BotVoiceChat_Camp 2001ef49 f ai_vcmd.obj + 0001:0001e139 _BotVoiceChat_FollowMe 2001f139 f ai_vcmd.obj + 0001:0001e345 _BotVoiceChat_FollowFlagCarrier 2001f345 f ai_vcmd.obj + 0001:0001e382 _BotVoiceChat_ReturnFlag 2001f382 f ai_vcmd.obj + 0001:0001e432 _BotVoiceChat_StartLeader 2001f432 f ai_vcmd.obj + 0001:0001e44e _BotVoiceChat_StopLeader 2001f44e f ai_vcmd.obj + 0001:0001e4a2 _BotVoiceChat_WhoIsLeader 2001f4a2 f ai_vcmd.obj + 0001:0001e52d _BotVoiceChat_WantOnDefense 2001f52d f ai_vcmd.obj + 0001:0001e5d9 _BotVoiceChat_WantOnOffense 2001f5d9 f ai_vcmd.obj + 0001:0001e685 _BotVoiceChat_Dummy 2001f685 f ai_vcmd.obj + 0001:0001e68a _BotVoiceChatCommand 2001f68a f ai_vcmd.obj + 0001:0001e8f0 _BG_FindItemForPowerup 2001f8f0 f bg_misc.obj + 0001:0001e964 _BG_FindItemForHoldable 2001f964 f bg_misc.obj + 0001:0001e9c9 _BG_FindItemForWeapon 2001f9c9 f bg_misc.obj + 0001:0001ea1b _BG_FindItem 2001fa1b f bg_misc.obj + 0001:0001ea5d _BG_PlayerTouchesItem 2001fa5d f bg_misc.obj + 0001:0001eb0b _BG_CanItemBeGrabbed 2001fb0b f bg_misc.obj + 0001:0001ee30 _BG_EvaluateTrajectory 2001fe30 f bg_misc.obj + 0001:0001f0ff _BG_EvaluateTrajectoryDelta 200200ff f bg_misc.obj + 0001:0001f30f _BG_AddPredictableEventToPlayerstate 2002030f f bg_misc.obj + 0001:0001f3b3 _BG_TouchJumpPad 200203b3 f bg_misc.obj + 0001:0001f495 _BG_PlayerStateToEntityState 20020495 f bg_misc.obj + 0001:0001f8da _BG_PlayerStateToEntityStateExtraPolate 200208da f bg_misc.obj + 0001:0001fd40 _PM_AddEvent 20020d40 f bg_pmove.obj + 0001:0001fd5b _PM_AddTouchEnt 20020d5b f bg_pmove.obj + 0001:0001fddb _PM_ClipVelocity 20020ddb f bg_pmove.obj + 0001:0001fe6d _PM_UpdateViewAngles 20020e6d f bg_pmove.obj + 0001:0001ff5c _PM_NextHoldable 20020f5c f bg_pmove.obj + 0001:00020045 _PmoveSingle 20021045 f bg_pmove.obj + 0001:000236c3 _Pmove 200246c3 f bg_pmove.obj + 0001:000237b0 _PM_SlideMove 200247b0 f bg_slidemove.obj + 0001:00024114 _PM_StepSlideMove 20025114 f bg_slidemove.obj + 0001:00024550 _P_DamageFeedback 20025550 f g_active.obj + 0001:000246d8 _P_WorldEffects 200256d8 f g_active.obj + 0001:00024971 _G_SetClientSound 20025971 f g_active.obj + 0001:000249bc _ClientImpacts 200259bc f g_active.obj + 0001:00024ab2 _G_TouchTriggers 20025ab2 f g_active.obj + 0001:00024dbc _SpectatorThink 20025dbc f g_active.obj + 0001:00024f05 _ClientInactivityTimer 20025f05 f g_active.obj + 0001:00025037 _ClientTimerActions 20026037 f g_active.obj + 0001:00025249 _ClientIntermissionThink 20026249 f g_active.obj + 0001:000252c1 _ClientEvents 200262c1 f g_active.obj + 0001:00025640 _SendPendingPredictableEvents 20026640 f g_active.obj + 0001:0002573d _ClientThink_real 2002673d f g_active.obj + 0001:00025fa2 _ClientThink 20026fa2 f g_active.obj + 0001:00026010 _G_RunClient 20027010 f g_active.obj + 0001:00026051 _SpectatorClientEndFrame 20027051 f g_active.obj + 0001:000261cf _ClientEndFrame 200271cf f g_active.obj + 0001:00026340 _UpdateTournamentInfo 20027340 f g_arenas.obj + 0001:00026645 _SpawnModelsOnVictoryPads 20027645 f g_arenas.obj + 0001:000271d9 _Svcmd_AbortPodium_f 200281d9 f g_arenas.obj + 0001:00027220 _trap_Cvar_VariableValue 20028220 f g_bot.obj + 0001:00027254 _G_ParseInfos 20028254 f g_bot.obj + 0001:00027415 _G_GetArenaInfoByMap 20028415 f g_bot.obj + 0001:00027473 _G_AddRandomBot 20028473 f g_bot.obj + 0001:00027709 _G_RemoveRandomBot 20028709 f g_bot.obj + 0001:000277f3 _G_CountHumanPlayers 200287f3 f g_bot.obj + 0001:00027884 _G_CountBotPlayers 20028884 f g_bot.obj + 0001:00027985 _G_CheckMinimumPlayers 20028985 f g_bot.obj + 0001:00027b91 _G_CheckBotSpawn 20028b91 f g_bot.obj + 0001:00027ce9 _G_RemoveQueuedBotBegin 20028ce9 f g_bot.obj + 0001:00027d2a _G_BotConnect 20028d2a f g_bot.obj + 0001:00027df9 _Svcmd_AddBot_f 20028df9 f g_bot.obj + 0001:000284ba _botdriverThink 200294ba f g_bot.obj + 0001:0002867e _G_AddPrivateBot 2002967e f g_bot.obj + 0001:00028c47 _Svcmd_BotList_f 20029c47 f g_bot.obj + 0001:00028df1 _G_GetBotInfoByNumber 20029df1 f g_bot.obj + 0001:00028e2f _G_GetBotInfoByName 20029e2f f g_bot.obj + 0001:00028e95 _G_InitBots 20029e95 f g_bot.obj + 0001:00029720 _SortHub 2002a720 f g_client.obj + 0001:0002975c _func_hubvote_think 2002a75c f g_client.obj + 0001:00029ba5 _spawn_hubvote 2002aba5 f g_client.obj + 0001:00029c03 _SP_info_player_postvote 2002ac03 f g_client.obj + 0001:00029c6d _SP_info_player_deathmatch 2002ac6d f g_client.obj + 0001:00029d09 _SP_info_player_start 2002ad09 f g_client.obj + 0001:00029d27 _SP_info_player_intermission 2002ad27 f g_client.obj + 0001:00029d2c _SpotWouldTelefrag 2002ad2c f g_client.obj + 0001:00029e05 _SelectNearestDeathmatchSpawnPoint 2002ae05 f g_client.obj + 0001:00029f52 _SelectRandomDeathmatchSpawnPoint 2002af52 f g_client.obj + 0001:0002a055 _SelectRandomDistantSpawnPoint 2002b055 f g_client.obj + 0001:0002a21b _SelectClosestSpawnPoint 2002b21b f g_client.obj + 0001:0002a37a _SelectRandomFurthestSpawnPoint 2002b37a f g_client.obj + 0001:0002a71f _SelectRandomDistantSpawnPoint2 2002b71f f g_client.obj + 0001:0002a926 _SelectSpawnPoint 2002b926 f g_client.obj + 0001:0002aa38 _SelectInitialSpawnPoint 2002ba38 f g_client.obj + 0001:0002ab41 _SelectSpectatorSpawnPoint 2002bb41 f g_client.obj + 0001:0002ab91 _InitBodyQue 2002bb91 f g_client.obj + 0001:0002abee _BodySink 2002bbee f g_client.obj + 0001:0002ac49 _CopyToBodyQue 2002bc49 f g_client.obj + 0001:0002afc9 _SetClientViewAngle 2002bfc9 f g_client.obj + 0001:0002b097 _respawn 2002c097 f g_client.obj + 0001:0002b0e3 _TeamCount 2002c0e3 f g_client.obj + 0001:0002b15e _TeamLeader 2002c15e f g_client.obj + 0001:0002b1db _PickTeam 2002c1db f g_client.obj + 0001:0002b23f _ClientUserinfoChanged 2002c23f f g_client.obj + 0001:0002ba4d _ClientConnect 2002ca4d f g_client.obj + 0001:0002bc99 _ClientBegin 2002cc99 f g_client.obj + 0001:0002bdfc _ClientSpawn 2002cdfc f g_client.obj + 0001:0002c8ac _ClientDisconnect 2002d8ac f g_client.obj + 0001:0002cb00 _DeathmatchScoreboardMessage 2002db00 f g_cmds.obj + 0001:0002cda9 _Cmd_Score_f 2002dda9 f g_cmds.obj + 0001:0002cdba _CheatsOk 2002ddba f g_cmds.obj + 0001:0002ce2f _ConcatArgs 2002de2f f g_cmds.obj + 0001:0002cf31 _SanitizeString 2002df31 f g_cmds.obj + 0001:0002cfa3 _ClientNumberFromString 2002dfa3 f g_cmds.obj + 0001:0002d16e _Cmd_Give_f 2002e16e f g_cmds.obj + 0001:0002d5e6 _Cmd_God_f 2002e5e6 f g_cmds.obj + 0001:0002d660 _Cmd_Notarget_f 2002e660 f g_cmds.obj + 0001:0002d6da _Cmd_Noclip_f 2002e6da f g_cmds.obj + 0001:0002d765 _Cmd_LevelShot_f 2002e765 f g_cmds.obj + 0001:0002d7c8 _Cmd_TeamTask_f 2002e7c8 f g_cmds.obj + 0001:0002d895 _Cmd_Kill_f 2002e895 f g_cmds.obj + 0001:0002d90c _BroadcastTeamChange 2002e90c f g_cmds.obj + 0001:0002d9d7 _SetTeam 2002e9d7 f g_cmds.obj + 0001:0002dddb _StopFollowing 2002eddb f g_cmds.obj + 0001:0002de67 _Cmd_Team_f 2002ee67 f g_cmds.obj + 0001:0002e011 _Cmd_Follow_f 2002f011 f g_cmds.obj + 0001:0002e15d _Cmd_FollowCycle_f 2002f15d f g_cmds.obj + 0001:0002e299 _G_Say 2002f299 f g_cmds.obj + 0001:0002e6ea _G_Voice 2002f6ea f g_cmds.obj + 0001:0002e87d _Cmd_GameCommand_f 2002f87d f g_cmds.obj + 0001:0002e956 _Cmd_Where_f 2002f956 f g_cmds.obj + 0001:0002e992 _Cmd_CallVote_f 2002f992 f g_cmds.obj + 0001:0002efbf _Cmd_Vote_f 2002ffbf f g_cmds.obj + 0001:0002f124 _Cmd_CallTeamVote_f 20030124 f g_cmds.obj + 0001:0002f873 _Cmd_TeamVote_f 20030873 f g_cmds.obj + 0001:0002fa37 _Cmd_SetViewpos_f 20030a37 f g_cmds.obj + 0001:0002fb49 _Cmd_Stats_f 20030b49 f g_cmds.obj + 0001:0002fb4e _Cmd_DragonDeploy_f 20030b4e f g_cmds.obj + 0001:0002fcce _ClientCommand 20030cce f g_cmds.obj + 0001:00030a20 _PrivateBotOwnerTeam 20031a20 f g_combat.obj + 0001:00030aa6 _ScorePlum 20031aa6 f g_combat.obj + 0001:00030af9 _AddScore 20031af9 f g_combat.obj + 0001:00030c2f _TossClientItems 20031c2f f g_combat.obj + 0001:000311f8 _LookAtKiller 200321f8 f g_combat.obj + 0001:000312d7 _GibEntity 200322d7 f g_combat.obj + 0001:00031312 _body_die 20032312 f g_combat.obj + 0001:0003134b _CheckAlmostCapture 2003234b f g_combat.obj + 0001:00031519 _CheckAlmostScored 20032519 f g_combat.obj + 0001:00031621 _player_die 20032621 f g_combat.obj + 0001:00031fac _CheckArmor 20032fac f g_combat.obj + 0001:0003203f _RaySphereIntersections 2003303f f g_combat.obj + 0001:00032242 _G_Damage 20033242 f g_combat.obj + 0001:0003299a _CanDamage 2003399a f g_combat.obj + 0001:00032b72 _G_RadiusDamage 20033b72 f g_combat.obj + 0001:00032e30 _Pickup_Powerup 20033e30 f g_items.obj + 0001:0003305f _Pickup_Holdable 2003405f f g_items.obj + 0001:0003322b _Add_Ammo 2003422b f g_items.obj + 0001:00033405 _Pickup_Ammo 20034405 f g_items.obj + 0001:0003349e _Pickup_Weapon 2003449e f g_items.obj + 0001:000336df _Pickup_Health 200346df f g_items.obj + 0001:000337e5 _Pickup_Armor 200347e5 f g_items.obj + 0001:0003385b _PKA_RestoreItem 2003485b f g_items.obj + 0001:000339f5 _RespawnItem 200349f5 f g_items.obj + 0001:00033c79 _PlayerRadiateThink 20034c79 f g_items.obj + 0001:00033eac _ItemRadiateThink 20034eac f g_items.obj + 0001:00033fb9 _Touch_Item 20034fb9 f g_items.obj + 0001:00034872 _Remove_Holdable 20035872 f g_items.obj + 0001:000348bc _Remove_Ammo 200358bc f g_items.obj + 0001:000349db _Remove_Weapon 200359db f g_items.obj + 0001:00034bae _Remove_Health 20035bae f g_items.obj + 0001:00034c00 _Remove_Armor 20035c00 f g_items.obj + 0001:00034c7a _Remove_Powerup 20035c7a f g_items.obj + 0001:00034cc7 _Touch_Item_Remove 20035cc7 f g_items.obj + 0001:00034eb8 _LaunchItem 20035eb8 f g_items.obj + 0001:00035061 _Drop_Item 20036061 f g_items.obj + 0001:0003531e _Throw_Item 2003631e f g_items.obj + 0001:0003537a _Use_Item 2003637a f g_items.obj + 0001:0003538b _FinishSpawningItem 2003638b f g_items.obj + 0001:00035866 _G_CheckTeamItems 20036866 f g_items.obj + 0001:000358f6 _ClearRegisteredItems 200368f6 f g_items.obj + 0001:00035948 _RegisterItem 20036948 f g_items.obj + 0001:0003597b _SaveRegisteredItems 2003697b f g_items.obj + 0001:00035a2c _G_ItemDisabled 20036a2c f g_items.obj + 0001:00035a61 _G_SpawnItem 20036a61 f g_items.obj + 0001:00035e3c _G_SlideItem 20036e3c f g_items.obj + 0001:00035fb4 _G_BounceItem 20036fb4 f g_items.obj + 0001:000361cd _G_RunItem 200371cd f g_items.obj + 0001:00036380 _vmMain 20037380 f g_main.obj + 0001:0003648a _G_Printf 2003748a f g_main.obj + 0001:000364d3 _G_Error 200374d3 f g_main.obj + 0001:0003651c _G_FindTeams 2003751c f g_main.obj + 0001:000366de _G_RemapTeamShaders 200376de f g_main.obj + 0001:000366e3 _G_RegisterCvars 200376e3 f g_main.obj + 0001:000367b9 _G_UpdateCvars 200377b9 f g_main.obj + 0001:0003687c _G_InitGame 2003787c f g_main.obj + 0001:00036bee _G_ShutdownGame 20037bee f g_main.obj + 0001:00036c53 _Com_Error 20037c53 f g_main.obj + 0001:00036ca1 _Com_Printf 20037ca1 f g_main.obj + 0001:00036cef _AddTournamentPlayer 20037cef f g_main.obj + 0001:00036e11 _RemoveTournamentLoser 20037e11 f g_main.obj + 0001:00036e63 _RemoveTournamentWinner 20037e63 f g_main.obj + 0001:00036eb5 _AdjustTournamentScores 20037eb5 f g_main.obj + 0001:00036f75 _SortRanks 20037f75 f g_main.obj + 0001:000370c1 _CalculateRanks 200380c1 f g_main.obj + 0001:000375c4 _SendScoreboardMessageToAllClients 200385c4 f g_main.obj + 0001:0003761b _MoveClientToIntermission 2003861b f g_main.obj + 0001:00037759 _FindIntermissionPoint 20038759 f g_main.obj + 0001:00037849 _BeginIntermission 20038849 f g_main.obj + 0001:000378f8 _ExitLevel 200388f8 f g_main.obj + 0001:00037a33 _G_LogPrintf 20038a33 f g_main.obj + 0001:00037b52 _LogExit 20038b52 f g_main.obj + 0001:00037c74 _CheckIntermissionExit 20038c74 f g_main.obj + 0001:00037df4 _ScoreIsTied 20038df4 f g_main.obj + 0001:00037e69 _CheckExitRules 20038e69 f g_main.obj + 0001:000380fe _CheckTournament 200390fe f g_main.obj + 0001:000383ca _CheckVote 200393ca f g_main.obj + 0001:000384b2 _PrintTeam 200394b2 f g_main.obj + 0001:00038505 _SetLeader 20039505 f g_main.obj + 0001:0003867b _CheckTeamLeader 2003967b f g_main.obj + 0001:000387a9 _CheckTeamVote 200397a9 f g_main.obj + 0001:000388f6 _CheckCvars 200398f6 f g_main.obj + 0001:0003895b _G_RunThink 2003995b f g_main.obj + 0001:000389c6 _G_RunFrame 200399c6 f g_main.obj + 0001:00038d00 _G_Alloc 20039d00 f g_mem.obj + 0001:00038d85 _G_InitMemory 20039d85 f g_mem.obj + 0001:00038d94 _Svcmd_GameMem_f 20039d94 f g_mem.obj + 0001:00038dc0 _SP_info_camp 20039dc0 f g_misc.obj + 0001:00038dd8 _SP_info_null 20039dd8 f g_misc.obj + 0001:00038de9 _SP_info_notnull 20039de9 f g_misc.obj + 0001:00038e01 _SP_light 20039e01 f g_misc.obj + 0001:00038e12 _TeleportPlayer 20039e12 f g_misc.obj + 0001:00039073 _SP_misc_teleporter_dest 2003a073 f g_misc.obj + 0001:00039078 _SP_misc_model 2003a078 f g_misc.obj + 0001:00039089 _locateCamera 2003a089 f g_misc.obj + 0001:000391f8 _SP_misc_portal_surface 2003a1f8 f g_misc.obj + 0001:000392bf _SP_misc_portal_camera 2003a2bf f g_misc.obj + 0001:00039354 _Use_Shooter 2003a354 f g_misc.obj + 0001:000397ad _InitShooter 2003a7ad f g_misc.obj + 0001:00039933 _SP_shooter_rocket 2003a933 f g_misc.obj + 0001:00039946 _SP_shooter_plasma 2003a946 f g_misc.obj + 0001:00039959 _SP_shooter_grenade 2003a959 f g_misc.obj + 0001:0003996c _SP_shooter_shell 2003a96c f g_misc.obj + 0001:0003997f _SP_shooter_rail 2003a97f f g_misc.obj + 0001:00039992 _SP_shooter_lightning 2003a992 f g_misc.obj + 0001:000399a5 _SP_shooter_bullet 2003a9a5 f g_misc.obj + 0001:000399b8 _SP_shooter_nail 2003a9b8 f g_misc.obj + 0001:000399cb _SP_shooter_beartrap 2003a9cb f g_misc.obj + 0001:000399e0 _alt_vtos 2003a9e0 f g_missile.obj + 0001:00039a55 _G_BounceMissile 2003aa55 f g_missile.obj + 0001:00039db4 _G_ExplodeMissile 2003adb4 f g_missile.obj + 0001:00039ef8 _G_MissileImpact 2003aef8 f g_missile.obj + 0001:0003a73a _G_RunMissile 2003b73a f g_missile.obj + 0001:0003af1c _AutoSentry_BaseDeath 2003bf1c f g_missile.obj + 0001:0003af41 _AutoSentry_TurretDeath 2003bf41 f g_missile.obj + 0001:0003b00a _AutoSentry_BaseDie 2003c00a f g_missile.obj + 0001:0003b01b _AutoSentry_TurretDie 2003c01b f g_missile.obj + 0001:0003b02c _AutoSentry_Touch 2003c02c f g_missile.obj + 0001:0003b031 _AutoSentryBase_Think 2003c031 f g_missile.obj + 0001:0003b174 _AutoSentryAimed 2003c174 f g_missile.obj + 0001:0003b296 _AutoSentry_Fire 2003c296 f g_missile.obj + 0001:0003b588 _AutoSentryAttack_Think 2003c588 f g_missile.obj + 0001:0003b8a4 _Find_Sentry 2003c8a4 f g_missile.obj + 0001:0003b908 _AutoSentryFindTarget 2003c908 f g_missile.obj + 0001:0003bc74 _AutoSentryTurret_Rotate 2003cc74 f g_missile.obj + 0001:0003bd91 _AutoSentryTurret_Think 2003cd91 f g_missile.obj + 0001:0003be97 _AutoSentryEnable_Think 2003ce97 f g_missile.obj + 0001:0003c224 _AutoSentryDeploy_Think 2003d224 f g_missile.obj + 0001:0003c2ac _fire_autosentry 2003d2ac f g_missile.obj + 0001:0003c53a _PersonalSentry_Exit 2003d53a f g_missile.obj + 0001:0003c669 _PersonalSentry_Fire 2003d669 f g_missile.obj + 0001:0003c8d4 _PersonalSentryAttack_Think 2003d8d4 f g_missile.obj + 0001:0003cc7c _PersonalSentry_Think 2003dc7c f g_missile.obj + 0001:0003cf54 _G_AddPersonalSentry 2003df54 f g_missile.obj + 0001:0003d215 _ignore_entity 2003e215 f g_missile.obj + 0001:0003d304 _G_airfistThink 2003e304 f g_missile.obj + 0001:0003da53 _fire_airfist 2003ea53 f g_missile.obj + 0001:0003dd50 _NailTouch 2003ed50 f g_missile.obj + 0001:0003ddc4 _NailDeath 2003edc4 f g_missile.obj + 0001:0003ddf8 _fire_nailgun 2003edf8 f g_missile.obj + 0001:0003dfc0 _GravityTouch 2003efc0 f g_missile.obj + 0001:0003dffd _GravityThink 2003effd f g_missile.obj + 0001:0003ea9b _Release_Gravity 2003fa9b f g_missile.obj + 0001:0003ecc1 _fire_gravity 2003fcc1 f g_missile.obj + 0001:0003eec5 _BearTrapDeath 2003fec5 f g_missile.obj + 0001:0003ef33 _BearTrapKill 2003ff33 f g_missile.obj + 0001:0003ef44 _BearTrapFollow 2003ff44 f g_missile.obj + 0001:0003f2c3 _BearTrapTouch 200402c3 f g_missile.obj + 0001:0003f45c _Beartrap_Think 2004045c f g_missile.obj + 0001:0003f52b _fire_beartrap 2004052b f g_missile.obj + 0001:0003f90c _fire_plasma 2004090c f g_missile.obj + 0001:0003facc _fire_grenade 20040acc f g_missile.obj + 0001:0003fc95 _fire_bfg 20040c95 f g_missile.obj + 0001:0003fe55 _fire_rocket 20040e55 f g_missile.obj + 0001:00040015 _DragonReturn 20041015 f g_missile.obj + 0001:00040403 _DragonTouch 20041403 f g_missile.obj + 0001:000405a7 _fire_grapple 200415a7 f g_missile.obj + 0001:000407d8 _fire_dragon_deploy 200417d8 f g_missile.obj + 0001:00040bde _activate_dragon_deploy 20041bde f g_missile.obj + 0001:00040e70 _G_TestEntityPosition 20041e70 f g_mover.obj + 0001:00040f66 _G_CreateRotationMatrix 20041f66 f g_mover.obj + 0001:00040f98 _G_TransposeMatrix 20041f98 f g_mover.obj + 0001:00040ffa _G_RotatePoint 20041ffa f g_mover.obj + 0001:0004108b _G_TryPushingEntity 2004208b f g_mover.obj + 0001:00041681 _G_CheckProxMinePosition 20042681 f g_mover.obj + 0001:00041760 _G_TryPushingProxMine 20042760 f g_mover.obj + 0001:000418ff _G_QPush 200428ff f g_mover.obj + 0001:00041989 _G_MoverPush 20042989 f g_mover.obj + 0001:00041f76 _G_MoverTeam 20042f76 f g_mover.obj + 0001:00042182 _G_zombieMove 20043182 f g_mover.obj + 0001:0004228b _G_RunMover 2004328b f g_mover.obj + 0001:000422e6 _SetMoverState 200432e6 f g_mover.obj + 0001:00042529 _MatchTeam 20043529 f g_mover.obj + 0001:00042561 _ReturnToPos1 20043561 f g_mover.obj + 0001:000425b0 _Reached_BinaryMover 200435b0 f g_mover.obj + 0001:000426e5 _Use_BinaryMover 200436e5 f g_mover.obj + 0001:000428c7 _InitMover 200438c7 f g_mover.obj + 0001:00042bed _Blocked_Door 20043bed f g_mover.obj + 0001:00042d75 _Touch_DoorTrigger 20043d75 f g_mover.obj + 0001:00042f3b _Think_SpawnNewDoorTrigger 20043f3b f g_mover.obj + 0001:00043141 _Think_MatchTeam 20044141 f g_mover.obj + 0001:00043162 _SP_func_door 20044162 f g_mover.obj + 0001:000434fc _Touch_Plat 200444fc f g_mover.obj + 0001:00043542 _Touch_PlatCenterTrigger 20044542 f g_mover.obj + 0001:00043581 _SpawnPlatTrigger 20044581 f g_mover.obj + 0001:0004372e _SP_func_plat 2004472e f g_mover.obj + 0001:00043926 _Touch_Button 20044926 f g_mover.obj + 0001:00043959 _SP_func_button 20044959 f g_mover.obj + 0001:00043b7f _Think_BeginMoving 20044b7f f g_mover.obj + 0001:00043b9a _Reached_Train 20044b9a f g_mover.obj + 0001:00043d75 _Think_SetupTrainTargets 20044d75 f g_mover.obj + 0001:00043eb5 _SP_path_corner 20044eb5 f g_mover.obj + 0001:00043eef _SP_func_train 20044eef f g_mover.obj + 0001:00043ff4 _SP_func_static 20044ff4 f g_mover.obj + 0001:0004406c _SP_func_rotating 2004506c f g_mover.obj + 0001:000441b4 _SP_func_bobbing 200451b4 f g_mover.obj + 0001:0004430b _SP_func_pendulum 2004530b f g_mover.obj + 0001:00044497 _Touch_Zombie 20045497 f g_mover.obj + 0001:0004450b _Zombie_Think 2004550b f g_mover.obj + 0001:00044553 _SP_func_zombie 20045553 f g_mover.obj + 0001:00044a70 _G_WriteClientSessionData 20045a70 f g_session.obj + 0001:00044b41 _G_ReadSessionData 20045b41 f g_session.obj + 0001:00044c2f _G_InitSessionData 20045c2f f g_session.obj + 0001:00044d2d _G_InitWorldSession 20045d2d f g_session.obj + 0001:00044d8d _G_WriteSessionData 20045d8d f g_session.obj + 0001:00044e10 _G_SpawnString 20045e10 f g_spawn.obj + 0001:00044e83 _G_SpawnFloat 20045e83 f g_spawn.obj + 0001:00044eb8 _G_SpawnInt 20045eb8 f g_spawn.obj + 0001:00044eed _G_SpawnVector 20045eed f g_spawn.obj + 0001:00044f34 _SP_item_botroam 20045f34 f g_spawn.obj + 0001:00044f39 _G_CallSpawn 20045f39 f g_spawn.obj + 0001:00045060 _G_NewString 20046060 f g_spawn.obj + 0001:00045118 _G_ParseField 20046118 f g_spawn.obj + 0001:0004527b _G_SpawnGEntityFromSpawnVars 2004627b f g_spawn.obj + 0001:00045463 _G_AddSpawnVarToken 20046463 f g_spawn.obj + 0001:000454d8 _G_ParseSpawnVars 200464d8 f g_spawn.obj + 0001:00045620 _SP_worldspawn 20046620 f g_spawn.obj + 0001:00045815 _G_SpawnEntitiesFromString 20046815 f g_spawn.obj + 0001:00045870 _G_FilterPacket 20046870 f g_svcmds.obj + 0001:00045963 _G_ProcessIPBans 20046963 f g_svcmds.obj + 0001:00045d77 _Svcmd_AddIP_f 20046d77 f g_svcmds.obj + 0001:00045dc2 _Svcmd_RemoveIP_f 20046dc2 f g_svcmds.obj + 0001:00045eae _Svcmd_EntityList_f 20046eae f g_svcmds.obj + 0001:000460a5 _ClientForString 200470a5 f g_svcmds.obj + 0001:000461aa _Svcmd_ForceTeam_f 200471aa f g_svcmds.obj + 0001:00046232 _ConsoleCommand 20047232 f g_svcmds.obj + 0001:00046490 _dllEntry 20047490 f g_syscalls.obj + 0001:0004649d _PASSFLOAT 2004749d f g_syscalls.obj + 0001:000464ae _trap_Printf 200474ae f g_syscalls.obj + 0001:000464c2 _trap_Error 200474c2 f g_syscalls.obj + 0001:000464d6 _trap_Milliseconds 200474d6 f g_syscalls.obj + 0001:000464e6 _trap_Argc 200474e6 f g_syscalls.obj + 0001:000464f6 _trap_Argv 200474f6 f g_syscalls.obj + 0001:00046512 _trap_FS_FOpenFile 20047512 f g_syscalls.obj + 0001:0004652e _trap_FS_Read 2004752e f g_syscalls.obj + 0001:0004654a _trap_FS_Write 2004754a f g_syscalls.obj + 0001:00046566 _trap_FS_FCloseFile 20047566 f g_syscalls.obj + 0001:0004657a _trap_FS_GetFileList 2004757a f g_syscalls.obj + 0001:0004659a _trap_FS_Seek 2004759a f g_syscalls.obj + 0001:000465b6 _trap_SendConsoleCommand 200475b6 f g_syscalls.obj + 0001:000465ce _trap_Cvar_Register 200475ce f g_syscalls.obj + 0001:000465ee _trap_Cvar_Update 200475ee f g_syscalls.obj + 0001:00046602 _trap_Cvar_Set 20047602 f g_syscalls.obj + 0001:0004661a _trap_Cvar_VariableIntegerValue 2004761a f g_syscalls.obj + 0001:0004662e _trap_Cvar_VariableStringBuffer 2004762e f g_syscalls.obj + 0001:0004664a _trap_LocateGameData 2004764a f g_syscalls.obj + 0001:0004666e _trap_DropClient 2004766e f g_syscalls.obj + 0001:00046686 _trap_SendServerCommand 20047686 f g_syscalls.obj + 0001:0004669e _trap_SetConfigstring 2004769e f g_syscalls.obj + 0001:000466b6 _trap_GetConfigstring 200476b6 f g_syscalls.obj + 0001:000466d2 _trap_GetUserinfo 200476d2 f g_syscalls.obj + 0001:000466ee _trap_SetUserinfo 200476ee f g_syscalls.obj + 0001:00046706 _trap_GetServerinfo 20047706 f g_syscalls.obj + 0001:0004671e _trap_SetBrushModel 2004771e f g_syscalls.obj + 0001:00046736 _trap_Trace 20047736 f g_syscalls.obj + 0001:00046762 _trap_TraceCapsule 20047762 f g_syscalls.obj + 0001:0004678e _trap_PointContents 2004778e f g_syscalls.obj + 0001:000467a6 _trap_InPVS 200477a6 f g_syscalls.obj + 0001:000467be _trap_InPVSIgnorePortals 200477be f g_syscalls.obj + 0001:000467d6 _trap_AdjustAreaPortalState 200477d6 f g_syscalls.obj + 0001:000467ee _trap_AreasConnected 200477ee f g_syscalls.obj + 0001:00046806 _trap_LinkEntity 20047806 f g_syscalls.obj + 0001:0004681a _trap_UnlinkEntity 2004781a f g_syscalls.obj + 0001:0004682e _trap_EntitiesInBox 2004782e f g_syscalls.obj + 0001:0004684e _trap_EntityContact 2004784e f g_syscalls.obj + 0001:0004686a _trap_EntityContactCapsule 2004786a f g_syscalls.obj + 0001:00046886 _trap_BotAllocateClient 20047886 f g_syscalls.obj + 0001:00046896 _trap_BotFreeClient 20047896 f g_syscalls.obj + 0001:000468aa _trap_GetUsercmd 200478aa f g_syscalls.obj + 0001:000468c2 _trap_GetEntityToken 200478c2 f g_syscalls.obj + 0001:000468da _trap_DebugPolygonCreate 200478da f g_syscalls.obj + 0001:000468f6 _trap_DebugPolygonDelete 200478f6 f g_syscalls.obj + 0001:0004690a _trap_RealTime 2004790a f g_syscalls.obj + 0001:0004691e _trap_SnapVector 2004791e f g_syscalls.obj + 0001:00046932 _trap_BotLibSetup 20047932 f g_syscalls.obj + 0001:00046945 _trap_BotLibShutdown 20047945 f g_syscalls.obj + 0001:00046958 _trap_BotLibVarSet 20047958 f g_syscalls.obj + 0001:00046973 _trap_BotLibVarGet 20047973 f g_syscalls.obj + 0001:00046992 _trap_BotLibDefine 20047992 f g_syscalls.obj + 0001:000469a9 _trap_BotLibStartFrame 200479a9 f g_syscalls.obj + 0001:000469c9 _trap_BotLibLoadMap 200479c9 f g_syscalls.obj + 0001:000469e0 _trap_BotLibUpdateEntity 200479e0 f g_syscalls.obj + 0001:000469fb _trap_BotLibTest 200479fb f g_syscalls.obj + 0001:00046a1e _trap_BotGetSnapshotEntity 20047a1e f g_syscalls.obj + 0001:00046a39 _trap_BotGetServerCommand 20047a39 f g_syscalls.obj + 0001:00046a58 _trap_BotUserCommand 20047a58 f g_syscalls.obj + 0001:00046a73 _trap_AAS_EntityInfo 20047a73 f g_syscalls.obj + 0001:00046a8e _trap_AAS_Initialized 20047a8e f g_syscalls.obj + 0001:00046aa1 _trap_AAS_PresenceTypeBoundingBox 20047aa1 f g_syscalls.obj + 0001:00046ac0 _trap_AAS_Time 20047ac0 f g_syscalls.obj + 0001:00046adc _trap_AAS_PointAreaNum 20047adc f g_syscalls.obj + 0001:00046af3 _trap_AAS_PointReachabilityAreaIndex 20047af3 f g_syscalls.obj + 0001:00046b0a _trap_AAS_TraceAreas 20047b0a f g_syscalls.obj + 0001:00046b31 _trap_AAS_BBoxAreas 20047b31 f g_syscalls.obj + 0001:00046b54 _trap_AAS_AreaInfo 20047b54 f g_syscalls.obj + 0001:00046b6f _trap_AAS_PointContents 20047b6f f g_syscalls.obj + 0001:00046b86 _trap_AAS_NextBSPEntity 20047b86 f g_syscalls.obj + 0001:00046b9d _trap_AAS_ValueForBSPEpairKey 20047b9d f g_syscalls.obj + 0001:00046bc0 _trap_AAS_VectorForBSPEpairKey 20047bc0 f g_syscalls.obj + 0001:00046bdf _trap_AAS_FloatForBSPEpairKey 20047bdf f g_syscalls.obj + 0001:00046bfe _trap_AAS_IntForBSPEpairKey 20047bfe f g_syscalls.obj + 0001:00046c1d _trap_AAS_AreaReachability 20047c1d f g_syscalls.obj + 0001:00046c34 _trap_AAS_AreaTravelTimeToGoalArea 20047c34 f g_syscalls.obj + 0001:00046c57 _trap_AAS_EnableRoutingArea 20047c57 f g_syscalls.obj + 0001:00046c72 _trap_AAS_PredictRoute 20047c72 f g_syscalls.obj + 0001:00046cb1 _trap_AAS_AlternativeRouteGoals 20047cb1 f g_syscalls.obj + 0001:00046ce4 _trap_AAS_Swimming 20047ce4 f g_syscalls.obj + 0001:00046cfb _trap_AAS_PredictClientMovement 20047cfb f g_syscalls.obj + 0001:00046d4b _trap_EA_Say 20047d4b f g_syscalls.obj + 0001:00046d66 _trap_EA_SayTeam 20047d66 f g_syscalls.obj + 0001:00046d81 _trap_EA_Command 20047d81 f g_syscalls.obj + 0001:00046d9c _trap_EA_Action 20047d9c f g_syscalls.obj + 0001:00046db7 _trap_EA_Gesture 20047db7 f g_syscalls.obj + 0001:00046dce _trap_EA_Talk 20047dce f g_syscalls.obj + 0001:00046de5 _trap_EA_Attack 20047de5 f g_syscalls.obj + 0001:00046dfc _trap_EA_Use 20047dfc f g_syscalls.obj + 0001:00046e13 _trap_EA_Respawn 20047e13 f g_syscalls.obj + 0001:00046e2a _trap_EA_Crouch 20047e2a f g_syscalls.obj + 0001:00046e41 _trap_EA_MoveUp 20047e41 f g_syscalls.obj + 0001:00046e58 _trap_EA_MoveDown 20047e58 f g_syscalls.obj + 0001:00046e6f _trap_EA_MoveForward 20047e6f f g_syscalls.obj + 0001:00046e86 _trap_EA_MoveBack 20047e86 f g_syscalls.obj + 0001:00046e9d _trap_EA_MoveLeft 20047e9d f g_syscalls.obj + 0001:00046eb4 _trap_EA_MoveRight 20047eb4 f g_syscalls.obj + 0001:00046ecb _trap_EA_SelectWeapon 20047ecb f g_syscalls.obj + 0001:00046ee6 _trap_EA_Jump 20047ee6 f g_syscalls.obj + 0001:00046efd _trap_EA_DelayedJump 20047efd f g_syscalls.obj + 0001:00046f14 _trap_EA_Move 20047f14 f g_syscalls.obj + 0001:00046f3c _trap_EA_View 20047f3c f g_syscalls.obj + 0001:00046f57 _trap_EA_EndRegular 20047f57 f g_syscalls.obj + 0001:00046f7b _trap_EA_GetInput 20047f7b f g_syscalls.obj + 0001:00046fa3 _trap_EA_ResetInput 20047fa3 f g_syscalls.obj + 0001:00046fba _trap_BotLoadCharacter 20047fba f g_syscalls.obj + 0001:00046fde _trap_BotFreeCharacter 20047fde f g_syscalls.obj + 0001:00046ff5 _trap_Characteristic_Float 20047ff5 f g_syscalls.obj + 0001:00047019 _trap_Characteristic_BFloat 20048019 f g_syscalls.obj + 0001:00047057 _trap_Characteristic_Integer 20048057 f g_syscalls.obj + 0001:00047072 _trap_Characteristic_BInteger 20048072 f g_syscalls.obj + 0001:00047095 _trap_Characteristic_String 20048095 f g_syscalls.obj + 0001:000470b8 _trap_BotAllocChatState 200480b8 f g_syscalls.obj + 0001:000470cb _trap_BotFreeChatState 200480cb f g_syscalls.obj + 0001:000470e2 _trap_BotQueueConsoleMessage 200480e2 f g_syscalls.obj + 0001:00047101 _trap_BotRemoveConsoleMessage 20048101 f g_syscalls.obj + 0001:0004711c _trap_BotNextConsoleMessage 2004811c f g_syscalls.obj + 0001:00047137 _trap_BotNumConsoleMessages 20048137 f g_syscalls.obj + 0001:0004714e _trap_BotInitialChat 2004814e f g_syscalls.obj + 0001:0004718d _trap_BotNumInitialChats 2004818d f g_syscalls.obj + 0001:000471a8 _trap_BotReplyChat 200481a8 f g_syscalls.obj + 0001:000471eb _trap_BotChatLength 200481eb f g_syscalls.obj + 0001:00047202 _trap_BotEnterChat 20048202 f g_syscalls.obj + 0001:00047221 _trap_BotGetChatMessage 20048221 f g_syscalls.obj + 0001:00047240 _trap_StringContains 20048240 f g_syscalls.obj + 0001:0004725f _trap_BotFindMatch 2004825f f g_syscalls.obj + 0001:0004727e _trap_BotMatchVariable 2004827e f g_syscalls.obj + 0001:000472a1 _trap_UnifyWhiteSpaces 200482a1 f g_syscalls.obj + 0001:000472b8 _trap_BotReplaceSynonyms 200482b8 f g_syscalls.obj + 0001:000472d3 _trap_BotLoadChatFile 200482d3 f g_syscalls.obj + 0001:000472f2 _trap_BotSetChatGender 200482f2 f g_syscalls.obj + 0001:0004730d _trap_BotSetChatName 2004830d f g_syscalls.obj + 0001:0004732c _trap_BotResetGoalState 2004832c f g_syscalls.obj + 0001:00047343 _trap_BotResetAvoidGoals 20048343 f g_syscalls.obj + 0001:0004735a _trap_BotRemoveFromAvoidGoals 2004835a f g_syscalls.obj + 0001:00047375 _trap_BotPushGoal 20048375 f g_syscalls.obj + 0001:00047390 _trap_BotPopGoal 20048390 f g_syscalls.obj + 0001:000473a7 _trap_BotEmptyGoalStack 200483a7 f g_syscalls.obj + 0001:000473be _trap_BotDumpAvoidGoals 200483be f g_syscalls.obj + 0001:000473d5 _trap_BotDumpGoalStack 200483d5 f g_syscalls.obj + 0001:000473ec _trap_BotGoalName 200483ec f g_syscalls.obj + 0001:0004740b _trap_BotGetTopGoal 2004840b f g_syscalls.obj + 0001:00047426 _trap_BotGetSecondGoal 20048426 f g_syscalls.obj + 0001:00047441 _trap_BotChooseLTGItem 20048441 f g_syscalls.obj + 0001:00047464 _trap_BotChooseNBGItem 20048464 f g_syscalls.obj + 0001:00047498 _trap_BotTouchingGoal 20048498 f g_syscalls.obj + 0001:000474b3 _trap_BotItemGoalInVisButNotVisible 200484b3 f g_syscalls.obj + 0001:000474d6 _trap_BotGetLevelItemGoal 200484d6 f g_syscalls.obj + 0001:000474f5 _trap_BotGetNextCampSpotGoal 200484f5 f g_syscalls.obj + 0001:00047510 _trap_BotGetMapLocationGoal 20048510 f g_syscalls.obj + 0001:0004752b _trap_BotAvoidGoalTime 2004852b f g_syscalls.obj + 0001:0004754f _trap_BotSetAvoidGoalTime 2004854f f g_syscalls.obj + 0001:00047577 _trap_BotInitLevelItems 20048577 f g_syscalls.obj + 0001:0004758a _trap_BotUpdateEntityItems 2004858a f g_syscalls.obj + 0001:0004759d _trap_BotLoadItemWeights 2004859d f g_syscalls.obj + 0001:000475b8 _trap_BotFreeItemWeights 200485b8 f g_syscalls.obj + 0001:000475cf _trap_BotInterbreedGoalFuzzyLogic 200485cf f g_syscalls.obj + 0001:000475ee _trap_BotSaveGoalFuzzyLogic 200485ee f g_syscalls.obj + 0001:00047609 _trap_BotMutateGoalFuzzyLogic 20048609 f g_syscalls.obj + 0001:00047629 _trap_BotAllocGoalState 20048629 f g_syscalls.obj + 0001:00047640 _trap_BotFreeGoalState 20048640 f g_syscalls.obj + 0001:00047657 _trap_BotResetMoveState 20048657 f g_syscalls.obj + 0001:0004766e _trap_BotAddAvoidSpot 2004866e f g_syscalls.obj + 0001:0004769a _trap_BotMoveToGoal 2004869a f g_syscalls.obj + 0001:000476bd _trap_BotMoveInDirection 200486bd f g_syscalls.obj + 0001:000476e9 _trap_BotResetAvoidReach 200486e9 f g_syscalls.obj + 0001:00047700 _trap_BotResetLastAvoidReach 20048700 f g_syscalls.obj + 0001:00047717 _trap_BotReachabilityArea 20048717 f g_syscalls.obj + 0001:00047732 _trap_BotMovementViewTarget 20048732 f g_syscalls.obj + 0001:00047762 _trap_BotPredictVisiblePosition 20048762 f g_syscalls.obj + 0001:00047789 _trap_BotAllocMoveState 20048789 f g_syscalls.obj + 0001:0004779c _trap_BotFreeMoveState 2004879c f g_syscalls.obj + 0001:000477b3 _trap_BotInitMoveState 200487b3 f g_syscalls.obj + 0001:000477ce _trap_BotChooseBestFightWeapon 200487ce f g_syscalls.obj + 0001:000477e9 _trap_BotGetWeaponInfo 200487e9 f g_syscalls.obj + 0001:00047808 _trap_BotLoadWeaponWeights 20048808 f g_syscalls.obj + 0001:00047823 _trap_BotAllocWeaponState 20048823 f g_syscalls.obj + 0001:00047836 _trap_BotFreeWeaponState 20048836 f g_syscalls.obj + 0001:0004784d _trap_BotResetWeaponState 2004884d f g_syscalls.obj + 0001:00047864 _trap_GeneticParentsAndChildSelection 20048864 f g_syscalls.obj + 0001:0004788b _trap_PC_LoadSource 2004888b f g_syscalls.obj + 0001:000478a2 _trap_PC_FreeSource 200488a2 f g_syscalls.obj + 0001:000478b9 _trap_PC_ReadToken 200488b9 f g_syscalls.obj + 0001:000478d4 _trap_PC_SourceFileAndLine 200488d4 f g_syscalls.obj + 0001:00047900 _Use_Target_Remove 20048900 f g_target.obj + 0001:000479a1 _SP_target_remove 200489a1 f g_target.obj + 0001:000479b3 _Use_Target_Give 200489b3 f g_target.obj + 0001:00047a54 _SP_target_give 20048a54 f g_target.obj + 0001:00047a66 _Use_target_remove_powerups 20048a66 f g_target.obj + 0001:00047aed _SP_target_remove_powerups 20048aed f g_target.obj + 0001:00047aff _Think_Target_Delay 20048aff f g_target.obj + 0001:00047b1a _Use_Target_Delay 20048b1a f g_target.obj + 0001:00047b9d _SP_target_delay 20048b9d f g_target.obj + 0001:00047c0d _Use_Target_Score 20048c0d f g_target.obj + 0001:00047c32 _SP_target_score 20048c32 f g_target.obj + 0001:00047c5d _Use_Target_Print 20048c5d f g_target.obj + 0001:00047d4c _SP_target_print 20048d4c f g_target.obj + 0001:00047dbb _Use_Target_Speaker 20048dbb f g_target.obj + 0001:00047e6b _SP_target_speaker 20048e6b f g_target.obj + 0001:00048032 _target_laser_think 20049032 f g_target.obj + 0001:0004824e _target_laser_on 2004924e f g_target.obj + 0001:00048277 _target_laser_off 20049277 f g_target.obj + 0001:00048295 _target_laser_use 20049295 f g_target.obj + 0001:000482cc _target_laser_start 200492cc f g_target.obj + 0001:000483c1 _SP_target_laser 200493c1 f g_target.obj + 0001:000483e5 _target_teleporter_use 200493e5 f g_target.obj + 0001:0004843f _SP_target_teleporter 2004943f f g_target.obj + 0001:00048484 _target_relay_use 20049484 f g_target.obj + 0001:00048550 _SP_target_relay 20049550 f g_target.obj + 0001:00048562 _target_kill_use 20049562 f g_target.obj + 0001:00048584 _SP_target_kill 20049584 f g_target.obj + 0001:00048596 _SP_target_position 20049596 f g_target.obj + 0001:000485ae _SP_target_location 200495ae f g_target.obj + 0001:000486e0 _Team_InitGame 200496e0 f g_team.obj + 0001:00048737 _OtherTeam 20049737 f g_team.obj + 0001:00048759 _TeamName 20049759 f g_team.obj + 0001:0004878a _OtherTeamName 2004978a f g_team.obj + 0001:000487bb _TeamColorString 200497bb f g_team.obj + 0001:000487ec _PrintMsg 200497ec f g_team.obj + 0001:0004889f _AddTeamScore 2004989f f g_team.obj + 0001:000489a0 _OnSameTeam 200499a0 f g_team.obj + 0001:000489f7 _Team_SetFlagStatus 200499f7 f g_team.obj + 0001:00048ac8 _Team_CheckDroppedItem 20049ac8 f g_team.obj + 0001:00048b22 _Team_ForceGesture 20049b22 f g_team.obj + 0001:00048b9f _Team_FragBonuses 20049b9f f g_team.obj + 0001:00049570 _Team_CheckHurtCarrier 2004a570 f g_team.obj + 0001:000495ff _Team_ResetFlag 2004a5ff f g_team.obj + 0001:000496b4 _Team_ResetFlags 2004a6b4 f g_team.obj + 0001:000496d6 _Team_ReturnFlagSound 2004a6d6 f g_team.obj + 0001:0004973e _Team_TakeFlagSound 2004a73e f g_team.obj + 0001:00049817 _Team_CaptureFlagSound 2004a817 f g_team.obj + 0001:0004987f _Team_ReturnFlag 2004a87f f g_team.obj + 0001:000498d0 _Team_FreeEntity 2004a8d0 f g_team.obj + 0001:00049924 _Team_DroppedFlagThink 2004a924 f g_team.obj + 0001:00049992 _Team_TouchOurFlag 2004a992 f g_team.obj + 0001:00049e60 _Team_TouchEnemyFlag 2004ae60 f g_team.obj + 0001:00049f0a _Pickup_Team 2004af0a f g_team.obj + 0001:00049fb5 _Team_GetLocation 2004afb5 f g_team.obj + 0001:0004a0a4 _Team_GetLocationMsg 2004b0a4 f g_team.obj + 0001:0004a15a _SelectRandomTeamSpawnPoint 2004b15a f g_team.obj + 0001:0004a248 _SelectCTFSpawnPoint 2004b248 f g_team.obj + 0001:0004a2df _TeamplayInfoMessage 2004b2df f g_team.obj + 0001:0004a5d8 _CheckTeamStatus 2004b5d8 f g_team.obj + 0001:0004a742 _SP_team_CTF_redplayer 2004b742 f g_team.obj + 0001:0004a747 _SP_team_CTF_blueplayer 2004b747 f g_team.obj + 0001:0004a74c _SP_team_CTF_redspawn 2004b74c f g_team.obj + 0001:0004a751 _SP_team_CTF_bluespawn 2004b751 f g_team.obj + 0001:0004a760 _InitTrigger 2004b760 f g_trigger.obj + 0001:0004a7c6 _multi_wait 2004b7c6 f g_trigger.obj + 0001:0004a7d8 _multi_trigger 2004b7d8 f g_trigger.obj + 0001:0004a964 _Use_Multi 2004b964 f g_trigger.obj + 0001:0004a979 _Touch_Multi 2004b979 f g_trigger.obj + 0001:0004a99c _SP_trigger_multiple 2004b99c f g_trigger.obj + 0001:0004aa68 _trigger_always_think 2004ba68 f g_trigger.obj + 0001:0004aa89 _SP_trigger_always 2004ba89 f g_trigger.obj + 0001:0004aaae _trigger_push_touch 2004baae f g_trigger.obj + 0001:0004aad7 _AimAtTarget 2004bad7 f g_trigger.obj + 0001:0004ac4a _SP_trigger_push 2004bc4a f g_trigger.obj + 0001:0004ace9 _Use_target_push 2004bce9 f g_trigger.obj + 0001:0004ad9b _SP_target_push 2004bd9b f g_trigger.obj + 0001:0004aee8 _trigger_teleporter_touch 2004bee8 f g_trigger.obj + 0001:0004af77 _SP_trigger_teleport 2004bf77 f g_trigger.obj + 0001:0004aff4 _postvote_music_think 2004bff4 f g_trigger.obj + 0001:0004b059 _spawn_postvote_music 2004c059 f g_trigger.obj + 0001:0004b0ae _trigger_hubvote_touch 2004c0ae f g_trigger.obj + 0001:0004b18e _SP_trigger_hubvote 2004c18e f g_trigger.obj + 0001:0004b4e1 _trigger_hubalternates_touch 2004c4e1 f g_trigger.obj + 0001:0004b55b _SP_trigger_hubalternates 2004c55b f g_trigger.obj + 0001:0004b5d8 _Svcmd_hubaltvoting_f 2004c5d8 f g_trigger.obj + 0001:0004ba6f _hurt_use 2004ca6f f g_trigger.obj + 0001:0004ba9a _hurt_touch 2004ca9a f g_trigger.obj + 0001:0004bb72 _SP_trigger_hurt 2004cb72 f g_trigger.obj + 0001:0004bc05 _func_timer_think 2004cc05 f g_trigger.obj + 0001:0004bc7f _func_timer_use 2004cc7f f g_trigger.obj + 0001:0004bcb7 _SP_func_timer 2004ccb7 f g_trigger.obj + 0001:0004bd96 _func_lightning_fx_think 2004cd96 f g_trigger.obj + 0001:0004bfc0 _SP_func_lightning_fx 2004cfc0 f g_trigger.obj + 0001:0004c1bc _Use_Gravity 2004d1bc f g_trigger.obj + 0001:0004c20d _Touch_Gravity 2004d20d f g_trigger.obj + 0001:0004c26c _SP_func_local_gravity 2004d26c f g_trigger.obj + 0001:0004c330 _AddRemap 2004d330 f g_utils.obj + 0001:0004c412 _BuildShaderStateConfig 2004d412 f g_utils.obj + 0001:0004c4d5 _G_FindConfigstringIndex 2004d4d5 f g_utils.obj + 0001:0004c5a9 _G_ModelIndex 2004d5a9 f g_utils.obj + 0001:0004c5c3 _G_SoundIndex 2004d5c3 f g_utils.obj + 0001:0004c5e0 _G_TeamCommand 2004d5e0 f g_utils.obj + 0001:0004c658 _G_Find 2004d658 f g_utils.obj + 0001:0004c6dd _G_PickTarget 2004d6dd f g_utils.obj + 0001:0004c79b _G_UseTargets 2004d79b f g_utils.obj + 0001:0004c89b _tv 2004d89b f g_utils.obj + 0001:0004c8e2 _vtos 2004d8e2 f g_utils.obj + 0001:0004c945 _G_SetMovedir 2004d945 f g_utils.obj + 0001:0004ca2e _vectoyaw 2004da2e f g_utils.obj + 0001:0004caeb _G_InitGentity 2004daeb f g_utils.obj + 0001:0004cb2c _G_Spawn 2004db2c f g_utils.obj + 0001:0004cc90 _G_EntitiesFree 2004dc90 f g_utils.obj + 0001:0004cce1 _G_FreeEntity 2004dce1 f g_utils.obj + 0001:0004cd3c _G_TempEntity 2004dd3c f g_utils.obj + 0001:0004cdef _G_KillBox 2004ddef f g_utils.obj + 0001:0004cf2a _G_AddPredictableEvent 2004df2a f g_utils.obj + 0001:0004cf57 _G_AddEvent 2004df57 f g_utils.obj + 0001:0004d039 _G_Sound 2004e039 f g_utils.obj + 0001:0004d063 _G_SetOrigin 2004e063 f g_utils.obj + 0001:0004d0f3 _DebugLine 2004e0f3 f g_utils.obj + 0001:0004d350 _CalcMuzzlePoint 2004e350 f g_weapon.obj + 0001:0004d420 _CalcMuzzlePointOrigin 2004e420 f g_weapon.obj + 0001:0004d4f0 _G_BounceProjectile 2004e4f0 f g_weapon.obj + 0001:0004d5cf _Weapon_Gauntlet 2004e5cf f g_weapon.obj + 0001:0004d5d4 _CheckGauntletAttack 2004e5d4 f g_weapon.obj + 0001:0004d78f _Weapon_fire_gravity 2004e78f f g_weapon.obj + 0001:0004d7db _Weapon_fire_autosentry 2004e7db f g_weapon.obj + 0001:0004d806 _Weapon_fire_beartrap 2004e806 f g_weapon.obj + 0001:0004d833 _Weapon_fire_airfist 2004e833 f g_weapon.obj + 0001:0004da78 _Weapon_fire_nailgun 2004ea78 f g_weapon.obj + 0001:0004dad3 _SnapVectorTowards 2004ead3 f g_weapon.obj + 0001:0004db51 _Bullet_Fire 2004eb51 f g_weapon.obj + 0001:0004de3d _shooter_bullet_fire 2004ee3d f g_weapon.obj + 0001:0004de8a _BFG_Fire 2004ee8a f g_weapon.obj + 0001:0004dee5 _ShotgunPellet 2004eee5 f g_weapon.obj + 0001:0004e028 _ShotgunPattern 2004f028 f g_weapon.obj + 0001:0004e1fb _weapon_supershotgun_fire 2004f1fb f g_weapon.obj + 0001:0004e2e4 _shooter_supershotgun_fire 2004f2e4 f g_weapon.obj + 0001:0004e3cb _weapon_grenadelauncher_fire 2004f3cb f g_weapon.obj + 0001:0004e447 _Weapon_RocketLauncher_Fire 2004f447 f g_weapon.obj + 0001:0004e4a2 _Weapon_Plasmagun_Fire 2004f4a2 f g_weapon.obj + 0001:0004e4fd _weapon_railgun_fire 2004f4fd f g_weapon.obj + 0001:0004eb9c _shooter_railgun_fire 2004fb9c f g_weapon.obj + 0001:0004ebec _Weapon_GrapplingHook_Fire 2004fbec f g_weapon.obj + 0001:0004ec68 _Weapon_HookFree 2004fc68 f g_weapon.obj + 0001:0004ecf0 _Weapon_HookThink 2004fcf0 f g_weapon.obj + 0001:0004ee47 _G_LightningDischargeDamage 2004fe47 f g_weapon.obj + 0001:0004f016 _Find_ChainLightningTarget 20050016 f g_weapon.obj + 0001:0004f2b5 _ChainLightningThink 200502b5 f g_weapon.obj + 0001:0004f8a0 _ChainLightningStrike 200508a0 f g_weapon.obj + 0001:0004fa9c _Weapon_LightningFire 20050a9c f g_weapon.obj + 0001:0004ff4c _shooter_Lightning_fire 20050f4c f g_weapon.obj + 0001:0005005f _Exploding_ShellsPellet 2005105f f g_weapon.obj + 0001:000501b4 _Exploding_ShellsPattern 200511b4 f g_weapon.obj + 0001:00050327 _weapon_exploding_shells_fire 20051327 f g_weapon.obj + 0001:00050415 _TootThink 20051415 f g_weapon.obj + 0001:000507c8 _weapon_beans_eat 200517c8 f g_weapon.obj + 0001:00050969 _LogAccuracyHit 20051969 f g_weapon.obj + 0001:000509dd _FireWeapon 200519dd f g_weapon.obj + 0001:00050c60 _Q_rand 20051c60 f q_math.obj + 0001:00050c7d _Q_random 20051c7d f q_math.obj + 0001:00050ca2 _Q_crandom 20051ca2 f q_math.obj + 0001:00050cc5 _ClampChar 20051cc5 f q_math.obj + 0001:00050ce1 _ClampShort 20051ce1 f q_math.obj + 0001:00050d08 _DirToByte 20051d08 f q_math.obj + 0001:00050d9f _ByteToDir 20051d9f f q_math.obj + 0001:00050e0c _ColorBytes3 20051e0c f q_math.obj + 0001:00050e4a _ColorBytes4 20051e4a f q_math.obj + 0001:00050e99 _NormalizeColor 20051e99 f q_math.obj + 0001:00050f38 _PlaneFromPoints 20051f38 f q_math.obj + 0001:0005105a _RotatePointAroundVector 2005205a f q_math.obj + 0001:00051258 _RotateAroundDirection 20052258 f q_math.obj + 0001:000512d5 _vectoangles 200522d5 f q_math.obj + 0001:0005143c _AnglesToAxis 2005243c f q_math.obj + 0001:0005148e _AxisClear 2005248e f q_math.obj + 0001:000514ec _AxisCopy 200524ec f q_math.obj + 0001:0005155b _ProjectPointOnPlane 2005255b f q_math.obj + 0001:0005164d _MakeNormalVectors 2005264d f q_math.obj + 0001:00051703 _VectorRotate 20052703 f q_math.obj + 0001:0005178d _Q_rsqrt 2005278d f q_math.obj + 0001:000517dd _Q_fabs 200527dd f q_math.obj + 0001:000517fa _LerpAngle 200527fa f q_math.obj + 0001:0005184f _AngleSubtract 2005284f f q_math.obj + 0001:0005189f _AnglesSubtract 2005289f f q_math.obj + 0001:000518f5 _AngleMod 200528f5 f q_math.obj + 0001:0005191f _AngleNormalize360 2005291f f q_math.obj + 0001:0005194b _AngleNormalize180 2005294b f q_math.obj + 0001:0005197b _AngleDelta 2005297b f q_math.obj + 0001:00051992 _SetPlaneSignbits 20052992 f q_math.obj + 0001:000519ee _BoxOnPlaneSide 200529ee f q_math.obj + 0001:00051c25 _RadiusFromBounds 20052c25 f q_math.obj + 0001:00051ced _ClearBounds 20052ced f q_math.obj + 0001:00051d2c _AddPointToBounds 20052d2c f q_math.obj + 0001:00051de3 _VectorNormalize 20052de3 f q_math.obj + 0001:00051e6e _VectorNormalize2 20052e6e f q_math.obj + 0001:00051f18 __VectorMA 20052f18 f q_math.obj + 0001:00051f59 __DotProduct 20052f59 f q_math.obj + 0001:00051f84 __VectorSubtract 20052f84 f q_math.obj + 0001:00051fbc __VectorAdd 20052fbc f q_math.obj + 0001:00051ff4 __VectorCopy 20052ff4 f q_math.obj + 0001:0005201b __VectorScale 2005301b f q_math.obj + 0001:0005204b _Vector4Scale 2005304b f q_math.obj + 0001:0005208a _Q_log2 2005308a f q_math.obj + 0001:000520b5 _MatrixMultiply 200530b5 f q_math.obj + 0001:00052251 _AngleVectors 20053251 f q_math.obj + 0001:0005242d _AngleVectorsForward 2005342d f q_math.obj + 0001:000524d3 _PerpendicularVector 200534d3 f q_math.obj + 0001:00052590 _Com_Clamp 20053590 f q_shared.obj + 0001:000525bc _COM_SkipPath 200535bc f q_shared.obj + 0001:000525f6 _COM_StripExtension 200535f6 f q_shared.obj + 0001:00052634 _COM_DefaultExtension 20053634 f q_shared.obj + 0001:000526ae _ShortSwap 200536ae f q_shared.obj + 0001:000526ea _ShortNoSwap 200536ea f q_shared.obj + 0001:000526f3 _LongSwap 200536f3 f q_shared.obj + 0001:00052766 _LongNoSwap 20053766 f q_shared.obj + 0001:0005276e _Long64Swap 2005376e f q_shared.obj + 0001:000527ae _Long64NoSwap 200537ae f q_shared.obj + 0001:000527b9 _FloatSwap 200537b9 f q_shared.obj + 0001:000527dd _FloatNoSwap 200537dd f q_shared.obj + 0001:000527e7 _COM_BeginParseSession 200537e7 f q_shared.obj + 0001:00052811 _COM_GetCurrentParseLine 20053811 f q_shared.obj + 0001:0005281b _COM_Parse 2005381b f q_shared.obj + 0001:0005282e _COM_ParseError 2005382e f q_shared.obj + 0001:00052875 _COM_ParseWarning 20053875 f q_shared.obj + 0001:000528bc _COM_Compress 200538bc f q_shared.obj + 0001:00052ab6 _COM_ParseExt 20053ab6 f q_shared.obj + 0001:00052d2e _COM_MatchToken 20053d2e f q_shared.obj + 0001:00052d70 _SkipBracedSection 20053d70 f q_shared.obj + 0001:00052dd5 _SkipRestOfLine 20053dd5 f q_shared.obj + 0001:00052e1f _Parse1DMatrix 20053e1f f q_shared.obj + 0001:00052e8b _Parse2DMatrix 20053e8b f q_shared.obj + 0001:00052eef _Parse3DMatrix 20053eef f q_shared.obj + 0001:00052f5b _Q_isprint 20053f5b f q_shared.obj + 0001:00052f75 _Q_islower 20053f75 f q_shared.obj + 0001:00052f8f _Q_isupper 20053f8f f q_shared.obj + 0001:00052fa9 _Q_isalpha 20053fa9 f q_shared.obj + 0001:00052fcf _Q_strrchr 20053fcf f q_shared.obj + 0001:00053026 _Q_strncpyz 20054026 f q_shared.obj + 0001:0005308b _Q_stricmpn 2005408b f q_shared.obj + 0001:00053154 _Q_strncmp 20054154 f q_shared.obj + 0001:000531bc _Q_stricmp 200541bc f q_shared.obj + 0001:000531f4 _Q_strlwr 200541f4 f q_shared.obj + 0001:0005322e _Q_strupr 2005422e f q_shared.obj + 0001:00053268 _Q_strcat 20054268 f q_shared.obj + 0001:000532b0 _Q_PrintStrlen 200542b0 f q_shared.obj + 0001:00053325 _Q_CleanStr 20054325 f q_shared.obj + 0001:000533ae _Com_sprintf 200543ae f q_shared.obj + 0001:0005344e _va 2005444e f q_shared.obj + 0001:000534a1 _Info_ValueForKey 200544a1 f q_shared.obj + 0001:0005361b _Info_NextPair 2005461b f q_shared.obj + 0001:000536ec _Info_RemoveKey 200546ec f q_shared.obj + 0001:00053855 _Info_RemoveKey_Big 20054855 f q_shared.obj + 0001:000539c2 _Info_Validate 200549c2 f q_shared.obj + 0001:000539f8 _Info_SetValueForKey 200549f8 f q_shared.obj + 0001:00053b70 _Info_SetValueForKey_Big 20054b70 f q_shared.obj + 0001:00053ce0 _atol 20054ce0 f LIBCMTD:atox.obj + 0001:00053de0 _atoi 20054de0 f LIBCMTD:atox.obj + 0001:00053e00 __atoi64 20054e00 f LIBCMTD:atox.obj + 0001:00053f30 _strlen 20054f30 f LIBCMTD:strlen.obj + 0001:00053fb0 _srand 20054fb0 f LIBCMTD:rand.obj + 0001:00053fc0 _rand 20054fc0 f LIBCMTD:rand.obj + 0001:00054000 __fpmath 20055000 f LIBCMTD:fpinit.obj + 0001:00054030 __fpclear 20055030 f LIBCMTD:fpinit.obj + 0001:00054040 __cfltcvt_init 20055040 f LIBCMTD:fpinit.obj + 0001:00054090 ___setfflag 20055090 f LIBCMTD:fpinit.obj + 0001:000540ac __ftol 200550ac f LIBCMTD:ftol.obj + 0001:000540e0 _strcpy 200550e0 f LIBCMTD:strcat.obj + 0001:000540f0 _strcat 200550f0 f LIBCMTD:strcat.obj + 0001:000541d0 _strncpy 200551d0 f LIBCMTD:strncpy.obj + 0001:000542d0 _memcpy 200552d0 f LIBCMTD:memcpy.obj + 0001:00054610 _atof 20055610 f LIBCMTD:atof.obj + 0001:00054690 _strcmp 20055690 f LIBCMTD:strcmp.obj + 0001:00054720 _sscanf 20055720 f LIBCMTD:sscanf.obj + 0001:000547e0 __CIsqrt 200557e0 f LIBCMTD:sqrt.obj + 0001:000547f4 _sqrt 200557f4 LIBCMTD:sqrt.obj + 0001:000548a0 _memset 200558a0 f LIBCMTD:memset.obj + 0001:00054900 __toupper 20055900 f LIBCMTD:toupper.obj + 0001:00054910 _toupper 20055910 f LIBCMTD:toupper.obj + 0001:000549b0 __toupper_lk 200559b0 f LIBCMTD:toupper.obj + 0001:00054ae0 _memmove 20055ae0 f LIBCMTD:memmove.obj + 0001:00054e20 _strstr 20055e20 f LIBCMTD:strstr.obj + 0001:00054ea0 _fabs 20055ea0 f LIBCMTD:fabs.obj + 0001:00054fa0 _vsprintf 20055fa0 f LIBCMTD:vsprintf.obj + 0001:000550a0 _abs 200560a0 f LIBCMTD:abs.obj + 0001:000550e0 _strchr 200560e0 f LIBCMTD:strchr.obj + 0001:000550e6 ___from_strstr_to_strchr 200560e6 LIBCMTD:strchr.obj + 0001:000551a0 __CIsin 200561a0 f LIBCMTD:sin.obj + 0001:000551b4 _sin 200561b4 LIBCMTD:sin.obj + 0001:00055250 __CIcos 20056250 f LIBCMTD:cos.obj + 0001:00055264 _cos 20056264 LIBCMTD:cos.obj + 0001:00055300 __alloca_probe 20056300 LIBCMTD:chkstk.obj + 0001:00055300 __chkstk 20056300 LIBCMTD:chkstk.obj + 0001:00055330 _qsort 20056330 f LIBCMTD:qsort.obj + 0001:00055630 __tolower 20056630 f LIBCMTD:tolower.obj + 0001:00055640 _tolower 20056640 f LIBCMTD:tolower.obj + 0001:000556e0 __tolower_lk 200566e0 f LIBCMTD:tolower.obj + 0001:00055810 _ceil 20056810 f LIBCMTD:ceil.obj + 0001:00055950 _atan2 20056950 f LIBCMTD:87ctriga.obj + 0001:0005595a __CIatan2 2005695a f LIBCMTD:87ctriga.obj + 0001:00055970 __assert 20056970 f LIBCMTD:assert.obj + 0001:00055d00 __CRT_INIT@12 20056d00 f LIBCMTD:dllcrt0.obj + 0001:00055ed0 __DllMainCRTStartup@12 20056ed0 f LIBCMTD:dllcrt0.obj + 0001:00055fd0 __amsg_exit 20056fd0 f LIBCMTD:dllcrt0.obj + 0001:00056010 __isctype 20057010 f LIBCMTD:isctype.obj + 0001:000560d0 __allmul 200570d0 f LIBCMTD:llmul.obj + 0001:00056110 __mtinit 20057110 f LIBCMTD:tidtable.obj + 0001:000561a0 __mtterm 200571a0 f LIBCMTD:tidtable.obj + 0001:000561d0 __initptd 200571d0 f LIBCMTD:tidtable.obj + 0001:000561f0 __getptd 200571f0 f LIBCMTD:tidtable.obj + 0001:00056290 __freeptd 20057290 f LIBCMTD:tidtable.obj + 0001:00056380 ___threadid 20057380 f LIBCMTD:tidtable.obj + 0001:00056390 ___threadhandle 20057390 f LIBCMTD:tidtable.obj + 0001:000563a0 __setdefaultprecision 200573a0 f LIBCMTD:fp8.obj + 0001:000563c0 __ms_p5_test_fdiv 200573c0 f LIBCMTD:testfdiv.obj + 0001:00056420 __ms_p5_mp_test_fdiv 20057420 f LIBCMTD:testfdiv.obj + 0001:00056470 __forcdecpt 20057470 f LIBCMTD:cvt.obj + 0001:00056520 __cropzeros 20057520 f LIBCMTD:cvt.obj + 0001:00056600 __positive 20057600 f LIBCMTD:cvt.obj + 0001:00056630 __fassign 20057630 f LIBCMTD:cvt.obj + 0001:00056680 __cftoe 20057680 f LIBCMTD:cvt.obj + 0001:00056880 __cftof 20057880 f LIBCMTD:cvt.obj + 0001:00056a40 __cftog 20057a40 f LIBCMTD:cvt.obj + 0001:00056b30 __cfltcvt 20057b30 f LIBCMTD:cvt.obj + 0001:00056bc0 __fltin2 20057bc0 f LIBCMTD:cfin.obj + 0001:00056c80 __input 20057c80 f LIBCMTD:input.obj + 0001:00058190 __CrtDbgBreak 20059190 f LIBCMTD:dbgrpt.obj + 0001:000581a0 __CrtSetReportMode 200591a0 f LIBCMTD:dbgrpt.obj + 0001:00058200 __CrtSetReportFile 20059200 f LIBCMTD:dbgrpt.obj + 0001:00058280 __CrtSetReportHook 20059280 f LIBCMTD:dbgrpt.obj + 0001:000582a0 __CrtDbgReport 200592a0 f LIBCMTD:dbgrpt.obj + 0001:00058940 __trandisp1 20059940 f LIBCMTD:87disp.obj + 0001:000589a7 __trandisp2 200599a7 f LIBCMTD:87disp.obj + 0001:00058a33 __rttospopde 20059a33 LIBCMTD:87disp.obj + 0001:00058a38 __rttospop 20059a38 LIBCMTD:87disp.obj + 0001:00058a3a __rtnospop 20059a3a LIBCMTD:87disp.obj + 0001:00058a3c __rttosnpop 20059a3c LIBCMTD:87disp.obj + 0001:00058a3d __rtnospopde 20059a3d LIBCMTD:87disp.obj + 0001:00058a44 __rtzeropop 20059a44 LIBCMTD:87disp.obj + 0001:00058a46 __rtzeronpop 20059a46 LIBCMTD:87disp.obj + 0001:00058a4b __rtonepop 20059a4b LIBCMTD:87disp.obj + 0001:00058a4d __rtonenpop 20059a4d LIBCMTD:87disp.obj + 0001:00058a52 __tosnan1 20059a52 LIBCMTD:87disp.obj + 0001:00058a7d __nosnan2 20059a7d LIBCMTD:87disp.obj + 0001:00058a7f __tosnan2 20059a7f LIBCMTD:87disp.obj + 0001:00058aa7 __nan2 20059aa7 LIBCMTD:87disp.obj + 0001:00058ae6 __rtindfpop 20059ae6 LIBCMTD:87disp.obj + 0001:00058ae8 __rtindfnpop 20059ae8 LIBCMTD:87disp.obj + 0001:00058af9 __rttosnpopde 20059af9 LIBCMTD:87disp.obj + 0001:00058b03 __rtchsifneg 20059b03 LIBCMTD:87disp.obj + 0001:00058b10 __startTwoArgErrorHandling 20059b10 f LIBCMTD:genexcep.obj + 0001:00058b27 __startOneArgErrorHandling 20059b27 f LIBCMTD:genexcep.obj + 0001:00058b70 __twoToTOS 20059b70 f LIBCMTD:common.obj + 0001:00058b85 __load_CW 20059b85 f LIBCMTD:common.obj + 0001:00058b9c __convertTOStoQNaN 20059b9c f LIBCMTD:common.obj + 0001:00058bb5 __fload_withFB 20059bb5 f LIBCMTD:common.obj + 0001:00058bf8 __checkTOS_withFB 20059bf8 f LIBCMTD:common.obj + 0001:00058c0e __fast_exit 20059c0e f LIBCMTD:common.obj + 0001:00058c1b __math_exit 20059c1b f LIBCMTD:common.obj + 0001:00058c45 __check_overflow_exit 20059c45 f LIBCMTD:common.obj + 0001:00058c59 __check_range_exit 20059c59 f LIBCMTD:common.obj + 0001:00058d00 __mtinitlocks 20059d00 f LIBCMTD:mlock.obj + 0001:00058d40 __mtdeletelocks 20059d40 f LIBCMTD:mlock.obj + 0001:00058de0 __lock 20059de0 f LIBCMTD:mlock.obj + 0001:00058e80 __unlock 20059e80 f LIBCMTD:mlock.obj + 0001:00058ea0 __lock_file 20059ea0 f LIBCMTD:mlock.obj + 0001:00058ee0 __lock_file2 20059ee0 f LIBCMTD:mlock.obj + 0001:00058f10 __unlock_file 20059f10 f LIBCMTD:mlock.obj + 0001:00058f50 __unlock_file2 20059f50 f LIBCMTD:mlock.obj + 0001:00058f80 __lockerr_exit 20059f80 f LIBCMTD:mlock.obj + 0001:00058fa0 _setlocale 20059fa0 f LIBCMTD:setlocal.obj + 0001:00059620 __expandlocale 2005a620 f LIBCMTD:setlocal.obj + 0001:000597b0 ___init_dummy 2005a7b0 f LIBCMTD:setlocal.obj + 0001:000597c0 __strcats 2005a7c0 f LIBCMTD:setlocal.obj + 0001:00059810 ___lc_strtolc 2005a810 f LIBCMTD:setlocal.obj + 0001:00059970 ___lc_lctostr 2005a970 f LIBCMTD:setlocal.obj + 0001:000599e0 ___crtLCMapStringW 2005a9e0 f LIBCMTD:aw_map.obj + 0001:00059cf0 ___crtLCMapStringA 2005acf0 f LIBCMTD:aw_map.obj + 0001:0005a000 __handle_qnan1 2005b000 f LIBCMTD:fpexcept.obj + 0001:0005a060 __handle_qnan2 2005b060 f LIBCMTD:fpexcept.obj + 0001:0005a0d0 __except1 2005b0d0 f LIBCMTD:fpexcept.obj + 0001:0005a190 __except2 2005b190 f LIBCMTD:fpexcept.obj + 0001:0005a260 __raise_exc 2005b260 f LIBCMTD:fpexcept.obj + 0001:0005a730 __handle_exc 2005b730 f LIBCMTD:fpexcept.obj + 0001:0005aab0 __umatherr 2005bab0 f LIBCMTD:fpexcept.obj + 0001:0005ab50 __set_errno 2005bb50 f LIBCMTD:fpexcept.obj + 0001:0005abd0 __errcode 2005bbd0 f LIBCMTD:fpexcept.obj + 0001:0005ac50 __set_exp 2005bc50 f LIBCMTD:util.obj + 0001:0005ac90 __get_exp 2005bc90 f LIBCMTD:util.obj + 0001:0005acc0 __add_exp 2005bcc0 f LIBCMTD:util.obj + 0001:0005ad00 __set_bexp 2005bd00 f LIBCMTD:util.obj + 0001:0005ad40 __sptype 2005bd40 f LIBCMTD:util.obj + 0001:0005adc0 __decomp 2005bdc0 f LIBCMTD:util.obj + 0001:0005af00 __statfp 2005bf00 f LIBCMTD:fpctrl.obj + 0001:0005af20 __clrfp 2005bf20 f LIBCMTD:fpctrl.obj + 0001:0005af40 __ctrlfp 2005bf40 f LIBCMTD:fpctrl.obj + 0001:0005af80 __set_statfp 2005bf80 f LIBCMTD:fpctrl.obj + 0001:0005b000 __flsbuf 2005c000 f LIBCMTD:_flsbuf.obj + 0001:0005b280 __output 2005c280 f LIBCMTD:output.obj + 0001:0005c120 __frnd 2005d120 f LIBCMTD:frnd.obj + 0001:0005c15c __fFATN2 2005d15c LIBCMTD:87triga.obj + 0001:0005c1ac __rtpiby2 2005d1ac LIBCMTD:87triga.obj + 0001:0005c1f0 __cintrindisp2 2005d1f0 f LIBCMTD:87cdisp.obj + 0001:0005c22e __cintrindisp1 2005d22e f LIBCMTD:87cdisp.obj + 0001:0005c26b __ctrandisp2 2005d26b f LIBCMTD:87cdisp.obj + 0001:0005c3eb __ctrandisp1 2005d3eb f LIBCMTD:87cdisp.obj + 0001:0005c41e __fload 2005d41e f LIBCMTD:87cdisp.obj + 0001:0005c460 _abort 2005d460 f LIBCMTD:abort.obj + 0001:0005c490 __cinit 2005d490 f LIBCMTD:crt0dat.obj + 0001:0005c4d0 _exit 2005d4d0 f LIBCMTD:crt0dat.obj + 0001:0005c4f0 __exit 2005d4f0 f LIBCMTD:crt0dat.obj + 0001:0005c510 __cexit 2005d510 f LIBCMTD:crt0dat.obj + 0001:0005c530 __c_exit 2005d530 f LIBCMTD:crt0dat.obj + 0001:0005c640 __lockexit 2005d640 f LIBCMTD:crt0dat.obj + 0001:0005c650 __unlockexit 2005d650 f LIBCMTD:crt0dat.obj + 0001:0005c690 _signal 2005d690 f LIBCMTD:winsig.obj + 0001:0005c960 _raise 2005d960 f LIBCMTD:winsig.obj + 0001:0005cc10 ___fpecode 2005dc10 f LIBCMTD:winsig.obj + 0001:0005cc20 ___pxcptinfoptrs 2005dc20 f LIBCMTD:winsig.obj + 0001:0005cc30 ___crtMessageBoxA 2005dc30 f LIBCMTD:crtmbox.obj + 0001:0005ccf0 __itoa 2005dcf0 f LIBCMTD:xtoa.obj + 0001:0005ce10 __ltoa 2005de10 f LIBCMTD:xtoa.obj + 0001:0005ce50 __ultoa 2005de50 f LIBCMTD:xtoa.obj + 0001:0005ce70 __i64toa 2005de70 f LIBCMTD:xtoa.obj + 0001:0005cfa0 __ui64toa 2005dfa0 f LIBCMTD:xtoa.obj + 0001:0005cfc0 _strncat 2005dfc0 f LIBCMTD:strncat.obj + 0001:0005d0f0 _fflush 2005e0f0 f LIBCMTD:fflush.obj + 0001:0005d140 __fflush_lk 2005e140 f LIBCMTD:fflush.obj + 0001:0005d190 __flush 2005e190 f LIBCMTD:fflush.obj + 0001:0005d250 __flushall 2005e250 f LIBCMTD:fflush.obj + 0001:0005d3a0 _fprintf 2005e3a0 f LIBCMTD:fprintf.obj + 0001:0005d470 _setvbuf 2005e470 f LIBCMTD:setvbuf.obj + 0001:0005d5e0 ___initstdio 2005e5e0 f LIBCMTD:_file.obj + 0001:0005d710 ___endstdio 2005e710 f LIBCMTD:_file.obj + 0001:0005d730 __ioinit 2005e730 f LIBCMTD:ioinit.obj + 0001:0005da60 __ioterm 2005ea60 f LIBCMTD:ioinit.obj + 0001:0005db00 _malloc 2005eb00 f LIBCMTD:dbgheap.obj + 0001:0005db20 __malloc_dbg 2005eb20 f LIBCMTD:dbgheap.obj + 0001:0005db50 __nh_malloc 2005eb50 f LIBCMTD:dbgheap.obj + 0001:0005db70 __nh_malloc_dbg 2005eb70 f LIBCMTD:dbgheap.obj + 0001:0005dbd0 __heap_alloc 2005ebd0 f LIBCMTD:dbgheap.obj + 0001:0005dbf0 __heap_alloc_dbg 2005ebf0 f LIBCMTD:dbgheap.obj + 0001:0005df10 _calloc 2005ef10 f LIBCMTD:dbgheap.obj + 0001:0005df30 __calloc_dbg 2005ef30 f LIBCMTD:dbgheap.obj + 0001:0005df90 _realloc 2005ef90 f LIBCMTD:dbgheap.obj + 0001:0005dfb0 __realloc_dbg 2005efb0 f LIBCMTD:dbgheap.obj + 0001:0005e510 __expand 2005f510 f LIBCMTD:dbgheap.obj + 0001:0005e530 __expand_dbg 2005f530 f LIBCMTD:dbgheap.obj + 0001:0005e570 _free 2005f570 f LIBCMTD:dbgheap.obj + 0001:0005e590 __free_lk 2005f590 f LIBCMTD:dbgheap.obj + 0001:0005e5b0 __free_dbg 2005f5b0 f LIBCMTD:dbgheap.obj + 0001:0005e5e0 __free_dbg_lk 2005f5e0 f LIBCMTD:dbgheap.obj + 0001:0005e9d0 __msize 2005f9d0 f LIBCMTD:dbgheap.obj + 0001:0005e9f0 __msize_dbg 2005f9f0 f LIBCMTD:dbgheap.obj + 0001:0005eb50 __CrtSetBreakAlloc 2005fb50 f LIBCMTD:dbgheap.obj + 0001:0005eb70 __CrtSetDbgBlockType 2005fb70 f LIBCMTD:dbgheap.obj + 0001:0005ec10 __CrtSetAllocHook 2005fc10 f LIBCMTD:dbgheap.obj + 0001:0005ecc0 __CrtCheckMemory 2005fcc0 f LIBCMTD:dbgheap.obj + 0001:0005f030 __CrtSetDbgFlag 20060030 f LIBCMTD:dbgheap.obj + 0001:0005f060 __CrtDoForAllClientObjects 20060060 f LIBCMTD:dbgheap.obj + 0001:0005f0d0 __CrtIsValidPointer 200600d0 f LIBCMTD:dbgheap.obj + 0001:0005f120 __CrtIsValidHeapPointer 20060120 f LIBCMTD:dbgheap.obj + 0001:0005f1b0 __CrtIsMemoryBlock 200601b0 f LIBCMTD:dbgheap.obj + 0001:0005f2a0 __CrtSetDumpClient 200602a0 f LIBCMTD:dbgheap.obj + 0001:0005f2c0 __CrtMemCheckpoint 200602c0 f LIBCMTD:dbgheap.obj + 0001:0005f430 __CrtMemDifference 20060430 f LIBCMTD:dbgheap.obj + 0001:0005f560 __CrtMemDumpAllObjectsSince 20060560 f LIBCMTD:dbgheap.obj + 0001:0005f940 __CrtDumpMemoryLeaks 20060940 f LIBCMTD:dbgheap.obj + 0001:0005f9c0 __CrtMemDumpStatistics 200609c0 f LIBCMTD:dbgheap.obj + 0001:0005fa90 __setenvp 20060a90 f LIBCMTD:stdenvp.obj + 0001:0005fbd0 __setargv 20060bd0 f LIBCMTD:stdargv.obj + 0001:000600d0 __setmbcp 200610d0 f LIBCMTD:mbctype.obj + 0001:00060540 __getmbcp 20061540 f LIBCMTD:mbctype.obj + 0001:00060550 ___initmbctable 20061550 f LIBCMTD:mbctype.obj + 0001:00060560 ___crtGetEnvironmentStringsW 20061560 f LIBCMTD:aw_env.obj + 0001:000607e0 ___crtGetEnvironmentStringsA 200617e0 f LIBCMTD:aw_env.obj + 0001:00060a00 __heap_init 20061a00 f LIBCMTD:heapinit.obj + 0001:00060a50 __heap_term 20061a50 f LIBCMTD:heapinit.obj + 0001:00060aa0 __set_error_mode 20061aa0 f LIBCMTD:errmode.obj + 0001:00060af0 ___set_app_type 20061af0 f LIBCMTD:errmode.obj + 0001:00060b00 _DllMain@12 20061b00 f LIBCMTD:dllmain.obj + 0001:00060b10 __FF_MSGBANNER 20061b10 f LIBCMTD:crt0msg.obj + 0001:00060b60 __NMSG_WRITE 20061b60 f LIBCMTD:crt0msg.obj + 0001:00060d60 __GET_RTERRMSG 20061d60 f LIBCMTD:crt0msg.obj + 0001:00060db0 ___crtGetStringTypeW 20061db0 f LIBCMTD:aw_str.obj + 0001:00060fc0 ___crtGetStringTypeA 20061fc0 f LIBCMTD:aw_str.obj + 0001:00061120 __XcptFilter 20062120 f LIBCMTD:winxfltr.obj + 0001:00061340 __statusfp 20062340 f LIBCMTD:ieee87.obj + 0001:00061360 __clearfp 20062360 f LIBCMTD:ieee87.obj + 0001:00061380 __control87 20062380 f LIBCMTD:ieee87.obj + 0001:000613d0 __controlfp 200623d0 f LIBCMTD:ieee87.obj + 0001:000613f0 __fpreset 200623f0 f LIBCMTD:ieee87.obj + 0001:000617e0 __ZeroTail 200627e0 f LIBCMTD:intrncvt.obj + 0001:00061870 __IncMan 20062870 f LIBCMTD:intrncvt.obj + 0001:00061920 __RoundMan 20062920 f LIBCMTD:intrncvt.obj + 0001:00061a00 __CopyMan 20062a00 f LIBCMTD:intrncvt.obj + 0001:00061a50 __FillZeroMan 20062a50 f LIBCMTD:intrncvt.obj + 0001:00061a80 __IsZeroMan 20062a80 f LIBCMTD:intrncvt.obj + 0001:00061ac0 __ShrMan 20062ac0 f LIBCMTD:intrncvt.obj + 0001:00061bb0 __ld12cvt 20062bb0 f LIBCMTD:intrncvt.obj + 0001:00061e00 __ld12tod 20062e00 f LIBCMTD:intrncvt.obj + 0001:00061e20 __ld12tof 20062e20 f LIBCMTD:intrncvt.obj + 0001:00061e40 __ld12told 20062e40 f LIBCMTD:intrncvt.obj + 0001:00061f00 __atodbl 20062f00 f LIBCMTD:intrncvt.obj + 0001:00061f40 __atoldbl 20062f40 f LIBCMTD:intrncvt.obj + 0001:00061f80 __atoflt 20062f80 f LIBCMTD:intrncvt.obj + 0001:00061fc0 __fptostr 20062fc0 f LIBCMTD:_fptostr.obj + 0001:000620b0 __fltout2 200630b0 f LIBCMTD:cfout.obj + 0001:00062130 ___dtold 20063130 f LIBCMTD:cfout.obj + 0001:000622a0 __fptrap 200632a0 f LIBCMTD:crt0fp.obj + 0001:000622b0 ___strgtold12 200632b0 f LIBCMTD:strgtold.obj + 0001:00062df0 ___STRINGTOLD 20063df0 f LIBCMTD:strgtold.obj + 0001:00062e40 _mbtowc 20063e40 f LIBCMTD:mbtowc.obj + 0001:00062ec0 __mbtowc_lk 20063ec0 f LIBCMTD:mbtowc.obj + 0001:00063020 _isalpha 20064020 f LIBCMTD:_ctype.obj + 0001:00063070 _isupper 20064070 f LIBCMTD:_ctype.obj + 0001:000630b0 _islower 200640b0 f LIBCMTD:_ctype.obj + 0001:000630f0 _isdigit 200640f0 f LIBCMTD:_ctype.obj + 0001:00063130 _isxdigit 20064130 f LIBCMTD:_ctype.obj + 0001:00063180 _isspace 20064180 f LIBCMTD:_ctype.obj + 0001:000631c0 _ispunct 200641c0 f LIBCMTD:_ctype.obj + 0001:00063200 _isalnum 20064200 f LIBCMTD:_ctype.obj + 0001:00063250 _isprint 20064250 f LIBCMTD:_ctype.obj + 0001:000632a0 _isgraph 200642a0 f LIBCMTD:_ctype.obj + 0001:000632f0 _iscntrl 200642f0 f LIBCMTD:_ctype.obj + 0001:00063330 ___isascii 20064330 f LIBCMTD:_ctype.obj + 0001:00063340 ___toascii 20064340 f LIBCMTD:_ctype.obj + 0001:00063350 ___iscsymf 20064350 f LIBCMTD:_ctype.obj + 0001:000633b0 ___iscsym 200643b0 f LIBCMTD:_ctype.obj + 0001:00063410 __allshl 20064410 f LIBCMTD:llshl.obj + 0001:00063430 __filbuf 20064430 f LIBCMTD:_filbuf.obj + 0001:00063620 _ungetc 20064620 f LIBCMTD:ungetc.obj + 0001:00063690 __ungetc_lk 20064690 f LIBCMTD:ungetc.obj + 0001:000637d0 __snprintf 200647d0 f LIBCMTD:snprintf.obj + 0001:000638d0 __vsnprintf 200648d0 f LIBCMTD:vsnprint.obj + 0001:000639d0 __87except 200649d0 f LIBCMTD:87except.obj + 0001:00063b50 ___init_time 20064b50 f LIBCMTD:inittime.obj + 0001:00064690 ___init_numeric 20065690 f LIBCMTD:initnum.obj + 0001:000649c0 ___init_monetary 200659c0 f LIBCMTD:initmon.obj + 0001:00064e20 ___init_ctype 20065e20 f LIBCMTD:initctyp.obj + 0001:000651c0 ___init_collate 200661c0 f LIBCMTD:initcoll.obj + 0001:000651d0 _strcspn 200661d0 f LIBCMTD:strcspn.obj + 0001:00065210 _strncmp 20066210 f LIBCMTD:strncmp.obj + 0001:00065250 _strpbrk 20066250 f LIBCMTD:strpbrk.obj + 0001:00065290 ___get_qualified_locale 20066290 f LIBCMTD:getqloc.obj + 0001:00065930 __dosmaperr 20066930 f LIBCMTD:dosmap.obj + 0001:000659d0 __errno 200669d0 f LIBCMTD:dosmap.obj + 0001:000659e0 ___doserrno 200669e0 f LIBCMTD:dosmap.obj + 0001:000659f0 __matherr 200669f0 f LIBCMTD:matherr.obj + 0001:00065a00 __lseek 20066a00 f LIBCMTD:lseek.obj + 0001:00065a90 __lseek_lk 20066a90 f LIBCMTD:lseek.obj + 0001:00065b50 __write 20066b50 f LIBCMTD:write.obj + 0001:00065be0 __write_lk 20066be0 f LIBCMTD:write.obj + 0001:00065e60 __getbuf 20066e60 f LIBCMTD:_getbuf.obj + 0001:00065f30 __isatty 20066f30 f LIBCMTD:isatty.obj + 0001:00065f70 _wctomb 20066f70 f LIBCMTD:wctomb.obj + 0001:00065ff0 __wctomb_lk 20066ff0 f LIBCMTD:wctomb.obj + 0001:00066090 __aulldiv 20067090 f LIBCMTD:ulldiv.obj + 0001:00066100 __aullrem 20067100 f LIBCMTD:ullrem.obj + 0001:00066180 __commit 20067180 f LIBCMTD:commit.obj + 0001:00066260 __stbuf 20067260 f LIBCMTD:_sftbuf.obj + 0001:00066390 __ftbuf 20067390 f LIBCMTD:_sftbuf.obj + 0001:00066430 __freebuf 20067430 f LIBCMTD:_freebuf.obj + 0001:000664d0 __fcloseall 200674d0 f LIBCMTD:closeall.obj + 0001:000665b0 ?_set_new_handler@@YAP6AHI@ZP6AHI@Z@Z 200675b0 f LIBCMTD:handler.obj + 0001:000665e0 ?_query_new_handler@@YAP6AHI@ZXZ 200675e0 f LIBCMTD:handler.obj + 0001:000665f0 __callnewh 200675f0 f LIBCMTD:handler.obj + 0001:00066620 __malloc_base 20067620 f LIBCMTD:malloc.obj + 0001:00066640 __nh_malloc_base 20067640 f LIBCMTD:malloc.obj + 0001:000666c0 __heap_alloc_base 200676c0 f LIBCMTD:malloc.obj + 0001:00066730 __CrtDefaultAllocHook 20067730 f LIBCMTD:dbghook.obj + 0001:00066740 __expand_base 20067740 f LIBCMTD:expand.obj + 0001:00066800 __realloc_base 20067800 f LIBCMTD:realloc.obj + 0001:00066a00 __free_base 20067a00 f LIBCMTD:free.obj + 0001:00066a80 __heapchk 20067a80 f LIBCMTD:heapchk.obj + 0001:00066b00 __heapset 20067b00 f LIBCMTD:heapchk.obj + 0001:00066b10 __get_sbh_threshold 20067b10 f LIBCMTD:sbheap.obj + 0001:00066b20 __set_sbh_threshold 20067b20 f LIBCMTD:sbheap.obj + 0001:00066b50 ___sbh_new_region 20067b50 f LIBCMTD:sbheap.obj + 0001:00066d30 ___sbh_release_region 20067d30 f LIBCMTD:sbheap.obj + 0001:00066db0 ___sbh_decommit_pages 20067db0 f LIBCMTD:sbheap.obj + 0001:00066f10 ___sbh_find_block 20067f10 f LIBCMTD:sbheap.obj + 0001:00066fa0 ___sbh_free_block 20067fa0 f LIBCMTD:sbheap.obj + 0001:00067010 ___sbh_alloc_block 20068010 f LIBCMTD:sbheap.obj + 0001:000673d0 ___sbh_alloc_block_from_page 200683d0 f LIBCMTD:sbheap.obj + 0001:00067680 ___sbh_resize_block 20068680 f LIBCMTD:sbheap.obj + 0001:000677f0 ___sbh_heap_check 200687f0 f LIBCMTD:sbheap.obj + 0001:00067a30 _sprintf 20068a30 f LIBCMTD:sprintf.obj + 0001:00067b30 _wcslen 20068b30 f LIBCMTD:wcslen.obj + 0001:00067b60 ___addl 20068b60 f LIBCMTD:mantold.obj + 0001:00067ba0 ___add_12 20068ba0 f LIBCMTD:mantold.obj + 0001:00067c50 ___shl_12 20068c50 f LIBCMTD:mantold.obj + 0001:00067cb0 ___shr_12 20068cb0 f LIBCMTD:mantold.obj + 0001:00067d20 ___mtold12 20068d20 f LIBCMTD:mantold.obj + 0001:00067e60 _$I10_OUTPUT 20068e60 f LIBCMTD:x10fout.obj + 0001:00068380 ___ld12mul 20069380 f LIBCMTD:tenpow.obj + 0001:000687d0 ___multtenpow12 200697d0 f LIBCMTD:tenpow.obj + 0001:000688a0 __read 200698a0 f LIBCMTD:read.obj + 0001:00068930 __read_lk 20069930 f LIBCMTD:read.obj + 0001:00068d80 __Getdays 20069d80 f LIBCMTD:strftime.obj + 0001:00068eb0 __Getmonths 20069eb0 f LIBCMTD:strftime.obj + 0001:00068fe0 __Gettnames 20069fe0 f LIBCMTD:strftime.obj + 0001:00069350 _strftime 2006a350 f LIBCMTD:strftime.obj + 0001:00069370 __Strftime 2006a370 f LIBCMTD:strftime.obj + 0001:0006a120 ___getlocaleinfo 2006b120 f LIBCMTD:inithelp.obj + 0001:0006a3b0 _localeconv 2006b3b0 f LIBCMTD:lconv.obj + 0001:0006a3c0 ___crtGetLocaleInfoW 2006b3c0 f LIBCMTD:aw_loc.obj + 0001:0006a540 ___crtGetLocaleInfoA 2006b540 f LIBCMTD:aw_loc.obj + 0001:0006a6d0 _wcstol 2006b6d0 f LIBCMTD:wcstol.obj + 0001:0006a9d0 _wcstoul 2006b9d0 f LIBCMTD:wcstol.obj + 0001:0006a9f0 __strcmpi 2006b9f0 f LIBCMTD:stricmp.obj + 0001:0006a9f0 __stricmp 2006b9f0 f LIBCMTD:stricmp.obj + 0001:0006aac0 __alloc_osfhnd 2006bac0 f LIBCMTD:osfinfo.obj + 0001:0006acb0 __set_osfhnd 2006bcb0 f LIBCMTD:osfinfo.obj + 0001:0006ad70 __free_osfhnd 2006bd70 f LIBCMTD:osfinfo.obj + 0001:0006ae50 __get_osfhandle 2006be50 f LIBCMTD:osfinfo.obj + 0001:0006aec0 __open_osfhandle 2006bec0 f LIBCMTD:osfinfo.obj + 0001:0006afb0 __lock_fhandle 2006bfb0 f LIBCMTD:osfinfo.obj + 0001:0006b040 __unlock_fhandle 2006c040 f LIBCMTD:osfinfo.obj + 0001:0006b070 _fclose 2006c070 f LIBCMTD:fclose.obj + 0001:0006b100 __fclose_lk 2006c100 f LIBCMTD:fclose.obj + 0001:0006b1c0 ___tzset 2006c1c0 f LIBCMTD:tzset.obj + 0001:0006b200 __tzset 2006c200 f LIBCMTD:tzset.obj + 0001:0006b580 __isindst 2006c580 f LIBCMTD:tzset.obj + 0001:0006bab0 _towupper 2006cab0 f LIBCMTD:towupper.obj + 0001:0006bb60 __towupper_lk 2006cb60 f LIBCMTD:towupper.obj + 0001:0006bc20 _iswctype 2006cc20 f LIBCMTD:iswctype.obj + 0001:0006bca0 _is_wctype 2006cca0 f LIBCMTD:iswctype.obj + 0001:0006bcc0 __close 2006ccc0 f LIBCMTD:close.obj + 0001:0006bd40 __close_lk 2006cd40 f LIBCMTD:close.obj + 0001:0006bdf0 _wcstombs 2006cdf0 f LIBCMTD:wcstombs.obj + 0001:0006be70 __wcstombs_lk 2006ce70 f LIBCMTD:wcstombs.obj + 0001:0006c1d0 _getenv 2006d1d0 f LIBCMTD:getenv.obj + 0001:0006c200 __getenv_lk 2006d200 f LIBCMTD:getenv.obj + 0001:0006c2c0 __mbsnbicoll 2006d2c0 f LIBCMTD:mbsnbico.obj + 0001:0006c310 ___wtomb_environ 2006d310 f LIBCMTD:wtombenv.obj + 0001:0006c3c0 ___crtCompareStringW 2006d3c0 f LIBCMTD:aw_cmp.obj + 0001:0006c6c0 ___crtCompareStringA 2006d6c0 f LIBCMTD:aw_cmp.obj + 0001:0006cad0 ___crtsetenv 2006dad0 f LIBCMTD:setenv.obj + 0001:0006cf20 __mbschr 2006df20 f LIBCMTD:mbschr.obj + 0001:0006d00e _InterlockedDecrement@4 2006e00e f kernel32:KERNEL32.dll + 0001:0006d014 _InterlockedIncrement@4 2006e014 f kernel32:KERNEL32.dll + 0001:0006d01a _GetModuleFileNameA@12 2006e01a f kernel32:KERNEL32.dll + 0001:0006d020 _GetCommandLineA@0 2006e020 f kernel32:KERNEL32.dll + 0001:0006d026 _GetProcAddress@8 2006e026 f kernel32:KERNEL32.dll + 0001:0006d02c _GetModuleHandleA@4 2006e02c f kernel32:KERNEL32.dll + 0001:0006d032 _GetVersion@0 2006e032 f kernel32:KERNEL32.dll + 0001:0006d038 _GetCurrentThreadId@0 2006e038 f kernel32:KERNEL32.dll + 0001:0006d03e _TlsSetValue@8 2006e03e f kernel32:KERNEL32.dll + 0001:0006d044 _TlsAlloc@0 2006e044 f kernel32:KERNEL32.dll + 0001:0006d04a _TlsFree@4 2006e04a f kernel32:KERNEL32.dll + 0001:0006d050 _SetLastError@4 2006e050 f kernel32:KERNEL32.dll + 0001:0006d056 _TlsGetValue@4 2006e056 f kernel32:KERNEL32.dll + 0001:0006d05c _GetLastError@0 2006e05c f kernel32:KERNEL32.dll + 0001:0006d062 _GetCurrentThread@0 2006e062 f kernel32:KERNEL32.dll + 0001:0006d068 _DebugBreak@0 2006e068 f kernel32:KERNEL32.dll + 0001:0006d06e _GetStdHandle@4 2006e06e f kernel32:KERNEL32.dll + 0001:0006d074 _WriteFile@20 2006e074 f kernel32:KERNEL32.dll + 0001:0006d07a _OutputDebugStringA@4 2006e07a f kernel32:KERNEL32.dll + 0001:0006d080 _LoadLibraryA@4 2006e080 f kernel32:KERNEL32.dll + 0001:0006d086 _InitializeCriticalSection@4 2006e086 f kernel32:KERNEL32.dll + 0001:0006d08c _DeleteCriticalSection@4 2006e08c f kernel32:KERNEL32.dll + 0001:0006d092 _EnterCriticalSection@4 2006e092 f kernel32:KERNEL32.dll + 0001:0006d098 _LeaveCriticalSection@4 2006e098 f kernel32:KERNEL32.dll + 0001:0006d09e _ExitProcess@4 2006e09e f kernel32:KERNEL32.dll + 0001:0006d0a4 _FatalAppExitA@8 2006e0a4 f kernel32:KERNEL32.dll + 0001:0006d0aa _Sleep@4 2006e0aa f kernel32:KERNEL32.dll + 0001:0006d0b0 _MultiByteToWideChar@24 2006e0b0 f kernel32:KERNEL32.dll + 0001:0006d0b6 _WideCharToMultiByte@32 2006e0b6 f kernel32:KERNEL32.dll + 0001:0006d0bc _LCMapStringA@24 2006e0bc f kernel32:KERNEL32.dll + 0001:0006d0c2 _LCMapStringW@24 2006e0c2 f kernel32:KERNEL32.dll + 0001:0006d0c8 _RaiseException@16 2006e0c8 f kernel32:KERNEL32.dll + 0001:0006d0ce _TerminateProcess@8 2006e0ce f kernel32:KERNEL32.dll + 0001:0006d0d4 _GetCurrentProcess@0 2006e0d4 f kernel32:KERNEL32.dll + 0001:0006d0da _SetConsoleCtrlHandler@8 2006e0da f kernel32:KERNEL32.dll + 0001:0006d0e0 _SetHandleCount@4 2006e0e0 f kernel32:KERNEL32.dll + 0001:0006d0e6 _GetFileType@4 2006e0e6 f kernel32:KERNEL32.dll + 0001:0006d0ec _GetStartupInfoA@4 2006e0ec f kernel32:KERNEL32.dll + 0001:0006d0f2 _IsBadWritePtr@8 2006e0f2 f kernel32:KERNEL32.dll + 0001:0006d0f8 _IsBadReadPtr@8 2006e0f8 f kernel32:KERNEL32.dll + 0001:0006d0fe _HeapValidate@12 2006e0fe f kernel32:KERNEL32.dll + 0001:0006d104 _GetCPInfo@8 2006e104 f kernel32:KERNEL32.dll + 0001:0006d10a _GetACP@0 2006e10a f kernel32:KERNEL32.dll + 0001:0006d110 _GetOEMCP@0 2006e110 f kernel32:KERNEL32.dll + 0001:0006d116 _FreeEnvironmentStringsA@4 2006e116 f kernel32:KERNEL32.dll + 0001:0006d11c _FreeEnvironmentStringsW@4 2006e11c f kernel32:KERNEL32.dll + 0001:0006d122 _GetEnvironmentStrings@0 2006e122 f kernel32:KERNEL32.dll + 0001:0006d128 _GetEnvironmentStringsW@0 2006e128 f kernel32:KERNEL32.dll + 0001:0006d12e _HeapDestroy@4 2006e12e f kernel32:KERNEL32.dll + 0001:0006d134 _HeapCreate@12 2006e134 f kernel32:KERNEL32.dll + 0001:0006d13a _VirtualFree@12 2006e13a f kernel32:KERNEL32.dll + 0001:0006d140 _GetStringTypeA@20 2006e140 f kernel32:KERNEL32.dll + 0001:0006d146 _GetStringTypeW@16 2006e146 f kernel32:KERNEL32.dll + 0001:0006d14c _UnhandledExceptionFilter@4 2006e14c f kernel32:KERNEL32.dll + 0001:0006d152 _IsValidLocale@8 2006e152 f kernel32:KERNEL32.dll + 0001:0006d158 _IsValidCodePage@4 2006e158 f kernel32:KERNEL32.dll + 0001:0006d15e _GetUserDefaultLCID@0 2006e15e f kernel32:KERNEL32.dll + 0001:0006d164 _SetFilePointer@16 2006e164 f kernel32:KERNEL32.dll + 0001:0006d16a _FlushFileBuffers@4 2006e16a f kernel32:KERNEL32.dll + 0001:0006d170 _HeapAlloc@12 2006e170 f kernel32:KERNEL32.dll + 0001:0006d176 _HeapReAlloc@16 2006e176 f kernel32:KERNEL32.dll + 0001:0006d17c _HeapFree@12 2006e17c f kernel32:KERNEL32.dll + 0001:0006d182 _VirtualAlloc@16 2006e182 f kernel32:KERNEL32.dll + 0001:0006d188 _ReadFile@20 2006e188 f kernel32:KERNEL32.dll + 0001:0006d18e _GetLocaleInfoA@16 2006e18e f kernel32:KERNEL32.dll + 0001:0006d194 _GetLocaleInfoW@16 2006e194 f kernel32:KERNEL32.dll + 0001:0006d19a _SetStdHandle@8 2006e19a f kernel32:KERNEL32.dll + 0001:0006d1a0 _GetTimeZoneInformation@4 2006e1a0 f kernel32:KERNEL32.dll + 0001:0006d1a6 _CloseHandle@4 2006e1a6 f kernel32:KERNEL32.dll + 0001:0006d1ac _CompareStringA@24 2006e1ac f kernel32:KERNEL32.dll + 0001:0006d1b2 _CompareStringW@24 2006e1b2 f kernel32:KERNEL32.dll + 0001:0006d1b8 _SetEnvironmentVariableA@8 2006e1b8 f kernel32:KERNEL32.dll + 0002:00000b18 ??_C@_0P@LNLA@format?5?$CB?$DN?5NULL?$AA@ 2006fb18 LIBCMTD:sscanf.obj + 0002:00000b28 ??_C@_08EJDA@sscanf?4c?$AA@ 2006fb28 LIBCMTD:sscanf.obj + 0002:00000b34 ??_C@_0P@OOIN@string?5?$CB?$DN?5NULL?$AA@ 2006fb34 LIBCMTD:sscanf.obj + 0002:00000b50 ??_C@_0L@HE@vsprintf?4c?$AA@ 2006fb50 LIBCMTD:vsprintf.obj + 0002:00000b68 ??_C@_02JJJH@?6?6?$AA@ 2006fb68 LIBCMTD:assert.obj + 0002:00000b6c ??_C@_01BJG@?6?$AA@ 2006fb6c LIBCMTD:assert.obj + 0002:00000b70 ??_C@_03NAME@?4?4?4?$AA@ 2006fb70 LIBCMTD:assert.obj + 0002:00000b74 ??_C@_0CF@JPDF@Microsoft?5Visual?5C?$CL?$CL?5Runtime?5Lib@ 2006fb74 LIBCMTD:assert.obj + 0002:00000b9c ??_C@_0DN@MHHJ@?$CIPress?5Retry?5to?5debug?5the?5applic@ 2006fb9c LIBCMTD:assert.obj + 0002:00000bdc ??_C@_0HA@EJLJ@For?5information?5on?5how?5your?5prog@ 2006fbdc LIBCMTD:assert.obj + 0002:00000c4c ??_C@_0N@NOAB@Expression?3?5?$AA@ 2006fc4c LIBCMTD:assert.obj + 0002:00000c5c ??_C@_06JMOL@Line?3?5?$AA@ 2006fc5c LIBCMTD:assert.obj + 0002:00000c64 ??_C@_06FAAO@File?3?5?$AA@ 2006fc64 LIBCMTD:assert.obj + 0002:00000c6c ??_C@_0BH@NNCD@?$DMprogram?5name?5unknown?$DO?$AA@ 2006fc6c LIBCMTD:assert.obj + 0002:00000c84 ??_C@_09PIFG@Program?3?5?$AA@ 2006fc84 LIBCMTD:assert.obj + 0002:00000c90 ??_C@_0BC@OIMC@Assertion?5failed?$CB?$AA@ 2006fc90 LIBCMTD:assert.obj + 0002:00000ca4 ??_C@_05EODD@IsTNT?$AA@ 2006fca4 LIBCMTD:dllcrt0.obj + 0002:00000cac ??_C@_0N@IDOE@kernel32?4dll?$AA@ 2006fcac LIBCMTD:dllcrt0.obj + 0002:00000cbc ??_C@_0L@ENLJ@tidtable?4c?$AA@ 2006fcbc LIBCMTD:tidtable.obj + 0002:00000cd0 ??_C@_0BK@JEGK@IsProcessorFeaturePresent?$AA@ 2006fcd0 LIBCMTD:testfdiv.obj + 0002:00000cec ??_C@_08OBID@KERNEL32?$AA@ 2006fcec LIBCMTD:testfdiv.obj + 0002:00000d00 ??_C@_05OFLO@e?$CL000?$AA@ 2006fd00 LIBCMTD:cvt.obj + 0002:00000d08 ??_C@_0P@FHEA@stream?5?$CB?$DN?5NULL?$AA@ 2006fd08 LIBCMTD:input.obj + 0002:00000d18 ??_C@_07IEFA@input?4c?$AA@ 2006fd18 LIBCMTD:input.obj + 0002:00000d20 ??_C@_0BB@BLL@Assertion?5Failed?$AA@ 2006fd20 LIBCMTD:dbgrpt.obj + 0002:00000d34 ??_C@_05CKBG@Error?$AA@ 2006fd34 LIBCMTD:dbgrpt.obj + 0002:00000d3c ??_C@_07JCGI@Warning?$AA@ 2006fd3c LIBCMTD:dbgrpt.obj + 0002:00000d44 ??_C@_0M@ODEP@?$CFs?$CI?$CFd?$CJ?5?3?5?$CFs?$AA@ 2006fd44 LIBCMTD:dbgrpt.obj + 0002:00000d50 ??_C@_01FEHD@?$AN?$AA@ 2006fd50 LIBCMTD:dbgrpt.obj + 0002:00000d54 ??_C@_0BD@BEAK@Assertion?5failed?3?5?$AA@ 2006fd54 LIBCMTD:dbgrpt.obj + 0002:00000d68 ??_C@_0CL@LCCP@_CrtDbgReport?3?5String?5too?5long?5o@ 2006fd68 LIBCMTD:dbgrpt.obj + 0002:00000d94 ??_C@_0DC@EMDK@Second?5Chance?5Assertion?5Failed?3?5@ 2006fd94 LIBCMTD:dbgrpt.obj + 0002:00000dc8 ??_C@_09NJAK@wsprintfA?$AA@ 2006fdc8 LIBCMTD:dbgrpt.obj + 0002:00000dd4 ??_C@_0L@HKL@user32?4dll?$AA@ 2006fdd4 LIBCMTD:dbgrpt.obj + 0002:00000de0 ??_C@_0CD@GCMD@Microsoft?5Visual?5C?$CL?$CL?5Debug?5Libra@ 2006fde0 LIBCMTD:dbgrpt.obj + 0002:00000e04 ??_C@_0FD@EBDD@Debug?5?$CFs?$CB?6?6Program?3?5?$CFs?$CFs?$CFs?$CFs?$CFs?$CFs@ 2006fe04 LIBCMTD:dbgrpt.obj + 0002:00000e58 ??_C@_09DCKN@?6Module?3?5?$AA@ 2006fe58 LIBCMTD:dbgrpt.obj + 0002:00000e64 ??_C@_07HJII@?6File?3?5?$AA@ 2006fe64 LIBCMTD:dbgrpt.obj + 0002:00000e6c ??_C@_07LFGN@?6Line?3?5?$AA@ 2006fe6c LIBCMTD:dbgrpt.obj + 0002:00000e74 ??_C@_00A@?$AA@ 2006fe74 LIBCMTD:dbgrpt.obj + 0002:00000e78 ??_C@_0HD@BHFH@?6?6For?5information?5on?5how?5your?5pr@ 2006fe78 LIBCMTD:dbgrpt.obj + 0002:00000eec ??_C@_08GFHP@dbgrpt?4c?$AA@ 2006feec LIBCMTD:dbgrpt.obj + 0002:00000ef8 ??_C@_0BG@FBEA@szUserMessage?5?$CB?$DN?5NULL?$AA@ 2006fef8 LIBCMTD:dbgrpt.obj + 0002:00000f18 __DEFAULT_CW_in_mem 2006ff18 LIBCMTD:common.obj + 0002:00000f1a __pi_by_2_to_61 2006ff1a LIBCMTD:common.obj + 0002:00000f54 ??_C@_07PDLA@mlock?4c?$AA@ 2006ff54 LIBCMTD:mlock.obj + 0002:00000f5c ??_C@_07PCLE@LC_TIME?$AA@ 2006ff5c LIBCMTD:setlocal.obj + 0002:00000f64 ??_C@_0L@NKG@LC_NUMERIC?$AA@ 2006ff64 LIBCMTD:setlocal.obj + 0002:00000f70 ??_C@_0M@CBIH@LC_MONETARY?$AA@ 2006ff70 LIBCMTD:setlocal.obj + 0002:00000f7c ??_C@_08LFGE@LC_CTYPE?$AA@ 2006ff7c LIBCMTD:setlocal.obj + 0002:00000f88 ??_C@_0L@CFLC@LC_COLLATE?$AA@ 2006ff88 LIBCMTD:setlocal.obj + 0002:00000f94 ??_C@_06GCPK@LC_ALL?$AA@ 2006ff94 LIBCMTD:setlocal.obj + 0002:00000f9c ??_C@_01FAJB@?$DL?$AA@ 2006ff9c LIBCMTD:setlocal.obj + 0002:00000fa0 ??_C@_02BGDO@?$DN?$DL?$AA@ 2006ffa0 LIBCMTD:setlocal.obj + 0002:00000fa4 ??_C@_0L@MOEE@setlocal?4c?$AA@ 2006ffa4 LIBCMTD:setlocal.obj + 0002:00000fb0 ??_C@_01KPOD@?$DN?$AA@ 2006ffb0 LIBCMTD:setlocal.obj + 0002:00000fb4 ??_C@_03DBOJ@_?4?0?$AA@ 2006ffb4 LIBCMTD:setlocal.obj + 0002:00000fb8 ??_C@_01PJCK@?4?$AA@ 2006ffb8 LIBCMTD:setlocal.obj + 0002:00000fbc ??_C@_01NON@_?$AA@ 2006ffbc LIBCMTD:setlocal.obj + 0002:00000fc0 ??_C@_08GINJ@aw_map?4c?$AA@ 2006ffc0 LIBCMTD:aw_map.obj + 0002:00000fcc ??_C@_01A@?$AA?$AA@ 2006ffcc LIBCMTD:aw_map.obj + 0002:00000fd0 ??_C@_13A@?$AA?$AA?$AA?$AA@ 2006ffd0 LIBCMTD:aw_map.obj + 0002:00000fd4 ??_C@_03GAFO@_yn?$AA@ 2006ffd4 LIBCMTD:fpexcept.obj + 0002:00000fd8 ??_C@_03GNLD@_y1?$AA@ 2006ffd8 LIBCMTD:fpexcept.obj + 0002:00000fdc ??_C@_03MHCE@_y0?$AA@ 2006ffdc LIBCMTD:fpexcept.obj + 0002:00000fe0 ??_C@_05PAK@frexp?$AA@ 2006ffe0 LIBCMTD:fpexcept.obj + 0002:00000fe8 ??_C@_04JKCH@fmod?$AA@ 2006ffe8 LIBCMTD:fpexcept.obj + 0002:00000ff0 ??_C@_06GGHF@_hypot?$AA@ 2006fff0 LIBCMTD:fpexcept.obj + 0002:00000ff8 ??_C@_05JNMC@_cabs?$AA@ 2006fff8 LIBCMTD:fpexcept.obj + 0002:00001000 ??_C@_05MEKH@ldexp?$AA@ 20070000 LIBCMTD:fpexcept.obj + 0002:00001008 ??_C@_04KAOJ@modf?$AA@ 20070008 LIBCMTD:fpexcept.obj + 0002:00001010 ??_C@_04EHAJ@fabs?$AA@ 20070010 LIBCMTD:fpexcept.obj + 0002:00001018 ??_C@_05MFBN@floor?$AA@ 20070018 LIBCMTD:fpexcept.obj + 0002:00001020 ??_C@_04JJMP@ceil?$AA@ 20070020 LIBCMTD:fpexcept.obj + 0002:00001028 ??_C@_03JLPI@tan?$AA@ 20070028 LIBCMTD:fpexcept.obj + 0002:0000102c ??_C@_03DLFL@cos?$AA@ 2007002c LIBCMTD:fpexcept.obj + 0002:00001030 ??_C@_03JAMN@sin?$AA@ 20070030 LIBCMTD:fpexcept.obj + 0002:00001034 ??_C@_04LMBE@sqrt?$AA@ 20070034 LIBCMTD:fpexcept.obj + 0002:0000103c ??_C@_05BFPO@atan2?$AA@ 2007003c LIBCMTD:fpexcept.obj + 0002:00001044 ??_C@_04EAGN@atan?$AA@ 20070044 LIBCMTD:fpexcept.obj + 0002:0000104c ??_C@_04OAMO@acos?$AA@ 2007004c LIBCMTD:fpexcept.obj + 0002:00001054 ??_C@_04ELFI@asin?$AA@ 20070054 LIBCMTD:fpexcept.obj + 0002:0000105c ??_C@_04LFAD@tanh?$AA@ 2007005c LIBCMTD:fpexcept.obj + 0002:00001064 ??_C@_04PFKD@cosh?$AA@ 20070064 LIBCMTD:fpexcept.obj + 0002:0000106c ??_C@_04NPAI@sinh?$AA@ 2007006c LIBCMTD:fpexcept.obj + 0002:00001074 ??_C@_05IJHH@log10?$AA@ 20070074 LIBCMTD:fpexcept.obj + 0002:0000107c ??_C@_03BGKD@log?$AA@ 2007007c LIBCMTD:fpexcept.obj + 0002:00001080 ??_C@_03EGFG@pow?$AA@ 20070080 LIBCMTD:fpexcept.obj + 0002:00001084 ??_C@_03CDGJ@exp?$AA@ 20070084 LIBCMTD:fpexcept.obj + 0002:00001098 ??_C@_0DP@IHIN@?$CI?$CCinconsistent?5IOB?5fields?$CC?0?5stre@ 20070098 LIBCMTD:_flsbuf.obj + 0002:000010d8 ??_C@_09LGEI@_flsbuf?4c?$AA@ 200700d8 LIBCMTD:_flsbuf.obj + 0002:000010e4 ??_C@_0M@KJPK@str?5?$CB?$DN?5NULL?$AA@ 200700e4 LIBCMTD:_flsbuf.obj + 0002:000010f0 ___lookuptable 200700f0 LIBCMTD:output.obj + 0002:0000114c ??_C@_1O@POHA@?$AA?$CI?$AAn?$AAu?$AAl?$AAl?$AA?$CJ?$AA?$AA@ 2007014c LIBCMTD:output.obj + 0002:0000115c ??_C@_06ONKE@?$CInull?$CJ?$AA@ 2007015c LIBCMTD:output.obj + 0002:00001164 ??_C@_08MIHI@output?4c?$AA@ 20070164 LIBCMTD:output.obj + 0002:00001170 ??_C@_0P@PIHB@ch?5?$CB?$DN?5_T?$CI?8?20?8?$CJ?$AA@ 20070170 LIBCMTD:output.obj + 0002:000011b0 ??_C@_08CNMA@winsig?4c?$AA@ 200701b0 LIBCMTD:winsig.obj + 0002:000011bc ??_C@_0BD@NJFP@GetLastActivePopup?$AA@ 200701bc LIBCMTD:crtmbox.obj + 0002:000011d0 ??_C@_0BA@GILI@GetActiveWindow?$AA@ 200701d0 LIBCMTD:crtmbox.obj + 0002:000011e0 ??_C@_0M@PKCK@MessageBoxA?$AA@ 200701e0 LIBCMTD:crtmbox.obj + 0002:000011ec ??_C@_09DLOM@fprintf?4c?$AA@ 200701ec LIBCMTD:fprintf.obj + 0002:000011f8 ??_C@_09GCII@setvbuf?4c?$AA@ 200701f8 LIBCMTD:setvbuf.obj + 0002:00001204 ??_C@_07HPAH@_file?4c?$AA@ 20070204 LIBCMTD:_file.obj + 0002:0000120c ??_C@_08KFDJ@ioinit?4c?$AA@ 2007020c LIBCMTD:ioinit.obj + 0002:00001218 ??_C@_06DPMO@Client?$AA@ 20070218 LIBCMTD:dbgheap.obj + 0002:00001220 ??_C@_06BAFM@Ignore?$AA@ 20070220 LIBCMTD:dbgheap.obj + 0002:00001228 ??_C@_03DICE@CRT?$AA@ 20070228 LIBCMTD:dbgheap.obj + 0002:0000122c ??_C@_06BILL@Normal?$AA@ 2007022c LIBCMTD:dbgheap.obj + 0002:00001234 ??_C@_04NEN@Free?$AA@ 20070234 LIBCMTD:dbgheap.obj + 0002:0000123c ??_C@_0DC@CGID@Error?3?5memory?5allocation?3?5bad?5me@ 2007023c LIBCMTD:dbgheap.obj + 0002:00001270 ??_C@_0CE@FMIA@Invalid?5allocation?5size?3?5?$CFu?5byte@ 20070270 LIBCMTD:dbgheap.obj + 0002:00001294 ??_C@_02DILL@?$CFs?$AA@ 20070294 LIBCMTD:dbgheap.obj + 0002:00001298 ??_C@_0CB@CPBI@Client?5hook?5allocation?5failure?4?6@ 20070298 LIBCMTD:dbgheap.obj + 0002:000012bc ??_C@_0DF@DKHF@Client?5hook?5allocation?5failure?5a@ 200702bc LIBCMTD:dbgheap.obj + 0002:000012f4 ??_C@_09IMJC@dbgheap?4c?$AA@ 200702f4 LIBCMTD:dbgheap.obj + 0002:00001300 ??_C@_0BC@EPKL@_CrtCheckMemory?$CI?$CJ?$AA@ 20070300 LIBCMTD:dbgheap.obj + 0002:00001314 ??_C@_0BK@JDHA@_pFirstBlock?5?$DN?$DN?5pOldBlock?$AA@ 20070314 LIBCMTD:dbgheap.obj + 0002:00001330 ??_C@_0BJ@NMEF@_pLastBlock?5?$DN?$DN?5pOldBlock?$AA@ 20070330 LIBCMTD:dbgheap.obj + 0002:0000134c ??_C@_0DC@BOCN@fRealloc?5?$HM?$HM?5?$CI?$CBfRealloc?5?$CG?$CG?5pNewBl@ 2007034c LIBCMTD:dbgheap.obj + 0002:00001380 ??_C@_0DK@DKOM@_BLOCK_TYPE?$CIpOldBlock?9?$DOnBlockUse@ 20070380 LIBCMTD:dbgheap.obj + 0002:000013bc ??_C@_0EF@EDKM@pOldBlock?9?$DOnLine?5?$DN?$DN?5IGNORE_LINE?5@ 200703bc LIBCMTD:dbgheap.obj + 0002:00001404 ??_C@_0CC@GPMO@_CrtIsValidHeapPointer?$CIpUserData@ 20070404 LIBCMTD:dbgheap.obj + 0002:00001428 ??_C@_0CN@FHGE@Allocation?5too?5large?5or?5negative@ 20070428 LIBCMTD:dbgheap.obj + 0002:00001458 ??_C@_0CE@EAMN@Client?5hook?5re?9allocation?5failur@ 20070458 LIBCMTD:dbgheap.obj + 0002:0000147c ??_C@_0DI@KKMM@Client?5hook?5re?9allocation?5failur@ 2007047c LIBCMTD:dbgheap.obj + 0002:000014b4 ??_C@_0BG@JCEC@_pFirstBlock?5?$DN?$DN?5pHead?$AA@ 200704b4 LIBCMTD:dbgheap.obj + 0002:000014cc ??_C@_0BF@NLNN@_pLastBlock?5?$DN?$DN?5pHead?$AA@ 200704cc LIBCMTD:dbgheap.obj + 0002:000014e4 ??_C@_0BO@PLHH@pHead?9?$DOnBlockUse?5?$DN?$DN?5nBlockUse?$AA@ 200704e4 LIBCMTD:dbgheap.obj + 0002:00001504 ??_C@_0DN@KKIO@pHead?9?$DOnLine?5?$DN?$DN?5IGNORE_LINE?5?$CG?$CG?5p@ 20070504 LIBCMTD:dbgheap.obj + 0002:00001544 ??_C@_0CK@OJNB@DAMAGE?3?5after?5?$CFhs?5block?5?$CI?$CD?$CFd?$CJ?5at@ 20070544 LIBCMTD:dbgheap.obj + 0002:00001570 ??_C@_0CL@IJIL@DAMAGE?3?5before?5?$CFhs?5block?5?$CI?$CD?$CFd?$CJ?5a@ 20070570 LIBCMTD:dbgheap.obj + 0002:0000159c ??_C@_0CH@PHOC@_BLOCK_TYPE_IS_VALID?$CIpHead?9?$DOnBlo@ 2007059c LIBCMTD:dbgheap.obj + 0002:000015c4 ??_C@_0BL@MBNA@Client?5hook?5free?5failure?4?6?$AA@ 200705c4 LIBCMTD:dbgheap.obj + 0002:000015e0 ??_C@_0DK@EKKL@memory?5check?5error?5at?50x?$CF08X?5?$DN?50@ 200705e0 LIBCMTD:dbgheap.obj + 0002:0000161c ??_C@_0CJ@GCII@?$CFhs?5located?5at?50x?$CF08X?5is?5?$CFu?5byte@ 2007061c LIBCMTD:dbgheap.obj + 0002:00001648 ??_C@_0CA@EJPI@?$CFhs?5allocated?5at?5file?5?$CFhs?$CI?$CFd?$CJ?4?6?$AA@ 20070648 LIBCMTD:dbgheap.obj + 0002:00001668 ??_C@_0CJ@LHHG@DAMAGE?3?5on?5top?5of?5Free?5block?5at?5@ 20070668 LIBCMTD:dbgheap.obj + 0002:00001694 ??_C@_07GJHM@DAMAGED?$AA@ 20070694 LIBCMTD:dbgheap.obj + 0002:0000169c ??_C@_0CL@ODIE@_heapchk?5fails?5with?5unknown?5retu@ 2007069c LIBCMTD:dbgheap.obj + 0002:000016c8 ??_C@_0CC@EHNC@_heapchk?5fails?5with?5_HEAPBADPTR?4@ 200706c8 LIBCMTD:dbgheap.obj + 0002:000016ec ??_C@_0CC@JNDP@_heapchk?5fails?5with?5_HEAPBADEND?4@ 200706ec LIBCMTD:dbgheap.obj + 0002:00001710 ??_C@_0CD@DNMG@_heapchk?5fails?5with?5_HEAPBADNODE@ 20070710 LIBCMTD:dbgheap.obj + 0002:00001734 ??_C@_0CE@JMCB@_heapchk?5fails?5with?5_HEAPBADBEGI@ 20070734 LIBCMTD:dbgheap.obj + 0002:00001758 ??_C@_0CD@MEMI@Bad?5memory?5block?5found?5at?50x?$CF08X@ 20070758 LIBCMTD:dbgheap.obj + 0002:0000177c ??_C@_0CI@GMAO@_CrtMemCheckPoint?3?5NULL?5state?5po@ 2007077c LIBCMTD:dbgheap.obj + 0002:000017a4 ??_C@_0CI@MNAE@_CrtMemDifference?3?5NULL?5state?5po@ 200707a4 LIBCMTD:dbgheap.obj + 0002:000017cc ??_C@_0BH@PKIJ@Object?5dump?5complete?4?6?$AA@ 200707cc LIBCMTD:dbgheap.obj + 0002:000017e4 ??_C@_0DB@DBPF@crt?5block?5at?50x?$CF08X?0?5subtype?5?$CFx?0@ 200707e4 LIBCMTD:dbgheap.obj + 0002:00001818 ??_C@_0CI@JLM@normal?5block?5at?50x?$CF08X?0?5?$CFu?5bytes@ 20070818 LIBCMTD:dbgheap.obj + 0002:00001840 ??_C@_0DE@PFEA@client?5block?5at?50x?$CF08X?0?5subtype?5@ 20070840 LIBCMTD:dbgheap.obj + 0002:00001874 ??_C@_06MBCE@?$HL?$CFld?$HN?5?$AA@ 20070874 LIBCMTD:dbgheap.obj + 0002:0000187c ??_C@_0L@BLDJ@?$CFhs?$CI?$CFd?$CJ?5?3?5?$AA@ 2007087c LIBCMTD:dbgheap.obj + 0002:00001888 ??_C@_0BE@FEMA@?$CDFile?5Error?$CD?$CI?$CFd?$CJ?5?3?5?$AA@ 20070888 LIBCMTD:dbgheap.obj + 0002:0000189c ??_C@_0BE@FPPM@Dumping?5objects?5?9?$DO?6?$AA@ 2007089c LIBCMTD:dbgheap.obj + 0002:000018b0 ??_C@_0BA@POLM@?5Data?3?5?$DM?$CFs?$DO?5?$CFs?6?$AA@ 200708b0 LIBCMTD:dbgheap.obj + 0002:000018c0 ??_C@_05JLAO@?$CF?42X?5?$AA@ 200708c0 LIBCMTD:dbgheap.obj + 0002:000018c8 ??_C@_0BI@KONA@Detected?5memory?5leaks?$CB?6?$AA@ 200708c8 LIBCMTD:dbgheap.obj + 0002:000018e0 ??_C@_0BP@LCCN@Total?5allocations?3?5?$CFld?5bytes?4?6?$AA@ 200708e0 LIBCMTD:dbgheap.obj + 0002:00001900 ??_C@_0CB@COID@Largest?5number?5used?3?5?$CFld?5bytes?4?6@ 20070900 LIBCMTD:dbgheap.obj + 0002:00001924 ??_C@_0BO@MEJD@?$CFld?5bytes?5in?5?$CFld?5?$CFhs?5Blocks?4?6?$AA@ 20070924 LIBCMTD:dbgheap.obj + 0002:00001944 ??_C@_09IGKD@stdenvp?4c?$AA@ 20070944 LIBCMTD:stdenvp.obj + 0002:00001950 ??_C@_09MPMN@stdargv?4c?$AA@ 20070950 LIBCMTD:stdargv.obj + 0002:0000195c ??_C@_08FFKK@aw_env?4c?$AA@ 2007095c LIBCMTD:aw_env.obj + 0002:00001968 ??_C@_0P@GGKG@runtime?5error?5?$AA@ 20070968 LIBCMTD:crt0msg.obj + 0002:00001978 ??_C@_02PIMC@?$AN?6?$AA@ 20070978 LIBCMTD:crt0msg.obj + 0002:0000197c ??_C@_0O@DELO@TLOSS?5error?$AN?6?$AA@ 2007097c LIBCMTD:crt0msg.obj + 0002:0000198c ??_C@_0N@OMLL@SING?5error?$AN?6?$AA@ 2007098c LIBCMTD:crt0msg.obj + 0002:0000199c ??_C@_0P@OJAK@DOMAIN?5error?$AN?6?$AA@ 2007099c LIBCMTD:crt0msg.obj + 0002:000019ac ??_C@_0CF@EANP@R6028?$AN?6?9?5unable?5to?5initialize?5he@ 200709ac LIBCMTD:crt0msg.obj + 0002:000019d4 ??_C@_0DF@ECGN@R6027?$AN?6?9?5not?5enough?5space?5for?5lo@ 200709d4 LIBCMTD:crt0msg.obj + 0002:00001a0c ??_C@_0DF@FKAC@R6026?$AN?6?9?5not?5enough?5space?5for?5st@ 20070a0c LIBCMTD:crt0msg.obj + 0002:00001a44 ??_C@_0CG@DPMN@R6025?$AN?6?9?5pure?5virtual?5function?5c@ 20070a44 LIBCMTD:crt0msg.obj + 0002:00001a6c ??_C@_0DF@CKIP@R6024?$AN?6?9?5not?5enough?5space?5for?5_o@ 20070a6c LIBCMTD:crt0msg.obj + 0002:00001aa4 ??_C@_0CJ@GGOE@R6019?$AN?6?9?5unable?5to?5open?5console?5@ 20070aa4 LIBCMTD:crt0msg.obj + 0002:00001ad0 ??_C@_0CB@LBOB@R6018?$AN?6?9?5unexpected?5heap?5error?$AN?6@ 20070ad0 LIBCMTD:crt0msg.obj + 0002:00001af4 ??_C@_0CN@FPEG@R6017?$AN?6?9?5unexpected?5multithread?5@ 20070af4 LIBCMTD:crt0msg.obj + 0002:00001b24 ??_C@_0CM@OBIC@R6016?$AN?6?9?5not?5enough?5space?5for?5th@ 20070b24 LIBCMTD:crt0msg.obj + 0002:00001b50 ??_C@_0CB@HPAL@?$AN?6abnormal?5program?5termination?$AN?6@ 20070b50 LIBCMTD:crt0msg.obj + 0002:00001b74 ??_C@_0CM@JOOB@R6009?$AN?6?9?5not?5enough?5space?5for?5en@ 20070b74 LIBCMTD:crt0msg.obj + 0002:00001ba0 ??_C@_0CK@OIBL@R6008?$AN?6?9?5not?5enough?5space?5for?5ar@ 20070ba0 LIBCMTD:crt0msg.obj + 0002:00001bcc ??_C@_0CF@LKPB@R6002?$AN?6?9?5floating?5point?5not?5load@ 20070bcc LIBCMTD:crt0msg.obj + 0002:00001bf4 ??_C@_0BK@DEOK@Runtime?5Error?$CB?6?6Program?3?5?$AA@ 20070bf4 LIBCMTD:crt0msg.obj + 0002:00001c10 ??_C@_08OKGP@aw_str?4c?$AA@ 20070c10 LIBCMTD:aw_str.obj + 0002:00001c1c ??_C@_08MOLN@mbtowc?4c?$AA@ 20070c1c LIBCMTD:mbtowc.obj + 0002:00001c28 ??_C@_0CD@NPPL@MB_CUR_MAX?5?$DN?$DN?51?5?$HM?$HM?5MB_CUR_MAX?5?$DN?$DN@ 20070c28 LIBCMTD:mbtowc.obj + 0002:00001c4c ??_C@_09DNOI@_filbuf?4c?$AA@ 20070c4c LIBCMTD:_filbuf.obj + 0002:00001c58 ??_C@_08JKP@ungetc?4c?$AA@ 20070c58 LIBCMTD:ungetc.obj + 0002:00001c64 ??_C@_09NEAG@sprintf?4c?$AA@ 20070c64 LIBCMTD:snprintf.obj + 0002:00001c70 ??_C@_0L@EEGJ@inittime?4c?$AA@ 20070c70 LIBCMTD:inittime.obj + 0002:00001c7c ??_C@_09IMJJ@initnum?4c?$AA@ 20070c7c LIBCMTD:initnum.obj + 0002:00001c88 ??_C@_09KPNI@initmon?4c?$AA@ 20070c88 LIBCMTD:initmon.obj + 0002:00001c94 ??_C@_0L@COIL@initctyp?4c?$AA@ 20070c94 LIBCMTD:initctyp.obj + 0002:00001ca0 ??_C@_0O@GLHB@united?9states?$AA@ 20070ca0 LIBCMTD:getqloc.obj + 0002:00001cb0 ??_C@_0P@IIKK@united?9kingdom?$AA@ 20070cb0 LIBCMTD:getqloc.obj + 0002:00001cc0 ??_C@_0O@ILBP@united?5states?$AA@ 20070cc0 LIBCMTD:getqloc.obj + 0002:00001cd0 ??_C@_0P@KBEK@united?5kingdom?$AA@ 20070cd0 LIBCMTD:getqloc.obj + 0002:00001ce0 ??_C@_03IGKO@twn?$AA@ 20070ce0 LIBCMTD:getqloc.obj + 0002:00001ce4 ??_C@_06CMKP@turkey?$AA@ 20070ce4 LIBCMTD:getqloc.obj + 0002:00001cec ??_C@_03PMGP@tur?$AA@ 20070cec LIBCMTD:getqloc.obj + 0002:00001cf0 ??_C@_06OBJC@taiwan?$AA@ 20070cf0 LIBCMTD:getqloc.obj + 0002:00001cf8 ??_C@_0M@EKGN@switzerland?$AA@ 20070cf8 LIBCMTD:getqloc.obj + 0002:00001d04 ??_C@_06PJFL@sweden?$AA@ 20070d04 LIBCMTD:getqloc.obj + 0002:00001d0c ??_C@_03JDMO@swe?$AA@ 20070d0c LIBCMTD:getqloc.obj + 0002:00001d10 ??_C@_03COKO@svk?$AA@ 20070d10 LIBCMTD:getqloc.obj + 0002:00001d14 ??_C@_05FBKI@spain?$AA@ 20070d14 LIBCMTD:getqloc.obj + 0002:00001d1c ??_C@_0M@BDFM@south?9korea?$AA@ 20070d1c LIBCMTD:getqloc.obj + 0002:00001d28 ??_C@_0M@HNHN@south?5korea?$AA@ 20070d28 LIBCMTD:getqloc.obj + 0002:00001d34 ??_C@_03FANM@sgp?$AA@ 20070d34 LIBCMTD:getqloc.obj + 0002:00001d38 ??_C@_09EOGO@singapore?$AA@ 20070d38 LIBCMTD:getqloc.obj + 0002:00001d44 ??_C@_06NHNE@russia?$AA@ 20070d44 LIBCMTD:getqloc.obj + 0002:00001d4c ??_C@_03CKBC@prt?$AA@ 20070d4c LIBCMTD:getqloc.obj + 0002:00001d50 ??_C@_08EJOH@pr?9china?$AA@ 20070d50 LIBCMTD:getqloc.obj + 0002:00001d5c ??_C@_08CHMG@pr?5china?$AA@ 20070d5c LIBCMTD:getqloc.obj + 0002:00001d68 ??_C@_08GGAN@portugal?$AA@ 20070d68 LIBCMTD:getqloc.obj + 0002:00001d74 ??_C@_06NOHH@poland?$AA@ 20070d74 LIBCMTD:getqloc.obj + 0002:00001d7c ??_C@_03EECH@pol?$AA@ 20070d7c LIBCMTD:getqloc.obj + 0002:00001d80 ??_C@_03DPCG@nzl?$AA@ 20070d80 LIBCMTD:getqloc.obj + 0002:00001d84 ??_C@_02OCJK@nz?$AA@ 20070d84 LIBCMTD:getqloc.obj + 0002:00001d88 ??_C@_06BAPG@norway?$AA@ 20070d88 LIBCMTD:getqloc.obj + 0002:00001d90 ??_C@_0M@MMKA@new?9zealand?$AA@ 20070d90 LIBCMTD:getqloc.obj + 0002:00001d9c ??_C@_0M@OFEA@new?5zealand?$AA@ 20070d9c LIBCMTD:getqloc.obj + 0002:00001da8 ??_C@_0M@POGE@netherlands?$AA@ 20070da8 LIBCMTD:getqloc.obj + 0002:00001db4 ??_C@_06KJLN@mexico?$AA@ 20070db4 LIBCMTD:getqloc.obj + 0002:00001dbc ??_C@_03HEJJ@mex?$AA@ 20070dbc LIBCMTD:getqloc.obj + 0002:00001dc0 ??_C@_05OOCK@korea?$AA@ 20070dc0 LIBCMTD:getqloc.obj + 0002:00001dc8 ??_C@_05EEKJ@japan?$AA@ 20070dc8 LIBCMTD:getqloc.obj + 0002:00001dd0 ??_C@_05DHEA@italy?$AA@ 20070dd0 LIBCMTD:getqloc.obj + 0002:00001dd8 ??_C@_03DEBD@irl?$AA@ 20070dd8 LIBCMTD:getqloc.obj + 0002:00001ddc ??_C@_07JJLD@ireland?$AA@ 20070ddc LIBCMTD:getqloc.obj + 0002:00001de4 ??_C@_07FGBB@iceland?$AA@ 20070de4 LIBCMTD:getqloc.obj + 0002:00001dec ??_C@_07IDDB@hungary?$AA@ 20070dec LIBCMTD:getqloc.obj + 0002:00001df4 ??_C@_09MGEH@hong?9kong?$AA@ 20070df4 LIBCMTD:getqloc.obj + 0002:00001e00 ??_C@_09OHNN@hong?5kong?$AA@ 20070e00 LIBCMTD:getqloc.obj + 0002:00001e0c ??_C@_07CCAK@holland?$AA@ 20070e0c LIBCMTD:getqloc.obj + 0002:00001e14 ??_C@_03EFD@hkg?$AA@ 20070e14 LIBCMTD:getqloc.obj + 0002:00001e18 ??_C@_06FLIB@greece?$AA@ 20070e18 LIBCMTD:getqloc.obj + 0002:00001e20 ??_C@_0O@MEPL@great?5britain?$AA@ 20070e20 LIBCMTD:getqloc.obj + 0002:00001e30 ??_C@_03EJIM@grc?$AA@ 20070e30 LIBCMTD:getqloc.obj + 0002:00001e34 ??_C@_07CPP@germany?$AA@ 20070e34 LIBCMTD:getqloc.obj + 0002:00001e3c ??_C@_03CAMC@gbr?$AA@ 20070e3c LIBCMTD:getqloc.obj + 0002:00001e40 ??_C@_06DNOH@france?$AA@ 20070e40 LIBCMTD:getqloc.obj + 0002:00001e48 ??_C@_07IMFA@finland?$AA@ 20070e48 LIBCMTD:getqloc.obj + 0002:00001e50 ??_C@_07LGIB@england?$AA@ 20070e50 LIBCMTD:getqloc.obj + 0002:00001e58 ??_C@_03GOFI@dnk?$AA@ 20070e58 LIBCMTD:getqloc.obj + 0002:00001e5c ??_C@_07ILKC@denmark?$AA@ 20070e5c LIBCMTD:getqloc.obj + 0002:00001e64 ??_C@_03ELPA@cze?$AA@ 20070e64 LIBCMTD:getqloc.obj + 0002:00001e68 ??_C@_03KHAN@chn?$AA@ 20070e68 LIBCMTD:getqloc.obj + 0002:00001e6c ??_C@_05NMPB@china?$AA@ 20070e6c LIBCMTD:getqloc.obj + 0002:00001e74 ??_C@_03MAM@che?$AA@ 20070e74 LIBCMTD:getqloc.obj + 0002:00001e78 ??_C@_06CJAO@canada?$AA@ 20070e78 LIBCMTD:getqloc.obj + 0002:00001e80 ??_C@_03EPD@can?$AA@ 20070e80 LIBCMTD:getqloc.obj + 0002:00001e84 ??_C@_07GN@britain?$AA@ 20070e84 LIBCMTD:getqloc.obj + 0002:00001e8c ??_C@_06GEHJ@brazil?$AA@ 20070e8c LIBCMTD:getqloc.obj + 0002:00001e94 ??_C@_03GOO@bra?$AA@ 20070e94 LIBCMTD:getqloc.obj + 0002:00001e98 ??_C@_07ECDD@belgium?$AA@ 20070e98 LIBCMTD:getqloc.obj + 0002:00001ea0 ??_C@_03FJGB@bel?$AA@ 20070ea0 LIBCMTD:getqloc.obj + 0002:00001ea4 ??_C@_03DIDL@aut?$AA@ 20070ea4 LIBCMTD:getqloc.obj + 0002:00001ea8 ??_C@_07OIK@austria?$AA@ 20070ea8 LIBCMTD:getqloc.obj + 0002:00001eb0 ??_C@_09MKKB@australia?$AA@ 20070eb0 LIBCMTD:getqloc.obj + 0002:00001ebc ??_C@_03GNNO@aus?$AA@ 20070ebc LIBCMTD:getqloc.obj + 0002:00001ec0 ??_C@_07DLAK@america?$AA@ 20070ec0 LIBCMTD:getqloc.obj + 0002:00001ec8 ??_C@_03IPEP@usa?$AA@ 20070ec8 LIBCMTD:getqloc.obj + 0002:00001ecc ??_C@_02PILH@us?$AA@ 20070ecc LIBCMTD:getqloc.obj + 0002:00001ed0 ??_C@_02FHP@uk?$AA@ 20070ed0 LIBCMTD:getqloc.obj + 0002:00001ed4 ??_C@_07BNFK@turkish?$AA@ 20070ed4 LIBCMTD:getqloc.obj + 0002:00001edc ??_C@_03MKGF@trk?$AA@ 20070edc LIBCMTD:getqloc.obj + 0002:00001ee0 ??_C@_05DEAI@swiss?$AA@ 20070ee0 LIBCMTD:getqloc.obj + 0002:00001ee8 ??_C@_07ENHD@swedish?$AA@ 20070ee8 LIBCMTD:getqloc.obj + 0002:00001ef0 ??_C@_03IFGE@sve?$AA@ 20070ef0 LIBCMTD:getqloc.obj + 0002:00001ef4 ??_C@_0P@NJBN@spanish?9modern?$AA@ 20070ef4 LIBCMTD:getqloc.obj + 0002:00001f04 ??_C@_0BA@NHDN@spanish?9mexican?$AA@ 20070f04 LIBCMTD:getqloc.obj + 0002:00001f14 ??_C@_07EFLC@spanish?$AA@ 20070f14 LIBCMTD:getqloc.obj + 0002:00001f1c ??_C@_06KFFJ@slovak?$AA@ 20070f1c LIBCMTD:getqloc.obj + 0002:00001f24 ??_C@_03EBAN@sky?$AA@ 20070f24 LIBCMTD:getqloc.obj + 0002:00001f28 ??_C@_07JNDN@russian?$AA@ 20070f28 LIBCMTD:getqloc.obj + 0002:00001f30 ??_C@_03LKIP@rus?$AA@ 20070f30 LIBCMTD:getqloc.obj + 0002:00001f34 ??_C@_03LCE@ptg?$AA@ 20070f34 LIBCMTD:getqloc.obj + 0002:00001f38 ??_C@_03LOP@ptb?$AA@ 20070f38 LIBCMTD:getqloc.obj + 0002:00001f3c ??_C@_0BF@KMMM@portuguese?9brazilian?$AA@ 20070f3c LIBCMTD:getqloc.obj + 0002:00001f54 ??_C@_0L@HNPF@portuguese?$AA@ 20070f54 LIBCMTD:getqloc.obj + 0002:00001f60 ??_C@_06CDNL@polish?$AA@ 20070f60 LIBCMTD:getqloc.obj + 0002:00001f68 ??_C@_03CKDN@plk?$AA@ 20070f68 LIBCMTD:getqloc.obj + 0002:00001f6c ??_C@_0BC@BAAE@norwegian?9nynorsk?$AA@ 20070f6c LIBCMTD:getqloc.obj + 0002:00001f80 ??_C@_0BB@PBHK@norwegian?9bokmal?$AA@ 20070f80 LIBCMTD:getqloc.obj + 0002:00001f94 ??_C@_09EHKL@norwegian?$AA@ 20070f94 LIBCMTD:getqloc.obj + 0002:00001fa0 ??_C@_03BLDF@nor?$AA@ 20070fa0 LIBCMTD:getqloc.obj + 0002:00001fa4 ??_C@_03EMKB@non?$AA@ 20070fa4 LIBCMTD:getqloc.obj + 0002:00001fa8 ??_C@_03HGMI@nld?$AA@ 20070fa8 LIBCMTD:getqloc.obj + 0002:00001fac ??_C@_03IJLK@nlb?$AA@ 20070fac LIBCMTD:getqloc.obj + 0002:00001fb0 ??_C@_06KKAE@korean?$AA@ 20070fb0 LIBCMTD:getqloc.obj + 0002:00001fb8 ??_C@_03BHJ@kor?$AA@ 20070fb8 LIBCMTD:getqloc.obj + 0002:00001fbc ??_C@_03LKFD@jpn?$AA@ 20070fbc LIBCMTD:getqloc.obj + 0002:00001fc0 ??_C@_08KCAB@japanese?$AA@ 20070fc0 LIBCMTD:getqloc.obj + 0002:00001fcc ??_C@_03OLMB@its?$AA@ 20070fcc LIBCMTD:getqloc.obj + 0002:00001fd0 ??_C@_0O@BEAN@italian?9swiss?$AA@ 20070fd0 LIBCMTD:getqloc.obj + 0002:00001fe0 ??_C@_07CBME@italian?$AA@ 20070fe0 LIBCMTD:getqloc.obj + 0002:00001fe8 ??_C@_03BHJP@ita?$AA@ 20070fe8 LIBCMTD:getqloc.obj + 0002:00001fec ??_C@_03CCLJ@isl?$AA@ 20070fec LIBCMTD:getqloc.obj + 0002:00001ff0 ??_C@_0O@OIFK@irish?9english?$AA@ 20070ff0 LIBCMTD:getqloc.obj + 0002:00002000 ??_C@_09OEEF@icelandic?$AA@ 20071000 LIBCMTD:getqloc.obj + 0002:0000200c ??_C@_09IBNG@hungarian?$AA@ 2007100c LIBCMTD:getqloc.obj + 0002:00002018 ??_C@_03FCHO@hun?$AA@ 20071018 LIBCMTD:getqloc.obj + 0002:0000201c ??_C@_05DOKD@greek?$AA@ 2007101c LIBCMTD:getqloc.obj + 0002:00002024 ??_C@_0N@NBPL@german?9swiss?$AA@ 20071024 LIBCMTD:getqloc.obj + 0002:00002034 ??_C@_0BA@MKDC@german?9austrian?$AA@ 20071034 LIBCMTD:getqloc.obj + 0002:00002044 ??_C@_06IAFA@german?$AA@ 20071044 LIBCMTD:getqloc.obj + 0002:0000204c ??_C@_03LCOK@frs?$AA@ 2007104c LIBCMTD:getqloc.obj + 0002:00002050 ??_C@_0N@ODNB@french?9swiss?$AA@ 20071050 LIBCMTD:getqloc.obj + 0002:00002060 ??_C@_0BA@NABK@french?9canadian?$AA@ 20071060 LIBCMTD:getqloc.obj + 0002:00002070 ??_C@_0P@CJNO@french?9belgian?$AA@ 20071070 LIBCMTD:getqloc.obj + 0002:00002080 ??_C@_06PMNM@french?$AA@ 20071080 LIBCMTD:getqloc.obj + 0002:00002088 ??_C@_03BLJK@frc?$AA@ 20071088 LIBCMTD:getqloc.obj + 0002:0000208c ??_C@_03LBAN@frb?$AA@ 2007108c LIBCMTD:getqloc.obj + 0002:00002090 ??_C@_03EOLE@fra?$AA@ 20071090 LIBCMTD:getqloc.obj + 0002:00002094 ??_C@_07DLFI@finnish?$AA@ 20071094 LIBCMTD:getqloc.obj + 0002:0000209c ??_C@_03KLOL@fin?$AA@ 2007109c LIBCMTD:getqloc.obj + 0002:000020a0 ??_C@_03KNMC@esp?$AA@ 200710a0 LIBCMTD:getqloc.obj + 0002:000020a4 ??_C@_03KPHI@esn?$AA@ 200710a4 LIBCMTD:getqloc.obj + 0002:000020a8 ??_C@_03FAMB@esm?$AA@ 200710a8 LIBCMTD:getqloc.obj + 0002:000020ac ??_C@_03DPKJ@enz?$AA@ 200710ac LIBCMTD:getqloc.obj + 0002:000020b0 ??_C@_03DOPE@enu?$AA@ 200710b0 LIBCMTD:getqloc.obj + 0002:000020b4 ??_C@_03GJGA@eni?$AA@ 200710b4 LIBCMTD:getqloc.obj + 0002:000020b8 ??_C@_0M@LJOL@english?9usa?$AA@ 200710b8 LIBCMTD:getqloc.obj + 0002:000020c4 ??_C@_0L@FMNC@english?9us?$AA@ 200710c4 LIBCMTD:getqloc.obj + 0002:000020d0 ??_C@_0L@KBBK@english?9uk?$AA@ 200710d0 LIBCMTD:getqloc.obj + 0002:000020dc ??_C@_0L@EGPP@english?9nz?$AA@ 200710dc LIBCMTD:getqloc.obj + 0002:000020e8 ??_C@_0M@PMJI@english?9ire?$AA@ 200710e8 LIBCMTD:getqloc.obj + 0002:000020f4 ??_C@_0M@DCFH@english?9can?$AA@ 200710f4 LIBCMTD:getqloc.obj + 0002:00002100 ??_C@_0M@FLHK@english?9aus?$AA@ 20071100 LIBCMTD:getqloc.obj + 0002:0000210c ??_C@_0BB@HNCK@english?9american?$AA@ 2007110c LIBCMTD:getqloc.obj + 0002:00002120 ??_C@_07ELCN@english?$AA@ 20071120 LIBCMTD:getqloc.obj + 0002:00002128 ??_C@_03MCKK@eng?$AA@ 20071128 LIBCMTD:getqloc.obj + 0002:0000212c ??_C@_03GIPG@enc?$AA@ 2007112c LIBCMTD:getqloc.obj + 0002:00002130 ??_C@_03DNNI@ena?$AA@ 20071130 LIBCMTD:getqloc.obj + 0002:00002134 ??_C@_03EEPO@ell?$AA@ 20071134 LIBCMTD:getqloc.obj + 0002:00002138 ??_C@_0O@PAMI@dutch?9belgian?$AA@ 20071138 LIBCMTD:getqloc.obj + 0002:00002148 ??_C@_05NPEI@dutch?$AA@ 20071148 LIBCMTD:getqloc.obj + 0002:00002150 ??_C@_03OCEJ@deu?$AA@ 20071150 LIBCMTD:getqloc.obj + 0002:00002154 ??_C@_03BNDL@des?$AA@ 20071154 LIBCMTD:getqloc.obj + 0002:00002158 ??_C@_03OBGF@dea?$AA@ 20071158 LIBCMTD:getqloc.obj + 0002:0000215c ??_C@_06KEAI@danish?$AA@ 2007115c LIBCMTD:getqloc.obj + 0002:00002164 ??_C@_03LKJC@dan?$AA@ 20071164 LIBCMTD:getqloc.obj + 0002:00002168 ??_C@_05BMOA@czech?$AA@ 20071168 LIBCMTD:getqloc.obj + 0002:00002170 ??_C@_03LPJK@csy?$AA@ 20071170 LIBCMTD:getqloc.obj + 0002:00002174 ??_C@_03POL@cht?$AA@ 20071174 LIBCMTD:getqloc.obj + 0002:00002178 ??_C@_03FKAO@chs?$AA@ 20071178 LIBCMTD:getqloc.obj + 0002:0000217c ??_C@_0BE@FPEJ@chinese?9traditional?$AA@ 2007117c LIBCMTD:getqloc.obj + 0002:00002190 ??_C@_0BC@CHFL@chinese?9singapore?$AA@ 20071190 LIBCMTD:getqloc.obj + 0002:000021a4 ??_C@_0BD@DCMH@chinese?9simplified?$AA@ 200711a4 LIBCMTD:getqloc.obj + 0002:000021b8 ??_C@_0BB@JMOE@chinese?9hongkong?$AA@ 200711b8 LIBCMTD:getqloc.obj + 0002:000021cc ??_C@_07NGFN@chinese?$AA@ 200711cc LIBCMTD:getqloc.obj + 0002:000021d4 ??_C@_03PCOI@chi?$AA@ 200711d4 LIBCMTD:getqloc.obj + 0002:000021d8 ??_C@_03FIHP@chh?$AA@ 200711d8 LIBCMTD:getqloc.obj + 0002:000021dc ??_C@_08HFLO@canadian?$AA@ 200711dc LIBCMTD:getqloc.obj + 0002:000021e8 ??_C@_07INIJ@belgian?$AA@ 200711e8 LIBCMTD:getqloc.obj + 0002:000021f0 ??_C@_0L@LCCA@australian?$AA@ 200711f0 LIBCMTD:getqloc.obj + 0002:000021fc ??_C@_0BB@BJIO@american?9english?$AA@ 200711fc LIBCMTD:getqloc.obj + 0002:00002210 ??_C@_0BB@DAGO@american?5english?$AA@ 20071210 LIBCMTD:getqloc.obj + 0002:00002224 ??_C@_08HENB@american?$AA@ 20071224 LIBCMTD:getqloc.obj + 0002:00002230 ??_C@_03DGJE@OCP?$AA@ 20071230 LIBCMTD:getqloc.obj + 0002:00002234 ??_C@_03EKFG@ACP?$AA@ 20071234 LIBCMTD:getqloc.obj + 0002:00002238 ??_C@_09EMDO@_getbuf?4c?$AA@ 20071238 LIBCMTD:_getbuf.obj + 0002:00002244 ??_C@_09JNND@_sftbuf?4c?$AA@ 20071244 LIBCMTD:_sftbuf.obj + 0002:00002250 ??_C@_0BH@EOPG@flag?5?$DN?$DN?50?5?$HM?$HM?5flag?5?$DN?$DN?51?$AA@ 20071250 LIBCMTD:_sftbuf.obj + 0002:00002268 ??_C@_0L@JIE@_freebuf?4c?$AA@ 20071268 LIBCMTD:_freebuf.obj + 0002:00002274 ??_C@_06PAPI@1?$CDQNAN?$AA@ 20071274 LIBCMTD:x10fout.obj + 0002:0000227c ??_C@_05BGNL@1?$CDINF?$AA@ 2007127c LIBCMTD:x10fout.obj + 0002:00002284 ??_C@_05EDPF@1?$CDIND?$AA@ 20071284 LIBCMTD:x10fout.obj + 0002:0000228c ??_C@_06LKFM@1?$CDSNAN?$AA@ 2007128c LIBCMTD:x10fout.obj + 0002:00002294 ??_C@_07CJME@H?3mm?3ss?$AA@ 20071294 LIBCMTD:strftime.obj + 0002:0000229c ??_C@_0BE@MHI@dddd?0?5MMMM?5dd?0?5yyyy?$AA@ 2007129c LIBCMTD:strftime.obj + 0002:000022b0 ??_C@_06HCAD@M?1d?1yy?$AA@ 200712b0 LIBCMTD:strftime.obj + 0002:000022b8 ??_C@_02DBLP@PM?$AA@ 200712b8 LIBCMTD:strftime.obj + 0002:000022bc ??_C@_02ENLM@AM?$AA@ 200712bc LIBCMTD:strftime.obj + 0002:000022c0 ??_C@_08LIDF@December?$AA@ 200712c0 LIBCMTD:strftime.obj + 0002:000022cc ??_C@_08NJLI@November?$AA@ 200712cc LIBCMTD:strftime.obj + 0002:000022d8 ??_C@_07IAMM@October?$AA@ 200712d8 LIBCMTD:strftime.obj + 0002:000022e0 ??_C@_09MKGD@September?$AA@ 200712e0 LIBCMTD:strftime.obj + 0002:000022ec ??_C@_06PADP@August?$AA@ 200712ec LIBCMTD:strftime.obj + 0002:000022f4 ??_C@_04PIJO@July?$AA@ 200712f4 LIBCMTD:strftime.obj + 0002:000022fc ??_C@_04ICFP@June?$AA@ 200712fc LIBCMTD:strftime.obj + 0002:00002304 ??_C@_05JFGC@April?$AA@ 20071304 LIBCMTD:strftime.obj + 0002:0000230c ??_C@_05FGPD@March?$AA@ 2007130c LIBCMTD:strftime.obj + 0002:00002314 ??_C@_08PGBA@February?$AA@ 20071314 LIBCMTD:strftime.obj + 0002:00002320 ??_C@_07BPKJ@January?$AA@ 20071320 LIBCMTD:strftime.obj + 0002:00002328 ??_C@_03PGJO@Dec?$AA@ 20071328 LIBCMTD:strftime.obj + 0002:0000232c ??_C@_03PDLM@Nov?$AA@ 2007132c LIBCMTD:strftime.obj + 0002:00002330 ??_C@_03BLHK@Oct?$AA@ 20071330 LIBCMTD:strftime.obj + 0002:00002334 ??_C@_03DPFM@Sep?$AA@ 20071334 LIBCMTD:strftime.obj + 0002:00002338 ??_C@_03CMCH@Aug?$AA@ 20071338 LIBCMTD:strftime.obj + 0002:0000233c ??_C@_03OBKI@Jul?$AA@ 2007133c LIBCMTD:strftime.obj + 0002:00002340 ??_C@_03LEIG@Jun?$AA@ 20071340 LIBCMTD:strftime.obj + 0002:00002344 ??_C@_03MGHB@May?$AA@ 20071344 LIBCMTD:strftime.obj + 0002:00002348 ??_C@_03MJJM@Apr?$AA@ 20071348 LIBCMTD:strftime.obj + 0002:0000234c ??_C@_03GNHA@Mar?$AA@ 2007134c LIBCMTD:strftime.obj + 0002:00002350 ??_C@_03PICE@Feb?$AA@ 20071350 LIBCMTD:strftime.obj + 0002:00002354 ??_C@_03IEIF@Jan?$AA@ 20071354 LIBCMTD:strftime.obj + 0002:00002358 ??_C@_08FAKH@Saturday?$AA@ 20071358 LIBCMTD:strftime.obj + 0002:00002364 ??_C@_06ONCK@Friday?$AA@ 20071364 LIBCMTD:strftime.obj + 0002:0000236c ??_C@_08CCFO@Thursday?$AA@ 2007136c LIBCMTD:strftime.obj + 0002:00002378 ??_C@_09PBIN@Wednesday?$AA@ 20071378 LIBCMTD:strftime.obj + 0002:00002384 ??_C@_07BMBC@Tuesday?$AA@ 20071384 LIBCMTD:strftime.obj + 0002:0000238c ??_C@_06CHLK@Monday?$AA@ 2007138c LIBCMTD:strftime.obj + 0002:00002394 ??_C@_06OOEM@Sunday?$AA@ 20071394 LIBCMTD:strftime.obj + 0002:0000239c ??_C@_03MPKK@Sat?$AA@ 2007139c LIBCMTD:strftime.obj + 0002:000023a0 ??_C@_03FINJ@Fri?$AA@ 200713a0 LIBCMTD:strftime.obj + 0002:000023a4 ??_C@_03HIKC@Thu?$AA@ 200713a4 LIBCMTD:strftime.obj + 0002:000023a8 ??_C@_03HECK@Wed?$AA@ 200713a8 LIBCMTD:strftime.obj + 0002:000023ac ??_C@_03ECCP@Tue?$AA@ 200713ac LIBCMTD:strftime.obj + 0002:000023b0 ??_C@_03PIEP@Mon?$AA@ 200713b0 LIBCMTD:strftime.obj + 0002:000023b4 ??_C@_03FHEP@Sun?$AA@ 200713b4 LIBCMTD:strftime.obj + 0002:000023b8 ??_C@_03MKGK@a?1p?$AA@ 200713b8 LIBCMTD:strftime.obj + 0002:000023bc ??_C@_05BDJK@am?1pm?$AA@ 200713bc LIBCMTD:strftime.obj + 0002:000023c4 ??_C@_0L@HKCB@inithelp?4c?$AA@ 200713c4 LIBCMTD:inithelp.obj + 0002:000023d0 ??_C@_08OKNB@aw_loc?4c?$AA@ 200713d0 LIBCMTD:aw_loc.obj + 0002:000023dc ??_C@_09LNLM@osfinfo?4c?$AA@ 200713dc LIBCMTD:osfinfo.obj + 0002:000023e8 ??_C@_08PHBD@fclose?4c?$AA@ 200713e8 LIBCMTD:fclose.obj + 0002:000023f8 ___dnames 200713f8 LIBCMTD:timeset.obj + 0002:00002410 ___mnames 20071410 LIBCMTD:timeset.obj + 0002:00002438 ??_C@_07DEKI@tzset?4c?$AA@ 20071438 LIBCMTD:tzset.obj + 0002:00002440 ??_C@_02JHIA@TZ?$AA@ 20071440 LIBCMTD:tzset.obj + 0002:00002444 ??_C@_0L@LIHO@wcstombs?4c?$AA@ 20071444 LIBCMTD:wcstombs.obj + 0002:00002450 ??_C@_0N@ILLM@pwcs?5?$CB?$DN?5NULL?$AA@ 20071450 LIBCMTD:wcstombs.obj + 0002:00002460 ??_C@_0L@EJNO@wtombenv?4c?$AA@ 20071460 LIBCMTD:wtombenv.obj + 0002:0000246c ??_C@_08BGPE@aw_cmp?4c?$AA@ 2007146c LIBCMTD:aw_cmp.obj + 0002:00002478 ??_C@_0DN@PPKB@cchCount1?$DN?$DN0?5?$CG?$CG?5cchCount2?$DN?$DN1?5?$HM?$HM?5@ 20071478 LIBCMTD:aw_cmp.obj + 0002:000024b8 ??_C@_08FEIK@setenv?4c?$AA@ 200714b8 LIBCMTD:setenv.obj + 0003:00000000 ___xc_a 20072000 LIBCMTD:crt0init.obj + 0003:00000004 ___xc_z 20072004 LIBCMTD:crt0init.obj + 0003:00000008 ___xi_a 20072008 LIBCMTD:crt0init.obj + 0003:00000010 ___xi_z 20072010 LIBCMTD:crt0init.obj + 0003:00000014 ___xp_a 20072014 LIBCMTD:crt0init.obj + 0003:0000001c ___xp_z 2007201c LIBCMTD:crt0init.obj + 0003:00000020 ___xt_a 20072020 LIBCMTD:crt0init.obj + 0003:00000024 ___xt_z 20072024 LIBCMTD:crt0init.obj + 0003:00003240 _voiceCommands 20075240 ai_vcmd.obj + 0003:000033c8 _bg_itemlist 200753c8 bg_misc.obj + 0003:00003e58 _bg_numItems 20075e58 bg_misc.obj + 0003:00003e60 _eventnames 20075e60 bg_misc.obj + 0003:00005f04 _pm_stopspeed 20077f04 bg_pmove.obj + 0003:00005f08 _pm_duckScale 20077f08 bg_pmove.obj + 0003:00005f0c _pm_swimScale 20077f0c bg_pmove.obj + 0003:00005f10 _pm_wadeScale 20077f10 bg_pmove.obj + 0003:00005f14 _pm_accelerate 20077f14 bg_pmove.obj + 0003:00005f18 _pm_airaccelerate 20077f18 bg_pmove.obj + 0003:00005f1c _pm_wateraccelerate 20077f1c bg_pmove.obj + 0003:00005f20 _pm_flyaccelerate 20077f20 bg_pmove.obj + 0003:00005f24 _pm_friction 20077f24 bg_pmove.obj + 0003:00005f28 _pm_waterfriction 20077f28 bg_pmove.obj + 0003:00005f2c _pm_flightfriction 20077f2c bg_pmove.obj + 0003:00005f30 _pm_spectatorfriction 20077f30 bg_pmove.obj + 0003:00007f18 _modNames 20079f18 g_combat.obj + 0003:0000a188 _fields 2007c188 g_spawn.obj + 0003:0000a2d8 _spawns 2007c2d8 g_spawn.obj + 0003:0000bc90 _axisDefault 2007dc90 q_math.obj + 0003:0000bcb8 _colorBlack 2007dcb8 q_math.obj + 0003:0000bcc8 _colorRed 2007dcc8 q_math.obj + 0003:0000bcd8 _colorGreen 2007dcd8 q_math.obj + 0003:0000bce8 _colorBlue 2007dce8 q_math.obj + 0003:0000bcf8 _colorYellow 2007dcf8 q_math.obj + 0003:0000bd08 _colorMagenta 2007dd08 q_math.obj + 0003:0000bd18 _colorCyan 2007dd18 q_math.obj + 0003:0000bd28 _colorWhite 2007dd28 q_math.obj + 0003:0000bd38 _colorLtGrey 2007dd38 q_math.obj + 0003:0000bd48 _colorMdGrey 2007dd48 q_math.obj + 0003:0000bd58 _colorDkGrey 2007dd58 q_math.obj + 0003:0000bd68 _g_color_table 2007dd68 q_math.obj + 0003:0000bde8 _bytedirs 2007dde8 q_math.obj + 0003:0000c8f0 __fltused 2007e8f0 LIBCMTD:fpinit.obj + 0003:0000c8f4 __ldused 2007e8f4 LIBCMTD:fpinit.obj + 0003:0000c8f8 __FPinit 2007e8f8 LIBCMTD:fpinit.obj + 0003:0000c8fc __FPmtinit 2007e8fc LIBCMTD:fpinit.obj + 0003:0000c900 __FPmtterm 2007e900 LIBCMTD:fpinit.obj + 0003:0000c984 __aexit_rtn 2007e984 LIBCMTD:dllcrt0.obj + 0003:0000c988 __pctype 2007e988 LIBCMTD:ctype.obj + 0003:0000c98c __pwctype 2007e98c LIBCMTD:ctype.obj + 0003:0000c990 __ctype 2007e990 LIBCMTD:ctype.obj + 0003:0000cb94 ___mb_cur_max 2007eb94 LIBCMTD:nlsdata1.obj + 0003:0000cb98 ___decimal_point 2007eb98 LIBCMTD:nlsdata1.obj + 0003:0000cb9c ___decimal_point_length 2007eb9c LIBCMTD:nlsdata1.obj + 0003:0000cba0 ___tlsindex 2007eba0 LIBCMTD:tidtable.obj + 0003:0000cba8 __cfltcvt_tab 2007eba8 LIBCMTD:cmiscdat.obj + 0003:0000cbd0 __crtAssertBusy 2007ebd0 LIBCMTD:dbgrpt.obj + 0003:0000cbd8 __CrtDbgMode 2007ebd8 LIBCMTD:dbgrpt.obj + 0003:0000cbe8 __CrtDbgFile 2007ebe8 LIBCMTD:dbgrpt.obj + 0003:0000cc10 __indefinite 2007ec10 LIBCMTD:87disp.obj + 0003:0000cc1a __piby2 2007ec1a LIBCMTD:87disp.obj + 0003:0000cc40 __locktable 2007ec40 LIBCMTD:mlock.obj + 0003:0000cf38 __d_inf 2007ef38 LIBCMTD:util.obj + 0003:0000cf40 __d_ind 2007ef40 LIBCMTD:util.obj + 0003:0000cf48 __d_max 2007ef48 LIBCMTD:util.obj + 0003:0000cf50 __d_min 2007ef50 LIBCMTD:util.obj + 0003:0000cf58 __d_mzero 2007ef58 LIBCMTD:util.obj + 0003:0000cf7c ___nullstring 2007ef7c LIBCMTD:output.obj + 0003:0000cf80 ___wnullstring 2007ef80 LIBCMTD:output.obj + 0003:0000cf90 __OP_ATAN2jmptab 2007ef90 LIBCMTD:87triga.obj + 0003:0000cfe0 __iob 2007efe0 LIBCMTD:_file.obj + 0003:0000d260 ___badioinfo 2007f260 LIBCMTD:ioinit.obj + 0003:0000d288 __crtDbgFlag 2007f288 LIBCMTD:dbgheap.obj + 0003:0000d290 __crtBreakAlloc 2007f290 LIBCMTD:dbgheap.obj + 0003:0000d3b0 __amblksiz 2007f3b0 LIBCMTD:heapinit.obj + 0003:0000d448 __XcptActTab 2007f448 LIBCMTD:winxfltr.obj + 0003:0000d4c0 __First_FPE_Indx 2007f4c0 LIBCMTD:winxfltr.obj + 0003:0000d4c4 __Num_FPE 2007f4c4 LIBCMTD:winxfltr.obj + 0003:0000d4c8 __XcptActTabSize 2007f4c8 LIBCMTD:winxfltr.obj + 0003:0000d4cc __XcptActTabCount 2007f4cc LIBCMTD:winxfltr.obj + 0003:0000d500 ___rg_lang_rec 2007f500 LIBCMTD:getqloc.obj + 0003:0000d818 ___rg_ctry_rec 2007f818 LIBCMTD:getqloc.obj + 0003:0000dad0 ___rgrgwlang 2007fad0 LIBCMTD:getqloc.obj + 0003:0000de60 __matherr_flag 2007fe60 LIBCMTD:matherr.obj + 0003:0000de70 __pfnAllocHook 2007fe70 LIBCMTD:dbghook.obj + 0003:0000de78 ___small_block_heap 2007fe78 LIBCMTD:sbheap.obj + 0003:0000fe9c ___sbh_threshold 20081e9c LIBCMTD:sbheap.obj + 0003:0000fea0 ___lc_time_c 20081ea0 LIBCMTD:strftime.obj + 0003:0000ff4c ___lc_time_curr 20081f4c LIBCMTD:strftime.obj + 0003:0000ff50 ___lconv_static_decimal 20081f50 LIBCMTD:lconv.obj + 0003:0000ff58 ___lconv_c 20081f58 LIBCMTD:lconv.obj + 0003:0000ff88 ___lconv 20081f88 LIBCMTD:lconv.obj + 0003:0000ff90 __pow10pos 20081f90 LIBCMTD:constpow.obj + 0003:000100f0 __pow10neg 200820f0 LIBCMTD:constpow.obj + 0003:00010250 __timezone 20082250 LIBCMTD:timeset.obj + 0003:00010254 __daylight 20082254 LIBCMTD:timeset.obj + 0003:00010258 __dstbias 20082258 LIBCMTD:timeset.obj + 0003:000102e0 __tzname 200822e0 LIBCMTD:timeset.obj + 0003:00010308 __lpdays 20082308 LIBCMTD:days.obj + 0003:00010340 __days 20082340 LIBCMTD:days.obj + 0003:00010678 _c_pmove 20082678 bg_pmove.obj + 0003:00055de8 _remapCount 200c7de8 g_utils.obj + 0003:00055e80 _vec3_origin 200c7e80 q_math.obj + 0003:0006c0ac ___fastflag 200de0ac LIBCMTD:fpinit.obj + 0003:0006c0b0 __adjust_fdiv 200de0b0 LIBCMTD:fpinit.obj + 0003:0006c0b8 __aenvptr 200de0b8 LIBCMTD:dllcrt0.obj + 0003:0006c0bc __wenvptr 200de0bc LIBCMTD:dllcrt0.obj + 0003:0006c0c0 ___error_mode 200de0c0 LIBCMTD:dllcrt0.obj + 0003:0006c0c4 ___app_type 200de0c4 LIBCMTD:dllcrt0.obj + 0003:0006c140 ___lc_handle 200de140 LIBCMTD:nlsdata2.obj + 0003:0006c158 ___lc_codepage 200de158 LIBCMTD:nlsdata2.obj + 0003:0006c164 __umaskval 200de164 LIBCMTD:crt0dat.obj + 0003:0006c168 __osver 200de168 LIBCMTD:crt0dat.obj + 0003:0006c16c __winver 200de16c LIBCMTD:crt0dat.obj + 0003:0006c170 __winmajor 200de170 LIBCMTD:crt0dat.obj + 0003:0006c174 __winminor 200de174 LIBCMTD:crt0dat.obj + 0003:0006c178 ___argc 200de178 LIBCMTD:crt0dat.obj + 0003:0006c17c ___argv 200de17c LIBCMTD:crt0dat.obj + 0003:0006c180 ___wargv 200de180 LIBCMTD:crt0dat.obj + 0003:0006c184 __environ 200de184 LIBCMTD:crt0dat.obj + 0003:0006c188 ___initenv 200de188 LIBCMTD:crt0dat.obj + 0003:0006c18c __wenviron 200de18c LIBCMTD:crt0dat.obj + 0003:0006c190 ___winitenv 200de190 LIBCMTD:crt0dat.obj + 0003:0006c194 __pgmptr 200de194 LIBCMTD:crt0dat.obj + 0003:0006c198 __wpgmptr 200de198 LIBCMTD:crt0dat.obj + 0003:0006c19c __exitflag 200de19c LIBCMTD:crt0dat.obj + 0003:0006c1a0 __C_Termination_Done 200de1a0 LIBCMTD:crt0dat.obj + 0003:0006c1a4 __C_Exit_Done 200de1a4 LIBCMTD:crt0dat.obj + 0003:0006c1cc __cflush 200de1cc LIBCMTD:_file.obj + 0003:0006c2f0 __mbctype 200de2f0 LIBCMTD:mbctype.obj + 0003:0006c3f4 ___mbcodepage 200de3f4 LIBCMTD:mbctype.obj + 0003:0006c3f8 ___mblcid 200de3f8 LIBCMTD:mbctype.obj + 0003:0006c400 ___mbulinfo 200de400 LIBCMTD:mbctype.obj + 0003:0006c418 __adbgmsg 200de418 LIBCMTD:crt0msg.obj + 0003:0006c424 ___lc_time_intl 200de424 LIBCMTD:inittime.obj + 0003:0006c440 ___lc_id 200de440 LIBCMTD:nlsdata3.obj + 0003:0006c468 __stdbuf 200de468 LIBCMTD:_sftbuf.obj + 0003:0006c470 __newmode 200de470 LIBCMTD:_newmode.obj + 0003:0006c474 ?_pnhHeap@@3P6AHI@ZA 200de474 LIBCMTD:handler.obj + 0003:0006c47c __alternate_form 200de47c LIBCMTD:strftime.obj + 0003:0006c480 __no_lead_zeros 200de480 LIBCMTD:strftime.obj + 0003:0006c490 ___lconv_static_null 200de490 LIBCMTD:lconv.obj + 0003:0006c580 _remappedShaders 200de580 + 0003:00070780 _neutralObelisk 200e2780 + 0003:000707a0 _teamgame 200e27a0 + 0003:000707c4 _pushed_p 200e27c4 + 0003:000707e0 _pushed 200e27e0 + 0003:000787e0 _g_hub_timelimit 200ea7e0 + 0003:00078900 _g_synchronousClients 200ea900 + 0003:00078a20 _g_maxGameClients 200eaa20 + 0003:00078b40 _g_fraglimit 200eab40 + 0003:00078c60 _g_gametype 200eac60 + 0003:00078d80 _g_pkatourneyrules 200ead80 + 0003:00078ea0 _g_password 200eaea0 + 0003:00078fc0 _hub_flag 200eafc0 + 0003:000790e0 _g_dmflags 200eb0e0 + 0003:00079200 _pmove_msec 200eb200 + 0003:00079320 _Respawn_Positions 200eb320 + 0003:00079460 _g_cheats 200eb460 + 0003:00079580 _g_capturelimit 200eb580 + 0003:000796a0 _g_weaponTeamRespawn 200eb6a0 + 0003:000797c0 _g_needpass 200eb7c0 + 0003:000798e0 _g_pkatourneychat 200eb8e0 + 0003:00079a00 _g_speed 200eba00 + 0003:00079b20 _g_teamForceBalance 200ebb20 + 0003:00079c40 _g_doWarmup 200ebc40 + 0003:00079d60 _g_debugDamage 200ebd60 + 0003:00079e80 _g_podiumDist 200ebe80 + 0003:00079fa0 _g_banIPs 200ebfa0 + 0003:0007a0c0 _pmove_fixed 200ec0c0 + 0003:0007a1e0 _g_rankings 200ec1e0 + 0003:0007a300 _g_knockback 200ec300 + 0003:0007a420 _g_clients 200ec420 + 0003:00086720 _g_maxclients 200f8720 + 0003:00086830 _Respawn_Position_Index 200f8830 + 0003:00086840 _g_podiumDrop 200f8840 + 0003:00086960 _g_timelimit 200f8960 + 0003:00086a80 _g_filterBan 200f8a80 + 0003:00086ba0 _g_smoothClients 200f8ba0 + 0003:00086cc0 _g_debugAlloc 200f8cc0 + 0003:00086de0 _g_inactivity 200f8de0 + 0003:00086f00 _level 200f8f00 + 0003:00089300 _g_HubAltMap1 200fb300 + 0003:00089420 _g_HubAltMap2 200fb420 + 0003:00089540 _g_HubAltMap3 200fb540 + 0003:00089660 _g_HubAltMap4 200fb660 + 0003:00089780 _g_restarted 200fb780 + 0003:000898a0 _g_teamAutoJoin 200fb8a0 + 0003:000899c0 _g_blood 200fb9c0 + 0003:00089ae0 _g_gravity 200fbae0 + 0003:00089c00 _g_listEntity 200fbc00 + 0003:00089d20 _g_allowVote 200fbd20 + 0003:00089e40 _g_entities 200fbe40 + 0003:0015ee40 _g_hub_fraglimit 201d0e40 + 0003:0015ef60 _g_log 201d0f60 + 0003:0015f080 _g_friendlyFire 201d1080 + 0003:0015f1a0 _g_weaponRespawn 201d11a0 + 0003:0015f2c0 _g_debugMove 201d12c0 + 0003:0015f3e0 _g_PrivateBotSkill 201d13e0 + 0003:0015f500 _g_motd 201d1500 + 0003:0015f620 _g_HubAltTitle3 201d1620 + 0003:0015f740 _g_HubAltTitle4 201d1740 + 0003:0015f860 _g_HubAltTitle1 201d1860 + 0003:0015f980 _g_HubAltTitle2 201d1980 + 0003:0015faa0 _g_forcerespawn 201d1aa0 + 0003:0015fbc0 _g_warmup 201d1bc0 + 0003:0015fce0 _g_logSync 201d1ce0 + 0003:0015fe00 _g_quadfactor 201d1e00 + 0003:0015ff20 _g_dedicated 201d1f20 + 0003:00160040 _itemRegistered 201d2040 + 0003:00160440 _bot_minplayers 201d2440 + 0003:00160550 _g_numArenas 201d2550 + 0003:00160554 _podium2 201d2554 + 0003:00160558 _podium3 201d2558 + 0003:0016055c _podium1 201d255c + 0003:00160560 _pml 201d2560 + 0003:001605ec _pm 201d25ec + 0003:001605f0 _Hub_Index 201d25f0 + 0003:00160600 _hubInfo 201d2600 + 0003:00161a3c _active_private_bots 201d3a3c + 0003:00161a40 _ctftaskpreferences 201d3a40 + 0003:00162440 _bot_interbreedcycle 201d4440 + 0003:00162550 _numbots 201d4550 + 0003:00162560 _bot_testsolid 201d4560 + 0003:00162680 _bot_interbreedwrite 201d4680 + 0003:001627a0 _bot_developer 201d47a0 + 0003:001628c0 _bot_testclusters 201d48c0 + 0003:001629d0 _bot_interbreedmatchcount 201d49d0 + 0003:001629e0 _bot_saveroutingcache 201d49e0 + 0003:00162b00 _bot_thinktime 201d4b00 + 0003:00162c20 _botstates 201d4c20 + 0003:00162d20 _regularupdate_time 201d4d20 + 0003:00162d24 _bot_interbreed 201d4d24 + 0003:00162d28 _floattime 201d4d28 + 0003:00162d40 _bot_pause 201d4d40 + 0003:00162e60 _bot_interbreedbots 201d4e60 + 0003:00162f80 _bot_memorydump 201d4f80 + 0003:001630a0 _bot_report 201d50a0 + 0003:001631c0 _bot_interbreedchar 201d51c0 + 0003:001632d0 _lastteleport_origin 201d52d0 + 0003:001632e0 _ctf_blueflag 201d52e0 + 0003:00163320 _g_spSkill 201d5320 + 0003:00163440 _bot_testrchat 201d5440 + 0003:00163550 _gametype 201d5550 + 0003:00163560 _bot_nochat 201d5560 + 0003:00163680 _bot_dragon 201d5680 + 0003:00163790 _max_bspmodelindex 201d5790 + 0003:001637a0 _bot_fastchat 201d57a0 + 0003:001638c0 _bot_rocketjump 201d58c0 + 0003:001639e0 _bot_predictobstacles 201d59e0 + 0003:00163b00 _bot_challenge 201d5b00 + 0003:00163c10 _red_numaltroutegoals 201d5c10 + 0003:00163c20 _ctf_redflag 201d5c20 + 0003:00163c58 _botai_freewaypoints 201d5c58 + 0003:00163c5c _altroutegoals_setup 201d5c5c + 0003:00163c60 _lastteleport_time 201d5c60 + 0003:00163c80 _blue_altroutegoals 201d5c80 + 0003:00163f80 _blue_numaltroutegoals 201d5f80 + 0003:00163fa0 _botai_waypoints 201d5fa0 + 0003:001671a0 _bot_grapple 201d91a0 + 0003:001672c0 _red_altroutegoals 201d92c0 + 0003:001675c0 _maxclients 201d95c0 + 0003:001675c4 _numnodeswitches 201d95c4 + 0003:001675e0 _nodeswitch 201d95e0 + 0003:001692a0 _notleader 201db2a0 + 0003:001693a0 __crtheap 201db3a0 + 0003:001693a4 __pfnDumpClient 201db3a4 + 0003:001693c0 ___pioinfo 201db3c0 + 0003:001694c0 __nhandle 201db4c0 + 0003:001694c4 ___piob 201db4c4 + 0003:001694e0 __bufin 201db4e0 + 0003:0016a4e0 __nstream 201dc4e0 + 0003:0016a4e4 ___onexitend 201dc4e4 + 0003:0016a4e8 ___onexitbegin 201dc4e8 + 0003:0016a4ec ___setlc_active 201dc4ec + 0003:0016a4f0 ___unguarded_readlc_active 201dc4f0 + 0003:0016a4f4 __pfnReportHook 201dc4f4 + 0003:0016a4f8 __acmdln 201dc4f8 + 0003:0016a4fc __pRawDllMain 201dc4fc + 0004:00000000 __IMPORT_DESCRIPTOR_KERNEL32 201dd000 kernel32:KERNEL32.dll + 0004:00000014 __NULL_IMPORT_DESCRIPTOR 201dd014 kernel32:KERNEL32.dll + 0004:0000014c __imp__InterlockedDecrement@4 201dd14c kernel32:KERNEL32.dll + 0004:00000150 __imp__InterlockedIncrement@4 201dd150 kernel32:KERNEL32.dll + 0004:00000154 __imp__GetModuleFileNameA@12 201dd154 kernel32:KERNEL32.dll + 0004:00000158 __imp__GetCommandLineA@0 201dd158 kernel32:KERNEL32.dll + 0004:0000015c __imp__GetProcAddress@8 201dd15c kernel32:KERNEL32.dll + 0004:00000160 __imp__GetModuleHandleA@4 201dd160 kernel32:KERNEL32.dll + 0004:00000164 __imp__GetVersion@0 201dd164 kernel32:KERNEL32.dll + 0004:00000168 __imp__GetCurrentThreadId@0 201dd168 kernel32:KERNEL32.dll + 0004:0000016c __imp__TlsSetValue@8 201dd16c kernel32:KERNEL32.dll + 0004:00000170 __imp__TlsAlloc@0 201dd170 kernel32:KERNEL32.dll + 0004:00000174 __imp__TlsFree@4 201dd174 kernel32:KERNEL32.dll + 0004:00000178 __imp__SetLastError@4 201dd178 kernel32:KERNEL32.dll + 0004:0000017c __imp__TlsGetValue@4 201dd17c kernel32:KERNEL32.dll + 0004:00000180 __imp__GetLastError@0 201dd180 kernel32:KERNEL32.dll + 0004:00000184 __imp__GetCurrentThread@0 201dd184 kernel32:KERNEL32.dll + 0004:00000188 __imp__DebugBreak@0 201dd188 kernel32:KERNEL32.dll + 0004:0000018c __imp__GetStdHandle@4 201dd18c kernel32:KERNEL32.dll + 0004:00000190 __imp__WriteFile@20 201dd190 kernel32:KERNEL32.dll + 0004:00000194 __imp__OutputDebugStringA@4 201dd194 kernel32:KERNEL32.dll + 0004:00000198 __imp__LoadLibraryA@4 201dd198 kernel32:KERNEL32.dll + 0004:0000019c __imp__InitializeCriticalSection@4 201dd19c kernel32:KERNEL32.dll + 0004:000001a0 __imp__DeleteCriticalSection@4 201dd1a0 kernel32:KERNEL32.dll + 0004:000001a4 __imp__EnterCriticalSection@4 201dd1a4 kernel32:KERNEL32.dll + 0004:000001a8 __imp__LeaveCriticalSection@4 201dd1a8 kernel32:KERNEL32.dll + 0004:000001ac __imp__ExitProcess@4 201dd1ac kernel32:KERNEL32.dll + 0004:000001b0 __imp__FatalAppExitA@8 201dd1b0 kernel32:KERNEL32.dll + 0004:000001b4 __imp__Sleep@4 201dd1b4 kernel32:KERNEL32.dll + 0004:000001b8 __imp__MultiByteToWideChar@24 201dd1b8 kernel32:KERNEL32.dll + 0004:000001bc __imp__WideCharToMultiByte@32 201dd1bc kernel32:KERNEL32.dll + 0004:000001c0 __imp__LCMapStringA@24 201dd1c0 kernel32:KERNEL32.dll + 0004:000001c4 __imp__LCMapStringW@24 201dd1c4 kernel32:KERNEL32.dll + 0004:000001c8 __imp__RaiseException@16 201dd1c8 kernel32:KERNEL32.dll + 0004:000001cc __imp__TerminateProcess@8 201dd1cc kernel32:KERNEL32.dll + 0004:000001d0 __imp__GetCurrentProcess@0 201dd1d0 kernel32:KERNEL32.dll + 0004:000001d4 __imp__SetConsoleCtrlHandler@8 201dd1d4 kernel32:KERNEL32.dll + 0004:000001d8 __imp__SetHandleCount@4 201dd1d8 kernel32:KERNEL32.dll + 0004:000001dc __imp__GetFileType@4 201dd1dc kernel32:KERNEL32.dll + 0004:000001e0 __imp__GetStartupInfoA@4 201dd1e0 kernel32:KERNEL32.dll + 0004:000001e4 __imp__IsBadWritePtr@8 201dd1e4 kernel32:KERNEL32.dll + 0004:000001e8 __imp__IsBadReadPtr@8 201dd1e8 kernel32:KERNEL32.dll + 0004:000001ec __imp__HeapValidate@12 201dd1ec kernel32:KERNEL32.dll + 0004:000001f0 __imp__GetCPInfo@8 201dd1f0 kernel32:KERNEL32.dll + 0004:000001f4 __imp__GetACP@0 201dd1f4 kernel32:KERNEL32.dll + 0004:000001f8 __imp__GetOEMCP@0 201dd1f8 kernel32:KERNEL32.dll + 0004:000001fc __imp__FreeEnvironmentStringsA@4 201dd1fc kernel32:KERNEL32.dll + 0004:00000200 __imp__FreeEnvironmentStringsW@4 201dd200 kernel32:KERNEL32.dll + 0004:00000204 __imp__GetEnvironmentStrings@0 201dd204 kernel32:KERNEL32.dll + 0004:00000208 __imp__GetEnvironmentStringsW@0 201dd208 kernel32:KERNEL32.dll + 0004:0000020c __imp__HeapDestroy@4 201dd20c kernel32:KERNEL32.dll + 0004:00000210 __imp__HeapCreate@12 201dd210 kernel32:KERNEL32.dll + 0004:00000214 __imp__VirtualFree@12 201dd214 kernel32:KERNEL32.dll + 0004:00000218 __imp__GetStringTypeA@20 201dd218 kernel32:KERNEL32.dll + 0004:0000021c __imp__GetStringTypeW@16 201dd21c kernel32:KERNEL32.dll + 0004:00000220 __imp__UnhandledExceptionFilter@4 201dd220 kernel32:KERNEL32.dll + 0004:00000224 __imp__IsValidLocale@8 201dd224 kernel32:KERNEL32.dll + 0004:00000228 __imp__IsValidCodePage@4 201dd228 kernel32:KERNEL32.dll + 0004:0000022c __imp__GetUserDefaultLCID@0 201dd22c kernel32:KERNEL32.dll + 0004:00000230 __imp__SetFilePointer@16 201dd230 kernel32:KERNEL32.dll + 0004:00000234 __imp__FlushFileBuffers@4 201dd234 kernel32:KERNEL32.dll + 0004:00000238 __imp__HeapAlloc@12 201dd238 kernel32:KERNEL32.dll + 0004:0000023c __imp__HeapReAlloc@16 201dd23c kernel32:KERNEL32.dll + 0004:00000240 __imp__HeapFree@12 201dd240 kernel32:KERNEL32.dll + 0004:00000244 __imp__VirtualAlloc@16 201dd244 kernel32:KERNEL32.dll + 0004:00000248 __imp__ReadFile@20 201dd248 kernel32:KERNEL32.dll + 0004:0000024c __imp__GetLocaleInfoA@16 201dd24c kernel32:KERNEL32.dll + 0004:00000250 __imp__GetLocaleInfoW@16 201dd250 kernel32:KERNEL32.dll + 0004:00000254 __imp__SetStdHandle@8 201dd254 kernel32:KERNEL32.dll + 0004:00000258 __imp__GetTimeZoneInformation@4 201dd258 kernel32:KERNEL32.dll + 0004:0000025c __imp__CloseHandle@4 201dd25c kernel32:KERNEL32.dll + 0004:00000260 __imp__CompareStringA@24 201dd260 kernel32:KERNEL32.dll + 0004:00000264 __imp__CompareStringW@24 201dd264 kernel32:KERNEL32.dll + 0004:00000268 __imp__SetEnvironmentVariableA@8 201dd268 kernel32:KERNEL32.dll + 0004:0000026c \177KERNEL32_NULL_THUNK_DATA 201dd26c kernel32:KERNEL32.dll + + entry point at 0001:00055ed0 + + Static symbols + + 0001:00052ce1 _SkipWhitespace 20053ce1 f q_shared.obj + 0001:00050ffa _CrossProduct 20051ffa f q_math.obj + 0001:00051cae _VectorLength 20052cae f q_math.obj + 0001:0004e19b _CrossProduct 2004f19b f g_weapon.obj + 0001:0004efd7 _VectorLength 2004ffd7 f g_weapon.obj + 0001:0004fee4 _VectorNormalizeFast 20050ee4 f g_weapon.obj + 0001:0004c9e9 _VectorCompare 2004d9e9 f g_utils.obj + 0001:0004d2e8 _CrossProduct 2004e2e8 f g_utils.obj + 0001:0004a91f _VectorCompare 2004b91f f g_trigger.obj + 0001:00049531 _VectorLength 2004a531 f g_team.obj + 0001:0004a5c9 _SortClients 2004b5c9 f g_team.obj + 0001:000485e8 _target_location_linkup 200495e8 f g_target.obj + 0001:00045a23 _AddIP 20046a23 f g_svcmds.obj + 0001:00045abc _StringToFilter 20046abc f g_svcmds.obj + 0001:00045c0d _UpdateIPBans 20046c0d f g_svcmds.obj + 0001:00041654 _VectorInverse 20042654 f g_mover.obj + 0001:00042bae _VectorLength 20043bae f g_mover.obj + 0001:00042dfe _Touch_DoorTriggerSpectator 20043dfe f g_mover.obj + 0001:00039d75 _VectorLength 2003ad75 f g_missile.obj + 0001:0003974d _CrossProduct 2003a74d f g_misc.obj + 0001:000398f9 _InitShooter_Finish 2003a8f9 f g_misc.obj + 0001:000314da _VectorLength 200324da f g_combat.obj + 0001:0002e5d8 _G_SayTo 2002f5d8 f g_cmds.obj + 0001:0002e7a5 _G_VoiceTo 2002f7a5 f g_cmds.obj + 0001:000302b3 _Cmd_Say_f 200312b3 f g_cmds.obj + 0001:00030305 _Cmd_say_teambyid_f 20031305 f g_cmds.obj + 0001:00030456 _Cmd_Tell_f 20031456 f g_cmds.obj + 0001:00030587 _Cmd_Voice_f 20031587 f g_cmds.obj + 0001:000305dd _Cmd_VoiceTell_f 200315dd f g_cmds.obj + 0001:00030722 _Cmd_VoiceTaunt_f 20031722 f g_cmds.obj + 0001:00029f13 _VectorLength 2002af13 f g_client.obj + 0001:0002a34f _VectorLengthSquared 2002b34f f g_client.obj + 0001:0002b8d5 _ClientCleanName 2002c8d5 f g_client.obj + 0001:00027c68 _PlayerIntroSound 20028c68 f g_bot.obj + 0001:00027f60 _G_AddBot 20028f60 f g_bot.obj + 0001:00028450 _AddBotToSpawnQueue 20029450 f g_bot.obj + 0001:000290c8 _G_LoadArenas 2002a0c8 f g_bot.obj + 0001:00029250 _G_LoadArenasFromFile 2002a250 f g_bot.obj + 0001:00029358 _G_SpawnBots 2002a358 f g_bot.obj + 0001:000294d2 _G_LoadBots 2002a4d2 f g_bot.obj + 0001:00029615 _G_LoadBotsFromFile 2002a615 f g_bot.obj + 0001:00026790 _SpawnModelOnVictoryPad 20027790 f g_arenas.obj + 0001:00026b48 _CelebrateStart 20027b48 f g_arenas.obj + 0001:00026b9e _CelebrateStop 20027b9e f g_arenas.obj + 0001:00026bed _SpawnPodium 20027bed f g_arenas.obj + 0001:00026d75 _PodiumPlacementThink 20027d75 f g_arenas.obj + 0001:000240b4 _CrossProduct 200250b4 f bg_slidemove.obj + 0001:000205aa _PM_WaterJumpMove 200215aa f bg_pmove.obj + 0001:00020621 _PM_WaterMove 20021621 f bg_pmove.obj + 0001:00020851 _VectorLength 20021851 f bg_pmove.obj + 0001:00020890 _PM_Friction 20021890 f bg_pmove.obj + 0001:00020a47 _PM_Accelerate 20021a47 f bg_pmove.obj + 0001:00020b02 _PM_CmdScale 20021b02 f bg_pmove.obj + 0001:00020be7 _PM_CheckWaterJump 20021be7 f bg_pmove.obj + 0001:00020d7c _PM_FlyMove 20021d7c f bg_pmove.obj + 0001:00020e77 _PM_AirMove 20021e77 f bg_pmove.obj + 0001:00020fc6 _PM_SetMovementDir 20021fc6 f bg_pmove.obj + 0001:00021194 _PM_GrappleMove 20022194 f bg_pmove.obj + 0001:000212e3 _PM_WalkMove 200222e3 f bg_pmove.obj + 0001:0002168f _PM_CheckJump 2002268f f bg_pmove.obj + 0001:00021793 _PM_ForceLegsAnim 20022793 f bg_pmove.obj + 0001:000217b2 _PM_StartLegsAnim 200227b2 f bg_pmove.obj + 0001:000217f9 _PM_DeadMove 200227f9 f bg_pmove.obj + 0001:000218c8 _PM_NoclipMove 200228c8 f bg_pmove.obj + 0001:00021b38 _PM_GroundTrace 20022b38 f bg_pmove.obj + 0001:00021e44 _PM_CrashLand 20022e44 f bg_pmove.obj + 0001:0002203d _PM_FootstepForSurface 2002303d f bg_pmove.obj + 0001:00022070 _PM_CorrectAllSolid 20023070 f bg_pmove.obj + 0001:0002225a _PM_GroundTraceMissed 2002325a f bg_pmove.obj + 0001:000223a7 _PM_SetWaterLevel 200233a7 f bg_pmove.obj + 0001:0002251e _PM_CheckDuck 2002351e f bg_pmove.obj + 0001:000227f8 _PM_Footsteps 200237f8 f bg_pmove.obj + 0001:00022a72 _PM_ContinueLegsAnim 20023a72 f bg_pmove.obj + 0001:00022aa6 _PM_WaterEvents 20023aa6 f bg_pmove.obj + 0001:00022b31 _PM_TorsoAnimation 20023b31 f bg_pmove.obj + 0001:00022b7e _PM_ContinueTorsoAnim 20023b7e f bg_pmove.obj + 0001:00022bb2 _PM_StartTorsoAnim 20023bb2 f bg_pmove.obj + 0001:00022beb _PM_Weapon 20023beb f bg_pmove.obj + 0001:000233ae _PM_BeginWeaponChange 200243ae f bg_pmove.obj + 0001:000234f6 _PM_FinishWeaponChange 200244f6 f bg_pmove.obj + 0001:00023587 _PM_Animate 20024587 f bg_pmove.obj + 0001:000235cc _PM_DropTimers 200245cc f bg_pmove.obj + 0001:0000e0cd _VectorLength 2000f0cd f ai_dmq3.obj + 0001:000107b8 _CrossProduct 200117b8 f ai_dmq3.obj + 0001:00011084 _VectorLengthSquared 20012084 f ai_dmq3.obj + 0001:00013998 _VectorCompare 20014998 f ai_dmq3.obj + 0001:00009641 _VectorLengthSquared 2000a641 f ai_dmnet.obj + 0001:0000a381 _VectorLength 2000b381 f ai_dmnet.obj + 0001:0000ac43 _VectorCompare 2000bc43 f ai_dmnet.obj + 0001:0000628f _VectorLength 2000728f f ai_cmd.obj + 0001:0006cdb0 _findenv 2006ddb0 f LIBCMTD:setenv.obj + 0001:0006ce30 _copy_environ 2006de30 f LIBCMTD:setenv.obj + 0001:0006c660 _wcsncnt 2006d660 f LIBCMTD:aw_cmp.obj + 0001:0006ca80 _strncnt 2006da80 f LIBCMTD:aw_cmp.obj + 0001:0006c160 _wcsncnt 2006d160 f LIBCMTD:wcstombs.obj + 0001:0006b220 __tzset_lk 2006c220 f LIBCMTD:tzset.obj + 0001:0006b5b0 __isindst_lk 2006c5b0 f LIBCMTD:tzset.obj + 0001:0006b8b0 _cvtdate 2006c8b0 f LIBCMTD:tzset.obj + 0001:0006a6f0 _wcstoxl 2006b6f0 f LIBCMTD:wcstol.obj + 0001:00069500 __expandtime 2006a500 f LIBCMTD:strftime.obj + 0001:00069b30 __store_str 2006ab30 f LIBCMTD:strftime.obj + 0001:00069b80 __store_num 2006ab80 f LIBCMTD:strftime.obj + 0001:00069c30 __store_number 2006ac30 f LIBCMTD:strftime.obj + 0001:00069ce0 __store_winword 2006ace0 f LIBCMTD:strftime.obj + 0001:00065650 _trans_lang_lang 20066650 f LIBCMTD:getqloc.obj + 0001:000656e0 _trans_ctry_ctry 200666e0 f LIBCMTD:getqloc.obj + 0001:00065770 _trans_ctry_lang 20066770 f LIBCMTD:getqloc.obj + 0001:000657f0 _testSpecialCtry 200667f0 f LIBCMTD:getqloc.obj + 0001:00065820 _match_ctry_lang 20066820 f LIBCMTD:getqloc.obj + 0001:00064af0 __get_lc_lconv 20065af0 f LIBCMTD:initmon.obj + 0001:00064d00 _fix_grouping 20065d00 f LIBCMTD:initmon.obj + 0001:00064d80 __free_lc_lconv 20065d80 f LIBCMTD:initmon.obj + 0001:00064940 _fix_grouping 20065940 f LIBCMTD:initnum.obj + 0001:00063c30 __get_lc_time 20064c30 f LIBCMTD:inittime.obj + 0001:000641c0 __free_lc_time 200651c0 f LIBCMTD:inittime.obj + 0001:000644d0 _storeTimeFmt 200654d0 f LIBCMTD:inittime.obj + 0001:00061450 __abstract_cw 20062450 f LIBCMTD:ieee87.obj + 0001:000615d0 __hw_cw 200625d0 f LIBCMTD:ieee87.obj + 0001:00061730 __abstract_sw 20062730 f LIBCMTD:ieee87.obj + 0001:000612e0 _xcptlookup 200622e0 f LIBCMTD:winxfltr.obj + 0001:000603f0 _getSystemCP 200613f0 f LIBCMTD:mbctype.obj + 0001:00060450 _CPtoLCID 20061450 f LIBCMTD:mbctype.obj + 0001:000604d0 _setSBCS 200614d0 f LIBCMTD:mbctype.obj + 0001:0005fca0 _parse_cmdline 20060ca0 f LIBCMTD:stdargv.obj + 0001:0005dff0 _realloc_help 2005eff0 f LIBCMTD:dbgheap.obj + 0001:0005ec30 _CheckBytes 2005fc30 f LIBCMTD:dbgheap.obj + 0001:0005f820 __printMemBlockData 20060820 f LIBCMTD:dbgheap.obj + 0001:0005d260 _flsall 2005e260 f LIBCMTD:fflush.obj + 0001:0005cd40 _xtoa 2005dd40 f LIBCMTD:xtoa.obj + 0001:0005cec0 _x64toa@20 2005dec0 f LIBCMTD:xtoa.obj + 0001:0005c8c0 _ctrlevent_capture@4 2005d8c0 f LIBCMTD:winsig.obj + 0001:0005cbb0 _siglookup 2005dbb0 f LIBCMTD:winsig.obj + 0001:0005c550 _doexit 2005d550 f LIBCMTD:crt0dat.obj + 0001:0005c660 __initterm 2005d660 f LIBCMTD:crt0dat.obj + 0001:0005bfb0 _write_char 2005cfb0 f LIBCMTD:output.obj + 0001:0005c030 _write_multi_char 2005d030 f LIBCMTD:output.obj + 0001:0005c070 _write_string 2005d070 f LIBCMTD:output.obj + 0001:0005c0c0 _get_int_arg 2005d0c0 f LIBCMTD:output.obj + 0001:0005c0e0 _get_int64_arg 2005d0e0 f LIBCMTD:output.obj + 0001:0005c100 _get_short_arg 2005d100 f LIBCMTD:output.obj + 0001:0005ab90 __get_fname 2005bb90 f LIBCMTD:fpexcept.obj + 0001:00059c90 _wcsncnt 2005ac90 f LIBCMTD:aw_map.obj + 0001:00059fb0 _strncnt 2005afb0 f LIBCMTD:aw_map.obj + 0001:00059390 __setlocale_set_cat 2005a390 f LIBCMTD:setlocal.obj + 0001:00059520 __setlocale_get_all 2005a520 f LIBCMTD:setlocal.obj + 0001:00058630 _CrtMessageWindow 20059630 f LIBCMTD:dbgrpt.obj + 0001:00058070 __hextodec 20059070 f LIBCMTD:input.obj + 0001:000580d0 __inc 200590d0 f LIBCMTD:input.obj + 0001:00058130 __un_inc 20059130 f LIBCMTD:input.obj + 0001:00058150 __whiteout 20059150 f LIBCMTD:input.obj + 0001:00056700 __cftoe2 20057700 f LIBCMTD:cvt.obj + 0001:000568f0 __cftof2 200578f0 f LIBCMTD:cvt.obj + 0001:00056b90 __shift 20057b90 f LIBCMTD:cvt.obj + 0001:00055570 _shortsort 20056570 f LIBCMTD:qsort.obj + 0001:000555e0 _swap 200565e0 f LIBCMTD:qsort.obj + 0001:00055300 _$$$00001 20056300 f LIBCMTD:chkstk.obj + 0001:000550d0 _$$$00001 200560d0 f LIBCMTD:strchr.obj + +FIXUPS: 559f5 a5 fffffd4d 12 18 16 10 1c 31 10 fffffeb8 2a 33 1a fffffe67 +FIXUPS: 55669 11 fffff98d 5 a ffffff9e 11 fffffd35 85 69 33 8c 34 1579 +FIXUPS: 5646d 15a fffffc90 12 2a 10 1b 2a 10 fffffeaf 12 2a 10 1b 2a 10 +FIXUPS: 55fc6 2a 45 4a fffffe32 2b 28 16 1d 29 25 ffffff0a fffffaca 1c +FIXUPS: 55988 1238 e 1e 20 16 15 16 14 16 14 19 19 a 1f fffffc70 a fffffec2 +FIXUPS: 56850 28 1e 2c 12 21 20 25 fffffd4f 1c 13 6d a5 fffffbd2 2a ad0 +FIXUPS: 56e9b 15 7e 1e 1e 1d 68 9 fffff9b9 1e d 19 16 14 2c 1b 17 27 16 +FIXUPS: 56ad0 14 c 1d 16 15 16 14 11 14 16 14 564 2a 2e 37 81 2a 21 62 +FIXUPS: 572ec 1a 1a 1a 1a e fffffd45 fffffc9d 31 8 5b 9 17 1c 5 c 5 5 5 +FIXUPS: 56e39 3d 7 f 5 22b1 24 f ffffea6d 36 fffff866 28 19f 20 3a 2f 1a +FIXUPS: 5773c 61 102 27 16 98 35 68 12 40 33 48 3c 36 1c 1a 18 17 fffff8a5 +FIXUPS: 573ae 12bc 2f 4d 23 10 70 24 5a 46 2f c1 24 e5 39 76 4a 3d 76 6c +FIXUPS: 58cc0 20 77 27 70 46 31 35 44 35 1c 157 8a 5a9 1d 21 14 26 19d +FIXUPS: 598e6 19 19 a ffffe384 2d 47 4a 9 19 a4 1aa 1d 118 1de 32 97 1f +FIXUPS: 58427 a8 28 33 43 64 46 41 17a8 13 a 34 a 6a 2c 44 2c fffffcd9 +FIXUPS: 59c98 7 fffffead fffff75d 10b 2a 18 38 16 2b 14 2e 18 18 7a 63 +FIXUPS: 595f2 47 1e d 2f 15 d41 47 1b 40 3e 31 4e 4e 1a 25 2a 9b 19 1b +FIXUPS: 5a6f4 2e 1b 14 19 19 11 67 27 40 2a 45 2e 33 4a 25 2b fffff3d4 +FIXUPS: 5ac4b e 13 e eb 9b c9 a3 e 13 e fffff024 43 8c 48 51 19 2f 2d 39 +FIXUPS: 5a1f6 5a 38 47 17 31 11 1a 22 10 43 26 d06 2c c 3a e 11 2f 44 c +FIXUPS: 5b231 e 11 182 1d8 1ac 2b 33 1b6 f4 27 36 44 c 10 16 c 32 d ffffeed9 +FIXUPS: 5aaf5 7c 78 1e9e 1e 2a 1b 116 25 122 1e 30 75 32 22 26 177 55 53 +FIXUPS: 5c020 108 10 3e 54 4d 28 fffffaab 1c3 17 fffff167 a 14 52 a 14 +FIXUPS: 5d5de 13 19 b 31 10 fffffe10 a a fffffd92 3d 5e 133 38 fffffd96 +FIXUPS: 5d1c7 7 10 fffff198 34 24 cb 67 133 18 21 34 30 a8 e2 142 ba 14b8 +FIXUPS: 5dfb6 fffff711 43 9 82 24 27 2a 17 58 50 44 1d 16 c4 13 28 16 1d +FIXUPS: 5da7c 99 ff 10 fffff889 12 1d 20 1e 20 1b 77 f1b c c 5b 72 fffffdfa +FIXUPS: 5e3f0 1e c 17 13 c fffffcad e c f 22 28 78 6e 21 68 33 40 25 f +FIXUPS: 5dd0e 18 11b 21 4b 4a 2696 a1 20 31 28 4f 3a 31 18 45 18 f 1a 88 +FIXUPS: 608f0 2a 34 39 16 7c 2a 28 ffffdcdd 13 e8 29a fffffb44 35 18 a6 +FIXUPS: 5e724 fffffd73 1852 8 51 2b 28 28 28 16 94 2b 32 2b 39 20 49 33 +FIXUPS: 6000e 67 47 80 1f 5e 15 4d 5a 11 5c 1b ec 33 49 10e fffff09d 50 +FIXUPS: 5f67d 1f 62 34 3b 2b 3b 45 2b c 41 6b 56 36 c 2b 1c 2c 1c 16 c +FIXUPS: 5fa63 62 51 1f 45 c 5a 1f 93 fffff325 1e d 35 21 1b 1c 6b 28 32 +FIXUPS: 5f183 18 1f 5b 66 25 25 b3 19 59 5f 54 85 15 1e d 18 20 1c 10 a +FIXUPS: 5f5f4 164f 13 22 fffffe50 28 1f 29 26 17 12 1c ffffdf71 26 28 15 +FIXUPS: 5eb8f d 1d 25 2f 1c 69 28 5f 54 21 128 1e 1b 33 2f 51 2d33 14 13 +FIXUPS: 61d10 1a 19 fffffce2 fffffc0a 2e 88 2f 69 1a 16 12a 4a 94 2b fffff6f9 +FIXUPS: 610e5 18 15 7 10a 45 ff 4c 15 7 e 179 fffff6d2 21e9 20 68 82 10 +FIXUPS: 62f5b 10 30 10 fffff3a6 21 21 24 2a 19 c fffffd21 13 fffffd67 5d +FIXUPS: 61f96 e e 103 59 fffffa26 1c 77 6e 57 15 17 230f 3e fffff575 14d +FIXUPS: 637b2 1c8 8c 152 6d 248 13 fffff486 fffffe19 28 29 ffffff8c fffff828 +FIXUPS: 62905 8b 14 270 19 1c 13 2a 3a 10 13 16 26 1e 41 1be6 4b fffffe4d +FIXUPS: 64826 44 4b fffffd89 18 10 f 39 5f fffffd43 82 2a fffffb3b 4d 40 +FIXUPS: 64104 43 4d 40 43 50 50 4d 65 60 fffffa9f 24 13 4a 1475 11 11 11 +FIXUPS: 653a2 11 11 11 11 14 14 14 14 14 14 14 14 14 14 14 36 1c 1c 2c +FIXUPS: 65677 fffff3ea 72 13 24 17 fffffdd5 2a 776 22 22 22 22 22 22 22 +FIXUPS: 6519c 3c 11 11 11 11 11 10 11 11 11 11 11 11 11 11 11 11 11 11 +FIXUPS: 6531a 11 11 11 fffff963 1f 1f 1f 1c 1f 1f 1f 1f 1f 1f 1f 1f 1f +FIXUPS: 64e5f 1f 1f 1f 1f 1f 1f 1f 1f 1f 1f 1f 1f 1f 1f 1f 22 22 d52 11 +FIXUPS: 65de8 11 11 fffff8b9 1d 1d 17 16 11 11 46 13 14 5b 11 10 34 30 +FIXUPS: 658af 3b fffff288 1f 10 e 1e 11 25 11 5e 1f 14a3 e e e 2b 11 fffff845 +FIXUPS: 659fe 10 e 4c 11 4f 11 4d 1f 1f 1f 1f 17 17 1f 1f 1f 1f 1f 1f 1f +FIXUPS: 65cc8 1f bd 11 bb1 21 1f d 21 10 fffff902 41 52 1d 22 8a 92 6a +FIXUPS: 66559 89 2c 29 54 90 6c fffff6d6 1f 19 19 19 106 65 a6 22 17 e +FIXUPS: 671c9 2e 2d a 16 fffffd54 21 13 50 5e fffffe04 37 fffffccd b 14 +FIXUPS: 66bb5 f 6d 1a2 b 10 3e b fffffbfb b 14 14 f 27 11 4c fffffe3d fdc +FIXUPS: 67925 59 14 a c 39 fffffd8f 14 36 14 f fffffe51 48 29 3e f d fffffebe +FIXUPS: 675d2 ffffff0e 5c 41 1f fffffeb1 3a fffffdf9 21 7e 98 33 fffffdc8 +FIXUPS: 68d98 c 27 6c fffffc25 2a 45 4b fffff19b 236 11a 9e a3 102 119 +FIXUPS: 67a8e 8 12 27 b 2a ffffff0d 14 1d a c fffffdb9 17 43 14 37 1b 371c +FIXUPS: 698d2 b 14 14 f 137 b 23 2c5 fffff8a0 70 4f 1e9 fffff70c 43 33 +FIXUPS: 6902c bd 2d 9b 32 43 c 10 c fffff969 21 35 35 13a c 1632 84 8d +FIXUPS: 6a549 23 23 23 3a 51 24 51 2a 28 45 2d 2d 28 28 1c 29 41 84 3a +FIXUPS: 6a95b 31 36 50 a 21 164 31c 20 fffff08e 9 2e 9 6e 15 37 15 1e 14 +FIXUPS: 6a0c8 1c 1c 1b 36 3a 9 2e 9 48 9 2e 9 2e 9 2c 9 2c 9 2c 9 3d 13b8 +FIXUPS: 6b854 27 11 d8 7e fffffaa7 88 13 ea 96 13 fffffab5 33 2c 32 20 +FIXUPS: 6b24b 14 1b 43 6f ffffea8d 15 1b 50 9 2e 9 71 15 1b 21b8 f 4c 27 +FIXUPS: 6c163 f 27 fffff938 82 2f 83 7d 11 b8 b d6 b 65 b 66 2f e b 18 +FIXUPS: 6bf9e 3d 2f fffffa57 24 a 23 fffffc30 b24 8 7 1b 2b 17 af 16 3e +FIXUPS: 6c383 15 18 c 18 17 a 15 3b 5b 52 6c 29 c d b0 53 66 53 25 23 fffff8f1 +FIXUPS: 6c0c6 d52 24 13 62 3e 6b 60 64 54 8c 3b fffffbb9 b 14 c f 1e 1b +FIXUPS: 6cd71 10 29 2d fffffea3 34 fffffe62 1d 14 8e 2c fffff5d4 11 14 +FIXUPS: 6dda2 3c a7 19 28 c 21 fffff542 19 e4 81 63 e 13 e f3 19 fc 140 +FIXUPS: 6d9ff 5c e fffff8f7 43 ffffff4b fffffee9 c d 2e 29 19 2c fff96fd1 +FIXUPS: 426a 1a 17 27 6 18 12 16 19 27 6 18 12 16 69ba8 f 58 28 29 fffffaf6 +FIXUPS: 6db29 29 3a 40 3e 33 4b 59 5a c 19 fff960de 16 1a 17 34 16 19 34 +FIXUPS: 3f32 19 34 16 19 34 16 19 34 16 19 34 16 29 21 1a 38 16 19 38 16 +FIXUPS: 41f0 38 16 fffff826 16 19 34 16 19 34 16 19 34 16 19 34 16 19 31 +FIXUPS: 3c66 16 19 31 16 16 19 31 16 16 19 31 16 16 19 31 fffff9aa 2b 9 +FIXUPS: 380c 6 a 16 12 16 19 2b 9 6 6 a 16 12 16 19 2b 9 6 6 a 16 12 16 +FIXUPS: 39b6 17 34 16 19 fffffa90 15 f 2c 14 6 18 12 c 6 18 12 44 2a 2b +FIXUPS: 3632 14 16 12 16 19 2b 9 14 16 12 16 19 2b 2a 12 16 fffff83c e +FIXUPS: 302c 3c 17 24 f 23 21 27 69 24 e 29 3c 17 24 f 26 24 27 47 84 b +FIXUPS: 33ec 27 2a 15 17 28 d 1a fffff5c6 1b 1a 15 21 2a 1f 2a 1f 2a a +FIXUPS: 2bdb 21 19 58 24 b 29 2b 17 2c 13 17 66 71 b 29 33 32 21 1e c5 +FIXUPS: 2614 1f 21 12 5b 21 12 1f 2a 41 39 22 16 1f 22 16 10 22 16 d 2c +FIXUPS: 28e5 16 16 16 79 24 3f 15 17 23 15 fffff872 12 9 b a 16 12 10 9 +FIXUPS: 2350 6 a 16 12 79 24 27 39 13 8 17 33 1a 15 24 21 12 1f 21 12 1f +FIXUPS: 2602 fffff935 33 14 27 14 9 14 16 12 47 36 15 32 14 24 14 2a 12 +FIXUPS: 2190 36 d 18 35 14 27 15 12 9 6 f 16 12 fffff519 5d 14 26 16 e +FIXUPS: 193d 33 90 1c ce 1a 20 13 1a 25 38 3a a6 63 83 28 57 33 14 27 15 +FIXUPS: 1e8c 9 14 16 12 fffff42d 9 20 3c 76 f 18 9 1d 9 20 41 28 76 f 18 +FIXUPS: 155e 1d 9 20 41 28 87 f 18 9 1d 9 1d 38 22 61 678b 18 18 18 18 +FIXUPS: 7fc5 15 15 15 15 17 11 ffff8fd7 6c f 18 9 1d 9 47 6f f 18 9 1d +FIXUPS: 11e6 20 3c 6f f 18 9 6841 1c f 24 1c f 95 4b c 35 c 13 1d 2c 71 +FIXUPS: 7dae 18 18 18 18 18 18 18 18 18 18 18 18 19 19 18 1d fffff6df 21 +FIXUPS: 764a 16 35 f 33 2f f 33 1b 28 19 d2 1c 1b 3b 6e 18 13 16 23 1a +FIXUPS: 79e7 23 f 1b 16 57 c 31 27 fffff4f2 1a 15 15 15 15 15 15 1a f 21 +FIXUPS: 7124 15 4b 1b 13 4e 40 31 4f 12a 18 13 16 23 48 5a 2d 56 52 20 +FIXUPS: 7605 fffff5e5 14 15 33 1b 26 f 2a 15 20 2c f 17 2a 13 34 1e 18 +FIXUPS: 6e06 43 18 13 16 23 56 1a 23 1a 23 1a 23 1a fffff789 f 2f 21 1f +FIXUPS: 67f2 1b 1f 19 70 49 41 21 1b 15 16 23 1f 1d 8e 18 13 16 23 f 49 +FIXUPS: 6b67 15 13 1a 1a f fffff7e7 14 2b 23 f 32 64 c 15 16 23 1b 24 1a +FIXUPS: 6582 21 15 16 1b 20 1a f 1b 17 f 13 18 20 15 1b 13 20 fffff7d6 +FIXUPS: 5f1a f 32 60 d c c c 26 17 23 f 33 64 c c 44 23 f 32 64 c 25 18 +FIXUPS: 6282 f 27 99 1b 1a 1b fffff581 18 13 16 23 f 29 1b 1f 108 19 c9 +FIXUPS: 5bf1 21 24 3b 42 4d c c 43 18 13 16 23 10 18 32 42 40 c c fffff56a +FIXUPS: 542c 21 2e f 48 35 ad c c 15 16 23 1d 23 f 39 42 4d c c 43 18 13 +FIXUPS: 57cd 23 1a 1d 1c 39 57 c fffff546 f 17 27 2b 1f f 17 27 3e 18 26 +FIXUPS: 5092 5c 18 13 16 23 1a 18 1c 21 33 3f 1c f 21 3b 19 d9 1d 38 fffff40c +FIXUPS: 4820 5b 13 34 4d 21 13 56 21 13 34 64 f 1a 59 25 18 f 31 17 13 +FIXUPS: 4bb9 e3 32 f 20 5c 13 44 18 18 909f c 7f 1f 34 5c 1b 40 ffff63a0 +FIXUPS: 440b 21 21 21 21 21 21 21 21 1e 43 15 40 23 3f 21 1e 44 18 5e 15 +FIXUPS: 475c 5c 9219 34 5b 1b 40 26 24 15 13 15 13 15 24 20 f 15 3e 28 +FIXUPS: dc5d 32 49 1e 59 26 39 19 15 13 13 2a 1b 22 fffff6cb f 15 1d 13 +FIXUPS: d5bf 28 16 c 16 11 39 49 1e 6d 2b 15 15 c 1f 15 55 32 16 32 13 +FIXUPS: d8de 1b 22 c c 7f fffff627 1e 15 15 24 3e 28 25 7c 54 5b 36 11 +FIXUPS: d25f c 24 18 1f 6f 31 4d 17 5b 15 2d 24 15 13 15 13 15 24 fffff6d2 +FIXUPS: cc3c 20 10 20 27 10 20 38 19 15 18 13 37 28 25 c 1d 2a 22 c c 1c +FIXUPS: ceb6 2f 32 15 13 15 13 15 24 fffff7e6 11 22 d 3f 49 6c 12 2c 12 +FIXUPS: c944 15 13 15 13 15 1d 13 1d 20 61 20 10 20 20 18 21 13 1d 15 32 +FIXUPS: cbc8 fffff6e5 13 10 20 3e 28 25 31 3b e 10 15 19 12 11 15 1c 49 +FIXUPS: c501 2c 16 31 17 17 24 18 1f 10 49 3f 48 5d fffff725 35 26 17 17 +FIXUPS: bf0c 18 1f 10 49 3f 45 47 1a 2c 49 18 3c 10 15 14 12 11 28 1c 1c +FIXUPS: c222 26 15 13 15 13 fffff61a 13 87 36 95 43 58 60 5c 1b 42 10 15 +FIXUPS: bc04 12 11 c 6d 1c 1c 1c 26 15 13 15 13 15 3e 27 25 23 23 fffff587 +FIXUPS: b3fa 10 11 13 10 11 13 10 11 3e 27 25 11 6b b4 1e 13 26 19 13 35 +FIXUPS: b731 1a 48 13 35 3c 1a 17 2a 1b fffff40b 24 47 10 21 39 15 28 34 +FIXUPS: adef 2b 12 12 12 12 c 10 37 60 15 11 34 34 50 164 4e 4e 8b 9d 31 +FIXUPS: b376 3c fffff316 12 1e 25 53 12 1e 28 19 22 cc a4 5c 12 1e 47 5d +FIXUPS: aab5 1e 5e 34 21 c c 22 1e 10 10 37 24 c 1e fffff3aa 4c e2 1e 48 +FIXUPS: a227 16 13 19 2d 15 2c 10 76 40 2d 15 22 49 10 16 1a 48 59 16 13 +FIXUPS: a56d 2d 15 28 44 1b fffff3ef 1e 9a 12 1e 1b 14 25 35 1e 5a 54 12 +FIXUPS: 9c92 1b 16 43 58 4f e 74 12 32 26 1e 38 73 42 20 1c 1e 1b fffff441 +FIXUPS: 9505 1a 16 13 25 34 1a 16 5a 23 1a 35 8e 1a 1e 3f 1a 1e 51 71 1a +FIXUPS: 98d4 1b 14 24 49 22 1a 1e 1f 22 fffff2a1 64 35 110 19 47 43 20 +FIXUPS: 8f4f e 61 5e 1d 12 1e 36 a 3b 6b 25 43 58 3c 16 26 19 20 22 b3 +FIXUPS: 93ce 12 1e fffff2db 2e 23 10 39 29 33 10 12 12 16 7f 12 1e 1b 14 +FIXUPS: 8969 31 52 1f 31 22 b2 75 12 1e 1b 14 40 12 1e 26 fb59 14 17 2f +FIXUPS: 1884e 20 fffef895 21 36 11 1f 41 21 a1 3c 20 e9 1a 3b 1a c 20 14 +FIXUPS: 84a3 1a 2f 40 33 2f 30 a2 33 fbbc 15 61 12 c 1b 24 3a a0 7b 3e +FIXUPS: 184ef a 17 33 19 23 bb 47 12 21 19 19 19 19 19 19 19 19 1d 13 14 +FIXUPS: 17d03 18 2b 25 77 36 53 19 15a 1b 1b 16 38 16 20 22 16 1b 20 39 +FIXUPS: 18162 10 c c c c c 10 10 1d 34 10 fffff109 11 24 10 20 e 22 2b +FIXUPS: 17456 1d c 20 1b 43 2b a9 66 aa 77 262 26 11 10 17a 23 11 1f 98 +FIXUPS: 17cae 1c 13 13 ffffef6c 40 ea 68 2d 8a 10d 4b 24 43 17 18 24 19 +FIXUPS: 170f5 16 5f 1e c 19 13 3d 1d 2a 1f 1d f 21 14 d 1b 39 fffff30f +FIXUPS: 16661 53 1f 86 12 2c 53 1d 18 31 1d 71 2b 15 15 27 1d 58 51 13 +FIXUPS: 16a4c 2e 1d 85 1b 3a 49 17 1f 1c 31 fffff3ce 27 27 20 1b 23 2e +FIXUPS: 1610b 2f 3c 2b 1e 47 1f 57 20 95 20 57 2b 23 11 4e 2f 26 13 46 +FIXUPS: 16537 2d 19 1f 86 ffffeca5 49 1e 64 69 21 3b 1e f2 45 12d 21 49 +FIXUPS: 15761 71 21 4a 18f 21 4d 123 45 111 f0 77 55 100 13 1d a 17 2f +FIXUPS: 146fd 98 1b 1d 55 20 55 2a 13 16 34 39 38 23b 21 4a 1e 35 2a ad +FIXUPS: 14d7c 23 33 c3 dc 69 21 3b 1e f7 45 116 ffffe81f 86 ce 73 41 62 +FIXUPS: 13d58 55 32 55 c5 6e 1b a1 24 d0 21 39 1f 3b 49 63 13f 33 a7 11 +FIXUPS: 144d7 16 10 1b 157 1a ffffe708 2a 21 2a 21 2a 21 3c f 1d 15 198 +FIXUPS: 13116 8 cb 98 67 86 fe 32 8e 52 123 80 1a 56 56 4c 4b 54 54 149 +FIXUPS: 1256c 17 c e2 1a 19 38 88 1a 19 81 1a 19 38 ae 1a 4b 42 2b a4 8c +FIXUPS: 12b80 21 34 71 24 2f 2f 21 2f 21 2c ffffed5c d2 17 1b c8 c3 5e +FIXUPS: 11dec 5f 60 44 60 45 138 21 52 f 59 7f 1a 26 13 1a 34 5e 57 15 +FIXUPS: 12400 27 8a 55 51 ffffe8f5 56 42 c2 25 10d 21 2b 1b 21 21 3d 16 +FIXUPS: 11222 62 51 127 5a 94 50 9c 1f 42 121 1df 1e 14 9 16 9 46 15 ffffec9c +FIXUPS: 106c0 21 f 27 b2 137 1b 49 70 ba 3f 78 15 2d 29 1f 2e 1f 14 1d +FIXUPS: 10c5e 11 11 11 60 30 29 30 42 29 30 ffffe9e6 31 3b 59 1b 39 1d +FIXUPS: f975 1a 141 2f 43c 20 3e 1f 8 3f 1b 2f 24 13 27 87 14 9 91 54 2b +FIXUPS: 10306 31c 30 f ffffecb9 1b d 30 5a f 18 9 37 51 27 f 18 9 65 14 +FIXUPS: f5a4 10 4e 19 18 17 16 17 26 17 11 17 3f 1a 90 17 fffff518 c 14 +FIXUPS: ed2b e7 4b 1a 87 83 d c 5e 1e 1b 3b 30 14 12 3c 16 1c 57 20 17 +FIXUPS: f1df 4c 3c 25 1b d 16 2b fffff2ba 57 cc 19 d 1e 12 13 24 71 f 1d +FIXUPS: e8a2 40 6e 3b 14 3e 82 24 6b d c 14 5e 1f 6e 3b 14 17 24 57 d61a +FIXUPS: 1c33f 18 1c f ffff1ccb 17 16 f 7e 14 9 20 9 22 34 22 44 59 ca 40 +FIXUPS: e403 33 29 f 2b 2b 17 d 64 1b 24 da04 18 14 19 f 18 19 f 18 19 +FIXUPS: 1c063 18 f e f e 8 21 1c 1c 1c 1c 1c 1c 1c 19 19 19 19 21 8 88 +FIXUPS: 1bcef 19 f 18 19 f 18 14 12 14 19 f 16 19 f 18 19 f 18 19 f 18 +FIXUPS: 1bec0 f 18 19 f 18 19 f 18 19 fffff796 8 1b 22 6f 8 52 1c 20 25 +FIXUPS: 1b8a1 15 184 d 5 91 39 70 26 30 f 18 f 1d e 1e 14 19 f 18 19 f +FIXUPS: 1b253 1b 8f 12 13 57 1e 5f 1e 1e 1e 1e 2e f 5c 21 17 a d d d d +FIXUPS: 1b58a d d d d 11 de 2a 12 1b fffff7d0 f 26 20 20 21 12 12 27 2c +FIXUPS: 1b00f 16 3f 21 25 16 c 8 1d 12 41 c 1f 12 12 12 12 12 12 12 c 13 +FIXUPS: 1a841 14 c 20 16 1a 60 ba 19 19 5b 174 17 13 21 21 b9 27 38 f 10 +FIXUPS: 1ad8c 37 21 c 29 19 21 f 26 19 21 fffff3a4 92 45 30 e 6 1a 107 +FIXUPS: 1a4ca 20 44 46 31 cd 2e 1a 18 19 26 14 1d 1d 10 14 c 20 19 10 14 +FIXUPS: 1a7f8 20 19 fffff1f5 12 8 10 d 21 e 8 4e 14 3f b 14 21 21 a1 f +FIXUPS: 19cfc 62 21 80 8 6 47 28 23 31 f6 84 1b 1bd 22 fffff195 23 20 23 +FIXUPS: 1945f 23 20 23 20 1e 1e 1e 1e 1e 1e 1b 18 18 20 1f 1f bf f 18 9 +FIXUPS: 19750 d7 48 21 134 44 d fffff523 28 26 26 26 26 26 23 23 23 50 +FIXUPS: 19155 f 18 9 1d 9 1b 14 7e f 18 9 1d 9 1b 2d 18 28 15 18 56 fffff777 +FIXUPS: 18b4c 15 20 69 3d 17 d 11 12 18 11 13 f 18 12 17 28 18 28 15 10 +FIXUPS: 18da0 16 56 28 23 28 23 28 23 28 57b1 49 37 16 14 6f 39 63 16 24 +FIXUPS: 1e91c 16 13 15 18 4b 15 15 a0 a7 7c ffff9d0b 4c 16 16 16 16 f 42 +FIXUPS: 18a99 31 49 56e4 38 17 1a 1f 18 59 e e e 2e 32 24 1d 16 28 69 f +FIXUPS: 1e489 9 1d 9 1d 7f 1a 15 1a 15 57 7f 1c c fffff7e2 38 17 1a 1f +FIXUPS: 1df68 17 13 18 15 17 13 18 1a 17 13 18 15 17 13 18 15 17 13 18 +FIXUPS: 1e11b 1d 18 1d 40 17 17 fffff950 1f 1a 2a 1a 5b 17 13 18 15 17 +FIXUPS: 1dc84 18 1a 17 13 18 15 17 13 18 15 17 13 18 10 1d 18 1d 40 17 +FIXUPS: 1de76 fffff82f 1f 27 1a 69 17 10 15 3c 17 10 15 37 17 10 15 10 +FIXUPS: 1d8c2 18 1d 57 17 17 1c 2d 54 28 1f 1d 1f 1a 5e fffff82d 18 54 +FIXUPS: 1d3c4 13 18 15 17 13 18 1a 17 13 18 15 17 13 18 15 17 13 18 10 +FIXUPS: 1d57d 18 1d 40 17 17 1c 38 17 fffff943 1a 61 17 13 18 15 17 13 +FIXUPS: 1d0e2 1a 17 13 18 15 17 13 18 15 17 13 18 10 1d 18 1d 40 17 17 +FIXUPS: 1d2d8 38 17 fffff875 28 28 15 1d 15 19 15 10 37 17 10 15 10 1d +FIXUPS: 1cd29 1d 35 4e 28 1c 1d 1c 17 54 17 1c 17 59 17 1a 1f fffff56e +FIXUPS: 1c541 33 2b 6a f 18 9 1d 9 1d 1b 123 19 2f 18 7d ad 2c 2c 42 18 +FIXUPS: 1ca1d 1b 1a 17 32 1a 6f 17 10 15 29dc 28 12 17 18 15 14 1a 28 12 +FIXUPS: 1f639 18 15 14 1a 2d 84 7e 7e 19 50 ffffcaf3 1a 2f 6c f 18 9 1d +FIXUPS: 1c4a8 1d 3e 2a81 c3 12 18 35 70 c c 2d 19 c3 12 18 50 71 c c 14 +FIXUPS: 1f36b c 46 5e c 1e 23 13 31 1e 18 17 16 13 10d6 371 17 17 8b 17 +FIXUPS: 20a50 ffffe1d4 5a d c c c 25 35 5e c c 1f 2a 1b 3e 5e c c 1b 4b +FIXUPS: 1ef15 13 c c 2b 52e3 f4 174 35 91 2e 19 1c1 ffffb238 52 37 2e d2 +FIXUPS: 1fb54 283 14 115 1d8 be 152 42 f 3a 81 c b 3b de 17 17 78 17 335a +FIXUPS: 2396f c 2c c 24 55 6 1a 1b 36 26 22 22 21 41 c 34 15b 18 37 1c +FIXUPS: 23ecd 2b 50 c 5a dc 1c c 7c 47 ffffe6fd 7a 115 9a 20 120 21 8e +FIXUPS: 22ca2 23 70 aa 8 50 27 c 9a c7 2d 1c a 6 6c 1fc b1 23 4d1 37 19 +FIXUPS: 238e4 c 3f ffffe592 b 1e f 64 20 32 a 277 f f0 a 18 7 a 42 b 2d +FIXUPS: 223f8 d f 5d f2 59 2c 14 86 af 17 25 3c 6e ffffed65 5 5 5 5 11 +FIXUPS: 215b0 78 9 a e b8 48 68 2c 14 55 3c 50 23f 13 15 13 15 5c 77 14f +FIXUPS: 21d91 b8 19 a 14 3611 3d 1f 1f c 1f ffffb81b 36d 17 170 43 dd 25 +FIXUPS: 2145b 5 5 18 5 3d 14 5 13 5 16 1b 5 1b 15 10 7 5 5 5891 1e 51 13 +FIXUPS: 26f65 1e 12 37 39 43 130 74 7d c 61 2d 1a 12 ffffd567 67 14d 234 +FIXUPS: 24caa ac 2c 80 f 90 f 2c3 1c7 68 d4f 16 67 24e 11 2f 28 28 22 87 +FIXUPS: 26509 c1 31 b2 1d 159 1d 58 3b 14 c9 1e 2c 62 15 22 47 147 4d 1a +FIXUPS: 26c9a 162 12ef 37 ac ffffd42a 17 1d 54 20 15b f a 11 f f f 37 7d +FIXUPS: 25937 2f 66 ef bf 15c 1f 19 104 43 39 c 5e df 52 167f 72 59 26 +FIXUPS: 277ac 29f 2f a6 c 69 5f 60 1a d 1f 1f 1f 1c 42 12 4e d 1f 1f 1f +FIXUPS: 27e1c 59 37 ac 58 37 ab 2419 b 2b 2e 1a f 1e 60 17 13 c 15 9 14 +FIXUPS: 2a630 20 9 2e 9 f 1f 1d 29 ffffccba 39 166 f 82 c 2a 16 30 29e1 +FIXUPS: 2a05d 18 14 2b 9 32 1a f 1e 60 17 13 c 15 9 3d 1c 16 12 20 9 2e +FIXUPS: 2a2cb f 1f 1d 29 46 25 31 22 fffff939 10 1f 1b 10 1f 29 9 2e 9 +FIXUPS: 29e63 13 29 5 16 21 16 10 f 1f 14 15 16 f 1a 14 11 15 16 f 20 14 +FIXUPS: 29821 1f 1b 14 10 21 1b 14 10 1f 1b 19 19 19 8 19 d 4f 1b ab 4e +FIXUPS: 29b93 7e 15 2a a 48 10 1f 1b 10 1f fffff63d 15 16 15 18 5b 10 18 +FIXUPS: 29445 5e c 24 28 28 45 1b 4e 1b 49 25 1b 12 1d 19 19 24 15 3f 44 +FIXUPS: 297d3 2a 14 fffff731 13 19 20 20 21 31 19 19 16 15 39 3b 3b 1a +FIXUPS: 29151 1e 1a 2a 1e 1a 2c 1a 2b 1a 2b 14 1a d 13 d 2a fffff843 1d +FIXUPS: 28b9b 64 3c 14 9 21 e 31 1b b 66 19 10 14 9 1f 10 17 15 1d a 1f +FIXUPS: 28e43 1b 26 1c 16 26 1c 2b fffff531 5e 8c 2c 19 39 8c 3a 3a 10 +FIXUPS: 286ec b c0 c 11 b 1e3 4a d 18 1d a d 18 1d 2f d 18 1d e 2b d 4d39 +FIXUPS: 2d89f 18 a3 3d 21 11 83 c 77 8 1b ffffa745 c 34 23 11 1a 27 1a +FIXUPS: 28319 1e e 1e 1a 14 f 14 9 d 2e 53 483c 8 46 c 6a 15 23 35 b 11 +FIXUPS: 2cddc 9f 4e 3d 59 32 150 127 14 9 185 198 b4 6f 10 20 c b2 12 a7 +FIXUPS: 2d833 4b ffffee1a 2c 10 14 10 14 10 14 10 14 10 40 61 8e 1f 18 +FIXUPS: 2ca42 36 14 f 2f 18 20 15 75 3f c 3d 1c c 1d b fffff62e 18 14 1a +FIXUPS: 2c300 15 47 14 24 39 2b 25 b 14 9 7d d 16 10 18 d 16 10 3b 1a 18 +FIXUPS: 2c5d1 18 1c 2e 22 3a fffff0ff 53 f7 16 d 19 27 f 17 17 11 28 21 +FIXUPS: 2ba58 35 2f 1d 6c 75 53 49 11 49 2f4 59 a3 c 17 125 11 4f 3b ffffe9fd +FIXUPS: 2acb1 6d 17 73 8d 41 33 41 5a 3a 15 36 15 3e 16 a 5d 19 63 b8 16 +FIXUPS: 2b1f0 19 93 10d 22 53 186 1c 7d 33 604f 20 38 92 29 ab 29 2b 29 +FIXUPS: 319c8 29 1e ffff8dcf aa f 20 d5 26 1b f 20 90 1d 84 31 e d e 23 +FIXUPS: 2abf8 1e 31 662e 10 16 d 10 1a f 19 15 1d f 8c a 17 13 1c 34 15 +FIXUPS: 3147d f 6f 37 19 31 10 1a f 1d 15 1d f 6f fffff965 10 19 10 19 +FIXUPS: 31049 19 10 19 10 19 10 19 12 19 12 19 10 19 10 19 10 19 10 19 +FIXUPS: 311e7 19 10 19 10 16 10 fffffacd 19 14 19 14 19 10 19 10 19 16 +FIXUPS: 30e20 16 19 12 19 16 19 16 19 12 19 10 19 10 19 c 1b 19 10 19 10 +FIXUPS: 306d6 10 f1 13 22 1b 13 1b 13 6a 39 35 23 2e 4a 10 31 10 23 1a +FIXUPS: 30a76 f 1a 4b f 1f f 17 30 9 191 14 fffff3f2 b 78 33 35 35 1b 2a +FIXUPS: 30295 f 17 17 13 15 22 19 bb 31 1a 2e 1a 22 f 99 f 16 2d 1a 25 +FIXUPS: 305fe 1e 35 fffff61a 3f 2a 2c 19 21 37 27 1c 19 1d 29 1c 1c 27 +FIXUPS: 2fe8e 1d b a7 b f 14 b 13 b 31 39 35 23 2b 3d b fffff7bb 1c f 5c +FIXUPS: 2f94b 16 e 1a 12 24 2c 35 35 1b 16 11 15 22 19 1d 1d 1d 1d 1d 1d +FIXUPS: 2fbaf 1a 24 1e 2a b 14 fffff415 c1 9a 1d 106 2a 2c 12 32 2c 2c +FIXUPS: 2f3eb 32 2c 4d 32 2c 1f 2c 29 63 6a 14 7c 1a 42 2c 4b 4f 71 1a +FIXUPS: 2f895 fffff1bd 2f 2f 15 3c 15 1e 15 19 2a 1d 31 33 7d 63 5e 45 +FIXUPS: 2eda8 10 c c a1 5f 20 20 20 3a 60 13 3e 20 1b fffff599 4c 1a 14 +FIXUPS: 2e6b5 1a 14 5d 1a 13 2d a 1b 30 1d f 21 14 15 16 f 79 28 b 27 b +FIXUPS: 2e993 b 25 b 47 15 fffff46c 3d af 4d 31 1a 44 1a 26 73 16 1f 1a +FIXUPS: 2e179 13 14 2b 3e 39 15e 33 3b 3b 3b 3b 40 16 4b c 10 14 18 4f18 +FIXUPS: 3350f 2c 56 15a 2e 41 109 1ac 61 4b 4b 48 2d 90 135 39 18 69 1b +FIXUPS: 2dd0a f 32 39 1a 13 1b 1a 1d 1a 26 44 4931 35 14 e5 14 1b 1d 228 +FIXUPS: 32b6e 26 31 31 27 21 1e 1e 1e c 73 5e 8e 37 b5 52 1d 9e 8 46 d9 +FIXUPS: 33184 1ac 61 ffffeb42 b 1f e 12 1f 97 30 27 27 26 b 1f 34 b 1f +FIXUPS: 3215b 1f 10f 11 11 f 2b 5d ad 85 97 58 58 97 52 18 4bcb 3d c 22 +FIXUPS: 37334 e 1b 12 ffffa742 77 2d d4 a9 15 4b b 1f e 12 1f 22 b 1f e +FIXUPS: 31e07 1f 22 b 1f e 12 1f 4d80 18 c 14 18 24 16 13 18 c 14 18 1d +FIXUPS: 36d51 13 18 11 13 18 c 1a 18 6c 17 161 17 f3 17 17 1c de 1f fffff60f +FIXUPS: 3685b 10 13 30 d 30 1b a 9 a 9 a 9 18 b9 11 29 c 24 20 20 20 25 +FIXUPS: 36b1a c c 5a 1c 25 18 39 fffff361 85 17 38 48 3f 3f 3a 3a 37 43 +FIXUPS: 3624f 59 5c 2c 3f 13 d6 58 15 18 c 21 55 5d 42 5e 42 5d 42 5e 42 +FIXUPS: 353c0 4e 18 15 15 15 1c 15 55 1a 7b 1c 49 3d 48 b1 24 2e da 3a9 +FIXUPS: 35d53 15 15 15 15 15 2d 2a 1a 2f 39 28 ffffeccc 16 10 2d 64 55 +FIXUPS: 34cbd 61 55 55 d 22 2c 44 3a 24 5b 49 47 72 58 5b 59 5a 1f 3a 1e +FIXUPS: 35219 74 7d 50 3e 4956 ffffa2ba 2b 4d b5 2e 19 2e 19 2e 19 2e 16 +FIXUPS: 3421c 272 17b 46 2e 260 30 58 15 18 c 1e 2e 33 40 6d 36 16 10 4d73 +FIXUPS: 398c5 26 3d 16 14 59 60 5 a7 2a 43 26 1a 1a 23 1a 1e 17 11 d 8 +FIXUPS: 39c5e a 8 5 5 5 7 a 8 42 fffff54d f 4a f 55 b d 6e b 31 f 43 b +FIXUPS: 39434 23 35 1d 4f 46 d 44 d 7d 27 23 d 192 2f 1c 18 d 1c fffff2a9 +FIXUPS: 38b47 1b 1b 39 ae 14c 3c 8d 2f 48 d f 42 d 60 d 22 d 6c 17 b 35 +FIXUPS: 390c7 1f d 36 2c b d 79 b fffff329 b 12 b 28 b 26 b 8 e 52 27 c3 +FIXUPS: 3876c 22 69 47 29 f e 5 51 c a f 20 c 32 7d f2 23 27 fffff030 d +FIXUPS: 37b63 c c 8 5e 16 16 d e 8 a 10 2e 1e 30 1e 122 52 52 64 5c 34b +FIXUPS: 38474 b 14 b 20 b 12 b fffff3a5 12 12 c 8 5 5 11 4e 28 d 3e 16 +FIXUPS: 379d0 16 d 14 f 8 11 2a 78 8 5 5 5 e 5 a 16 11 d 2e63 13 13 13 +FIXUPS: 39d2e 20 5a ffffd603 13 1b e 10 10 10 10 10 c b 61 19 30 19 12c +FIXUPS: 376d3 66 36 23 12 5e 44 b 23 2b39 8f 92 14 8 52 56 4a 1c 1c 1c +FIXUPS: 3a64c 1c 1c 13 3d 1d 1c d1 22 16 2e 9 18 46 43 19 36 13 13 13 13 +FIXUPS: 41ce1 2b 13 50 64 11 35 ffff7fb6 11 18 11 2f 23 21 6c b0 1e 18 +FIXUPS: 3a04a 1e 18 1a 16 c c3 42 17 c 6a c8 16 17 6b1d a 13a 17 17 156 +FIXUPS: 41134 b8 27 c2 d7 2d 40 96 ef 14 a 17b 17 17 7e 1c 1c2 17 17 201 +FIXUPS: 41c46 27 2a 12 13 12 ffffe6e3 38 5b 31 28 31 32 36 a 1b 33 e2 fe +FIXUPS: 4088f 17 17 41 19 a 13a 17 17 4e a 143 17 17 4e a 13a 17 17 ffffe9c9 +FIXUPS: 3f7f8 1b b9 3c 47 133 e 10 1e4 10 3f a 129 17 17 aa 5d 25 28 35 +FIXUPS: 3ffe6 7f fe 22 35 59 1b 1f 4a 52 70 ffffe839 1d f9 16 bd 27 61 +FIXUPS: 3ee0d 11f 17 17 9a 5a 13 119 c9 69 31 31 161 1f 1f 3d 30 31 31 +FIXUPS: 3f607 9b 7c 2e c 47 ffffe7aa 2e c5 19 1b5 103 c9 8a 5d 74 15 101 +FIXUPS: 3e695 2c 1d 1b 1b 71 48 131 29 29 79 1e 53 e 16 a 95 11 11 56 ffffe8b9 +FIXUPS: 3d473 17 17 5f 2c 37 dd 5c 3f 31 8 af 9d 17 17 69 40 7a 10f c4 +FIXUPS: 3dbb1 21 9f 27 28 35 3b 40 24 80 f9 ffffe635 81 5f e2 2d 28 5f +FIXUPS: 3c899 cf 85 2b 70 14 7d 4d 51 2a 19 14 9f b7 50 8 96 3a 1c 94 2a5 +FIXUPS: 3d219 69 31 fa ffffe960 73 53 31 c 5b 62 12 32 75 8 13 3b 36 11 +FIXUPS: 3c04a 2e c 37 c 4e 128 c 29 5f 10 67 79 8 a9 a2 17 ffffee81 a2 +FIXUPS: 3b45a 3e f 48 f 11 f 28 f 3f e8 13 51 4f 54 78 69 69 45 4f 74 86 +FIXUPS: 3baa6 94 93 35 11 c 58 54 fffff0db 27 b 11 11 16 2a f 5b 49 42 +FIXUPS: 3af59 2e 35 57 20 53 22 4a 47 5d c 5b 17 10 2f f 9f 2c f 20 40 +FIXUPS: 455a0 19 1f 16 17 34 11 16f 40 10 2c 14 12 f 173 17 27 1d ffff4fdb +FIXUPS: 3aa25 11 11 34 17 127 24 57 34 12 20 3b 95 a218 e c 18 c 46 c 103 +FIXUPS: 45121 8a 23 16 1c 16 16 c 68 14 6c 1c 16 16 17 34 1a f 86 14 4c +FIXUPS: 454e9 82 1c fffff27d 23 16 a0 3e 35 15 aa 16 18 17 1a 1a ec 56 +FIXUPS: 44cda 14 2a 35 40 27 e 53 e 27 18 e 19 21 22 e c ffffeee1 2e 4d +FIXUPS: 43e6a b1 14 b6 1a 7e 9d 1c 23 15 28 9c 1c 43 18 17 1a 1a 153 2e +FIXUPS: 444b0 c8 10 19b 17 28 54 1c 1c ffffeedf f 35 34 24 3a 28 59 24 +FIXUPS: 43898 24 28 1f 10 2e 19 24 21 21 21 b4 82 76 52 52 4d 35 16 c 49 +FIXUPS: 43d46 26 ffffec26 a1 217 1c 9d 48 b5 24 3f af 4a 62 1a 72 86 1f +FIXUPS: 430fb cf e7 20 c 225 c 46 1d 36 3c 24 24 34 22 24 2491 2c 21 54 +FIXUPS: 45beb 6a 13 21 99 26 f 2b 1b e 4f ffffc0e5 39 68 f 14f e0 10 ae +FIXUPS: 423d6 3c eb 66 b7 11c 6d 10c 42 3d53 11 f f 14 b 16 e 16 e 16 e +FIXUPS: 4670d 16 11 16 11 16 11 23 1b 32 b d 23 e 8 5 9 fffff29b 22 18 +FIXUPS: 461e9 1a 1a 65 3f 21 12 24 12 1d 12 1b 12 18 36 15 5f 10 16 24 +FIXUPS: 464b8 4a 2b 21 11 25 11 19 16 f 1c 44 d3c c 1b 13 1e c 1b 13 24 +FIXUPS: 47433 e b 11 e b ffffe9dc 44 f 26 f 26 26 2b 39 5a 3e 31 18 12 +FIXUPS: 46147 37 2e e17 f f f f 16 23 d 81 25 38 5e 1c 25 f 27 2f 23 14 +FIXUPS: 4726a 1b c 1b c 1b c 1b c 1b c 1b c fffff808 61 e7 19 f 31 14 e +FIXUPS: 46d41 f 1b 16 f 18 f 15 f 1b 16 7a 8 15 5d 2a 12 12 12 12 12 12 +FIXUPS: 46fa5 f 244e 16 1c 22 18 98 3f 36 2a 3a 3c 5a 2c ffffe310 35e 20d +FIXUPS: 47f5f 28 3b 60 d 43d eb 123 4f 75 ffffe240 34 55 6b 28 1a 22b3 +FIXUPS: 48d7b 28 7b 2a 1a 23 1c 16 13 e 31 1b 14 c 3c 1d 8a 12b 70 48 27 +FIXUPS: 4926e 11 36 e 35 22 18 27 4f e 2180 33 3b 1a be b9 ffffd1f9 22 +FIXUPS: 4897b 19 4d 22 2b 19 45 1e 1e 1c 2d 31 31 41 20 55 63 1a 3c b 27 +FIXUPS: 48d1f 19 b 1dc6 18 ad 10 b1 3f dd a6 5 1f 20 30 18 1f 30 24 1e +FIXUPS: 4af94 16 d2 31 7c 21 87 15 3f a 26 22 71 f0 14f ffffef67 d4 f8 +FIXUPS: 4a682 14 10 1d a 19 13 57 83 54 13 51 9 15 e 10 1e 1b 1b 63 9 55 +FIXUPS: 4a9f5 18 46 9 2a 9 19 ffffec78 c df 14 18 4d 10 16 213 22 1d 1d +FIXUPS: 49bd5 33 90 2d 20 a8 2d 20 e1 121 171 b4 28 10 2c 43 13e 28 10 +FIXUPS: 4a44a 2730 d 75 1e 1d 37 40 20 1c 5a e 1e6 37 17 1b 39 1c 16 19 +FIXUPS: 4d046 21 21 21 6f e 8b 79 36 14 1c 93 ffffc3cb 2ebe 23 21 18 14 +FIXUPS: 4c638 18 14 2c 9 30 9 9d 3c 33 87 16 1b 16 18 16 18 16 4a 89 2a +FIXUPS: 4ca55 f 1f e 8e 48 fffff610 c 19 54 18 1f 13 18 c 14 18 1f 16 13 +FIXUPS: 4c2ea c 14 18 1f 16 13 18 17 18 7b 21 2f 23 23 17 79 11 fffff4c0 +FIXUPS: 4ba49 16 15 c 4e 7d 15 36 1c 4b 56 22 16 53 b4 3e 5c 18 f6 16 1c +FIXUPS: 4bf7f 49 23 21 16 20 c 10 84 16 1b3a f 35 c 67 21 39 5a 11 11 16 +FIXUPS: 4dde1 17 a9 7c 31 1f dc 15d 63 c c8 ffffd493 1d 16 c1 4c 31 8f +FIXUPS: 4b993 20 1c 1a15 44 7f 19 72 20 30 16 22 1d 67 d 7a 3e 25 51 c +FIXUPS: 4d7ec 8 8 33 1e 3f 83 c b 11 1b 39 38 ca 148 3be8 6e 22 50 5d 63 +FIXUPS: 519c7 9d 30 35 11 11 22 f 16 f 11 11 11 11 e e e e e e e e e ffffb763 +FIXUPS: 4d386 3f 3e05 12 14 3a 1b a0 50 59 17 17 11 3d 2e 28 35 3b 2d 38 +FIXUPS: 51562 26 af 16 18 13 18 13 2a 37 1b 3b 2f 26 fffff0f8 5e f5 97 +FIXUPS: 50aaa 3c f 39 f a1 d4 c1 8a 48 7c f 43 40 1a 23 33 15 26 1a 3c +FIXUPS: 51088 4e 37 1c 25 10 3d ffffeb4c f4 10 100 22 62 55 44 b3 5b 144 +FIXUPS: 5029d 6c 4f 22 54 62 4c 84 4f 67 6d 6f 77 42 1b 42 10 3f 3f 2e +FIXUPS: 50894 ffffead1 11 4a 27 18 1a 1d 24 1a 1d 24 1a 1d 27 84 4f 49 +FIXUPS: 4f684 54 7e 50 e 120 1b 5f 13 e ed 179 e 51 18 fffff0d9 31 2f 21 +FIXUPS: 4eddb f 48 3a 15 1c 1a 1d 7f 44 40 25 10 32 12 14 3a 1b a0 b0 47 +FIXUPS: 4f267 17 11 4a 18 46 17 fffff18e a9 75 20 63 49 1d 4d 2a 25 17 +FIXUPS: 4e7b5 18 15 16 15 18 72 43 20 57 13a 1a 1d 53 1f 24 2b b 3a b fe +FIXUPS: 528af 1b 1c 1d 2c 24 36 2ca 1a 36 3c 13a 8b 3c7 17 22 17 23 17 +FIXUPS: 53447 17 22 17 78 29 37 c ffffae64 16 17 a3 16 37c1 25 16f 11 11 +FIXUPS: 51e58 11 11 11 116 c d9 14 66 46 33 14 14 16 1d 1a 7c 46 1a 8f +FIXUPS: 523cf 1d 6a 139 24 12f 16 237b 11 13 12 11 15 12 27 f e 17 15 13 +FIXUPS: 54b79 a 16 e 12 11 13 12 11 13 12 11 15 12 27 f e 17 15 fffff5a4 +FIXUPS: 5428b 1a 112 23 2b 23 18 4c 1e 1f 16 ff 11c 16 e 102 17 27 9 16 +FIXUPS: 5488b 102 17 28 16 25 16 e 12 11 13 ffffebde 47 1d 129 3c 1d 21 +FIXUPS: 5386a 23 24 25e 228 13 1b 1f ab 26 f 1c 19 38 13 19 40 13 e7 15 +FIXUPS: 54061 17 162 36 3a diff --git a/quake3/source/code/game/Debug_TA/qagamex86.pdb b/quake3/source/code/game/Debug_TA/qagamex86.pdb new file mode 100644 index 0000000..c425296 Binary files /dev/null and b/quake3/source/code/game/Debug_TA/qagamex86.pdb differ diff --git a/quake3/source/code/game/Debug_TA/vc50.idb b/quake3/source/code/game/Debug_TA/vc50.idb new file mode 100644 index 0000000..b4f702b Binary files /dev/null and b/quake3/source/code/game/Debug_TA/vc50.idb differ diff --git a/quake3/source/code/game/ai_chat.c b/quake3/source/code/game/ai_chat.c new file mode 100644 index 0000000..94fa703 --- /dev/null +++ b/quake3/source/code/game/ai_chat.c @@ -0,0 +1,1391 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_chat.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_chat.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#ifdef MISSIONPACK // bk001205 +#include "../../ui/menudef.h" +#endif + +#define TIME_BETWEENCHATTING 25 + + +/* +================== +BotNumActivePlayers +================== +*/ +int BotNumActivePlayers(void) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + + num = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + num++; + } + return num; +} + +/* +================== +BotIsFirstInRankings +================== +*/ +int BotIsFirstInRankings(bot_state_t *bs) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score < ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; +} + +/* +================== +BotIsLastInRankings +================== +*/ +int BotIsLastInRankings(bot_state_t *bs) { + int i, score; + char buf[MAX_INFO_STRING]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + + score = bs->cur_ps.persistant[PERS_SCORE]; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (score > ps.persistant[PERS_SCORE]) return qfalse; + } + return qtrue; +} + +/* +================== +BotFirstClientInRankings +================== +*/ +char *BotFirstClientInRankings(void) { + int i, bestscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + + bestscore = -999999; + bestclient = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (ps.persistant[PERS_SCORE] > bestscore) { + bestscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotLastClientInRankings +================== +*/ +char *BotLastClientInRankings(void) { + int i, worstscore, bestclient; + char buf[MAX_INFO_STRING]; + static char name[32]; + static int maxclients; + playerState_t ps; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + + worstscore = 999999; + bestclient = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + BotAI_GetClientState(i, &ps); + if (ps.persistant[PERS_SCORE] < worstscore) { + worstscore = ps.persistant[PERS_SCORE]; + bestclient = i; + } + } + EasyClientName(bestclient, name, 32); + return name; +} + +/* +================== +BotRandomOpponentName +================== +*/ +char *BotRandomOpponentName(bot_state_t *bs) { + int i, count; + char buf[MAX_INFO_STRING]; + int opponents[MAX_CLIENTS], numopponents; + static int maxclients; + static char name[32]; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + + numopponents = 0; + opponents[0] = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + //skip team mates + if (BotSameTeam(bs, i)) continue; + // + opponents[numopponents] = i; + numopponents++; + } + count = random() * numopponents; + for (i = 0; i < numopponents; i++) { + count--; + if (count <= 0) { + EasyClientName(opponents[i], name, sizeof(name)); + return name; + } + } + EasyClientName(opponents[0], name, sizeof(name)); + return name; +} + +/* +================== +PrivateBotOwner + +PKMOD - Ergodic 12/15/01 - return Private Bot's owner +================== +*/ +char *PrivateBotOwner(bot_state_t *bs) { + int i; + static char name[32]; + + //PKMOD - Ergodic 03/18/02 - is calling BOT a Private Bot? + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + i = g_entities[bs->entitynum].parent->client->ps.clientNum; + EasyClientName( i, name, sizeof(name)); + } + else { + strcpy( name, "[invalid var]" ); + } + return name; +} + +/* +================== +BotMapTitle +================== +*/ + +char *BotMapTitle(void) { + char info[1024]; + static char mapname[128]; + + trap_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + return mapname; +} + + +/* +================== +BotWeaponNameForMeansOfDeath +================== +*/ + +char *BotWeaponNameForMeansOfDeath(int mod) { + switch(mod) { +//PKMOD - Ergodic 03/22/01 - change shotgun name to boomstick + case MOD_SHOTGUN: return "Boomstick"; + case MOD_GAUNTLET: return "Gauntlet"; + case MOD_MACHINEGUN: return "Machinegun"; + case MOD_GRENADE: + case MOD_GRENADE_SPLASH: return "Grenade Launcher"; + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: return "Rocket Launcher"; + case MOD_PLASMA: + case MOD_PLASMA_SPLASH: return "Plasmagun"; +//PKMOD - Ergodic 07/12/01 - change railgun name to magnum + case MOD_RAILGUN: return "Magnum"; +//PKMOD - Ergodic 12/19/03 - change Lightning Gun name to Chain Lightning Gun + case MOD_LIGHTNING: return "Chain Lightning Gun"; + case MOD_BFG: + case MOD_BFG_SPLASH: return "BFG10K"; +#ifdef MISSIONPACK + case MOD_NAIL: return "Nailgun"; + case MOD_CHAINGUN: return "Chaingun"; + case MOD_PROXIMITY_MINE: return "Proximity Launcher"; + case MOD_KAMIKAZE: return "Kamikaze"; + case MOD_JUICED: return "Prox mine"; +#endif + case MOD_GRAPPLE: return "Grapple"; + //PKMOD - Ergodic 07/02/01 - add reverse damage on lightning from autosentry + case MOD_REVERSE_LIGHTNING: + switch ( rand() % 3 ) { + case 0: + return "Lightning Shield"; + break; + case 1: + return "Zap Mirror"; + break; + default: + return "Sizzle Reflector"; + break; + } + default: + //PKMOD - Ergodic 03/28/01 - fix "[unknown weapon]" with a bandaide patch +// return "[unknown weapon]"; + switch ( rand() % 3 ) { + case 0: + return "PainKeep Weapon"; + break; + case 1: + return "Evolved Weapon"; + break; + default: + return "Smelly New Weapon"; + break; + } + + } +} + +/* +================== +BotRandomWeaponName +================== +*/ +char *BotRandomWeaponName(void) { + int rnd; + +#ifdef MISSIONPACK + rnd = random() * 11.9; +#else + rnd = random() * 8.9; +#endif + switch(rnd) { + case 0: return "Gauntlet"; +//PKMOD - Ergodic 03/22/01 - change shotgun name to boomstick + case 1: return "Boomstick"; + case 2: return "Machinegun"; + case 3: return "Grenade Launcher"; + case 4: return "Rocket Launcher"; + case 5: return "Plasmagun"; +//PKMOD - Ergodic 07/12/01 - change railgun name to magnum + case 6: return "Magnum"; +//PKMOD - Ergodic 12/19/03 - change Lightning Gun name to Chain Lightning Gun + case 7: return "Chain Lightning Gun"; +#ifdef MISSIONPACK + case 8: return "Nailgun"; + case 9: return "Chaingun"; + case 10: return "Proximity Launcher"; +#endif + default: return "BFG10K"; + } +} + +/* +================== +BotVisibleEnemies +================== +*/ +int BotVisibleEnemies(bot_state_t *bs) { + float vis; + int i; + aas_entityinfo_t entinfo; + + for (i = 0; i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + //PKMOD - Ergodic 10/31/02 - check if enemy has a beartrap attached + // attached beartraps will make the player visible to the bots + entityState_t state; + BotAI_GetEntityState(i, &state); + if ( ( state.time2 & 3 ) == 0 ) //if no beartraps are attached + continue; + } + //if on the same team + if (BotSameTeam(bs, i)) continue; + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis > 0) return qtrue; + } + return qfalse; +} + +/* +================== +BotValidChatPosition +================== +*/ +int BotValidChatPosition(bot_state_t *bs) { + vec3_t point, start, end, mins, maxs; + bsp_trace_t trace; + + //if the bot is dead all positions are valid + if (BotIsDead(bs)) return qtrue; + //never start chatting with a powerup + if (bs->inventory[INVENTORY_QUAD] || + bs->inventory[INVENTORY_HASTE] || + bs->inventory[INVENTORY_INVISIBILITY] || + bs->inventory[INVENTORY_REGEN] || + bs->inventory[INVENTORY_FLIGHT]) return qfalse; + //must be on the ground + //if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) return qfalse; + //do not chat if in lava or slime + VectorCopy(bs->origin, point); + point[2] -= 24; + if (trap_PointContents(point,bs->entitynum) & (CONTENTS_LAVA|CONTENTS_SLIME)) return qfalse; + //do not chat if under water + VectorCopy(bs->origin, point); + point[2] += 32; + if (trap_PointContents(point,bs->entitynum) & MASK_WATER) return qfalse; + //must be standing on the world entity + VectorCopy(bs->origin, start); + VectorCopy(bs->origin, end); + start[2] += 1; + end[2] -= 10; + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); + BotAI_Trace(&trace, start, mins, maxs, end, bs->client, MASK_SOLID); + if (trace.ent != ENTITYNUM_WORLD) return qfalse; + //the bot is in a position where it can chat + return qtrue; +} + +/* +================== +BotChat_EnterGame +================== +*/ +int BotChat_EnterGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 12/13/01 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_ExitGame +================== +*/ +int BotChat_ExitGame(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_ENTEREXITGAME, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 01/14/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_StartLevel +================== +*/ +int BotChat_StartLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + //don't chat in teamplay + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qfalse; + } + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_EndLevel +================== +*/ +int BotChat_EndLevel(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + // teamplay + if (TeamPlayIsOn()) + { + if (BotIsFirstInRankings(bs)) { + trap_EA_Command(bs->client, "vtaunt"); + } + return qtrue; + } + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_STARTENDLEVEL, 0, 1); + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (BotIsFirstInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + } + else if (BotIsLastInRankings(bs)) { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + } + else { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + } + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Death +================== +*/ +int BotChat_Death(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_DEATH, 0, 1); + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chatting is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (bs->lastkilledby >= 0 && bs->lastkilledby < MAX_CLIENTS) + EasyClientName(bs->lastkilledby, name, 32); + else + strcpy(name, "[world]"); + // + if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledby)) { + if (bs->lastkilledby == bs->client) return qfalse; + BotAI_BotInitialChat(bs, "death_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else + { + //teamplay + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qtrue; + } + // + if (bs->botdeathtype == MOD_WATER) + BotAI_BotInitialChat(bs, "death_drown", + BotRandomOpponentName(bs), // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + else if (bs->botdeathtype == MOD_SLIME) + BotAI_BotInitialChat(bs, "death_slime", + BotRandomOpponentName(bs), // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + else if (bs->botdeathtype == MOD_LAVA) + BotAI_BotInitialChat(bs, "death_lava", + BotRandomOpponentName(bs), // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + else if (bs->botdeathtype == MOD_FALLING) + BotAI_BotInitialChat(bs, "death_cratered", + BotRandomOpponentName(bs), // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + else if (bs->botsuicide || //all other suicides by own weapon + bs->botdeathtype == MOD_CRUSH || + bs->botdeathtype == MOD_SUICIDE || + bs->botdeathtype == MOD_TARGET_LASER || + bs->botdeathtype == MOD_TRIGGER_HURT || + bs->botdeathtype == MOD_UNKNOWN) + BotAI_BotInitialChat(bs, "death_suicide", + BotRandomOpponentName(bs), // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + else if (bs->botdeathtype == MOD_TELEFRAG) + BotAI_BotInitialChat(bs, "death_telefrag", + name, // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); +#ifdef MISSIONPACK + else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "death_kamikaze")) + BotAI_BotInitialChat(bs, "death_kamikaze", name, NULL); +#endif + else { + if ((bs->botdeathtype == MOD_GAUNTLET || + bs->botdeathtype == MOD_RAILGUN || + bs->botdeathtype == MOD_BFG || + bs->botdeathtype == MOD_BFG_SPLASH) && random() < 0.5) { + + if (bs->botdeathtype == MOD_GAUNTLET) + BotAI_BotInitialChat(bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + else if (bs->botdeathtype == MOD_RAILGUN) + BotAI_BotInitialChat(bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + else + BotAI_BotInitialChat(bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + } + //choose between insult and praise + else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + else { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + } + } + bs->chatto = CHAT_ALL; + } + bs->lastchat_time = FloatTime(); + return qtrue; +} + +/* +================== +BotChat_Kill +================== +*/ +int BotChat_Kill(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (bs->lastkilledplayer == bs->client) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + bs->chatto = CHAT_ALL; + if (TeamPlayIsOn() && BotSameTeam(bs, bs->lastkilledplayer)) { + BotAI_BotInitialChat(bs, "kill_teammate", name, NULL); + bs->chatto = CHAT_TEAM; + } + else + { + //don't chat in teamplay + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qfalse; // don't wait + } + // + if (bs->enemydeathtype == MOD_GAUNTLET) { + BotAI_BotInitialChat(bs, "kill_gauntlet", + name, // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + } + else if (bs->enemydeathtype == MOD_RAILGUN) { + BotAI_BotInitialChat(bs, "kill_rail", + name, // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + } + else if (bs->enemydeathtype == MOD_TELEFRAG) { + BotAI_BotInitialChat(bs, "kill_telefrag", + name, // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + } +#ifdef MISSIONPACK + else if (bs->botdeathtype == MOD_KAMIKAZE && trap_BotNumInitialChats(bs->cs, "kill_kamikaze")) + BotAI_BotInitialChat(bs, "kill_kamikaze", name, NULL); +#endif + //choose between insult and praise + else if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_INSULT, 0, 1)) { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + } + } + bs->lastchat_time = FloatTime(); + return qtrue; +} + +/* +================== +BotChat_EnemySuicide +================== +*/ +int BotChat_EnemySuicide(bot_state_t *bs) { + char name[32]; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + // + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_KILL, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + if (bs->enemy >= 0) EasyClientName(bs->enemy, name, 32); + else strcpy(name, ""); + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitTalking +================== +*/ +int BotChat_HitTalking(bot_state_t *bs) { + char name[32], *weap; + int lasthurt_client; + float rnd; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITTALKING, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + // + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoDeath +================== +*/ +int BotChat_HitNoDeath(bot_state_t *bs) { + char name[32], *weap; + float rnd; + int lasthurt_client; + aas_entityinfo_t entinfo; + + lasthurt_client = g_entities[bs->client].client->lasthurt_client; + if (!lasthurt_client) return qfalse; + if (lasthurt_client == bs->client) return qfalse; + // + if (lasthurt_client < 0 || lasthurt_client >= MAX_CLIENTS) return qfalse; + // + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNODEATH, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_HitNoKill +================== +*/ +int BotChat_HitNoKill(bot_state_t *bs) { + char name[32], *weap; + float rnd; + aas_entityinfo_t entinfo; + + if (bot_nochat.integer) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + if (BotNumActivePlayers() <= 1) return qfalse; + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_HITNOKILL, 0, 1); + //don't chat in teamplay + if (TeamPlayIsOn()) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //if fast chat is off + if (!bot_fastchat.integer) { + if (random() > rnd * 0.5) return qfalse; + } + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsShooting(&entinfo)) return qfalse; + // + ClientName(bs->enemy, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->enemy].client->lasthurt_mod); + // + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChat_Random +================== +*/ +int BotChat_Random(bot_state_t *bs) { + float rnd; + char name[32]; + + if (bot_nochat.integer) return qfalse; + if (BotIsObserver(bs)) return qfalse; + if (bs->lastchat_time > FloatTime() - TIME_BETWEENCHATTING) return qfalse; + // don't chat in tournament mode + if (gametype == GT_TOURNAMENT) return qfalse; + //don't chat when doing something important :) + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_RUSHBASE) return qfalse; + // + rnd = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_RANDOM, 0, 1); + if (random() > bs->thinktime * 0.1) return qfalse; + if (!bot_fastchat.integer) { + if (random() > rnd) return qfalse; + if (random() > 0.25) return qfalse; + } + if (BotNumActivePlayers() <= 1) return qfalse; + // + if (!BotValidChatPosition(bs)) return qfalse; + // + if (BotVisibleEnemies(bs)) return qfalse; + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + if (TeamPlayIsOn()) { + trap_EA_Command(bs->client, "vtaunt"); + return qfalse; // don't wait + } + // + if (random() < trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_MISC, 0, 1)) { + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + else { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + } + bs->lastchat_time = FloatTime(); + bs->chatto = CHAT_ALL; + return qtrue; +} + +/* +================== +BotChatTime +================== +*/ +float BotChatTime(bot_state_t *bs) { + int cpm; + + cpm = trap_Characteristic_BInteger(bs->character, CHARACTERISTIC_CHAT_CPM, 1, 4000); + + return 2.0; //(float) trap_BotChatLength(bs->cs) * 30 / cpm; +} + +/* +================== +BotChatTest +================== +*/ +void BotChatTest(bot_state_t *bs) { + + char name[32]; + char *weap; + int num, i; + + num = trap_BotNumInitialChats(bs->cs, "game_enter"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_enter", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "game_exit"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "game_exit", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_start"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_start", + EasyClientName(bs->client, name, 32), // 0 + "[invalid var]", // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + "[invalid var]", // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end_victory"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_victory", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end_lose"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end_lose", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "level_end"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "level_end", + EasyClientName(bs->client, name, 32), // 0 + BotRandomOpponentName(bs), // 1 + BotFirstClientInRankings(), // 2 + BotLastClientInRankings(), // 3 + BotMapTitle(), // 4 + //PKMOD - Ergodic 01/16/02 - add new variable for Private Bot's Owner + PrivateBotOwner(bs), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + EasyClientName(bs->lastkilledby, name, sizeof(name)); + num = trap_BotNumInitialChats(bs->cs, "death_drown"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "death_drown", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_slime"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_slime", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_lava"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_lava", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_cratered"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_cratered", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_suicide", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_telefrag", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_gauntlet"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_gauntlet", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_rail", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_bfg"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_bfg", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_insult", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "death_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "death_praise", + name, // 0 + BotWeaponNameForMeansOfDeath(bs->botdeathtype), // 1 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + // + EasyClientName(bs->lastkilledplayer, name, 32); + // + num = trap_BotNumInitialChats(bs->cs, "kill_gauntlet"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "kill_gauntlet", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_rail"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_rail", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_telefrag"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_telefrag", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_insult", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "kill_praise"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "kill_praise", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "enemy_suicide"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "enemy_suicide", name, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + ClientName(g_entities[bs->client].client->lasthurt_client, name, sizeof(name)); + weap = BotWeaponNameForMeansOfDeath(g_entities[bs->client].client->lasthurt_client); + num = trap_BotNumInitialChats(bs->cs, "hit_talking"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_talking", name, weap, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "hit_nodeath"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nodeath", name, weap, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "hit_nokill"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "hit_nokill", name, weap, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + // + if (bs->lastkilledplayer == bs->client) { + strcpy(name, BotRandomOpponentName(bs)); + } + else { + EasyClientName(bs->lastkilledplayer, name, sizeof(name)); + } + // + num = trap_BotNumInitialChats(bs->cs, "random_misc"); + for (i = 0; i < num; i++) + { + // + BotAI_BotInitialChat(bs, "random_misc", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } + num = trap_BotNumInitialChats(bs->cs, "random_insult"); + for (i = 0; i < num; i++) + { + BotAI_BotInitialChat(bs, "random_insult", + BotRandomOpponentName(bs), // 0 + name, // 1 + "[invalid var]", // 2 + "[invalid var]", // 3 + BotMapTitle(), // 4 + BotRandomWeaponName(), // 5 + NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_ALL); + } +} diff --git a/quake3/source/code/game/ai_chat.h b/quake3/source/code/game/ai_chat.h new file mode 100644 index 0000000..7c5a642 --- /dev/null +++ b/quake3/source/code/game/ai_chat.h @@ -0,0 +1,41 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_chat.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +// +int BotChat_EnterGame(bot_state_t *bs); +// +int BotChat_ExitGame(bot_state_t *bs); +// +int BotChat_StartLevel(bot_state_t *bs); +// +int BotChat_EndLevel(bot_state_t *bs); +// +int BotChat_HitTalking(bot_state_t *bs); +// +int BotChat_HitNoDeath(bot_state_t *bs); +// +int BotChat_HitNoKill(bot_state_t *bs); +// +int BotChat_Death(bot_state_t *bs); +// +int BotChat_Kill(bot_state_t *bs); +// +int BotChat_EnemySuicide(bot_state_t *bs); +// +int BotChat_Random(bot_state_t *bs); +// time the selected chat takes to type in +float BotChatTime(bot_state_t *bs); +// returns true if the bot can chat at the current position +int BotValidChatPosition(bot_state_t *bs); +// test the initial bot chats +void BotChatTest(bot_state_t *bs); + diff --git a/quake3/source/code/game/ai_cmd.c b/quake3/source/code/game/ai_cmd.c new file mode 100644 index 0000000..e17dbc3 --- /dev/null +++ b/quake3/source/code/game/ai_cmd.c @@ -0,0 +1,2085 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_cmd.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_cmd.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" + +int notleader[MAX_CLIENTS]; + +#ifdef DEBUG +/* +================== +BotPrintTeamGoal +================== +*/ +void BotPrintTeamGoal(bot_state_t *bs) { + char netname[MAX_NETNAME]; + float t; + + ClientName(bs->client, netname, sizeof(netname)); + t = bs->teamgoal_time - FloatTime(); + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna help a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_TEAMACCOMPANY: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna accompany a team mate for %1.0f secs\n", netname, t); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get the flag for %1.0f secs\n", netname, t); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna rush to the base for %1.0f secs\n", netname, t); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna try to return the flag for %1.0f secs\n", netname, t); + break; + } +#ifdef MISSIONPACK + case LTG_ATTACKENEMYBASE: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna attack the enemy base for %1.0f secs\n", netname, t); + break; + } + case LTG_HARVEST: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna harvest for %1.0f secs\n", netname, t); + break; + } +#endif + case LTG_DEFENDKEYAREA: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna defend a key area for %1.0f secs\n", netname, t); + break; + } + case LTG_GETITEM: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna get an item for %1.0f secs\n", netname, t); + break; + } + case LTG_KILL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna kill someone for %1.0f secs\n", netname, t); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna camp for %1.0f secs\n", netname, t); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna patrol for %1.0f secs\n", netname, t); + break; + } + default: + { + if (bs->ctfroam_time > FloatTime()) { + t = bs->ctfroam_time - FloatTime(); + BotAI_Print(PRT_MESSAGE, "%s: I'm gonna roam for %1.0f secs\n", netname, t); + } + else { + BotAI_Print(PRT_MESSAGE, "%s: I've got a regular goal\n", netname); + } + } + } +} +#endif //DEBUG + +/* +================== +BotGetItemTeamGoal + +FIXME: add stuff like "upper rocket launcher" +"the rl near the railgun", "lower grenade launcher" etc. +================== +*/ +int BotGetItemTeamGoal(char *goalname, bot_goal_t *goal) { + int i; + + if (!strlen(goalname)) return qfalse; + i = -1; + do { + i = trap_BotGetLevelItemGoal(i, goalname, goal); + if (i > 0) { + //do NOT defend dropped items + if (goal->flags & GFL_DROPPED) + continue; + return qtrue; + } + } while(i > 0); + return qfalse; +} + +/* +================== +BotGetMessageTeamGoal +================== +*/ +int BotGetMessageTeamGoal(bot_state_t *bs, char *goalname, bot_goal_t *goal) { + bot_waypoint_t *cp; + + if (BotGetItemTeamGoal(goalname, goal)) return qtrue; + + cp = BotFindWayPoint(bs->checkpoints, goalname); + if (cp) { + memcpy(goal, &cp->goal, sizeof(bot_goal_t)); + return qtrue; + } + return qfalse; +} + +/* +================== +BotGetTime +================== +*/ +float BotGetTime(bot_match_t *match) { + bot_match_t timematch; + char timestring[MAX_MESSAGE_SIZE]; + float t; + + //if the matched string has a time + if (match->subtype & ST_TIME) { + //get the time string + trap_BotMatchVariable(match, TIME, timestring, MAX_MESSAGE_SIZE); + //match it to find out if the time is in seconds or minutes + if (trap_BotFindMatch(timestring, &timematch, MTCONTEXT_TIME)) { + if (timematch.type == MSG_FOREVER) { + t = 99999999.0f; + } + else if (timematch.type == MSG_FORAWHILE) { + t = 10 * 60; // 10 minutes + } + else if (timematch.type == MSG_FORALONGTIME) { + t = 30 * 60; // 30 minutes + } + else { + trap_BotMatchVariable(&timematch, TIME, timestring, MAX_MESSAGE_SIZE); + if (timematch.type == MSG_MINUTES) t = atof(timestring) * 60; + else if (timematch.type == MSG_SECONDS) t = atof(timestring); + else t = 0; + } + //if there's a valid time + if (t > 0) return FloatTime() + t; + } + } + return 0; +} + +/* +================== +FindClientByName +================== +*/ +int FindClientByName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +FindEnemyByName +================== +*/ +int FindEnemyByName(bot_state_t *bs, char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (!Q_stricmp(buf, name)) return i; + } + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (BotSameTeam(bs, i)) continue; + ClientName(i, buf, sizeof(buf)); + if (stristr(buf, name)) return i; + } + return -1; +} + +/* +================== +NumPlayersOnSameTeam +================== +*/ +int NumPlayersOnSameTeam(bot_state_t *bs) { + int i, num; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + + num = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, MAX_INFO_STRING); + if (strlen(buf)) { + if (BotSameTeam(bs, i+1)) num++; + } + } + return num; +} + +/* +================== +TeamPlayIsOn +================== +*/ +int BotGetPatrolWaypoints(bot_state_t *bs, bot_match_t *match) { + char keyarea[MAX_MESSAGE_SIZE]; + int patrolflags; + bot_waypoint_t *wp, *newwp, *newpatrolpoints; + bot_match_t keyareamatch; + bot_goal_t goal; + + newpatrolpoints = NULL; + patrolflags = 0; + // + trap_BotMatchVariable(match, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + // + while(1) { + if (!trap_BotFindMatch(keyarea, &keyareamatch, MTCONTEXT_PATROLKEYAREA)) { + trap_EA_SayTeam(bs->client, "what do you say?"); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + trap_BotMatchVariable(&keyareamatch, KEYAREA, keyarea, MAX_MESSAGE_SIZE); + if (!BotGetMessageTeamGoal(bs, keyarea, &goal)) { + //BotAI_BotInitialChat(bs, "cannotfind", keyarea, NULL); + //trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotFreeWaypoints(newpatrolpoints); + bs->patrolpoints = NULL; + return qfalse; + } + //create a new waypoint + newwp = BotCreateWayPoint(keyarea, goal.origin, goal.areanum); + if (!newwp) + break; + //add the waypoint to the patrol points + newwp->next = NULL; + for (wp = newpatrolpoints; wp && wp->next; wp = wp->next); + if (!wp) { + newpatrolpoints = newwp; + newwp->prev = NULL; + } + else { + wp->next = newwp; + newwp->prev = wp; + } + // + if (keyareamatch.subtype & ST_BACK) { + patrolflags = PATROL_LOOP; + break; + } + else if (keyareamatch.subtype & ST_REVERSE) { + patrolflags = PATROL_REVERSE; + break; + } + else if (keyareamatch.subtype & ST_MORE) { + trap_BotMatchVariable(&keyareamatch, MORE, keyarea, MAX_MESSAGE_SIZE); + } + else { + break; + } + } + // + if (!newpatrolpoints || !newpatrolpoints->next) { + trap_EA_SayTeam(bs->client, "I need more key points to patrol\n"); + BotFreeWaypoints(newpatrolpoints); + newpatrolpoints = NULL; + return qfalse; + } + // + BotFreeWaypoints(bs->patrolpoints); + bs->patrolpoints = newpatrolpoints; + // + bs->curpatrolpoint = bs->patrolpoints; + bs->patrolflags = patrolflags; + // + return qtrue; +} + +/* +================== +BotAddressedToBot +================== +*/ +int BotAddressedToBot(bot_state_t *bs, bot_match_t *match) { + char addressedto[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char name[MAX_MESSAGE_SIZE]; + char botname[128]; + int client; + bot_match_t addresseematch; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientOnSameTeamFromName(bs, netname); + if (client < 0) return qfalse; + //if the message is addressed to someone + if (match->subtype & ST_ADDRESSED) { + trap_BotMatchVariable(match, ADDRESSEE, addressedto, sizeof(addressedto)); + //the name of this bot + ClientName(bs->client, botname, 128); + // + while(trap_BotFindMatch(addressedto, &addresseematch, MTCONTEXT_ADDRESSEE)) { + if (addresseematch.type == MSG_EVERYONE) { + return qtrue; + } + else if (addresseematch.type == MSG_MULTIPLENAMES) { + trap_BotMatchVariable(&addresseematch, TEAMMATE, name, sizeof(name)); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + trap_BotMatchVariable(&addresseematch, MORE, addressedto, MAX_MESSAGE_SIZE); + } + else { + trap_BotMatchVariable(&addresseematch, TEAMMATE, name, MAX_MESSAGE_SIZE); + if (strlen(name)) { + if (stristr(botname, name)) return qtrue; + if (stristr(bs->subteam, name)) return qtrue; + } + break; + } + } + //Com_sprintf(buf, sizeof(buf), "not addressed to me but %s", addressedto); + //trap_EA_Say(bs->client, buf); + return qfalse; + } + else { + bot_match_t tellmatch; + + tellmatch.type = 0; + //if this message wasn't directed solely to this bot + if (!trap_BotFindMatch(match->string, &tellmatch, MTCONTEXT_REPLYCHAT) || + tellmatch.type != MSG_CHATTELL) { + //make sure not everyone reacts to this message + if (random() > (float ) 1.0 / (NumPlayersOnSameTeam(bs)-1)) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotGPSToPosition +================== +*/ +int BotGPSToPosition(char *buf, vec3_t position) { + int i, j = 0; + int num, sign; + + for (i = 0; i < 3; i++) { + num = 0; + while(buf[j] == ' ') j++; + if (buf[j] == '-') { + j++; + sign = -1; + } + else { + sign = 1; + } + while (buf[j]) { + if (buf[j] >= '0' && buf[j] <= '9') { + num = num * 10 + buf[j] - '0'; + j++; + } + else { + j++; + break; + } + } + BotAI_Print(PRT_MESSAGE, "%d\n", sign * num); + position[i] = (float) sign * num; + } + return qtrue; +} + +/* +================== +BotMatch_HelpAccompany +================== +*/ +void BotMatch_HelpAccompany(bot_state_t *bs, bot_match_t *match) { + int client, other, areanum; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + bot_match_t teammatematch; + aas_entityinfo_t entinfo; + + //PKMOD - Ergodic 04/02/02 - check if bot is a Private Bot + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + //get message originator + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //PKMOD - Ergodic 04/02/02 - check if message is from Private bot's owner + if ( strcmp( bs->teamleader, netname) ) + //don't do anything if command is not coming from the teamleader + return; + } + else { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the netname + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + } + //get the team mate name + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //get the client to help + if (trap_BotFindMatch(teammate, &teammatematch, MTCONTEXT_TEAMMATE) && + //if someone asks for him or herself + teammatematch.type == MSG_ME) { + client = ClientFromName(netname); + other = qfalse; + } + else { + //asked for someone else + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + if (other) BotAI_BotInitialChat(bs, "whois", teammate, NULL); + else BotAI_BotInitialChat(bs, "whois", netname, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + //don't help or accompany yourself + if (client == bs->client) { + return; + } + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) {// && trap_AAS_AreaReachability(areanum)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //if no teamgoal yet + if (bs->teamgoal.entitynum < 0) { + //if near an item + if (match->subtype & ST_NEARITEM) { + //get the match variable + trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + } + } + // + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TEAM); + return; + } + //the team mate + bs->teammate = client; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = ClientFromName(netname); + //the team mate who ordered + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //last time the team mate was assumed visible + bs->teammatevisible_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the ltg type + if (match->type == MSG_HELP) { + bs->ltgtype = LTG_TEAMHELP; + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_HELP_TIME; + } + else { + bs->ltgtype = LTG_TEAMACCOMPANY; + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_DefendKeyArea +================== +*/ +void BotMatch_DefendKeyArea(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the match variable + trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = ClientFromName(netname); + //the team mate who ordered + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + //away from defending + bs->defendaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetItem +================== +*/ +void BotMatch_GetItem(bot_state_t *bs, bot_match_t *match) { + char itemname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + +//PKMOD - Ergodic 04/02/02 - check if bot is a Private Bot + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + //get message originator + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //PKMOD - Ergodic 04/02/02 - check if message is from Private bot's owner + if ( strcmp( bs->teamleader, netname) ) + //don't do anything if command is not coming from the teamleader + return; + } + else { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + } + //get the match variable + trap_BotMatchVariable(match, ITEM, itemname, sizeof(itemname)); + // + if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + client = ClientOnSameTeamFromName(bs, netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETITEM; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_GETITEM_TIME; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Camp +================== +*/ +void BotMatch_Camp(bot_state_t *bs, bot_match_t *match) { + int client, areanum; + char netname[MAX_MESSAGE_SIZE]; + char itemname[MAX_MESSAGE_SIZE]; + aas_entityinfo_t entinfo; + +//PKMOD - Ergodic 04/02/02 - check if bot is a Private Bot + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + //get message originator + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //PKMOD - Ergodic 04/02/02 - check if message is from Private bot's owner + if ( strcmp( bs->teamleader, netname) ) + //don't do anything if command is not coming from the teamleader + return; + } + else { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + } + + //asked for someone else + client = FindClientByName(netname); + //if there's no valid client with this name + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + //get the match variable + trap_BotMatchVariable(match, KEYAREA, itemname, sizeof(itemname)); + //in CTF it could be the base + if (match->subtype & ST_THERE) { + //camp at the spot the bot is currently standing + bs->teamgoal.entitynum = bs->entitynum; + bs->teamgoal.areanum = bs->areanum; + VectorCopy(bs->origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + else if (match->subtype & ST_HERE) { + //if this is the bot self + if (client == bs->client) return; + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) {// && trap_AAS_AreaReachability(areanum)) { + //NOTE: just assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + //} + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + } + else if (!BotGetMessageTeamGoal(bs, itemname, &bs->teamgoal)) { + //BotAI_BotInitialChat(bs, "cannotfind", itemname, NULL); + //client = ClientFromName(netname); + //trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; + //not arrived yet + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_Patrol +================== +*/ +void BotMatch_Patrol(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + +//PKMOD - Ergodic 04/02/02 - check if bot is a Private Bot + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + //get message originator + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //PKMOD - Ergodic 04/02/02 - check if message is from Private bot's owner + if ( strcmp( bs->teamleader, netname) ) + //don't do anything if command is not coming from the teamleader + return; + } + else { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + } + + //get the patrol waypoints + if (!BotGetPatrolWaypoints(bs, match)) return; + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_PATROL; + //get the team goal time + bs->teamgoal_time = BotGetTime(match); + //set the team goal time if not set already + if (!bs->teamgoal_time) bs->teamgoal_time = FloatTime() + TEAM_PATROL_TIME; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_GetFlag +================== +*/ +void BotMatch_GetFlag(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + // get an alternate route in ctf + if (gametype == GT_CTF) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_AttackEnemyBase +================== +*/ +void BotMatch_AttackEnemyBase(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + BotMatch_GetFlag(bs, match); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF || gametype == GT_OBELISK || gametype == GT_HARVESTER) { + if (!redobelisk.areanum || !blueobelisk.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + bs->attackaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +#ifdef MISSIONPACK +/* +================== +BotMatch_Harvest +================== +*/ +void BotMatch_Harvest(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_HARVESTER) { + if (!neutralobelisk.areanum || !redobelisk.areanum || !blueobelisk.areanum) + return; + } + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} +#endif + +/* +================== +BotMatch_RushBase +================== +*/ +void BotMatch_RushBase(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF || gametype == GT_HARVESTER) { + if (!redobelisk.areanum || !blueobelisk.areanum) + return; + } +#endif + else { + return; + } + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RUSHBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_TaskPreference +================== +*/ +void BotMatch_TaskPreference(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + char teammatename[MAX_MESSAGE_SIZE]; + int teammate, preference; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + + trap_BotMatchVariable(match, NETNAME, teammatename, sizeof(teammatename)); + teammate = ClientFromName(teammatename); + if (teammate < 0) return; + + preference = BotGetTeamMateTaskPreference(bs, teammate); + switch(match->subtype) + { + case ST_DEFENDER: + { + preference &= ~TEAMTP_ATTACKER; + preference |= TEAMTP_DEFENDER; + break; + } + case ST_ATTACKER: + { + preference &= ~TEAMTP_DEFENDER; + preference |= TEAMTP_ATTACKER; + break; + } + case ST_ROAMER: + { + preference &= ~(TEAMTP_ATTACKER|TEAMTP_DEFENDER); + break; + } + } + BotSetTeamMateTaskPreference(bs, teammate, preference); + // + EasyClientName(teammate, teammatename, sizeof(teammatename)); + BotAI_BotInitialChat(bs, "keepinmind", teammatename, NULL); + trap_BotEnterChat(bs->cs, teammate, CHAT_TELL); + BotVoiceChatOnly(bs, teammate, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotMatch_ReturnFlag +================== +*/ +void BotMatch_ReturnFlag(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + //if not in CTF mode + if ( + gametype != GT_CTF +#ifdef MISSIONPACK + && gametype != GT_1FCTF +#endif + ) + return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) + return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + // + client = FindClientByName(netname); + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_JoinSubteam +================== +*/ +void BotMatch_JoinSubteam(bot_state_t *bs, bot_match_t *match) { + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //get the sub team name + trap_BotMatchVariable(match, TEAMNAME, teammate, sizeof(teammate)); + //set the sub team name + strncpy(bs->subteam, teammate, 32); + bs->subteam[31] = '\0'; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "joinedteam", teammate, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_LeaveSubteam(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) + { + BotAI_BotInitialChat(bs, "leftteam", bs->subteam, NULL); + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } //end if + strcpy(bs->subteam, ""); +} + +/* +================== +BotMatch_LeaveSubteam +================== +*/ +void BotMatch_WhichTeam(bot_state_t *bs, bot_match_t *match) { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + if (strlen(bs->subteam)) { + BotAI_BotInitialChat(bs, "inteam", bs->subteam, NULL); + } + else { + BotAI_BotInitialChat(bs, "noteam", NULL); + } + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); +} + +/* +================== +BotMatch_CheckPoint +================== +*/ +void BotMatch_CheckPoint(bot_state_t *bs, bot_match_t *match) { + int areanum, client; + char buf[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + vec3_t position; + bot_waypoint_t *cp; + + if (!TeamPlayIsOn()) return; + // + trap_BotMatchVariable(match, POSITION, buf, MAX_MESSAGE_SIZE); + VectorClear(position); + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + //BotGPSToPosition(buf, position); + sscanf(buf, "%f %f %f", &position[0], &position[1], &position[2]); + position[2] += 0.5; + areanum = BotPointAreaNum(position); + if (!areanum) { + if (BotAddressedToBot(bs, match)) { + BotAI_BotInitialChat(bs, "checkpoint_invalid", NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } + return; + } + // + trap_BotMatchVariable(match, NAME, buf, MAX_MESSAGE_SIZE); + //check if there already exists a checkpoint with this name + cp = BotFindWayPoint(bs->checkpoints, buf); + if (cp) { + if (cp->next) cp->next->prev = cp->prev; + if (cp->prev) cp->prev->next = cp->next; + else bs->checkpoints = cp->next; + cp->inuse = qfalse; + } + //create a new check point + cp = BotCreateWayPoint(buf, position, areanum); + //add the check point to the bot's known chech points + cp->next = bs->checkpoints; + if (bs->checkpoints) bs->checkpoints->prev = cp; + bs->checkpoints = cp; + // + if (BotAddressedToBot(bs, match)) { + Com_sprintf(buf, sizeof(buf), "%1.0f %1.0f %1.0f", cp->goal.origin[0], + cp->goal.origin[1], + cp->goal.origin[2]); + + BotAI_BotInitialChat(bs, "checkpoint_confirm", cp->name, buf, NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } +} + +/* +================== +BotMatch_FormationSpace +================== +*/ +void BotMatch_FormationSpace(bot_state_t *bs, bot_match_t *match) { + char buf[MAX_MESSAGE_SIZE]; + float space; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NUMBER, buf, MAX_MESSAGE_SIZE); + //if it's the distance in feet + if (match->subtype & ST_FEET) space = 0.3048 * 32 * atof(buf); + //else it's in meters + else space = 32 * atof(buf); + //check if the formation intervening space is valid + if (space < 48 || space > 500) space = 100; + bs->formation_dist = space; +} + +/* +================== +BotMatch_Dismiss +================== +*/ +void BotMatch_Dismiss(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + +//PKMOD - Ergodic 04/02/02 - check if bot is a Private Bot + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + //get message originator + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //PKMOD - Ergodic 04/02/02 - check if message is from Private bot's owner + if ( strcmp( bs->teamleader, netname) ) + //don't do anything if command is not coming from the teamleader + return; + } + else { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + } + client = ClientFromName(netname); + // + bs->decisionmaker = client; + // + bs->ltgtype = 0; + bs->lead_time = 0; + bs->lastgoal_ltgtype = 0; + // + BotAI_BotInitialChat(bs, "dismissed", NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_Suicide +================== +*/ +void BotMatch_Suicide(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + int client; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_EA_Command(bs->client, "kill"); + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + // + BotVoiceChat(bs, client, VOICECHAT_TAUNT); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotMatch_StartTeamLeaderShip +================== +*/ +void BotMatch_StartTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //if chats for him or herself + if (match->subtype & ST_I) { + //get the team mate that will be the team leader + trap_BotMatchVariable(match, NETNAME, teammate, sizeof(teammate)); + strncpy(bs->teamleader, teammate, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader)] = '\0'; + } + //chats for someone else + else { + //get the team mate that will be the team leader + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + if (client >= 0) ClientName(client, bs->teamleader, sizeof(bs->teamleader)); + } +} + +/* +================== +BotMatch_StopTeamLeaderShip +================== +*/ +void BotMatch_StopTeamLeaderShip(bot_state_t *bs, bot_match_t *match) { + int client; + char teammate[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + //get the team mate that stops being the team leader + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + //if chats for him or herself + if (match->subtype & ST_I) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + } + //chats for someone else + else { + client = FindClientByName(teammate); + } //end else + if (client >= 0) { + if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { + bs->teamleader[0] = '\0'; + notleader[client] = qtrue; + } + } +} + +/* +================== +BotMatch_WhoIsTeamLeader +================== +*/ +void BotMatch_WhoIsTeamLeader(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + + ClientName(bs->client, netname, sizeof(netname)); + //if this bot IS the team leader + if (!Q_stricmp(netname, bs->teamleader)) { + trap_EA_SayTeam(bs->client, "I'm the team leader\n"); + } +} + +/* +================== +BotMatch_WhatAreYouDoing +================== +*/ +void BotMatch_WhatAreYouDoing(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_MESSAGE_SIZE]; + char goalname[MAX_MESSAGE_SIZE]; + int client; + +//PKMOD - Ergodic 04/02/02 - check if bot is a Private Bot + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + //get message originator + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //PKMOD - Ergodic 04/02/02 - check if message is from Private bot's owner + if ( strcmp( bs->teamleader, netname) ) + //don't do anything if command is not coming from the teamleader + return; + } + else { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + } + // + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "helping", netname, NULL); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "accompanying", netname, NULL); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "defending", goalname, NULL); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_BotInitialChat(bs, "gettingitem", goalname, NULL); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "killing", netname, NULL); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_BotInitialChat(bs, "camping", NULL); + break; + } + case LTG_PATROL: + { + BotAI_BotInitialChat(bs, "patrolling", NULL); + break; + } + case LTG_GETFLAG: + { + BotAI_BotInitialChat(bs, "capturingflag", NULL); + break; + } + case LTG_RUSHBASE: + { + BotAI_BotInitialChat(bs, "rushingbase", NULL); + break; + } + case LTG_RETURNFLAG: + { + BotAI_BotInitialChat(bs, "returningflag", NULL); + break; + } +#ifdef MISSIONPACK + case LTG_ATTACKENEMYBASE: + { + BotAI_BotInitialChat(bs, "attackingenemybase", NULL); + break; + } + case LTG_HARVEST: + { + BotAI_BotInitialChat(bs, "harvesting", NULL); + break; + } +#endif + default: + { + BotAI_BotInitialChat(bs, "roaming", NULL); + break; + } + } + //chat what the bot is doing + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); +} + +/* +================== +BotMatch_WhatIsMyCommand +================== +*/ +void BotMatch_WhatIsMyCommand(bot_state_t *bs, bot_match_t *match) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + bs->forceorders = qtrue; +} + +/* +================== +BotNearestVisibleItem +================== +*/ +float BotNearestVisibleItem(bot_state_t *bs, char *itemname, bot_goal_t *goal) { + int i; + char name[64]; + bot_goal_t tmpgoal; + float dist, bestdist; + vec3_t dir; + bsp_trace_t trace; + + bestdist = 999999; + i = -1; + do { + i = trap_BotGetLevelItemGoal(i, itemname, &tmpgoal); + trap_BotGoalName(tmpgoal.number, name, sizeof(name)); + if (Q_stricmp(itemname, name) != 0) + continue; + VectorSubtract(tmpgoal.origin, bs->origin, dir); + dist = VectorLength(dir); + if (dist < bestdist) { + //trace from start to end + BotAI_Trace(&trace, bs->eye, NULL, NULL, tmpgoal.origin, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction >= 1.0) { + bestdist = dist; + memcpy(goal, &tmpgoal, sizeof(bot_goal_t)); + } + } + } while(i > 0); + return bestdist; +} + +/* +================== +BotMatch_WhereAreYou +================== +*/ +void BotMatch_WhereAreYou(bot_state_t *bs, bot_match_t *match) { + float dist, bestdist; + int i, bestitem, redtt, bluett, client; + bot_goal_t goal; + char netname[MAX_MESSAGE_SIZE]; + char *nearbyitems[] = { +//PKMOD - Ergodic 03/22/01 - change shotgun name to boomstick + "Boomstick", + "Grenade Launcher", + "Rocket Launcher", + "Plasmagun", +//PKMOD - Ergodic 07/12/01 - change railgun name to magnum + "Magnum", +//PKMOD - Ergodic 12/19/03 - change Lightning Gun name to Chain Lightning Gun + "Chain Lightning Gun", + "BFG10K", + "Quad Damage", + "Regeneration", + "Battle Suit", + "Speed", + "Invisibility", + "Flight", + "Armor", + "Heavy Armor", + "Red Flag", + "Blue Flag", +//PKMOD - Ergodic 04/02/02 - add more PKA items + "Personal Sentry", + "Radiate", + "Private Bot (Legs)", + "Private Bot (Torso)", + "Private Bot (Head)", + "Gravity Well", + "Air Fist", + "Nail Gun", + "Dragon", +#ifdef MISSIONPACK + "Nailgun", + "Prox Launcher", + "Chaingun", + "Scout", + "Guard", + "Doubler", + "Ammo Regen", + "Neutral Flag", + "Red Obelisk", + "Blue Obelisk", + "Neutral Obelisk", +#endif + NULL + }; + // +//PKMOD - Ergodic 04/02/02 - check if bot is a Private Bot + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + //get message originator + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //PKMOD - Ergodic 04/02/02 - check if message is from Private bot's owner + if ( strcmp( bs->teamleader, netname) ) + //don't do anything if command is not coming from the teamleader + return; + } + else { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + } + + bestitem = -1; + bestdist = 999999; + for (i = 0; nearbyitems[i]; i++) { + dist = BotNearestVisibleItem(bs, nearbyitems[i], &goal); + if (dist < bestdist) { + bestdist = dist; + bestitem = i; + } + } + if (bestitem != -1) { + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_redflag.areanum, TFL_DEFAULT); + bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, ctf_blueflag.areanum, TFL_DEFAULT); + if (redtt < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); + } + else if (bluett < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_OBELISK || gametype == GT_HARVESTER) { + redtt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, redobelisk.areanum, TFL_DEFAULT); + bluett = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, blueobelisk.areanum, TFL_DEFAULT); + if (redtt < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "red", NULL); + } + else if (bluett < (redtt + bluett) * 0.4) { + BotAI_BotInitialChat(bs, "teamlocation", nearbyitems[bestitem], "blue", NULL); + } + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + } +#endif + else { + BotAI_BotInitialChat(bs, "location", nearbyitems[bestitem], NULL); + } + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + } +} + +/* +================== +BotMatch_LeadTheWay +================== +*/ +void BotMatch_LeadTheWay(bot_state_t *bs, bot_match_t *match) { + aas_entityinfo_t entinfo; + char netname[MAX_MESSAGE_SIZE], teammate[MAX_MESSAGE_SIZE]; + int client, areanum, other; + + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + //if someone asks for someone else + if (match->subtype & ST_SOMEONE) { + //get the team mate name + trap_BotMatchVariable(match, TEAMMATE, teammate, sizeof(teammate)); + client = FindClientByName(teammate); + //if this is the bot self + if (client == bs->client) { + other = qfalse; + } + else if (!BotSameTeam(bs, client)) { + //FIXME: say "I don't help the enemy" + return; + } + else { + other = qtrue; + } + } + else { + //get the netname + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = ClientFromName(netname); + other = qfalse; + } + //if the bot doesn't know who to help (FindClientByName returned -1) + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + // + bs->lead_teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && trap_AAS_AreaReachability(areanum)) { + bs->lead_teamgoal.entitynum = client; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + + if (bs->teamgoal.entitynum < 0) { + if (other) BotAI_BotInitialChat(bs, "whereis", teammate, NULL); + else BotAI_BotInitialChat(bs, "whereareyou", netname, NULL); + trap_BotEnterChat(bs->cs, bs->client, CHAT_TEAM); + return; + } + bs->lead_teammate = client; + bs->lead_time = FloatTime() + TEAM_LEAD_TIME; + bs->leadvisible_time = 0; + bs->leadmessage_time = -(FloatTime() + 2 * random()); +} + +/* +================== +BotMatch_Kill +================== +*/ +void BotMatch_Kill(bot_state_t *bs, bot_match_t *match) { + char enemy[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + int client; + +//PKMOD - Ergodic 04/02/02 - check if bot is a Private Bot + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + //get message originator + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + //PKMOD - Ergodic 04/02/02 - check if message is from Private bot's owner + if ( strcmp( bs->teamleader, netname) ) + //don't do anything if command is not coming from the teamleader + return; + } + else { + if (!TeamPlayIsOn()) return; + //if not addressed to this bot + if (!BotAddressedToBot(bs, match)) return; + // + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + } + + trap_BotMatchVariable(match, ENEMY, enemy, sizeof(enemy)); + // + client = FindEnemyByName(bs, enemy); + if (client < 0) { + BotAI_BotInitialChat(bs, "whois", enemy, NULL); + client = ClientFromName(netname); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + bs->teamgoal.entitynum = client; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_KILL; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_KILL_SOMEONE; + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotMatch_CTF +================== +*/ +void BotMatch_CTF(bot_state_t *bs, bot_match_t *match) { + + char flag[128], netname[MAX_NETNAME]; + + if (gametype == GT_CTF) { + trap_BotMatchVariable(match, FLAG, flag, sizeof(flag)); + if (match->subtype & ST_GOTFLAG) { + if (!Q_stricmp(flag, "red")) { + bs->redflagstatus = 1; + if (BotTeam(bs) == TEAM_BLUE) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + else { + bs->blueflagstatus = 1; + if (BotTeam(bs) == TEAM_RED) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } + bs->flagstatuschanged = 1; + bs->lastflagcapture_time = FloatTime(); + } + else if (match->subtype & ST_CAPTUREDFLAG) { + bs->redflagstatus = 0; + bs->blueflagstatus = 0; + bs->flagcarrier = 0; + bs->flagstatuschanged = 1; + } + else if (match->subtype & ST_RETURNEDFLAG) { + if (!Q_stricmp(flag, "red")) bs->redflagstatus = 0; + else bs->blueflagstatus = 0; + bs->flagstatuschanged = 1; + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (match->subtype & ST_1FCTFGOTFLAG) { + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + bs->flagcarrier = ClientFromName(netname); + } + } +#endif +} + +void BotMatch_EnterGame(bot_state_t *bs, bot_match_t *match) { + int client; + char netname[MAX_NETNAME]; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + if (client >= 0) { + notleader[client] = qfalse; + } + //NOTE: eliza chats will catch this + //Com_sprintf(buf, sizeof(buf), "heya %s", netname); + //EA_Say(bs->client, buf); +} + +void BotMatch_NewLeader(bot_state_t *bs, bot_match_t *match) { + int client; + char netname[MAX_NETNAME]; + + trap_BotMatchVariable(match, NETNAME, netname, sizeof(netname)); + client = FindClientByName(netname); + if (!BotSameTeam(bs, client)) + return; + Q_strncpyz(bs->teamleader, netname, sizeof(bs->teamleader)); +} + +/* +================== +BotMatchMessage +================== +*/ +int BotMatchMessage(bot_state_t *bs, char *message) { + bot_match_t match; + + match.type = 0; + //if it is an unknown message + if (!trap_BotFindMatch(message, &match, MTCONTEXT_MISC + |MTCONTEXT_INITIALTEAMCHAT + |MTCONTEXT_CTF)) { + return qfalse; + } + + //react to the found message + switch(match.type) + { + case MSG_HELP: //someone calling for help + case MSG_ACCOMPANY: //someone calling for company + { + BotMatch_HelpAccompany(bs, &match); + break; + } + case MSG_DEFENDKEYAREA: //teamplay defend a key area + { + BotMatch_DefendKeyArea(bs, &match); + break; + } + case MSG_CAMP: //camp somewhere + { + BotMatch_Camp(bs, &match); + break; + } + case MSG_PATROL: //patrol between several key areas + { + BotMatch_Patrol(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_GETFLAG: //ctf get the enemy flag + { + BotMatch_GetFlag(bs, &match); + break; + } +#ifdef MISSIONPACK + //CTF & 1FCTF & Obelisk & Harvester + case MSG_ATTACKENEMYBASE: + { + BotMatch_AttackEnemyBase(bs, &match); + break; + } + //Harvester + case MSG_HARVEST: + { + BotMatch_Harvest(bs, &match); + break; + } +#endif + //CTF & 1FCTF & Harvester + case MSG_RUSHBASE: //ctf rush to the base + { + BotMatch_RushBase(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_RETURNFLAG: + { + BotMatch_ReturnFlag(bs, &match); + break; + } + //CTF & 1FCTF & Obelisk & Harvester + case MSG_TASKPREFERENCE: + { + BotMatch_TaskPreference(bs, &match); + break; + } + //CTF & 1FCTF + case MSG_CTF: + { + BotMatch_CTF(bs, &match); + break; + } + case MSG_GETITEM: + { + BotMatch_GetItem(bs, &match); + break; + } + case MSG_JOINSUBTEAM: //join a sub team + { + BotMatch_JoinSubteam(bs, &match); + break; + } + case MSG_LEAVESUBTEAM: //leave a sub team + { + BotMatch_LeaveSubteam(bs, &match); + break; + } + case MSG_WHICHTEAM: + { + BotMatch_WhichTeam(bs, &match); + break; + } + case MSG_CHECKPOINT: //remember a check point + { + BotMatch_CheckPoint(bs, &match); + break; + } + case MSG_CREATENEWFORMATION: //start the creation of a new formation + { + trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONPOSITION: //tell someone his/her position in the formation + { + trap_EA_SayTeam(bs->client, "the part of my brain to create formations has been damaged"); + break; + } + case MSG_FORMATIONSPACE: //set the formation space + { + BotMatch_FormationSpace(bs, &match); + break; + } + case MSG_DOFORMATION: //form a certain formation + { + break; + } + case MSG_DISMISS: //dismiss someone + { + BotMatch_Dismiss(bs, &match); + break; + } + case MSG_STARTTEAMLEADERSHIP: //someone will become the team leader + { + BotMatch_StartTeamLeaderShip(bs, &match); + break; + } + case MSG_STOPTEAMLEADERSHIP: //someone will stop being the team leader + { + BotMatch_StopTeamLeaderShip(bs, &match); + break; + } + case MSG_WHOISTEAMLAEDER: + { + BotMatch_WhoIsTeamLeader(bs, &match); + break; + } + case MSG_WHATAREYOUDOING: //ask a bot what he/she is doing + { + BotMatch_WhatAreYouDoing(bs, &match); + break; + } + case MSG_WHATISMYCOMMAND: + { + BotMatch_WhatIsMyCommand(bs, &match); + break; + } + case MSG_WHEREAREYOU: + { + BotMatch_WhereAreYou(bs, &match); + break; + } + case MSG_LEADTHEWAY: + { + BotMatch_LeadTheWay(bs, &match); + break; + } + case MSG_KILL: + { + BotMatch_Kill(bs, &match); + break; + } + case MSG_ENTERGAME: //someone entered the game + { + BotMatch_EnterGame(bs, &match); + break; + } + case MSG_NEWLEADER: + { + BotMatch_NewLeader(bs, &match); + break; + } + case MSG_WAIT: + { + break; + } + case MSG_SUICIDE: + { + BotMatch_Suicide(bs, &match); + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "unknown match type\n"); + break; + } + } + return qtrue; +} diff --git a/quake3/source/code/game/ai_cmd.h b/quake3/source/code/game/ai_cmd.h new file mode 100644 index 0000000..1689e30 --- /dev/null +++ b/quake3/source/code/game/ai_cmd.h @@ -0,0 +1,17 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_cmd.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +extern int notleader[MAX_CLIENTS]; + +int BotMatchMessage(bot_state_t *bs, char *message); +void BotPrintTeamGoal(bot_state_t *bs); + diff --git a/quake3/source/code/game/ai_dmnet.c b/quake3/source/code/game/ai_dmnet.c new file mode 100644 index 0000000..6d0f60a --- /dev/null +++ b/quake3/source/code/game/ai_dmnet.c @@ -0,0 +1,2586 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmnet.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_dmnet.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +//data file headers +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" + +//goal flag, see be_ai_goal.h for the other GFL_* +#define GFL_AIR 128 + +int numnodeswitches; +char nodeswitch[MAX_NODESWITCHES+1][144]; + +#define LOOKAHEAD_DISTANCE 300 + +/* +================== +BotResetNodeSwitches +================== +*/ +void BotResetNodeSwitches(void) { + numnodeswitches = 0; +} + +/* +================== +BotDumpNodeSwitches +================== +*/ +void BotDumpNodeSwitches(bot_state_t *bs) { + int i; + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s at %1.1f switched more than %d AI nodes\n", netname, FloatTime(), MAX_NODESWITCHES); + for (i = 0; i < numnodeswitches; i++) { + BotAI_Print(PRT_MESSAGE, nodeswitch[i]); + } + BotAI_Print(PRT_FATAL, ""); +} + +/* +================== +BotRecordNodeSwitch +================== +*/ +void BotRecordNodeSwitch(bot_state_t *bs, char *node, char *str, char *s) { + char netname[MAX_NETNAME]; + + ClientName(bs->client, netname, sizeof(netname)); + Com_sprintf(nodeswitch[numnodeswitches], 144, "%s at %2.1f entered %s: %s from %s\n", netname, FloatTime(), node, str, s); +#ifdef DEBUG + if (0) { + BotAI_Print(PRT_MESSAGE, nodeswitch[numnodeswitches]); + } +#endif //DEBUG + numnodeswitches++; +} + +/* +================== +BotGetAirGoal +================== +*/ +int BotGetAirGoal(bot_state_t *bs, bot_goal_t *goal) { + bsp_trace_t bsptrace; + vec3_t end, mins = {-15, -15, -2}, maxs = {15, 15, 2}; + int areanum; + + //trace up until we hit solid + VectorCopy(bs->origin, end); + end[2] += 1000; + BotAI_Trace(&bsptrace, bs->origin, mins, maxs, end, bs->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + //trace down until we hit water + VectorCopy(bsptrace.endpos, end); + BotAI_Trace(&bsptrace, end, mins, maxs, bs->origin, bs->entitynum, CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); + //if we found the water surface + if (bsptrace.fraction > 0) { + areanum = BotPointAreaNum(bsptrace.endpos); + if (areanum) { + VectorCopy(bsptrace.endpos, goal->origin); + goal->origin[2] -= 2; + goal->areanum = areanum; + goal->mins[0] = -15; + goal->mins[1] = -15; + goal->mins[2] = -1; + goal->maxs[0] = 15; + goal->maxs[1] = 15; + goal->maxs[2] = 1; + goal->flags = GFL_AIR; + goal->number = 0; + goal->iteminfo = 0; + goal->entitynum = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGoForAir +================== +*/ +int BotGoForAir(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + bot_goal_t goal; + + //if the bot needs air + if (bs->lastair_time < FloatTime() - 6) { + // +#ifdef DEBUG + //BotAI_Print(PRT_MESSAGE, "going for air\n"); +#endif //DEBUG + //if we can find an air goal + if (BotGetAirGoal(bs, &goal)) { + trap_BotPushGoal(bs->gs, &goal); + return qtrue; + } + else { + //get a nearby goal outside the water + while(trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range)) { + trap_BotGetTopGoal(bs->gs, &goal); + //if the goal is not in water + if (!(trap_AAS_PointContents(goal.origin) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA))) { + return qtrue; + } + trap_BotPopGoal(bs->gs); + } + trap_BotResetAvoidGoals(bs->gs); + } + } + return qfalse; +} + +/* +================== +BotNearbyGoal +================== +*/ +int BotNearbyGoal(bot_state_t *bs, int tfl, bot_goal_t *ltg, float range) { + int ret; + + //check if the bot should go for air + if (BotGoForAir(bs, tfl, ltg, range)) return qtrue; + //if the bot is carrying the enemy flag + if (BotCTFCarryingFlag(bs)) { + //if the bot is just a few secs away from the base + if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) < 300) { + //make the range really small + range = 50; + } + } + // + ret = trap_BotChooseNBGItem(bs->gs, bs->origin, bs->inventory, tfl, ltg, range); + /* + if (ret) + { + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, &goal); + trap_BotGoalName(goal.number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new nearby goal %s\n", FloatTime(), buf); + } + */ + return ret; +} + +/* +================== +BotReachedGoal +================== +*/ +int BotReachedGoal(bot_state_t *bs, bot_goal_t *goal) { + if (goal->flags & GFL_ITEM) { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) { + if (!(goal->flags & GFL_DROPPED)) { + trap_BotSetAvoidGoalTime(bs->gs, goal->number, -1); + } + return qtrue; + } + //if the goal isn't there + if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { + /* + float avoidtime; + int t; + + avoidtime = trap_BotAvoidGoalTime(bs->gs, goal->number); + if (avoidtime > 0) { + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal->areanum, bs->tfl); + if ((float) t * 0.009 < avoidtime) + return qtrue; + } + */ + return qtrue; + } + //if in the goal area and below or above the goal and not swimming + if (bs->areanum == goal->areanum) { + if (bs->origin[0] > goal->origin[0] + goal->mins[0] && bs->origin[0] < goal->origin[0] + goal->maxs[0]) { + if (bs->origin[1] > goal->origin[1] + goal->mins[1] && bs->origin[1] < goal->origin[1] + goal->maxs[1]) { + if (!trap_AAS_Swimming(bs->origin)) { + return qtrue; + } + } + } + } + } + else if (goal->flags & GFL_AIR) { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; + //if the bot got air + if (bs->lastair_time > FloatTime() - 1) return qtrue; + } + else { + //if touching the goal + if (trap_BotTouchingGoal(bs->origin, goal)) return qtrue; + } + return qfalse; +} + +/* +================== +BotGetItemLongTermGoal +================== +*/ +int BotGetItemLongTermGoal(bot_state_t *bs, int tfl, bot_goal_t *goal) { + //if the bot has no goal + if (!trap_BotGetTopGoal(bs->gs, goal)) { + //BotAI_Print(PRT_MESSAGE, "no ltg on stack\n"); + bs->ltg_time = 0; + } + //if the bot touches the current goal + else if (BotReachedGoal(bs, goal)) { + BotChooseWeapon(bs); + bs->ltg_time = 0; + } + //if it is time to find a new long term goal + if (bs->ltg_time < FloatTime()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //BotAI_Print(PRT_MESSAGE, "%s: choosing new ltg\n", ClientName(bs->client, netname, sizeof(netname))); + //choose a new goal + //BotAI_Print(PRT_MESSAGE, "%6.1f client %d: BotChooseLTGItem\n", FloatTime(), bs->client); + if (trap_BotChooseLTGItem(bs->gs, bs->origin, bs->inventory, tfl)) { + /* + char buf[128]; + //get the goal at the top of the stack + trap_BotGetTopGoal(bs->gs, goal); + trap_BotGoalName(goal->number, buf, sizeof(buf)); + BotAI_Print(PRT_MESSAGE, "%1.1f: new long term goal %s\n", FloatTime(), buf); + */ + bs->ltg_time = FloatTime() + 20; + } + else {//the bot gets sorta stuck with all the avoid timings, shouldn't happen though + // +#ifdef DEBUG + char netname[128]; + + BotAI_Print(PRT_MESSAGE, "%s: no valid ltg (probably stuck)\n", ClientName(bs->client, netname, sizeof(netname))); +#endif + //trap_BotDumpAvoidGoals(bs->gs); + //reset the avoid goals and the avoid reach + trap_BotResetAvoidGoals(bs->gs); + trap_BotResetAvoidReach(bs->ms); + } + //get the goal at the top of the stack + return trap_BotGetTopGoal(bs->gs, goal); + } + return qtrue; +} + +/* +================== +BotGetLongTermGoal + +we could also create a seperate AI node for every long term goal type +however this saves us a lot of code +================== +*/ +int BotGetLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + vec3_t target, dir, dir2; + char netname[MAX_NETNAME]; + char buf[MAX_MESSAGE_SIZE]; + int areanum; + float croucher; + aas_entityinfo_t entinfo, botinfo; + bot_waypoint_t *wp; + + if (bs->ltgtype == LTG_TEAMHELP && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "help_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //if trying to help the team mate for more than a minute + if (bs->teamgoal_time < FloatTime()) + bs->ltgtype = 0; + //if the team mate IS visible for quite some time + if (bs->teammatevisible_time < FloatTime() - 10) bs->ltgtype = 0; + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //if close just stand still there + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(100)) { + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + } + else { + //last time the bot was NOT visible + bs->teammatevisible_time = FloatTime(); + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + //if the bot accompanies someone + if (bs->ltgtype == LTG_TEAMACCOMPANY && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "accompany_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //if accompanying the companion for 3 minutes + if (bs->teamgoal_time < FloatTime()) { + BotAI_BotInitialChat(bs, "accompany_stop", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->ltgtype = 0; + } + //get entity information of the companion + BotEntityInfo(bs->teammate, &entinfo); + //if the companion is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->teammate)) { + //update visible time + bs->teammatevisible_time = FloatTime(); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(bs->formation_dist)) { + // + // if the client being followed bumps into this bot then + // the bot should back up + BotEntityInfo(bs->entitynum, &botinfo); + // if the followed client is not standing ontop of the bot + if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2]) { + // if the bounding boxes touch each other + if (botinfo.origin[0] + botinfo.maxs[0] > entinfo.origin[0] + entinfo.mins[0] - 4&& + botinfo.origin[0] + botinfo.mins[0] < entinfo.origin[0] + entinfo.maxs[0] + 4) { + if (botinfo.origin[1] + botinfo.maxs[1] > entinfo.origin[1] + entinfo.mins[1] - 4 && + botinfo.origin[1] + botinfo.mins[1] < entinfo.origin[1] + entinfo.maxs[1] + 4) { + if (botinfo.origin[2] + botinfo.maxs[2] > entinfo.origin[2] + entinfo.mins[2] - 4 && + botinfo.origin[2] + botinfo.mins[2] < entinfo.origin[2] + entinfo.maxs[2] + 4) { + // if the followed client looks in the direction of this bot + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors(entinfo.angles, dir, NULL, NULL); + AngleVectorsForward( entinfo.angles, dir ); + dir[2] = 0; + VectorNormalize(dir); + //VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + VectorSubtract(bs->origin, entinfo.origin, dir2); + VectorNormalize(dir2); + if (DotProduct(dir, dir2) > 0.7) { + // back up + BotSetupForMovement(bs); + trap_BotMoveInDirection(bs->ms, dir2, 400, MOVE_WALK); + } + } + } + } + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < FloatTime() - 5) { + croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; + } + } + //don't crouch when swimming + if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; + //if not arrived yet or arived some time ago + if (bs->arrive_time < FloatTime() - 2) { + //if not arrived yet + if (!bs->arrive_time) { + trap_EA_Gesture(bs->client); + BotAI_BotInitialChat(bs, "accompany_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->arrive_time = FloatTime(); + } + //if the bot wants to crouch + else if (bs->attackcrouch_time > FloatTime()) { + trap_EA_Crouch(bs->client); + } + //else do some model taunts + else if (random() < bs->thinktime * 0.05) { + //do a gesture :) + trap_EA_Gesture(bs->client); + } + } + //if just arrived look at the companion + if (bs->arrive_time > FloatTime() - 2) { + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //else look strategically around for enemies + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to go for air + if (BotGoForAir(bs, bs->tfl, &bs->teamgoal, 400)) { + trap_BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + 8; + AIEnter_Seek_NBG(bs, "BotLongTermGoal: go for air"); + return qfalse; + } + // + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + } + //if the entity information is valid (entity in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->teamgoal.entitynum = bs->teammate; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //the goal the bot should go for + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //if the companion is NOT visible for too long + if (bs->teammatevisible_time < FloatTime() - 60) { + BotAI_BotInitialChat(bs, "accompany_cannotfind", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->ltgtype = 0; + // just to make sure the bot won't spam this message + bs->teammatevisible_time = FloatTime(); + } + return qtrue; + } + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) { + if (trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, + bs->teamgoal.areanum, TFL_DEFAULT) > bs->defendaway_range) { + bs->defendaway_time = 0; + } + } + //if defending a key area + if (bs->ltgtype == LTG_DEFENDKEYAREA && !retreat && + bs->defendaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_start", buf, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONDEFENSE); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "defend_stop", buf, NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + bs->ltgtype = 0; + } + //if very close... go away for some time + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(70)) { + trap_BotResetAvoidReach(bs->ms); + bs->defendaway_time = FloatTime() + 3 + 3 * random(); + if (BotHasPersistantPowerupAndWeapon(bs)) { + bs->defendaway_range = 100; + } + else { + bs->defendaway_range = 350; + } + } + return qtrue; + } + //going to kill someone + if (bs->ltgtype == LTG_KILL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->teammessage_time = 0; + } + // + if (bs->lastkilledplayer == bs->teamgoal.entitynum) { + EasyClientName(bs->teamgoal.entitynum, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "kill_done", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->lastkilledplayer = -1; + bs->ltgtype = 0; + } + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } + //get an item + if (bs->ltgtype == LTG_GETITEM && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + //stop after some time + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + // + if (trap_BotItemGoalInVisButNotVisible(bs->entitynum, bs->eye, bs->viewangles, goal)) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_notthere", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + else if (BotReachedGoal(bs, goal)) { + trap_BotGoalName(bs->teamgoal.number, buf, sizeof(buf)); + BotAI_BotInitialChat(bs, "getitem_gotit", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + return qtrue; + } + //if camping somewhere + if ((bs->ltgtype == LTG_CAMP || bs->ltgtype == LTG_CAMPORDER) && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_start", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + } + bs->teammessage_time = 0; + } + //set the bot goal + memcpy(goal, &bs->teamgoal, sizeof(bot_goal_t)); + // + if (bs->teamgoal_time < FloatTime()) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + } + bs->ltgtype = 0; + } + //if really near the camp spot + VectorSubtract(goal->origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(60)) + { + //if not arrived yet + if (!bs->arrive_time) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_arrive", EasyClientName(bs->teammate, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_INPOSITION); + } + bs->arrive_time = FloatTime(); + } + //look strategically around for enemies + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //check if the bot wants to crouch + //don't crouch if crouched less than 5 seconds ago + if (bs->attackcrouch_time < FloatTime() - 5) { + croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + if (random() < bs->thinktime * croucher) { + bs->attackcrouch_time = FloatTime() + 5 + croucher * 15; + } + } + //if the bot wants to crouch + if (bs->attackcrouch_time > FloatTime()) { + trap_EA_Crouch(bs->client); + } + //don't crouch when swimming + if (trap_AAS_Swimming(bs->origin)) bs->attackcrouch_time = FloatTime() - 1; + //make sure the bot is not gonna drown + if (trap_PointContents(bs->eye,bs->entitynum) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + if (bs->ltgtype == LTG_CAMPORDER) { + BotAI_BotInitialChat(bs, "camp_stop", NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + // + if (bs->lastgoal_ltgtype == LTG_CAMPORDER) { + bs->lastgoal_ltgtype = 0; + } + } + bs->ltgtype = 0; + } + // + if (bs->camp_range > 0) { + //FIXME: move around a bit + } + // + trap_BotResetAvoidReach(bs->ms); + return qfalse; + } + return qtrue; + } + //patrolling along several waypoints + if (bs->ltgtype == LTG_PATROL && !retreat) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + strcpy(buf, ""); + for (wp = bs->patrolpoints; wp; wp = wp->next) { + strcat(buf, wp->name); + if (wp->next) strcat(buf, " to "); + } + BotAI_BotInitialChat(bs, "patrol_start", buf, NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + BotVoiceChatOnly(bs, bs->decisionmaker, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); + bs->teammessage_time = 0; + } + // + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + //if the bot touches the current goal + if (trap_BotTouchingGoal(bs->origin, &bs->curpatrolpoint->goal)) { + if (bs->patrolflags & PATROL_BACK) { + if (bs->curpatrolpoint->prev) { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->next; + bs->patrolflags &= ~PATROL_BACK; + } + } + else { + if (bs->curpatrolpoint->next) { + bs->curpatrolpoint = bs->curpatrolpoint->next; + } + else { + bs->curpatrolpoint = bs->curpatrolpoint->prev; + bs->patrolflags |= PATROL_BACK; + } + } + } + //stop after 5 minutes + if (bs->teamgoal_time < FloatTime()) { + BotAI_BotInitialChat(bs, "patrol_stop", NULL); + trap_BotEnterChat(bs->cs, bs->decisionmaker, CHAT_TELL); + bs->ltgtype = 0; + } + if (!bs->curpatrolpoint) { + bs->ltgtype = 0; + return qfalse; + } + memcpy(goal, &bs->curpatrolpoint->goal, sizeof(bot_goal_t)); + return qtrue; + } +#ifdef CTF + if (gametype == GT_CTF) { + //if going for enemy flag + if (bs->ltgtype == LTG_GETFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "captureflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); + bs->teammessage_time = 0; + } + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + // make sure the bot knows the flag isn't there anymore + switch(BotTeam(bs)) { + case TEAM_RED: bs->blueflagstatus = 1; break; + case TEAM_BLUE: bs->redflagstatus = 1; break; + } + bs->ltgtype = 0; + } + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE && bs->rushbaseaway_time < FloatTime()) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if not carrying the flag anymore + if (!BotCTFCarryingFlag(bs)) bs->ltgtype = 0; + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) bs->ltgtype = 0; + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + //if the bot is still carrying the enemy flag then the + //base flag is gone, now just walk near the base a bit + if (BotCTFCarryingFlag(bs)) { + trap_BotResetAvoidReach(bs->ms); + bs->rushbaseaway_time = FloatTime() + 5 + 10 * random(); + //FIXME: add chat to tell the others to get back the flag + } + else { + bs->ltgtype = 0; + } + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //returning flag + if (bs->ltgtype == LTG_RETURNFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "returnflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); + bs->teammessage_time = 0; + } + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) bs->ltgtype = 0; + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (bs->ltgtype == LTG_GETFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "captureflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONGETFLAG); + bs->teammessage_time = 0; + } + memcpy(goal, &ctf_neutralflag, sizeof(bot_goal_t)); + //if touching the flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->ltgtype = 0; + } + //stop after 3 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + return qtrue; + } + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if not carrying the flag anymore + if (!Bot1FCTFCarryingFlag(bs)) { + bs->ltgtype = 0; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //attack the enemy base + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &ctf_blueflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &ctf_redflag, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 2 + 5 * random(); + } + return qtrue; + } + //returning flag + if (bs->ltgtype == LTG_RETURNFLAG) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "returnflag_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONRETURNFLAG); + bs->teammessage_time = 0; + } + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //just roam around + return BotGetItemLongTermGoal(bs, tfl, goal); + } + } + else if (gametype == GT_OBELISK) { + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //if the bot no longer wants to attack the obelisk + if (BotFeelingBad(bs) > 50) { + return BotGetItemLongTermGoal(bs, tfl, goal); + } + //if touching the obelisk + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 3 + 5 * random(); + } + // or very close to the obelisk + VectorSubtract(bs->origin, goal->origin, dir); + if (VectorLengthSquared(dir) < Square(60)) { + bs->attackaway_time = FloatTime() + 3 + 5 * random(); + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + BotAlternateRoute(bs, goal); + //just move towards the obelisk + return qtrue; + } + } + else if (gametype == GT_HARVESTER) { + //if rushing to the base + if (bs->ltgtype == LTG_RUSHBASE) { + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: BotGoHarvest(bs); return qfalse; + } + //if not carrying any cubes + if (!BotHarvesterCarryingCubes(bs)) { + BotGoHarvest(bs); + return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + BotGoHarvest(bs); + return qfalse; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + BotGoHarvest(bs); + return qfalse; + } + BotAlternateRoute(bs, goal); + return qtrue; + } + //attack the enemy base + if (bs->ltgtype == LTG_ATTACKENEMYBASE && + bs->attackaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "attackenemybase_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(goal, &blueobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(goal, &redobelisk, sizeof(bot_goal_t)); break; + default: bs->ltgtype = 0; return qfalse; + } + //quit rushing after 2 minutes + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + //if touching the base flag the bot should loose the enemy flag + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->attackaway_time = FloatTime() + 2 + 5 * random(); + } + return qtrue; + } + //harvest cubes + if (bs->ltgtype == LTG_HARVEST && + bs->harvestaway_time < FloatTime()) { + //check for bot typing status message + if (bs->teammessage_time && bs->teammessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "harvest_start", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONOFFENSE); + bs->teammessage_time = 0; + } + memcpy(goal, &neutralobelisk, sizeof(bot_goal_t)); + // + if (bs->teamgoal_time < FloatTime()) { + bs->ltgtype = 0; + } + // + if (trap_BotTouchingGoal(bs->origin, goal)) { + bs->harvestaway_time = FloatTime() + 4 + 3 * random(); + } + return qtrue; + } + } +#endif + //normal goal stuff + return BotGetItemLongTermGoal(bs, tfl, goal); +} + +/* +================== +BotLongTermGoal +================== +*/ +int BotLongTermGoal(bot_state_t *bs, int tfl, int retreat, bot_goal_t *goal) { + aas_entityinfo_t entinfo; + char teammate[MAX_MESSAGE_SIZE]; + float squaredist; + int areanum; + vec3_t dir; + + //FIXME: also have air long term goals? + // + //if the bot is leading someone and not retreating + if (bs->lead_time > 0 && !retreat) { + if (bs->lead_time < FloatTime()) { + BotAI_BotInitialChat(bs, "lead_stop", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->lead_time = 0; + return BotGetLongTermGoal(bs, tfl, retreat, goal); + } + // + if (bs->leadmessage_time < 0 && -bs->leadmessage_time < FloatTime()) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //get entity information of the companion + BotEntityInfo(bs->lead_teammate, &entinfo); + // + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum && trap_AAS_AreaReachability(areanum)) { + //update team goal + bs->lead_teamgoal.entitynum = bs->lead_teammate; + bs->lead_teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->lead_teamgoal.origin); + VectorSet(bs->lead_teamgoal.mins, -8, -8, -8); + VectorSet(bs->lead_teamgoal.maxs, 8, 8, 8); + } + } + //if the team mate is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->lead_teammate)) { + bs->leadvisible_time = FloatTime(); + } + //if the team mate is not visible for 1 seconds + if (bs->leadvisible_time < FloatTime() - 1) { + bs->leadbackup_time = FloatTime() + 2; + } + //distance towards the team mate + VectorSubtract(bs->origin, bs->lead_teamgoal.origin, dir); + squaredist = VectorLengthSquared(dir); + //if backing up towards the team mate + if (bs->leadbackup_time > FloatTime()) { + if (bs->leadmessage_time < FloatTime() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //if very close to the team mate + if (squaredist < Square(100)) { + bs->leadbackup_time = 0; + } + //the bot should go back to the team mate + memcpy(goal, &bs->lead_teamgoal, sizeof(bot_goal_t)); + return qtrue; + } + else { + //if quite distant from the team mate + if (squaredist > Square(500)) { + if (bs->leadmessage_time < FloatTime() - 20) { + BotAI_BotInitialChat(bs, "followme", EasyClientName(bs->lead_teammate, teammate, sizeof(teammate)), NULL); + trap_BotEnterChat(bs->cs, bs->teammate, CHAT_TELL); + bs->leadmessage_time = FloatTime(); + } + //look at the team mate + VectorSubtract(entinfo.origin, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + //just wait for the team mate + return qfalse; + } + } + } + return BotGetLongTermGoal(bs, tfl, retreat, goal); +} + +/* +================== +AIEnter_Intermission +================== +*/ +void AIEnter_Intermission(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "intermission", "", s); + //reset the bot state + BotResetState(bs); + //check for end level chat + if (BotChat_EndLevel(bs)) { + trap_BotEnterChat(bs->cs, 0, bs->chatto); + } + bs->ainode = AINode_Intermission; +} + +/* +================== +AINode_Intermission +================== +*/ +int AINode_Intermission(bot_state_t *bs) { + //if the intermission ended + if (!BotIntermission(bs)) { + if (BotChat_StartLevel(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + } + else { + bs->stand_time = FloatTime() + 2; + } + AIEnter_Stand(bs, "intermission: chat"); + } + return qtrue; +} + +/* +================== +AIEnter_Observer +================== +*/ +void AIEnter_Observer(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "observer", "", s); + //reset the bot state + BotResetState(bs); + bs->ainode = AINode_Observer; +} + +/* +================== +AINode_Observer +================== +*/ +int AINode_Observer(bot_state_t *bs) { + //if the bot left observer mode + if (!BotIsObserver(bs)) { + AIEnter_Stand(bs, "observer: left observer"); + } + return qtrue; +} + +/* +================== +AIEnter_Stand +================== +*/ +void AIEnter_Stand(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "stand", "", s); + bs->standfindenemy_time = FloatTime() + 1; + bs->ainode = AINode_Stand; +} + +/* +================== +AINode_Stand +================== +*/ +int AINode_Stand(bot_state_t *bs) { + + //if the bot's health decreased + if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { + if (BotChat_HitTalking(bs)) { + bs->standfindenemy_time = FloatTime() + BotChatTime(bs) + 0.1; + bs->stand_time = FloatTime() + BotChatTime(bs) + 0.1; + } + } + if (bs->standfindenemy_time < FloatTime()) { + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "stand: found enemy"); + return qfalse; + } + bs->standfindenemy_time = FloatTime() + 1; + } + // put up chat icon + trap_EA_Talk(bs->client); + // when done standing + if (bs->stand_time < FloatTime()) { + trap_BotEnterChat(bs->cs, 0, bs->chatto); + AIEnter_Seek_LTG(bs, "stand: time out"); + return qfalse; + } + // + return qtrue; +} + +/* +================== +AIEnter_Respawn +================== +*/ +void AIEnter_Respawn(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "respawn", "", s); + //reset some states + trap_BotResetMoveState(bs->ms); + trap_BotResetGoalState(bs->gs); + trap_BotResetAvoidGoals(bs->gs); + trap_BotResetAvoidReach(bs->ms); + //if the bot wants to chat + if (BotChat_Death(bs)) { + bs->respawn_time = FloatTime() + BotChatTime(bs); + bs->respawnchat_time = FloatTime(); + } + else { + bs->respawn_time = FloatTime() + 1 + random(); + bs->respawnchat_time = 0; + } + //set respawn state + bs->respawn_wait = qfalse; + bs->ainode = AINode_Respawn; +} + +/* +================== +AINode_Respawn +================== +*/ +int AINode_Respawn(bot_state_t *bs) { + // if waiting for the actual respawn + if (bs->respawn_wait) { + if (!BotIsDead(bs)) { + AIEnter_Seek_LTG(bs, "respawn: respawned"); + } + else { + trap_EA_Respawn(bs->client); + } + } + else if (bs->respawn_time < FloatTime()) { + // wait until respawned + bs->respawn_wait = qtrue; + // elementary action respawn + trap_EA_Respawn(bs->client); + // + if (bs->respawnchat_time) { + trap_BotEnterChat(bs->cs, 0, bs->chatto); + bs->enemy = -1; + } + } + if (bs->respawnchat_time && bs->respawnchat_time < FloatTime() - 0.5) { + trap_EA_Talk(bs->client); + } + // + return qtrue; +} + +/* +================== +BotSelectActivateWeapon +================== +*/ +int BotSelectActivateWeapon(bot_state_t *bs) { + // + if (bs->inventory[INVENTORY_MACHINEGUN] > 0 && bs->inventory[INVENTORY_BULLETS] > 0) + return WEAPONINDEX_MACHINEGUN; + else if (bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 0) + return WEAPONINDEX_SHOTGUN; + //PKMOD - Ergodic 04/14/01 - remove plasma gun from PKA +// else if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) +// return WEAPONINDEX_PLASMAGUN; + else if (bs->inventory[INVENTORY_LIGHTNING] > 0 && bs->inventory[INVENTORY_LIGHTNINGAMMO] > 0) + return WEAPONINDEX_LIGHTNING; +#ifdef MISSIONPACK + else if (bs->inventory[INVENTORY_CHAINGUN] > 0 && bs->inventory[INVENTORY_BELT] > 0) + return WEAPONINDEX_CHAINGUN; + else if (bs->inventory[INVENTORY_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0) + return WEAPONINDEX_NAILGUN; +#endif + else if (bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0) + return WEAPONINDEX_RAILGUN; + else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) + return WEAPONINDEX_ROCKET_LAUNCHER; + //PKMOD - Ergodic 04/14/01 - remove BFG from PKA +// else if (bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) +// return WEAPONINDEX_BFG; + else { + return -1; + } +} + +/* +================== +BotClearPath + + try to deactivate obstacles like proximity mines on the bot's path + + PKMOD - Ergodic 04/14/01 - change logic to clear beartraps and + autosentrys that are on the bot's path + +================== +*/ +void BotClearPath(bot_state_t *bs, bot_moveresult_t *moveresult) { + int i, bestbeartrap; + float dist, bestdist; + vec3_t target, dir; + bsp_trace_t bsptrace; + entityState_t state; + +//PKMOD - Ergodic 04/14/01 - remove previous kamakazi code + if (moveresult->flags & MOVERESULT_BLOCKEDBYAVOIDSPOT) { + bs->blockedbyavoidspot_time = FloatTime() + 5; + } + // if blocked by an avoid spot and the view angles and weapon are used for movement + if (bs->blockedbyavoidspot_time > FloatTime() && + !(moveresult->flags & (MOVERESULT_MOVEMENTVIEW | MOVERESULT_MOVEMENTWEAPON)) ) { + bestdist = 300; + bestbeartrap = -1; + for (i = 0; i < bs->numbeartraps; i++) { + BotAI_GetEntityState(bs->beartraps[i], &state); + VectorSubtract(state.pos.trBase, bs->origin, dir); + dist = VectorLength(dir); + if (dist < bestdist) { + bestdist = dist; + bestbeartrap = i; + } + } + if (bestbeartrap != -1) { + // + // state->generic1 == TEAM_RED || state->generic1 == TEAM_BLUE + // + // deactivate prox mines in the bot's path by shooting + // rockets or plasma cells etc. at them + BotAI_GetEntityState(bs->beartraps[bestbeartrap], &state); + VectorCopy(state.pos.trBase, target); + target[2] += 2; + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, moveresult->ideal_viewangles); + // if the bot has a weapon that does splash damage + //PKMOD - Ergodic 04/14/01 - modify the weapons pick + if (bs->inventory[INVENTORY_EXPLOSIVESHOTGUN] > 0 && bs->inventory[INVENTORY_EXPLOSIVESHELLS] > 0) + moveresult->weapon = WEAPONINDEX_EXPLODING_SHELLS; + else if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) + moveresult->weapon = WEAPONINDEX_ROCKET_LAUNCHER; + else { + moveresult->weapon = 0; + } + if (moveresult->weapon) { + // + moveresult->flags |= MOVERESULT_MOVEMENTWEAPON | MOVERESULT_MOVEMENTVIEW; + // if holding the right weapon + if (bs->cur_ps.weapon == moveresult->weapon) { + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, moveresult->ideal_viewangles)) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, target, bs->entitynum, MASK_SHOT); + // if the mine is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == state.number) { + // shoot at the mine + trap_EA_Attack(bs->client); + } + } + } + } + } + } +} + +/* +================== +AIEnter_Seek_ActivateEntity +================== +*/ +void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "activate entity", "", s); + bs->ainode = AINode_Seek_ActivateEntity; +} + +/* +================== +AINode_Seek_Activate_Entity +================== +*/ +int AINode_Seek_ActivateEntity(bot_state_t *bs) { + bot_goal_t *goal; + vec3_t target, dir, ideal_viewangles; + bot_moveresult_t moveresult; + int targetvisible; + bsp_trace_t bsptrace; + aas_entityinfo_t entinfo; + + if (BotIsObserver(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Observer(bs, "active entity: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Intermission(bs, "activate entity: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + BotClearActivateGoalStack(bs); + AIEnter_Respawn(bs, "activate entity: bot dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + //PKMOD - Ergodic 02/11/02 - change grapple cvar name from "bot_grapple" to "bot_dragon" +// if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + if (bot_dragon.integer) bs->tfl |= TFL_GRAPPLEHOOK; + // if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // map specific code + BotMapScripts(bs); + // no enemy + bs->enemy = -1; + // if the bot has no activate goal + if (!bs->activatestack) { + BotClearActivateGoalStack(bs); + AIEnter_Seek_NBG(bs, "activate entity: no goal"); + return qfalse; + } + // + goal = &bs->activatestack->goal; + // initialize target being visible to false + targetvisible = qfalse; + // if the bot has to shoot at a target to activate something + if (bs->activatestack->shoot) { + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->activatestack->target, bs->entitynum, MASK_SHOT); + // if the shootable entity is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == goal->entitynum) { + targetvisible = qtrue; + // if holding the right weapon + if (bs->cur_ps.weapon == bs->activatestack->weapon) { + VectorSubtract(bs->activatestack->target, bs->eye, dir); + vectoangles(dir, ideal_viewangles); + // if the bot is pretty close with it's aim + if (InFieldOfVision(bs->viewangles, 20, ideal_viewangles)) { + trap_EA_Attack(bs->client); + } + } + } + } + // if the shoot target is visible + if (targetvisible) { + // get the entity info of the entity the bot is shooting at + BotEntityInfo(goal->entitynum, &entinfo); + // if the entity the bot shoots at moved + if (!VectorCompare(bs->activatestack->origin, entinfo.origin)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "hit shootable button or trigger\n"); +#endif //DEBUG + bs->activatestack->time = 0; + } + // if the activate goal has been activated or the bot takes too long + if (bs->activatestack->time < FloatTime()) { + BotPopFromActivateGoalStack(bs); + // if there are more activate goals on the stack + if (bs->activatestack) { + bs->activatestack->time = FloatTime() + 10; + return qfalse; + } + AIEnter_Seek_NBG(bs, "activate entity: time out"); + return qfalse; + } + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + } + else { + // if the bot has no goal + if (!goal) { + bs->activatestack->time = 0; + } + // if the bot does not have a shoot goal + else if (!bs->activatestack->shoot) { + //if the bot touches the current goal + if (trap_BotTouchingGoal(bs->origin, goal)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "touched button or trigger\n"); +#endif //DEBUG + bs->activatestack->time = 0; + } + } + // if the activate goal has been activated or the bot takes too long + if (bs->activatestack->time < FloatTime()) { + BotPopFromActivateGoalStack(bs); + // if there are more activate goals on the stack + if (bs->activatestack) { + bs->activatestack->time = FloatTime() + 10; + return qfalse; + } + AIEnter_Seek_NBG(bs, "activate entity: activated"); + return qfalse; + } + //predict obstacles + if (BotAIPredictObstacles(bs, goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + // + bs->activatestack->time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + } + // + BotClearPath(bs, &moveresult); + // if the bot has to shoot to activate + if (bs->activatestack->shoot) { + // if the view angles aren't yet used for the movement + if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEW)) { + VectorSubtract(bs->activatestack->target, bs->eye, dir); + vectoangles(dir, moveresult.ideal_viewangles); + moveresult.flags |= MOVERESULT_MOVEMENTVIEW; + } + // if there's no weapon yet used for the movement + if (!(moveresult.flags & MOVERESULT_MOVEMENTWEAPON)) { + moveresult.flags |= MOVERESULT_MOVEMENTWEAPON; + // + bs->activatestack->weapon = BotSelectActivateWeapon(bs); + if (bs->activatestack->weapon == -1) { + //FIXME: find a decent weapon first + bs->activatestack->weapon = 0; + } + moveresult.weapon = bs->activatestack->weapon; + } + } + // if the ideal view angles are set for movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + // if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (trap_BotMovementViewTarget(bs->ms, goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + // if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) + bs->weaponnum = moveresult.weapon; + // if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs, "activate entity: found enemy"); + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "activate entity: found enemy"); + } + BotClearActivateGoalStack(bs); + } + return qtrue; +} + +/* +================== +AIEnter_Seek_NBG +================== +*/ +void AIEnter_Seek_NBG(bot_state_t *bs, char *s) { + bot_goal_t goal; + char buf[144]; + + if (trap_BotGetTopGoal(bs->gs, &goal)) { + trap_BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek NBG", buf, s); + } + else { + BotRecordNodeSwitch(bs, "seek NBG", "no goal", s); + } + bs->ainode = AINode_Seek_NBG; +} + +/* +================== +AINode_Seek_NBG +================== +*/ +int AINode_Seek_NBG(bot_state_t *bs) { + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "seek nbg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "seek nbg: intermision"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "seek nbg: bot dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + //PKMOD - Ergodic 02/11/02 - change grapple cvar name from "bot_grapple" to "bot_dragon" +// if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + if (bot_dragon.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + //if the bot has no goal + if (!trap_BotGetTopGoal(bs->gs, &goal)) bs->nbg_time = 0; + //if the bot touches the current goal + else if (BotReachedGoal(bs, &goal)) { + BotChooseWeapon(bs); + bs->nbg_time = 0; + } + // + if (bs->nbg_time < FloatTime()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //check for new nearby items right away + //NOTE: we canNOT reset the check_time to zero because it would create an endless loop of node switches + bs->check_time = FloatTime() + 0.05; + //go back to seek ltg + AIEnter_Seek_LTG(bs, "seek nbg: time out"); + return qfalse; + } + //predict obstacles + if (BotAIPredictObstacles(bs, &goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + bs->nbg_time = 0; + } + //check if the bot is blocked + BotAIBlocked(bs, &moveresult, qtrue); + // + BotClearPath(bs, &moveresult); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (!trap_BotGetSecondGoal(bs->gs, &goal)) trap_BotGetTopGoal(bs->gs, &goal); + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else vectoangles(moveresult.movedir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_NBG(bs, "seek nbg: found enemy"); + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "seek nbg: found enemy"); + } + } + return qtrue; +} + +/* +================== +AIEnter_Seek_LTG +================== +*/ +void AIEnter_Seek_LTG(bot_state_t *bs, char *s) { + bot_goal_t goal; + char buf[144]; + + if (trap_BotGetTopGoal(bs->gs, &goal)) { + trap_BotGoalName(goal.number, buf, 144); + BotRecordNodeSwitch(bs, "seek LTG", buf, s); + } + else { + BotRecordNodeSwitch(bs, "seek LTG", "no goal", s); + } + bs->ainode = AINode_Seek_LTG; +} + +/* +================== +AINode_Seek_LTG +================== +*/ +int AINode_Seek_LTG(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + int range; + //char buf[128]; + //bot_goal_t tmpgoal; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "seek ltg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "seek ltg: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "seek ltg: bot dead"); + return qfalse; + } + // + if (BotChat_Random(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "seek ltg: random chat"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + //PKMOD - Ergodic 02/11/02 - change grapple cvar name from "bot_grapple" to "bot_dragon" +// if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + if (bot_dragon.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //no enemy + bs->enemy = -1; + // + if (bs->killedenemy_time > FloatTime() - 2) { + if (random() < bs->thinktime * 1) { + trap_EA_Gesture(bs->client); + } + } + //if there is an enemy + if (BotFindEnemy(bs, -1)) { + if (BotWantsToRetreat(bs)) { + //keep the current long term goal and retreat + AIEnter_Battle_Retreat(bs, "seek ltg: found enemy"); + return qfalse; + } + else { + trap_BotResetLastAvoidReach(bs->ms); + //empty the goal stack + trap_BotEmptyGoalStack(bs->gs); + //go fight + AIEnter_Battle_Fight(bs, "seek ltg: found enemy"); + return qfalse; + } + } + // + BotTeamGoals(bs, qfalse); + //get the current long term goal + if (!BotLongTermGoal(bs, bs->tfl, qfalse, &goal)) { + return qtrue; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 0.5; + //check if the bot wants to camp + BotWantsToCamp(bs); + // + if (bs->ltgtype == LTG_DEFENDKEYAREA) range = 400; + else range = 150; + // +#ifdef CTF + if (gametype == GT_CTF) { + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) + range = 50; + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) + range = 50; + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) + range = 80; + } +#endif + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + trap_BotResetLastAvoidReach(bs->ms); + //get the goal at the top of the stack + //trap_BotGetTopGoal(bs->gs, &tmpgoal); + //trap_BotGoalName(tmpgoal.number, buf, 144); + //BotAI_Print(PRT_MESSAGE, "new nearby goal %s\n", buf); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + 4 + range * 0.01; + AIEnter_Seek_NBG(bs, "ltg seek: nbg"); + return qfalse; + } + } + //predict obstacles + if (BotAIPredictObstacles(bs, &goal)) + return qfalse; + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qtrue); + // + BotClearPath(bs, &moveresult); + //if the viewangles are used for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + //if waiting for something + else if (moveresult.flags & MOVERESULT_WAITING) { + if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + //FIXME: look at cluster portals? + else if (VectorLengthSquared(moveresult.movedir)) { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + else if (random() < bs->thinktime * 0.8) { + BotRoamGoal(bs, target); + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + bs->ideal_viewangles[2] *= 0.5; + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + // + return qtrue; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_Fight(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle fight", "", s); + trap_BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; +} + +/* +================== +AIEnter_Battle_Fight +================== +*/ +void AIEnter_Battle_SuicidalFight(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle fight", "", s); + trap_BotResetLastAvoidReach(bs->ms); + bs->ainode = AINode_Battle_Fight; + bs->flags |= BFL_FIGHTSUICIDAL; +} + +/* +================== +AINode_Battle_Fight +================== +*/ +int AINode_Battle_Fight(bot_state_t *bs) { + int areanum; + vec3_t target; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle fight: observer"); + return qfalse; + } + + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle fight: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle fight: bot dead"); + return qfalse; + } + //if there is another better enemy + if (BotFindEnemy(bs, bs->enemy)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); +#endif + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle fight: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is dead + if (bs->enemydeath_time) { + if (bs->enemydeath_time < FloatTime() - 1.0) { + bs->enemydeath_time = 0; + if (bs->enemysuicide) { + BotChat_EnemySuicide(bs); + } + if (bs->lastkilledplayer == bs->enemy && BotChat_Kill(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: enemy dead"); + } + else { + bs->ltg_time = 0; + AIEnter_Seek_LTG(bs, "battle fight: enemy dead"); + } + return qfalse; + } + } + else { + if (EntityIsDead(&entinfo)) { + bs->enemydeath_time = FloatTime(); + } + } + //if the enemy is invisible and not shooting the bot looses track easily + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + //PKMOD - Ergodic 10/31/02 - check if enemy has a beartrap attached + // attached beartraps will make the player visible to the bots + entityState_t state; + BotAI_GetEntityState(bs->enemy, &state); + if ( ( state.time2 & 3 ) == 0 ) //if no beartraps are attached + if (random() < 0.2) { + AIEnter_Seek_LTG(bs, "battle fight: invisible"); + return qfalse; + } + } + // + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot's health decreased + if (bs->lastframe_health > bs->inventory[INVENTORY_HEALTH]) { + if (BotChat_HitNoDeath(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: chat health decreased"); + return qfalse; + } + } + //if the bot hit someone + if (bs->cur_ps.persistant[PERS_HITS] > bs->lasthitcount) { + if (BotChat_HitNoKill(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "battle fight: chat hit someone"); + return qfalse; + } + } + //if the enemy is not visible + if (!BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + if (BotWantsToChase(bs)) { + AIEnter_Battle_Chase(bs, "battle fight: enemy out of sight"); + return qfalse; + } + else { + AIEnter_Seek_LTG(bs, "battle fight: enemy out of sight"); + return qfalse; + } + } + //use holdable items + BotBattleUseItems(bs); + // + bs->tfl = TFL_DEFAULT; + //PKMOD - Ergodic 02/11/02 - change grapple cvar name from "bot_grapple" to "bot_dragon" +// if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + if (bot_dragon.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //PKMOD - Ergodic 04/15/01 - use beans if needed + BotEatBeans(bs); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //do attack movements + moveresult = BotAttackMove(bs, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //aim at the enemy + BotAimAtEnemy(bs); + //attack the enemy if possible + BotCheckAttack(bs); + //if the bot wants to retreat + if (!(bs->flags & BFL_FIGHTSUICIDAL)) { + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs, "battle fight: wants to retreat"); + return qtrue; + } + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Chase +================== +*/ +void AIEnter_Battle_Chase(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle chase", "", s); + bs->chase_time = FloatTime(); + bs->ainode = AINode_Battle_Chase; +} + +/* +================== +AINode_Battle_Chase +================== +*/ +int AINode_Battle_Chase(bot_state_t *bs) +{ + bot_goal_t goal; + vec3_t target, dir; + bot_moveresult_t moveresult; + float range; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle chase: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle chase: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle chase: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle chase: no enemy"); + return qfalse; + } + //if the enemy is visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + AIEnter_Battle_Fight(bs, "battle chase"); + return qfalse; + } + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "battle chase: better enemy"); + return qfalse; + } + //there is no last enemy area + if (!bs->lastenemyareanum) { + AIEnter_Seek_LTG(bs, "battle chase: no enemy area"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + //PKMOD - Ergodic 02/11/02 - change grapple cvar name from "bot_grapple" to "bot_dragon" +// if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + if (bot_dragon.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //create the chase goal + goal.entitynum = bs->enemy; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //if the last seen enemy spot is reached the enemy could not be found + if (trap_BotTouchingGoal(bs->origin, &goal)) bs->chase_time = 0; + //if there's no chase time left + if (!bs->chase_time || bs->chase_time < FloatTime() - 10) { + AIEnter_Seek_LTG(bs, "battle chase: time out"); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 1; + range = 150; + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + //the bot gets 5 seconds to pick up the nearby goal item + bs->nbg_time = FloatTime() + 0.1 * range + 1; + trap_BotResetLastAvoidReach(bs->ms); + AIEnter_Battle_NBG(bs, "battle chase: nbg"); + return qfalse; + } + } + // + BotUpdateBattleInventory(bs, bs->enemy); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + // + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEWSET|MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(bs->flags & BFL_IDEALVIEWSET)) { + if (bs->chase_time > FloatTime() - 2) { + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + } + bs->ideal_viewangles[2] *= 0.5; + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //if the bot is in the area the enemy was last seen in + if (bs->areanum == bs->lastenemyareanum) bs->chase_time = 0; + //if the bot wants to retreat (the bot could have been damage during the chase) + if (BotWantsToRetreat(bs)) { + AIEnter_Battle_Retreat(bs, "battle chase: wants to retreat"); + return qtrue; + } + return qtrue; +} + +/* +================== +AIEnter_Battle_Retreat +================== +*/ +void AIEnter_Battle_Retreat(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle retreat", "", s); + bs->ainode = AINode_Battle_Retreat; +} + +/* +================== +AINode_Battle_Retreat +================== +*/ +int AINode_Battle_Retreat(bot_state_t *bs) { + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + vec3_t target, dir; + float attack_skill, range; + int areanum; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle retreat: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle retreat: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle retreat: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_LTG(bs, "battle retreat: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_LTG(bs, "battle retreat: enemy dead"); + return qfalse; + } + //if there is another better enemy + if (BotFindEnemy(bs, bs->enemy)) { +#ifdef DEBUG + BotAI_Print(PRT_MESSAGE, "found new better enemy\n"); +#endif + } + // + bs->tfl = TFL_DEFAULT; + //PKMOD - Ergodic 02/11/02 - change grapple cvar name from "bot_grapple" to "bot_dragon" +// if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + if (bot_dragon.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + //map specific code + BotMapScripts(bs); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //if the bot doesn't want to retreat anymore... probably picked up some nice items + if (BotWantsToChase(bs)) { + //empty the goal stack, when chasing, only the enemy is the goal + trap_BotEmptyGoalStack(bs->gs); + //go chase the enemy + AIEnter_Battle_Chase(bs, "battle retreat: wants to chase"); + return qfalse; + } + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = FloatTime(); + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the enemy is NOT visible for 4 seconds + if (bs->enemyvisible_time < FloatTime() - 4) { + AIEnter_Seek_LTG(bs, "battle retreat: lost enemy"); + return qfalse; + } + //else if the enemy is NOT visible + else if (bs->enemyvisible_time < FloatTime()) { + //if there is another enemy + if (BotFindEnemy(bs, -1)) { + AIEnter_Battle_Fight(bs, "battle retreat: another enemy"); + return qfalse; + } + } + // + BotTeamGoals(bs, qtrue); + //use holdable items + BotBattleUseItems(bs); + //get the current long term goal while retreating + if (!BotLongTermGoal(bs, bs->tfl, qtrue, &goal)) { + AIEnter_Battle_SuicidalFight(bs, "battle retreat: no way out"); + return qfalse; + } + //check for nearby goals periodicly + if (bs->check_time < FloatTime()) { + bs->check_time = FloatTime() + 1; + range = 150; +#ifdef CTF + if (gametype == GT_CTF) { + //if carrying a flag the bot shouldn't be distracted too much + if (BotCTFCarryingFlag(bs)) + range = 50; + } +#endif //CTF +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) + range = 50; + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) + range = 80; + } +#endif + // + if (BotNearbyGoal(bs, bs->tfl, &goal, range)) { + trap_BotResetLastAvoidReach(bs->ms); + //time the bot gets to pick up the nearby goal item + bs->nbg_time = FloatTime() + range / 100 + 1; + AIEnter_Battle_NBG(bs, "battle retreat: nbg"); + return qfalse; + } + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->ltg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //PKMOD - Ergodic 04/15/01 - use beans if needed + BotEatBeans(bs); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) + && !(bs->flags & BFL_IDEALVIEWSET) ) { + attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + //if the bot is skilled anough + if (attack_skill > 0.3) { + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + +/* +================== +AIEnter_Battle_NBG +================== +*/ +void AIEnter_Battle_NBG(bot_state_t *bs, char *s) { + BotRecordNodeSwitch(bs, "battle NBG", "", s); + bs->ainode = AINode_Battle_NBG; +} + +/* +================== +AINode_Battle_NBG +================== +*/ +int AINode_Battle_NBG(bot_state_t *bs) { + int areanum; + bot_goal_t goal; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + float attack_skill; + vec3_t target, dir; + + if (BotIsObserver(bs)) { + AIEnter_Observer(bs, "battle nbg: observer"); + return qfalse; + } + //if in the intermission + if (BotIntermission(bs)) { + AIEnter_Intermission(bs, "battle nbg: intermission"); + return qfalse; + } + //respawn if dead + if (BotIsDead(bs)) { + AIEnter_Respawn(bs, "battle nbg: bot dead"); + return qfalse; + } + //if no enemy + if (bs->enemy < 0) { + AIEnter_Seek_NBG(bs, "battle nbg: no enemy"); + return qfalse; + } + // + BotEntityInfo(bs->enemy, &entinfo); + if (EntityIsDead(&entinfo)) { + AIEnter_Seek_NBG(bs, "battle nbg: enemy dead"); + return qfalse; + } + // + bs->tfl = TFL_DEFAULT; + //PKMOD - Ergodic 02/11/02 - change grapple cvar name from "bot_grapple" to "bot_dragon" +// if (bot_grapple.integer) bs->tfl |= TFL_GRAPPLEHOOK; + if (bot_dragon.integer) bs->tfl |= TFL_GRAPPLEHOOK; + //if in lava or slime the bot should be able to get out + if (BotInLavaOrSlime(bs)) bs->tfl |= TFL_LAVA|TFL_SLIME; + // + if (BotCanAndWantsToRocketJump(bs)) { + bs->tfl |= TFL_ROCKETJUMP; + } + //map specific code + BotMapScripts(bs); + //update the last time the enemy was visible + if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy)) { + bs->enemyvisible_time = FloatTime(); + VectorCopy(entinfo.origin, target); + // if not a player enemy + if (bs->enemy >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 16; + } +#endif + } + //update the reachability area and origin if possible + areanum = BotPointAreaNum(target); + if (areanum && trap_AAS_AreaReachability(areanum)) { + VectorCopy(target, bs->lastenemyorigin); + bs->lastenemyareanum = areanum; + } + } + //if the bot has no goal or touches the current goal + if (!trap_BotGetTopGoal(bs->gs, &goal)) { + bs->nbg_time = 0; + } + else if (BotReachedGoal(bs, &goal)) { + bs->nbg_time = 0; + } + // + if (bs->nbg_time < FloatTime()) { + //pop the current goal from the stack + trap_BotPopGoal(bs->gs); + //if the bot still has a goal + if (trap_BotGetTopGoal(bs->gs, &goal)) + AIEnter_Battle_Retreat(bs, "battle nbg: time out"); + else + AIEnter_Battle_Fight(bs, "battle nbg: time out"); + // + return qfalse; + } + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, bs->tfl); + //if the movement failed + if (moveresult.failure) { + //reset the avoid reach, otherwise bot is stuck in current area + trap_BotResetAvoidReach(bs->ms); + //BotAI_Print(PRT_MESSAGE, "movement failure %d\n", moveresult.traveltype); + bs->nbg_time = 0; + } + // + BotAIBlocked(bs, &moveresult, qfalse); + //update the attack inventory values + BotUpdateBattleInventory(bs, bs->enemy); + //choose the best weapon to fight with + BotChooseWeapon(bs); + //if the view is fixed for the movement + if (moveresult.flags & (MOVERESULT_MOVEMENTVIEW|MOVERESULT_SWIMVIEW)) { + VectorCopy(moveresult.ideal_viewangles, bs->ideal_viewangles); + } + else if (!(moveresult.flags & MOVERESULT_MOVEMENTVIEWSET) + && !(bs->flags & BFL_IDEALVIEWSET)) { + attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + //if the bot is skilled anough and the enemy is visible + if (attack_skill > 0.3) { + //&& BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy) + BotAimAtEnemy(bs); + } + else { + if (trap_BotMovementViewTarget(bs->ms, &goal, bs->tfl, 300, target)) { + VectorSubtract(target, bs->origin, dir); + vectoangles(dir, bs->ideal_viewangles); + } + else { + vectoangles(moveresult.movedir, bs->ideal_viewangles); + } + bs->ideal_viewangles[2] *= 0.5; + } + } + //if the weapon is used for the bot movement + if (moveresult.flags & MOVERESULT_MOVEMENTWEAPON) bs->weaponnum = moveresult.weapon; + //attack the enemy if possible + BotCheckAttack(bs); + // + return qtrue; +} + diff --git a/quake3/source/code/game/ai_dmnet.h b/quake3/source/code/game/ai_dmnet.h new file mode 100644 index 0000000..87f8cf2 --- /dev/null +++ b/quake3/source/code/game/ai_dmnet.h @@ -0,0 +1,41 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmnet.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +#define MAX_NODESWITCHES 50 + +void AIEnter_Intermission(bot_state_t *bs, char *s); +void AIEnter_Observer(bot_state_t *bs, char *s); +void AIEnter_Respawn(bot_state_t *bs, char *s); +void AIEnter_Stand(bot_state_t *bs, char *s); +void AIEnter_Seek_ActivateEntity(bot_state_t *bs, char *s); +void AIEnter_Seek_NBG(bot_state_t *bs, char *s); +void AIEnter_Seek_LTG(bot_state_t *bs, char *s); +void AIEnter_Seek_Camp(bot_state_t *bs, char *s); +void AIEnter_Battle_Fight(bot_state_t *bs, char *s); +void AIEnter_Battle_Chase(bot_state_t *bs, char *s); +void AIEnter_Battle_Retreat(bot_state_t *bs, char *s); +void AIEnter_Battle_NBG(bot_state_t *bs, char *s); +int AINode_Intermission(bot_state_t *bs); +int AINode_Observer(bot_state_t *bs); +int AINode_Respawn(bot_state_t *bs); +int AINode_Stand(bot_state_t *bs); +int AINode_Seek_ActivateEntity(bot_state_t *bs); +int AINode_Seek_NBG(bot_state_t *bs); +int AINode_Seek_LTG(bot_state_t *bs); +int AINode_Battle_Fight(bot_state_t *bs); +int AINode_Battle_Chase(bot_state_t *bs); +int AINode_Battle_Retreat(bot_state_t *bs); +int AINode_Battle_NBG(bot_state_t *bs); + +void BotResetNodeSwitches(void); +void BotDumpNodeSwitches(bot_state_t *bs); + diff --git a/quake3/source/code/game/ai_dmq3.c b/quake3/source/code/game/ai_dmq3.c new file mode 100644 index 0000000..32aca4f --- /dev/null +++ b/quake3/source/code/game/ai_dmq3.c @@ -0,0 +1,5799 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmq3.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_dmq3.c $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" // sos001205 - for q3_ui also + +// from aasfile.h +#define AREACONTENTS_MOVER 1024 +#define AREACONTENTS_MODELNUMSHIFT 24 +#define AREACONTENTS_MAXMODELNUM 0xFF +#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) + +#define IDEAL_ATTACKDIST 140 + +#define MAX_WAYPOINTS 128 +// +bot_waypoint_t botai_waypoints[MAX_WAYPOINTS]; +bot_waypoint_t *botai_freewaypoints; + +//NOTE: not using a cvars which can be updated because the game should be reloaded anyway +int gametype; //game type +int maxclients; //maximum number of clients + +vmCvar_t bot_grapple; +vmCvar_t bot_rocketjump; +vmCvar_t bot_fastchat; +vmCvar_t bot_nochat; +vmCvar_t bot_testrchat; +vmCvar_t bot_challenge; +vmCvar_t bot_predictobstacles; +vmCvar_t g_spSkill; + +//PKMOD - Ergodic 02/11/02 - add dragon cvar +vmCvar_t bot_dragon; + +extern vmCvar_t bot_developer; + +vec3_t lastteleport_origin; //last teleport event origin +float lastteleport_time; //last teleport event time +int max_bspmodelindex; //maximum BSP model index + +//CTF flag goals +bot_goal_t ctf_redflag; +bot_goal_t ctf_blueflag; +#ifdef MISSIONPACK +bot_goal_t ctf_neutralflag; +bot_goal_t redobelisk; +bot_goal_t blueobelisk; +bot_goal_t neutralobelisk; +#endif + +#define MAX_ALTROUTEGOALS 32 + +int altroutegoals_setup; +aas_altroutegoal_t red_altroutegoals[MAX_ALTROUTEGOALS]; +int red_numaltroutegoals; +aas_altroutegoal_t blue_altroutegoals[MAX_ALTROUTEGOALS]; +int blue_numaltroutegoals; + + +/* +================== +BotSetUserInfo +================== +*/ +void BotSetUserInfo(bot_state_t *bs, char *key, char *value) { + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, key, value); + trap_SetUserinfo(bs->client, userinfo); + ClientUserinfoChanged( bs->client ); +} + +/* +================== +BotCTFCarryingFlag +================== +*/ +int BotCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_CTF) return CTF_FLAG_NONE; + + if (bs->inventory[INVENTORY_REDFLAG] > 0) return CTF_FLAG_RED; + else if (bs->inventory[INVENTORY_BLUEFLAG] > 0) return CTF_FLAG_BLUE; + return CTF_FLAG_NONE; +} + +/* +================== +BotTeam +================== +*/ +int BotTeam(bot_state_t *bs) { + char info[1024]; + + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotCTFTeam: client out of range\n"); + return qfalse; + } + trap_GetConfigstring(CS_PLAYERS+bs->client, info, sizeof(info)); + // + if (atoi(Info_ValueForKey(info, "t")) == TEAM_RED) return TEAM_RED; + else if (atoi(Info_ValueForKey(info, "t")) == TEAM_BLUE) return TEAM_BLUE; + return TEAM_FREE; +} + +/* +================== +BotOppositeTeam +================== +*/ +int BotOppositeTeam(bot_state_t *bs) { + switch(BotTeam(bs)) { + case TEAM_RED: return TEAM_BLUE; + case TEAM_BLUE: return TEAM_RED; + default: return TEAM_FREE; + } +} + +/* +================== +BotEnemyFlag +================== +*/ +bot_goal_t *BotEnemyFlag(bot_state_t *bs) { + if (BotTeam(bs) == TEAM_RED) { + return &ctf_blueflag; + } + else { + return &ctf_redflag; + } +} + +/* +================== +BotTeamFlag +================== +*/ +bot_goal_t *BotTeamFlag(bot_state_t *bs) { + if (BotTeam(bs) == TEAM_RED) { + return &ctf_redflag; + } + else { + return &ctf_blueflag; + } +} + + +/* +================== +EntityIsDead +================== +*/ +qboolean EntityIsDead(aas_entityinfo_t *entinfo) { + playerState_t ps; + + if (entinfo->number >= 0 && entinfo->number < MAX_CLIENTS) { + //retrieve the current client state + BotAI_GetClientState( entinfo->number, &ps ); + if (ps.pm_type != PM_NORMAL) return qtrue; + } + return qfalse; +} + +/* +================== +EntityCarriesFlag +================== +*/ +qboolean EntityCarriesFlag(aas_entityinfo_t *entinfo) { + if ( entinfo->powerups & ( 1 << PW_REDFLAG ) ) + return qtrue; + if ( entinfo->powerups & ( 1 << PW_BLUEFLAG ) ) + return qtrue; +#ifdef MISSIONPACK + if ( entinfo->powerups & ( 1 << PW_NEUTRALFLAG ) ) + return qtrue; +#endif + return qfalse; +} + +/* +================== +EntityIsInvisible +================== +*/ +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo) { + // the flag is always visible + if (EntityCarriesFlag(entinfo)) { + return qfalse; + } + if (entinfo->powerups & (1 << PW_INVIS)) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsShooting +================== +*/ +qboolean EntityIsShooting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_FIRING) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityIsChatting +================== +*/ +qboolean EntityIsChatting(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_TALK) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityHasQuad +================== +*/ +qboolean EntityHasQuad(aas_entityinfo_t *entinfo) { + if (entinfo->powerups & (1 << PW_QUAD)) { + return qtrue; + } + return qfalse; +} + +#ifdef MISSIONPACK +/* +================== +EntityHasKamikze +================== +*/ +qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo) { + if (entinfo->flags & EF_KAMIKAZE) { + return qtrue; + } + return qfalse; +} + +/* +================== +EntityCarriesCubes +================== +*/ +qboolean EntityCarriesCubes(aas_entityinfo_t *entinfo) { + entityState_t state; + + if (gametype != GT_HARVESTER) + return qfalse; + //FIXME: get this info from the aas_entityinfo_t ? + BotAI_GetEntityState(entinfo->number, &state); + if (state.generic1 > 0) + return qtrue; + return qfalse; +} + +/* +================== +Bot1FCTFCarryingFlag +================== +*/ +int Bot1FCTFCarryingFlag(bot_state_t *bs) { + if (gametype != GT_1FCTF) return qfalse; + + if (bs->inventory[INVENTORY_NEUTRALFLAG] > 0) return qtrue; + return qfalse; +} + +/* +================== +BotHarvesterCarryingCubes +================== +*/ +int BotHarvesterCarryingCubes(bot_state_t *bs) { + if (gametype != GT_HARVESTER) return qfalse; + + if (bs->inventory[INVENTORY_REDCUBE] > 0) return qtrue; + if (bs->inventory[INVENTORY_BLUECUBE] > 0) return qtrue; + return qfalse; +} +#endif + +/* +================== +BotRememberLastOrderedTask +================== +*/ +void BotRememberLastOrderedTask(bot_state_t *bs) { + if (!bs->ordered) { + return; + } + bs->lastgoal_decisionmaker = bs->decisionmaker; + bs->lastgoal_ltgtype = bs->ltgtype; + memcpy(&bs->lastgoal_teamgoal, &bs->teamgoal, sizeof(bot_goal_t)); + bs->lastgoal_teammate = bs->teammate; +} + +/* +================== +BotSetTeamStatus +================== +*/ +void BotSetTeamStatus(bot_state_t *bs) { +#ifdef MISSIONPACK + int teamtask; + aas_entityinfo_t entinfo; + + teamtask = TEAMTASK_PATROL; + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + break; + case LTG_TEAMACCOMPANY: + BotEntityInfo(bs->teammate, &entinfo); + if ( ( (gametype == GT_CTF || gametype == GT_1FCTF) && EntityCarriesFlag(&entinfo)) + || ( gametype == GT_HARVESTER && EntityCarriesCubes(&entinfo)) ) { + teamtask = TEAMTASK_ESCORT; + } + else { + teamtask = TEAMTASK_FOLLOW; + } + break; + case LTG_DEFENDKEYAREA: + teamtask = TEAMTASK_DEFENSE; + break; + case LTG_GETFLAG: + teamtask = TEAMTASK_OFFENSE; + break; + case LTG_RUSHBASE: + teamtask = TEAMTASK_DEFENSE; + break; + case LTG_RETURNFLAG: + teamtask = TEAMTASK_RETRIEVE; + break; + case LTG_CAMP: + case LTG_CAMPORDER: + teamtask = TEAMTASK_CAMP; + break; + case LTG_PATROL: + teamtask = TEAMTASK_PATROL; + break; + case LTG_GETITEM: + teamtask = TEAMTASK_PATROL; + break; + case LTG_KILL: + teamtask = TEAMTASK_PATROL; + break; + case LTG_HARVEST: + teamtask = TEAMTASK_OFFENSE; + break; + case LTG_ATTACKENEMYBASE: + teamtask = TEAMTASK_OFFENSE; + break; + default: + teamtask = TEAMTASK_PATROL; + break; + } + BotSetUserInfo(bs, "teamtask", va("%d", teamtask)); +#endif +} + +/* +================== +BotSetLastOrderedTask +================== +*/ +int BotSetLastOrderedTask(bot_state_t *bs) { + + if (gametype == GT_CTF) { + // don't go back to returning the flag if it's at the base + if ( bs->lastgoal_ltgtype == LTG_RETURNFLAG ) { + if ( BotTeam(bs) == TEAM_RED ) { + if ( bs->redflagstatus == 0 ) { + bs->lastgoal_ltgtype = 0; + } + } + else { + if ( bs->blueflagstatus == 0 ) { + bs->lastgoal_ltgtype = 0; + } + } + } + } + + if ( bs->lastgoal_ltgtype ) { + bs->decisionmaker = bs->lastgoal_decisionmaker; + bs->ordered = qtrue; + bs->ltgtype = bs->lastgoal_ltgtype; + memcpy(&bs->teamgoal, &bs->lastgoal_teamgoal, sizeof(bot_goal_t)); + bs->teammate = bs->lastgoal_teammate; + bs->teamgoal_time = FloatTime() + 300; + BotSetTeamStatus(bs); + // + if ( gametype == GT_CTF ) { + if ( bs->ltgtype == LTG_GETFLAG ) { + bot_goal_t *tb, *eb; + int tt, et; + + tb = BotTeamFlag(bs); + eb = BotEnemyFlag(bs); + tt = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, tb->areanum, TFL_DEFAULT); + et = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, eb->areanum, TFL_DEFAULT); + // if the travel time towards the enemy base is larger than towards our base + if (et > tt) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + } + } + return qtrue; + } + return qfalse; +} + +/* +================== +BotRefuseOrder +================== +*/ +void BotRefuseOrder(bot_state_t *bs) { + if (!bs->ordered) + return; + // if the bot was ordered to do something + if ( bs->order_time && bs->order_time > FloatTime() - 10 ) { + trap_EA_Action(bs->client, ACTION_NEGATIVE); + BotVoiceChat(bs, bs->decisionmaker, VOICECHAT_NO); + bs->order_time = 0; + } +} + +/* +================== +BotCTFSeekGoals +================== +*/ +void BotCTFSeekGoals(bot_state_t *bs) { + float rnd, l1, l2; + int flagstatus, c; + vec3_t dir; + aas_entityinfo_t entinfo; + + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + switch(BotTeam(bs)) { + case TEAM_RED: VectorSubtract(bs->origin, ctf_blueflag.origin, dir); break; + case TEAM_BLUE: VectorSubtract(bs->origin, ctf_redflag.origin, dir); break; + default: VectorSet(dir, 999, 999, 999); break; + } + // if the bot picked up the flag very close to the enemy base + if ( VectorLength(dir) < 128 ) { + // get an alternative route goal through the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } else { + // don't use any alt route goal, just get the hell out of the base + bs->altroutegoal.areanum = 0; + } + BotSetUserInfo(bs, "teamtask", va("%d", TEAMTASK_OFFENSE)); + BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); + } + else if (bs->rushbaseaway_time > FloatTime()) { + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus; + else flagstatus = bs->blueflagstatus; + //if the flag is back + if (flagstatus == 0) { + bs->rushbaseaway_time = 0; + } + } + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesFlag(&entinfo)) { + bs->ltgtype = 0; + } + } + // + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + //if our team has the enemy flag and our flag is at the base + if (flagstatus == 1) { + // + if (bs->owndecision_time < FloatTime()) { + //if Not defending the base already + if (!(bs->ltgtype == LTG_DEFENDKEYAREA && + (bs->teamgoal.number == ctf_redflag.number || + bs->teamgoal.number == ctf_blueflag.number))) { + //if there is a visible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0 && + // and not already following the team mate flag carrier + (bs->ltgtype != LTG_TEAMACCOMPANY || bs->teammate != c)) { + // + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + } + return; + } + //if the enemy has our flag + else if (flagstatus == 2) { + // + if (bs->owndecision_time < FloatTime()) { + //if enemy flag carrier is visible + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + //FIXME: fight enemy flag carrier + } + //if not already doing something important + if (bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_RETURNFLAG && + bs->ltgtype != LTG_TEAMHELP && + bs->ltgtype != LTG_TEAMACCOMPANY && + bs->ltgtype != LTG_CAMPORDER && + bs->ltgtype != LTG_PATROL && + bs->ltgtype != LTG_GETITEM) { + + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (random() < 0.5) { + //go for the enemy flag + bs->ltgtype = LTG_GETFLAG; + } + else { + bs->ltgtype = LTG_RETURNFLAG; + } + //no team message + bs->teammessage_time = 0; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + return; + } + //if both flags Not at their bases + else if (flagstatus == 3) { + // + if (bs->owndecision_time < FloatTime()) { + // if not trying to return the flag and not following the team flag carrier + if ( bs->ltgtype != LTG_RETURNFLAG && bs->ltgtype != LTG_TEAMACCOMPANY ) { + // + c = BotTeamFlagCarrierVisible(bs); + // if there is a visible team mate flag carrier + if (c >= 0) { + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + // + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + else { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get the enemy flag + bs->teammessage_time = FloatTime() + 2 * random(); + //get the flag + bs->ltgtype = LTG_RETURNFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + // if the bot decided to do something on it's own and has a last ordered goal + if ( !bs->ordered && bs->lastgoal_ltgtype ) { + bs->ltgtype = 0; + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + // + if (bs->owndecision_time > FloatTime()) + return;; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } + bs->owndecision_time = FloatTime() + 5; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotCTFRetreatGoals +================== +*/ +void BotCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the base + if (BotCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotSetTeamStatus(bs); + } + } +} + +#ifdef MISSIONPACK +/* +================== +Bot1FCTFSeekGoals +================== +*/ +void Bot1FCTFSeekGoals(bot_state_t *bs) { + aas_entityinfo_t entinfo; + float rnd, l1, l2; + int c; + + //when carrying a flag in ctf the bot should rush to the base + if (Bot1FCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + BotVoiceChat(bs, -1, VOICECHAT_IHAVEFLAG); + } + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesFlag(&entinfo)) { + bs->ltgtype = 0; + } + } + //our team has the flag + if (bs->neutralflagstatus == 1) { + if (bs->owndecision_time < FloatTime()) { + // if not already following someone + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is a visible team mate flag carrier + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotRefuseOrder(bs); + //follow the flag carrier + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + return; + } + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + //if not already attacking the enemy base + if (bs->ltgtype != LTG_ATTACKENEMYBASE) { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + return; + } + //enemy team has the flag + else if (bs->neutralflagstatus == 2) { + if (bs->owndecision_time < FloatTime()) { + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + //FIXME: attack enemy flag carrier + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_GETITEM) { + return; + } + // if not already defending the base + if (bs->ltgtype != LTG_DEFENDKEYAREA) { + BotRefuseOrder(bs); + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + bs->owndecision_time = FloatTime() + 5; + } + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + // if the bot decided to do something on it's own and has a last ordered goal + if ( !bs->ordered && bs->lastgoal_ltgtype ) { + bs->ltgtype = 0; + } + //if already a CTF or team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + // + if (bs->owndecision_time > FloatTime()) + return;; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && ctf_neutralflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + bs->ltgtype = LTG_GETFLAG; + //set the time the bot will stop getting the flag + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + BotSetTeamStatus(bs); + } + else if (rnd < l2 && ctf_redflag.areanum && ctf_blueflag.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } + bs->owndecision_time = FloatTime() + 5; +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +Bot1FCTFRetreatGoals +================== +*/ +void Bot1FCTFRetreatGoals(bot_state_t *bs) { + //when carrying a flag in ctf the bot should rush to the enemy base + if (Bot1FCTFCarryingFlag(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + } +} + +/* +================== +BotObeliskSeekGoals +================== +*/ +void BotObeliskSeekGoals(bot_state_t *bs) { + float rnd, l1, l2; + + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + //if already a team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_RETURNFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + //get the flag or defend the base + rnd = random(); + if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the time the bot will stop attacking the enemy base + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + //get an alternate route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + BotSetTeamStatus(bs); + } + else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } +} + +/* +================== +BotGoHarvest +================== +*/ +void BotGoHarvest(bot_state_t *bs) { + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the time the bot will stop harvesting + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + BotSetTeamStatus(bs); +} + +/* +================== +BotObeliskRetreatGoals +================== +*/ +void BotObeliskRetreatGoals(bot_state_t *bs) { + //nothing special +} + +/* +================== +BotHarvesterSeekGoals +================== +*/ +void BotHarvesterSeekGoals(bot_state_t *bs) { + aas_entityinfo_t entinfo; + float rnd, l1, l2; + int c; + + //when carrying cubes in harvester the bot should rush to the base + if (BotHarvesterCarryingCubes(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + // + BotSetTeamStatus(bs); + } + return; + } + // don't just do something wait for the bot team leader to give orders + if (BotTeamLeader(bs)) { + return; + } + // if the bot decided to follow someone + if ( bs->ltgtype == LTG_TEAMACCOMPANY && !bs->ordered ) { + // if the team mate being accompanied no longer carries the flag + BotEntityInfo(bs->teammate, &entinfo); + if (!EntityCarriesCubes(&entinfo)) { + bs->ltgtype = 0; + } + } + // if the bot is ordered to do something + if ( bs->lastgoal_ltgtype ) { + bs->teamgoal_time += 60; + } + //if not yet doing something + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL || + bs->ltgtype == LTG_ATTACKENEMYBASE || + bs->ltgtype == LTG_HARVEST || + bs->ltgtype == LTG_GETITEM || + bs->ltgtype == LTG_MAKELOVE_UNDER || + bs->ltgtype == LTG_MAKELOVE_ONTOP) { + return; + } + // + if (BotSetLastOrderedTask(bs)) + return; + //if the bot is roaming + if (bs->ctfroam_time > FloatTime()) + return; + //if the bot has anough aggression to decide what to do + if (BotAggression(bs) < 50) + return; + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + // + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) { + //FIXME: attack enemy cube carrier + } + if (bs->ltgtype != LTG_TEAMACCOMPANY) { + //if there is a visible team mate carrying cubes + c = BotTeamCubeCarrierVisible(bs); + if (c >= 0) { + //follow the team mate carrying cubes + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + //the team mate + bs->teammate = c; + //last time the team mate was visible + bs->teammatevisible_time = FloatTime(); + //no message + bs->teammessage_time = 0; + //no arrive message + bs->arrive_time = 1; + // + BotVoiceChat(bs, bs->teammate, VOICECHAT_ONFOLLOW); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + BotSetTeamStatus(bs); + return; + } + } + // + if (bs->teamtaskpreference & (TEAMTP_ATTACKER|TEAMTP_DEFENDER)) { + if (bs->teamtaskpreference & TEAMTP_ATTACKER) { + l1 = 0.7f; + } + else { + l1 = 0.2f; + } + l2 = 0.9f; + } + else { + l1 = 0.4f; + l2 = 0.7f; + } + // + rnd = random(); + if (rnd < l1 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotGoHarvest(bs); + } + else if (rnd < l2 && redobelisk.areanum && blueobelisk.areanum) { + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + // + if (BotTeam(bs) == TEAM_RED) memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); + else memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //set the time the bot stops defending the base + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + bs->defendaway_time = 0; + BotSetTeamStatus(bs); + } + else { + bs->ltgtype = 0; + //set the time the bot will stop roaming + bs->ctfroam_time = FloatTime() + CTF_ROAM_TIME; + BotSetTeamStatus(bs); + } +} + +/* +================== +BotHarvesterRetreatGoals +================== +*/ +void BotHarvesterRetreatGoals(bot_state_t *bs) { + //when carrying cubes in harvester the bot should rush to the base + if (BotHarvesterCarryingCubes(bs)) { + //if not already rushing to the base + if (bs->ltgtype != LTG_RUSHBASE) { + BotRefuseOrder(bs); + bs->ltgtype = LTG_RUSHBASE; + bs->teamgoal_time = FloatTime() + CTF_RUSHBASE_TIME; + bs->rushbaseaway_time = 0; + bs->decisionmaker = bs->client; + bs->ordered = qfalse; + BotSetTeamStatus(bs); + } + return; + } +} +#endif + +/* +================== +BotTeamGoals +================== +*/ +void BotTeamGoals(bot_state_t *bs, int retreat) { + + if ( retreat ) { + if (gametype == GT_CTF) { + BotCTFRetreatGoals(bs); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + Bot1FCTFRetreatGoals(bs); + } + else if (gametype == GT_OBELISK) { + BotObeliskRetreatGoals(bs); + } + else if (gametype == GT_HARVESTER) { + BotHarvesterRetreatGoals(bs); + } +#endif + } + else { + if (gametype == GT_CTF) { + //decide what to do in CTF mode + BotCTFSeekGoals(bs); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + Bot1FCTFSeekGoals(bs); + } + else if (gametype == GT_OBELISK) { + BotObeliskSeekGoals(bs); + } + else if (gametype == GT_HARVESTER) { + BotHarvesterSeekGoals(bs); + } +#endif + } + // reset the order time which is used to see if + // we decided to refuse an order + bs->order_time = 0; +} + +/* +================== +BotPointAreaNum +================== +*/ +int BotPointAreaNum(vec3_t origin) { + int areanum, numareas, areas[10]; + vec3_t end; + + areanum = trap_AAS_PointAreaNum(origin); + if (areanum) return areanum; + VectorCopy(origin, end); + end[2] += 10; + numareas = trap_AAS_TraceAreas(origin, end, areas, NULL, 10); + if (numareas > 0) return areas[0]; + return 0; +} + +/* +================== +ClientName +================== +*/ +char *ClientName(int client, char *name, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + //PKMOD - Ergodic 01/07/02 - modify message to display client for error with /addbot doom + BotAI_Print(PRT_ERROR, "ClientName: client out of range: Client Number:%d\n", client); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(name, Info_ValueForKey(buf, "n"), size-1); + name[size-1] = '\0'; + Q_CleanStr( name ); + return name; +} + +/* +================== +ClientSkin +================== +*/ +char *ClientSkin(int client, char *skin, int size) { + char buf[MAX_INFO_STRING]; + + if (client < 0 || client >= MAX_CLIENTS) { + BotAI_Print(PRT_ERROR, "ClientSkin: client out of range\n"); + return "[client out of range]"; + } + trap_GetConfigstring(CS_PLAYERS+client, buf, sizeof(buf)); + strncpy(skin, Info_ValueForKey(buf, "model"), size-1); + skin[size-1] = '\0'; + return skin; +} + +/* +================== +ClientFromName +================== +*/ +int ClientFromName(char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + } + return -1; +} + +/* +================== +ClientOnSameTeamFromName +================== +*/ +int ClientOnSameTeamFromName(bot_state_t *bs, char *name) { + int i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (!BotSameTeam(bs, i)) + continue; + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + Q_CleanStr( buf ); + if (!Q_stricmp(Info_ValueForKey(buf, "n"), name)) return i; + } + return -1; +} + +/* +================== +stristr +================== +*/ +char *stristr(char *str, char *charset) { + int i; + + while(*str) { + for (i = 0; charset[i] && str[i]; i++) { + if (toupper(charset[i]) != toupper(str[i])) break; + } + if (!charset[i]) return str; + str++; + } + return NULL; +} + +/* +================== +EasyClientName +================== +*/ +char *EasyClientName(int client, char *buf, int size) { + int i; + char *str1, *str2, *ptr, c; + char name[128]; + + strcpy(name, ClientName(client, name, sizeof(name))); + for (i = 0; name[i]; i++) name[i] &= 127; + //remove all spaces + for (ptr = strstr(name, " "); ptr; ptr = strstr(name, " ")) { + memmove(ptr, ptr+1, strlen(ptr+1)+1); + } + //check for [x] and ]x[ clan names + str1 = strstr(name, "["); + str2 = strstr(name, "]"); + if (str1 && str2) { + if (str2 > str1) memmove(str1, str2+1, strlen(str2+1)+1); + else memmove(str2, str1+1, strlen(str1+1)+1); + } + //remove Mr prefix + if ((name[0] == 'm' || name[0] == 'M') && + (name[1] == 'r' || name[1] == 'R')) { + memmove(name, name+2, strlen(name+2)+1); + } + //only allow lower case alphabet characters + ptr = name; + while(*ptr) { + c = *ptr; + if ((c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '_') { + ptr++; + } + else if (c >= 'A' && c <= 'Z') { + *ptr += 'a' - 'A'; + ptr++; + } + else { + memmove(ptr, ptr+1, strlen(ptr + 1)+1); + } + } + strncpy(buf, name, size-1); + buf[size-1] = '\0'; + return buf; +} + +/* +================== +BotSynonymContext +================== +*/ +int BotSynonymContext(bot_state_t *bs) { + int context; + + context = CONTEXT_NORMAL|CONTEXT_NEARBYITEM|CONTEXT_NAMES; + // + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_CTFREDTEAM; + else context |= CONTEXT_CTFBLUETEAM; + } +#ifdef MISSIONPACK + else if (gametype == GT_OBELISK) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_OBELISKREDTEAM; + else context |= CONTEXT_OBELISKBLUETEAM; + } + else if (gametype == GT_HARVESTER) { + if (BotTeam(bs) == TEAM_RED) context |= CONTEXT_HARVESTERREDTEAM; + else context |= CONTEXT_HARVESTERBLUETEAM; + } +#endif + return context; +} + +/* +================== +BotEatBeans +PKMOD - Ergodic 04/15/01 - bots will select beans at low health times +================== +*/ +void BotEatBeans(bot_state_t *bs) { + //PKMOD - Ergodic 04/15/01 - debug bot name + char netname[MAX_NETNAME]; + + //PKMOD - Ergodic 04/15/01 - debug bot name + ClientName(bs->client, netname, sizeof(netname)); + + + if (bs->inventory[INVENTORY_HEALTH] < 70) { + //PKMOD - Ergodic 04/15/01 - debug beans eating (inactive) +// Com_Printf("BotEatBeans - health>%d<, beans>%d<\n", bs->inventory[INVENTORY_HEALTH], bs->inventory[INVENTORY_BEANS]); + + if (bs->inventory[INVENTORY_BEANS] > 0) { + //PKMOD - Ergodic 04/15/01 - debug beans eating (inactive) +// Com_Printf("BotEatBeans - beans in inventory\n"); + if (bs->weaponnum != WP_BEANS) { + + bs->weaponchange_time = FloatTime(); + bs->weaponnum = WP_BEANS; + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + + //co-op method for rocket jump (hack) [NOPE - it does not work] +// bs->tfl |= TFL_ROCKETJUMP; + + //if bot has an enemy then use beans + if (bs->enemy >= 0) { + //PKMOD - Ergodic 04/15/01 - debug beans eating (inactive) +// Com_Printf("BotEatBeans - %s: Eating my beans\n", netname); + trap_EA_Attack(bs->client); + } + } + } + } +} + +/* +================== +BotChooseWeapon +================== +*/ +void BotChooseWeapon(bot_state_t *bs) { + int newweaponnum; + + if (bs->cur_ps.weaponstate == WEAPON_RAISING || + bs->cur_ps.weaponstate == WEAPON_DROPPING) { + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } + else { + newweaponnum = trap_BotChooseBestFightWeapon(bs->ws, bs->inventory); + if (bs->weaponnum != newweaponnum) bs->weaponchange_time = FloatTime(); + bs->weaponnum = newweaponnum; + //BotAI_Print(PRT_MESSAGE, "bs->weaponnum = %d\n", bs->weaponnum); + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + } +} + +/* +================== +BotSetupForMovement +================== +*/ +void BotSetupForMovement(bot_state_t *bs) { + bot_initmove_t initmove; + + memset(&initmove, 0, sizeof(bot_initmove_t)); + VectorCopy(bs->cur_ps.origin, initmove.origin); + VectorCopy(bs->cur_ps.velocity, initmove.velocity); + VectorClear(initmove.viewoffset); + initmove.viewoffset[2] += bs->cur_ps.viewheight; + initmove.entitynum = bs->entitynum; + initmove.client = bs->client; + initmove.thinktime = bs->thinktime; + //set the onground flag + if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE) initmove.or_moveflags |= MFL_ONGROUND; + //set the teleported flag + if ((bs->cur_ps.pm_flags & PMF_TIME_KNOCKBACK) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_TELEPORTED; + } + //set the waterjump flag + if ((bs->cur_ps.pm_flags & PMF_TIME_WATERJUMP) && (bs->cur_ps.pm_time > 0)) { + initmove.or_moveflags |= MFL_WATERJUMP; + } + //set presence type + if (bs->cur_ps.pm_flags & PMF_DUCKED) initmove.presencetype = PRESENCE_CROUCH; + else initmove.presencetype = PRESENCE_NORMAL; + // + if (bs->walker > 0.5) initmove.or_moveflags |= MFL_WALK; + // + VectorCopy(bs->viewangles, initmove.viewangles); + // + trap_BotInitMoveState(bs->ms, &initmove); +} + +/* +================== +BotCheckItemPickup +================== +*/ +void BotCheckItemPickup(bot_state_t *bs, int *oldinventory) { +#ifdef MISSIONPACK + int offence, leader; + + if (gametype <= GT_TEAM) + return; + + offence = -1; + // go into offence if picked up the kamikaze or invulnerability + if (!oldinventory[INVENTORY_KAMIKAZE] && bs->inventory[INVENTORY_KAMIKAZE] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_INVULNERABILITY] && bs->inventory[INVENTORY_INVULNERABILITY] >= 1) { + offence = qtrue; + } + // if not already wearing the kamikaze or invulnerability + if (!bs->inventory[INVENTORY_KAMIKAZE] && !bs->inventory[INVENTORY_INVULNERABILITY]) { + if (!oldinventory[INVENTORY_SCOUT] && bs->inventory[INVENTORY_SCOUT] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_GUARD] && bs->inventory[INVENTORY_GUARD] >= 1) { + offence = qtrue; + } + if (!oldinventory[INVENTORY_DOUBLER] && bs->inventory[INVENTORY_DOUBLER] >= 1) { + offence = qfalse; + } + if (!oldinventory[INVENTORY_AMMOREGEN] && bs->inventory[INVENTORY_AMMOREGEN] >= 1) { + offence = qfalse; + } + } + + if (offence >= 0) { + leader = ClientFromName(bs->teamleader); + if (offence) { + if (!(bs->teamtaskpreference & TEAMTP_ATTACKER)) { + // if we have a bot team leader + if (BotTeamLeader(bs)) { + // tell the leader we want to be on offence + BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); + //BotAI_BotInitialChat(bs, "wantoffence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + else if (g_spSkill.integer <= 3) { + if ( bs->ltgtype != LTG_GETFLAG && + bs->ltgtype != LTG_ATTACKENEMYBASE && + bs->ltgtype != LTG_HARVEST ) { + // + if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && + (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { + // tell the leader we want to be on offence + BotVoiceChat(bs, leader, VOICECHAT_WANTONOFFENSE); + //BotAI_BotInitialChat(bs, "wantoffence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + } + bs->teamtaskpreference |= TEAMTP_ATTACKER; + } + } + bs->teamtaskpreference &= ~TEAMTP_DEFENDER; + } + else { + if (!(bs->teamtaskpreference & TEAMTP_DEFENDER)) { + // if we have a bot team leader + if (BotTeamLeader(bs)) { + // tell the leader we want to be on defense + BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); + //BotAI_BotInitialChat(bs, "wantdefence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + else if (g_spSkill.integer <= 3) { + if ( bs->ltgtype != LTG_DEFENDKEYAREA ) { + // + if ((gametype != GT_CTF || (bs->redflagstatus == 0 && bs->blueflagstatus == 0)) && + (gametype != GT_1FCTF || bs->neutralflagstatus == 0) ) { + // tell the leader we want to be on defense + BotVoiceChat(bs, -1, VOICECHAT_WANTONDEFENSE); + //BotAI_BotInitialChat(bs, "wantdefence", NULL); + //trap_BotEnterChat(bs->cs, leader, CHAT_TELL); + } + } + } + bs->teamtaskpreference |= TEAMTP_DEFENDER; + } + bs->teamtaskpreference &= ~TEAMTP_ATTACKER; + } + } +#endif +} + +/* +================== +BotUpdateInventory +================== +*/ +void BotUpdateInventory(bot_state_t *bs) { + int oldinventory[MAX_ITEMS]; + + memcpy(oldinventory, bs->inventory, sizeof(oldinventory)); + //armor + bs->inventory[INVENTORY_ARMOR] = bs->cur_ps.stats[STAT_ARMOR]; + //weapons + bs->inventory[INVENTORY_GAUNTLET] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GAUNTLET)) != 0; + bs->inventory[INVENTORY_SHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SHOTGUN)) != 0; + bs->inventory[INVENTORY_MACHINEGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_MACHINEGUN)) != 0; + bs->inventory[INVENTORY_GRENADELAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRENADE_LAUNCHER)) != 0; + bs->inventory[INVENTORY_ROCKETLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_ROCKET_LAUNCHER)) != 0; + bs->inventory[INVENTORY_LIGHTNING] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_LIGHTNING)) != 0; + bs->inventory[INVENTORY_RAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_RAILGUN)) != 0; + + //PKMOD - Ergodic 09/07/00 add PainKeepArena weapons + bs->inventory[INVENTORY_GRAVITY] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAVITY)) != 0; + bs->inventory[INVENTORY_SENTRY] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SENTRY)) != 0; + bs->inventory[INVENTORY_TRAP] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BEARTRAP)) != 0; + bs->inventory[INVENTORY_BEANS] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BEANS)) != 0; + bs->inventory[INVENTORY_AIRFIST] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_AIRFIST)) != 0; + //PKMOD - Ergodic - 12/17/00 rename NAILGUN to PKA_NAILGUN to resolve 1.27g conflict + bs->inventory[INVENTORY_PKA_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0; + + + //PKMOD - Ergodic 09/07/00 remove plasma gun from PKA +// bs->inventory[INVENTORY_PLASMAGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PLASMAGUN)) != 0; + //PKMOD - Ergodic 09/07/00 remove BFG gun from PKA +// bs->inventory[INVENTORY_BFG10K] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_BFG)) != 0; + + bs->inventory[INVENTORY_GRAPPLINGHOOK] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_GRAPPLING_HOOK)) != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NAILGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_NAILGUN)) != 0;; + bs->inventory[INVENTORY_PROXLAUNCHER] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_PROX_LAUNCHER)) != 0;; + bs->inventory[INVENTORY_CHAINGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_CHAINGUN)) != 0;; +#endif + //ammo + bs->inventory[INVENTORY_SHELLS] = bs->cur_ps.ammo[WP_SHOTGUN]; + bs->inventory[INVENTORY_BULLETS] = bs->cur_ps.ammo[WP_MACHINEGUN]; + bs->inventory[INVENTORY_GRENADES] = bs->cur_ps.ammo[WP_GRENADE_LAUNCHER]; + //PKMOD - Ergodic 04/14/01 remove cells from PKA +// bs->inventory[INVENTORY_CELLS] = bs->cur_ps.ammo[WP_PLASMAGUN]; + bs->inventory[INVENTORY_LIGHTNINGAMMO] = bs->cur_ps.ammo[WP_LIGHTNING]; + bs->inventory[INVENTORY_ROCKETS] = bs->cur_ps.ammo[WP_ROCKET_LAUNCHER]; + bs->inventory[INVENTORY_SLUGS] = bs->cur_ps.ammo[WP_RAILGUN]; + //PKMOD - Ergodic 04/14/01 remove bfg ammo from PKA +// bs->inventory[INVENTORY_BFGAMMO] = bs->cur_ps.ammo[WP_BFG]; + + //PKMOD - Ergodic 09/07/00 add PainKeepArena ammo + bs->inventory[INVENTORY_GRAVITY_AMMO] = bs->cur_ps.ammo[WP_GRAVITY]; + bs->inventory[INVENTORY_SENTRY_AMMO] = bs->cur_ps.ammo[WP_SENTRY]; + bs->inventory[INVENTORY_TRAP_AMMO] = bs->cur_ps.ammo[WP_BEARTRAP]; + //PKMOD - Ergodic - 12/17/00 rename NAILS to PKA_NAILS to resolve 1.27g conflict + bs->inventory[INVENTORY_PKA_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; + + //PKMOD - Ergodic - 04/14/01 - add explosive shells - duh + bs->inventory[INVENTORY_EXPLOSIVESHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_EXPLODING_SHELLS)) != 0; + bs->inventory[INVENTORY_EXPLOSIVESHELLS] = bs->cur_ps.ammo[WP_EXPLODING_SHELLS]; + +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NAILS] = bs->cur_ps.ammo[WP_NAILGUN]; + bs->inventory[INVENTORY_MINES] = bs->cur_ps.ammo[WP_PROX_LAUNCHER]; + bs->inventory[INVENTORY_BELT] = bs->cur_ps.ammo[WP_CHAINGUN]; +#endif + //powerups + bs->inventory[INVENTORY_HEALTH] = bs->cur_ps.stats[STAT_HEALTH]; + //PKMOD - Ergodic 05/11/01 - allow holding of more than 1 type of + // holdable but only 1 of each kind +// bs->inventory[INVENTORY_TELEPORTER] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_TELEPORTER; + bs->inventory[INVENTORY_TELEPORTER] = 0 < ( bs->cur_ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_TELEPORTER) ); +// bs->inventory[INVENTORY_MEDKIT] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_MEDKIT; + bs->inventory[INVENTORY_MEDKIT] = 0 < ( bs->cur_ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_MEDKIT) ); + //PKMOD - Ergodic 11/10/01 - new holdables + bs->inventory[INVENTORY_RADIATE] = 0 < ( bs->cur_ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_RADIATE) ); + //PKMOD - Ergodic 11/23/01 - new holdables + bs->inventory[INVENTORY_PERSENTRY] = 0 < ( bs->cur_ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_PERSENTRY) ); + //PKMOD - Ergodic 12/03/01 - New Holdables - Private Bot pieces (Legs, Torso, Head) + bs->inventory[INVENTORY_PRIBOTLEGS] = 0 < ( bs->cur_ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_BOTLEGS) ); + bs->inventory[INVENTORY_PRIBOTTORSO] = 0 < ( bs->cur_ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_BOTTORSO) ); + bs->inventory[INVENTORY_PRIBOTHEAD] = 0 < ( bs->cur_ps.stats[STAT_HOLDABLE_ITEM] & (1 << HI_BOTHEAD) ); + +#ifdef MISSIONPACK + bs->inventory[INVENTORY_KAMIKAZE] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_KAMIKAZE; + bs->inventory[INVENTORY_PORTAL] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_PORTAL; + bs->inventory[INVENTORY_INVULNERABILITY] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM] == MODELINDEX_INVULNERABILITY; +#endif + bs->inventory[INVENTORY_QUAD] = bs->cur_ps.powerups[PW_QUAD] != 0; + bs->inventory[INVENTORY_ENVIRONMENTSUIT] = bs->cur_ps.powerups[PW_BATTLESUIT] != 0; + bs->inventory[INVENTORY_HASTE] = bs->cur_ps.powerups[PW_HASTE] != 0; + bs->inventory[INVENTORY_INVISIBILITY] = bs->cur_ps.powerups[PW_INVIS] != 0; + bs->inventory[INVENTORY_REGEN] = bs->cur_ps.powerups[PW_REGEN] != 0; + bs->inventory[INVENTORY_FLIGHT] = bs->cur_ps.powerups[PW_FLIGHT] != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_SCOUT] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_SCOUT; + bs->inventory[INVENTORY_GUARD] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_GUARD; + bs->inventory[INVENTORY_DOUBLER] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_DOUBLER; + bs->inventory[INVENTORY_AMMOREGEN] = bs->cur_ps.stats[STAT_PERSISTANT_POWERUP] == MODELINDEX_AMMOREGEN; +#endif + bs->inventory[INVENTORY_REDFLAG] = bs->cur_ps.powerups[PW_REDFLAG] != 0; + bs->inventory[INVENTORY_BLUEFLAG] = bs->cur_ps.powerups[PW_BLUEFLAG] != 0; +#ifdef MISSIONPACK + bs->inventory[INVENTORY_NEUTRALFLAG] = bs->cur_ps.powerups[PW_NEUTRALFLAG] != 0; + if (BotTeam(bs) == TEAM_RED) { + bs->inventory[INVENTORY_REDCUBE] = bs->cur_ps.generic1; + bs->inventory[INVENTORY_BLUECUBE] = 0; + } + else { + bs->inventory[INVENTORY_REDCUBE] = 0; + bs->inventory[INVENTORY_BLUECUBE] = bs->cur_ps.generic1; + } +#endif + + // + //PKMOD - Ergodic 07/09/00 try to get bots to display the attached beartrap + bs->inventory[INVENTORY_BEARTRAPS_ATTACHED] = bs->cur_ps.stats[STAT_BEARTRAPS_ATTACHED]; + + //PKMOD - Ergodic 12/20/00 Debug bot state for attached beartraps (inactive) +// if (bs->inventory[INVENTORY_BEARTRAPS_ATTACHED] > 0) { +// Com_Printf("BotUpdateInventory - bot has beartrap attached\n" ); +// } + + BotCheckItemPickup(bs, oldinventory); +} + +/* +================== +BotUpdateBattleInventory +================== +*/ +void BotUpdateBattleInventory(bot_state_t *bs, int enemy) { + vec3_t dir; + aas_entityinfo_t entinfo; + + BotEntityInfo(enemy, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + bs->inventory[ENEMY_HEIGHT] = (int) dir[2]; + dir[2] = 0; + bs->inventory[ENEMY_HORIZONTAL_DIST] = (int) VectorLength(dir); + //FIXME: add num visible enemies and num visible team mates to the inventory +} + +#ifdef MISSIONPACK +/* +================== +BotUseKamikaze +================== +*/ +#define KAMIKAZE_DIST 1024 + +void BotUseKamikaze(bot_state_t *bs) { + int c, teammates, enemies; + aas_entityinfo_t entinfo; + vec3_t dir, target; + bot_goal_t *goal; + bsp_trace_t trace; + + //if the bot has no kamikaze + if (bs->inventory[INVENTORY_KAMIKAZE] <= 0) + return; + if (bs->kamikaze_time > FloatTime()) + return; + bs->kamikaze_time = FloatTime() + 0.2; + if (gametype == GT_CTF) { + //never use kamikaze if the team flag carrier is visible + if (BotCTFCarryingFlag(bs)) + return; + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_1FCTF) { + //never use kamikaze if the team flag carrier is visible + if (Bot1FCTFCarryingFlag(bs)) + return; + c = BotTeamFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_OBELISK) { + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST * 0.9)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_HARVESTER) { + // + if (BotHarvesterCarryingCubes(bs)) + return; + //never use kamikaze if a team mate carrying cubes is visible + c = BotTeamCubeCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) + return; + } + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) { + BotEntityInfo(c, &entinfo); + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) < Square(KAMIKAZE_DIST)) { + trap_EA_Use(bs->client); + return; + } + } + } + // + BotVisibleTeamMatesAndEnemies(bs, &teammates, &enemies, KAMIKAZE_DIST); + // + if (enemies > 2 && enemies > teammates+1) { + trap_EA_Use(bs->client); + return; + } +} + +/* +================== +BotUseInvulnerability +================== +*/ +void BotUseInvulnerability(bot_state_t *bs) { + int c; + vec3_t dir, target; + bot_goal_t *goal; + bsp_trace_t trace; + + //if the bot has no invulnerability + if (bs->inventory[INVENTORY_INVULNERABILITY] <= 0) + return; + if (bs->invulnerability_time > FloatTime()) + return; + bs->invulnerability_time = FloatTime() + 0.2; + if (gametype == GT_CTF) { + //never use kamikaze if the team flag carrier is visible + if (BotCTFCarryingFlag(bs)) + return; + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy flag and the flag is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &ctf_blueflag; break; + default: goal = &ctf_redflag; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_1FCTF) { + //never use kamikaze if the team flag carrier is visible + if (Bot1FCTFCarryingFlag(bs)) + return; + c = BotEnemyFlagCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy flag and the flag is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &ctf_blueflag; break; + default: goal = &ctf_redflag; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_OBELISK) { + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(300)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } + else if (gametype == GT_HARVESTER) { + // + if (BotHarvesterCarryingCubes(bs)) + return; + c = BotEnemyCubeCarrierVisible(bs); + if (c >= 0) + return; + //if near enemy base and enemy base is visible + switch(BotTeam(bs)) { + case TEAM_RED: goal = &blueobelisk; break; + default: goal = &redobelisk; break; + } + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + VectorSubtract(bs->origin, target, dir); + if (VectorLengthSquared(dir) < Square(200)) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + trap_EA_Use(bs->client); + return; + } + } + } +} +#endif + +/* +================== +BotBattleUseItems +================== +*/ +void BotBattleUseItems(bot_state_t *bs) { + //PKMOD - Ergodic 11/01/01 - add radiate holdable logic + if ( ( bs->inventory[INVENTORY_HEALTH] + bs->inventory[INVENTORY_ARMOR] ) > 75) { + if (bs->inventory[INVENTORY_RADIATE] > 0) { + //PKMOD - Ergodic 03/17/04 - set the active holdable item for the BOT + g_entities[bs->entitynum].client->ps.stats[STAT_ACTIVE_HOLDABLE] = HI_RADIATE; + trap_EA_Use(bs->client); + } + } + + //PKMOD - Ergodic 11/23/01 - add personal sentry holdable logic + if (bs->inventory[INVENTORY_PERSENTRY] > 0) { + //PKMOD - Ergodic 03/17/04 - set the active holdable item for the BOT + g_entities[bs->entitynum].client->ps.stats[STAT_ACTIVE_HOLDABLE] = HI_PERSENTRY; + trap_EA_Use(bs->client); + } + + //PKMOD - Ergodic 02/12/02 - add private bot holdable logic (must have all the parts) + if ( ( bs->inventory[INVENTORY_PRIBOTLEGS] + bs->inventory[INVENTORY_PRIBOTTORSO] + bs->inventory[INVENTORY_PRIBOTHEAD] ) == 3 ) { + //PKMOD - Ergodic 03/17/04 - set the active holdable item for the BOT + g_entities[bs->entitynum].client->ps.stats[STAT_ACTIVE_HOLDABLE] = HI_BOTHEAD; + trap_EA_Use(bs->client); + } + + if (bs->inventory[INVENTORY_HEALTH] < 40) { + if (bs->inventory[INVENTORY_TELEPORTER] > 0) { + if (!BotCTFCarryingFlag(bs) +#ifdef MISSIONPACK + && !Bot1FCTFCarryingFlag(bs) + && !BotHarvesterCarryingCubes(bs) +#endif + ) { + //PKMOD - Ergodic 03/17/04 - set the active holdable item for the BOT + g_entities[bs->entitynum].client->ps.stats[STAT_ACTIVE_HOLDABLE] = HI_TELEPORTER; + trap_EA_Use(bs->client); + } + } + } + if (bs->inventory[INVENTORY_HEALTH] < 60) { + if (bs->inventory[INVENTORY_MEDKIT] > 0) { + //PKMOD - Ergodic 03/17/04 - set the active holdable item for the BOT + g_entities[bs->entitynum].client->ps.stats[STAT_ACTIVE_HOLDABLE] = HI_MEDKIT; + trap_EA_Use(bs->client); + } + } +#ifdef MISSIONPACK + BotUseKamikaze(bs); + BotUseInvulnerability(bs); +#endif +} + +/* +================== +BotSetTeleportTime +================== +*/ +void BotSetTeleportTime(bot_state_t *bs) { + if ((bs->cur_ps.eFlags ^ bs->last_eFlags) & EF_TELEPORT_BIT) { + bs->teleport_time = FloatTime(); + } + bs->last_eFlags = bs->cur_ps.eFlags; +} + +/* +================== +BotIsDead +================== +*/ +qboolean BotIsDead(bot_state_t *bs) { + return (bs->cur_ps.pm_type == PM_DEAD); +} + +/* +================== +BotIsObserver +================== +*/ +qboolean BotIsObserver(bot_state_t *bs) { + char buf[MAX_INFO_STRING]; + if (bs->cur_ps.pm_type == PM_SPECTATOR) return qtrue; + trap_GetConfigstring(CS_PLAYERS+bs->client, buf, sizeof(buf)); + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) return qtrue; + return qfalse; +} + +/* +================== +BotIntermission +================== +*/ +qboolean BotIntermission(bot_state_t *bs) { + //NOTE: we shouldn't be looking at the game code... + if (level.intermissiontime) return qtrue; + return (bs->cur_ps.pm_type == PM_FREEZE || bs->cur_ps.pm_type == PM_INTERMISSION); +} + +/* +================== +BotInLavaOrSlime +================== +*/ +qboolean BotInLavaOrSlime(bot_state_t *bs) { + vec3_t feet; + + VectorCopy(bs->origin, feet); + feet[2] -= 23; + return (trap_AAS_PointContents(feet) & (CONTENTS_LAVA|CONTENTS_SLIME)); +} + +/* +================== +BotCreateWayPoint +================== +*/ +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum) { + bot_waypoint_t *wp; + vec3_t waypointmins = {-8, -8, -8}, waypointmaxs = {8, 8, 8}; + + wp = botai_freewaypoints; + if ( !wp ) { + BotAI_Print( PRT_WARNING, "BotCreateWayPoint: Out of waypoints\n" ); + return NULL; + } + botai_freewaypoints = botai_freewaypoints->next; + + Q_strncpyz( wp->name, name, sizeof(wp->name) ); + VectorCopy(origin, wp->goal.origin); + VectorCopy(waypointmins, wp->goal.mins); + VectorCopy(waypointmaxs, wp->goal.maxs); + wp->goal.areanum = areanum; + wp->next = NULL; + wp->prev = NULL; + return wp; +} + +/* +================== +BotFindWayPoint +================== +*/ +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name) { + bot_waypoint_t *wp; + + for (wp = waypoints; wp; wp = wp->next) { + if (!Q_stricmp(wp->name, name)) return wp; + } + return NULL; +} + +/* +================== +BotFreeWaypoints +================== +*/ +void BotFreeWaypoints(bot_waypoint_t *wp) { + bot_waypoint_t *nextwp; + + for (; wp; wp = nextwp) { + nextwp = wp->next; + wp->next = botai_freewaypoints; + botai_freewaypoints = wp; + } +} + +/* +================== +BotInitWaypoints +================== +*/ +void BotInitWaypoints(void) { + int i; + + botai_freewaypoints = NULL; + for (i = 0; i < MAX_WAYPOINTS; i++) { + botai_waypoints[i].next = botai_freewaypoints; + botai_freewaypoints = &botai_waypoints[i]; + } +} + +/* +================== +TeamPlayIsOn +================== +*/ +int TeamPlayIsOn(void) { + return ( gametype >= GT_TEAM ); +} + +/* +================== +BotAggression +================== +*/ +float BotAggression(bot_state_t *bs) { + //if the bot has quad + if (bs->inventory[INVENTORY_QUAD]) { + //if the bot is not holding the gauntlet or the enemy is really nearby + if (bs->weaponnum != WP_GAUNTLET || + bs->inventory[ENEMY_HORIZONTAL_DIST] < 80) { + return 70; + } + } + //if the enemy is located way higher than the bot + if (bs->inventory[ENEMY_HEIGHT] > 200) return 0; + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return 0; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return 0; + } + //PKMOD - Ergodic 09/07/00 remove the usage of the BFG + //if the bot can use the bfg +// if (bs->inventory[INVENTORY_BFG10K] > 0 && +// bs->inventory[INVENTORY_BFGAMMO] > 7) return 100; + + //PKMOD - Ergodic 09/07/00 switch the priorities of the rail and lightninggun + //if the bot can use the lightning gun + if (bs->inventory[INVENTORY_LIGHTNING] > 0 && + bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return 95; + //if the bot can use the railgun + if (bs->inventory[INVENTORY_RAILGUN] > 0 && + bs->inventory[INVENTORY_SLUGS] > 5) return 90; + //if the bot can use the rocketlauncher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_ROCKETS] > 5) return 90; + //PKMOD - Ergodic 09/07/00 bot is aggressive if it has the airfist + //if the bot can use the airfist + if (bs->inventory[INVENTORY_AIRFIST] > 0) return 85; + + //PKMOD - Ergodic 09/07/00 remove the usage of the plasmagun + //if the bot can use the plasmagun +// if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && +// bs->inventory[INVENTORY_CELLS] > 40) return 85; + + //PKMOD - Ergodic 09/07/00 bot is aggressive if it has the nailgun + //if the bot can use the nailgun + //PKMOD - Ergodic - 12/17/00 rename NAILGUN to PKA_NAILGUN to resolve 1.27g conflict + if (bs->inventory[INVENTORY_PKA_NAILGUN] > 0 && + //PKMOD - Ergodic - 12/17/00 rename NAIL to PKA_NAILS to resolve 1.27g conflict + bs->inventory[INVENTORY_PKA_NAILS] > 20) return 75; + + //if the bot can use the grenade launcher + if (bs->inventory[INVENTORY_GRENADELAUNCHER] > 0 && + bs->inventory[INVENTORY_GRENADES] > 10) return 70; + + //PKMOD - Ergodic 09/07/00 bot is aggressive if it has the explosive shells shotgun + //if the bot can use the explosive shells shotgun + if (bs->inventory[INVENTORY_SHOTGUN] > 0 && + bs->inventory[INVENTORY_EXPLOSIVESHELLS] > 4) return 50; + + //if the bot can use the shotgun + if (bs->inventory[INVENTORY_SHOTGUN] > 0 && + bs->inventory[INVENTORY_SHELLS] > 10) return 40; + + //PKMOD - Ergodic 09/07/00 bot is aggressive if it has a gravity well + //if the bot can use the gravity well + if (bs->inventory[INVENTORY_GRAVITY] > 0) return 30; + + //PKMOD - Ergodic 09/07/00 bot is aggressive if it has more than one autosentries + //if the bot can use the autosentry + if (bs->inventory[INVENTORY_SENTRY] > 0 && + bs->inventory[INVENTORY_SENTRY_AMMO] > 1) return 20; + + //PKMOD - Ergodic 09/07/00 bot is aggressive if it has is closeby and has beartraps (grin) + if (bs->inventory[INVENTORY_TRAP] > 0 && + ( bs->inventory[INVENTORY_TRAP_AMMO] > 0 ) && + ( bs->inventory[ENEMY_HORIZONTAL_DIST] < 200 ) ) return 25; + + //otherwise the bot is not feeling too good + return 0; +} + +/* +================== +BotFeelingBad +================== +*/ +float BotFeelingBad(bot_state_t *bs) { + if (bs->weaponnum == WP_GAUNTLET) { + return 100; + } + if (bs->inventory[INVENTORY_HEALTH] < 40) { + return 100; + } + if (bs->weaponnum == WP_MACHINEGUN) { + return 90; + } + if (bs->inventory[INVENTORY_HEALTH] < 60) { + return 80; + } + return 0; +} + +/* +================== +BotWantsToRetreat +================== +*/ +int BotWantsToRetreat(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + if (gametype == GT_CTF) { + //always retreat when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) + return qtrue; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + //if carrying the flag then always retreat + if (Bot1FCTFCarryingFlag(bs)) + return qtrue; + } + else if (gametype == GT_OBELISK) { + //the bots should be dedicated to attacking the enemy obelisk + if (bs->ltgtype == LTG_ATTACKENEMYBASE) { + if (bs->enemy != redobelisk.entitynum || + bs->enemy != blueobelisk.entitynum) { + return qtrue; + } + } + if (BotFeelingBad(bs) > 50) { + return qtrue; + } + return qfalse; + } + else if (gametype == GT_HARVESTER) { + //if carrying cubes then always retreat + if (BotHarvesterCarryingCubes(bs)) return qtrue; + } +#endif + // + if (bs->enemy >= 0) { + //if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qfalse; + } + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) + return qtrue; + // + if (BotAggression(bs) < 50) + return qtrue; + return qfalse; +} + +/* +================== +BotWantsToChase +================== +*/ +int BotWantsToChase(bot_state_t *bs) { + aas_entityinfo_t entinfo; + + if (gametype == GT_CTF) { + //never chase when carrying a CTF flag + if (BotCTFCarryingFlag(bs)) + return qfalse; + //always chase if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qtrue; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + //never chase if carrying the flag + if (Bot1FCTFCarryingFlag(bs)) + return qfalse; + //always chase if the enemy is carrying a flag + BotEntityInfo(bs->enemy, &entinfo); + if (EntityCarriesFlag(&entinfo)) + return qtrue; + } + else if (gametype == GT_OBELISK) { + //the bots should be dedicated to attacking the enemy obelisk + if (bs->ltgtype == LTG_ATTACKENEMYBASE) { + if (bs->enemy != redobelisk.entitynum || + bs->enemy != blueobelisk.entitynum) { + return qfalse; + } + } + } + else if (gametype == GT_HARVESTER) { + //never chase if carrying cubes + if (BotHarvesterCarryingCubes(bs)) + return qfalse; + } +#endif + //if the bot is getting the flag + if (bs->ltgtype == LTG_GETFLAG) + return qfalse; + // + if (BotAggression(bs) > 50) + return qtrue; + return qfalse; +} + +/* +================== +BotWantsToHelp +================== +*/ +int BotWantsToHelp(bot_state_t *bs) { + return qtrue; +} + +/* +================== +BotCanAndWantsToRocketJump +================== +*/ +int BotCanAndWantsToRocketJump(bot_state_t *bs) { + float rocketjumper; + + //if rocket jumping is disabled + if (!bot_rocketjump.integer) return qfalse; + //if no rocket launcher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0) return qfalse; + //if low on rockets + if (bs->inventory[INVENTORY_ROCKETS] < 3) return qfalse; + //never rocket jump with the Quad + if (bs->inventory[INVENTORY_QUAD]) return qfalse; + //if low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; + //if not full health + if (bs->inventory[INVENTORY_HEALTH] < 90) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; + } + rocketjumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WEAPONJUMPING, 0, 1); + if (rocketjumper < 0.5) return qfalse; + return qtrue; +} + +/* +================== +BotHasPersistantPowerupAndWeapon +================== +*/ +int BotHasPersistantPowerupAndWeapon(bot_state_t *bs) { +#ifdef MISSIONPACK + // if the bot does not have a persistant powerup + if (!bs->inventory[INVENTORY_SCOUT] && + !bs->inventory[INVENTORY_GUARD] && + !bs->inventory[INVENTORY_DOUBLER] && + !bs->inventory[INVENTORY_AMMOREGEN] ) { + return qfalse; + } +#endif + //if the bot is very low on health + if (bs->inventory[INVENTORY_HEALTH] < 60) return qfalse; + //if the bot is low on health + if (bs->inventory[INVENTORY_HEALTH] < 80) { + //if the bot has insufficient armor + if (bs->inventory[INVENTORY_ARMOR] < 40) return qfalse; + } + //PKMOD - Ergodic 12/18/00 - remove from PKA Inventory + //if the bot can use the bfg +// if (bs->inventory[INVENTORY_BFG10K] > 0 && +// bs->inventory[INVENTORY_BFGAMMO] > 7) return qtrue; + //if the bot can use the railgun + if (bs->inventory[INVENTORY_RAILGUN] > 0 && + bs->inventory[INVENTORY_SLUGS] > 5) return qtrue; + //if the bot can use the lightning gun + if (bs->inventory[INVENTORY_LIGHTNING] > 0 && + bs->inventory[INVENTORY_LIGHTNINGAMMO] > 50) return qtrue; + //if the bot can use the rocketlauncher + if (bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && + bs->inventory[INVENTORY_ROCKETS] > 5) return qtrue; + // + //PKMOD - Ergodic 12/18/00 - remove from PKA Inventory +// if (bs->inventory[INVENTORY_NAILGUN] > 0 && +// bs->inventory[INVENTORY_NAILS] > 5) return qtrue; + // + //PKMOD - Ergodic 12/18/00 - remove from PKA Inventory +// if (bs->inventory[INVENTORY_PROXLAUNCHER] > 0 && +// bs->inventory[INVENTORY_MINES] > 5) return qtrue; + // + //PKMOD - Ergodic 12/18/00 - remove from PKA Inventory +// if (bs->inventory[INVENTORY_CHAINGUN] > 0 && +// bs->inventory[INVENTORY_BELT] > 40) return qtrue; + // + if (bs->inventory[INVENTORY_CHAINGUN] > 0 && + bs->inventory[INVENTORY_BELT] > 40) return qtrue; + //if the bot can use the plasmagun + if (bs->inventory[INVENTORY_PLASMAGUN] > 0 && + bs->inventory[INVENTORY_CELLS] > 20) return qtrue; + return qfalse; +} + +/* +================== +BotGoCamp +================== +*/ +void BotGoCamp(bot_state_t *bs, bot_goal_t *goal) { + float camper; + + bs->decisionmaker = bs->client; + //set message time to zero so bot will NOT show any message + bs->teammessage_time = 0; + //set the ltg type + bs->ltgtype = LTG_CAMP; + //set the team goal + memcpy(&bs->teamgoal, goal, sizeof(bot_goal_t)); + //get the team goal time + camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); + if (camper > 0.99) bs->teamgoal_time = FloatTime() + 99999; + else bs->teamgoal_time = FloatTime() + 120 + 180 * camper + random() * 15; + //set the last time the bot started camping + bs->camp_time = FloatTime(); + //the teammate that requested the camping + bs->teammate = 0; + //do NOT type arrive message + bs->arrive_time = 1; +} + +/* +================== +BotWantsToCamp +================== +*/ +int BotWantsToCamp(bot_state_t *bs) { + float camper; + int cs, traveltime, besttraveltime; + bot_goal_t goal, bestgoal; + + camper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CAMPER, 0, 1); + if (camper < 0.1) return qfalse; + //if the bot has a team goal + if (bs->ltgtype == LTG_TEAMHELP || + bs->ltgtype == LTG_TEAMACCOMPANY || + bs->ltgtype == LTG_DEFENDKEYAREA || + bs->ltgtype == LTG_GETFLAG || + bs->ltgtype == LTG_RUSHBASE || + bs->ltgtype == LTG_CAMP || + bs->ltgtype == LTG_CAMPORDER || + bs->ltgtype == LTG_PATROL) { + return qfalse; + } + //if camped recently + if (bs->camp_time > FloatTime() - 60 + 300 * (1-camper)) return qfalse; + // + if (random() > camper) { + bs->camp_time = FloatTime(); + return qfalse; + } + //if the bot isn't healthy anough + if (BotAggression(bs) < 50) return qfalse; + //the bot should have at least have the rocket launcher, the railgun or the bfg10k with some ammo + if ((bs->inventory[INVENTORY_ROCKETLAUNCHER] <= 0 || bs->inventory[INVENTORY_ROCKETS < 10]) && + (bs->inventory[INVENTORY_RAILGUN] <= 0 || bs->inventory[INVENTORY_SLUGS] < 10) && + (bs->inventory[INVENTORY_BFG10K] <= 0 || bs->inventory[INVENTORY_BFGAMMO] < 10)) { + return qfalse; + } + //find the closest camp spot + besttraveltime = 99999; + for (cs = trap_BotGetNextCampSpotGoal(0, &goal); cs; cs = trap_BotGetNextCampSpotGoal(cs, &goal)) { + traveltime = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, goal.areanum, TFL_DEFAULT); + if (traveltime && traveltime < besttraveltime) { + besttraveltime = traveltime; + memcpy(&bestgoal, &goal, sizeof(bot_goal_t)); + } + } + if (besttraveltime > 150) return qfalse; + //ok found a camp spot, go camp there + BotGoCamp(bs, &bestgoal); + bs->ordered = qfalse; + // + return qtrue; +} + +/* +================== +BotDontAvoid +================== +*/ +void BotDontAvoid(bot_state_t *bs, char *itemname) { + bot_goal_t goal; + int num; + + num = trap_BotGetLevelItemGoal(-1, itemname, &goal); + while(num >= 0) { + trap_BotRemoveFromAvoidGoals(bs->gs, goal.number); + num = trap_BotGetLevelItemGoal(num, itemname, &goal); + } +} + +/* +================== +BotGoForPowerups +================== +*/ +void BotGoForPowerups(bot_state_t *bs) { + + //don't avoid any of the powerups anymore + BotDontAvoid(bs, "Quad Damage"); + BotDontAvoid(bs, "Regeneration"); + BotDontAvoid(bs, "Battle Suit"); + BotDontAvoid(bs, "Speed"); + BotDontAvoid(bs, "Invisibility"); + //BotDontAvoid(bs, "Flight"); + //reset the long term goal time so the bot will go for the powerup + //NOTE: the long term goal type doesn't change + bs->ltg_time = 0; +} + +/* +================== +BotRoamGoal +================== +*/ +void BotRoamGoal(bot_state_t *bs, vec3_t goal) { + int pc, i; + float len, rnd; + vec3_t dir, bestorg, belowbestorg; + bsp_trace_t trace; + + for (i = 0; i < 10; i++) { + //start at the bot origin + VectorCopy(bs->origin, bestorg); + rnd = random(); + if (rnd > 0.25) { + //add a random value to the x-coordinate + if (random() < 0.5) bestorg[0] -= 800 * random() + 100; + else bestorg[0] += 800 * random() + 100; + } + if (rnd < 0.75) { + //add a random value to the y-coordinate + if (random() < 0.5) bestorg[1] -= 800 * random() + 100; + else bestorg[1] += 800 * random() + 100; + } + //add a random value to the z-coordinate (NOTE: 48 = maxjump?) + bestorg[2] += 2 * 48 * crandom(); + //trace a line from the origin to the roam target + BotAI_Trace(&trace, bs->origin, NULL, NULL, bestorg, bs->entitynum, MASK_SOLID); + //direction and length towards the roam target + VectorSubtract(trace.endpos, bs->origin, dir); + len = VectorNormalize(dir); + //if the roam target is far away anough + if (len > 200) { + //the roam target is in the given direction before walls + VectorScale(dir, len * trace.fraction - 40, dir); + VectorAdd(bs->origin, dir, bestorg); + //get the coordinates of the floor below the roam target + belowbestorg[0] = bestorg[0]; + belowbestorg[1] = bestorg[1]; + belowbestorg[2] = bestorg[2] - 800; + BotAI_Trace(&trace, bestorg, NULL, NULL, belowbestorg, bs->entitynum, MASK_SOLID); + // + if (!trace.startsolid) { + trace.endpos[2]++; + pc = trap_PointContents(trace.endpos, bs->entitynum); + if (!(pc & (CONTENTS_LAVA | CONTENTS_SLIME))) { + VectorCopy(bestorg, goal); + return; + } + } + } + } + VectorCopy(bestorg, goal); +} + +/* +================== +BotAttackMove +================== +*/ +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl) { + int movetype, i, attackentity; + float attack_skill, jumper, croucher, dist, strafechange_time; + float attack_dist, attack_range; + vec3_t forward, backward, sideward, hordir, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_moveresult_t moveresult; + bot_goal_t goal; + + attackentity = bs->enemy; + // + if (bs->attackchase_time > FloatTime()) { + //create the chase goal + goal.entitynum = attackentity; + goal.areanum = bs->lastenemyareanum; + VectorCopy(bs->lastenemyorigin, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + //initialize the movement state + BotSetupForMovement(bs); + //move towards the goal + trap_BotMoveToGoal(&moveresult, bs->ms, &goal, tfl); + return moveresult; + } + // + memset(&moveresult, 0, sizeof(bot_moveresult_t)); + // + attack_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ATTACK_SKILL, 0, 1); + jumper = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_JUMPER, 0, 1); + croucher = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CROUCHER, 0, 1); + //if the bot is really stupid + if (attack_skill < 0.2) return moveresult; + //initialize the movement state + BotSetupForMovement(bs); + //get the enemy entity info + BotEntityInfo(attackentity, &entinfo); + //direction towards the enemy + VectorSubtract(entinfo.origin, bs->origin, forward); + //the distance towards the enemy + dist = VectorNormalize(forward); + VectorNegate(forward, backward); + //walk, crouch or jump + movetype = MOVE_WALK; + // + if (bs->attackcrouch_time < FloatTime() - 1) { + if (random() < jumper) { + movetype = MOVE_JUMP; + } + //wait at least one second before crouching again + else if (bs->attackcrouch_time < FloatTime() - 1 && random() < croucher) { + bs->attackcrouch_time = FloatTime() + croucher * 5; + } + } + if (bs->attackcrouch_time > FloatTime()) movetype = MOVE_CROUCH; + //if the bot should jump + if (movetype == MOVE_JUMP) { + //if jumped last frame + if (bs->attackjump_time > FloatTime()) { + movetype = MOVE_WALK; + } + else { + bs->attackjump_time = FloatTime() + 1; + } + } + if (bs->cur_ps.weapon == WP_GAUNTLET) { + attack_dist = 0; + attack_range = 0; + } + else { + attack_dist = IDEAL_ATTACKDIST; + attack_range = 40; + } + //if the bot is stupid + if (attack_skill <= 0.4) { + //just walk to or away from the enemy + if (dist > attack_dist + attack_range) { + if (trap_BotMoveInDirection(bs->ms, forward, 400, movetype)) return moveresult; + } + if (dist < attack_dist - attack_range) { + if (trap_BotMoveInDirection(bs->ms, backward, 400, movetype)) return moveresult; + } + return moveresult; + } + //increase the strafe time + bs->attackstrafe_time += bs->thinktime; + //get the strafe change time + strafechange_time = 0.4 + (1 - attack_skill) * 0.2; + if (attack_skill > 0.7) strafechange_time += crandom() * 0.2; + //if the strafe direction should be changed + if (bs->attackstrafe_time > strafechange_time) { + //some magic number :) + if (random() > 0.935) { + //flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + } + // + for (i = 0; i < 2; i++) { + hordir[0] = forward[0]; + hordir[1] = forward[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //get the sideward vector + CrossProduct(hordir, up, sideward); + //reverse the vector depending on the strafe direction + if (bs->flags & BFL_STRAFERIGHT) VectorNegate(sideward, sideward); + //randomly go back a little + if (random() > 0.9) { + VectorAdd(sideward, backward, sideward); + } + else { + //walk forward or backward to get at the ideal attack distance + if (dist > attack_dist + attack_range) { + VectorAdd(sideward, forward, sideward); + } + else if (dist < attack_dist - attack_range) { + VectorAdd(sideward, backward, sideward); + } + } + //perform the movement + if (trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) + return moveresult; + //movement failed, flip the strafe direction + bs->flags ^= BFL_STRAFERIGHT; + bs->attackstrafe_time = 0; + } + //bot couldn't do any usefull movement +// bs->attackchase_time = AAS_Time() + 6; + return moveresult; +} + +/* +================== +BotSameTeam +================== +*/ +int BotSameTeam(bot_state_t *bs, int entnum) { + char info1[1024], info2[1024]; + + if (bs->client < 0 || bs->client >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + if (entnum < 0 || entnum >= MAX_CLIENTS) { + //BotAI_Print(PRT_ERROR, "BotSameTeam: client out of range\n"); + return qfalse; + } + //PKMOD - Ergodic 01/07/02 - Check if Private Bot's owner (don't target PB's owner) + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + if ( g_entities[bs->entitynum].parent->client->ps.clientNum == entnum ) { + //PKMOD - Ergodic 01/07/02 - Private Bot's team owner (inactive) +// Com_Printf("BotSameTeam - Team member detected, entnum>%d<\n", entnum ); + return qtrue; + } + } + + //PKMOD - Ergodic 03/18/02 - Check if Private Bot's brother (don't target PB's owned by same parent) + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + if ( g_entities[entnum].r.svFlags & SVF_PRIVATEBOT ) { + if ( g_entities[bs->entitynum].parent->client->ps.clientNum == g_entities[entnum].parent->client->ps.clientNum ) { + //PKMOD - Ergodic 01/07/02 - Private Bot's team owner (inactive) +// Com_Printf("BotSameTeam - Team member detected, entnum>%d<\n", entnum ); + return qtrue; + } + } + } + + + if ( gametype >= GT_TEAM ) { + trap_GetConfigstring(CS_PLAYERS+bs->client, info1, sizeof(info1)); + trap_GetConfigstring(CS_PLAYERS+entnum, info2, sizeof(info2)); + // + if (atoi(Info_ValueForKey(info1, "t")) == atoi(Info_ValueForKey(info2, "t"))) return qtrue; + } + return qfalse; +} + +/* +================== +InFieldOfVision +================== +*/ +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles) +{ + int i; + float diff, angle; + + for (i = 0; i < 2; i++) { + angle = AngleMod(viewangles[i]); + angles[i] = AngleMod(angles[i]); + diff = angles[i] - angle; + if (angles[i] > angle) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + if (diff > 0) { + if (diff > fov * 0.5) return qfalse; + } + else { + if (diff < -fov * 0.5) return qfalse; + } + } + return qtrue; +} + +/* +================== +BotEntityVisible + +returns visibility in the range [0, 1] taking fog and water surfaces into account +================== +*/ +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent) { + int i, contents_mask, passent, hitent, infog, inwater, otherinfog, pc; + float squaredfogdist, waterfactor, vis, bestvis; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t dir, entangles, start, end, middle; + + //calculate middle of bounding box + BotEntityInfo(ent, &entinfo); + VectorAdd(entinfo.mins, entinfo.maxs, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(entinfo.origin, middle, middle); + //check if entity is within field of vision + VectorSubtract(middle, eye, dir); + vectoangles(dir, entangles); + if (!InFieldOfVision(viewangles, fov, entangles)) return 0; + // + pc = trap_AAS_PointContents(eye); + infog = (pc & CONTENTS_FOG); + inwater = (pc & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)); + // + bestvis = 0; + for (i = 0; i < 3; i++) { + //if the point is not in potential visible sight + //if (!AAS_inPVS(eye, middle)) continue; + // + contents_mask = CONTENTS_SOLID|CONTENTS_PLAYERCLIP; + passent = viewer; + hitent = ent; + VectorCopy(eye, start); + VectorCopy(middle, end); + //if the entity is in water, lava or slime + if (trap_AAS_PointContents(middle) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + contents_mask |= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //if eye is in water, lava or slime + if (inwater) { + if (!(contents_mask & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) { + passent = ent; + hitent = viewer; + VectorCopy(middle, start); + VectorCopy(eye, end); + } + contents_mask ^= (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + } + //trace from start to end + BotAI_Trace(&trace, start, NULL, NULL, end, passent, contents_mask); + //if water was hit + waterfactor = 1.0; + if (trace.contents & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) { + //if the water surface is translucent + if (1) { + //trace through the water + contents_mask &= ~(CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER); + BotAI_Trace(&trace, trace.endpos, NULL, NULL, end, passent, contents_mask); + waterfactor = 0.5; + } + } + //if a full trace or the hitent was hit + if (trace.fraction >= 1 || trace.ent == hitent) { + //check for fog, assuming there's only one fog brush where + //either the viewer or the entity is in or both are in + otherinfog = (trap_AAS_PointContents(middle) & CONTENTS_FOG); + if (infog && otherinfog) { + VectorSubtract(trace.endpos, eye, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else if (infog) { + VectorCopy(trace.endpos, start); + BotAI_Trace(&trace, start, NULL, NULL, eye, viewer, CONTENTS_FOG); + VectorSubtract(eye, trace.endpos, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else if (otherinfog) { + VectorCopy(trace.endpos, end); + BotAI_Trace(&trace, eye, NULL, NULL, end, viewer, CONTENTS_FOG); + VectorSubtract(end, trace.endpos, dir); + squaredfogdist = VectorLengthSquared(dir); + } + else { + //if the entity and the viewer are not in fog assume there's no fog in between + squaredfogdist = 0; + } + //decrease visibility with the view distance through fog + vis = 1 / ((squaredfogdist * 0.001) < 1 ? 1 : (squaredfogdist * 0.001)); + //if entering water visibility is reduced + vis *= waterfactor; + // + if (vis > bestvis) bestvis = vis; + //if pretty much no fog + if (bestvis >= 0.95) return bestvis; + } + //check bottom and top of bounding box as well + if (i == 0) middle[2] += entinfo.mins[2]; + else if (i == 1) middle[2] += entinfo.maxs[2] - entinfo.mins[2]; + } + return bestvis; +} + +/* +================== +BotFindEnemy +================== +*/ +int BotFindEnemy(bot_state_t *bs, int curenemy) { + int i, healthdecrease; + float f, alertness, easyfragger, vis; + float squaredist, cursquaredist; + aas_entityinfo_t entinfo, curenemyinfo; + vec3_t dir, angles; + + alertness = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_ALERTNESS, 0, 1); + easyfragger = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_EASY_FRAGGER, 0, 1); + //check if the health decreased + healthdecrease = bs->lasthealth > bs->inventory[INVENTORY_HEALTH]; + //remember the current health value + bs->lasthealth = bs->inventory[INVENTORY_HEALTH]; + // + if (curenemy >= 0) { + BotEntityInfo(curenemy, &curenemyinfo); + if (EntityCarriesFlag(&curenemyinfo)) return qfalse; + VectorSubtract(curenemyinfo.origin, bs->origin, dir); + cursquaredist = VectorLengthSquared(dir); + } + else { + cursquaredist = 0; + } +#ifdef MISSIONPACK + if (gametype == GT_OBELISK) { + vec3_t target; + bot_goal_t *goal; + bsp_trace_t trace; + + if (BotTeam(bs) == TEAM_RED) + goal = &blueobelisk; + else + goal = &redobelisk; + //if the obelisk is visible + VectorCopy(goal->origin, target); + target[2] += 1; + BotAI_Trace(&trace, bs->eye, NULL, NULL, target, bs->client, CONTENTS_SOLID); + if (trace.fraction >= 1 || trace.ent == goal->entitynum) { + if (goal->entitynum == bs->enemy) { + return qfalse; + } + bs->enemy = goal->entitynum; + bs->enemysight_time = FloatTime(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + bs->enemyvisible_time = FloatTime(); + return qtrue; + } + } +#endif + // + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + //if it's the current enemy + if (i == curenemy) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + //if the enemy is invisible and not shooting + if (EntityIsInvisible(&entinfo) && !EntityIsShooting(&entinfo)) { + //PKMOD - Ergodic 10/31/02 - check if enemy has a beartrap attached + // attached beartraps will make the player visible to the bots + entityState_t state; + BotAI_GetEntityState(i, &state); + if ( ( state.time2 & 3 ) == 0 ) //if no beartraps are attached + continue; + } + //if not an easy fragger don't shoot at chatting players + if (easyfragger < 0.5 && EntityIsChatting(&entinfo)) continue; + // + if (lastteleport_time > FloatTime() - 3) { + VectorSubtract(entinfo.origin, lastteleport_origin, dir); + if (VectorLengthSquared(dir) < Square(70)) continue; + } + //calculate the distance towards the enemy + VectorSubtract(entinfo.origin, bs->origin, dir); + squaredist = VectorLengthSquared(dir); + //if this entity is not carrying a flag + if (!EntityCarriesFlag(&entinfo)) + { + //if this enemy is further away than the current one + if (curenemy >= 0 && squaredist > cursquaredist) continue; + } //end if + //if the bot has no + if (squaredist > Square(900.0 + alertness * 4000.0)) continue; + //if on the same team + if (BotSameTeam(bs, i)) continue; + //if the bot's health decreased or the enemy is shooting + if (curenemy < 0 && (healthdecrease || EntityIsShooting(&entinfo))) + f = 360; + else + f = 90 + 90 - (90 - (squaredist > Square(810) ? Square(810) : squaredist) / (810 * 9)); + //check if the enemy is visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, f, i); + if (vis <= 0) continue; + //if the enemy is quite far away, not shooting and the bot is not damaged + if (curenemy < 0 && squaredist > Square(100) && !healthdecrease && !EntityIsShooting(&entinfo)) + { + //check if we can avoid this enemy + VectorSubtract(bs->origin, entinfo.origin, dir); + vectoangles(dir, angles); + //if the bot isn't in the fov of the enemy + if (!InFieldOfVision(entinfo.angles, 90, angles)) { + //update some stuff for this enemy + BotUpdateBattleInventory(bs, i); + //if the bot doesn't really want to fight + if (BotWantsToRetreat(bs)) continue; + } + } + //found an enemy + bs->enemy = entinfo.number; + if (curenemy >= 0) bs->enemysight_time = FloatTime() - 2; + else bs->enemysight_time = FloatTime(); + bs->enemysuicide = qfalse; + bs->enemydeath_time = 0; + bs->enemyvisible_time = FloatTime(); + return qtrue; + } + return qfalse; +} + +/* +================== +BotTeamFlagCarrierVisible +================== +*/ +int BotTeamFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotTeamFlagCarrier +================== +*/ +int BotTeamFlagCarrier(bot_state_t *bs) { + int i; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotEnemyFlagCarrierVisible +================== +*/ +int BotEnemyFlagCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} + +/* +================== +BotVisibleTeamMatesAndEnemies +================== +*/ +void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range) { + int i; + float vis; + aas_entityinfo_t entinfo; + vec3_t dir; + + if (teammates) + *teammates = 0; + if (enemies) + *enemies = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesFlag(&entinfo)) + continue; + //if not within range + VectorSubtract(entinfo.origin, bs->origin, dir); + if (VectorLengthSquared(dir) > Square(range)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) { + if (teammates) + (*teammates)++; + } + else { + if (enemies) + (*enemies)++; + } + } +} + +#ifdef MISSIONPACK +/* +================== +BotTeamCubeCarrierVisible +================== +*/ +int BotTeamCubeCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) continue; + //if this player is carrying a flag + if (!EntityCarriesCubes(&entinfo)) continue; + //if the flag carrier is not on the same team + if (!BotSameTeam(bs, i)) continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) continue; + // + return i; + } + return -1; +} + +/* +================== +BotEnemyCubeCarrierVisible +================== +*/ +int BotEnemyCubeCarrierVisible(bot_state_t *bs) { + int i; + float vis; + aas_entityinfo_t entinfo; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + if (i == bs->client) + continue; + // + BotEntityInfo(i, &entinfo); + //if this player is active + if (!entinfo.valid) + continue; + //if this player is carrying a flag + if (!EntityCarriesCubes(&entinfo)) continue; + //if the flag carrier is on the same team + if (BotSameTeam(bs, i)) + continue; + //if the flag carrier is not visible + vis = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, i); + if (vis <= 0) + continue; + // + return i; + } + return -1; +} +#endif + +/* +================== +BotAimAtEnemy +================== +*/ +void BotAimAtEnemy(bot_state_t *bs) { + int i, enemyvisible; + float dist, f, aim_skill, aim_accuracy, speed, reactiontime; + vec3_t dir, bestorigin, end, start, groundtarget, cmdmove, enemyvelocity; + vec3_t mins = {-4,-4,-4}, maxs = {4, 4, 4}; + weaponinfo_t wi; + aas_entityinfo_t entinfo; + bot_goal_t goal; + bsp_trace_t trace; + vec3_t target; + + //if the bot has no enemy + if (bs->enemy < 0) { + return; + } + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if this is not a player (should be an obelisk) + if (bs->enemy >= MAX_CLIENTS) { + //if the obelisk is visible + VectorCopy(entinfo.origin, target); +#ifdef MISSIONPACK + // if attacking an obelisk + if ( bs->enemy == redobelisk.entitynum || + bs->enemy == blueobelisk.entitynum ) { + target[2] += 32; + } +#endif + //aim at the obelisk + VectorSubtract(target, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + //set the aim target before trying to attack + VectorCopy(target, bs->aimtarget); + return; + } + // + //BotAI_Print(PRT_MESSAGE, "client %d: aiming at client %d\n", bs->entitynum, bs->enemy); + // + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL, 0, 1); + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); + // + if (aim_skill > 0.95) { + //don't aim too early + reactiontime = 0.5 * trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > FloatTime() - reactiontime) return; + if (bs->teleport_time > FloatTime() - reactiontime) return; + } + + //get the weapon information + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the weapon specific aim accuracy and or aim skill + if (wi.number == WP_MACHINEGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN, 0, 1); + } + else if (wi.number == WP_SHOTGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_SHOTGUN, 0, 1); + } + else if (wi.number == WP_GRENADE_LAUNCHER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER, 0, 1); + } + else if (wi.number == WP_ROCKET_LAUNCHER) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER, 0, 1); + } + else if (wi.number == WP_LIGHTNING) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_LIGHTNING, 0, 1); + } + else if (wi.number == WP_RAILGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_RAILGUN, 0, 1); + } + + //PKMOD - Ergodic - 09/07/00 Add PKA weapon specific definitions + if (wi.number == WP_AIRFIST) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_AIRFIST, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_AIRFIST, 0, 1); + } + if (wi.number == WP_NAILGUN) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_NAILGUN, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_NAILGUN, 0, 1); + } + if (wi.number == WP_BEARTRAP) { + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BEARTRAP, 0, 1); + aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BEARTRAP, 0, 1); + } + + //PKMOD - Ergodic - 09/07/00 Remove plasmagun definition +// if (wi.number == WP_PLASMAGUN) { +// aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN, 0, 1); +// aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_PLASMAGUN, 0, 1); +// } + //PKMOD - Ergodic - 09/07/00 Remove bfg definition +// if (wi.number == WP_BFG) { +// aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY_BFG10K, 0, 1); +// aim_skill = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_SKILL_BFG10K, 0, 1); +// } + // + if (aim_accuracy <= 0) aim_accuracy = 0.0001f; + //get the enemy entity information + BotEntityInfo(bs->enemy, &entinfo); + //if the enemy is invisible then shoot crappy most of the time + if (EntityIsInvisible(&entinfo)) { + //PKMOD - Ergodic 10/31/02 - check if enemy has a beartrap attached + // attached beartraps will make the player visible to the bots + entityState_t state; + BotAI_GetEntityState(bs->enemy, &state); + if ( ( state.time2 & 3 ) == 0 ) //if no beartraps are attached + if (random() > 0.1) aim_accuracy *= 0.4f; + } + // + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, enemyvelocity); + VectorScale(enemyvelocity, 1 / entinfo.update_time, enemyvelocity); + //enemy origin and velocity is remembered every 0.5 seconds + if (bs->enemyposition_time < FloatTime()) { + // + bs->enemyposition_time = FloatTime() + 0.5; + VectorCopy(enemyvelocity, bs->enemyvelocity); + VectorCopy(entinfo.origin, bs->enemyorigin); + } + //if not extremely skilled + if (aim_skill < 0.9) { + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy moved a bit + if (VectorLengthSquared(dir) > Square(48)) { + //if the enemy changed direction + if (DotProduct(bs->enemyvelocity, enemyvelocity) < 0) { + //aim accuracy should be worse now + aim_accuracy *= 0.7f; + } + } + } + //check visibility of enemy + enemyvisible = BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, bs->enemy); + //if the enemy is visible + if (enemyvisible) { + // + VectorCopy(entinfo.origin, bestorigin); + bestorigin[2] += 8; + //get the start point shooting from + //NOTE: the x and y projectile start offsets are ignored + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + start[2] += wi.offset[2]; + // + BotAI_Trace(&trace, start, mins, maxs, bestorigin, bs->entitynum, MASK_SHOT); + //if the enemy is NOT hit + if (trace.fraction <= 1 && trace.ent != entinfo.number) { + bestorigin[2] += 16; + } + //if it is not an instant hit weapon the bot might want to predict the enemy + if (wi.speed) { + // + VectorSubtract(bestorigin, bs->origin, dir); + dist = VectorLength(dir); + VectorSubtract(entinfo.origin, bs->enemyorigin, dir); + //if the enemy is NOT pretty far away and strafing just small steps left and right + if (!(dist > 100 && VectorLengthSquared(dir) < Square(32))) { + //if skilled anough do exact prediction + if (aim_skill > 0.8 && + //if the weapon is ready to fire + bs->cur_ps.weaponstate == WEAPON_READY) { + aas_clientmove_t move; + vec3_t origin; + + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + // + VectorScale(dir, 1 / entinfo.update_time, dir); + // + VectorCopy(entinfo.origin, origin); + origin[2] += 1; + // + VectorClear(cmdmove); + //AAS_ClearShownDebugLines(); + trap_AAS_PredictClientMovement(&move, bs->enemy, origin, + PRESENCE_CROUCH, qfalse, + dir, cmdmove, 0, + dist * 10 / wi.speed, 0.1f, 0, 0, qfalse); + VectorCopy(move.endpos, bestorigin); + //BotAI_Print(PRT_MESSAGE, "%1.1f predicted speed = %f, frames = %f\n", FloatTime(), VectorLength(dir), dist * 10 / wi.speed); + } + //if not that skilled do linear prediction + else if (aim_skill > 0.4) { + VectorSubtract(entinfo.origin, bs->origin, dir); + //distance towards the enemy + dist = VectorLength(dir); + //direction the enemy is moving in + VectorSubtract(entinfo.origin, entinfo.lastvisorigin, dir); + dir[2] = 0; + // + speed = VectorNormalize(dir) / entinfo.update_time; + //botimport.Print(PRT_MESSAGE, "speed = %f, wi->speed = %f\n", speed, wi->speed); + //best spot to aim at + VectorMA(entinfo.origin, (dist / wi.speed) * speed, dir, bestorigin); + } + } + } + //if the projectile does radial damage + if (aim_skill > 0.6 && wi.proj.damagetype & DAMAGETYPE_RADIAL) { + //if the enemy isn't standing significantly higher than the bot + if (entinfo.origin[2] < bs->origin[2] + 16) { + //try to aim at the ground in front of the enemy + VectorCopy(entinfo.origin, end); + end[2] -= 64; + BotAI_Trace(&trace, entinfo.origin, NULL, NULL, end, entinfo.number, MASK_SHOT); + // + VectorCopy(bestorigin, groundtarget); + if (trace.startsolid) groundtarget[2] = entinfo.origin[2] - 16; + else groundtarget[2] = trace.endpos[2] - 8; + //trace a line from projectile start to ground target + BotAI_Trace(&trace, start, NULL, NULL, groundtarget, bs->entitynum, MASK_SHOT); + //if hitpoint is not vertically too far from the ground target + if (fabs(trace.endpos[2] - groundtarget[2]) < 50) { + VectorSubtract(trace.endpos, groundtarget, dir); + //if the hitpoint is near anough the ground target + if (VectorLengthSquared(dir) < Square(60)) { + VectorSubtract(trace.endpos, start, dir); + //if the hitpoint is far anough from the bot + if (VectorLengthSquared(dir) > Square(100)) { + //check if the bot is visible from the ground target + trace.endpos[2] += 1; + BotAI_Trace(&trace, trace.endpos, NULL, NULL, entinfo.origin, entinfo.number, MASK_SHOT); + if (trace.fraction >= 1) { + //botimport.Print(PRT_MESSAGE, "%1.1f aiming at ground\n", AAS_Time()); + VectorCopy(groundtarget, bestorigin); + } + } + } + } + } + } + bestorigin[0] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[1] += 20 * crandom() * (1 - aim_accuracy); + bestorigin[2] += 10 * crandom() * (1 - aim_accuracy); + } + else { + // + VectorCopy(bs->lastenemyorigin, bestorigin); + bestorigin[2] += 8; + //if the bot is skilled anough + if (aim_skill > 0.5) { + //do prediction shots around corners + if (wi.number == WP_BFG || + wi.number == WP_ROCKET_LAUNCHER || + wi.number == WP_GRENADE_LAUNCHER) { + //create the chase goal + goal.entitynum = bs->client; + goal.areanum = bs->areanum; + VectorCopy(bs->eye, goal.origin); + VectorSet(goal.mins, -8, -8, -8); + VectorSet(goal.maxs, 8, 8, 8); + // + if (trap_BotPredictVisiblePosition(bs->lastenemyorigin, bs->lastenemyareanum, &goal, TFL_DEFAULT, target)) { + VectorSubtract(target, bs->eye, dir); + if (VectorLengthSquared(dir) > Square(80)) { + VectorCopy(target, bestorigin); + bestorigin[2] -= 20; + } + } + aim_accuracy = 1; + } + } + } + // + if (enemyvisible) { + BotAI_Trace(&trace, bs->eye, NULL, NULL, bestorigin, bs->entitynum, MASK_SHOT); + VectorCopy(trace.endpos, bs->aimtarget); + } + else { + VectorCopy(bestorigin, bs->aimtarget); + } + //get aim direction + VectorSubtract(bestorigin, bs->eye, dir); + // + if (wi.number == WP_MACHINEGUN || + wi.number == WP_SHOTGUN || + wi.number == WP_LIGHTNING || + wi.number == WP_RAILGUN) { + //distance towards the enemy + dist = VectorLength(dir); + if (dist > 150) dist = 150; + f = 0.6 + dist / 150 * 0.4; + aim_accuracy *= f; + } + //add some random stuff to the aim direction depending on the aim accuracy + if (aim_accuracy < 0.8) { + VectorNormalize(dir); + for (i = 0; i < 3; i++) dir[i] += 0.3 * crandom() * (1 - aim_accuracy); + } + //set the ideal view angles + vectoangles(dir, bs->ideal_viewangles); + //take the weapon spread into account for lower skilled bots + bs->ideal_viewangles[PITCH] += 6 * wi.vspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 6 * wi.hspread * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + //if the bots should be really challenging + if (bot_challenge.integer) { + //if the bot is really accurate and has the enemy in view for some time + if (aim_accuracy > 0.9 && bs->enemysight_time < FloatTime() - 1) { + //set the view angles directly + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + VectorCopy(bs->ideal_viewangles, bs->viewangles); + trap_EA_View(bs->client, bs->viewangles); + } + } +} + +/* +================== +BotCheckAttack +================== +*/ +void BotCheckAttack(bot_state_t *bs) { + float points, reactiontime, fov, firethrottle; + int attackentity; + bsp_trace_t bsptrace; + //float selfpreservation; + vec3_t forward, right, start, end, dir, angles; + weaponinfo_t wi; + bsp_trace_t trace; + aas_entityinfo_t entinfo; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + attackentity = bs->enemy; + // + BotEntityInfo(attackentity, &entinfo); + // if not attacking a player + if (attackentity >= MAX_CLIENTS) { +#ifdef MISSIONPACK + // if attacking an obelisk + if ( entinfo.number == redobelisk.entitynum || + entinfo.number == blueobelisk.entitynum ) { + // if obelisk is respawning return + if ( g_entities[entinfo.number].activator && + g_entities[entinfo.number].activator->s.frame == 2 ) { + return; + } + } +#endif + } + // + reactiontime = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_REACTIONTIME, 0, 1); + if (bs->enemysight_time > FloatTime() - reactiontime) return; + if (bs->teleport_time > FloatTime() - reactiontime) return; + //if changing weapons + if (bs->weaponchange_time > FloatTime() - 0.1) return; + //check fire throttle characteristic + if (bs->firethrottlewait_time > FloatTime()) return; + firethrottle = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_FIRETHROTTLE, 0, 1); + if (bs->firethrottleshoot_time < FloatTime()) { + if (random() > firethrottle) { + bs->firethrottlewait_time = FloatTime() + firethrottle; + bs->firethrottleshoot_time = 0; + } + else { + bs->firethrottleshoot_time = FloatTime() + 1 - firethrottle; + bs->firethrottlewait_time = 0; + } + } + // + // + VectorSubtract(bs->aimtarget, bs->eye, dir); + // + if (bs->weaponnum == WP_GAUNTLET) { + if (VectorLengthSquared(dir) > Square(60)) { + return; + } + } + if (VectorLengthSquared(dir) < Square(100)) + fov = 120; + else + fov = 50; + // + vectoangles(dir, angles); + if (!InFieldOfVision(bs->viewangles, fov, angles)) + return; + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, bs->aimtarget, bs->client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (bsptrace.fraction < 1 && bsptrace.ent != attackentity) + return; + + //get the weapon info + trap_BotGetWeaponInfo(bs->ws, bs->weaponnum, &wi); + //get the start point shooting from + VectorCopy(bs->origin, start); + start[2] += bs->cur_ps.viewheight; + AngleVectors(bs->viewangles, forward, right, NULL); + start[0] += forward[0] * wi.offset[0] + right[0] * wi.offset[1]; + start[1] += forward[1] * wi.offset[0] + right[1] * wi.offset[1]; + start[2] += forward[2] * wi.offset[0] + right[2] * wi.offset[1] + wi.offset[2]; + //end point aiming at + VectorMA(start, 1000, forward, end); + //a little back to make sure not inside a very close enemy + VectorMA(start, -12, forward, start); + BotAI_Trace(&trace, start, mins, maxs, end, bs->entitynum, MASK_SHOT); + //if the entity is a client + if (trace.ent > 0 && trace.ent <= MAX_CLIENTS) { + if (trace.ent != attackentity) { + //if a teammate is hit + if (BotSameTeam(bs, trace.ent)) + return; + } + } + //if won't hit the enemy or not attacking a player (obelisk) + if (trace.ent != attackentity || attackentity >= MAX_CLIENTS) { + //if the projectile does radial damage + if (wi.proj.damagetype & DAMAGETYPE_RADIAL) { + if (trace.fraction * 1000 < wi.proj.radius) { + points = (wi.proj.damage - 0.5 * trace.fraction * 1000) * 0.5; + if (points > 0) { + return; + } + } + //FIXME: check if a teammate gets radial damage + } + } + //if fire has to be release to activate weapon + if (wi.flags & WFL_FIRERELEASED) { + if (bs->flags & BFL_ATTACKED) { + trap_EA_Attack(bs->client); + } + } + else { + trap_EA_Attack(bs->client); + } + bs->flags ^= BFL_ATTACKED; +} + +/* +================== +BotMapScripts +================== +*/ +void BotMapScripts(bot_state_t *bs) { + char info[1024]; + char mapname[128]; + int i, shootbutton; + float aim_accuracy; + aas_entityinfo_t entinfo; + vec3_t dir; + + trap_GetServerinfo(info, sizeof(info)); + + strncpy(mapname, Info_ValueForKey( info, "mapname" ), sizeof(mapname)-1); + mapname[sizeof(mapname)-1] = '\0'; + + if (!Q_stricmp(mapname, "q3tourney6")) { + vec3_t mins = {700, 204, 672}, maxs = {964, 468, 680}; + vec3_t buttonorg = {304, 352, 920}; + //NOTE: NEVER use the func_bobbing in q3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + //if the bot is below the bounding box + if (bs->origin[0] > mins[0] && bs->origin[0] < maxs[0]) { + if (bs->origin[1] > mins[1] && bs->origin[1] < maxs[1]) { + if (bs->origin[2] < mins[2]) { + return; + } + } + } + shootbutton = qfalse; + //if an enemy is below this bounding box then shoot the button + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + + if (i == bs->client) continue; + // + BotEntityInfo(i, &entinfo); + // + if (!entinfo.valid) continue; + //if the enemy isn't dead and the enemy isn't the bot self + if (EntityIsDead(&entinfo) || entinfo.number == bs->entitynum) continue; + // + if (entinfo.origin[0] > mins[0] && entinfo.origin[0] < maxs[0]) { + if (entinfo.origin[1] > mins[1] && entinfo.origin[1] < maxs[1]) { + if (entinfo.origin[2] < mins[2]) { + //if there's a team mate below the crusher + if (BotSameTeam(bs, i)) { + shootbutton = qfalse; + break; + } + else { + shootbutton = qtrue; + } + } + } + } + } + if (shootbutton) { + bs->flags |= BFL_IDEALVIEWSET; + VectorSubtract(buttonorg, bs->eye, dir); + vectoangles(dir, bs->ideal_viewangles); + aim_accuracy = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_AIM_ACCURACY, 0, 1); + bs->ideal_viewangles[PITCH] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[PITCH] = AngleMod(bs->ideal_viewangles[PITCH]); + bs->ideal_viewangles[YAW] += 8 * crandom() * (1 - aim_accuracy); + bs->ideal_viewangles[YAW] = AngleMod(bs->ideal_viewangles[YAW]); + // + if (InFieldOfVision(bs->viewangles, 20, bs->ideal_viewangles)) { + trap_EA_Attack(bs->client); + } + } + } + else if (!Q_stricmp(mapname, "mpq3tourney6")) { + //NOTE: NEVER use the func_bobbing in mpq3tourney6 + bs->tfl &= ~TFL_FUNCBOB; + } +} + +/* +================== +BotSetMovedir +================== +*/ +// bk001205 - made these static +static vec3_t VEC_UP = {0, -1, 0}; +static vec3_t MOVEDIR_UP = {0, 0, 1}; +static vec3_t VEC_DOWN = {0, -2, 0}; +static vec3_t MOVEDIR_DOWN = {0, 0, -1}; + +void BotSetMovedir(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 { + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors(angles, movedir, NULL, NULL); + AngleVectorsForward( angles, movedir ); + } +} + +/* +================== +BotModelMinsMaxs + +this is ugly +================== +*/ +int BotModelMinsMaxs(int modelindex, int eType, int contents, vec3_t mins, vec3_t maxs) { + gentity_t *ent; + int i; + + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( eType && ent->s.eType != eType) { + continue; + } + if ( contents && ent->r.contents != contents) { + continue; + } + if (ent->s.modelindex == modelindex) { + if (mins) + VectorAdd(ent->r.currentOrigin, ent->r.mins, mins); + if (maxs) + VectorAdd(ent->r.currentOrigin, ent->r.maxs, maxs); + return i; + } + } + if (mins) + VectorClear(mins); + if (maxs) + VectorClear(maxs); + return 0; +} + +/* +================== +BotFuncButtonGoal +================== +*/ +int BotFuncButtonActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int i, areas[10], numareas, modelindex, entitynum; + char model[128]; + float lip, dist, health, angle; + vec3_t size, start, end, mins, maxs, angles, points[10]; + vec3_t movedir, origin, goalorigin, bboxmins, bboxmaxs; + vec3_t extramins = {1, 1, 1}, extramaxs = {-1, -1, -1}; + bsp_trace_t bsptrace; + + activategoal->shoot = qfalse; + VectorClear(activategoal->target); + //create a bot goal towards the button + trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); + //get the lip of the button + trap_AAS_FloatForBSPEpairKey(bspent, "lip", &lip); + if (!lip) lip = 4; + //get the move direction from the angle + trap_AAS_FloatForBSPEpairKey(bspent, "angle", &angle); + VectorSet(angles, 0, angle, 0); + BotSetMovedir(angles, movedir); + //button size + VectorSubtract(maxs, mins, size); + //button origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + //touch distance of the button + dist = fabs(movedir[0]) * size[0] + fabs(movedir[1]) * size[1] + fabs(movedir[2]) * size[2]; + dist *= 0.5; + // + trap_AAS_FloatForBSPEpairKey(bspent, "health", &health); + //if the button is shootable + if (health) { + //calculate the shoot target + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, activategoal->target); + activategoal->shoot = qtrue; + // + BotAI_Trace(&bsptrace, bs->eye, NULL, NULL, goalorigin, bs->entitynum, MASK_SHOT); + // if the button is visible from the current position + if (bsptrace.fraction >= 1.0 || bsptrace.ent == entitynum) { + // + activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable button + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + VectorCopy(bs->origin, activategoal->goal.origin); + activategoal->goal.areanum = bs->areanum; + VectorSet(activategoal->goal.mins, -8, -8, -8); + VectorSet(activategoal->goal.maxs, 8, 8, 8); + // + return qtrue; + } + else { + //create a goal from where the button is visible and shoot at the button from there + //add bounding box size to the dist + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 512; + numareas = trap_AAS_TraceAreas(start, end, areas, points, 10); + // + for (i = numareas-1; i >= 0; i--) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < 0) { + // FIXME: trace forward and maybe in other directions to find a valid area + } + if (i >= 0) { + // + VectorCopy(points[i], activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSet(activategoal->goal.mins, 8, 8, 8); + VectorSet(activategoal->goal.maxs, -8, -8, -8); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + } + return qfalse; + } + else { + //add bounding box size to the dist + trap_AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bboxmins, bboxmaxs); + for (i = 0; i < 3; i++) { + if (movedir[i] < 0) dist += fabs(movedir[i]) * fabs(bboxmaxs[i]); + else dist += fabs(movedir[i]) * fabs(bboxmins[i]); + } + //calculate the goal origin + VectorMA(origin, -dist, movedir, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + // + VectorCopy(origin, activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSubtract(mins, origin, activategoal->goal.mins); + VectorSubtract(maxs, origin, activategoal->goal.maxs); + // + for (i = 0; i < 3; i++) + { + if (movedir[i] < 0) activategoal->goal.maxs[i] += fabs(movedir[i]) * fabs(extramaxs[i]); + else activategoal->goal.mins[i] += fabs(movedir[i]) * fabs(extramins[i]); + } //end for + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotFuncDoorGoal +================== +*/ +int BotFuncDoorActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int modelindex, entitynum; + char model[MAX_INFO_STRING]; + vec3_t mins, maxs, origin, angles; + + //shoot at the shootable door + trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, ET_MOVER, 0, mins, maxs); + //door origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + VectorCopy(origin, activategoal->target); + activategoal->shoot = qtrue; + // + activategoal->goal.entitynum = entitynum; //NOTE: this is the entity number of the shootable door + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + VectorCopy(bs->origin, activategoal->goal.origin); + activategoal->goal.areanum = bs->areanum; + VectorSet(activategoal->goal.mins, -8, -8, -8); + VectorSet(activategoal->goal.maxs, 8, 8, 8); + return qtrue; +} + +/* +================== +BotTriggerMultipleGoal +================== +*/ +int BotTriggerMultipleActivateGoal(bot_state_t *bs, int bspent, bot_activategoal_t *activategoal) { + int i, areas[10], numareas, modelindex, entitynum; + char model[128]; + vec3_t start, end, mins, maxs, angles; + vec3_t origin, goalorigin; + + activategoal->shoot = qfalse; + VectorClear(activategoal->target); + //create a bot goal towards the trigger + trap_AAS_ValueForBSPEpairKey(bspent, "model", model, sizeof(model)); + if (!*model) + return qfalse; + modelindex = atoi(model+1); + if (!modelindex) + return qfalse; + VectorClear(angles); + entitynum = BotModelMinsMaxs(modelindex, 0, CONTENTS_TRIGGER, mins, maxs); + //trigger origin + VectorAdd(mins, maxs, origin); + VectorScale(origin, 0.5, origin); + VectorCopy(origin, goalorigin); + // + VectorCopy(goalorigin, start); + start[2] += 24; + VectorCopy(start, end); + end[2] -= 100; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + // + for (i = 0; i < numareas; i++) { + if (trap_AAS_AreaReachability(areas[i])) { + break; + } + } + if (i < numareas) { + VectorCopy(origin, activategoal->goal.origin); + activategoal->goal.areanum = areas[i]; + VectorSubtract(mins, origin, activategoal->goal.mins); + VectorSubtract(maxs, origin, activategoal->goal.maxs); + // + activategoal->goal.entitynum = entitynum; + activategoal->goal.number = 0; + activategoal->goal.flags = 0; + return qtrue; + } + return qfalse; +} + +/* +================== +BotPopFromActivateGoalStack +================== +*/ +int BotPopFromActivateGoalStack(bot_state_t *bs) { + if (!bs->activatestack) + return qfalse; + BotEnableActivateGoalAreas(bs->activatestack, qtrue); + bs->activatestack->inuse = qfalse; + bs->activatestack->justused_time = FloatTime(); + bs->activatestack = bs->activatestack->next; + return qtrue; +} + +/* +================== +BotPushOntoActivateGoalStack +================== +*/ +int BotPushOntoActivateGoalStack(bot_state_t *bs, bot_activategoal_t *activategoal) { + int i, best; + float besttime; + + best = -1; + besttime = FloatTime() + 9999; + // + for (i = 0; i < MAX_ACTIVATESTACK; i++) { + if (!bs->activategoalheap[i].inuse) { + if (bs->activategoalheap[i].justused_time < besttime) { + besttime = bs->activategoalheap[i].justused_time; + best = i; + } + } + } + if (best != -1) { + memcpy(&bs->activategoalheap[best], activategoal, sizeof(bot_activategoal_t)); + bs->activategoalheap[best].inuse = qtrue; + bs->activategoalheap[best].next = bs->activatestack; + bs->activatestack = &bs->activategoalheap[best]; + return qtrue; + } + return qfalse; +} + +/* +================== +BotClearActivateGoalStack +================== +*/ +void BotClearActivateGoalStack(bot_state_t *bs) { + while(bs->activatestack) + BotPopFromActivateGoalStack(bs); +} + +/* +================== +BotEnableActivateGoalAreas +================== +*/ +void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable) { + int i; + + if (activategoal->areasdisabled == !enable) + return; + for (i = 0; i < activategoal->numareas; i++) + trap_AAS_EnableRoutingArea( activategoal->areas[i], enable ); + activategoal->areasdisabled = !enable; +} + +/* +================== +BotIsGoingToActivateEntity +================== +*/ +int BotIsGoingToActivateEntity(bot_state_t *bs, int entitynum) { + bot_activategoal_t *a; + int i; + + for (a = bs->activatestack; a; a = a->next) { + if (a->time < FloatTime()) + continue; + if (a->goal.entitynum == entitynum) + return qtrue; + } + for (i = 0; i < MAX_ACTIVATESTACK; i++) { + if (bs->activategoalheap[i].inuse) + continue; + // + if (bs->activategoalheap[i].goal.entitynum == entitynum) { + // if the bot went for this goal less than 2 seconds ago + if (bs->activategoalheap[i].justused_time > FloatTime() - 2) + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotGetActivateGoal + + returns the number of the bsp entity to activate + goal->entitynum will be set to the game entity to activate +================== +*/ +//#define OBSTACLEDEBUG + +int BotGetActivateGoal(bot_state_t *bs, int entitynum, bot_activategoal_t *activategoal) { + int i, ent, cur_entities[10], spawnflags, modelindex, areas[MAX_ACTIVATEAREAS*2], numareas, t; + char model[MAX_INFO_STRING], tmpmodel[128]; + char target[128], classname[128]; + float health; + char targetname[10][128]; + aas_entityinfo_t entinfo; + aas_areainfo_t areainfo; + vec3_t origin, angles, absmins, absmaxs; + + memset(activategoal, 0, sizeof(bot_activategoal_t)); + BotEntityInfo(entitynum, &entinfo); + Com_sprintf(model, sizeof( model ), "*%d", entinfo.modelindex); + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", tmpmodel, sizeof(tmpmodel))) continue; + if (!strcmp(model, tmpmodel)) break; + } + if (!ent) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity found with model %s\n", model); + return 0; + } + trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname)); + if (!classname) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model %s has no classname\n", model); + return 0; + } + //if it is a door + if (!strcmp(classname, "func_door")) { + if (trap_AAS_FloatForBSPEpairKey(ent, "health", &health)) { + //if the door has health then the door must be shot to open + if (health) { + BotFuncDoorActivateGoal(bs, ent, activategoal); + return ent; + } + } + // + trap_AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // if the door starts open then just wait for the door to return + if ( spawnflags & 1 ) + return 0; + //get the door origin + if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) { + VectorClear(origin); + } + //if the door is open or opening already + if (!VectorCompare(origin, entinfo.origin)) + return 0; + // store all the areas the door is in + trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model)); + if (*model) { + modelindex = atoi(model+1); + if (modelindex) { + VectorClear(angles); + BotModelMinsMaxs(modelindex, ET_MOVER, 0, absmins, absmaxs); + // + numareas = trap_AAS_BBoxAreas(absmins, absmaxs, areas, MAX_ACTIVATEAREAS*2); + // store the areas with reachabilities first + for (i = 0; i < numareas; i++) { + if (activategoal->numareas >= MAX_ACTIVATEAREAS) + break; + if ( !trap_AAS_AreaReachability(areas[i]) ) { + continue; + } + trap_AAS_AreaInfo(areas[i], &areainfo); + if (areainfo.contents & AREACONTENTS_MOVER) { + activategoal->areas[activategoal->numareas++] = areas[i]; + } + } + // store any remaining areas + for (i = 0; i < numareas; i++) { + if (activategoal->numareas >= MAX_ACTIVATEAREAS) + break; + if ( trap_AAS_AreaReachability(areas[i]) ) { + continue; + } + trap_AAS_AreaInfo(areas[i], &areainfo); + if (areainfo.contents & AREACONTENTS_MOVER) { + activategoal->areas[activategoal->numareas++] = areas[i]; + } + } + } + } + } + // if the bot is blocked by or standing on top of a button + if (!strcmp(classname, "func_button")) { + return 0; + } + // get the targetname so we can find an entity with a matching target + if (!trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[0], sizeof(targetname[0]))) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with model \"%s\" has no targetname\n", model); + } + return 0; + } + // allow tree-like activation + cur_entities[0] = trap_AAS_NextBSPEntity(0); + for (i = 0; i >= 0 && i < 10;) { + for (ent = cur_entities[i]; ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "target", target, sizeof(target))) continue; + if (!strcmp(targetname[i], target)) { + cur_entities[i] = trap_AAS_NextBSPEntity(ent); + break; + } + } + if (!ent) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no entity with target \"%s\"\n", targetname[i]); + } + i--; + continue; + } + if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", classname, sizeof(classname))) { + if (bot_developer.integer) { + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: entity with target \"%s\" has no classname\n", targetname[i]); + } + continue; + } + // BSP button model + if (!strcmp(classname, "func_button")) { + // + if (!BotFuncButtonActivateGoal(bs, ent, activategoal)) + continue; + // if the bot tries to activate this button already + if ( bs->activatestack && bs->activatestack->inuse && + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) + continue; + // if the bot is in a reachability area + if ( trap_AAS_AreaReachability(bs->areanum) ) { + // disable all areas the blocking entity is in + BotEnableActivateGoalAreas( activategoal, qfalse ); + // + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); + // if the button is not reachable + if (!t) { + continue; + } + activategoal->time = FloatTime() + t * 0.01 + 5; + } + return ent; + } + // invisible trigger multiple box + else if (!strcmp(classname, "trigger_multiple")) { + // + if (!BotTriggerMultipleActivateGoal(bs, ent, activategoal)) + continue; + // if the bot tries to activate this trigger already + if ( bs->activatestack && bs->activatestack->inuse && + bs->activatestack->goal.entitynum == activategoal->goal.entitynum && + bs->activatestack->time > FloatTime() && + bs->activatestack->start_time < FloatTime() - 2) + continue; + // if the bot is in a reachability area + if ( trap_AAS_AreaReachability(bs->areanum) ) { + // disable all areas the blocking entity is in + BotEnableActivateGoalAreas( activategoal, qfalse ); + // + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, activategoal->goal.areanum, bs->tfl); + // if the trigger is not reachable + if (!t) { + continue; + } + activategoal->time = FloatTime() + t * 0.01 + 5; + } + return ent; + } + else if (!strcmp(classname, "func_timer")) { + // just skip the func_timer + continue; + } + // the actual button or trigger might be linked through a target_relay or target_delay + else if (!strcmp(classname, "target_relay") || !strcmp(classname, "target_delay")) { + if (trap_AAS_ValueForBSPEpairKey(ent, "targetname", targetname[i+1], sizeof(targetname[0]))) { + i++; + cur_entities[i] = trap_AAS_NextBSPEntity(0); + } + } + } +#ifdef OBSTACLEDEBUG + BotAI_Print(PRT_ERROR, "BotGetActivateGoal: no valid activator for entity with target \"%s\"\n", targetname[0]); +#endif + return 0; +} + +/* +================== +BotGoForActivateGoal +================== +*/ +int BotGoForActivateGoal(bot_state_t *bs, bot_activategoal_t *activategoal) { + aas_entityinfo_t activateinfo; + + activategoal->inuse = qtrue; + if (!activategoal->time) + activategoal->time = FloatTime() + 10; + activategoal->start_time = FloatTime(); + BotEntityInfo(activategoal->goal.entitynum, &activateinfo); + VectorCopy(activateinfo.origin, activategoal->origin); + // + if (BotPushOntoActivateGoalStack(bs, activategoal)) { + // enter the activate entity AI node + AIEnter_Seek_ActivateEntity(bs, "BotGoForActivateGoal"); + return qtrue; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(activategoal, qtrue); + return qfalse; + } +} + +/* +================== +BotPrintActivateGoalInfo +================== +*/ +void BotPrintActivateGoalInfo(bot_state_t *bs, bot_activategoal_t *activategoal, int bspent) { + char netname[MAX_NETNAME]; + char classname[128]; + char buf[128]; + + ClientName(bs->client, netname, sizeof(netname)); + trap_AAS_ValueForBSPEpairKey(bspent, "classname", classname, sizeof(classname)); + if (activategoal->shoot) { + Com_sprintf(buf, sizeof(buf), "%s: I have to shoot at a %s from %1.1f %1.1f %1.1f in area %d\n", + netname, classname, + activategoal->goal.origin[0], + activategoal->goal.origin[1], + activategoal->goal.origin[2], + activategoal->goal.areanum); + } + else { + Com_sprintf(buf, sizeof(buf), "%s: I have to activate a %s at %1.1f %1.1f %1.1f in area %d\n", + netname, classname, + activategoal->goal.origin[0], + activategoal->goal.origin[1], + activategoal->goal.origin[2], + activategoal->goal.areanum); + } + trap_EA_Say(bs->client, buf); +} + +/* +================== +BotRandomMove +================== +*/ +void BotRandomMove(bot_state_t *bs, bot_moveresult_t *moveresult) { + vec3_t dir, angles; + + angles[0] = 0; + angles[1] = random() * 360; + angles[2] = 0; + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors(angles, dir, NULL, NULL); + AngleVectorsForward( angles, dir ); + + trap_BotMoveInDirection(bs->ms, dir, 400, MOVE_WALK); + + moveresult->failure = qfalse; + VectorCopy(dir, moveresult->movedir); +} + +/* +================== +BotAIBlocked + +Very basic handling of bots being blocked by other entities. +Check what kind of entity is blocking the bot and try to activate +it. If that's not an option then try to walk around or over the entity. +Before the bot ends in this part of the AI it should predict which doors to +open, which buttons to activate etc. +================== +*/ +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate) { + int movetype, bspent; + vec3_t hordir, start, end, mins, maxs, sideward, angles, up = {0, 0, 1}; + aas_entityinfo_t entinfo; + bot_activategoal_t activategoal; + + // if the bot is not blocked by anything + if (!moveresult->blocked) { + bs->notblocked_time = FloatTime(); + return; + } + // if stuck in a solid area + if ( moveresult->type == RESULTTYPE_INSOLIDAREA ) { + // move in a random direction in the hope to get out + BotRandomMove(bs, moveresult); + // + return; + } + // get info for the entity that is blocking the bot + BotEntityInfo(moveresult->blockentity, &entinfo); +#ifdef OBSTACLEDEBUG + ClientName(bs->client, netname, sizeof(netname)); + BotAI_Print(PRT_MESSAGE, "%s: I'm blocked by model %d\n", netname, entinfo.modelindex); +#endif OBSTACLEDEBUG + // if blocked by a bsp model and the bot wants to activate it + if (activate && entinfo.modelindex > 0 && entinfo.modelindex <= max_bspmodelindex) { + // find the bsp entity which should be activated in order to get the blocking entity out of the way + bspent = BotGetActivateGoal(bs, entinfo.number, &activategoal); + if (bspent) { + // + if (bs->activatestack && !bs->activatestack->inuse) + bs->activatestack = NULL; + // if not already trying to activate this entity + if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { + // + BotGoForActivateGoal(bs, &activategoal); + } + // if ontop of an obstacle or + // if the bot is not in a reachability area it'll still + // need some dynamic obstacle avoidance, otherwise return + if (!(moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) && + trap_AAS_AreaReachability(bs->areanum)) + return; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(&activategoal, qtrue); + } + } + // just some basic dynamic obstacle avoidance code + hordir[0] = moveresult->movedir[0]; + hordir[1] = moveresult->movedir[1]; + hordir[2] = 0; + // if no direction just take a random direction + if (VectorNormalize(hordir) < 0.1) { + VectorSet(angles, 0, 360 * random(), 0); + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors(angles, hordir, NULL, NULL); + AngleVectorsForward( angles, hordir ); + } + // + //if (moveresult->flags & MOVERESULT_ONTOPOFOBSTACLE) movetype = MOVE_JUMP; + //else + movetype = MOVE_WALK; + // if there's an obstacle at the bot's feet and head then + // the bot might be able to crouch through + VectorCopy(bs->origin, start); + start[2] += 18; + VectorMA(start, 5, hordir, end); + VectorSet(mins, -16, -16, -24); + VectorSet(maxs, 16, 16, 4); + // + //bsptrace = AAS_Trace(start, mins, maxs, end, bs->entitynum, MASK_PLAYERSOLID); + //if (bsptrace.fraction >= 1) movetype = MOVE_CROUCH; + // get the sideward vector + CrossProduct(hordir, up, sideward); + // + if (bs->flags & BFL_AVOIDRIGHT) VectorNegate(sideward, sideward); + // try to crouch straight forward? + if (movetype != MOVE_CROUCH || !trap_BotMoveInDirection(bs->ms, hordir, 400, movetype)) { + // perform the movement + if (!trap_BotMoveInDirection(bs->ms, sideward, 400, movetype)) { + // flip the avoid direction flag + bs->flags ^= BFL_AVOIDRIGHT; + // flip the direction + // VectorNegate(sideward, sideward); + VectorMA(sideward, -1, hordir, sideward); + // move in the other direction + trap_BotMoveInDirection(bs->ms, sideward, 400, movetype); + } + } + // + if (bs->notblocked_time < FloatTime() - 0.4) { + // just reset goals and hope the bot will go into another direction? + // is this still needed?? + if (bs->ainode == AINode_Seek_NBG) bs->nbg_time = 0; + else if (bs->ainode == AINode_Seek_LTG) bs->ltg_time = 0; + } +} + +/* +================== +BotAIPredictObstacles + +Predict the route towards the goal and check if the bot +will be blocked by certain obstacles. When the bot has obstacles +on it's path the bot should figure out if they can be removed +by activating certain entities. +================== +*/ +int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal) { + int modelnum, entitynum, bspent; + bot_activategoal_t activategoal; + aas_predictroute_t route; + + if (!bot_predictobstacles.integer) + return qfalse; + + // always predict when the goal change or at regular intervals + if (bs->predictobstacles_goalareanum == goal->areanum && + bs->predictobstacles_time > FloatTime() - 6) { + return qfalse; + } + bs->predictobstacles_goalareanum = goal->areanum; + bs->predictobstacles_time = FloatTime(); + + // predict at most 100 areas or 10 seconds ahead + trap_AAS_PredictRoute(&route, bs->areanum, bs->origin, + goal->areanum, bs->tfl, 100, 1000, + RSE_USETRAVELTYPE|RSE_ENTERCONTENTS, + AREACONTENTS_MOVER, TFL_BRIDGE, 0); + // if bot has to travel through an area with a mover + if (route.stopevent & RSE_ENTERCONTENTS) { + // if the bot will run into a mover + if (route.endcontents & AREACONTENTS_MOVER) { + //NOTE: this only works with bspc 2.1 or higher + modelnum = (route.endcontents & AREACONTENTS_MODELNUM) >> AREACONTENTS_MODELNUMSHIFT; + if (modelnum) { + // + entitynum = BotModelMinsMaxs(modelnum, ET_MOVER, 0, NULL, NULL); + if (entitynum) { + //NOTE: BotGetActivateGoal already checks if the door is open or not + bspent = BotGetActivateGoal(bs, entitynum, &activategoal); + if (bspent) { + // + if (bs->activatestack && !bs->activatestack->inuse) + bs->activatestack = NULL; + // if not already trying to activate this entity + if (!BotIsGoingToActivateEntity(bs, activategoal.goal.entitynum)) { + // + //BotAI_Print(PRT_MESSAGE, "blocked by mover model %d, entity %d ?\n", modelnum, entitynum); + // + BotGoForActivateGoal(bs, &activategoal); + return qtrue; + } + else { + // enable any routing areas that were disabled + BotEnableActivateGoalAreas(&activategoal, qtrue); + } + } + } + } + } + } + else if (route.stopevent & RSE_USETRAVELTYPE) { + if (route.endtravelflags & TFL_BRIDGE) { + //FIXME: check if the bridge is available to travel over + } + } + return qfalse; +} + +/* +================== +BotCheckConsoleMessages +================== +*/ +void BotCheckConsoleMessages(bot_state_t *bs) { + char botname[MAX_NETNAME], message[MAX_MESSAGE_SIZE], netname[MAX_NETNAME], *ptr; + float chat_reply; + int context, handle; + bot_consolemessage_t m; + bot_match_t match; + + //the name of this bot + ClientName(bs->client, botname, sizeof(botname)); + // + while((handle = trap_BotNextConsoleMessage(bs->cs, &m)) != 0) { + //if the chat state is flooded with messages the bot will read them quickly + if (trap_BotNumConsoleMessages(bs->cs) < 10) { + //if it is a chat message the bot needs some time to read it + if (m.type == CMS_CHAT && m.time > FloatTime() - (1 + random())) break; + } + // + ptr = m.message; + //if it is a chat message then don't unify white spaces and don't + //replace synonyms in the netname + if (m.type == CMS_CHAT) { + // + if (trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + ptr = m.message + match.variables[MESSAGE].offset; + } + } + //unify the white spaces in the message + trap_UnifyWhiteSpaces(ptr); + //replace synonyms in the right context + context = BotSynonymContext(bs); + trap_BotReplaceSynonyms(ptr, context); + //if there's no match + if (!BotMatchMessage(bs, m.message)) { + //if it is a chat message + if (m.type == CMS_CHAT && !bot_nochat.integer) { + // + if (!trap_BotFindMatch(m.message, &match, MTCONTEXT_REPLYCHAT)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //don't use eliza chats with team messages + if (match.subtype & ST_TEAM) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + // + trap_BotMatchVariable(&match, NETNAME, netname, sizeof(netname)); + trap_BotMatchVariable(&match, MESSAGE, message, sizeof(message)); + //if this is a message from the bot self + if (bs->client == ClientFromName(netname)) { + trap_BotRemoveConsoleMessage(bs->cs, handle); + continue; + } + //unify the message + trap_UnifyWhiteSpaces(message); + // + trap_Cvar_Update(&bot_testrchat); + if (bot_testrchat.integer) { + // + trap_BotLibVarSet("bot_testrchat", "1"); + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + BotAI_Print(PRT_MESSAGE, "------------------------\n"); + } + else { + BotAI_Print(PRT_MESSAGE, "**** no valid reply ****\n"); + } + } + //if at a valid chat position and not chatting already and not in teamplay + else if (bs->ainode != AINode_Stand && BotValidChatPosition(bs) && !TeamPlayIsOn()) { + chat_reply = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_CHAT_REPLY, 0, 1); + if (random() < 1.5 / (NumBots()+1) && random() < chat_reply) { + //if bot replies with a chat message + if (trap_BotReplyChat(bs->cs, message, context, CONTEXT_REPLY, + NULL, NULL, + NULL, NULL, + NULL, NULL, + botname, netname)) { + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "BotCheckConsoleMessages: reply chat"); + //EA_Say(bs->client, bs->cs.chatmessage); + break; + } + } + } + } + } + //remove the console message + trap_BotRemoveConsoleMessage(bs->cs, handle); + } +} + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckForGrenades(bot_state_t *bs, entityState_t *state) { + // if this is not a grenade + if (state->eType != ET_MISSILE || state->weapon != WP_GRENADE_LAUNCHER) { + //PKMOD - Ergodic 03/15/01 - avoid beartraps too + //PKMOD - Ergodic 04/14/01 - remove avoid beartraps +// if (state->eType != ET_BEARTRAP || state->weapon != WP_BEARTRAP) { + //PKMOD - Ergodic 03/15/01 - avoid nails too + //PKMOD - Ergodic 04/14/01 - remove avoid nails +// if (state->eType != ET_NAIL || state->weapon != WP_NAILGUN) + return; + } + // try to avoid the grenade + trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); +} + +/* +================== +BotCheckForBeartraps +//PKMOD - Ergodic 04/14/01 - new code for bots to avoid/target enemy beartraps +================== +*/ +void BotCheckForBeartraps(bot_state_t *bs, entityState_t *state) { + // if this is not a Beartrap + if (state->eType != ET_BEARTRAP || state->weapon != WP_BEARTRAP) + return; + // if this beartrap is from someone on our own team + //PKMOD - Ergodic 10/10/03 - BOT AI will use modelindex instead of generic1 in order to free generic1 for "cell damages" + // if (state->generic1 == BotTeam(bs)) + if (state->modelindex == BotTeam(bs)) + + return; + // if the bot doesn't have a weapon to destroy the beartrap + //PKMOD - Ergodic - 04/14/01 - add explosive shells - duh + bs->inventory[INVENTORY_EXPLOSIVESHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_EXPLODING_SHELLS)) != 0; + bs->inventory[INVENTORY_EXPLOSIVESHELLS] = bs->cur_ps.ammo[WP_EXPLODING_SHELLS]; + + if (!(bs->inventory[INVENTORY_PKA_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0) && + !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && + !(bs->inventory[INVENTORY_EXPLOSIVESHOTGUN] > 0 && bs->inventory[INVENTORY_EXPLOSIVESHELLS] > 0) ) { + return; + } + // try to avoid the beartrap + //PKMOD - Ergodic - 08/30/01 - reduce avoid spot size from 160 to 120 + trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 120, AVOID_ALWAYS); + // + if (bs->numbeartraps >= MAX_BEARTRAPS) + return; + bs->beartraps[bs->numbeartraps] = state->number; + bs->numbeartraps++; +} + +/* +================== +BotCheckForAutosentrys +//PKMOD - Ergodic 08/30/01 - new code for bots to avoid/target enemy sentrys +================== +*/ +void BotCheckForAutosentrys(bot_state_t *bs, entityState_t *state) { + // if this is not an autosentry + if ( state->eType != ET_AUTOSENTRY_BASE || state->weapon != WP_SENTRY ) + return; + // if this autosentry is from someone on our own team + //PKMOD - Ergodic 10/10/03 - BOT AI will use modelindex instead of generic1 in order to free generic1 for "cell damages" + // if ( state->generic1 == BotTeam(bs) ) + if ( state->modelindex == BotTeam(bs) ) + return; + // if the bot doesn't have a weapon to destroy the beartrap + //PKMOD - Ergodic - 04/14/01 - add explosive shells - duh +// bs->inventory[INVENTORY_EXPLOSIVESHOTGUN] = (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_EXPLODING_SHELLS)) != 0; +// bs->inventory[INVENTORY_EXPLOSIVESHELLS] = bs->cur_ps.ammo[WP_EXPLODING_SHELLS]; + + if (!(bs->inventory[INVENTORY_PKA_NAILGUN] > 0 && bs->inventory[INVENTORY_NAILS] > 0) && + !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && + !(bs->inventory[INVENTORY_EXPLOSIVESHOTGUN] > 0 && bs->inventory[INVENTORY_EXPLOSIVESHELLS] > 0) && + !(bs->inventory[INVENTORY_SHOTGUN] > 0 && bs->inventory[INVENTORY_SHELLS] > 0) && + !(bs->inventory[INVENTORY_RAILGUN] > 0 && bs->inventory[INVENTORY_SLUGS] > 0) ) { + return; + } + // try to avoid the autosentry + //PKMOD - Ergodic - 08/30/01 - set avoid spot size to 400 + trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 400, AVOID_ALWAYS); + // + if ( bs->numautosentrys >= MAX_AUTOSENTRYS ) + return; + bs->autosentrys[bs->numautosentrys] = state->number; + bs->numautosentrys++; +} + + +/* +================== +BotCheckForGravityWells +//PKMOD - Ergodic 08/30/01 - new code for bots to avoid Gravity Wells +================== +*/ +void BotCheckForGravityWells(bot_state_t *bs, entityState_t *state) { + // if this is not a Beartrap + if ( state->eType != ET_GRAVITY_WELL || state->weapon != WP_SENTRY ) + return; + + // try to avoid the Gravity Well + //PKMOD - Ergodic - 08/30/01 - set avoid spot size to 1000 [Note: gw radius is 1200 - grin] + trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 1000, AVOID_ALWAYS); + // + if ( bs->numgravitywells >= MAX_GRAVITYWELLS ) + return; + bs->gravitywells[bs->numgravitywells] = state->number; + bs->numgravitywells++; +} + +#ifdef MISSIONPACK +/* +================== +BotCheckForProxMines +================== +*/ +void BotCheckForProxMines(bot_state_t *bs, entityState_t *state) { + // if this is not a prox mine + if (state->eType != ET_MISSILE || state->weapon != WP_PROX_LAUNCHER) + return; + // if this prox mine is from someone on our own team + if (state->generic1 == BotTeam(bs)) + return; + // if the bot doesn't have a weapon to deactivate the mine + if (!(bs->inventory[INVENTORY_PLASMAGUN] > 0 && bs->inventory[INVENTORY_CELLS] > 0) && + !(bs->inventory[INVENTORY_ROCKETLAUNCHER] > 0 && bs->inventory[INVENTORY_ROCKETS] > 0) && + !(bs->inventory[INVENTORY_BFG10K] > 0 && bs->inventory[INVENTORY_BFGAMMO] > 0) ) { + return; + } + // try to avoid the prox mine + trap_BotAddAvoidSpot(bs->ms, state->pos.trBase, 160, AVOID_ALWAYS); + // + if (bs->numproxmines >= MAX_PROXMINES) + return; + bs->proxmines[bs->numproxmines] = state->number; + bs->numproxmines++; +} + +/* +================== +BotCheckForKamikazeBody +================== +*/ +void BotCheckForKamikazeBody(bot_state_t *bs, entityState_t *state) { + // if this entity is not wearing the kamikaze + if (!(state->eFlags & EF_KAMIKAZE)) + return; + // if this entity isn't dead + if (!(state->eFlags & EF_DEAD)) + return; + //remember this kamikaze body + bs->kamikazebody = state->number; +} +#endif + +/* +================== +BotCheckEvents +================== +*/ +void BotCheckEvents(bot_state_t *bs, entityState_t *state) { + int event; + char buf[128]; +#ifdef MISSIONPACK + aas_entityinfo_t entinfo; +#endif + + //NOTE: this sucks, we're accessing the gentity_t directly + //but there's no other fast way to do it right now + if (bs->entityeventTime[state->number] == g_entities[state->number].eventTime) { + return; + } + bs->entityeventTime[state->number] = g_entities[state->number].eventTime; + //if it's an event only entity + if (state->eType > ET_EVENTS) { + event = (state->eType - ET_EVENTS) & ~EV_EVENT_BITS; + } + else { + event = state->event & ~EV_EVENT_BITS; + } + // + switch(event) { + //client obituary event + case EV_OBITUARY: + { + int target, attacker, mod; + + target = state->otherEntityNum; + attacker = state->otherEntityNum2; + mod = state->eventParm; + // + if (target == bs->client) { + bs->botdeathtype = mod; + bs->lastkilledby = attacker; + // + if (target == attacker || + target == ENTITYNUM_NONE || + target == ENTITYNUM_WORLD) bs->botsuicide = qtrue; + else bs->botsuicide = qfalse; + // + bs->num_deaths++; + } + //else if this client was killed by the bot + else if (attacker == bs->client) { + bs->enemydeathtype = mod; + bs->lastkilledplayer = target; + bs->killedenemy_time = FloatTime(); + // + bs->num_kills++; + } + else if (attacker == bs->enemy && target == attacker) { + bs->enemysuicide = qtrue; + } + // +#ifdef MISSIONPACK + if (gametype == GT_1FCTF) { + // + BotEntityInfo(target, &entinfo); + if ( entinfo.powerups & ( 1 << PW_NEUTRALFLAG ) ) { + if (!BotSameTeam(bs, target)) { + bs->neutralflagstatus = 3; //enemy dropped the flag + bs->flagstatuschanged = qtrue; + } + } + } +#endif + break; + } + case EV_GLOBAL_SOUND: + { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GLOBAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + /* + if (!strcmp(buf, "sound/teamplay/flagret_red.wav")) { + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + else if (!strcmp(buf, "sound/teamplay/flagret_blu.wav")) { + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + } + else*/ +#ifdef MISSIONPACK + if (!strcmp(buf, "sound/items/kamikazerespawn.wav" )) { + //the kamikaze respawned so dont avoid it + BotDontAvoid(bs, "Kamikaze"); + } + else +#endif + if (!strcmp(buf, "sound/items/poweruprespawn.wav")) { + //powerup respawned... go get it + BotGoForPowerups(bs); + } + break; + } + case EV_GLOBAL_TEAM_SOUND: + { + if (gametype == GT_CTF) { + switch(state->eventParm) { + case GTS_RED_CAPTURE: + bs->blueflagstatus = 0; + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_BLUE_CAPTURE: + bs->blueflagstatus = 0; + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_RED_RETURN: + //blue flag is returned + bs->blueflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_RETURN: + //red flag is returned + bs->redflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_TAKEN: + //blue flag is taken + bs->blueflagstatus = 1; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + case GTS_BLUE_TAKEN: + //red flag is taken + bs->redflagstatus = 1; + bs->flagstatuschanged = qtrue; + break; //see BotMatch_CTF + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + switch(state->eventParm) { + case GTS_RED_CAPTURE: + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_CAPTURE: + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_RETURN: + //flag has returned + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_RETURN: + //flag has returned + bs->neutralflagstatus = 0; + bs->flagstatuschanged = qtrue; + break; + case GTS_RED_TAKEN: + bs->neutralflagstatus = BotTeam(bs) == TEAM_RED ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c + bs->flagstatuschanged = qtrue; + break; + case GTS_BLUE_TAKEN: + bs->neutralflagstatus = BotTeam(bs) == TEAM_BLUE ? 2 : 1; //FIXME: check Team_TakeFlagSound in g_team.c + bs->flagstatuschanged = qtrue; + break; + } + } +#endif + break; + } + case EV_PLAYER_TELEPORT_IN: + { + VectorCopy(state->origin, lastteleport_origin); + lastteleport_time = FloatTime(); + break; + } + case EV_GENERAL_SOUND: + { + //if this sound is played on the bot + if (state->number == bs->client) { + if (state->eventParm < 0 || state->eventParm > MAX_SOUNDS) { + BotAI_Print(PRT_ERROR, "EV_GENERAL_SOUND: eventParm (%d) out of range\n", state->eventParm); + break; + } + //check out the sound + trap_GetConfigstring(CS_SOUNDS + state->eventParm, buf, sizeof(buf)); + //if falling into a death pit + if (!strcmp(buf, "*falling1.wav")) { + //if the bot has a personal teleporter + if (bs->inventory[INVENTORY_TELEPORTER] > 0) { + //PKMOD - Ergodic 03/17/04 - set the active holdable item for the BOT + g_entities[bs->entitynum].client->ps.stats[STAT_ACTIVE_HOLDABLE] = HI_TELEPORTER; + //use the holdable item + trap_EA_Use(bs->client); + } + } + } + break; + } + case EV_FOOTSTEP: + case EV_FOOTSTEP_METAL: + case EV_FOOTSPLASH: + case EV_FOOTWADE: + case EV_SWIM: + case EV_FALL_SHORT: + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: + case EV_JUMP_PAD: + case EV_JUMP: + case EV_TAUNT: + case EV_WATER_TOUCH: + case EV_WATER_LEAVE: + case EV_WATER_UNDER: + case EV_WATER_CLEAR: + case EV_ITEM_PICKUP: + case EV_GLOBAL_ITEM_PICKUP: + case EV_NOAMMO: + case EV_CHANGE_WEAPON: + case EV_FIRE_WEAPON: + //FIXME: either add to sound queue or mark player as someone making noise + break; + case EV_USE_ITEM0: + case EV_USE_ITEM1: + case EV_USE_ITEM2: + case EV_USE_ITEM3: + case EV_USE_ITEM4: + case EV_USE_ITEM5: + case EV_USE_ITEM6: + case EV_USE_ITEM7: + case EV_USE_ITEM8: + case EV_USE_ITEM9: + case EV_USE_ITEM10: + case EV_USE_ITEM11: + case EV_USE_ITEM12: + case EV_USE_ITEM13: + case EV_USE_ITEM14: + break; + } +} + +/* +================== +BotCheckSnapshot +================== +*/ +void BotCheckSnapshot(bot_state_t *bs) { + int ent; + entityState_t state; + + //remove all avoid spots + trap_BotAddAvoidSpot(bs->ms, vec3_origin, 0, AVOID_CLEAR); + + //PKMOD - Ergodic 08/30/01 - remove these fields in PKA + /*++++ + //reset kamikaze body + bs->kamikazebody = 0; + //reset number of proxmines + bs->numproxmines = 0; + ----*/ + + //PKMOD - Ergodic 04/14/01 - reset number of beartraps + bs->numbeartraps = 0; + //PKMOD - Ergodic 08/30/01 - reset number of autosentrys + bs->numautosentrys = 0; + //PKMOD - Ergodic 08/30/01 - reset number of gravitywells + bs->numgravitywells = 0; + + // + ent = 0; + while( ( ent = BotAI_GetSnapshotEntity( bs->client, ent, &state ) ) != -1 ) { + //check the entity state for events + BotCheckEvents(bs, &state); + //check for grenades the bot should avoid + BotCheckForGrenades(bs, &state); + //PKMOD - Ergodic 04/14/01 - check for beartraps which the bot should destroy + BotCheckForAutosentrys(bs, &state); + //PKMOD - Ergodic 04/14/01 - check for beartraps which the bot should destroy + BotCheckForBeartraps(bs, &state); + // +#ifdef MISSIONPACK + //check for proximity mines which the bot should deactivate + BotCheckForProxMines(bs, &state); + //check for dead bodies with the kamikaze effect which should be gibbed + BotCheckForKamikazeBody(bs, &state); +#endif + } + //check the player state for events + BotAI_GetEntityState(bs->client, &state); + //copy the player state events to the entity state + state.event = bs->cur_ps.externalEvent; + state.eventParm = bs->cur_ps.externalEventParm; + // + BotCheckEvents(bs, &state); +} + +/* +================== +BotCheckAir +================== +*/ +void BotCheckAir(bot_state_t *bs) { + if (bs->inventory[INVENTORY_ENVIRONMENTSUIT] <= 0) { + if (trap_AAS_PointContents(bs->eye) & (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) { + return; + } + } + bs->lastair_time = FloatTime(); +} + +/* +================== +BotAlternateRoute +================== +*/ +bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal) { + int t; + + // if the bot has an alternative route goal + if (bs->altroutegoal.areanum) { + // + if (bs->reachedaltroutegoal_time) + return goal; + // travel time towards alternative route goal + t = trap_AAS_AreaTravelTimeToGoalArea(bs->areanum, bs->origin, bs->altroutegoal.areanum, bs->tfl); + if (t && t < 20) { + //BotAI_Print(PRT_MESSAGE, "reached alternate route goal\n"); + bs->reachedaltroutegoal_time = FloatTime(); + } + memcpy(goal, &bs->altroutegoal, sizeof(bot_goal_t)); + return &bs->altroutegoal; + } + return goal; +} + +/* +================== +BotGetAlternateRouteGoal +================== +*/ +int BotGetAlternateRouteGoal(bot_state_t *bs, int base) { + aas_altroutegoal_t *altroutegoals; + bot_goal_t *goal; + int numaltroutegoals, rnd; + + if (base == TEAM_RED) { + altroutegoals = red_altroutegoals; + numaltroutegoals = red_numaltroutegoals; + } + else { + altroutegoals = blue_altroutegoals; + numaltroutegoals = blue_numaltroutegoals; + } + if (!numaltroutegoals) + return qfalse; + rnd = (float) random() * numaltroutegoals; + if (rnd >= numaltroutegoals) + rnd = numaltroutegoals-1; + goal = &bs->altroutegoal; + goal->areanum = altroutegoals[rnd].areanum; + VectorCopy(altroutegoals[rnd].origin, goal->origin); + VectorSet(goal->mins, -8, -8, -8); + VectorSet(goal->maxs, 8, 8, 8); + goal->entitynum = 0; + goal->iteminfo = 0; + goal->number = 0; + goal->flags = 0; + // + bs->reachedaltroutegoal_time = 0; + return qtrue; +} + +/* +================== +BotSetupAlternateRouteGoals +================== +*/ +void BotSetupAlternativeRouteGoals(void) { + + if (altroutegoals_setup) + return; +#ifdef MISSIONPACK + if (gametype == GT_CTF) { + if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) + BotAI_Print(PRT_WARNING, "no alt routes without Neutral Flag\n"); + if (ctf_neutralflag.areanum) { + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + } + else if (gametype == GT_1FCTF) { + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_redflag.origin, ctf_redflag.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + ctf_neutralflag.origin, ctf_neutralflag.areanum, + ctf_blueflag.origin, ctf_blueflag.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + else if (gametype == GT_OBELISK) { + if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } + else if (gametype == GT_HARVESTER) { + // + red_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + redobelisk.origin, redobelisk.areanum, TFL_DEFAULT, + red_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + blue_numaltroutegoals = trap_AAS_AlternativeRouteGoals( + neutralobelisk.origin, neutralobelisk.areanum, + blueobelisk.origin, blueobelisk.areanum, TFL_DEFAULT, + blue_altroutegoals, MAX_ALTROUTEGOALS, + ALTROUTEGOAL_CLUSTERPORTALS| + ALTROUTEGOAL_VIEWPORTALS); + } +#endif + altroutegoals_setup = qtrue; +} + +/* +================== +BotDeathmatchAI +================== +*/ +void BotDeathmatchAI(bot_state_t *bs, float thinktime) { + char gender[144], name[144], buf[144]; + char userinfo[MAX_INFO_STRING]; + int i; + + //if the bot has just been setup + if (bs->setupcount > 0) { + bs->setupcount--; + if (bs->setupcount > 0) return; + //get the gender characteristic + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, sizeof(gender)); + //set the bot gender + trap_GetUserinfo(bs->client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, "sex", gender); + trap_SetUserinfo(bs->client, userinfo); + //set the team + if ( !bs->map_restart && g_gametype.integer != GT_TOURNAMENT ) { + Com_sprintf(buf, sizeof(buf), "team %s", bs->settings.team); + trap_EA_Command(bs->client, buf); + } + //set the chat gender + if (gender[0] == 'm') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else if (gender[0] == 'f') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); + //set the chat name + ClientName(bs->client, name, sizeof(name)); + trap_BotSetChatName(bs->cs, name, bs->client); + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; + // + bs->setupcount = 0; + // + BotSetupAlternativeRouteGoals(); + } + //no ideal view set + bs->flags &= ~BFL_IDEALVIEWSET; + // + if (!BotIntermission(bs)) { + //set the teleport time + BotSetTeleportTime(bs); + //update some inventory values + BotUpdateInventory(bs); + //check out the snapshot + BotCheckSnapshot(bs); + //check for air + BotCheckAir(bs); + } + //check the console messages + BotCheckConsoleMessages(bs); + //if not in the intermission and not in observer mode + if (!BotIntermission(bs) && !BotIsObserver(bs)) { + //do team AI + BotTeamAI(bs); + } + //if the bot has no ai node + if (!bs->ainode) { + AIEnter_Seek_LTG(bs, "BotDeathmatchAI: no ai node"); + } + //if the bot entered the game less than 8 seconds ago + if (!bs->entergamechat && bs->entergame_time > FloatTime() - 8) { + if (BotChat_EnterGame(bs)) { + bs->stand_time = FloatTime() + BotChatTime(bs); + AIEnter_Stand(bs, "BotDeathmatchAI: chat enter game"); + } + bs->entergamechat = qtrue; + } + //reset the node switches from the previous frame + BotResetNodeSwitches(); + //execute AI nodes + for (i = 0; i < MAX_NODESWITCHES; i++) { + if (bs->ainode(bs)) break; + } + //if the bot removed itself :) + if (!bs->inuse) return; + //if the bot executed too many AI nodes + if (i >= MAX_NODESWITCHES) { + trap_BotDumpGoalStack(bs->gs); + trap_BotDumpAvoidGoals(bs->gs); + BotDumpNodeSwitches(bs); + ClientName(bs->client, name, sizeof(name)); + BotAI_Print(PRT_ERROR, "%s at %1.1f switched more than %d AI nodes\n", name, FloatTime(), MAX_NODESWITCHES); + } + // + bs->lastframe_health = bs->inventory[INVENTORY_HEALTH]; + bs->lasthitcount = bs->cur_ps.persistant[PERS_HITS]; +} + +/* +================== +BotSetEntityNumForGoalWithModel +================== +*/ +void BotSetEntityNumForGoalWithModel(bot_goal_t *goal, int eType, char *modelname) { + gentity_t *ent; + int i, modelindex; + vec3_t dir; + + modelindex = G_ModelIndex( modelname ); + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( eType && ent->s.eType != eType) { + continue; + } + if (ent->s.modelindex != modelindex) { + continue; + } + VectorSubtract(goal->origin, ent->s.origin, dir); + if (VectorLengthSquared(dir) < Square(10)) { + goal->entitynum = i; + return; + } + } +} + +/* +================== +BotSetEntityNumForGoal +================== +*/ +void BotSetEntityNumForGoal(bot_goal_t *goal, char *classname) { + gentity_t *ent; + int i; + vec3_t dir; + + ent = &g_entities[0]; + for (i = 0; i < level.num_entities; i++, ent++) { + if ( !ent->inuse ) { + continue; + } + if ( !Q_stricmp(ent->classname, classname) ) { + continue; + } + VectorSubtract(goal->origin, ent->s.origin, dir); + if (VectorLengthSquared(dir) < Square(10)) { + goal->entitynum = i; + return; + } + } +} + +/* +================== +BotGoalForBSPEntity +================== +*/ +int BotGoalForBSPEntity( char *classname, bot_goal_t *goal ) { + char value[MAX_INFO_STRING]; + vec3_t origin, start, end; + int ent, numareas, areas[10]; + + memset(goal, 0, sizeof(bot_goal_t)); + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "classname", value, sizeof(value))) + continue; + if (!strcmp(value, classname)) { + if (!trap_AAS_VectorForBSPEpairKey(ent, "origin", origin)) + return qfalse; + VectorCopy(origin, goal->origin); + VectorCopy(origin, start); + start[2] -= 32; + VectorCopy(origin, end); + end[2] += 32; + numareas = trap_AAS_TraceAreas(start, end, areas, NULL, 10); + if (!numareas) + return qfalse; + goal->areanum = areas[0]; + return qtrue; + } + } + return qfalse; +} + +/* +================== +BotSetupDeathmatchAI +================== +*/ +void BotSetupDeathmatchAI(void) { + int ent, modelnum; + char model[128]; + + gametype = trap_Cvar_VariableIntegerValue("g_gametype"); + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + + trap_Cvar_Register(&bot_rocketjump, "bot_rocketjump", "1", 0); + + //PKMOD - Ergodic 12/02/01 - debug: setting bot_dragon to 1 (inactive) +// Com_Printf("BotSetupDeathmatchAI - setting bot_dragon to 1\n" ); + +//PKMOD - Ergodic 12/02/01 - set default to "1" so that bots will use grapple +// trap_Cvar_Register(&bot_grapple, "bot_grapple", "1", 0); +//PKMOD - Ergodic 02/11/02 - change the cvar flags +//PKMOD - Ergodic 02/11/02 - move bot_grapple to g_bot.c:G_InitBots +// trap_Cvar_Register(&bot_grapple, "bot_grapple", "1", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH); + trap_Cvar_Register(&bot_grapple, "bot_grapple", "0", 0); + trap_Cvar_Register(&bot_fastchat, "bot_fastchat", "0", 0); + trap_Cvar_Register(&bot_nochat, "bot_nochat", "0", 0); + trap_Cvar_Register(&bot_testrchat, "bot_testrchat", "0", 0); + trap_Cvar_Register(&bot_challenge, "bot_challenge", "0", 0); + trap_Cvar_Register(&bot_predictobstacles, "bot_predictobstacles", "1", 0); + trap_Cvar_Register(&g_spSkill, "g_spSkill", "2", 0); + + //PKMOD - Ergodic 12/02/01 - set default to "1" so that bots will use grapple + trap_Cvar_Register(&g_spSkill, "bot_dragon", "1", CVAR_SERVERINFO | CVAR_USERINFO | CVAR_LATCH); + + // + if (gametype == GT_CTF) { + if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (trap_BotGetLevelItemGoal(-1, "Neutral Flag", &ctf_neutralflag) < 0) + BotAI_Print(PRT_WARNING, "One Flag CTF without Neutral Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Red Flag", &ctf_redflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Red Flag\n"); + if (trap_BotGetLevelItemGoal(-1, "Blue Flag", &ctf_blueflag) < 0) + BotAI_Print(PRT_WARNING, "CTF without Blue Flag\n"); + } + else if (gametype == GT_OBELISK) { + if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) + BotAI_Print(PRT_WARNING, "Obelisk without red obelisk\n"); + BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); + if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) + BotAI_Print(PRT_WARNING, "Obelisk without blue obelisk\n"); + BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); + } + else if (gametype == GT_HARVESTER) { + if (trap_BotGetLevelItemGoal(-1, "Red Obelisk", &redobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without red obelisk\n"); + BotSetEntityNumForGoal(&redobelisk, "team_redobelisk"); + if (trap_BotGetLevelItemGoal(-1, "Blue Obelisk", &blueobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without blue obelisk\n"); + BotSetEntityNumForGoal(&blueobelisk, "team_blueobelisk"); + if (trap_BotGetLevelItemGoal(-1, "Neutral Obelisk", &neutralobelisk) < 0) + BotAI_Print(PRT_WARNING, "Harvester without neutral obelisk\n"); + BotSetEntityNumForGoal(&neutralobelisk, "team_neutralobelisk"); + } +#endif + + max_bspmodelindex = 0; + for (ent = trap_AAS_NextBSPEntity(0); ent; ent = trap_AAS_NextBSPEntity(ent)) { + if (!trap_AAS_ValueForBSPEpairKey(ent, "model", model, sizeof(model))) continue; + if (model[0] == '*') { + modelnum = atoi(model+1); + if (modelnum > max_bspmodelindex) + max_bspmodelindex = modelnum; + } + } + //initialize the waypoint heap + BotInitWaypoints(); +} + +/* +================== +BotShutdownDeathmatchAI +================== +*/ +void BotShutdownDeathmatchAI(void) { + altroutegoals_setup = qfalse; +} + diff --git a/quake3/source/code/game/ai_dmq3.h b/quake3/source/code/game/ai_dmq3.h new file mode 100644 index 0000000..0860c18 --- /dev/null +++ b/quake3/source/code/game/ai_dmq3.h @@ -0,0 +1,197 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_dmq3.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +//setup the deathmatch AI +void BotSetupDeathmatchAI(void); +//shutdown the deathmatch AI +void BotShutdownDeathmatchAI(void); +//let the bot live within it's deathmatch AI net +void BotDeathmatchAI(bot_state_t *bs, float thinktime); +//free waypoints +void BotFreeWaypoints(bot_waypoint_t *wp); + +//PKMOD - Ergodic 04/15/01 - bots will select beans at low health times +void BotEatBeans(bot_state_t *bs); + +//choose a weapon +void BotChooseWeapon(bot_state_t *bs); +//setup movement stuff +void BotSetupForMovement(bot_state_t *bs); +//update the inventory +void BotUpdateInventory(bot_state_t *bs); +//update the inventory during battle +void BotUpdateBattleInventory(bot_state_t *bs, int enemy); +//use holdable items during battle +void BotBattleUseItems(bot_state_t *bs); +//return true if the bot is dead +qboolean BotIsDead(bot_state_t *bs); +//returns true if the bot is in observer mode +qboolean BotIsObserver(bot_state_t *bs); +//returns true if the bot is in the intermission +qboolean BotIntermission(bot_state_t *bs); +//returns true if the bot is in lava or slime +qboolean BotInLavaOrSlime(bot_state_t *bs); +//returns true if the entity is dead +qboolean EntityIsDead(aas_entityinfo_t *entinfo); +//returns true if the entity is invisible +qboolean EntityIsInvisible(aas_entityinfo_t *entinfo); +//returns true if the entity is shooting +qboolean EntityIsShooting(aas_entityinfo_t *entinfo); +#ifdef MISSIONPACK +//returns true if this entity has the kamikaze +qboolean EntityHasKamikaze(aas_entityinfo_t *entinfo); +#endif +// set a user info key/value pair +void BotSetUserInfo(bot_state_t *bs, char *key, char *value); +// set the team status (offense, defense etc.) +void BotSetTeamStatus(bot_state_t *bs); + +//returns the name of the client +char *ClientName(int client, char *name, int size); + +//returns an simplyfied client name +char *EasyClientName(int client, char *name, int size); + +//returns the skin used by the client +char *ClientSkin(int client, char *skin, int size); +// returns the appropriate synonym context for the current game type and situation +int BotSynonymContext(bot_state_t *bs); +// set last ordered task +int BotSetLastOrderedTask(bot_state_t *bs); +// selection of goals for teamplay +void BotTeamGoals(bot_state_t *bs, int retreat); +//returns the aggression of the bot in the range [0, 100] +float BotAggression(bot_state_t *bs); +//returns how bad the bot feels +float BotFeelingBad(bot_state_t *bs); +//returns true if the bot wants to retreat +int BotWantsToRetreat(bot_state_t *bs); +//returns true if the bot wants to chase +int BotWantsToChase(bot_state_t *bs); +//returns true if the bot wants to help +int BotWantsToHelp(bot_state_t *bs); +//returns true if the bot can and wants to rocketjump +int BotCanAndWantsToRocketJump(bot_state_t *bs); +// returns true if the bot has a persistant powerup and a weapon +int BotHasPersistantPowerupAndWeapon(bot_state_t *bs); +//returns true if the bot wants to and goes camping +int BotWantsToCamp(bot_state_t *bs); +//the bot will perform attack movements +bot_moveresult_t BotAttackMove(bot_state_t *bs, int tfl); +//returns true if the bot and the entity are in the same team +int BotSameTeam(bot_state_t *bs, int entnum); +//returns true if teamplay is on +int TeamPlayIsOn(void); +// returns the client number of the team mate flag carrier (-1 if none) +int BotTeamFlagCarrier(bot_state_t *bs); +//returns visible team mate flag carrier if available +int BotTeamFlagCarrierVisible(bot_state_t *bs); +//returns visible enemy flag carrier if available +int BotEnemyFlagCarrierVisible(bot_state_t *bs); +//get the number of visible teammates and enemies +void BotVisibleTeamMatesAndEnemies(bot_state_t *bs, int *teammates, int *enemies, float range); +//returns true if within the field of vision for the given angles +qboolean InFieldOfVision(vec3_t viewangles, float fov, vec3_t angles); +//returns true and sets the .enemy field when an enemy is found +int BotFindEnemy(bot_state_t *bs, int curenemy); +//returns a roam goal +void BotRoamGoal(bot_state_t *bs, vec3_t goal); +//returns entity visibility in the range [0, 1] +float BotEntityVisible(int viewer, vec3_t eye, vec3_t viewangles, float fov, int ent); +//the bot will aim at the current enemy +void BotAimAtEnemy(bot_state_t *bs); +//check if the bot should attack +void BotCheckAttack(bot_state_t *bs); +//AI when the bot is blocked +void BotAIBlocked(bot_state_t *bs, bot_moveresult_t *moveresult, int activate); +//AI to predict obstacles +int BotAIPredictObstacles(bot_state_t *bs, bot_goal_t *goal); +//enable or disable the areas the blocking entity is in +void BotEnableActivateGoalAreas(bot_activategoal_t *activategoal, int enable); +//pop an activate goal from the stack +int BotPopFromActivateGoalStack(bot_state_t *bs); +//clear the activate goal stack +void BotClearActivateGoalStack(bot_state_t *bs); +//returns the team the bot is in +int BotTeam(bot_state_t *bs); +//retuns the opposite team of the bot +int BotOppositeTeam(bot_state_t *bs); +//returns the flag the bot is carrying (CTFFLAG_?) +int BotCTFCarryingFlag(bot_state_t *bs); +//remember the last ordered task +void BotRememberLastOrderedTask(bot_state_t *bs); +//set ctf goals (defend base, get enemy flag) during seek +void BotCTFSeekGoals(bot_state_t *bs); +//set ctf goals (defend base, get enemy flag) during retreat +void BotCTFRetreatGoals(bot_state_t *bs); +// +#ifdef MISSIONPACK +int Bot1FCTFCarryingFlag(bot_state_t *bs); +int BotHarvesterCarryingCubes(bot_state_t *bs); +void Bot1FCTFSeekGoals(bot_state_t *bs); +void Bot1FCTFRetreatGoals(bot_state_t *bs); +void BotObeliskSeekGoals(bot_state_t *bs); +void BotObeliskRetreatGoals(bot_state_t *bs); +void BotGoHarvest(bot_state_t *bs); +void BotHarvesterSeekGoals(bot_state_t *bs); +void BotHarvesterRetreatGoals(bot_state_t *bs); +int BotTeamCubeCarrierVisible(bot_state_t *bs); +int BotEnemyCubeCarrierVisible(bot_state_t *bs); +#endif +//get a random alternate route goal towards the given base +int BotGetAlternateRouteGoal(bot_state_t *bs, int base); +//returns either the alternate route goal or the given goal +bot_goal_t *BotAlternateRoute(bot_state_t *bs, bot_goal_t *goal); +//create a new waypoint +bot_waypoint_t *BotCreateWayPoint(char *name, vec3_t origin, int areanum); +//find a waypoint with the given name +bot_waypoint_t *BotFindWayPoint(bot_waypoint_t *waypoints, char *name); +//strstr but case insensitive +char *stristr(char *str, char *charset); +//returns the number of the client with the given name +int ClientFromName(char *name); +int ClientOnSameTeamFromName(bot_state_t *bs, char *name); +// +int BotPointAreaNum(vec3_t origin); +// +void BotMapScripts(bot_state_t *bs); + +//ctf flags +#define CTF_FLAG_NONE 0 +#define CTF_FLAG_RED 1 +#define CTF_FLAG_BLUE 2 +//CTF skins +#define CTF_SKIN_REDTEAM "red" +#define CTF_SKIN_BLUETEAM "blue" + +extern int gametype; //game type +extern int maxclients; //maximum number of clients + +extern vmCvar_t bot_grapple; +extern vmCvar_t bot_rocketjump; +extern vmCvar_t bot_fastchat; +extern vmCvar_t bot_nochat; +extern vmCvar_t bot_testrchat; +extern vmCvar_t bot_challenge; + +//PKMOD - Ergodic 02/11/02 - add dragon cvar +extern vmCvar_t bot_dragon; + + +extern bot_goal_t ctf_redflag; +extern bot_goal_t ctf_blueflag; +#ifdef MISSIONPACK +extern bot_goal_t ctf_neutralflag; +extern bot_goal_t redobelisk; +extern bot_goal_t blueobelisk; +extern bot_goal_t neutralobelisk; +#endif diff --git a/quake3/source/code/game/ai_main.c b/quake3/source/code/game/ai_main.c new file mode 100644 index 0000000..3579e95 --- /dev/null +++ b/quake3/source/code/game/ai_main.c @@ -0,0 +1,1703 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_main.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_main.c $ + * + *****************************************************************************/ + + +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" //bot lib interface +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_vcmd.h" + +// +#include "chars.h" +#include "inv.h" +#include "syn.h" + +#define MAX_PATH 144 + + +//bot states +bot_state_t *botstates[MAX_CLIENTS]; +//number of bots +int numbots; +//floating point time +float floattime; +//time to do a regular update +float regularupdate_time; +// +int bot_interbreed; +int bot_interbreedmatchcount; +// +vmCvar_t bot_thinktime; +vmCvar_t bot_memorydump; +vmCvar_t bot_saveroutingcache; +vmCvar_t bot_pause; +vmCvar_t bot_report; +vmCvar_t bot_testsolid; +vmCvar_t bot_testclusters; +vmCvar_t bot_developer; +vmCvar_t bot_interbreedchar; +vmCvar_t bot_interbreedbots; +vmCvar_t bot_interbreedcycle; +vmCvar_t bot_interbreedwrite; + + +void ExitLevel( void ); + + +/* +================== +BotAI_Print +================== +*/ +void QDECL BotAI_Print(int type, char *fmt, ...) { + char str[2048]; + va_list ap; + + va_start(ap, fmt); + vsprintf(str, fmt, ap); + va_end(ap); + + switch(type) { + case PRT_MESSAGE: { + G_Printf("%s", str); + break; + } + case PRT_WARNING: { + G_Printf( S_COLOR_YELLOW "Warning: %s", str ); + break; + } + case PRT_ERROR: { + G_Printf( S_COLOR_RED "Error: %s", str ); + break; + } + case PRT_FATAL: { + G_Printf( S_COLOR_RED "Fatal: %s", str ); + break; + } + case PRT_EXIT: { + G_Error( S_COLOR_RED "Exit: %s", str ); + break; + } + default: { + G_Printf( "unknown print type\n" ); + break; + } + } +} + + +/* +================== +BotAI_Trace +================== +*/ +void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) { + trace_t trace; + + trap_Trace(&trace, start, mins, maxs, end, passent, contentmask); + //copy the trace information + bsptrace->allsolid = trace.allsolid; + bsptrace->startsolid = trace.startsolid; + bsptrace->fraction = trace.fraction; + VectorCopy(trace.endpos, bsptrace->endpos); + bsptrace->plane.dist = trace.plane.dist; + VectorCopy(trace.plane.normal, bsptrace->plane.normal); + bsptrace->plane.signbits = trace.plane.signbits; + bsptrace->plane.type = trace.plane.type; + bsptrace->surface.value = trace.surfaceFlags; + bsptrace->ent = trace.entityNum; + bsptrace->exp_dist = 0; + bsptrace->sidenum = 0; + bsptrace->contents = 0; +} + +/* +================== +BotAI_GetClientState +================== +*/ +int BotAI_GetClientState( int clientNum, playerState_t *state ) { + gentity_t *ent; + + ent = &g_entities[clientNum]; + if ( !ent->inuse ) { + return qfalse; + } + if ( !ent->client ) { + return qfalse; + } + + memcpy( state, &ent->client->ps, sizeof(playerState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetEntityState +================== +*/ +int BotAI_GetEntityState( int entityNum, entityState_t *state ) { + gentity_t *ent; + + ent = &g_entities[entityNum]; + memset( state, 0, sizeof(entityState_t) ); + if (!ent->inuse) return qfalse; + if (!ent->r.linked) return qfalse; + if (ent->r.svFlags & SVF_NOCLIENT) return qfalse; + memcpy( state, &ent->s, sizeof(entityState_t) ); + return qtrue; +} + +/* +================== +BotAI_GetSnapshotEntity +================== +*/ +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) { + int entNum; + + entNum = trap_BotGetSnapshotEntity( clientNum, sequence ); + if ( entNum == -1 ) { + memset(state, 0, sizeof(entityState_t)); + return -1; + } + + BotAI_GetEntityState( entNum, state ); + + return sequence + 1; +} + +/* +================== +BotAI_BotInitialChat +================== +*/ +void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) { + int i, mcontext; + va_list ap; + char *p; + char *vars[MAX_MATCHVARIABLES]; + + memset(vars, 0, sizeof(vars)); + va_start(ap, type); + p = va_arg(ap, char *); + for (i = 0; i < MAX_MATCHVARIABLES; i++) { + if( !p ) { + break; + } + vars[i] = p; + p = va_arg(ap, char *); + } + va_end(ap); + + mcontext = BotSynonymContext(bs); + + trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] ); +} + + +/* +================== +BotTestAAS +================== +*/ +void BotTestAAS(vec3_t origin) { + int areanum; + aas_areainfo_t info; + + trap_Cvar_Update(&bot_testsolid); + trap_Cvar_Update(&bot_testclusters); + if (bot_testsolid.integer) { + if (!trap_AAS_Initialized()) return; + areanum = BotPointAreaNum(origin); + if (areanum) BotAI_Print(PRT_MESSAGE, "\remtpy area"); + else BotAI_Print(PRT_MESSAGE, "\r^1SOLID area"); + } + else if (bot_testclusters.integer) { + if (!trap_AAS_Initialized()) return; + areanum = BotPointAreaNum(origin); + if (!areanum) + BotAI_Print(PRT_MESSAGE, "\r^1Solid! "); + else { + trap_AAS_AreaInfo(areanum, &info); + BotAI_Print(PRT_MESSAGE, "\rarea %d, cluster %d ", areanum, info.cluster); + } + } +} + +/* +================== +BotReportStatus +================== +*/ +void BotReportStatus(bot_state_t *bs) { + char goalname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char *leader, flagstatus[32]; + // + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; + else leader = " "; + + strcpy(flagstatus, " "); + if (gametype == GT_CTF) { + if (BotCTFCarryingFlag(bs)) { + if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); + else strcpy(flagstatus, S_COLOR_BLUE"F "); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) { + if (BotTeam(bs) == TEAM_RED) strcpy(flagstatus, S_COLOR_RED"F "); + else strcpy(flagstatus, S_COLOR_BLUE"F "); + } + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) { + if (BotTeam(bs) == TEAM_RED) Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_RED"%2d", bs->inventory[INVENTORY_REDCUBE]); + else Com_sprintf(flagstatus, sizeof(flagstatus), S_COLOR_BLUE"%2d", bs->inventory[INVENTORY_BLUECUBE]); + } + } +#endif + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: helping %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: accompanying %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: defending %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: getting item %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: killing %s\n", netname, leader, flagstatus, goalname); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: camping\n", netname, leader, flagstatus); + break; + } + case LTG_PATROL: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: patrolling\n", netname, leader, flagstatus); + break; + } + case LTG_GETFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: capturing flag\n", netname, leader, flagstatus); + break; + } + case LTG_RUSHBASE: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: rushing base\n", netname, leader, flagstatus); + break; + } + case LTG_RETURNFLAG: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: returning flag\n", netname, leader, flagstatus); + break; + } + case LTG_ATTACKENEMYBASE: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: attacking the enemy base\n", netname, leader, flagstatus); + break; + } + case LTG_HARVEST: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: harvesting\n", netname, leader, flagstatus); + break; + } + default: + { + BotAI_Print(PRT_MESSAGE, "%-20s%s%s: roaming\n", netname, leader, flagstatus); + break; + } + } +} + +/* +================== +BotTeamplayReport +================== +*/ +void BotTeamplayReport(void) { + int i; + char buf[MAX_INFO_STRING]; + + BotAI_Print(PRT_MESSAGE, S_COLOR_RED"RED\n"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_RED) { + BotReportStatus(botstates[i]); + } + } + BotAI_Print(PRT_MESSAGE, S_COLOR_BLUE"BLUE\n"); + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_BLUE) { + BotReportStatus(botstates[i]); + } + } +} + +/* +================== +BotSetInfoConfigString +================== +*/ +void BotSetInfoConfigString(bot_state_t *bs) { + char goalname[MAX_MESSAGE_SIZE]; + char netname[MAX_MESSAGE_SIZE]; + char action[MAX_MESSAGE_SIZE]; + char *leader, carrying[32], *cs; + bot_goal_t goal; + // + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) == 0) leader = "L"; + else leader = " "; + + strcpy(carrying, " "); + if (gametype == GT_CTF) { + if (BotCTFCarryingFlag(bs)) { + strcpy(carrying, "F "); + } + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (Bot1FCTFCarryingFlag(bs)) { + strcpy(carrying, "F "); + } + } + else if (gametype == GT_HARVESTER) { + if (BotHarvesterCarryingCubes(bs)) { + if (BotTeam(bs) == TEAM_RED) Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_REDCUBE]); + else Com_sprintf(carrying, sizeof(carrying), "%2d", bs->inventory[INVENTORY_BLUECUBE]); + } + } +#endif + + switch(bs->ltgtype) { + case LTG_TEAMHELP: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "helping %s", goalname); + break; + } + case LTG_TEAMACCOMPANY: + { + EasyClientName(bs->teammate, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "accompanying %s", goalname); + break; + } + case LTG_DEFENDKEYAREA: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "defending %s", goalname); + break; + } + case LTG_GETITEM: + { + trap_BotGoalName(bs->teamgoal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "getting item %s", goalname); + break; + } + case LTG_KILL: + { + ClientName(bs->teamgoal.entitynum, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "killing %s", goalname); + break; + } + case LTG_CAMP: + case LTG_CAMPORDER: + { + Com_sprintf(action, sizeof(action), "camping"); + break; + } + case LTG_PATROL: + { + Com_sprintf(action, sizeof(action), "patrolling"); + break; + } + case LTG_GETFLAG: + { + Com_sprintf(action, sizeof(action), "capturing flag"); + break; + } + case LTG_RUSHBASE: + { + Com_sprintf(action, sizeof(action), "rushing base"); + break; + } + case LTG_RETURNFLAG: + { + Com_sprintf(action, sizeof(action), "returning flag"); + break; + } + case LTG_ATTACKENEMYBASE: + { + Com_sprintf(action, sizeof(action), "attacking the enemy base"); + break; + } + case LTG_HARVEST: + { + Com_sprintf(action, sizeof(action), "harvesting"); + break; + } + default: + { + trap_BotGetTopGoal(bs->gs, &goal); + trap_BotGoalName(goal.number, goalname, sizeof(goalname)); + Com_sprintf(action, sizeof(action), "roaming %s", goalname); + break; + } + } + cs = va("l\\%s\\c\\%s\\a\\%s", + leader, + carrying, + action); + trap_SetConfigstring (CS_BOTINFO + bs->client, cs); +} + +/* +============== +BotUpdateInfoConfigStrings +============== +*/ +void BotUpdateInfoConfigStrings(void) { + int i; + char buf[MAX_INFO_STRING]; + + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + // + if ( !botstates[i] || !botstates[i]->inuse ) + continue; + // + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) + continue; + BotSetInfoConfigString(botstates[i]); + } +} + +/* +============== +BotInterbreedBots +============== +*/ +void BotInterbreedBots(void) { + float ranks[MAX_CLIENTS]; + int parent1, parent2, child; + int i; + + // get rankings for all the bots + for (i = 0; i < MAX_CLIENTS; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + ranks[i] = -1; + } + } + + if (trap_GeneticParentsAndChildSelection(MAX_CLIENTS, ranks, &parent1, &parent2, &child)) { + trap_BotInterbreedGoalFuzzyLogic(botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs); + trap_BotMutateGoalFuzzyLogic(botstates[child]->gs, 1); + } + // reset the kills and deaths + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + botstates[i]->num_kills = 0; + botstates[i]->num_deaths = 0; + } + } +} + +/* +============== +BotWriteInterbreeded +============== +*/ +void BotWriteInterbreeded(char *filename) { + float rank, bestrank; + int i, bestbot; + + bestrank = 0; + bestbot = -1; + // get the best bot + for (i = 0; i < MAX_CLIENTS; i++) { + if ( botstates[i] && botstates[i]->inuse ) { + rank = botstates[i]->num_kills * 2 - botstates[i]->num_deaths; + } + else { + rank = -1; + } + if (rank > bestrank) { + bestrank = rank; + bestbot = i; + } + } + if (bestbot >= 0) { + //write out the new goal fuzzy logic + trap_BotSaveGoalFuzzyLogic(botstates[bestbot]->gs, filename); + } +} + +/* +============== +BotInterbreedEndMatch + +add link back into ExitLevel? +============== +*/ +void BotInterbreedEndMatch(void) { + + if (!bot_interbreed) return; + bot_interbreedmatchcount++; + if (bot_interbreedmatchcount >= bot_interbreedcycle.integer) { + bot_interbreedmatchcount = 0; + // + trap_Cvar_Update(&bot_interbreedwrite); + if (strlen(bot_interbreedwrite.string)) { + BotWriteInterbreeded(bot_interbreedwrite.string); + trap_Cvar_Set("bot_interbreedwrite", ""); + } + BotInterbreedBots(); + } +} + +/* +============== +BotInterbreeding +============== +*/ +void BotInterbreeding(void) { + int i; + + trap_Cvar_Update(&bot_interbreedchar); + if (!strlen(bot_interbreedchar.string)) return; + //make sure we are in tournament mode + if (gametype != GT_TOURNAMENT) { + trap_Cvar_Set("g_gametype", va("%d", GT_TOURNAMENT)); + ExitLevel(); + return; + } + //shutdown all the bots + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotAIShutdownClient(botstates[i]->client, qfalse); + } + } + //make sure all item weight configs are reloaded and Not shared + trap_BotLibVarSet("bot_reloadcharacters", "1"); + //add a number of bots using the desired bot character + for (i = 0; i < bot_interbreedbots.integer; i++) { + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s 4 free %i %s%d\n", + bot_interbreedchar.string, i * 50, bot_interbreedchar.string, i) ); + } + // + trap_Cvar_Set("bot_interbreedchar", ""); + bot_interbreed = qtrue; +} + +/* +============== +BotEntityInfo +============== +*/ +void BotEntityInfo(int entnum, aas_entityinfo_t *info) { + trap_AAS_EntityInfo(entnum, info); +} + +/* +============== +NumBots +============== +*/ +int NumBots(void) { + return numbots; +} + +/* +============== +BotTeamLeader +============== +*/ +int BotTeamLeader(bot_state_t *bs) { + int leader; + + leader = ClientFromName(bs->teamleader); + if (leader < 0) return qfalse; + if (!botstates[leader] || !botstates[leader]->inuse) return qfalse; + return qtrue; +} + +/* +============== +AngleDifference +============== +*/ +float AngleDifference(float ang1, float ang2) { + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) { + if (diff > 180.0) diff -= 360.0; + } + else { + if (diff < -180.0) diff += 360.0; + } + return diff; +} + +/* +============== +BotChangeViewAngle +============== +*/ +float BotChangeViewAngle(float angle, float ideal_angle, float speed) { + float move; + + angle = AngleMod(angle); + ideal_angle = AngleMod(ideal_angle); + if (angle == ideal_angle) return angle; + move = ideal_angle - angle; + if (ideal_angle > angle) { + if (move > 180.0) move -= 360.0; + } + else { + if (move < -180.0) move += 360.0; + } + if (move > 0) { + if (move > speed) move = speed; + } + else { + if (move < -speed) move = -speed; + } + return AngleMod(angle + move); +} + +/* +============== +BotChangeViewAngles +============== +*/ +void BotChangeViewAngles(bot_state_t *bs, float thinktime) { + float diff, factor, maxchange, anglespeed, disired_speed; + int i; + + if (bs->ideal_viewangles[PITCH] > 180) bs->ideal_viewangles[PITCH] -= 360; + // + if (bs->enemy >= 0) { + factor = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01f, 1); + maxchange = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800); + } + else { + factor = 0.05f; + maxchange = 360; + } + if (maxchange < 240) maxchange = 240; + maxchange *= thinktime; + for (i = 0; i < 2; i++) { + // + if (bot_challenge.integer) { + //smooth slowdown view model + diff = abs(AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i])); + anglespeed = diff * factor; + if (anglespeed > maxchange) anglespeed = maxchange; + bs->viewangles[i] = BotChangeViewAngle(bs->viewangles[i], + bs->ideal_viewangles[i], anglespeed); + } + else { + //over reaction view model + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + bs->ideal_viewangles[i] = AngleMod(bs->ideal_viewangles[i]); + diff = AngleDifference(bs->viewangles[i], bs->ideal_viewangles[i]); + disired_speed = diff * factor; + bs->viewanglespeed[i] += (bs->viewanglespeed[i] - disired_speed); + if (bs->viewanglespeed[i] > 180) bs->viewanglespeed[i] = maxchange; + if (bs->viewanglespeed[i] < -180) bs->viewanglespeed[i] = -maxchange; + anglespeed = bs->viewanglespeed[i]; + if (anglespeed > maxchange) anglespeed = maxchange; + if (anglespeed < -maxchange) anglespeed = -maxchange; + bs->viewangles[i] += anglespeed; + bs->viewangles[i] = AngleMod(bs->viewangles[i]); + //demping + bs->viewanglespeed[i] *= 0.45 * (1 - factor); + } + //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);` + //bs->viewangles[i] = bs->ideal_viewangles[i]; + } + //bs->viewangles[PITCH] = 0; + if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; + //elementary action: view + trap_EA_View(bs->client, bs->viewangles); +} + +/* +============== +BotInputToUserCommand +============== +*/ +void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time) { + vec3_t angles, forward, right; + short temp; + int j; + + //clear the whole structure + memset(ucmd, 0, sizeof(usercmd_t)); + // + //Com_Printf("dir = %f %f %f speed = %f\n", bi->dir[0], bi->dir[1], bi->dir[2], bi->speed); + //the duration for the user command in milli seconds + ucmd->serverTime = time; + // + if (bi->actionflags & ACTION_DELAYEDJUMP) { + bi->actionflags |= ACTION_JUMP; + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } + //set the buttons + if (bi->actionflags & ACTION_RESPAWN) ucmd->buttons = BUTTON_ATTACK; + if (bi->actionflags & ACTION_ATTACK) ucmd->buttons |= BUTTON_ATTACK; + if (bi->actionflags & ACTION_TALK) ucmd->buttons |= BUTTON_TALK; + if (bi->actionflags & ACTION_GESTURE) ucmd->buttons |= BUTTON_GESTURE; + if (bi->actionflags & ACTION_USE) ucmd->buttons |= BUTTON_USE_HOLDABLE; + if (bi->actionflags & ACTION_WALK) ucmd->buttons |= BUTTON_WALKING; + if (bi->actionflags & ACTION_AFFIRMATIVE) ucmd->buttons |= BUTTON_AFFIRMATIVE; + if (bi->actionflags & ACTION_NEGATIVE) ucmd->buttons |= BUTTON_NEGATIVE; + if (bi->actionflags & ACTION_GETFLAG) ucmd->buttons |= BUTTON_GETFLAG; + if (bi->actionflags & ACTION_GUARDBASE) ucmd->buttons |= BUTTON_GUARDBASE; + if (bi->actionflags & ACTION_PATROL) ucmd->buttons |= BUTTON_PATROL; + if (bi->actionflags & ACTION_FOLLOWME) ucmd->buttons |= BUTTON_FOLLOWME; + // + ucmd->weapon = bi->weapon; + //set the view angles + //NOTE: the ucmd->angles are the angles WITHOUT the delta angles + ucmd->angles[PITCH] = ANGLE2SHORT(bi->viewangles[PITCH]); + ucmd->angles[YAW] = ANGLE2SHORT(bi->viewangles[YAW]); + ucmd->angles[ROLL] = ANGLE2SHORT(bi->viewangles[ROLL]); + //subtract the delta angles + for (j = 0; j < 3; j++) { + temp = ucmd->angles[j] - delta_angles[j]; + /*NOTE: disabled because temp should be mod first + if ( j == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) temp = 16000; + else if ( temp < -16000 ) temp = -16000; + } + */ + ucmd->angles[j] = temp; + } + //NOTE: movement is relative to the REAL view angles + //get the horizontal forward and right vector + //get the pitch in the range [-180, 180] + if (bi->dir[2]) angles[PITCH] = bi->viewangles[PITCH]; + else angles[PITCH] = 0; + angles[YAW] = bi->viewangles[YAW]; + angles[ROLL] = 0; + AngleVectors(angles, forward, right, NULL); + //bot input speed is in the range [0, 400] + bi->speed = bi->speed * 127 / 400; + //set the view independent movement + ucmd->forwardmove = DotProduct(forward, bi->dir) * bi->speed; + ucmd->rightmove = DotProduct(right, bi->dir) * bi->speed; + ucmd->upmove = abs(forward[2]) * bi->dir[2] * bi->speed; + //normal keyboard movement + if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove += 127; + if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove -= 127; + if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove -= 127; + if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove += 127; + //jump/moveup + if (bi->actionflags & ACTION_JUMP) ucmd->upmove += 127; + //crouch/movedown + if (bi->actionflags & ACTION_CROUCH) ucmd->upmove -= 127; + // + //Com_Printf("forward = %d right = %d up = %d\n", ucmd.forwardmove, ucmd.rightmove, ucmd.upmove); + //Com_Printf("ucmd->serverTime = %d\n", ucmd->serverTime); +} + +/* +============== +BotUpdateInput +============== +*/ +void BotUpdateInput(bot_state_t *bs, int time, int elapsed_time) { + bot_input_t bi; + int j; + + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //change the bot view angles + BotChangeViewAngles(bs, (float) elapsed_time / 1000); + //retrieve the bot input + trap_EA_GetInput(bs->client, (float) time / 1000, &bi); + //respawn hack + if (bi.actionflags & ACTION_RESPAWN) { + if (bs->lastucmd.buttons & BUTTON_ATTACK) bi.actionflags &= ~(ACTION_RESPAWN|ACTION_ATTACK); + } + //convert the bot input to a usercmd + BotInputToUserCommand(&bi, &bs->lastucmd, bs->cur_ps.delta_angles, time); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } +} + +/* +============== +BotAIRegularUpdate +============== +*/ +void BotAIRegularUpdate(void) { + if (regularupdate_time < FloatTime()) { + trap_BotUpdateEntityItems(); + regularupdate_time = FloatTime() + 0.3; + } +} + +/* +============== +RemoveColorEscapeSequences +============== +*/ +void RemoveColorEscapeSequences( char *text ) { + int i, l; + + l = 0; + for ( i = 0; text[i]; i++ ) { + if (Q_IsColorString(&text[i])) { + i++; + continue; + } + if (text[i] > 0x7E) + continue; + text[l++] = text[i]; + } + text[l] = '\0'; +} + +/* +============== +BotAI +============== +*/ +int BotAI(int client, float thinktime) { + bot_state_t *bs; + char buf[1024], *args; + int j; + + trap_EA_ResetInput(client); + // + bs = botstates[client]; + if (!bs || !bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAI: client %d is not setup\n", client); + return qfalse; + } + + //retrieve the current client state + BotAI_GetClientState( client, &bs->cur_ps ); + + //retrieve any waiting server commands + while( trap_BotGetServerCommand(client, buf, sizeof(buf)) ) { + //have buf point to the command and args to the command arguments + args = strchr( buf, ' '); + if (!args) continue; + *args++ = '\0'; + + //remove color espace sequences from the arguments + RemoveColorEscapeSequences( args ); + + if (!Q_stricmp(buf, "cp ")) + { /*CenterPrintf*/ } + else if (!Q_stricmp(buf, "cs")) + { /*ConfigStringModified*/ } + else if (!Q_stricmp(buf, "print")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_NORMAL, args); + } + else if (!Q_stricmp(buf, "chat")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); + } + else if (!Q_stricmp(buf, "tchat")) { + //remove first and last quote from the chat message + memmove(args, args+1, strlen(args)); + args[strlen(args)-1] = '\0'; + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, args); + } +#ifdef MISSIONPACK + else if (!Q_stricmp(buf, "vchat")) { + BotVoiceChatCommand(bs, SAY_ALL, args); + } + else if (!Q_stricmp(buf, "vtchat")) { + BotVoiceChatCommand(bs, SAY_TEAM, args); + } + else if (!Q_stricmp(buf, "vtell")) { + BotVoiceChatCommand(bs, SAY_TELL, args); + } +#endif + else if (!Q_stricmp(buf, "scores")) + { /*FIXME: parse scores?*/ } + else if (!Q_stricmp(buf, "clientLevelShot")) + { /*ignore*/ } + } + //add the delta angles to the bot's current view angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] + SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //increase the local time of the bot + bs->ltime += thinktime; + // + bs->thinktime = thinktime; + //origin of the bot + VectorCopy(bs->cur_ps.origin, bs->origin); + //eye coordinates of the bot + VectorCopy(bs->cur_ps.origin, bs->eye); + bs->eye[2] += bs->cur_ps.viewheight; + //get the area the bot is in + bs->areanum = BotPointAreaNum(bs->origin); + //the real AI + BotDeathmatchAI(bs, thinktime); + //set the weapon selection every AI frame + trap_EA_SelectWeapon(bs->client, bs->weaponnum); + //subtract the delta angles + for (j = 0; j < 3; j++) { + bs->viewangles[j] = AngleMod(bs->viewangles[j] - SHORT2ANGLE(bs->cur_ps.delta_angles[j])); + } + //everything was ok + return qtrue; +} + +/* +================== +BotScheduleBotThink +================== +*/ +void BotScheduleBotThink(void) { + int i, botnum; + + botnum = 0; + + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + //initialize the bot think residual time + botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots; + botnum++; + } +} + +/* +============== +BotWriteSessionData +============== +*/ +void BotWriteSessionData(bot_state_t *bs) { + const char *s; + const char *var; + + s = va( + "%i %i %i %i %i %i %i %i" + " %f %f %f" + " %f %f %f" + " %f %f %f", + bs->lastgoal_decisionmaker, + bs->lastgoal_ltgtype, + bs->lastgoal_teammate, + bs->lastgoal_teamgoal.areanum, + bs->lastgoal_teamgoal.entitynum, + bs->lastgoal_teamgoal.flags, + bs->lastgoal_teamgoal.iteminfo, + bs->lastgoal_teamgoal.number, + bs->lastgoal_teamgoal.origin[0], + bs->lastgoal_teamgoal.origin[1], + bs->lastgoal_teamgoal.origin[2], + bs->lastgoal_teamgoal.mins[0], + bs->lastgoal_teamgoal.mins[1], + bs->lastgoal_teamgoal.mins[2], + bs->lastgoal_teamgoal.maxs[0], + bs->lastgoal_teamgoal.maxs[1], + bs->lastgoal_teamgoal.maxs[2] + ); + + var = va( "botsession%i", bs->client ); + + trap_Cvar_Set( var, s ); +} + +/* +============== +BotReadSessionData +============== +*/ +void BotReadSessionData(bot_state_t *bs) { + char s[MAX_STRING_CHARS]; + const char *var; + + var = va( "botsession%i", bs->client ); + trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); + + sscanf(s, + "%i %i %i %i %i %i %i %i" + " %f %f %f" + " %f %f %f" + " %f %f %f", + &bs->lastgoal_decisionmaker, + &bs->lastgoal_ltgtype, + &bs->lastgoal_teammate, + &bs->lastgoal_teamgoal.areanum, + &bs->lastgoal_teamgoal.entitynum, + &bs->lastgoal_teamgoal.flags, + &bs->lastgoal_teamgoal.iteminfo, + &bs->lastgoal_teamgoal.number, + &bs->lastgoal_teamgoal.origin[0], + &bs->lastgoal_teamgoal.origin[1], + &bs->lastgoal_teamgoal.origin[2], + &bs->lastgoal_teamgoal.mins[0], + &bs->lastgoal_teamgoal.mins[1], + &bs->lastgoal_teamgoal.mins[2], + &bs->lastgoal_teamgoal.maxs[0], + &bs->lastgoal_teamgoal.maxs[1], + &bs->lastgoal_teamgoal.maxs[2] + ); +} + +/* +============== +BotAISetupClient +============== +*/ +int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { + char filename[MAX_PATH], name[MAX_PATH], gender[MAX_PATH]; + bot_state_t *bs; + int errnum; + + if (!botstates[client]) botstates[client] = G_Alloc(sizeof(bot_state_t)); + bs = botstates[client]; + + if (bs && bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); + return qfalse; + } + + if (!trap_AAS_Initialized()) { + BotAI_Print(PRT_FATAL, "AAS not initialized\n"); + return qfalse; + } + + //load the bot character + bs->character = trap_BotLoadCharacter(settings->characterfile, settings->skill); + if (!bs->character) { + BotAI_Print(PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile); + return qfalse; + } + //copy the settings + memcpy(&bs->settings, settings, sizeof(bot_settings_t)); + //allocate a goal state + bs->gs = trap_BotAllocGoalState(client); + //load the item weights + trap_Characteristic_String(bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, MAX_PATH); + errnum = trap_BotLoadItemWeights(bs->gs, filename); + if (errnum != BLERR_NOERROR) { + trap_BotFreeGoalState(bs->gs); + return qfalse; + } + //allocate a weapon state + bs->ws = trap_BotAllocWeaponState(); + //load the weapon weights + trap_Characteristic_String(bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, MAX_PATH); + errnum = trap_BotLoadWeaponWeights(bs->ws, filename); + if (errnum != BLERR_NOERROR) { + trap_BotFreeGoalState(bs->gs); + trap_BotFreeWeaponState(bs->ws); + return qfalse; + } + //allocate a chat state + bs->cs = trap_BotAllocChatState(); + //load the chat file + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_FILE, filename, MAX_PATH); + trap_Characteristic_String(bs->character, CHARACTERISTIC_CHAT_NAME, name, MAX_PATH); + errnum = trap_BotLoadChatFile(bs->cs, filename, name); + if (errnum != BLERR_NOERROR) { + trap_BotFreeChatState(bs->cs); + trap_BotFreeGoalState(bs->gs); + trap_BotFreeWeaponState(bs->ws); + return qfalse; + } + //get the gender characteristic + trap_Characteristic_String(bs->character, CHARACTERISTIC_GENDER, gender, MAX_PATH); + //set the chat gender + if (*gender == 'f' || *gender == 'F') trap_BotSetChatGender(bs->cs, CHAT_GENDERFEMALE); + else if (*gender == 'm' || *gender == 'M') trap_BotSetChatGender(bs->cs, CHAT_GENDERMALE); + else trap_BotSetChatGender(bs->cs, CHAT_GENDERLESS); + + bs->inuse = qtrue; + bs->client = client; + bs->entitynum = client; + bs->setupcount = 4; + bs->entergame_time = FloatTime(); + bs->ms = trap_BotAllocMoveState(); + bs->walker = trap_Characteristic_BFloat(bs->character, CHARACTERISTIC_WALKER, 0, 1); + numbots++; + + if (trap_Cvar_VariableIntegerValue("bot_testichat")) { + trap_BotLibVarSet("bot_testichat", "1"); + BotChatTest(bs); + } + //NOTE: reschedule the bot thinking + BotScheduleBotThink(); + //if interbreeding start with a mutation + if (bot_interbreed) { + trap_BotMutateGoalFuzzyLogic(bs->gs, 1); + } + // if we kept the bot client + if (restart) { + BotReadSessionData(bs); + } + //bot has been setup succesfully + return qtrue; +} + +/* +============== +BotAIShutdownClient +============== +*/ +int BotAIShutdownClient(int client, qboolean restart) { + bot_state_t *bs; + + + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("BotAIShutdownClient - attempting to shutdown bot client>%d<\n", client ); + + bs = botstates[client]; + if (!bs || !bs->inuse) { + //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); + //PKMOD - Ergodic 02/13/04 - debug bot's restart (inactive) + //Com_Printf("BotAIShutdownClient - client>%d< already shutdown\n", client ); + return qfalse; + } + + if (restart) { + //PKMOD - Ergodic 01/14/02 - debug bot's restart (inactive) +// Com_Printf("BotAIShutdownClient - restarting client>%d<\n", client ); + BotWriteSessionData(bs); + } + + if (BotChat_ExitGame(bs)) { + trap_BotEnterChat(bs->cs, bs->client, CHAT_ALL); + } + + trap_BotFreeMoveState(bs->ms); + //free the goal state` + trap_BotFreeGoalState(bs->gs); + //free the chat file + trap_BotFreeChatState(bs->cs); + //free the weapon weights + trap_BotFreeWeaponState(bs->ws); + //free the bot character + trap_BotFreeCharacter(bs->character); + // + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //clear activate goal stack + BotClearActivateGoalStack(bs); + //clear the bot state + memset(bs, 0, sizeof(bot_state_t)); + //set the inuse flag to qfalse + bs->inuse = qfalse; + //there's one bot less + numbots--; + //everything went ok + return qtrue; +} + +/* +============== +BotResetState + +called when a bot enters the intermission or observer mode and +when the level is changed +============== +*/ +void BotResetState(bot_state_t *bs) { + int client, entitynum, inuse; + int movestate, goalstate, chatstate, weaponstate; + bot_settings_t settings; + int character; + playerState_t ps; //current player state + float entergame_time; + + //save some things that should not be reset here + memcpy(&settings, &bs->settings, sizeof(bot_settings_t)); + memcpy(&ps, &bs->cur_ps, sizeof(playerState_t)); + inuse = bs->inuse; + client = bs->client; + entitynum = bs->entitynum; + character = bs->character; + movestate = bs->ms; + goalstate = bs->gs; + chatstate = bs->cs; + weaponstate = bs->ws; + entergame_time = bs->entergame_time; + //free checkpoints and patrol points + BotFreeWaypoints(bs->checkpoints); + BotFreeWaypoints(bs->patrolpoints); + //reset the whole state + memset(bs, 0, sizeof(bot_state_t)); + //copy back some state stuff that should not be reset + bs->ms = movestate; + bs->gs = goalstate; + bs->cs = chatstate; + bs->ws = weaponstate; + memcpy(&bs->cur_ps, &ps, sizeof(playerState_t)); + memcpy(&bs->settings, &settings, sizeof(bot_settings_t)); + bs->inuse = inuse; + bs->client = client; + bs->entitynum = entitynum; + bs->character = character; + bs->entergame_time = entergame_time; + //reset several states + if (bs->ms) trap_BotResetMoveState(bs->ms); + if (bs->gs) trap_BotResetGoalState(bs->gs); + if (bs->ws) trap_BotResetWeaponState(bs->ws); + if (bs->gs) trap_BotResetAvoidGoals(bs->gs); + if (bs->ms) trap_BotResetAvoidReach(bs->ms); +} + +/* +============== +BotAILoadMap +============== +*/ +int BotAILoadMap( int restart ) { + int i; + vmCvar_t mapname; + + if (!restart) { + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + trap_BotLibLoadMap( mapname.string ); + } + + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotResetState( botstates[i] ); + botstates[i]->setupcount = 4; + } + } + + BotSetupDeathmatchAI(); + + return qtrue; +} + +#ifdef MISSIONPACK +void ProximityMine_Trigger( gentity_t *trigger, gentity_t *other, trace_t *trace ); +#endif + +/* +================== +BotAIStartFrame +================== +*/ +int BotAIStartFrame(int time) { + int i; + gentity_t *ent; + bot_entitystate_t state; + int elapsed_time, thinktime; + static int local_time; + static int botlib_residual; + static int lastbotthink_time; + + G_CheckBotSpawn(); + + trap_Cvar_Update(&bot_rocketjump); + trap_Cvar_Update(&bot_grapple); + trap_Cvar_Update(&bot_fastchat); + trap_Cvar_Update(&bot_nochat); + trap_Cvar_Update(&bot_testrchat); + trap_Cvar_Update(&bot_thinktime); + trap_Cvar_Update(&bot_memorydump); + trap_Cvar_Update(&bot_saveroutingcache); + trap_Cvar_Update(&bot_pause); + trap_Cvar_Update(&bot_report); + + if (bot_report.integer) { +// BotTeamplayReport(); +// trap_Cvar_Set("bot_report", "0"); + BotUpdateInfoConfigStrings(); + } + + if (bot_pause.integer) { + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( g_entities[i].client->pers.connected != CON_CONNECTED ) { + continue; + } + botstates[i]->lastucmd.forwardmove = 0; + botstates[i]->lastucmd.rightmove = 0; + botstates[i]->lastucmd.upmove = 0; + botstates[i]->lastucmd.buttons = 0; + botstates[i]->lastucmd.serverTime = time; + trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + return qtrue; + } + + if (bot_memorydump.integer) { + trap_BotLibVarSet("memorydump", "1"); + trap_Cvar_Set("bot_memorydump", "0"); + } + if (bot_saveroutingcache.integer) { + trap_BotLibVarSet("saveroutingcache", "1"); + trap_Cvar_Set("bot_saveroutingcache", "0"); + } + //check if bot interbreeding is activated + BotInterbreeding(); + //cap the bot think time + if (bot_thinktime.integer > 200) { + trap_Cvar_Set("bot_thinktime", "200"); + } + //if the bot think time changed we should reschedule the bots + if (bot_thinktime.integer != lastbotthink_time) { + lastbotthink_time = bot_thinktime.integer; + BotScheduleBotThink(); + } + + elapsed_time = time - local_time; + local_time = time; + + botlib_residual += elapsed_time; + + if (elapsed_time > bot_thinktime.integer) thinktime = elapsed_time; + else thinktime = bot_thinktime.integer; + + // update the bot library + if ( botlib_residual >= thinktime ) { + botlib_residual -= thinktime; + + trap_BotLibStartFrame((float) time / 1000); + + if (!trap_AAS_Initialized()) return qfalse; + + //update entities in the botlib + for (i = 0; i < MAX_GENTITIES; i++) { + ent = &g_entities[i]; + if (!ent->inuse) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + if (!ent->r.linked) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + if (ent->r.svFlags & SVF_NOCLIENT) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + // do not update missiles + if (ent->s.eType == ET_MISSILE && ent->s.weapon != WP_GRAPPLING_HOOK) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + // do not update event only entities + if (ent->s.eType > ET_EVENTS) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } +#ifdef MISSIONPACK + // never link prox mine triggers + if (ent->r.contents == CONTENTS_TRIGGER) { + if (ent->touch == ProximityMine_Trigger) { + trap_BotLibUpdateEntity(i, NULL); + continue; + } + } +#endif + // + memset(&state, 0, sizeof(bot_entitystate_t)); + // + VectorCopy(ent->r.currentOrigin, state.origin); + if (i < MAX_CLIENTS) { + VectorCopy(ent->s.apos.trBase, state.angles); + } else { + VectorCopy(ent->r.currentAngles, state.angles); + } + VectorCopy(ent->s.origin2, state.old_origin); + VectorCopy(ent->r.mins, state.mins); + VectorCopy(ent->r.maxs, state.maxs); + state.type = ent->s.eType; + state.flags = ent->s.eFlags; + if (ent->r.bmodel) state.solid = SOLID_BSP; + else state.solid = SOLID_BBOX; + state.groundent = ent->s.groundEntityNum; + state.modelindex = ent->s.modelindex; + state.modelindex2 = ent->s.modelindex2; + state.frame = ent->s.frame; + state.event = ent->s.event; + state.eventParm = ent->s.eventParm; + state.powerups = ent->s.powerups; + state.legsAnim = ent->s.legsAnim; + state.torsoAnim = ent->s.torsoAnim; + state.weapon = ent->s.weapon; + // + trap_BotLibUpdateEntity(i, &state); + } + + BotAIRegularUpdate(); + } + + floattime = trap_AAS_Time(); + + // execute scheduled bot AI + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + // + botstates[i]->botthink_residual += elapsed_time; + // + if ( botstates[i]->botthink_residual >= thinktime ) { + botstates[i]->botthink_residual -= thinktime; + + if (!trap_AAS_Initialized()) return qfalse; + + if (g_entities[i].client->pers.connected == CON_CONNECTED) { + BotAI(i, (float) thinktime / 1000); + } + } + } + + + // execute bot user commands every frame + for( i = 0; i < MAX_CLIENTS; i++ ) { + if( !botstates[i] || !botstates[i]->inuse ) { + continue; + } + if( g_entities[i].client->pers.connected != CON_CONNECTED ) { + continue; + } + + BotUpdateInput(botstates[i], time, elapsed_time); + trap_BotUserCommand(botstates[i]->client, &botstates[i]->lastucmd); + } + + return qtrue; +} + +/* +============== +BotInitLibrary +============== +*/ +int BotInitLibrary(void) { + char buf[144]; + int hold_maxclients; + + //set the maxclients and maxentities library variables before calling BotSetupLibrary + trap_Cvar_VariableStringBuffer("sv_maxclients", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "8"); + + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + hold_maxclients = atoi( buf ) + MAX_PRIVATE_BOTS; + trap_BotLibVarSet("maxclients", va( "%i", hold_maxclients ) ); + + Com_sprintf(buf, sizeof(buf), "%d", MAX_GENTITIES); + trap_BotLibVarSet("maxentities", buf); + //bsp checksum + trap_Cvar_VariableStringBuffer("sv_mapChecksum", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("sv_mapChecksum", buf); + //maximum number of aas links + trap_Cvar_VariableStringBuffer("max_aaslinks", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("max_aaslinks", buf); + //maximum number of items in a level + trap_Cvar_VariableStringBuffer("max_levelitems", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("max_levelitems", buf); + //game type + trap_Cvar_VariableStringBuffer("g_gametype", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + trap_BotLibVarSet("g_gametype", buf); + //bot developer mode and log file + trap_BotLibVarSet("bot_developer", bot_developer.string); + trap_BotLibVarSet("log", buf); + //no chatting + trap_Cvar_VariableStringBuffer("bot_nochat", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("nochat", "0"); + //visualize jump pads + trap_Cvar_VariableStringBuffer("bot_visualizejumppads", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("bot_visualizejumppads", buf); + //forced clustering calculations + trap_Cvar_VariableStringBuffer("bot_forceclustering", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forceclustering", buf); + //forced reachability calculations + trap_Cvar_VariableStringBuffer("bot_forcereachability", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forcereachability", buf); + //force writing of AAS to file + trap_Cvar_VariableStringBuffer("bot_forcewrite", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("forcewrite", buf); + //no AAS optimization + trap_Cvar_VariableStringBuffer("bot_aasoptimize", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("aasoptimize", buf); + // + trap_Cvar_VariableStringBuffer("bot_saveroutingcache", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("saveroutingcache", buf); + //reload instead of cache bot character files + trap_Cvar_VariableStringBuffer("bot_reloadcharacters", buf, sizeof(buf)); + if (!strlen(buf)) strcpy(buf, "0"); + trap_BotLibVarSet("bot_reloadcharacters", buf); + //base directory + trap_Cvar_VariableStringBuffer("fs_basepath", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("basedir", buf); + //game directory + trap_Cvar_VariableStringBuffer("fs_game", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("gamedir", buf); + //cd directory + trap_Cvar_VariableStringBuffer("fs_cdpath", buf, sizeof(buf)); + if (strlen(buf)) trap_BotLibVarSet("cddir", buf); + // +#ifdef MISSIONPACK + trap_BotLibDefine("MISSIONPACK"); +#endif + + //PKMOD - Ergodic 12/02/01 - initialize varibables for bots to use grapple + trap_BotLibVarSet("weapindex_grapple", va("%d", WP_GRAPPLING_HOOK)); + trap_BotLibVarSet("entitytypemissile", va("%d", ET_MISSILE)); + + //setup the bot library + return trap_BotLibSetup(); +} + +/* +============== +BotAISetup +============== +*/ +int BotAISetup( int restart ) { + int errnum; + + trap_Cvar_Register(&bot_thinktime, "bot_thinktime", "100", CVAR_CHEAT); + trap_Cvar_Register(&bot_memorydump, "bot_memorydump", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_saveroutingcache, "bot_saveroutingcache", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_pause, "bot_pause", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_report, "bot_report", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_testsolid, "bot_testsolid", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_testclusters, "bot_testclusters", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_developer, "bot_developer", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_interbreedchar, "bot_interbreedchar", "", 0); + trap_Cvar_Register(&bot_interbreedbots, "bot_interbreedbots", "10", 0); + trap_Cvar_Register(&bot_interbreedcycle, "bot_interbreedcycle", "20", 0); + trap_Cvar_Register(&bot_interbreedwrite, "bot_interbreedwrite", "", 0); + + //if the game is restarted for a tournament + if (restart) { + return qtrue; + } + + //initialize the bot states + memset( botstates, 0, sizeof(botstates) ); + + errnum = BotInitLibrary(); + if (errnum != BLERR_NOERROR) return qfalse; + return qtrue; +} + +/* +============== +BotAIShutdown +============== +*/ +int BotAIShutdown( int restart ) { + + int i; + + //PKMOD - Ergodic 02/13/04 - debug bot's userinfo (inactive) + //char netname[MAX_MESSAGE_SIZE]; + + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("+-+-+-BotAIShutdown-+-+-+\n" ); + + //if the game is restarted for a tournament + if ( restart ) { + //shutdown all the bots in the botlib + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + + //PKMOD - Ergodic 02/13/04 - debug bot's userinfo (inactive) + //ClientName(botstates[i]->client, netname, sizeof(netname)); + //Com_Printf("BotAIShutdown - clientnum>%d<, entitynum>%d<, name>%s<, flags>%d<\n", botstates[i]->client, botstates[i]->entitynum, netname, g_entities[botstates[i]->entitynum].r.svFlags ); + BotAIShutdownClient(botstates[i]->client, restart); + } + } + //don't shutdown the bot library + } + else { + trap_BotLibShutdown(); + } + return qtrue; +} + diff --git a/quake3/source/code/game/ai_main.h b/quake3/source/code/game/ai_main.h new file mode 100644 index 0000000..7a8ee0f --- /dev/null +++ b/quake3/source/code/game/ai_main.h @@ -0,0 +1,310 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_main.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +//#define DEBUG +#define CTF + +#define MAX_ITEMS 256 +//bot flags +#define BFL_STRAFERIGHT 1 //strafe to the right +#define BFL_ATTACKED 2 //bot has attacked last ai frame +#define BFL_ATTACKJUMPED 4 //bot jumped during attack last frame +#define BFL_AIMATENEMY 8 //bot aimed at the enemy this frame +#define BFL_AVOIDRIGHT 16 //avoid obstacles by going to the right +#define BFL_IDEALVIEWSET 32 //bot has ideal view angles set +#define BFL_FIGHTSUICIDAL 64 //bot is in a suicidal fight +//long term goal types +#define LTG_TEAMHELP 1 //help a team mate +#define LTG_TEAMACCOMPANY 2 //accompany a team mate +#define LTG_DEFENDKEYAREA 3 //defend a key area +#define LTG_GETFLAG 4 //get the enemy flag +#define LTG_RUSHBASE 5 //rush to the base +#define LTG_RETURNFLAG 6 //return the flag +#define LTG_CAMP 7 //camp somewhere +#define LTG_CAMPORDER 8 //ordered to camp somewhere +#define LTG_PATROL 9 //patrol +#define LTG_GETITEM 10 //get an item +#define LTG_KILL 11 //kill someone +#define LTG_HARVEST 12 //harvest skulls +#define LTG_ATTACKENEMYBASE 13 //attack the enemy base +#define LTG_MAKELOVE_UNDER 14 +#define LTG_MAKELOVE_ONTOP 15 +//some goal dedication times +#define TEAM_HELP_TIME 60 //1 minute teamplay help time +#define TEAM_ACCOMPANY_TIME 600 //10 minutes teamplay accompany time +#define TEAM_DEFENDKEYAREA_TIME 600 //10 minutes ctf defend base time +#define TEAM_CAMP_TIME 600 //10 minutes camping time +#define TEAM_PATROL_TIME 600 //10 minutes patrolling time +#define TEAM_LEAD_TIME 600 //10 minutes taking the lead +#define TEAM_GETITEM_TIME 60 //1 minute +#define TEAM_KILL_SOMEONE 180 //3 minute to kill someone +#define TEAM_ATTACKENEMYBASE_TIME 600 //10 minutes +#define TEAM_HARVEST_TIME 120 //2 minutes +#define CTF_GETFLAG_TIME 600 //10 minutes ctf get flag time +#define CTF_RUSHBASE_TIME 120 //2 minutes ctf rush base time +#define CTF_RETURNFLAG_TIME 180 //3 minutes to return the flag +#define CTF_ROAM_TIME 60 //1 minute ctf roam time +//patrol flags +#define PATROL_LOOP 1 +#define PATROL_REVERSE 2 +#define PATROL_BACK 4 +//teamplay task preference +#define TEAMTP_DEFENDER 1 +#define TEAMTP_ATTACKER 2 +//CTF strategy +#define CTFS_AGRESSIVE 1 +//copied from the aas file header +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 +// + +//PKMOD - Ergodic 08/30/01 - remove these fields in PKA +/*++++ +#define MAX_PROXMINES 64 +----*/ + +//PKMOD - Ergodic 04/14/01 - new code for bots to target enemy beartraps +//PKMOD - Ergodic 08/30/01 - reduce from 64 to 32 (practical) +#define MAX_BEARTRAPS 32 + +//PKMOD - Ergodic 08/30/01 - code for bots to target enemy autosentrys +#define MAX_AUTOSENTRYS 32 + +//PKMOD - Ergodic 08/30/01 - code for bots to avoid gravity wells +#define MAX_GRAVITYWELLS 8 + +//check points +typedef struct bot_waypoint_s +{ + int inuse; + char name[32]; + bot_goal_t goal; + struct bot_waypoint_s *next, *prev; +} bot_waypoint_t; + +#define MAX_ACTIVATESTACK 8 +#define MAX_ACTIVATEAREAS 32 + +typedef struct bot_activategoal_s +{ + int inuse; + bot_goal_t goal; //goal to activate (buttons etc.) + float time; //time to activate something + float start_time; //time starting to activate something + float justused_time; //time the goal was used + int shoot; //true if bot has to shoot to activate + int weapon; //weapon to be used for activation + vec3_t target; //target to shoot at to activate something + vec3_t origin; //origin of the blocking entity to activate + int areas[MAX_ACTIVATEAREAS]; //routing areas disabled by blocking entity + int numareas; //number of disabled routing areas + int areasdisabled; //true if the areas are disabled for the routing + struct bot_activategoal_s *next; //next activate goal on stack +} bot_activategoal_t; + +//bot state +typedef struct bot_state_s +{ + int inuse; //true if this state is used by a bot client + int botthink_residual; //residual for the bot thinks + int client; //client number of the bot + int entitynum; //entity number of the bot + playerState_t cur_ps; //current player state + int last_eFlags; //last ps flags + usercmd_t lastucmd; //usercmd from last frame + int entityeventTime[1024]; //last entity event time + // + bot_settings_t settings; //several bot settings + int (*ainode)(struct bot_state_s *bs); //current AI node + float thinktime; //time the bot thinks this frame + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + int presencetype; //presence type of the bot + vec3_t eye; //eye coordinates of the bot + int areanum; //the number of the area the bot is in + int inventory[MAX_ITEMS]; //string with items amounts the bot has + int tfl; //the travel flags the bot uses + int flags; //several flags + int respawn_wait; //wait until respawned + int lasthealth; //health value previous frame + int lastkilledplayer; //last killed player + int lastkilledby; //player that last killed this bot + int botdeathtype; //the death type of the bot + int enemydeathtype; //the death type of the enemy + int botsuicide; //true when the bot suicides + int enemysuicide; //true when the enemy of the bot suicides + int setupcount; //true when the bot has just been setup + int map_restart; //true when the map is being restarted + int entergamechat; //true when the bot used an enter game chat + int num_deaths; //number of time this bot died + int num_kills; //number of kills of this bot + int revenge_enemy; //the revenge enemy + int revenge_kills; //number of kills the enemy made + int lastframe_health; //health value the last frame + int lasthitcount; //number of hits last frame + int chatto; //chat to all or team + float walker; //walker charactertic + float ltime; //local bot time + float entergame_time; //time the bot entered the game + float ltg_time; //long term goal time + float nbg_time; //nearby goal time + float respawn_time; //time the bot takes to respawn + float respawnchat_time; //time the bot started a chat during respawn + float chase_time; //time the bot will chase the enemy + float enemyvisible_time; //time the enemy was last visible + float check_time; //time to check for nearby items + float stand_time; //time the bot is standing still + float lastchat_time; //time the bot last selected a chat + float kamikaze_time; //time to check for kamikaze usage + float invulnerability_time; //time to check for invulnerability usage + float standfindenemy_time; //time to find enemy while standing + float attackstrafe_time; //time the bot is strafing in one dir + float attackcrouch_time; //time the bot will stop crouching + float attackchase_time; //time the bot chases during actual attack + float attackjump_time; //time the bot jumped during attack + float enemysight_time; //time before reacting to enemy + float enemydeath_time; //time the enemy died + float enemyposition_time; //time the position and velocity of the enemy were stored + float defendaway_time; //time away while defending + float defendaway_range; //max travel time away from defend area + float rushbaseaway_time; //time away from rushing to the base + float attackaway_time; //time away from attacking the enemy base + float harvestaway_time; //time away from harvesting + float ctfroam_time; //time the bot is roaming in ctf + float killedenemy_time; //time the bot killed the enemy + float arrive_time; //time arrived (at companion) + float lastair_time; //last time the bot had air + float teleport_time; //last time the bot teleported + float camp_time; //last time camped + float camp_range; //camp range + float weaponchange_time; //time the bot started changing weapons + float firethrottlewait_time; //amount of time to wait + float firethrottleshoot_time; //amount of time to shoot + float notblocked_time; //last time the bot was not blocked + float blockedbyavoidspot_time; //time blocked by an avoid spot + float predictobstacles_time; //last time the bot predicted obstacles + int predictobstacles_goalareanum; //last goal areanum the bot predicted obstacles for + vec3_t aimtarget; + vec3_t enemyvelocity; //enemy velocity 0.5 secs ago during battle + vec3_t enemyorigin; //enemy origin 0.5 secs ago during battle + // + + //PKMOD - Ergodic 08/30/01 - remove these fields in PKA + /*++++ + int kamikazebody; //kamikaze body + int proxmines[MAX_PROXMINES]; + int numproxmines; + ----*/ + + //PKMOD - Ergodic 04/14/01 - new code for bots to target enemy beartraps + int beartraps[MAX_BEARTRAPS]; + int numbeartraps; + +//PKMOD - Ergodic 08/30/01 - code for bots to target enemy autosentrys + int autosentrys[MAX_AUTOSENTRYS]; + int numautosentrys; + +//PKMOD - Ergodic 08/30/01 - code for bots to target enemy autosentrys + int gravitywells[MAX_GRAVITYWELLS]; + int numgravitywells; + + // + int character; //the bot character + int ms; //move state of the bot + int gs; //goal state of the bot + int cs; //chat state of the bot + int ws; //weapon state of the bot + // + int enemy; //enemy entity number + int lastenemyareanum; //last reachability area the enemy was in + vec3_t lastenemyorigin; //last origin of the enemy in the reachability area + int weaponnum; //current weapon number + vec3_t viewangles; //current view angles + vec3_t ideal_viewangles; //ideal view angles + vec3_t viewanglespeed; + // + int ltgtype; //long term goal type + // team goals + int teammate; //team mate involved in this team goal + int decisionmaker; //player who decided to go for this goal + int ordered; //true if ordered to do something + float order_time; //time ordered to do something + int owndecision_time; //time the bot made it's own decision + bot_goal_t teamgoal; //the team goal + bot_goal_t altroutegoal; //alternative route goal + float reachedaltroutegoal_time; //time the bot reached the alt route goal + float teammessage_time; //time to message team mates what the bot is doing + float teamgoal_time; //time to stop helping team mate + float teammatevisible_time; //last time the team mate was NOT visible + int teamtaskpreference; //team task preference + // last ordered team goal + int lastgoal_decisionmaker; + int lastgoal_ltgtype; + int lastgoal_teammate; + bot_goal_t lastgoal_teamgoal; + // for leading team mates + int lead_teammate; //team mate the bot is leading + bot_goal_t lead_teamgoal; //team goal while leading + float lead_time; //time leading someone + float leadvisible_time; //last time the team mate was visible + float leadmessage_time; //last time a messaged was sent to the team mate + float leadbackup_time; //time backing up towards team mate + // + char teamleader[32]; //netname of the team leader + float askteamleader_time; //time asked for team leader + float becometeamleader_time; //time the bot will become the team leader + float teamgiveorders_time; //time to give team orders + float lastflagcapture_time; //last time a flag was captured + int numteammates; //number of team mates + int redflagstatus; //0 = at base, 1 = not at base + int blueflagstatus; //0 = at base, 1 = not at base + int neutralflagstatus; //0 = at base, 1 = our team has flag, 2 = enemy team has flag, 3 = enemy team dropped the flag + int flagstatuschanged; //flag status changed + int forceorders; //true if forced to give orders + int flagcarrier; //team mate carrying the enemy flag + int ctfstrategy; //ctf strategy + char subteam[32]; //sub team name + float formation_dist; //formation team mate intervening space + char formation_teammate[16]; //netname of the team mate the bot uses for relative positioning + float formation_angle; //angle relative to the formation team mate + vec3_t formation_dir; //the direction the formation is moving in + vec3_t formation_origin; //origin the bot uses for relative positioning + bot_goal_t formation_goal; //formation goal + + bot_activategoal_t *activatestack; //first activate goal on the stack + bot_activategoal_t activategoalheap[MAX_ACTIVATESTACK]; //activate goal heap + + bot_waypoint_t *checkpoints; //check points + bot_waypoint_t *patrolpoints; //patrol points + bot_waypoint_t *curpatrolpoint; //current patrol point the bot is going for + int patrolflags; //patrol flags +} bot_state_t; + +//resets the whole bot state +void BotResetState(bot_state_t *bs); +//returns the number of bots in the game +int NumBots(void); +//returns info about the entity +void BotEntityInfo(int entnum, aas_entityinfo_t *info); + +extern float floattime; +#define FloatTime() floattime + +// from the game source +void QDECL BotAI_Print(int type, char *fmt, ...); +void QDECL QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ); +void BotAI_Trace(bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); +int BotAI_GetClientState( int clientNum, playerState_t *state ); +int BotAI_GetEntityState( int entityNum, entityState_t *state ); +int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ); +int BotTeamLeader(bot_state_t *bs); diff --git a/quake3/source/code/game/ai_team.c b/quake3/source/code/game/ai_team.c new file mode 100644 index 0000000..6f01ccc --- /dev/null +++ b/quake3/source/code/game/ai_team.c @@ -0,0 +1,2071 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_team.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_team.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +#include "ai_vcmd.h" + +#include "match.h" + +// for the voice chats +#include "../../ui/menudef.h" + +//ctf task preferences for a client +typedef struct bot_ctftaskpreference_s +{ + char name[36]; + int preference; +} bot_ctftaskpreference_t; + +bot_ctftaskpreference_t ctftaskpreferences[MAX_CLIENTS]; + + +/* +================== +BotValidTeamLeader +================== +*/ +int BotValidTeamLeader(bot_state_t *bs) { + if (!strlen(bs->teamleader)) return qfalse; + if (ClientFromName(bs->teamleader) == -1) return qfalse; + return qtrue; +} + +/* +================== +BotNumTeamMates +================== +*/ +int BotNumTeamMates(bot_state_t *bs) { + int i, numplayers; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + //PKMOD - Ergodic 12/12/01 - include a player buffer for Private_Bots + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients") + MAX_PRIVATE_BOTS; + + numplayers = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + numplayers++; + } + } + return numplayers; +} + +/* +================== +BotClientTravelTimeToGoal +================== +*/ +int BotClientTravelTimeToGoal(int client, bot_goal_t *goal) { + playerState_t ps; + int areanum; + + BotAI_GetClientState(client, &ps); + areanum = BotPointAreaNum(ps.origin); + if (!areanum) return 1; + return trap_AAS_AreaTravelTimeToGoalArea(areanum, ps.origin, goal->areanum, TFL_DEFAULT); +} + +/* +================== +BotSortTeamMatesByBaseTravelTime +================== +*/ +int BotSortTeamMatesByBaseTravelTime(bot_state_t *bs, int *teammates, int maxteammates) { + + int i, j, k, numteammates, traveltime; + char buf[MAX_INFO_STRING]; + static int maxclients; + int traveltimes[MAX_CLIENTS]; + bot_goal_t *goal = NULL; + + if (gametype == GT_CTF || gametype == GT_1FCTF) { + if (BotTeam(bs) == TEAM_RED) + goal = &ctf_redflag; + else + goal = &ctf_blueflag; + } +#ifdef MISSIONPACK + else { + if (BotTeam(bs) == TEAM_RED) + goal = &redobelisk; + else + goal = &blueobelisk; + } +#endif + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numteammates = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + // + traveltime = BotClientTravelTimeToGoal(i, goal); + // + for (j = 0; j < numteammates; j++) { + if (traveltime < traveltimes[j]) { + for (k = numteammates; k > j; k--) { + traveltimes[k] = traveltimes[k-1]; + teammates[k] = teammates[k-1]; + } + break; + } + } + traveltimes[j] = traveltime; + teammates[j] = i; + numteammates++; + if (numteammates >= maxteammates) break; + } + } + return numteammates; +} + +/* +================== +BotSetTeamMateTaskPreference +================== +*/ +void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference) { + char teammatename[MAX_NETNAME]; + + ctftaskpreferences[teammate].preference = preference; + ClientName(teammate, teammatename, sizeof(teammatename)); + strcpy(ctftaskpreferences[teammate].name, teammatename); +} + +/* +================== +BotGetTeamMateTaskPreference +================== +*/ +int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate) { + char teammatename[MAX_NETNAME]; + + if (!ctftaskpreferences[teammate].preference) return 0; + ClientName(teammate, teammatename, sizeof(teammatename)); + if (Q_stricmp(teammatename, ctftaskpreferences[teammate].name)) return 0; + return ctftaskpreferences[teammate].preference; +} + +/* +================== +BotSortTeamMatesByTaskPreference +================== +*/ +int BotSortTeamMatesByTaskPreference(bot_state_t *bs, int *teammates, int numteammates) { + int defenders[MAX_CLIENTS], numdefenders; + int attackers[MAX_CLIENTS], numattackers; + int roamers[MAX_CLIENTS], numroamers; + int i, preference; + + numdefenders = numattackers = numroamers = 0; + for (i = 0; i < numteammates; i++) { + preference = BotGetTeamMateTaskPreference(bs, teammates[i]); + if (preference & TEAMTP_DEFENDER) { + defenders[numdefenders++] = teammates[i]; + } + else if (preference & TEAMTP_ATTACKER) { + attackers[numattackers++] = teammates[i]; + } + else { + roamers[numroamers++] = teammates[i]; + } + } + numteammates = 0; + //defenders at the front of the list + memcpy(&teammates[numteammates], defenders, numdefenders * sizeof(int)); + numteammates += numdefenders; + //roamers in the middle + memcpy(&teammates[numteammates], roamers, numroamers * sizeof(int)); + numteammates += numroamers; + //attacker in the back of the list + memcpy(&teammates[numteammates], attackers, numattackers * sizeof(int)); + numteammates += numattackers; + + return numteammates; +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrderAlways(bot_state_t *bs, int toclient) { + char teamchat[MAX_MESSAGE_SIZE]; + char buf[MAX_MESSAGE_SIZE]; + char name[MAX_NETNAME]; + + //if the bot is talking to itself + if (bs->client == toclient) { + //don't show the message just put it in the console message queue + trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); + ClientName(bs->client, name, sizeof(name)); + Com_sprintf(teamchat, sizeof(teamchat), EC"(%s"EC")"EC": %s", name, buf); + trap_BotQueueConsoleMessage(bs->cs, CMS_CHAT, teamchat); + } + else { + trap_BotEnterChat(bs->cs, toclient, CHAT_TELL); + } +} + +/* +================== +BotSayTeamOrders +================== +*/ +void BotSayTeamOrder(bot_state_t *bs, int toclient) { +#ifdef MISSIONPACK + // voice chats only + char buf[MAX_MESSAGE_SIZE]; + + trap_BotGetChatMessage(bs->cs, buf, sizeof(buf)); +#else + BotSayTeamOrderAlways(bs, toclient); +#endif +} + +/* +================== +BotVoiceChat +================== +*/ +void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + if (toclient == -1) + // voice only say team + trap_EA_Command(bs->client, va("vsay_team %s", voicechat)); + else + // voice only tell single player + trap_EA_Command(bs->client, va("vtell %d %s", toclient, voicechat)); +#endif +} + +/* +================== +BotVoiceChatOnly +================== +*/ +void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + if (toclient == -1) + // voice only say team + trap_EA_Command(bs->client, va("vosay_team %s", voicechat)); + else + // voice only tell single player + trap_EA_Command(bs->client, va("votell %d %s", toclient, voicechat)); +#endif +} + +/* +================== +BotSayVoiceTeamOrder +================== +*/ +void BotSayVoiceTeamOrder(bot_state_t *bs, int toclient, char *voicechat) { +#ifdef MISSIONPACK + BotVoiceChat(bs, toclient, voicechat); +#endif +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to accompany the flag carrier + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + } + else { + // + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + } + BotSayTeamOrder(bs, other); + //tell the one furthest from the the base not carrying the flag to get the enemy flag + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_RETURNFLAG); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.4 + 0.5; + if (defenders > 4) defenders = 4; + attackers = (int) (float) numteammates * 0.5 + 0.5; + if (attackers > 5) attackers = 5; + if (bs->flagcarrier != -1) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[i], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[i]); + } + } + else { + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, teammates[i]); + } + } + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_RETURNFLAG); + } + // + break; + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_FlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //both will go for the enemy flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //keep one near the base for when the flag is returned + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other two get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //keep some people near the base for when the flag is returned + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(bs->numteammates) { + case 1: break; + case 2: + { + //both will go for the enemy flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //everyone go for the flag + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_GETFLAG); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //keep some people near the base for when the flag is returned + defenders = (int) (float) numteammates * 0.2 + 0.5; + if (defenders > 2) defenders = 2; + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_EnemyFlagNotAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the other also to defend the base + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + default: + { + //60% will defend the base + defenders = (int) (float) numteammates * 0.6 + 0.5; + if (defenders > 6) defenders = 6; + //30% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.3 + 0.5; + if (attackers > 3) attackers = 3; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + // if we have a flag carrier + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + else { + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + // + break; + } + } +} + + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders_BothFlagsAtBase(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + defenders = (int) (float) numteammates * 0.4 + 0.5; + if (defenders > 4) defenders = 4; + attackers = (int) (float) numteammates * 0.5 + 0.5; + if (attackers > 5) attackers = 5; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +BotCTFOrders +================== +*/ +void BotCTFOrders(bot_state_t *bs) { + int flagstatus; + + // + if (BotTeam(bs) == TEAM_RED) flagstatus = bs->redflagstatus * 2 + bs->blueflagstatus; + else flagstatus = bs->blueflagstatus * 2 + bs->redflagstatus; + // + switch(flagstatus) { + case 0: BotCTFOrders_BothFlagsAtBase(bs); break; + case 1: BotCTFOrders_EnemyFlagNotAtBase(bs); break; + case 2: BotCTFOrders_FlagNotAtBase(bs); break; + case 3: BotCTFOrders_BothFlagsNotAtBase(bs); break; + } +} + + +/* +================== +BotCreateGroup +================== +*/ +void BotCreateGroup(bot_state_t *bs, int *teammates, int groupsize) { + char name[MAX_NETNAME], leadername[MAX_NETNAME]; + int i; + + // the others in the group will follow the teammates[0] + ClientName(teammates[0], leadername, sizeof(leadername)); + for (i = 1; i < groupsize; i++) + { + ClientName(teammates[i], name, sizeof(name)); + if (teammates[0] == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, leadername, NULL); + } + BotSayTeamOrderAlways(bs, teammates[i]); + } +} + +/* +================== +BotTeamOrders + + FIXME: defend key areas? +================== +*/ +void BotTeamOrders(bot_state_t *bs) { + int teammates[MAX_CLIENTS]; + int numteammates, i; + char buf[MAX_INFO_STRING]; + static int maxclients; + + if (!maxclients) + maxclients = trap_Cvar_VariableIntegerValue("sv_maxclients"); + + numteammates = 0; + for (i = 0; i < maxclients && i < MAX_CLIENTS; i++) { + trap_GetConfigstring(CS_PLAYERS+i, buf, sizeof(buf)); + //if no config string or no name + if (!strlen(buf) || !strlen(Info_ValueForKey(buf, "n"))) continue; + //skip spectators + if (atoi(Info_ValueForKey(buf, "t")) == TEAM_SPECTATOR) continue; + // + if (BotSameTeam(bs, i)) { + teammates[numteammates] = i; + numteammates++; + } + } + // + switch(numteammates) { + case 1: break; + case 2: + { + //nothing special + break; + } + case 3: + { + //have one follow another and one free roaming + BotCreateGroup(bs, teammates, 2); + break; + } + case 4: + { + BotCreateGroup(bs, teammates, 2); //a group of 2 + BotCreateGroup(bs, &teammates[2], 2); //a group of 2 + break; + } + case 5: + { + BotCreateGroup(bs, teammates, 2); //a group of 2 + BotCreateGroup(bs, &teammates[2], 3); //a group of 3 + break; + } + default: + { + if (numteammates <= 10) { + for (i = 0; i < numteammates / 2; i++) { + BotCreateGroup(bs, &teammates[i*2], 2); //groups of 2 + } + } + break; + } + } +} + +#ifdef MISSIONPACK + +/* +================== +Bot1FCTFOrders_FlagAtCenter + + X% defend the base, Y% get the flag +================== +*/ +void Bot1FCTFOrders_FlagAtCenter(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% get the flag + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //60% get the flag + attackers = (int) (float) numteammates * 0.6 + 0.5; + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_TeamHasFlag + + X% towards neutral flag, Y% go towards enemy base and accompany flag carrier if visible +================== +*/ +void Bot1FCTFOrders_TeamHasFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i, other; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME], carriername[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to attack the enemy base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_OFFENSE); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + if ( bs->flagcarrier != -1 ) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + } + else { + // + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_GETFLAG); + } + BotSayTeamOrder(bs, other); + break; + } + default: + { + //30% will defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //70% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + if (bs->flagcarrier != -1) { + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + } + else { + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //tell the one not carrying the flag to defend the base + if (teammates[0] == bs->flagcarrier) other = teammates[1]; + else other = teammates[0]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + break; + } + case 3: + { + //tell the one closest to the base not carrying the flag to defend the base + if (teammates[0] != bs->flagcarrier) other = teammates[0]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, other); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_DEFEND); + //tell the one furthest from the base not carrying the flag to accompany the flag carrier + if (teammates[2] != bs->flagcarrier) other = teammates[2]; + else other = teammates[1]; + ClientName(other, name, sizeof(name)); + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, other, VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, other); + break; + } + default: + { + //20% will defend the base + defenders = (int) (float) numteammates * 0.2 + 0.5; + if (defenders > 2) defenders = 2; + //80% accompanies the flag carrier + attackers = (int) (float) numteammates * 0.8 + 0.5; + if (attackers > 8) attackers = 8; + for (i = 0; i < defenders; i++) { + // + if (teammates[i] == bs->flagcarrier) { + continue; + } + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + ClientName(bs->flagcarrier, carriername, sizeof(carriername)); + for (i = 0; i < attackers; i++) { + // + if (teammates[numteammates - i - 1] == bs->flagcarrier) { + continue; + } + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + if (bs->flagcarrier == bs->client) { + BotAI_BotInitialChat(bs, "cmd_accompanyme", name, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWME); + } + else { + BotAI_BotInitialChat(bs, "cmd_accompany", name, carriername, NULL); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_FOLLOWFLAGCARRIER); + } + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_EnemyHasFlag + + X% defend the base, Y% towards neutral flag +================== +*/ +void Bot1FCTFOrders_EnemyHasFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //both defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + // + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will also defend the base + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_DEFEND); + break; + } + default: + { + //80% will defend the base + defenders = (int) (float) numteammates * 0.8 + 0.5; + if (defenders > 8) defenders = 8; + //10% will try to return the flag + attackers = (int) (float) numteammates * 0.1 + 0.5; + if (attackers > 2) attackers = 2; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //70% defend the base + defenders = (int) (float) numteammates * 0.7 + 0.5; + if (defenders > 8) defenders = 8; + //20% try to return the flag + attackers = (int) (float) numteammates * 0.2 + 0.5; + if (attackers > 2) attackers = 2; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_returnflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders_EnemyDroppedFlag + + X% defend the base, Y% get the flag +================== +*/ +void Bot1FCTFOrders_EnemyDroppedFlag(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the second one closest to the base will defend the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% get the flag + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_GETFLAG); + } + // + break; + } + } + } + else { //agressive + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will get the flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others should go for the enemy flag + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_GETFLAG); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_GETFLAG); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //60% get the flag + attackers = (int) (float) numteammates * 0.6 + 0.5; + if (attackers > 6) attackers = 6; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_getflag", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_DEFEND); + } + // + break; + } + } + } +} + +/* +================== +Bot1FCTFOrders +================== +*/ +void Bot1FCTFOrders(bot_state_t *bs) { + switch(bs->neutralflagstatus) { + case 0: Bot1FCTFOrders_FlagAtCenter(bs); break; + case 1: Bot1FCTFOrders_TeamHasFlag(bs); break; + case 2: Bot1FCTFOrders_EnemyHasFlag(bs); break; + case 3: Bot1FCTFOrders_EnemyDroppedFlag(bs); break; + } +} + +/* +================== +BotObeliskOrders + + X% in defence Y% in offence +================== +*/ +void BotObeliskOrders(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the one second closest to the base also defends the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other one attacks the enemy base + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% attack the enemy base + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others attack the enemy base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //70% attack the enemy base + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_attackenemybase", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } +} + +/* +================== +BotHarvesterOrders + + X% defend the base, Y% harvest +================== +*/ +void BotHarvesterOrders(bot_state_t *bs) { + int numteammates, defenders, attackers, i; + int teammates[MAX_CLIENTS]; + char name[MAX_NETNAME]; + + //sort team mates by travel time to base + numteammates = BotSortTeamMatesByBaseTravelTime(bs, teammates, sizeof(teammates)); + //sort team mates by CTF preference + BotSortTeamMatesByTaskPreference(bs, teammates, numteammates); + //passive strategy + if (!(bs->ctfstrategy & CTFS_AGRESSIVE)) { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will harvest + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the one second closest to the base also defends the base + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_DEFEND); + //the other one goes harvesting + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //50% defend the base + defenders = (int) (float) numteammates * 0.5 + 0.5; + if (defenders > 5) defenders = 5; + //40% goes harvesting + attackers = (int) (float) numteammates * 0.4 + 0.5; + if (attackers > 4) attackers = 4; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } + else { + //different orders based on the number of team mates + switch(numteammates) { + case 1: break; + case 2: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the other will harvest + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + break; + } + case 3: + { + //the one closest to the base will defend the base + ClientName(teammates[0], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[0]); + BotSayVoiceTeamOrder(bs, teammates[0], VOICECHAT_DEFEND); + //the others go harvesting + ClientName(teammates[1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[1]); + BotSayVoiceTeamOrder(bs, teammates[1], VOICECHAT_OFFENSE); + // + ClientName(teammates[2], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[2]); + BotSayVoiceTeamOrder(bs, teammates[2], VOICECHAT_OFFENSE); + break; + } + default: + { + //30% defend the base + defenders = (int) (float) numteammates * 0.3 + 0.5; + if (defenders > 3) defenders = 3; + //70% go harvesting + attackers = (int) (float) numteammates * 0.7 + 0.5; + if (attackers > 7) attackers = 7; + for (i = 0; i < defenders; i++) { + // + ClientName(teammates[i], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_defendbase", name, NULL); + BotSayTeamOrder(bs, teammates[i]); + BotSayVoiceTeamOrder(bs, teammates[i], VOICECHAT_DEFEND); + } + for (i = 0; i < attackers; i++) { + // + ClientName(teammates[numteammates - i - 1], name, sizeof(name)); + BotAI_BotInitialChat(bs, "cmd_harvest", name, NULL); + BotSayTeamOrder(bs, teammates[numteammates - i - 1]); + BotSayVoiceTeamOrder(bs, teammates[numteammates - i - 1], VOICECHAT_OFFENSE); + } + // + break; + } + } + } +} +#endif + +/* +================== +FindHumanTeamLeader +================== +*/ +int FindHumanTeamLeader(bot_state_t *bs) { + int i; + + for (i = 0; i < MAX_CLIENTS; i++) { + if ( g_entities[i].inuse ) { + // if this player is not a bot + if ( !(g_entities[i].r.svFlags & SVF_BOT) ) { + // if this player is ok with being the leader + if (!notleader[i]) { + // if this player is on the same team + if ( BotSameTeam(bs, i) ) { + ClientName(i, bs->teamleader, sizeof(bs->teamleader)); + // if not yet ordered to do anything + if ( !BotSetLastOrderedTask(bs) ) { + // go on defense by default + BotVoiceChat_Defend(bs, i, SAY_TELL); + } + return qtrue; + } + } + } + } + } + return qfalse; +} + +/* +================== +BotTeamAI +================== +*/ +void BotTeamAI(bot_state_t *bs) { + int numteammates; + char netname[MAX_NETNAME]; + + // + if ( gametype < GT_TEAM ) { + //PKMOD - Ergodic 03/30/02 - in a non-team game Private Bot will act as a team follower + if ( g_entities[bs->entitynum].r.svFlags & SVF_PRIVATEBOT ) { + //Does private bot have a teamleader set? + if ( !BotValidTeamLeader(bs) ) { + //Set owner as Private Bot leader + ClientName(g_entities[bs->entitynum].parent->client->ps.clientNum, bs->teamleader, sizeof(bs->teamleader)); + return; + } + } + return; + } + // make sure we've got a valid team leader + if (!BotValidTeamLeader(bs)) { + // + if (!FindHumanTeamLeader(bs)) { + // + if (!bs->askteamleader_time && !bs->becometeamleader_time) { + if (bs->entergame_time + 10 > FloatTime()) { + bs->askteamleader_time = FloatTime() + 5 + random() * 10; + } + else { + bs->becometeamleader_time = FloatTime() + 5 + random() * 10; + } + } + if (bs->askteamleader_time && bs->askteamleader_time < FloatTime()) { + // if asked for a team leader and no response + BotAI_BotInitialChat(bs, "whoisteamleader", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + bs->askteamleader_time = 0; + bs->becometeamleader_time = FloatTime() + 8 + random() * 10; + } + if (bs->becometeamleader_time && bs->becometeamleader_time < FloatTime()) { + BotAI_BotInitialChat(bs, "iamteamleader", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotSayVoiceTeamOrder(bs, -1, VOICECHAT_STARTLEADER); + ClientName(bs->client, netname, sizeof(netname)); + strncpy(bs->teamleader, netname, sizeof(bs->teamleader)); + bs->teamleader[sizeof(bs->teamleader)] = '\0'; + bs->becometeamleader_time = 0; + } + return; + } + } + bs->askteamleader_time = 0; + bs->becometeamleader_time = 0; + + //return if this bot is NOT the team leader + ClientName(bs->client, netname, sizeof(netname)); + if (Q_stricmp(netname, bs->teamleader) != 0) return; + // + numteammates = BotNumTeamMates(bs); + //give orders + switch(gametype) { + case GT_TEAM: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotTeamOrders(bs); + //give orders again after 120 seconds + bs->teamgiveorders_time = FloatTime() + 120; + } + break; + } + case GT_CTF: + { + //if the number of team mates changed or the flag status changed + //or someone wants to know what to do + if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if there were no flag captures the last 3 minutes + if (bs->lastflagcapture_time < FloatTime() - 240) { + bs->lastflagcapture_time = FloatTime(); + //randomly change the CTF strategy + if (random() < 0.4) { + bs->ctfstrategy ^= CTFS_AGRESSIVE; + bs->teamgiveorders_time = FloatTime(); + } + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 3) { + BotCTFOrders(bs); + // + bs->teamgiveorders_time = 0; + } + break; + } +#ifdef MISSIONPACK + case GT_1FCTF: + { + if (bs->numteammates != numteammates || bs->flagstatuschanged || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->flagstatuschanged = qfalse; + bs->forceorders = qfalse; + } + //if there were no flag captures the last 4 minutes + if (bs->lastflagcapture_time < FloatTime() - 240) { + bs->lastflagcapture_time = FloatTime(); + //randomly change the CTF strategy + if (random() < 0.4) { + bs->ctfstrategy ^= CTFS_AGRESSIVE; + bs->teamgiveorders_time = FloatTime(); + } + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 2) { + Bot1FCTFOrders(bs); + // + bs->teamgiveorders_time = 0; + } + break; + } + case GT_OBELISK: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotObeliskOrders(bs); + //give orders again after 30 seconds + bs->teamgiveorders_time = FloatTime() + 30; + } + break; + } + case GT_HARVESTER: + { + if (bs->numteammates != numteammates || bs->forceorders) { + bs->teamgiveorders_time = FloatTime(); + bs->numteammates = numteammates; + bs->forceorders = qfalse; + } + //if it's time to give orders + if (bs->teamgiveorders_time && bs->teamgiveorders_time < FloatTime() - 5) { + BotHarvesterOrders(bs); + //give orders again after 30 seconds + bs->teamgiveorders_time = FloatTime() + 30; + } + break; + } +#endif + } +} + diff --git a/quake3/source/code/game/ai_team.h b/quake3/source/code/game/ai_team.h new file mode 100644 index 0000000..4c3e8f1 --- /dev/null +++ b/quake3/source/code/game/ai_team.h @@ -0,0 +1,19 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_team.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_chat.c $ + * + *****************************************************************************/ + +void BotTeamAI(bot_state_t *bs); +int BotGetTeamMateTaskPreference(bot_state_t *bs, int teammate); +void BotSetTeamMateTaskPreference(bot_state_t *bs, int teammate, int preference); +void BotVoiceChat(bot_state_t *bs, int toclient, char *voicechat); +void BotVoiceChatOnly(bot_state_t *bs, int toclient, char *voicechat); + + diff --git a/quake3/source/code/game/ai_vcmd.c b/quake3/source/code/game/ai_vcmd.c new file mode 100644 index 0000000..48b095e --- /dev/null +++ b/quake3/source/code/game/ai_vcmd.c @@ -0,0 +1,530 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_vcmd.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_vcmd.c $ + * + *****************************************************************************/ + +#include "g_local.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_ea.h" +#include "be_ai_char.h" +#include "be_ai_chat.h" +#include "be_ai_gen.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +// +#include "ai_main.h" +#include "ai_dmq3.h" +#include "ai_chat.h" +#include "ai_cmd.h" +#include "ai_dmnet.h" +#include "ai_team.h" +#include "ai_vcmd.h" +// +#include "chars.h" //characteristics +#include "inv.h" //indexes into the inventory +#include "syn.h" //synonyms +#include "match.h" //string matching types and vars + +// for the voice chats +#include "../../ui/menudef.h" + + +typedef struct voiceCommand_s +{ + char *cmd; + void (*func)(bot_state_t *bs, int client, int mode); +} voiceCommand_t; + +/* +================== +BotVoiceChat_GetFlag +================== +*/ +void BotVoiceChat_GetFlag(bot_state_t *bs, int client, int mode) { + // + if (gametype == GT_CTF) { + if (!ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#ifdef MISSIONPACK + else if (gametype == GT_1FCTF) { + if (!ctf_neutralflag.areanum || !ctf_redflag.areanum || !ctf_blueflag.areanum) + return; + } +#endif + else { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_GETFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_GETFLAG_TIME; + // get an alternate route in ctf + if (gametype == GT_CTF) { + //get an alternative route goal towards the enemy base + BotGetAlternateRouteGoal(bs, BotOppositeTeam(bs)); + } + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Offense +================== +*/ +void BotVoiceChat_Offense(bot_state_t *bs, int client, int mode) { + if ( gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + BotVoiceChat_GetFlag(bs, client, mode); + return; + } +#ifdef MISSIONPACK + if (gametype == GT_HARVESTER) { + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_HARVEST; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_HARVEST_TIME; + bs->harvestaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } + else +#endif + { + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_ATTACKENEMYBASE; + //set the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ATTACKENEMYBASE_TIME; + bs->attackaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); + } +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Defend +================== +*/ +void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode) { +#ifdef MISSIONPACK + if ( gametype == GT_OBELISK || gametype == GT_HARVESTER) { + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(&bs->teamgoal, &redobelisk, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(&bs->teamgoal, &blueobelisk, sizeof(bot_goal_t)); break; + default: return; + } + } + else +#endif + if (gametype == GT_CTF +#ifdef MISSIONPACK + || gametype == GT_1FCTF +#endif + ) { + // + switch(BotTeam(bs)) { + case TEAM_RED: memcpy(&bs->teamgoal, &ctf_redflag, sizeof(bot_goal_t)); break; + case TEAM_BLUE: memcpy(&bs->teamgoal, &ctf_blueflag, sizeof(bot_goal_t)); break; + default: return; + } + } + else { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_DEFENDKEYAREA; + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_DEFENDKEYAREA_TIME; + //away from defending + bs->defendaway_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_DefendFlag +================== +*/ +void BotVoiceChat_DefendFlag(bot_state_t *bs, int client, int mode) { + BotVoiceChat_Defend(bs, client, mode); +} + +/* +================== +BotVoiceChat_Patrol +================== +*/ +void BotVoiceChat_Patrol(bot_state_t *bs, int client, int mode) { + // + bs->decisionmaker = client; + // + bs->ltgtype = 0; + bs->lead_time = 0; + bs->lastgoal_ltgtype = 0; + // + BotAI_BotInitialChat(bs, "dismissed", NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, -1, VOICECHAT_ONPATROL); + // + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_Camp +================== +*/ +void BotVoiceChat_Camp(bot_state_t *bs, int client, int mode) { + int areanum; + aas_entityinfo_t entinfo; + char netname[MAX_NETNAME]; + + // + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && trap_AAS_AreaReachability(areanum)) { + //NOTE: just assume the bot knows where the person is + //if (BotEntityVisible(bs->entitynum, bs->eye, bs->viewangles, 360, client)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + //} + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_CAMPORDER; + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_CAMP_TIME; + //the teammate that requested the camping + bs->teammate = client; + //not arrived yet + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_FollowMe +================== +*/ +void BotVoiceChat_FollowMe(bot_state_t *bs, int client, int mode) { + int areanum; + aas_entityinfo_t entinfo; + char netname[MAX_NETNAME]; + + bs->teamgoal.entitynum = -1; + BotEntityInfo(client, &entinfo); + //if info is valid (in PVS) + if (entinfo.valid) { + areanum = BotPointAreaNum(entinfo.origin); + if (areanum) { // && trap_AAS_AreaReachability(areanum)) { + bs->teamgoal.entitynum = client; + bs->teamgoal.areanum = areanum; + VectorCopy(entinfo.origin, bs->teamgoal.origin); + VectorSet(bs->teamgoal.mins, -8, -8, -8); + VectorSet(bs->teamgoal.maxs, 8, 8, 8); + } + } + //if the other is not visible + if (bs->teamgoal.entitynum < 0) { + BotAI_BotInitialChat(bs, "whereareyou", EasyClientName(client, netname, sizeof(netname)), NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //the team mate + bs->teammate = client; + //last time the team mate was assumed visible + bs->teammatevisible_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //get the team goal time + bs->teamgoal_time = FloatTime() + TEAM_ACCOMPANY_TIME; + //set the ltg type + bs->ltgtype = LTG_TEAMACCOMPANY; + bs->formation_dist = 3.5 * 32; //3.5 meter + bs->arrive_time = 0; + // + BotSetTeamStatus(bs); + // remember last ordered task + BotRememberLastOrderedTask(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_FollowFlagCarrier +================== +*/ +void BotVoiceChat_FollowFlagCarrier(bot_state_t *bs, int client, int mode) { + int carrier; + + carrier = BotTeamFlagCarrier(bs); + if (carrier >= 0) + BotVoiceChat_FollowMe(bs, carrier, mode); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_ReturnFlag +================== +*/ +void BotVoiceChat_ReturnFlag(bot_state_t *bs, int client, int mode) { + //if not in CTF mode + if ( + gametype != GT_CTF +#ifdef MISSIONPACK + && gametype != GT_1FCTF +#endif + ) { + return; + } + // + bs->decisionmaker = client; + bs->ordered = qtrue; + bs->order_time = FloatTime(); + //set the time to send a message to the team mates + bs->teammessage_time = FloatTime() + 2 * random(); + //set the ltg type + bs->ltgtype = LTG_RETURNFLAG; + //set the team goal time + bs->teamgoal_time = FloatTime() + CTF_RETURNFLAG_TIME; + bs->rushbaseaway_time = 0; + BotSetTeamStatus(bs); +#ifdef DEBUG + BotPrintTeamGoal(bs); +#endif //DEBUG +} + +/* +================== +BotVoiceChat_StartLeader +================== +*/ +void BotVoiceChat_StartLeader(bot_state_t *bs, int client, int mode) { + ClientName(client, bs->teamleader, sizeof(bs->teamleader)); +} + +/* +================== +BotVoiceChat_StopLeader +================== +*/ +void BotVoiceChat_StopLeader(bot_state_t *bs, int client, int mode) { + char netname[MAX_MESSAGE_SIZE]; + + if (!Q_stricmp(bs->teamleader, ClientName(client, netname, sizeof(netname)))) { + bs->teamleader[0] = '\0'; + notleader[client] = qtrue; + } +} + +/* +================== +BotVoiceChat_WhoIsLeader +================== +*/ +void BotVoiceChat_WhoIsLeader(bot_state_t *bs, int client, int mode) { + char netname[MAX_MESSAGE_SIZE]; + + if (!TeamPlayIsOn()) return; + + ClientName(bs->client, netname, sizeof(netname)); + //if this bot IS the team leader + if (!Q_stricmp(netname, bs->teamleader)) { + BotAI_BotInitialChat(bs, "iamteamleader", NULL); + trap_BotEnterChat(bs->cs, 0, CHAT_TEAM); + BotVoiceChatOnly(bs, -1, VOICECHAT_STARTLEADER); + } +} + +/* +================== +BotVoiceChat_WantOnDefense +================== +*/ +void BotVoiceChat_WantOnDefense(bot_state_t *bs, int client, int mode) { + char netname[MAX_NETNAME]; + int preference; + + preference = BotGetTeamMateTaskPreference(bs, client); + preference &= ~TEAMTP_ATTACKER; + preference |= TEAMTP_DEFENDER; + BotSetTeamMateTaskPreference(bs, client, preference); + // + EasyClientName(client, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, client, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +/* +================== +BotVoiceChat_WantOnOffense +================== +*/ +void BotVoiceChat_WantOnOffense(bot_state_t *bs, int client, int mode) { + char netname[MAX_NETNAME]; + int preference; + + preference = BotGetTeamMateTaskPreference(bs, client); + preference &= ~TEAMTP_DEFENDER; + preference |= TEAMTP_ATTACKER; + BotSetTeamMateTaskPreference(bs, client, preference); + // + EasyClientName(client, netname, sizeof(netname)); + BotAI_BotInitialChat(bs, "keepinmind", netname, NULL); + trap_BotEnterChat(bs->cs, client, CHAT_TELL); + BotVoiceChatOnly(bs, client, VOICECHAT_YES); + trap_EA_Action(bs->client, ACTION_AFFIRMATIVE); +} + +void BotVoiceChat_Dummy(bot_state_t *bs, int client, int mode) { +} + +voiceCommand_t voiceCommands[] = { + {VOICECHAT_GETFLAG, BotVoiceChat_GetFlag}, + {VOICECHAT_OFFENSE, BotVoiceChat_Offense }, + {VOICECHAT_DEFEND, BotVoiceChat_Defend }, + {VOICECHAT_DEFENDFLAG, BotVoiceChat_DefendFlag }, + {VOICECHAT_PATROL, BotVoiceChat_Patrol }, + {VOICECHAT_CAMP, BotVoiceChat_Camp }, + {VOICECHAT_FOLLOWME, BotVoiceChat_FollowMe }, + {VOICECHAT_FOLLOWFLAGCARRIER, BotVoiceChat_FollowFlagCarrier }, + {VOICECHAT_RETURNFLAG, BotVoiceChat_ReturnFlag }, + {VOICECHAT_STARTLEADER, BotVoiceChat_StartLeader }, + {VOICECHAT_STOPLEADER, BotVoiceChat_StopLeader }, + {VOICECHAT_WHOISLEADER, BotVoiceChat_WhoIsLeader }, + {VOICECHAT_WANTONDEFENSE, BotVoiceChat_WantOnDefense }, + {VOICECHAT_WANTONOFFENSE, BotVoiceChat_WantOnOffense }, + {NULL, BotVoiceChat_Dummy} +}; + +int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voiceChat) { + int i, voiceOnly, clientNum, color; + char *ptr, buf[MAX_MESSAGE_SIZE], *cmd; + + if (!TeamPlayIsOn()) { + return qfalse; + } + + if ( mode == SAY_ALL ) { + return qfalse; // don't do anything with voice chats to everyone + } + + Q_strncpyz(buf, voiceChat, sizeof(buf)); + cmd = buf; + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + while (*cmd && *cmd <= ' ') *cmd++ = '\0'; + voiceOnly = atoi(ptr); + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + while (*cmd && *cmd <= ' ') *cmd++ = '\0'; + clientNum = atoi(ptr); + for (ptr = cmd; *cmd && *cmd > ' '; cmd++); + while (*cmd && *cmd <= ' ') *cmd++ = '\0'; + color = atoi(ptr); + + if (!BotSameTeam(bs, clientNum)) { + return qfalse; + } + + for (i = 0; voiceCommands[i].cmd; i++) { + if (!Q_stricmp(cmd, voiceCommands[i].cmd)) { + voiceCommands[i].func(bs, clientNum, mode); + return qtrue; + } + } + return qfalse; +} diff --git a/quake3/source/code/game/ai_vcmd.h b/quake3/source/code/game/ai_vcmd.h new file mode 100644 index 0000000..e392a80 --- /dev/null +++ b/quake3/source/code/game/ai_vcmd.h @@ -0,0 +1,16 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_vcmd.h + * + * desc: Quake3 bot AI + * + * $Archive: /source/code/botai/ai_vcmd.c $ + * + *****************************************************************************/ + +int BotVoiceChatCommand(bot_state_t *bs, int mode, char *voicechat); +void BotVoiceChat_Defend(bot_state_t *bs, int client, int mode); + + diff --git a/quake3/source/code/game/be_aas.h b/quake3/source/code/game/be_aas.h new file mode 100644 index 0000000..4676588 --- /dev/null +++ b/quake3/source/code/game/be_aas.h @@ -0,0 +1,201 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_aas.h + * + * desc: Area Awareness System, stuff exported to the AI + * + * $Archive: /source/code/botlib/be_aas.h $ + * + *****************************************************************************/ + +#ifndef MAX_STRINGFIELD +#define MAX_STRINGFIELD 80 +#endif + +//travel flags +#define TFL_INVALID 0x00000001 //traveling temporary not possible +#define TFL_WALK 0x00000002 //walking +#define TFL_CROUCH 0x00000004 //crouching +#define TFL_BARRIERJUMP 0x00000008 //jumping onto a barrier +#define TFL_JUMP 0x00000010 //jumping +#define TFL_LADDER 0x00000020 //climbing a ladder +#define TFL_WALKOFFLEDGE 0x00000080 //walking of a ledge +#define TFL_SWIM 0x00000100 //swimming +#define TFL_WATERJUMP 0x00000200 //jumping out of the water +#define TFL_TELEPORT 0x00000400 //teleporting +#define TFL_ELEVATOR 0x00000800 //elevator +#define TFL_ROCKETJUMP 0x00001000 //rocket jumping +#define TFL_BFGJUMP 0x00002000 //bfg jumping +#define TFL_GRAPPLEHOOK 0x00004000 //grappling hook +#define TFL_DOUBLEJUMP 0x00008000 //double jump +#define TFL_RAMPJUMP 0x00010000 //ramp jump +#define TFL_STRAFEJUMP 0x00020000 //strafe jump +#define TFL_JUMPPAD 0x00040000 //jump pad +#define TFL_AIR 0x00080000 //travel through air +#define TFL_WATER 0x00100000 //travel through water +#define TFL_SLIME 0x00200000 //travel through slime +#define TFL_LAVA 0x00400000 //travel through lava +#define TFL_DONOTENTER 0x00800000 //travel through donotenter area +#define TFL_FUNCBOB 0x01000000 //func bobbing +#define TFL_FLIGHT 0x02000000 //flight +#define TFL_BRIDGE 0x04000000 //move over a bridge +// +#define TFL_NOTTEAM1 0x08000000 //not team 1 +#define TFL_NOTTEAM2 0x10000000 //not team 2 + +//default travel flags +#define TFL_DEFAULT TFL_WALK|TFL_CROUCH|TFL_BARRIERJUMP|\ + TFL_JUMP|TFL_LADDER|\ + TFL_WALKOFFLEDGE|TFL_SWIM|TFL_WATERJUMP|\ + TFL_TELEPORT|TFL_ELEVATOR|\ + TFL_AIR|TFL_WATER|TFL_JUMPPAD|TFL_FUNCBOB + +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; + +//a trace is returned when a box is swept through the AAS world +typedef struct aas_trace_s +{ + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + int ent; // entity blocking the trace + int lastarea; // last area the trace was in (zero if none) + int area; // area blocking the trace (zero if none) + int planenum; // number of the plane that was hit +} aas_trace_t; + +/* Defined in botlib.h + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//a trace is returned when a box is swept through the BSP world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // hit surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; +// +*/ + +//entity info +typedef struct aas_entityinfo_s +{ + int valid; // true if updated this frame + int type; // entity type + int flags; // entity flags + float ltime; // local time + float update_time; // time between last and current update + int number; // number of the entity + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t lastvisorigin; // last visible origin + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} aas_entityinfo_t; + +// area info +typedef struct aas_areainfo_s +{ + int contents; + int flags; + int presencetype; + int cluster; + vec3_t mins; + vec3_t maxs; + vec3_t center; +} aas_areainfo_t; + +// client movement prediction stop events, stop as soon as: +#define SE_NONE 0 +#define SE_HITGROUND 1 // the ground is hit +#define SE_LEAVEGROUND 2 // there's no ground +#define SE_ENTERWATER 4 // water is entered +#define SE_ENTERSLIME 8 // slime is entered +#define SE_ENTERLAVA 16 // lava is entered +#define SE_HITGROUNDDAMAGE 32 // the ground is hit with damage +#define SE_GAP 64 // there's a gap +#define SE_TOUCHJUMPPAD 128 // touching a jump pad area +#define SE_TOUCHTELEPORTER 256 // touching teleporter +#define SE_ENTERAREA 512 // the given stoparea is entered +#define SE_HITGROUNDAREA 1024 // a ground face in the area is hit +#define SE_HITBOUNDINGBOX 2048 // hit the specified bounding box +#define SE_TOUCHCLUSTERPORTAL 4096 // touching a cluster portal + +typedef struct aas_clientmove_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + vec3_t velocity; //velocity at the end of movement prediction + aas_trace_t trace; //last trace + int presencetype; //presence type at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + float time; //time predicted ahead + int frames; //number of frames predicted ahead +} aas_clientmove_t; + +// alternate route goals +#define ALTROUTEGOAL_ALL 1 +#define ALTROUTEGOAL_CLUSTERPORTALS 2 +#define ALTROUTEGOAL_VIEWPORTALS 4 + +typedef struct aas_altroutegoal_s +{ + vec3_t origin; + int areanum; + unsigned short starttraveltime; + unsigned short goaltraveltime; + unsigned short extratraveltime; +} aas_altroutegoal_t; + +// route prediction stop events +#define RSE_NONE 0 +#define RSE_NOROUTE 1 //no route to goal +#define RSE_USETRAVELTYPE 2 //stop as soon as on of the given travel types is used +#define RSE_ENTERCONTENTS 4 //stop when entering the given contents +#define RSE_ENTERAREA 8 //stop when entering the given area + +typedef struct aas_predictroute_s +{ + vec3_t endpos; //position at the end of movement prediction + int endarea; //area at end of movement prediction + int stopevent; //event that made the prediction stop + int endcontents; //contents at the end of movement prediction + int endtravelflags; //end travel flags + int numareas; //number of areas predicted ahead + int time; //time predicted ahead (in hundreth of a sec) +} aas_predictroute_t; diff --git a/quake3/source/code/game/be_ai_char.h b/quake3/source/code/game/be_ai_char.h new file mode 100644 index 0000000..693c0bb --- /dev/null +++ b/quake3/source/code/game/be_ai_char.h @@ -0,0 +1,28 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_char.h + * + * desc: bot characters + * + * $Archive: /source/code/botlib/be_ai_char.h $ + * + *****************************************************************************/ + +//loads a bot character from a file +int BotLoadCharacter(char *charfile, float skill); +//frees a bot character +void BotFreeCharacter(int character); +//returns a float characteristic +float Characteristic_Float(int character, int index); +//returns a bounded float characteristic +float Characteristic_BFloat(int character, int index, float min, float max); +//returns an integer characteristic +int Characteristic_Integer(int character, int index); +//returns a bounded integer characteristic +int Characteristic_BInteger(int character, int index, int min, int max); +//returns a string characteristic +void Characteristic_String(int character, int index, char *buf, int size); +//free cached bot characters +void BotShutdownCharacters(void); diff --git a/quake3/source/code/game/be_ai_chat.h b/quake3/source/code/game/be_ai_chat.h new file mode 100644 index 0000000..f5231ad --- /dev/null +++ b/quake3/source/code/game/be_ai_chat.h @@ -0,0 +1,93 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: be_ai_chat.h + * + * desc: char AI + * + * $Archive: /source/code/botlib/be_ai_chat.h $ + * + *****************************************************************************/ + +#define MAX_MESSAGE_SIZE 256 +#define MAX_CHATTYPE_NAME 32 +#define MAX_MATCHVARIABLES 8 + +#define CHAT_GENDERLESS 0 +#define CHAT_GENDERFEMALE 1 +#define CHAT_GENDERMALE 2 + +#define CHAT_ALL 0 +#define CHAT_TEAM 1 +#define CHAT_TELL 2 + +//a console message +typedef struct bot_consolemessage_s +{ + int handle; + float time; //message time + int type; //message type + char message[MAX_MESSAGE_SIZE]; //message + struct bot_consolemessage_s *prev, *next; //prev and next in list +} bot_consolemessage_t; + +//match variable +typedef struct bot_matchvariable_s +{ + char offset; + int length; +} bot_matchvariable_t; +//returned to AI when a match is found +typedef struct bot_match_s +{ + char string[MAX_MESSAGE_SIZE]; + int type; + int subtype; + bot_matchvariable_t variables[MAX_MATCHVARIABLES]; +} bot_match_t; + +//setup the chat AI +int BotSetupChatAI(void); +//shutdown the chat AI +void BotShutdownChatAI(void); +//returns the handle to a newly allocated chat state +int BotAllocChatState(void); +//frees the chatstate +void BotFreeChatState(int handle); +//adds a console message to the chat state +void BotQueueConsoleMessage(int chatstate, int type, char *message); +//removes the console message from the chat state +void BotRemoveConsoleMessage(int chatstate, int handle); +//returns the next console message from the state +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm); +//returns the number of console messages currently stored in the state +int BotNumConsoleMessages(int chatstate); +//selects a chat message of the given type +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the number of initial chat messages of the given type +int BotNumInitialChats(int chatstate, char *type); +//find and select a reply for the given message +int BotReplyChat(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); +//returns the length of the currently selected chat message +int BotChatLength(int chatstate); +//enters the selected chat message +void BotEnterChat(int chatstate, int clientto, int sendto); +//get the chat message ready to be output +void BotGetChatMessage(int chatstate, char *buf, int size); +//checks if the first string contains the second one, returns index into first string or -1 if not found +int StringContains(char *str1, char *str2, int casesensitive); +//finds a match for the given string using the match templates +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context); +//returns a variable from a match +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size); +//unify all the white spaces in the string +void UnifyWhiteSpaces(char *string); +//replace all the context related synonyms in the string +void BotReplaceSynonyms(char *string, unsigned long int context); +//loads a chat file for the chat state +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname); +//store the gender of the bot in the chat state +void BotSetChatGender(int chatstate, int gender); +//store the bot name in the chat state +void BotSetChatName(int chatstate, char *name, int client); + diff --git a/quake3/source/code/game/be_ai_gen.h b/quake3/source/code/game/be_ai_gen.h new file mode 100644 index 0000000..d7791ad --- /dev/null +++ b/quake3/source/code/game/be_ai_gen.h @@ -0,0 +1,13 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_gen.h + * + * desc: genetic selection + * + * $Archive: /source/code/botlib/be_ai_gen.h $ + * + *****************************************************************************/ + +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); diff --git a/quake3/source/code/game/be_ai_goal.h b/quake3/source/code/game/be_ai_goal.h new file mode 100644 index 0000000..2af416a --- /dev/null +++ b/quake3/source/code/game/be_ai_goal.h @@ -0,0 +1,98 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: be_ai_goal.h + * + * desc: goal AI + * + * $Archive: /source/code/botlib/be_ai_goal.h $ + * + *****************************************************************************/ + +#define MAX_AVOIDGOALS 256 +#define MAX_GOALSTACK 8 + +#define GFL_NONE 0 +#define GFL_ITEM 1 +#define GFL_ROAM 2 +#define GFL_DROPPED 4 + +//a bot goal +typedef struct bot_goal_s +{ + vec3_t origin; //origin of the goal + int areanum; //area number of the goal + vec3_t mins, maxs; //mins and maxs of the goal + int entitynum; //number of the goal entity + int number; //goal number + int flags; //goal flags + int iteminfo; //item information +} bot_goal_t; + +//reset the whole goal state, but keep the item weights +void BotResetGoalState(int goalstate); +//reset avoid goals +void BotResetAvoidGoals(int goalstate); +//remove the goal with the given number from the avoid goals +void BotRemoveFromAvoidGoals(int goalstate, int number); +//push a goal onto the goal stack +void BotPushGoal(int goalstate, bot_goal_t *goal); +//pop a goal from the goal stack +void BotPopGoal(int goalstate); +//empty the bot's goal stack +void BotEmptyGoalStack(int goalstate); +//dump the avoid goals +void BotDumpAvoidGoals(int goalstate); +//dump the goal stack +void BotDumpGoalStack(int goalstate); +//get the name name of the goal with the given number +void BotGoalName(int number, char *name, int size); +//get the top goal from the stack +int BotGetTopGoal(int goalstate, bot_goal_t *goal); +//get the second goal on the stack +int BotGetSecondGoal(int goalstate, bot_goal_t *goal); +//choose the best long term goal item for the bot +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags); +//choose the best nearby goal item for the bot +//the item may not be further away from the current bot position than maxtime +//also the travel time from the nearby goal towards the long term goal may not +//be larger than the travel time towards the long term goal from the current bot position +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime); +//returns true if the bot touches the goal +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal); +//returns true if the goal should be visible but isn't +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal); +//search for a goal for the given classname, the index can be used +//as a start point for the search when multiple goals are available with that same classname +int BotGetLevelItemGoal(int index, char *classname, bot_goal_t *goal); +//get the next camp spot in the map +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal); +//get the map location with the given name +int BotGetMapLocationGoal(char *name, bot_goal_t *goal); +//returns the avoid goal time +float BotAvoidGoalTime(int goalstate, int number); +//set the avoid goal time +void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime); +//initializes the items in the level +void BotInitLevelItems(void); +//regularly update dynamic entity items (dropped weapons, flags etc.) +void BotUpdateEntityItems(void); +//interbreed the goal fuzzy logic +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child); +//save the goal fuzzy logic to disk +void BotSaveGoalFuzzyLogic(int goalstate, char *filename); +//mutate the goal fuzzy logic +void BotMutateGoalFuzzyLogic(int goalstate, float range); +//loads item weights for the bot +int BotLoadItemWeights(int goalstate, char *filename); +//frees the item weights of the bot +void BotFreeItemWeights(int goalstate); +//returns the handle of a newly allocated goal state +int BotAllocGoalState(int client); +//free the given goal state +void BotFreeGoalState(int handle); +//setup the goal AI +int BotSetupGoalAI(void); +//shut down the goal AI +void BotShutdownGoalAI(void); diff --git a/quake3/source/code/game/be_ai_move.h b/quake3/source/code/game/be_ai_move.h new file mode 100644 index 0000000..7d14ac8 --- /dev/null +++ b/quake3/source/code/game/be_ai_move.h @@ -0,0 +1,122 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_move.h + * + * desc: movement AI + * + * $Archive: /source/code/botlib/be_ai_move.h $ + * + *****************************************************************************/ + +//movement types +#define MOVE_WALK 1 +#define MOVE_CROUCH 2 +#define MOVE_JUMP 4 +#define MOVE_GRAPPLE 8 +#define MOVE_ROCKETJUMP 16 +#define MOVE_BFGJUMP 32 +//move flags +#define MFL_BARRIERJUMP 1 //bot is performing a barrier jump +#define MFL_ONGROUND 2 //bot is in the ground +#define MFL_SWIMMING 4 //bot is swimming +#define MFL_AGAINSTLADDER 8 //bot is against a ladder +#define MFL_WATERJUMP 16 //bot is waterjumping +#define MFL_TELEPORTED 32 //bot is being teleported +#define MFL_GRAPPLEPULL 64 //bot is being pulled by the grapple +#define MFL_ACTIVEGRAPPLE 128 //bot is using the grapple hook +#define MFL_GRAPPLERESET 256 //bot has reset the grapple +#define MFL_WALK 512 //bot should walk slowly +// move result flags +#define MOVERESULT_MOVEMENTVIEW 1 //bot uses view for movement +#define MOVERESULT_SWIMVIEW 2 //bot uses view for swimming +#define MOVERESULT_WAITING 4 //bot is waiting for something +#define MOVERESULT_MOVEMENTVIEWSET 8 //bot has set the view in movement code +#define MOVERESULT_MOVEMENTWEAPON 16 //bot uses weapon for movement +#define MOVERESULT_ONTOPOFOBSTACLE 32 //bot is ontop of obstacle +#define MOVERESULT_ONTOPOF_FUNCBOB 64 //bot is ontop of a func_bobbing +#define MOVERESULT_ONTOPOF_ELEVATOR 128 //bot is ontop of an elevator (func_plat) +#define MOVERESULT_BLOCKEDBYAVOIDSPOT 256 //bot is blocked by an avoid spot +// +#define MAX_AVOIDREACH 1 +#define MAX_AVOIDSPOTS 32 +// avoid spot types +#define AVOID_CLEAR 0 //clear all avoid spots +#define AVOID_ALWAYS 1 //avoid always +#define AVOID_DONTBLOCK 2 //never totally block +// restult types +#define RESULTTYPE_ELEVATORUP 1 //elevator is up +#define RESULTTYPE_WAITFORFUNCBOBBING 2 //waiting for func bobbing to arrive +#define RESULTTYPE_BADGRAPPLEPATH 4 //grapple path is obstructed +#define RESULTTYPE_INSOLIDAREA 8 //stuck in solid area, this is bad + +//structure used to initialize the movement state +//the or_moveflags MFL_ONGROUND, MFL_TELEPORTED and MFL_WATERJUMP come from the playerstate +typedef struct bot_initmove_s +{ + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t viewoffset; //view offset + int entitynum; //entity number of the bot + int client; //client number of the bot + float thinktime; //time the bot thinks + int presencetype; //presencetype of the bot + vec3_t viewangles; //view angles of the bot + int or_moveflags; //values ored to the movement flags +} bot_initmove_t; + +//NOTE: the ideal_viewangles are only valid if MFL_MOVEMENTVIEW is set +typedef struct bot_moveresult_s +{ + int failure; //true if movement failed all together + int type; //failure or blocked type + int blocked; //true if blocked by an entity + int blockentity; //entity blocking the bot + int traveltype; //last executed travel type + int flags; //result flags + int weapon; //weapon used for movement + vec3_t movedir; //movement direction + vec3_t ideal_viewangles; //ideal viewangles for the movement +} bot_moveresult_t; + +// bk001204: from code/botlib/be_ai_move.c +// TTimo 04/12/2001 was moved here to avoid dup defines +typedef struct bot_avoidspot_s +{ + vec3_t origin; + float radius; + int type; +} bot_avoidspot_t; + +//resets the whole move state +void BotResetMoveState(int movestate); +//moves the bot to the given goal +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags); +//moves the bot in the specified direction using the specified type of movement +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type); +//reset avoid reachability +void BotResetAvoidReach(int movestate); +//resets the last avoid reachability +void BotResetLastAvoidReach(int movestate); +//returns a reachability area if the origin is in one +int BotReachabilityArea(vec3_t origin, int client); +//view target based on movement +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target); +//predict the position of a player based on movement towards a goal +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target); +//returns the handle of a newly allocated movestate +int BotAllocMoveState(void); +//frees the movestate with the given handle +void BotFreeMoveState(int handle); +//initialize movement state before performing any movement +void BotInitMoveState(int handle, bot_initmove_t *initmove); +//add a spot to avoid (if type == AVOID_CLEAR all spots are removed) +void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type); +//must be called every map change +void BotSetBrushModelTypes(void); +//setup movement AI +int BotSetupMoveAI(void); +//shutdown movement AI +void BotShutdownMoveAI(void); + diff --git a/quake3/source/code/game/be_ai_weap.h b/quake3/source/code/game/be_ai_weap.h new file mode 100644 index 0000000..bd2cc33 --- /dev/null +++ b/quake3/source/code/game/be_ai_weap.h @@ -0,0 +1,84 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_weap.h + * + * desc: weapon AI + * + * $Archive: /source/code/botlib/be_ai_weap.h $ + * + *****************************************************************************/ + +//projectile flags +#define PFL_WINDOWDAMAGE 1 //projectile damages through window +#define PFL_RETURN 2 //set when projectile returns to owner +//weapon flags +#define WFL_FIRERELEASED 1 //set when projectile is fired with key-up event +//damage types +#define DAMAGETYPE_IMPACT 1 //damage on impact +#define DAMAGETYPE_RADIAL 2 //radial damage +#define DAMAGETYPE_VISIBLE 4 //damage to all entities visible to the projectile + +typedef struct projectileinfo_s +{ + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int flags; + float gravity; + int damage; + float radius; + int visdamage; + int damagetype; + int healthinc; + float push; + float detonation; + float bounce; + float bouncefric; + float bouncestop; +} projectileinfo_t; + +typedef struct weaponinfo_s +{ + int valid; //true if the weapon info is valid + int number; //number of the weapon + char name[MAX_STRINGFIELD]; + char model[MAX_STRINGFIELD]; + int level; + int weaponindex; + int flags; + char projectile[MAX_STRINGFIELD]; + int numprojectiles; + float hspread; + float vspread; + float speed; + float acceleration; + vec3_t recoil; + vec3_t offset; + vec3_t angleoffset; + float extrazvelocity; + int ammoamount; + int ammoindex; + float activate; + float reload; + float spinup; + float spindown; + projectileinfo_t proj; //pointer to the used projectile +} weaponinfo_t; + +//setup the weapon AI +int BotSetupWeaponAI(void); +//shut down the weapon AI +void BotShutdownWeaponAI(void); +//returns the best weapon to fight with +int BotChooseBestFightWeapon(int weaponstate, int *inventory); +//returns the information of the current weapon +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo); +//loads the weapon weights +int BotLoadWeaponWeights(int weaponstate, char *filename); +//returns a handle to a newly allocated weapon state +int BotAllocWeaponState(void); +//frees the weapon state +void BotFreeWeaponState(int weaponstate); +//resets the whole weapon state +void BotResetWeaponState(int weaponstate); diff --git a/quake3/source/code/game/be_ea.h b/quake3/source/code/game/be_ea.h new file mode 100644 index 0000000..a9e0319 --- /dev/null +++ b/quake3/source/code/game/be_ea.h @@ -0,0 +1,46 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ea.h + * + * desc: elementary actions + * + * $Archive: /source/code/botlib/be_ea.h $ + * + *****************************************************************************/ + +//ClientCommand elementary actions +void EA_Say(int client, char *str); +void EA_SayTeam(int client, char *str); +void EA_Command(int client, char *command ); + +void EA_Action(int client, int action); +void EA_Crouch(int client); +void EA_Walk(int client); +void EA_MoveUp(int client); +void EA_MoveDown(int client); +void EA_MoveForward(int client); +void EA_MoveBack(int client); +void EA_MoveLeft(int client); +void EA_MoveRight(int client); +void EA_Attack(int client); +void EA_Respawn(int client); +void EA_Talk(int client); +void EA_Gesture(int client); +void EA_Use(int client); + +//regular elementary actions +void EA_SelectWeapon(int client, int weapon); +void EA_Jump(int client); +void EA_DelayedJump(int client); +void EA_Move(int client, vec3_t dir, float speed); +void EA_View(int client, vec3_t viewangles); + +//send regular input to the server +void EA_EndRegular(int client, float thinktime); +void EA_GetInput(int client, float thinktime, bot_input_t *input); +void EA_ResetInput(int client); +//setup and shutdown routines +int EA_Setup(void); +void EA_Shutdown(void); diff --git a/quake3/source/code/game/bg_lib.c b/quake3/source/code/game/bg_lib.c new file mode 100644 index 0000000..8a3853d --- /dev/null +++ b/quake3/source/code/game/bg_lib.c @@ -0,0 +1,1327 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_lib,c -- standard C library replacement routines used by code +// compiled for the virtual machine + +#include "q_shared.h" + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +#endif +static const char rcsid[] = +#endif /* LIBC_SCCS and not lint */ + +// bk001127 - needed for DLL's +#if !defined( Q3_VM ) +typedef int cmp_t(const void *, const void *); +#endif + +static char* med3(char *, char *, char *, cmp_t *); +static void swapfunc(char *, char *, int, int); + +#ifndef min +#define min(a, b) (a) < (b) ? a : b +#endif + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + long i = (n) / sizeof (TYPE); \ + register TYPE *pi = (TYPE *) (parmi); \ + register TYPE *pj = (TYPE *) (parmj); \ + do { \ + register TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static void +swapfunc(a, b, n, swaptype) + char *a, *b; + int n, swaptype; +{ + if(swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static char * +med3(a, b, c, cmp) + char *a, *b, *c; + cmp_t *cmp; +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +void +qsort(a, n, es, cmp) + void *a; + size_t n, es; + cmp_t *cmp; +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype, swap_cnt; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (r = cmp(pb, a)) <= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a)) >= 0) { + if (r == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; pl > (char *)a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ +} + +//================================================================================== + + +// this file is excluded from release builds because of intrinsics + +// bk001211 - gcc errors on compiling strcpy: parse error before `__extension__' +#if defined ( Q3_VM ) + +size_t strlen( const char *string ) { + const char *s; + + s = string; + while ( *s ) { + s++; + } + return s - string; +} + + +char *strcat( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *s ) { + s++; + } + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + +char *strcpy( char *strDestination, const char *strSource ) { + char *s; + + s = strDestination; + while ( *strSource ) { + *s++ = *strSource++; + } + *s = 0; + return strDestination; +} + + +int strcmp( const char *string1, const char *string2 ) { + while ( *string1 == *string2 && *string1 && *string2 ) { + string1++; + string2++; + } + return *string1 - *string2; +} + + +char *strchr( const char *string, int c ) { + while ( *string ) { + if ( *string == c ) { + return ( char * )string; + } + string++; + } + return (char *)0; +} + +char *strstr( const char *string, const char *strCharSet ) { + while ( *string ) { + int i; + + for ( i = 0 ; strCharSet[i] ; i++ ) { + if ( string[i] != strCharSet[i] ) { + break; + } + } + if ( !strCharSet[i] ) { + return (char *)string; + } + string++; + } + return (char *)0; +} +#endif // bk001211 + +// bk001120 - presumably needed for Mac +//#if !defined(_MSC_VER) && !defined(__linux__) +// bk001127 - undid undo +#if defined ( Q3_VM ) +int tolower( int c ) { + if ( c >= 'A' && c <= 'Z' ) { + c += 'a' - 'A'; + } + return c; +} + + +int toupper( int c ) { + if ( c >= 'a' && c <= 'z' ) { + c += 'A' - 'a'; + } + return c; +} + +#endif +//#ifndef _MSC_VER + +void *memmove( void *dest, const void *src, size_t count ) { + int i; + + if ( dest > src ) { + for ( i = count-1 ; i >= 0 ; i-- ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } else { + for ( i = 0 ; i < count ; i++ ) { + ((char *)dest)[i] = ((char *)src)[i]; + } + } + return dest; +} + + +#if 0 + +double floor( double x ) { + return (int)(x + 0x40000000) - 0x40000000; +} + +void *memset( void *dest, int c, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = c; + } + return dest; +} + +void *memcpy( void *dest, const void *src, size_t count ) { + while ( count-- ) { + ((char *)dest)[count] = ((char *)src)[count]; + } + return dest; +} + +char *strncpy( char *strDest, const char *strSource, size_t count ) { + char *s; + + s = strDest; + while ( *strSource && count ) { + *s++ = *strSource++; + count--; + } + while ( count-- ) { + *s++ = 0; + } + return strDest; +} + +double sqrt( double x ) { + float y; + float delta; + float maxError; + + if ( x <= 0 ) { + return 0; + } + + // initial guess + y = x / 2; + + // refine + maxError = x * 0.001; + + do { + delta = ( y * y ) - x; + y -= delta / ( 2 * y ); + } while ( delta > maxError || delta < -maxError ); + + return y; +} + + +float sintable[1024] = { +0.000000,0.001534,0.003068,0.004602,0.006136,0.007670,0.009204,0.010738, +0.012272,0.013805,0.015339,0.016873,0.018407,0.019940,0.021474,0.023008, +0.024541,0.026075,0.027608,0.029142,0.030675,0.032208,0.033741,0.035274, +0.036807,0.038340,0.039873,0.041406,0.042938,0.044471,0.046003,0.047535, +0.049068,0.050600,0.052132,0.053664,0.055195,0.056727,0.058258,0.059790, +0.061321,0.062852,0.064383,0.065913,0.067444,0.068974,0.070505,0.072035, +0.073565,0.075094,0.076624,0.078153,0.079682,0.081211,0.082740,0.084269, +0.085797,0.087326,0.088854,0.090381,0.091909,0.093436,0.094963,0.096490, +0.098017,0.099544,0.101070,0.102596,0.104122,0.105647,0.107172,0.108697, +0.110222,0.111747,0.113271,0.114795,0.116319,0.117842,0.119365,0.120888, +0.122411,0.123933,0.125455,0.126977,0.128498,0.130019,0.131540,0.133061, +0.134581,0.136101,0.137620,0.139139,0.140658,0.142177,0.143695,0.145213, +0.146730,0.148248,0.149765,0.151281,0.152797,0.154313,0.155828,0.157343, +0.158858,0.160372,0.161886,0.163400,0.164913,0.166426,0.167938,0.169450, +0.170962,0.172473,0.173984,0.175494,0.177004,0.178514,0.180023,0.181532, +0.183040,0.184548,0.186055,0.187562,0.189069,0.190575,0.192080,0.193586, +0.195090,0.196595,0.198098,0.199602,0.201105,0.202607,0.204109,0.205610, +0.207111,0.208612,0.210112,0.211611,0.213110,0.214609,0.216107,0.217604, +0.219101,0.220598,0.222094,0.223589,0.225084,0.226578,0.228072,0.229565, +0.231058,0.232550,0.234042,0.235533,0.237024,0.238514,0.240003,0.241492, +0.242980,0.244468,0.245955,0.247442,0.248928,0.250413,0.251898,0.253382, +0.254866,0.256349,0.257831,0.259313,0.260794,0.262275,0.263755,0.265234, +0.266713,0.268191,0.269668,0.271145,0.272621,0.274097,0.275572,0.277046, +0.278520,0.279993,0.281465,0.282937,0.284408,0.285878,0.287347,0.288816, +0.290285,0.291752,0.293219,0.294685,0.296151,0.297616,0.299080,0.300543, +0.302006,0.303468,0.304929,0.306390,0.307850,0.309309,0.310767,0.312225, +0.313682,0.315138,0.316593,0.318048,0.319502,0.320955,0.322408,0.323859, +0.325310,0.326760,0.328210,0.329658,0.331106,0.332553,0.334000,0.335445, +0.336890,0.338334,0.339777,0.341219,0.342661,0.344101,0.345541,0.346980, +0.348419,0.349856,0.351293,0.352729,0.354164,0.355598,0.357031,0.358463, +0.359895,0.361326,0.362756,0.364185,0.365613,0.367040,0.368467,0.369892, +0.371317,0.372741,0.374164,0.375586,0.377007,0.378428,0.379847,0.381266, +0.382683,0.384100,0.385516,0.386931,0.388345,0.389758,0.391170,0.392582, +0.393992,0.395401,0.396810,0.398218,0.399624,0.401030,0.402435,0.403838, +0.405241,0.406643,0.408044,0.409444,0.410843,0.412241,0.413638,0.415034, +0.416430,0.417824,0.419217,0.420609,0.422000,0.423390,0.424780,0.426168, +0.427555,0.428941,0.430326,0.431711,0.433094,0.434476,0.435857,0.437237, +0.438616,0.439994,0.441371,0.442747,0.444122,0.445496,0.446869,0.448241, +0.449611,0.450981,0.452350,0.453717,0.455084,0.456449,0.457813,0.459177, +0.460539,0.461900,0.463260,0.464619,0.465976,0.467333,0.468689,0.470043, +0.471397,0.472749,0.474100,0.475450,0.476799,0.478147,0.479494,0.480839, +0.482184,0.483527,0.484869,0.486210,0.487550,0.488889,0.490226,0.491563, +0.492898,0.494232,0.495565,0.496897,0.498228,0.499557,0.500885,0.502212, +0.503538,0.504863,0.506187,0.507509,0.508830,0.510150,0.511469,0.512786, +0.514103,0.515418,0.516732,0.518045,0.519356,0.520666,0.521975,0.523283, +0.524590,0.525895,0.527199,0.528502,0.529804,0.531104,0.532403,0.533701, +0.534998,0.536293,0.537587,0.538880,0.540171,0.541462,0.542751,0.544039, +0.545325,0.546610,0.547894,0.549177,0.550458,0.551738,0.553017,0.554294, +0.555570,0.556845,0.558119,0.559391,0.560662,0.561931,0.563199,0.564466, +0.565732,0.566996,0.568259,0.569521,0.570781,0.572040,0.573297,0.574553, +0.575808,0.577062,0.578314,0.579565,0.580814,0.582062,0.583309,0.584554, +0.585798,0.587040,0.588282,0.589521,0.590760,0.591997,0.593232,0.594466, +0.595699,0.596931,0.598161,0.599389,0.600616,0.601842,0.603067,0.604290, +0.605511,0.606731,0.607950,0.609167,0.610383,0.611597,0.612810,0.614022, +0.615232,0.616440,0.617647,0.618853,0.620057,0.621260,0.622461,0.623661, +0.624859,0.626056,0.627252,0.628446,0.629638,0.630829,0.632019,0.633207, +0.634393,0.635578,0.636762,0.637944,0.639124,0.640303,0.641481,0.642657, +0.643832,0.645005,0.646176,0.647346,0.648514,0.649681,0.650847,0.652011, +0.653173,0.654334,0.655493,0.656651,0.657807,0.658961,0.660114,0.661266, +0.662416,0.663564,0.664711,0.665856,0.667000,0.668142,0.669283,0.670422, +0.671559,0.672695,0.673829,0.674962,0.676093,0.677222,0.678350,0.679476, +0.680601,0.681724,0.682846,0.683965,0.685084,0.686200,0.687315,0.688429, +0.689541,0.690651,0.691759,0.692866,0.693971,0.695075,0.696177,0.697278, +0.698376,0.699473,0.700569,0.701663,0.702755,0.703845,0.704934,0.706021, +0.707107,0.708191,0.709273,0.710353,0.711432,0.712509,0.713585,0.714659, +0.715731,0.716801,0.717870,0.718937,0.720003,0.721066,0.722128,0.723188, +0.724247,0.725304,0.726359,0.727413,0.728464,0.729514,0.730563,0.731609, +0.732654,0.733697,0.734739,0.735779,0.736817,0.737853,0.738887,0.739920, +0.740951,0.741980,0.743008,0.744034,0.745058,0.746080,0.747101,0.748119, +0.749136,0.750152,0.751165,0.752177,0.753187,0.754195,0.755201,0.756206, +0.757209,0.758210,0.759209,0.760207,0.761202,0.762196,0.763188,0.764179, +0.765167,0.766154,0.767139,0.768122,0.769103,0.770083,0.771061,0.772036, +0.773010,0.773983,0.774953,0.775922,0.776888,0.777853,0.778817,0.779778, +0.780737,0.781695,0.782651,0.783605,0.784557,0.785507,0.786455,0.787402, +0.788346,0.789289,0.790230,0.791169,0.792107,0.793042,0.793975,0.794907, +0.795837,0.796765,0.797691,0.798615,0.799537,0.800458,0.801376,0.802293, +0.803208,0.804120,0.805031,0.805940,0.806848,0.807753,0.808656,0.809558, +0.810457,0.811355,0.812251,0.813144,0.814036,0.814926,0.815814,0.816701, +0.817585,0.818467,0.819348,0.820226,0.821103,0.821977,0.822850,0.823721, +0.824589,0.825456,0.826321,0.827184,0.828045,0.828904,0.829761,0.830616, +0.831470,0.832321,0.833170,0.834018,0.834863,0.835706,0.836548,0.837387, +0.838225,0.839060,0.839894,0.840725,0.841555,0.842383,0.843208,0.844032, +0.844854,0.845673,0.846491,0.847307,0.848120,0.848932,0.849742,0.850549, +0.851355,0.852159,0.852961,0.853760,0.854558,0.855354,0.856147,0.856939, +0.857729,0.858516,0.859302,0.860085,0.860867,0.861646,0.862424,0.863199, +0.863973,0.864744,0.865514,0.866281,0.867046,0.867809,0.868571,0.869330, +0.870087,0.870842,0.871595,0.872346,0.873095,0.873842,0.874587,0.875329, +0.876070,0.876809,0.877545,0.878280,0.879012,0.879743,0.880471,0.881197, +0.881921,0.882643,0.883363,0.884081,0.884797,0.885511,0.886223,0.886932, +0.887640,0.888345,0.889048,0.889750,0.890449,0.891146,0.891841,0.892534, +0.893224,0.893913,0.894599,0.895284,0.895966,0.896646,0.897325,0.898001, +0.898674,0.899346,0.900016,0.900683,0.901349,0.902012,0.902673,0.903332, +0.903989,0.904644,0.905297,0.905947,0.906596,0.907242,0.907886,0.908528, +0.909168,0.909806,0.910441,0.911075,0.911706,0.912335,0.912962,0.913587, +0.914210,0.914830,0.915449,0.916065,0.916679,0.917291,0.917901,0.918508, +0.919114,0.919717,0.920318,0.920917,0.921514,0.922109,0.922701,0.923291, +0.923880,0.924465,0.925049,0.925631,0.926210,0.926787,0.927363,0.927935, +0.928506,0.929075,0.929641,0.930205,0.930767,0.931327,0.931884,0.932440, +0.932993,0.933544,0.934093,0.934639,0.935184,0.935726,0.936266,0.936803, +0.937339,0.937872,0.938404,0.938932,0.939459,0.939984,0.940506,0.941026, +0.941544,0.942060,0.942573,0.943084,0.943593,0.944100,0.944605,0.945107, +0.945607,0.946105,0.946601,0.947094,0.947586,0.948075,0.948561,0.949046, +0.949528,0.950008,0.950486,0.950962,0.951435,0.951906,0.952375,0.952842, +0.953306,0.953768,0.954228,0.954686,0.955141,0.955594,0.956045,0.956494, +0.956940,0.957385,0.957826,0.958266,0.958703,0.959139,0.959572,0.960002, +0.960431,0.960857,0.961280,0.961702,0.962121,0.962538,0.962953,0.963366, +0.963776,0.964184,0.964590,0.964993,0.965394,0.965793,0.966190,0.966584, +0.966976,0.967366,0.967754,0.968139,0.968522,0.968903,0.969281,0.969657, +0.970031,0.970403,0.970772,0.971139,0.971504,0.971866,0.972226,0.972584, +0.972940,0.973293,0.973644,0.973993,0.974339,0.974684,0.975025,0.975365, +0.975702,0.976037,0.976370,0.976700,0.977028,0.977354,0.977677,0.977999, +0.978317,0.978634,0.978948,0.979260,0.979570,0.979877,0.980182,0.980485, +0.980785,0.981083,0.981379,0.981673,0.981964,0.982253,0.982539,0.982824, +0.983105,0.983385,0.983662,0.983937,0.984210,0.984480,0.984749,0.985014, +0.985278,0.985539,0.985798,0.986054,0.986308,0.986560,0.986809,0.987057, +0.987301,0.987544,0.987784,0.988022,0.988258,0.988491,0.988722,0.988950, +0.989177,0.989400,0.989622,0.989841,0.990058,0.990273,0.990485,0.990695, +0.990903,0.991108,0.991311,0.991511,0.991710,0.991906,0.992099,0.992291, +0.992480,0.992666,0.992850,0.993032,0.993212,0.993389,0.993564,0.993737, +0.993907,0.994075,0.994240,0.994404,0.994565,0.994723,0.994879,0.995033, +0.995185,0.995334,0.995481,0.995625,0.995767,0.995907,0.996045,0.996180, +0.996313,0.996443,0.996571,0.996697,0.996820,0.996941,0.997060,0.997176, +0.997290,0.997402,0.997511,0.997618,0.997723,0.997825,0.997925,0.998023, +0.998118,0.998211,0.998302,0.998390,0.998476,0.998559,0.998640,0.998719, +0.998795,0.998870,0.998941,0.999011,0.999078,0.999142,0.999205,0.999265, +0.999322,0.999378,0.999431,0.999481,0.999529,0.999575,0.999619,0.999660, +0.999699,0.999735,0.999769,0.999801,0.999831,0.999858,0.999882,0.999905, +0.999925,0.999942,0.999958,0.999971,0.999981,0.999989,0.999995,0.999999 +}; + +double sin( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 0: + return sintable[index]; + case 1: + return sintable[1023-index]; + case 2: + return -sintable[index]; + case 3: + return -sintable[1023-index]; + } + return 0; +} + + +double cos( double x ) { + int index; + int quad; + + index = 1024 * x / (M_PI * 0.5); + quad = ( index >> 10 ) & 3; + index &= 1023; + switch ( quad ) { + case 3: + return sintable[index]; + case 0: + return sintable[1023-index]; + case 1: + return -sintable[index]; + case 2: + return -sintable[1023-index]; + } + return 0; +} + + +/* +void create_acostable( void ) { + int i; + FILE *fp; + float a; + + fp = fopen("c:\\acostable.txt", "w"); + fprintf(fp, "float acostable[] = {"); + for (i = 0; i < 1024; i++) { + if (!(i & 7)) + fprintf(fp, "\n"); + a = acos( (float) -1 + i / 512 ); + fprintf(fp, "%1.8f,", a); + } + fprintf(fp, "\n}\n"); + fclose(fp); +} +*/ + +float acostable[] = { +3.14159265,3.07908248,3.05317551,3.03328655,3.01651113,3.00172442,2.98834964,2.97604422, +2.96458497,2.95381690,2.94362719,2.93393068,2.92466119,2.91576615,2.90720289,2.89893629, +2.89093699,2.88318015,2.87564455,2.86831188,2.86116621,2.85419358,2.84738169,2.84071962, +2.83419760,2.82780691,2.82153967,2.81538876,2.80934770,2.80341062,2.79757211,2.79182724, +2.78617145,2.78060056,2.77511069,2.76969824,2.76435988,2.75909250,2.75389319,2.74875926, +2.74368816,2.73867752,2.73372510,2.72882880,2.72398665,2.71919677,2.71445741,2.70976688, +2.70512362,2.70052613,2.69597298,2.69146283,2.68699438,2.68256642,2.67817778,2.67382735, +2.66951407,2.66523692,2.66099493,2.65678719,2.65261279,2.64847088,2.64436066,2.64028133, +2.63623214,2.63221238,2.62822133,2.62425835,2.62032277,2.61641398,2.61253138,2.60867440, +2.60484248,2.60103507,2.59725167,2.59349176,2.58975488,2.58604053,2.58234828,2.57867769, +2.57502832,2.57139977,2.56779164,2.56420354,2.56063509,2.55708594,2.55355572,2.55004409, +2.54655073,2.54307530,2.53961750,2.53617701,2.53275354,2.52934680,2.52595650,2.52258238, +2.51922417,2.51588159,2.51255441,2.50924238,2.50594525,2.50266278,2.49939476,2.49614096, +2.49290115,2.48967513,2.48646269,2.48326362,2.48007773,2.47690482,2.47374472,2.47059722, +2.46746215,2.46433933,2.46122860,2.45812977,2.45504269,2.45196720,2.44890314,2.44585034, +2.44280867,2.43977797,2.43675809,2.43374890,2.43075025,2.42776201,2.42478404,2.42181622, +2.41885841,2.41591048,2.41297232,2.41004380,2.40712480,2.40421521,2.40131491,2.39842379, +2.39554173,2.39266863,2.38980439,2.38694889,2.38410204,2.38126374,2.37843388,2.37561237, +2.37279910,2.36999400,2.36719697,2.36440790,2.36162673,2.35885335,2.35608768,2.35332964, +2.35057914,2.34783610,2.34510044,2.34237208,2.33965094,2.33693695,2.33423003,2.33153010, +2.32883709,2.32615093,2.32347155,2.32079888,2.31813284,2.31547337,2.31282041,2.31017388, +2.30753373,2.30489988,2.30227228,2.29965086,2.29703556,2.29442632,2.29182309,2.28922580, +2.28663439,2.28404881,2.28146900,2.27889490,2.27632647,2.27376364,2.27120637,2.26865460, +2.26610827,2.26356735,2.26103177,2.25850149,2.25597646,2.25345663,2.25094195,2.24843238, +2.24592786,2.24342836,2.24093382,2.23844420,2.23595946,2.23347956,2.23100444,2.22853408, +2.22606842,2.22360742,2.22115104,2.21869925,2.21625199,2.21380924,2.21137096,2.20893709, +2.20650761,2.20408248,2.20166166,2.19924511,2.19683280,2.19442469,2.19202074,2.18962092, +2.18722520,2.18483354,2.18244590,2.18006225,2.17768257,2.17530680,2.17293493,2.17056692, +2.16820274,2.16584236,2.16348574,2.16113285,2.15878367,2.15643816,2.15409630,2.15175805, +2.14942338,2.14709226,2.14476468,2.14244059,2.14011997,2.13780279,2.13548903,2.13317865, +2.13087163,2.12856795,2.12626757,2.12397047,2.12167662,2.11938600,2.11709859,2.11481435, +2.11253326,2.11025530,2.10798044,2.10570867,2.10343994,2.10117424,2.09891156,2.09665185, +2.09439510,2.09214129,2.08989040,2.08764239,2.08539725,2.08315496,2.08091550,2.07867884, +2.07644495,2.07421383,2.07198545,2.06975978,2.06753681,2.06531651,2.06309887,2.06088387, +2.05867147,2.05646168,2.05425445,2.05204979,2.04984765,2.04764804,2.04545092,2.04325628, +2.04106409,2.03887435,2.03668703,2.03450211,2.03231957,2.03013941,2.02796159,2.02578610, +2.02361292,2.02144204,2.01927344,2.01710710,2.01494300,2.01278113,2.01062146,2.00846399, +2.00630870,2.00415556,2.00200457,1.99985570,1.99770895,1.99556429,1.99342171,1.99128119, +1.98914271,1.98700627,1.98487185,1.98273942,1.98060898,1.97848051,1.97635399,1.97422942, +1.97210676,1.96998602,1.96786718,1.96575021,1.96363511,1.96152187,1.95941046,1.95730088, +1.95519310,1.95308712,1.95098292,1.94888050,1.94677982,1.94468089,1.94258368,1.94048818, +1.93839439,1.93630228,1.93421185,1.93212308,1.93003595,1.92795046,1.92586659,1.92378433, +1.92170367,1.91962459,1.91754708,1.91547113,1.91339673,1.91132385,1.90925250,1.90718266, +1.90511432,1.90304746,1.90098208,1.89891815,1.89685568,1.89479464,1.89273503,1.89067683, +1.88862003,1.88656463,1.88451060,1.88245794,1.88040664,1.87835668,1.87630806,1.87426076, +1.87221477,1.87017008,1.86812668,1.86608457,1.86404371,1.86200412,1.85996577,1.85792866, +1.85589277,1.85385809,1.85182462,1.84979234,1.84776125,1.84573132,1.84370256,1.84167495, +1.83964848,1.83762314,1.83559892,1.83357582,1.83155381,1.82953289,1.82751305,1.82549429, +1.82347658,1.82145993,1.81944431,1.81742973,1.81541617,1.81340362,1.81139207,1.80938151, +1.80737194,1.80536334,1.80335570,1.80134902,1.79934328,1.79733848,1.79533460,1.79333164, +1.79132959,1.78932843,1.78732817,1.78532878,1.78333027,1.78133261,1.77933581,1.77733985, +1.77534473,1.77335043,1.77135695,1.76936428,1.76737240,1.76538132,1.76339101,1.76140148, +1.75941271,1.75742470,1.75543743,1.75345090,1.75146510,1.74948002,1.74749565,1.74551198, +1.74352900,1.74154672,1.73956511,1.73758417,1.73560389,1.73362426,1.73164527,1.72966692, +1.72768920,1.72571209,1.72373560,1.72175971,1.71978441,1.71780969,1.71583556,1.71386199, +1.71188899,1.70991653,1.70794462,1.70597325,1.70400241,1.70203209,1.70006228,1.69809297, +1.69612416,1.69415584,1.69218799,1.69022062,1.68825372,1.68628727,1.68432127,1.68235571, +1.68039058,1.67842588,1.67646160,1.67449772,1.67253424,1.67057116,1.66860847,1.66664615, +1.66468420,1.66272262,1.66076139,1.65880050,1.65683996,1.65487975,1.65291986,1.65096028, +1.64900102,1.64704205,1.64508338,1.64312500,1.64116689,1.63920905,1.63725148,1.63529416, +1.63333709,1.63138026,1.62942366,1.62746728,1.62551112,1.62355517,1.62159943,1.61964388, +1.61768851,1.61573332,1.61377831,1.61182346,1.60986877,1.60791422,1.60595982,1.60400556, +1.60205142,1.60009739,1.59814349,1.59618968,1.59423597,1.59228235,1.59032882,1.58837536, +1.58642196,1.58446863,1.58251535,1.58056211,1.57860891,1.57665574,1.57470259,1.57274945, +1.57079633,1.56884320,1.56689007,1.56493692,1.56298375,1.56103055,1.55907731,1.55712403, +1.55517069,1.55321730,1.55126383,1.54931030,1.54735668,1.54540297,1.54344917,1.54149526, +1.53954124,1.53758710,1.53563283,1.53367843,1.53172389,1.52976919,1.52781434,1.52585933, +1.52390414,1.52194878,1.51999323,1.51803748,1.51608153,1.51412537,1.51216900,1.51021240, +1.50825556,1.50629849,1.50434117,1.50238360,1.50042576,1.49846765,1.49650927,1.49455060, +1.49259163,1.49063237,1.48867280,1.48671291,1.48475270,1.48279215,1.48083127,1.47887004, +1.47690845,1.47494650,1.47298419,1.47102149,1.46905841,1.46709493,1.46513106,1.46316677, +1.46120207,1.45923694,1.45727138,1.45530538,1.45333893,1.45137203,1.44940466,1.44743682, +1.44546850,1.44349969,1.44153038,1.43956057,1.43759024,1.43561940,1.43364803,1.43167612, +1.42970367,1.42773066,1.42575709,1.42378296,1.42180825,1.41983295,1.41785705,1.41588056, +1.41390346,1.41192573,1.40994738,1.40796840,1.40598877,1.40400849,1.40202755,1.40004594, +1.39806365,1.39608068,1.39409701,1.39211264,1.39012756,1.38814175,1.38615522,1.38416795, +1.38217994,1.38019117,1.37820164,1.37621134,1.37422025,1.37222837,1.37023570,1.36824222, +1.36624792,1.36425280,1.36225684,1.36026004,1.35826239,1.35626387,1.35426449,1.35226422, +1.35026307,1.34826101,1.34625805,1.34425418,1.34224937,1.34024364,1.33823695,1.33622932, +1.33422072,1.33221114,1.33020059,1.32818904,1.32617649,1.32416292,1.32214834,1.32013273, +1.31811607,1.31609837,1.31407960,1.31205976,1.31003885,1.30801684,1.30599373,1.30396951, +1.30194417,1.29991770,1.29789009,1.29586133,1.29383141,1.29180031,1.28976803,1.28773456, +1.28569989,1.28366400,1.28162688,1.27958854,1.27754894,1.27550809,1.27346597,1.27142257, +1.26937788,1.26733189,1.26528459,1.26323597,1.26118602,1.25913471,1.25708205,1.25502803, +1.25297262,1.25091583,1.24885763,1.24679802,1.24473698,1.24267450,1.24061058,1.23854519, +1.23647833,1.23440999,1.23234015,1.23026880,1.22819593,1.22612152,1.22404557,1.22196806, +1.21988898,1.21780832,1.21572606,1.21364219,1.21155670,1.20946958,1.20738080,1.20529037, +1.20319826,1.20110447,1.19900898,1.19691177,1.19481283,1.19271216,1.19060973,1.18850553, +1.18639955,1.18429178,1.18218219,1.18007079,1.17795754,1.17584244,1.17372548,1.17160663, +1.16948589,1.16736324,1.16523866,1.16311215,1.16098368,1.15885323,1.15672081,1.15458638, +1.15244994,1.15031147,1.14817095,1.14602836,1.14388370,1.14173695,1.13958808,1.13743709, +1.13528396,1.13312866,1.13097119,1.12881153,1.12664966,1.12448556,1.12231921,1.12015061, +1.11797973,1.11580656,1.11363107,1.11145325,1.10927308,1.10709055,1.10490563,1.10271831, +1.10052856,1.09833638,1.09614174,1.09394462,1.09174500,1.08954287,1.08733820,1.08513098, +1.08292118,1.08070879,1.07849378,1.07627614,1.07405585,1.07183287,1.06960721,1.06737882, +1.06514770,1.06291382,1.06067715,1.05843769,1.05619540,1.05395026,1.05170226,1.04945136, +1.04719755,1.04494080,1.04268110,1.04041841,1.03815271,1.03588399,1.03361221,1.03133735, +1.02905939,1.02677830,1.02449407,1.02220665,1.01991603,1.01762219,1.01532509,1.01302471, +1.01072102,1.00841400,1.00610363,1.00378986,1.00147268,0.99915206,0.99682798,0.99450039, +0.99216928,0.98983461,0.98749636,0.98515449,0.98280898,0.98045980,0.97810691,0.97575030, +0.97338991,0.97102573,0.96865772,0.96628585,0.96391009,0.96153040,0.95914675,0.95675912, +0.95436745,0.95197173,0.94957191,0.94716796,0.94475985,0.94234754,0.93993099,0.93751017, +0.93508504,0.93265556,0.93022170,0.92778341,0.92534066,0.92289341,0.92044161,0.91798524, +0.91552424,0.91305858,0.91058821,0.90811309,0.90563319,0.90314845,0.90065884,0.89816430, +0.89566479,0.89316028,0.89065070,0.88813602,0.88561619,0.88309116,0.88056088,0.87802531, +0.87548438,0.87293806,0.87038629,0.86782901,0.86526619,0.86269775,0.86012366,0.85754385, +0.85495827,0.85236686,0.84976956,0.84716633,0.84455709,0.84194179,0.83932037,0.83669277, +0.83405893,0.83141877,0.82877225,0.82611928,0.82345981,0.82079378,0.81812110,0.81544172, +0.81275556,0.81006255,0.80736262,0.80465570,0.80194171,0.79922057,0.79649221,0.79375655, +0.79101352,0.78826302,0.78550497,0.78273931,0.77996593,0.77718475,0.77439569,0.77159865, +0.76879355,0.76598029,0.76315878,0.76032891,0.75749061,0.75464376,0.75178826,0.74892402, +0.74605092,0.74316887,0.74027775,0.73737744,0.73446785,0.73154885,0.72862033,0.72568217, +0.72273425,0.71977644,0.71680861,0.71383064,0.71084240,0.70784376,0.70483456,0.70181469, +0.69878398,0.69574231,0.69268952,0.68962545,0.68654996,0.68346288,0.68036406,0.67725332, +0.67413051,0.67099544,0.66784794,0.66468783,0.66151492,0.65832903,0.65512997,0.65191753, +0.64869151,0.64545170,0.64219789,0.63892987,0.63564741,0.63235028,0.62903824,0.62571106, +0.62236849,0.61901027,0.61563615,0.61224585,0.60883911,0.60541564,0.60197515,0.59851735, +0.59504192,0.59154856,0.58803694,0.58450672,0.58095756,0.57738911,0.57380101,0.57019288, +0.56656433,0.56291496,0.55924437,0.55555212,0.55183778,0.54810089,0.54434099,0.54055758, +0.53675018,0.53291825,0.52906127,0.52517867,0.52126988,0.51733431,0.51337132,0.50938028, +0.50536051,0.50131132,0.49723200,0.49312177,0.48897987,0.48480547,0.48059772,0.47635573, +0.47207859,0.46776530,0.46341487,0.45902623,0.45459827,0.45012983,0.44561967,0.44106652, +0.43646903,0.43182577,0.42713525,0.42239588,0.41760600,0.41276385,0.40786755,0.40291513, +0.39790449,0.39283339,0.38769946,0.38250016,0.37723277,0.37189441,0.36648196,0.36099209, +0.35542120,0.34976542,0.34402054,0.33818204,0.33224495,0.32620390,0.32005298,0.31378574, +0.30739505,0.30087304,0.29421096,0.28739907,0.28042645,0.27328078,0.26594810,0.25841250, +0.25065566,0.24265636,0.23438976,0.22582651,0.21693146,0.20766198,0.19796546,0.18777575, +0.17700769,0.16554844,0.15324301,0.13986823,0.12508152,0.10830610,0.08841715,0.06251018, +} + +double acos( double x ) { + int index; + + if (x < -1) + x = -1; + if (x > 1) + x = 1; + index = (float) (1.0 + x) * 511.9; + return acostable[index]; +} + +double atan2( double y, double x ) { + float base; + float temp; + float dir; + float test; + int i; + + if ( x < 0 ) { + if ( y >= 0 ) { + // quad 1 + base = M_PI / 2; + temp = x; + x = y; + y = -temp; + } else { + // quad 2 + base = M_PI; + x = -x; + y = -y; + } + } else { + if ( y < 0 ) { + // quad 3 + base = 3 * M_PI / 2; + temp = x; + x = -y; + y = temp; + } + } + + if ( y > x ) { + base += M_PI/2; + temp = x; + x = y; + y = temp; + dir = -1; + } else { + dir = 1; + } + + // calcualte angle in octant 0 + if ( x == 0 ) { + return base; + } + y /= x; + + for ( i = 0 ; i < 512 ; i++ ) { + test = sintable[i] / sintable[1023-i]; + if ( test > y ) { + break; + } + } + + return base + dir * i * ( M_PI/2048); +} + + +#endif + +#ifdef Q3_VM +// bk001127 - guarded this tan replacement +// ld: undefined versioned symbol name tan@@GLIBC_2.0 +double tan( double x ) { + return sin(x) / cos(x); +} +#endif + + +static int randSeed = 0; + +void srand( unsigned seed ) { + randSeed = seed; +} + +int rand( void ) { + randSeed = (69069 * randSeed + 1); + return randSeed & 0x7fff; +} + +double atof( const char *string ) { + float sign; + float value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + c = string[0]; + if ( c != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } else { + string++; + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + + return value * sign; +} + +double _atof( const char **stringPtr ) { + const char *string; + float sign; + float value; + int c = '0'; // bk001211 - uninitialized use possible + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + *stringPtr = string; + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + if ( string[0] != '.' ) { + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + } + + // check for decimal point + if ( c == '.' ) { + double fraction; + + fraction = 0.1; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value += c * fraction; + fraction *= 0.1; + } while ( 1 ); + + } + + // not handling 10e10 notation... + *stringPtr = string; + + return value * sign; +} + + +// bk001120 - presumably needed for Mac +//#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) + +// bk001127 - undid undo +#if defined ( Q3_VM ) +int atoi( const char *string ) { + int sign; + int value; + int c; + + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + return value * sign; +} + + +int _atoi( const char **stringPtr ) { + int sign; + int value; + int c; + const char *string; + + string = *stringPtr; + + // skip whitespace + while ( *string <= ' ' ) { + if ( !*string ) { + return 0; + } + string++; + } + + // check sign + switch ( *string ) { + case '+': + string++; + sign = 1; + break; + case '-': + string++; + sign = -1; + break; + default: + sign = 1; + break; + } + + // read digits + value = 0; + do { + c = *string++; + if ( c < '0' || c > '9' ) { + break; + } + c -= '0'; + value = value * 10 + c; + } while ( 1 ); + + // not handling 10e10 notation... + + *stringPtr = string; + + return value * sign; +} + +int abs( int n ) { + return n < 0 ? -n : n; +} + +double fabs( double x ) { + return x < 0 ? -x : x; +} + + + +//========================================================= + + +#define ALT 0x00000001 /* alternate form */ +#define HEXPREFIX 0x00000002 /* add 0x or 0X prefix */ +#define LADJUST 0x00000004 /* left adjustment */ +#define LONGDBL 0x00000008 /* long double */ +#define LONGINT 0x00000010 /* long integer */ +#define QUADINT 0x00000020 /* quad integer */ +#define SHORTINT 0x00000040 /* short integer */ +#define ZEROPAD 0x00000080 /* zero (as opposed to blank) pad */ +#define FPT 0x00000100 /* floating point number */ + +#define to_digit(c) ((c) - '0') +#define is_digit(c) ((unsigned)to_digit(c) <= 9) +#define to_char(n) ((n) + '0') + +void AddInt( char **buf_p, int val, int width, int flags ) { + char text[32]; + int digits; + int signedVal; + char *buf; + + digits = 0; + signedVal = val; + if ( val < 0 ) { + val = -val; + } + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + if( !( flags & LADJUST ) ) { + while ( digits < width ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + width--; + } + } + + while ( digits-- ) { + *buf++ = text[digits]; + width--; + } + + if( flags & LADJUST ) { + while ( width-- ) { + *buf++ = ( flags & ZEROPAD ) ? '0' : ' '; + } + } + + *buf_p = buf; +} + +void AddFloat( char **buf_p, float fval, int width, int prec ) { + char text[32]; + int digits; + float signedVal; + char *buf; + int val; + + // get the sign + signedVal = fval; + if ( fval < 0 ) { + fval = -fval; + } + + // write the float number + digits = 0; + val = (int)fval; + do { + text[digits++] = '0' + val % 10; + val /= 10; + } while ( val ); + + if ( signedVal < 0 ) { + text[digits++] = '-'; + } + + buf = *buf_p; + + while ( digits < width ) { + *buf++ = ' '; + width--; + } + + while ( digits-- ) { + *buf++ = text[digits]; + } + + *buf_p = buf; + + if (prec < 0) + prec = 6; + // write the fraction + digits = 0; + while (digits < prec) { + fval -= (int) fval; + fval *= 10.0; + val = (int) fval; + text[digits++] = '0' + val % 10; + } + + if (digits > 0) { + buf = *buf_p; + *buf++ = '.'; + for (prec = 0; prec < digits; prec++) { + *buf++ = text[prec]; + } + *buf_p = buf; + } +} + + +void AddString( char **buf_p, char *string, int width, int prec ) { + int size; + char *buf; + + buf = *buf_p; + + if ( string == NULL ) { + string = "(null)"; + prec = -1; + } + + if ( prec >= 0 ) { + for( size = 0; size < prec; size++ ) { + if( string[size] == '\0' ) { + break; + } + } + } + else { + size = strlen( string ); + } + + width -= size; + + while( size-- ) { + *buf++ = *string++; + } + + while( width-- > 0 ) { + *buf++ = ' '; + } + + *buf_p = buf; +} + +/* +vsprintf + +I'm not going to support a bunch of the more arcane stuff in here +just to keep it simpler. For example, the '*' and '$' are not +currently supported. I've tried to make it so that it will just +parse and ignore formats we don't support. +*/ +int vsprintf( char *buffer, const char *fmt, va_list argptr ) { + int *arg; + char *buf_p; + char ch; + int flags; + int width; + int prec; + int n; + char sign; + + buf_p = buffer; + arg = (int *)argptr; + + while( qtrue ) { + // run through the format string until we hit a '%' or '\0' + for ( ch = *fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++ ) { + *buf_p++ = ch; + } + if ( ch == '\0' ) { + goto done; + } + + // skip over the '%' + fmt++; + + // reset formatting state + flags = 0; + width = 0; + prec = -1; + sign = '\0'; + +rflag: + ch = *fmt++; +reswitch: + switch( ch ) { + case '-': + flags |= LADJUST; + goto rflag; + case '.': + n = 0; + while( is_digit( ( ch = *fmt++ ) ) ) { + n = 10 * n + ( ch - '0' ); + } + prec = n < 0 ? -1 : n; + goto reswitch; + case '0': + flags |= ZEROPAD; + goto rflag; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = 0; + do { + n = 10 * n + ( ch - '0' ); + ch = *fmt++; + } while( is_digit( ch ) ); + width = n; + goto reswitch; + case 'c': + *buf_p++ = (char)*arg; + arg++; + break; + case 'd': + case 'i': + AddInt( &buf_p, *arg, width, flags ); + arg++; + break; + case 'f': + AddFloat( &buf_p, *(double *)arg, width, prec ); +#ifdef __LCC__ + arg += 1; // everything is 32 bit in my compiler +#else + arg += 2; +#endif + break; + case 's': + AddString( &buf_p, (char *)*arg, width, prec ); + arg++; + break; + case '%': + *buf_p++ = ch; + break; + default: + *buf_p++ = (char)*arg; + arg++; + break; + } + } + +done: + *buf_p = 0; + return buf_p - buffer; +} + + +/* this is really crappy */ +int sscanf( const char *buffer, const char *fmt, ... ) { + int cmd; + int **arg; + int count; + + arg = (int **)&fmt + 1; + count = 0; + + while ( *fmt ) { + if ( fmt[0] != '%' ) { + fmt++; + continue; + } + + cmd = fmt[1]; + fmt += 2; + + switch ( cmd ) { + case 'i': + case 'd': + case 'u': + **arg = _atoi( &buffer ); + break; + case 'f': + *(float *)*arg = _atof( &buffer ); + break; + } + arg++; + } + + return count; +} + +#endif diff --git a/quake3/source/code/game/bg_lib.h b/quake3/source/code/game/bg_lib.h new file mode 100644 index 0000000..41f3fc1 --- /dev/null +++ b/quake3/source/code/game/bg_lib.h @@ -0,0 +1,70 @@ +// bg_lib.h -- standard C library replacement routines used by code +// compiled for the virtual machine + +// This file is NOT included on native builds + +typedef int size_t; + +typedef char * va_list; +#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) +#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) +#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) +#define va_end(ap) ( ap = (va_list)0 ) + +#define CHAR_BIT 8 /* number of bits in a char */ +#define SCHAR_MIN (-128) /* minimum signed char value */ +#define SCHAR_MAX 127 /* maximum signed char value */ +#define UCHAR_MAX 0xff /* maximum unsigned char value */ + +#define SHRT_MIN (-32768) /* minimum (signed) short value */ +#define SHRT_MAX 32767 /* maximum (signed) short value */ +#define USHRT_MAX 0xffff /* maximum unsigned short value */ +#define INT_MIN (-2147483647 - 1) /* minimum (signed) int value */ +#define INT_MAX 2147483647 /* maximum (signed) int value */ +#define UINT_MAX 0xffffffff /* maximum unsigned int value */ +#define LONG_MIN (-2147483647L - 1) /* minimum (signed) long value */ +#define LONG_MAX 2147483647L /* maximum (signed) long value */ +#define ULONG_MAX 0xffffffffUL /* maximum unsigned long value */ + +// Misc functions +typedef int cmp_t(const void *, const void *); +void qsort(void *a, size_t n, size_t es, cmp_t *cmp); +void srand( unsigned seed ); +int rand( void ); + +// String functions +size_t strlen( const char *string ); +char *strcat( char *strDestination, const char *strSource ); +char *strcpy( char *strDestination, const char *strSource ); +int strcmp( const char *string1, const char *string2 ); +char *strchr( const char *string, int c ); +char *strstr( const char *string, const char *strCharSet ); +char *strncpy( char *strDest, const char *strSource, size_t count ); +int tolower( int c ); +int toupper( int c ); + +double atof( const char *string ); +double _atof( const char **stringPtr ); +int atoi( const char *string ); +int _atoi( const char **stringPtr ); + +int vsprintf( char *buffer, const char *fmt, va_list argptr ); +int sscanf( const char *buffer, const char *fmt, ... ); + +// Memory functions +void *memmove( void *dest, const void *src, size_t count ); +void *memset( void *dest, int c, size_t count ); +void *memcpy( void *dest, const void *src, size_t count ); + +// Math functions +double ceil( double x ); +double floor( double x ); +double sqrt( double x ); +double sin( double x ); +double cos( double x ); +double atan2( double y, double x ); +double tan( double x ); +int abs( int n ); +double fabs( double x ); +double acos( double x ); + diff --git a/quake3/source/code/game/bg_local.h b/quake3/source/code/game/bg_local.h new file mode 100644 index 0000000..8ba4e62 --- /dev/null +++ b/quake3/source/code/game/bg_local.h @@ -0,0 +1,63 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_local.h -- local definitions for the bg (both games) files + +#define MIN_WALK_NORMAL 0.7f // can't walk on very steep slopes + +#define STEPSIZE 18 + +#define JUMP_VELOCITY 270 + +#define TIMER_LAND 130 +#define TIMER_GESTURE (34*66+50) + +#define OVERCLIP 1.001f + +// all of the locals will be zeroed before each +// pmove, just to make damn sure we don't have +// any differences when running on client or server +typedef struct { + vec3_t forward, right, up; + float frametime; + + int msec; + + qboolean walking; + qboolean groundPlane; + trace_t groundTrace; + + float impactSpeed; + + vec3_t previous_origin; + vec3_t previous_velocity; + int previous_waterlevel; +} pml_t; + +extern pmove_t *pm; +extern pml_t pml; + +// movement parameters +extern float pm_stopspeed; +extern float pm_duckScale; +extern float pm_swimScale; +extern float pm_wadeScale; + +extern float pm_accelerate; +extern float pm_airaccelerate; +extern float pm_wateraccelerate; +extern float pm_flyaccelerate; + +extern float pm_friction; +extern float pm_waterfriction; +extern float pm_flightfriction; + +extern int c_pmove; + +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ); +void PM_AddTouchEnt( int entityNum ); +void PM_AddEvent( int newEvent ); + +qboolean PM_SlideMove( qboolean gravity ); +void PM_StepSlideMove( qboolean gravity ); + + diff --git a/quake3/source/code/game/bg_misc.c b/quake3/source/code/game/bg_misc.c new file mode 100644 index 0000000..51baf43 --- /dev/null +++ b/quake3/source/code/game/bg_misc.c @@ -0,0 +1,2052 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_misc.c -- both games misc functions, all completely stateless + +#include "q_shared.h" +#include "bg_public.h" + +/*QUAKED item_***** ( 0 0 0 ) (-16 -16 -16) (16 16 16) suspended +DO NOT USE THIS CLASS, IT JUST HOLDS GENERAL INFORMATION. +The suspended flag will allow items to hang in the air, otherwise they are dropped to the next surface. + +If an item is the target of another entity, it will not spawn in until fired. + +An item fires all of its targets when it is picked up. If the toucher can't carry it, the targets won't be fired. + +"notfree" if set to 1, don't spawn in free for all games +"notteam" if set to 1, don't spawn in team games +"notsingle" if set to 1, don't spawn in single player games +"wait" override the default wait before respawning. -1 = never respawn automatically, which can be used with targeted spawning. +"random" random number of plus or minus seconds varied from the respawn time +"count" override quantity or duration on most items. +*/ + +gitem_t bg_itemlist[] = +{ + { + NULL, + NULL, + { NULL, + NULL, + 0, 0} , +/* icon */ NULL, +/* pickup */ NULL, + 0, + 0, + 0, +/* precache */ "", +/* sounds */ "" + }, // leave index 0 alone + + // + // ARMOR + // + +/*QUAKED item_armor_shard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_armor_shard", + "sound/misc/ar1_pkup.wav", + { "models/powerups/armor/shard.md3", + "models/powerups/armor/shard_sphere.md3", + 0, 0} , +/* icon */ "icons/iconr_shard", +/* pickup */ "Armor Shard", + 5, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_armor_combat (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_armor_combat", + "sound/misc/ar2_pkup.wav", + { "models/powerups/armor/armor_yel.md3", + 0, 0, 0}, +/* icon */ "icons/iconr_yellow", +/* pickup */ "Armor", + 50, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_armor_body", + "sound/misc/ar2_pkup.wav", + { "models/powerups/armor/armor_red.md3", + 0, 0, 0}, +/* icon */ "icons/iconr_red", +/* pickup */ "Heavy Armor", + 100, + IT_ARMOR, + 0, +/* precache */ "", +/* sounds */ "" + }, + + // + // health + // +/*QUAKED item_health_small (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health_small", + "sound/items/s_health.wav", + { "models/powerups/health/small_cross.md3", + "models/powerups/health/small_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_green", +/* pickup */ "5 Health", + 5, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health", + "sound/items/n_health.wav", + { "models/powerups/health/medium_cross.md3", + "models/powerups/health/medium_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_yellow", +/* pickup */ "25 Health", + 25, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_health_large (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health_large", + "sound/items/l_health.wav", + { "models/powerups/health/large_cross.md3", + "models/powerups/health/large_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_red", +/* pickup */ "50 Health", + 50, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_health_mega (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_health_mega", + "sound/items/m_health.wav", + { "models/powerups/health/mega_cross.md3", + "models/powerups/health/mega_sphere.md3", + 0, 0 }, +/* icon */ "icons/iconh_mega", +/* pickup */ "Mega Health", + 100, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "" + }, + + + // + // WEAPONS + // + +/*QUAKED weapon_gauntlet (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_gauntlet", + "sound/misc/w_pkup.wav", + { "models/weapons2/gauntlet/gauntlet.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_gauntlet", +/* pickup */ "Gauntlet", + 0, + IT_WEAPON, + WP_GAUNTLET, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_shotgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_shotgun", + "sound/misc/w_pkup.wav", + { "models/weapons2/shotgun/shotgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_shotgun", +//PKMOD - Ergodic 03/22/01 - change shotgun name to boomstick +/* pickup */ "Boomstick", + 10, + IT_WEAPON, + WP_SHOTGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_machinegun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_machinegun", + "sound/misc/w_pkup.wav", + { "models/weapons2/machinegun/machinegun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_machinegun", +/* pickup */ "Machinegun", + 40, + IT_WEAPON, + WP_MACHINEGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_grenadelauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_grenadelauncher", + "sound/misc/w_pkup.wav", + { "models/weapons2/grenadel/grenadel.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_grenade", +/* pickup */ "Grenade Launcher", + 10, + IT_WEAPON, + WP_GRENADE_LAUNCHER, +/* precache */ "", +/* sounds */ "sound/weapons/grenade/hgrenb1a.wav sound/weapons/grenade/hgrenb2a.wav" + }, + +/*QUAKED weapon_rocketlauncher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_rocketlauncher", + "sound/misc/w_pkup.wav", + { "models/weapons2/rocketl/rocketl.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_rocket", +/* pickup */ "Rocket Launcher", + 10, + IT_WEAPON, + WP_ROCKET_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_lightning", + "sound/misc/w_pkup.wav", + { + //PKMOD - Ergodic 03/05/01 - Lightning hand hold model is different than the pickup model (inactive) + //PKMOD - Ergodic 03/27/01 - code was inactivated due to CLG will not have a rotating barrel + //PKMOD - Ergodic 05/15/03 - re-add lightning gun to barrel list for Uber's clg model + //"models/weapons2/lightning/lightning_pickup.md3", + "models/weapons2/lightning/lightning.md3", + //PKMOD - Ergodic 03/04/01 - Lightning pickup model is different than the hand hold model (inactive) + //PKMOD - Ergodic 03/27/01 - code was inactivated due to CLG will not have a rotating barrel +// "models/weapons2/lightning/lightning_pickup.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_lightning", +//PKMOD - Ergodic 12/19/03 - change Lightning Gun name to Chain Lightning Gun +/* pickup */ "Chain Lightning Gun", + //PKMOD - Ergodic 07/03/01 - Reduce Lightning Gun ammo, was 100 + 75, + IT_WEAPON, + WP_LIGHTNING, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_railgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_railgun", + "sound/misc/w_pkup.wav", + { "models/weapons2/railgun/railgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_railgun", +//PKMOD - Ergodic 07/12/01 - change railgun name to magnum +/* pickup */ "Magnum", + 10, + IT_WEAPON, + WP_RAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_plasmagun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_plasmagun", + "sound/misc/w_pkup.wav", + { "models/weapons2/plasma/plasma.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_plasma", +/* pickup */ "Plasma Gun", + 50, + IT_WEAPON, + WP_PLASMAGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_bfg", + "sound/misc/w_pkup.wav", + { "models/weapons2/bfg/bfg.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_bfg", +/* pickup */ "BFG10K", + 20, + IT_WEAPON, + WP_BFG, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_grapplinghook (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_grapplinghook", + "sound/misc/w_pkup.wav", + { "models/weapons2/dragon/dragon.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_grapple", +/* pickup */ "Dragon", //PKMOD - Ergodic 09/05/00 - was "Grappling Hook" + 0, + IT_WEAPON, + WP_GRAPPLING_HOOK, +/* precache */ "", +/* sounds */ "" + }, + + // + // AMMO ITEMS + // + +/*QUAKED ammo_shells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_shells", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/shotgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_shotgun", +/* pickup */ "Shells", + 10, + IT_AMMO, + WP_SHOTGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_bullets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_bullets", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/machinegunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_machinegun", +/* pickup */ "Bullets", + 50, + IT_AMMO, + WP_MACHINEGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_grenades (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_grenades", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/grenadeam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_grenade", +/* pickup */ "Grenades", + 5, + IT_AMMO, + WP_GRENADE_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_cells (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_cells", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/plasmaam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_plasma", +/* pickup */ "Cells", + 30, + IT_AMMO, + WP_PLASMAGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_lightning (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_lightning", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/lightningam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_lightning", +/* pickup */ "Lightning", + //PKMOD - Ergodic 07/03/01 - Reduce Lightning Gun ammo, was 60 + 40, + IT_AMMO, + WP_LIGHTNING, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_rockets", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/rocketam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_rocket", +/* pickup */ "Rockets", + 5, + IT_AMMO, + WP_ROCKET_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_slugs (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_slugs", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/railgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_railgun", +//PKMOD - Ergodic 07/12/01 - change railgun name to magnum +/* pickup */ "Magnum Slugs", + 10, + IT_AMMO, + WP_RAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_bfg (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_bfg", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/bfgam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_bfg", +/* pickup */ "Bfg Ammo", + 15, + IT_AMMO, + WP_BFG, +/* precache */ "", +/* sounds */ "" + }, + + // + // HOLDABLE ITEMS + // +/*QUAKED holdable_teleporter (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_teleporter", + "sound/items/holdable.wav", + { "models/powerups/holdable/teleporter.md3", + 0, 0, 0}, +/* icon */ "icons/teleporter", +/* pickup */ "Personal Teleporter", + 60, + IT_HOLDABLE, + HI_TELEPORTER, +/* precache */ "", +/* sounds */ "" + }, +/*QUAKED holdable_medkit (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_medkit", + "sound/items/holdable.wav", + { + "models/powerups/holdable/medkit.md3", + "models/powerups/holdable/medkit_sphere.md3", + 0, 0}, +/* icon */ "icons/medkit", +/* pickup */ "Medkit", + 60, + IT_HOLDABLE, + HI_MEDKIT, +/* precache */ "", +/* sounds */ "sound/items/use_medkit.wav" + }, +//PKMOD - Ergodic 10/06/01 - add new holdable +/*QUAKED holdable_radiate (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_radiate", + "sound/items/holdable.wav", + { + "models/powerups/holdable/radiate.md3", + "models/powerups/holdable/radiate_sphere.md3", + 0, 0}, +/* icon */ "icons/radiate", +/* pickup */ "Radiate", + 60, + IT_HOLDABLE, + HI_RADIATE, +/* precache */ "", +/* sounds */ "sound/items/use_medkit.wav" + }, +//PKMOD - Ergodic 11/23/01 - add new holdable +/*QUAKED holdable_sentry (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_sentry", + "sound/items/holdable.wav", + { + "models/powerups/holdable/persentry.md3", + "models/powerups/holdable/persentry_sphere.md3", + 0, 0}, +/* icon */ "icons/persentry", +/* pickup */ "Personal Sentry", + 60, + IT_HOLDABLE, + HI_PERSENTRY, +/* precache */ "", +/* sounds */ "sound/items/use_medkit.wav" + }, + +//PKMOD - Ergodic 12/01/01 - add new holdable +/*QUAKED holdable_botlegs (.3 .3 1) (-16 -16 -20) (16 16 20) suspended +*/ + { + "holdable_botlegs", + "sound/items/holdable.wav", + { + "models/players/tankjr/lower.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_pribot_100", +/* pickup */ "Private Bot (Legs)", + 60, + IT_HOLDABLE, + HI_BOTLEGS, +/* precache */ "", +/* sounds */ "sound/items/use_medkit.wav" + }, + +//PKMOD - Ergodic 12/01/01 - add new holdable +/*QUAKED holdable_bottorso (.3 .3 1) (-24 -24 -16) (24 24 16) suspended +*/ + { + "holdable_bottorso", + "sound/items/holdable.wav", + { + "models/players/doom/upper.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_pribot_010", +/* pickup */ "Private Bot (Torso)", + 60, + IT_HOLDABLE, + HI_BOTTORSO, +/* precache */ "", +/* sounds */ "sound/items/use_medkit.wav" + }, + +//PKMOD - Ergodic 12/01/01 - add new holdable +/*QUAKED holdable_bothead (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_bothead", + "sound/items/holdable.wav", + { + "models/players/visor/head.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_pribot_001", +/* pickup */ "Private Bot (Head)", + 60, + IT_HOLDABLE, + HI_BOTHEAD, +/* precache */ "", +/* sounds */ "sound/items/use_medkit.wav" + }, + + + // + // POWERUP ITEMS + // +/*QUAKED item_quad (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_quad", + "sound/items/quaddamage.wav", + { "models/powerups/instant/quad.md3", + "models/powerups/instant/quad_ring.md3", + 0, 0 }, +/* icon */ "icons/quad", +/* pickup */ "Quad Damage", + 30, + IT_POWERUP, + PW_QUAD, +/* precache */ "", +/* sounds */ "sound/items/damage2.wav sound/items/damage3.wav" + }, + +/*QUAKED item_enviro (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_enviro", + "sound/items/protect.wav", + { "models/powerups/instant/enviro.md3", + "models/powerups/instant/enviro_ring.md3", + 0, 0 }, +/* icon */ "icons/envirosuit", +/* pickup */ "Battle Suit", + 30, + IT_POWERUP, + PW_BATTLESUIT, +/* precache */ "", +/* sounds */ "sound/items/airout.wav sound/items/protect3.wav" + }, + +/*QUAKED item_haste (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_haste", + "sound/items/haste.wav", + { "models/powerups/instant/haste.md3", + "models/powerups/instant/haste_ring.md3", + 0, 0 }, +/* icon */ "icons/haste", +/* pickup */ "Speed", + 30, + IT_POWERUP, + PW_HASTE, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_invis (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_invis", + "sound/items/invisibility.wav", + { "models/powerups/instant/invis.md3", + "models/powerups/instant/invis_ring.md3", + 0, 0 }, +/* icon */ "icons/invis", +/* pickup */ "Invisibility", + 30, + IT_POWERUP, + PW_INVIS, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_regen (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_regen", + "sound/items/regeneration.wav", + { "models/powerups/instant/regen.md3", + "models/powerups/instant/regen_ring.md3", + 0, 0 }, +/* icon */ "icons/regen", +/* pickup */ "Regeneration", + 30, + IT_POWERUP, + PW_REGEN, +/* precache */ "", +/* sounds */ "sound/items/regen.wav" + }, + +/*QUAKED item_flight (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "item_flight", + "sound/items/flight.wav", + { "models/powerups/instant/flight.md3", + "models/powerups/instant/flight_ring.md3", + 0, 0 }, +/* icon */ "icons/flight", +/* pickup */ "Flight", + 60, + IT_POWERUP, + PW_FLIGHT, +/* precache */ "", +/* sounds */ "sound/items/flight.wav" + }, + +/*QUAKED team_CTF_redflag (1 0 0) (-16 -16 -16) (16 16 16) +Only in CTF games +*/ + { + "team_CTF_redflag", + NULL, + { "models/flags/r_flag.md3", + 0, 0, 0 }, +/* icon */ "icons/iconf_red1", +/* pickup */ "Red Flag", + 0, + IT_TEAM, + PW_REDFLAG, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED team_CTF_blueflag (0 0 1) (-16 -16 -16) (16 16 16) +Only in CTF games +*/ + { + "team_CTF_blueflag", + NULL, + { "models/flags/b_flag.md3", + 0, 0, 0 }, +/* icon */ "icons/iconf_blu1", +/* pickup */ "Blue Flag", + 0, + IT_TEAM, + PW_BLUEFLAG, +/* precache */ "", +/* sounds */ "" + }, + +#ifdef MISSIONPACK +/*QUAKED holdable_kamikaze (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_kamikaze", + "sound/items/holdable.wav", + { "models/powerups/kamikazi.md3", + 0, 0, 0}, +/* icon */ "icons/kamikaze", +/* pickup */ "Kamikaze", + 60, + IT_HOLDABLE, + HI_KAMIKAZE, +/* precache */ "", +/* sounds */ "sound/items/kamikazerespawn.wav" + }, + +/*QUAKED holdable_portal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_portal", + "sound/items/holdable.wav", + { "models/powerups/holdable/porter.md3", + 0, 0, 0}, +/* icon */ "icons/portal", +/* pickup */ "Portal", + 60, + IT_HOLDABLE, + HI_PORTAL, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED holdable_invulnerability (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "holdable_invulnerability", + "sound/items/holdable.wav", + { "models/powerups/holdable/invulnerability.md3", + 0, 0, 0}, +/* icon */ "icons/invulnerability", +/* pickup */ "Invulnerability", + 60, + IT_HOLDABLE, + HI_INVULNERABILITY, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_nails (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_nails", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/nailgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_nailgun", +/* pickup */ "Nails", + 20, + IT_AMMO, + WP_NAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_mines (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_mines", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/proxmineam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_proxlauncher", +/* pickup */ "Proximity Mines", + 10, + IT_AMMO, + WP_PROX_LAUNCHER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_belt (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_belt", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/chaingunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_chaingun", +/* pickup */ "Chaingun Belt", + 100, + IT_AMMO, + WP_CHAINGUN, +/* precache */ "", +/* sounds */ "" + }, + + // + // PERSISTANT POWERUP ITEMS + // +/*QUAKED item_scout (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_scout", + "sound/items/scout.wav", + { "models/powerups/scout.md3", + 0, 0, 0 }, +/* icon */ "icons/scout", +/* pickup */ "Scout", + 30, + IT_PERSISTANT_POWERUP, + PW_SCOUT, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_guard (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_guard", + "sound/items/guard.wav", + { "models/powerups/guard.md3", + 0, 0, 0 }, +/* icon */ "icons/guard", +/* pickup */ "Guard", + 30, + IT_PERSISTANT_POWERUP, + PW_GUARD, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_doubler", + "sound/items/doubler.wav", + { "models/powerups/doubler.md3", + 0, 0, 0 }, +/* icon */ "icons/doubler", +/* pickup */ "Doubler", + 30, + IT_PERSISTANT_POWERUP, + PW_DOUBLER, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED item_doubler (.3 .3 1) (-16 -16 -16) (16 16 16) suspended redTeam blueTeam +*/ + { + "item_ammoregen", + "sound/items/ammoregen.wav", + { "models/powerups/ammo.md3", + 0, 0, 0 }, +/* icon */ "icons/ammo_regen", +/* pickup */ "Ammo Regen", + 30, + IT_PERSISTANT_POWERUP, + PW_AMMOREGEN, +/* precache */ "", +/* sounds */ "" + }, + + /*QUAKED team_CTF_neutralflag (0 0 1) (-16 -16 -16) (16 16 16) +Only in One Flag CTF games +*/ + { + "team_CTF_neutralflag", + NULL, + { "models/flags/n_flag.md3", + 0, 0, 0 }, +/* icon */ "icons/iconf_neutral1", +/* pickup */ "Neutral Flag", + 0, + IT_TEAM, + PW_NEUTRALFLAG, +/* precache */ "", +/* sounds */ "" + }, + + { + "item_redcube", + "sound/misc/am_pkup.wav", + { "models/powerups/orb/r_orb.md3", + 0, 0, 0 }, +/* icon */ "icons/iconh_rorb", +/* pickup */ "Red Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "" + }, + + { + "item_bluecube", + "sound/misc/am_pkup.wav", + { "models/powerups/orb/b_orb.md3", + 0, 0, 0 }, +/* icon */ "icons/iconh_borb", +/* pickup */ "Blue Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "" + }, +/*QUAKED weapon_nailgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_nailgun", + "sound/misc/w_pkup.wav", + { "models/weapons/nailgun/nailgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_nailgun", +/* pickup */ "Nailgun", + 10, + IT_WEAPON, + WP_NAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_prox_launcher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_prox_launcher", + "sound/misc/w_pkup.wav", + { "models/weapons/proxmine/proxmine.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_proxlauncher", +/* pickup */ "Prox Launcher", + 5, + IT_WEAPON, + WP_PROX_LAUNCHER, +/* precache */ "", +/* sounds */ "sound/weapons/proxmine/wstbtick.wav " + "sound/weapons/proxmine/wstbactv.wav " + "sound/weapons/proxmine/wstbimpl.wav " + "sound/weapons/proxmine/wstbimpm.wav " + "sound/weapons/proxmine/wstbimpd.wav " + "sound/weapons/proxmine/wstbactv.wav" + }, + +/*QUAKED weapon_chaingun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_chaingun", + "sound/misc/w_pkup.wav", + { "models/weapons/vulcan/vulcan.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_chaingun", +/* pickup */ "Chaingun", + 80, + IT_WEAPON, + WP_CHAINGUN, +/* precache */ "", +/* sounds */ "sound/weapons/vulcan/wvulwind.wav" + }, +#endif + + +/*PKMOD -Add Weapons. + WP_HARPOON, + WP_GRAVITY, + WP_SENTRY, + WP_BEARTRAP, + WP_CHAINLG, + WP_A2K, + WP_EMPNUKE, + WP_AIRFIST, + WP_NAILGUN, + PKMOD -Add Weapons. */ + +//PKMOD - Ergodic - 05/14/00 added new gw model +/*QUAKED weapon_gravity (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +//PKMOD - Ergodic 03/17/01 new PKA gravity well weapon to the "hold" model + */ + { + "weapon_gravity", + "sound/misc/w_pkup.wav", + { "models/weapons2/gwell/gwp.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_gravity", +/* pickup */ "Gravity Well", + 1, + IT_WEAPON, + WP_GRAVITY, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_sentry(.3 .3 1) (-16 -16 -16) (16 16 16) suspended + */ + { + "weapon_sentry", + "sound/misc/w_pkup.wav", + //PKMOD - Ergodic 05/31/02 - add animated autosentry model + { "models/weapons2/autosentry/autosentry.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_autosentry", +/* pickup */ "Auto Sentry", + 1, + IT_WEAPON, + WP_SENTRY, +/* precache */ "", +/* sounds */ "" + }, + /*QUAKED weapon_beartrap (.3 .3 1) (-16 -16 -16) (16 16 16) suspended + */ + { + "weapon_beartrap", + "sound/misc/w_pkup.wav", + { "models/weapons2/beartrap/bearpick.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_beartrap", +/* pickup */ "Bear Trap", + 1, + IT_WEAPON, + WP_BEARTRAP, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_airfist (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_airfist", + "sound/misc/w_pkup.wav", + { "models/weapons2/airfist/airfist.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_airfist", +/* pickup */ "Air Fist", +//PKMOD - Ergodic 05/18/01 - Airfist has displayable ammo corresponding to airfist_level + 4, //05/18/01 - was 0 + IT_WEAPON, + WP_AIRFIST, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_nailgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_nailgun", + "sound/misc/w_pkup.wav", + { "models/weapons2/nailgun/nailgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_nailgun", +/* pickup */ "Nail Gun", + 50, + IT_WEAPON, + WP_NAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +//PKMOD +/*QUAKED ammo_nails (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_nails", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/nailgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_nailgun", +/* pickup */ "Nails", + 50, + IT_AMMO, + WP_NAILGUN, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED weapon_beans (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_beans", + "sound/misc/w_pkup.wav", + { "models/weapons2/beans/beans.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_beans", +/* pickup */ "Can of Pork-N-Beans", + 1, + IT_WEAPON, + WP_BEANS, +/* precache */ "", +/* sounds */ "" + }, + + /*QUAKED weapon_expshotgun (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_expgun", + "sound/misc/w_pkup.wav", + { "models/weapons2/explgun/explgun.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_explgun", +/* pickup */ "Exploding Shells Shotgun", + 10, + IT_WEAPON, + WP_EXPLODING_SHELLS, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED ammo_expshell (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_expshells", + "sound/misc/am_pkup.wav", + { "models/powerups/ammo/explgunam.md3", + 0, 0, 0}, +/* icon */ "icons/icona_explgun", +/* pickup */ "Explosive Shells", + 10, + IT_AMMO, + WP_EXPLODING_SHELLS, +/* precache */ "", +/* sounds */ "" + }, + +/*QUAKED voting_image (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +"voting_mapname" "map name of voting entity" // <= 20 bytes +"voting_maptitle" "map title of voting entity" // <= 124 bytes "MAX_FILE_PATH==144" +"voting_shader" "index of the voting shader" +*/ + { + "voting_image", //classname + "sound/misc/am_pkup.wav", + { "models/voting/voting.md3", + 0, 0, 0}, +/* icon */ "icons/iconw_beans", +/* pickup */ "Voting Image", //can not pick this item up + 3, + IT_VOTING, +// PW_VOTING, //12/17/00 remove the PW_VOTING definition due to lack of space + 0, //12/17/00 +/* precache */ "", +/* sounds */ "" + }, + + //PKMOD + + // end of list marker + {NULL} +}; + +int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; + +//PKMOD - Ergodic 09/27/2000, included in both the game dll and the client +int Hub_Index; //points to last Hub element (Game Code only) +ghubInfo_t hubInfo[MAX_HUB_INDEX]; + +//PKMOD - Ergodic 01/13/02 - PRIVATE BOT definitions +int active_private_bots; //number of active Private Bots + + +/* +============== +BG_FindItemForPowerup +============== +*/ +gitem_t *BG_FindItemForPowerup( powerup_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( (bg_itemlist[i].giType == IT_POWERUP || + bg_itemlist[i].giType == IT_TEAM || + bg_itemlist[i].giType == IT_PERSISTANT_POWERUP) && + bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + return NULL; +} + + +/* +============== +BG_FindItemForHoldable +============== +*/ +gitem_t *BG_FindItemForHoldable( holdable_t pw ) { + int i; + + for ( i = 0 ; i < bg_numItems ; i++ ) { + if ( bg_itemlist[i].giType == IT_HOLDABLE && bg_itemlist[i].giTag == pw ) { + return &bg_itemlist[i]; + } + } + + Com_Error( ERR_DROP, "HoldableItem not found" ); + + return NULL; +} + + +/* +=============== +BG_FindItemForWeapon + +=============== +*/ +gitem_t *BG_FindItemForWeapon( weapon_t weapon ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++) { + if ( it->giType == IT_WEAPON && it->giTag == weapon ) { + return it; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for weapon %i", weapon); + return NULL; +} + +/* +=============== +BG_FindItem + +=============== +*/ +gitem_t *BG_FindItem( const char *pickupName ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( !Q_stricmp( it->pickup_name, pickupName ) ) + return it; + } + + return NULL; +} + +/* +============ +BG_PlayerTouchesItem + +Items can be picked up without actually touching their physical bounds to make +grabbing them easier +============ +*/ +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ) { + vec3_t origin; + + BG_EvaluateTrajectory( &item->pos, atTime, origin ); + + // we are ignoring ducked differences here + if ( ps->origin[0] - origin[0] > 44 + || ps->origin[0] - origin[0] < -50 + || ps->origin[1] - origin[1] > 36 + || ps->origin[1] - origin[1] < -36 + || ps->origin[2] - origin[2] > 36 + || ps->origin[2] - origin[2] < -36 ) { + return qfalse; + } + + return qtrue; +} + + + +/* +================ +BG_CanItemBeGrabbed + +Returns false if the item should not be picked up. +This needs to be the same for client side prediction and server use. +//PKMOD - Ergodic 07/06/00 - do not pick up PKA Items if at max inventory +================ +*/ +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ) { + gitem_t *item; +#ifdef MISSIONPACK + int upperBound; +#endif + + if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) { +//Ergodic - Debug + Com_Printf("BG_CanItemBeGrabbed: index out of range - modelindex>%d<, eType>%d<\n", ent->modelindex, ent->eType); + + + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); + } + + item = &bg_itemlist[ent->modelindex]; + + switch( item->giType ) { + case IT_WEAPON: + switch (item->giTag) { + case WP_BEARTRAP: + if ( ps->ammo[ item->giTag ] >= 3 ) + return qfalse; + break; + case WP_SENTRY: + if ( ps->ammo[ item->giTag ] >= 3 ) + return qfalse; + break; + case WP_GRAVITY: + if ( ps->ammo[ item->giTag ] >= 1 ) + return qfalse; + break; + case WP_BEANS: + if ( ps->ammo[ item->giTag ] >= 1 ) + return qfalse; + break; + } + return qtrue; // weapons are always picked up. 07/06 Ergodic - except PKA weapons + + case IT_AMMO: + //PKMOD - Ergodic 01/25/01 - can only hold 10 exploding shells at a time + if ( item->giTag == WP_EXPLODING_SHELLS ) { + if ( ps->ammo[ item->giTag ] >= 10 ) { + return qfalse; // can't hold any more + } + } + else { + if ( ps->ammo[ item->giTag ] >= 200 ) { + return qfalse; // can't hold any more + } + } + return qtrue; + + case IT_ARMOR: + if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } + return qtrue; + + case IT_HEALTH: + // small and mega healths will go over the max, otherwise + // don't pick up if already at max +#ifdef MISSIONPACK + if( bg_itemlist[ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + upperBound = ps->stats[STAT_MAX_HEALTH]; + } + else +#endif + if ( item->quantity == 5 || item->quantity == 100 ) { + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] * 2 ) { + return qfalse; + } + return qtrue; + } + + if ( ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH] ) { + return qfalse; + } + return qtrue; + + case IT_POWERUP: + return qtrue; // powerups are always picked up + +#ifdef MISSIONPACK + case IT_PERSISTANT_POWERUP: + // can only hold one item at a time + if ( ps->stats[STAT_PERSISTANT_POWERUP] ) { + return qfalse; + } + + // check team only + if( ( ent->generic1 & 2 ) && ( ps->persistant[PERS_TEAM] != TEAM_RED ) ) { + return qfalse; + } + if( ( ent->generic1 & 4 ) && ( ps->persistant[PERS_TEAM] != TEAM_BLUE ) ) { + return qfalse; + } + + return qtrue; +#endif + + case IT_TEAM: // team items, such as flags +#ifdef MISSIONPACK + if( gametype == GT_1FCTF ) { + // neutral flag can always be picked up + if( item->giTag == PW_NEUTRALFLAG ) { + return qtrue; + } + if (ps->persistant[PERS_TEAM] == TEAM_RED) { + if (item->giTag == PW_BLUEFLAG && ps->powerups[PW_NEUTRALFLAG] ) { + return qtrue; + } + } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { + if (item->giTag == PW_REDFLAG && ps->powerups[PW_NEUTRALFLAG] ) { + return qtrue; + } + } + } +#endif + if( gametype == GT_CTF ) { + // ent->modelindex2 is non-zero on items if they are dropped + // we need to know this because we can pick up our dropped flag (and return it) + // but we can't pick up our flag at base + if (ps->persistant[PERS_TEAM] == TEAM_RED) { + if (item->giTag == PW_BLUEFLAG || + (item->giTag == PW_REDFLAG && ent->modelindex2) || + (item->giTag == PW_REDFLAG && ps->powerups[PW_BLUEFLAG]) ) + return qtrue; + } else if (ps->persistant[PERS_TEAM] == TEAM_BLUE) { + if (item->giTag == PW_REDFLAG || + (item->giTag == PW_BLUEFLAG && ent->modelindex2) || + (item->giTag == PW_BLUEFLAG && ps->powerups[PW_REDFLAG]) ) + return qtrue; + } + } + +#ifdef MISSIONPACK + if( gametype == GT_HARVESTER ) { + return qtrue; + } +#endif + return qfalse; + + case IT_HOLDABLE: + //PKMOD - Ergodic 05/11/01 - allow holding of more than 1 type of + // holdable but only 1 of each kind + if ( ps->stats[STAT_HOLDABLE_ITEM] & (1 << item->giTag)) { + return qfalse; + } + return qtrue; + + //PKMOD - Ergodic 09/21/00 - make voting entity touchable + case IT_VOTING: + return qtrue; + + case IT_BAD: + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: IT_BAD" ); + default: +#ifndef Q3_VM +#ifndef NDEBUG // bk0001204 + Com_Printf("BG_CanItemBeGrabbed: unknown enum %d\n", item->giType ); +#endif +#endif + break; + } + + return qfalse; +} + +//====================================================================== + +/* +================ +BG_EvaluateTrajectory + +================ +*/ +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorCopy( tr->trBase, result ); + break; + case TR_LINEAR: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = sin( deltaTime * M_PI * 2 ); + VectorMA( tr->trBase, phase, tr->trDelta, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + atTime = tr->trTime + tr->trDuration; + } + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + if ( deltaTime < 0 ) { + deltaTime = 0; + } + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * DEFAULT_GRAVITY * deltaTime * deltaTime; // FIXME: local gravity... + break; + //PKMOD - Ergodic 04/17/01 - add local gravity + // trDuration has been co-opted to hold "gravity" + case TR_LOCAL_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorMA( tr->trBase, deltaTime, tr->trDelta, result ); + result[2] -= 0.5 * tr->trDuration * deltaTime * deltaTime; + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: unknown trType: %i", tr->trTime ); + break; + } +} + +/* +================ +BG_EvaluateTrajectoryDelta + +For determining velocity at a given time +================ +*/ +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ) { + float deltaTime; + float phase; + + switch( tr->trType ) { + case TR_STATIONARY: + case TR_INTERPOLATE: + VectorClear( result ); + break; + case TR_LINEAR: + VectorCopy( tr->trDelta, result ); + break; + case TR_SINE: + deltaTime = ( atTime - tr->trTime ) / (float) tr->trDuration; + phase = cos( deltaTime * M_PI * 2 ); // derivative of sin = cos + phase *= 0.5; + VectorScale( tr->trDelta, phase, result ); + break; + case TR_LINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) { + VectorClear( result ); + return; + } + VectorCopy( tr->trDelta, result ); + break; + case TR_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= DEFAULT_GRAVITY * deltaTime; // FIXME: local gravity... + break; + //PKMOD - Ergodic 04/17/01 - add local gravity + // trDuration has been co-opted to hold "gravity" + case TR_LOCAL_GRAVITY: + deltaTime = ( atTime - tr->trTime ) * 0.001; // milliseconds to seconds + VectorCopy( tr->trDelta, result ); + result[2] -= tr->trDuration * deltaTime; // FIXME: local gravity... + break; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +char *eventnames[] = { + "EV_NONE", + + "EV_FOOTSTEP", + "EV_FOOTSTEP_METAL", + "EV_FOOTSPLASH", + "EV_FOOTWADE", + "EV_SWIM", + + "EV_STEP_4", + "EV_STEP_8", + "EV_STEP_12", + "EV_STEP_16", + + "EV_FALL_SHORT", + "EV_FALL_MEDIUM", + "EV_FALL_FAR", + + "EV_JUMP_PAD", // boing sound at origin", jump sound on player + + "EV_JUMP", + "EV_WATER_TOUCH", // foot touches + "EV_WATER_LEAVE", // foot leaves + "EV_WATER_UNDER", // head touches + "EV_WATER_CLEAR", // head leaves + + "EV_ITEM_PICKUP", // normal item pickups are predictable + "EV_GLOBAL_ITEM_PICKUP", // powerup / team sounds are broadcast to everyone + + "EV_NOAMMO", + "EV_CHANGE_WEAPON", + "EV_FIRE_WEAPON", + + "EV_USE_ITEM0", + "EV_USE_ITEM1", + "EV_USE_ITEM2", + "EV_USE_ITEM3", + "EV_USE_ITEM4", + "EV_USE_ITEM5", + "EV_USE_ITEM6", + "EV_USE_ITEM7", + "EV_USE_ITEM8", + "EV_USE_ITEM9", + "EV_USE_ITEM10", + "EV_USE_ITEM11", + "EV_USE_ITEM12", + "EV_USE_ITEM13", + "EV_USE_ITEM14", + "EV_USE_ITEM15", + + "EV_ITEM_RESPAWN", + "EV_ITEM_POP", + "EV_PLAYER_TELEPORT_IN", + "EV_PLAYER_TELEPORT_OUT", + + "EV_GRENADE_BOUNCE", // eventParm will be the soundindex + + "EV_GENERAL_SOUND", + "EV_GLOBAL_SOUND", // no attenuation + "EV_GLOBAL_TEAM_SOUND", + + "EV_BULLET_HIT_FLESH", + "EV_BULLET_HIT_WALL", + + "EV_MISSILE_HIT", + "EV_MISSILE_MISS", + "EV_MISSILE_MISS_METAL", + "EV_RAILTRAIL", + "EV_SHOTGUN", + "EV_BULLET", // otherEntity is the shooter + + "EV_PAIN", + "EV_DEATH1", + "EV_DEATH2", + "EV_DEATH3", + "EV_OBITUARY", + + "EV_POWERUP_QUAD", + "EV_POWERUP_BATTLESUIT", + "EV_POWERUP_REGEN", + + "EV_GIB_PLAYER", // gib a previously living player + "EV_SCOREPLUM", // score plum + +//#ifdef MISSIONPACK + "EV_PROXIMITY_MINE_STICK", + "EV_PROXIMITY_MINE_TRIGGER", + "EV_KAMIKAZE", // kamikaze explodes + "EV_OBELISKEXPLODE", // obelisk explodes + "EV_INVUL_IMPACT", // invulnerability sphere impact + "EV_JUICED", // invulnerability juiced effect + "EV_LIGHTNINGBOLT", // lightning bolt bounced of invulnerability sphere +//#endif + + "EV_DEBUG_LINE", + "EV_STOPLOOPINGSOUND", + "EV_TAUNT" + +}; + +/* +=============== +BG_AddPredictableEventToPlayerstate + +Handles the sequence numbers +=============== +*/ + +void trap_Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ) { + +#ifdef _DEBUG + { + char buf[256]; + trap_Cvar_VariableStringBuffer("showevents", buf, sizeof(buf)); + if ( atof(buf) != 0 ) { +#ifdef QAGAME + Com_Printf(" game event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#else + Com_Printf("Cgame event svt %5d -> %5d: num = %20s parm %d\n", ps->pmove_framecount/*ps->commandTime*/, ps->eventSequence, eventnames[newEvent], eventParm); +#endif + } + } +#endif + ps->events[ps->eventSequence & (MAX_PS_EVENTS-1)] = newEvent; + ps->eventParms[ps->eventSequence & (MAX_PS_EVENTS-1)] = eventParm; + ps->eventSequence++; +} + +/* +======================== +BG_TouchJumpPad +======================== +*/ +void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ) { + vec3_t angles; + float p; + int effectNum; + + // spectators don't use jump pads + if ( ps->pm_type != PM_NORMAL ) { + return; + } + + // flying characters don't hit bounce pads + if ( ps->powerups[PW_FLIGHT] ) { + return; + } + + // if we didn't hit this same jumppad the previous frame + // then don't play the event sound again if we are in a fat trigger + if ( ps->jumppad_ent != jumppad->number ) { + + vectoangles( jumppad->origin2, angles); + p = fabs( AngleNormalize180( angles[PITCH] ) ); + if( p < 45 ) { + effectNum = 0; + } else { + effectNum = 1; + } + + //PKMOD - Ergodic 11/15/00 add functionality to make trigger_push silent + if ( jumppad->eType != ET_QUIET_TRIGGER ) + BG_AddPredictableEventToPlayerstate( EV_JUMP_PAD, effectNum, ps ); + } + // remember hitting this jumppad this frame + ps->jumppad_ent = jumppad->number; + ps->jumppad_frame = ps->pmove_framecount; + // give the player the velocity from the jumppad + VectorCopy( jumppad->origin2, ps->velocity ); +} + +/* +======================== +BG_PlayerStateToEntityState + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_INTERPOLATE; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction + VectorCopy( ps->velocity, s->pos.trDelta ); + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + //PKMOD - Ergodic 07/07/01 - movementDir is now packed into time2 +// s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + //PKMOD Ergodic 08/22/00 - set the target's playerstate shafted flag - for cgame + if ( ps->stats[STAT_PKA_ITEMS] & ( 1 << PKA_SHAFTED ) ) { //is client currently being shafted + s->powerups |= 1 << PW_CLGPLAYERHIT; + } + + +//PKMOD - Ergodic 07/10/00 add definition for beartraps attached constant for the +// angles2 co-opt hack. the entitystate_t angles2[beartraps_attached] +// variable will link the beartrap info from the game to the client +// Update - 07/10/00 pack in the variable for viewheight and offset it by 50 units +// so that it will always be positive +//PKMOD - Ergodic 12/19/00 use time2 instead of angles2 +// if ( ps->stats[STAT_BEARTRAPS_ATTACHED] > 0 ) +// s->angles2[BEARTRAPS_ATTACHED] = ps->stats[STAT_BEARTRAPS_ATTACHED] + 100 * (ps->viewheight + 50); +// else +// s->angles2[BEARTRAPS_ATTACHED] = 0; + +//PKMOD - Ergodic 07/07/01 - time2 bits : 33332221100 +// bits 00 [0-1] = # of attached beartraps (values: 0 - 3) +// bits 11 [2-3] = encoded viewheight (values: 0 - 2) +// bits 222 [4-6] = airfist level (values: 0 - 4) +// bits 333 [7-9] = ps->movementDir (values: 0 - 7) + +//PKMOD - Ergodic 12/01/01 - always store the encoded viewheight into co-opted time2 +//PKMOD - Ergodic 07/07/01 - encode viewheight to save space.. +// viewheight has 3 settings found in bg_public.h: +// #define DEFAULT_VIEWHEIGHT 26 [Encoded as 0] +// #define CROUCH_VIEWHEIGHT 12 [Encoded as 1] +// #define DEAD_VIEWHEIGHT -16 [Encoded as 2] + if ( ps->viewheight == DEFAULT_VIEWHEIGHT ) + i = 0; + else if ( ps->viewheight == CROUCH_VIEWHEIGHT ) + i = 1; + else + i = 2; + s->time2 = i << 2; + +//PKMOD - Ergodic 11/13/00 - pack beartraps into time2 + if ( ps->stats[STAT_BEARTRAPS_ATTACHED] > 0 ) { + i = ps->stats[STAT_BEARTRAPS_ATTACHED]; + if ( i > 3 ) + s->time2 |= 3; + else + s->time2 |= i; + } + +//PKMOD - Ergodic 11/16/00 - pack airfist level into time2 +//PKMOD - Ergodic 07/07/01 - change packing of airfist level from 9 to 4 + if ( s->weapon == WP_AIRFIST ) { + s->time2 |= ps->stats[STAT_PRIOR_AIRFIST_LEVEL] << 4; + } + + //PKMOD - Ergodic 10/18/01 - store pkaitems into modelindex2 + s->modelindex2 = ps->stats[STAT_PKA_ITEMS]; + + //PKMOD - Ergodic 07/07/01 - pack in movementdir + s->time2 |= ps->movementDir << 7; + + //PKMOD - Ergodic 07/07/01 - build angles2 + //PKMOD - Ergodic - 12/16/03 Removed angles2 because it is unused + //s->angles2[YAW] = ps->stats[STAT_CLG_SHAFT_YAW] / 1000; + //s->angles2[PITCH] = ps->stats[STAT_CLG_SHAFT_PITCH] / 1000; + //s->angles2[ROLL] = ps->viewangles[ROLL]; + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + + //PKMOD - Ergodic 12/16/03 - copy over otherEntityNum2 to entitystate. + // I thought this was done, but perhaps not. This is used in + // Chainshaft mechanism to store shaftee + //PKMOD - Ergodic - 12/16/03 Use stat location to store shaftee entity number + s->otherEntityNum2 = ps->stats[STAT_CLG_SHAFTEE_NUM]; +} + +/* +======================== +BG_PlayerStateToEntityStateExtraPolate + +This is done after each set of usercmd_t on the server, +and after local prediction on the client +======================== +*/ +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ) { + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPECTATOR ) { + s->eType = ET_INVISIBLE; + } else if ( ps->stats[STAT_HEALTH] <= GIB_HEALTH ) { + s->eType = ET_INVISIBLE; + } else { + s->eType = ET_PLAYER; + } + + s->number = ps->clientNum; + + s->pos.trType = TR_LINEAR_STOP; + VectorCopy( ps->origin, s->pos.trBase ); + if ( snap ) { + SnapVector( s->pos.trBase ); + } + // set the trDelta for flag direction and linear prediction + VectorCopy( ps->velocity, s->pos.trDelta ); + // set the time for linear prediction + s->pos.trTime = time; + // set maximum extra polation time + s->pos.trDuration = 50; // 1000 / sv_fps (default = 20) + + s->apos.trType = TR_INTERPOLATE; + VectorCopy( ps->viewangles, s->apos.trBase ); + if ( snap ) { + SnapVector( s->apos.trBase ); + } + + //PKMOD - Ergodic 07/07/01 - movementDir is now packed into time2 +// s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + if ( ps->stats[STAT_HEALTH] <= 0 ) { + s->eFlags |= EF_DEAD; + } else { + s->eFlags &= ~EF_DEAD; + } + + if ( ps->externalEvent ) { + s->event = ps->externalEvent; + s->eventParm = ps->externalEventParm; + } else if ( ps->entityEventSequence < ps->eventSequence ) { + int seq; + + if ( ps->entityEventSequence < ps->eventSequence - MAX_PS_EVENTS) { + ps->entityEventSequence = ps->eventSequence - MAX_PS_EVENTS; + } + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + s->event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + s->eventParm = ps->eventParms[ seq ]; + ps->entityEventSequence++; + } + + s->weapon = ps->weapon; + s->groundEntityNum = ps->groundEntityNum; + + s->powerups = 0; + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ps->powerups[ i ] ) { + s->powerups |= 1 << i; + } + } + + //PKMOD Ergodic 08/22/00 - set the target's playerstate shafted flag - for cgame + if ( ps->stats[STAT_PKA_ITEMS] & ( 1 << PKA_SHAFTED ) ) { //is client currently being shafted + s->powerups |= 1 << PW_CLGPLAYERHIT; + } + + +//PKMOD - Ergodic 07/10/00 add definition for beartraps attached constant for the +// angles2 co-opt hack. the entitystate_t angles2[beartraps_attached] +// variable will link the beartrap info from the game to the client +// Update - 07/10/00 pack in the variable for viewheight and offset it by 50 units +// so that it will always be positive +//PKMOD - Ergodic 12/19/00 use time2 instead of angles2 +// if ( ps->stats[STAT_BEARTRAPS_ATTACHED] > 0 ) +// s->angles2[BEARTRAPS_ATTACHED] = ps->stats[STAT_BEARTRAPS_ATTACHED] + 100 * (ps->viewheight + 50); +// else +// s->angles2[BEARTRAPS_ATTACHED] = 0; + +//PKMOD - Ergodic 07/07/01 - time2 bits : 3332221100 +// bits 00 [0-1] = # of attached beartraps +// bits 11 [2-3] = encoded viewheight +// bits 222 [4-6] = airfist level +// bits 333 [7-9] = ps->movementDir (0 - 7) + +//PKMOD - Ergodic 12/01/01 - always store the encoded viewheight into co-opted time2 +//PKMOD - Ergodic 07/07/01 - encode viewheight to save space.. +// viewheight has 3 settings found in bg_public.h: +// #define DEFAULT_VIEWHEIGHT 26 [Encoded as 0] +// #define CROUCH_VIEWHEIGHT 12 [Encoded as 1] +// #define DEAD_VIEWHEIGHT -16 [Encoded as 2] + if ( ps->viewheight == DEFAULT_VIEWHEIGHT ) + i = 0; + else if ( ps->viewheight == CROUCH_VIEWHEIGHT ) + i = 1; + else + i = 2; + s->time2 = i << 2; + +//PKMOD - Ergodic 11/13/00 - pack beartraps into time2 + if ( ps->stats[STAT_BEARTRAPS_ATTACHED] > 0 ) { + i = ps->stats[STAT_BEARTRAPS_ATTACHED]; + if ( i > 3 ) + s->time2 |= 3; + else + s->time2 |= i; + } + + +//PKMOD - Ergodic 11/16/00 - pack airfist level into time2 +//PKMOD - Ergodic 07/07/01 - change packing of airfist level from 9 to 4 + if ( s->weapon == WP_AIRFIST ) { + s->time2 |= ps->stats[STAT_PRIOR_AIRFIST_LEVEL] << 4; + } + + //PKMOD - Ergodic 10/18/01 - store pkaitems into modelindex2 + s->modelindex2 = ps->stats[STAT_PKA_ITEMS]; + + //PKMOD - Ergodic 07/07/01 - pack in movementdir + s->time2 |= ps->movementDir << 7; + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + + //PKMOD - Ergodic 12/16/03 - copy over otherEntityNum2 to entitystate. + // I thought this was done, but perhaps not. This is used in + // Chainshaft mechanism to store shaftee + //PKMOD - Ergodic - 12/16/03 Use stat location to store shaftee entity number + s->otherEntityNum2 = ps->stats[STAT_CLG_SHAFTEE_NUM]; + +} diff --git a/quake3/source/code/game/bg_pmove.c b/quake3/source/code/game/bg_pmove.c new file mode 100644 index 0000000..ecebdef --- /dev/null +++ b/quake3/source/code/game/bg_pmove.c @@ -0,0 +1,2354 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_pmove.c -- both games player movement code +// takes a playerstate and a usercmd as input and returns a modifed playerstate + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +pmove_t *pm; +pml_t pml; + +// movement parameters +float pm_stopspeed = 100.0f; +float pm_duckScale = 0.25f; +float pm_swimScale = 0.50f; +float pm_wadeScale = 0.70f; + +float pm_accelerate = 10.0f; +float pm_airaccelerate = 1.0f; +float pm_wateraccelerate = 4.0f; +float pm_flyaccelerate = 8.0f; + +float pm_friction = 6.0f; +float pm_waterfriction = 1.0f; +float pm_flightfriction = 3.0f; +float pm_spectatorfriction = 5.0f; + +int c_pmove = 0; + + +/* +=============== +PM_AddEvent + +=============== +*/ +void PM_AddEvent( int newEvent ) { + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); +} + +/* +=============== +PM_AddTouchEnt +=============== +*/ +void PM_AddTouchEnt( int entityNum ) { + int i; + + if ( entityNum == ENTITYNUM_WORLD ) { + return; + } + if ( pm->numtouch == MAXTOUCH ) { + return; + } + + // see if it is already added + for ( i = 0 ; i < pm->numtouch ; i++ ) { + if ( pm->touchents[ i ] == entityNum ) { + return; + } + } + + // add it + pm->touchents[pm->numtouch] = entityNum; + pm->numtouch++; +} + +/* +=================== +PM_StartTorsoAnim +=================== +*/ +static void PM_StartTorsoAnim( int anim ) { + if ( pm->ps->pm_type >= PM_DEAD ) { + return; + } + pm->ps->torsoAnim = ( ( pm->ps->torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} +static void PM_StartLegsAnim( int anim ) { + if ( pm->ps->pm_type >= PM_DEAD ) { + return; + } + if ( pm->ps->legsTimer > 0 ) { + return; // a high priority animation is running + } + pm->ps->legsAnim = ( ( pm->ps->legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) + | anim; +} + +static void PM_ContinueLegsAnim( int anim ) { + if ( ( pm->ps->legsAnim & ~ANIM_TOGGLEBIT ) == anim ) { + return; + } + if ( pm->ps->legsTimer > 0 ) { + return; // a high priority animation is running + } + PM_StartLegsAnim( anim ); +} + +static void PM_ContinueTorsoAnim( int anim ) { + if ( ( pm->ps->torsoAnim & ~ANIM_TOGGLEBIT ) == anim ) { + return; + } + if ( pm->ps->torsoTimer > 0 ) { + return; // a high priority animation is running + } + PM_StartTorsoAnim( anim ); +} + +static void PM_ForceLegsAnim( int anim ) { + pm->ps->legsTimer = 0; + PM_StartLegsAnim( anim ); +} + + +/* +================== +PM_ClipVelocity + +Slide off of the impacting surface +================== +*/ +void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out, float overbounce ) { + float backoff; + float change; + int i; + + backoff = DotProduct (in, normal); + + if ( backoff < 0 ) { + backoff *= overbounce; + } else { + backoff /= overbounce; + } + + for ( i=0 ; i<3 ; i++ ) { + change = normal[i]*backoff; + out[i] = in[i] - change; + } +} + + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +static void PM_Friction( void ) { + vec3_t vec; + float *vel; + float speed, newspeed, control; + float drop; + + vel = pm->ps->velocity; + + VectorCopy( vel, vec ); + if ( pml.walking ) { + vec[2] = 0; // ignore slope movement + } + + speed = VectorLength(vec); + if (speed < 1) { + vel[0] = 0; + vel[1] = 0; // allow sinking underwater + // FIXME: still have z friction underwater? + return; + } + + drop = 0; + + // apply ground friction + if ( pm->waterlevel <= 1 ) { + if ( pml.walking && !(pml.groundTrace.surfaceFlags & SURF_SLICK) ) { + // if getting knocked back, no friction + if ( ! (pm->ps->pm_flags & PMF_TIME_KNOCKBACK) ) { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*pm_friction*pml.frametime; + } + } + } + + // apply water friction even if just wading + if ( pm->waterlevel ) { + drop += speed*pm_waterfriction*pm->waterlevel*pml.frametime; + } + + // apply flying friction + if ( pm->ps->powerups[PW_FLIGHT]) { + drop += speed*pm_flightfriction*pml.frametime; + } + + if ( pm->ps->pm_type == PM_SPECTATOR) { + drop += speed*pm_spectatorfriction*pml.frametime; + } + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) { + newspeed = 0; + } + newspeed /= speed; + + vel[0] = vel[0] * newspeed; + vel[1] = vel[1] * newspeed; + vel[2] = vel[2] * newspeed; +} + + +/* +============== +PM_Accelerate + +Handles user intended acceleration +============== +*/ +static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { +#if 1 + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (pm->ps->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } + accelspeed = accel*pml.frametime*wishspeed; + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + + for (i=0 ; i<3 ; i++) { + pm->ps->velocity[i] += accelspeed*wishdir[i]; + } +#else + // proper way (avoids strafe jump maxspeed bug), but feels bad + vec3_t wishVelocity; + vec3_t pushDir; + float pushLen; + float canPush; + + VectorScale( wishdir, wishspeed, wishVelocity ); + VectorSubtract( wishVelocity, pm->ps->velocity, pushDir ); + pushLen = VectorNormalize( pushDir ); + + canPush = accel*pml.frametime*wishspeed; + if (canPush > pushLen) { + canPush = pushLen; + } + + VectorMA( pm->ps->velocity, canPush, pushDir, pm->ps->velocity ); +#endif +} + + + +/* +============ +PM_CmdScale + +Returns the scale factor to apply to cmd movements +This allows the clients to use axial -127 to 127 values for all directions +without getting a sqrt(2) distortion in speed. +============ +*/ +static float PM_CmdScale( usercmd_t *cmd ) { + int max; + float total; + float scale; + + max = abs( cmd->forwardmove ); + if ( abs( cmd->rightmove ) > max ) { + max = abs( cmd->rightmove ); + } + if ( abs( cmd->upmove ) > max ) { + max = abs( cmd->upmove ); + } + if ( !max ) { + return 0; + } + + total = sqrt( cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + cmd->upmove * cmd->upmove ); + scale = (float)pm->ps->speed * max / ( 127.0 * total ); + + return scale; +} + + +/* +================ +PM_SetMovementDir + +Determine the rotation of the legs reletive +to the facing dir +================ +*/ +static void PM_SetMovementDir( void ) { + if ( pm->cmd.forwardmove || pm->cmd.rightmove ) { + if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 0; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 1; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 2; + } else if ( pm->cmd.rightmove < 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 3; + } else if ( pm->cmd.rightmove == 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 4; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove < 0 ) { + pm->ps->movementDir = 5; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove == 0 ) { + pm->ps->movementDir = 6; + } else if ( pm->cmd.rightmove > 0 && pm->cmd.forwardmove > 0 ) { + pm->ps->movementDir = 7; + } + } else { + // if they aren't actively going directly sideways, + // change the animation to the diagonal so they + // don't stop too crooked + if ( pm->ps->movementDir == 2 ) { + pm->ps->movementDir = 1; + } else if ( pm->ps->movementDir == 6 ) { + pm->ps->movementDir = 7; + } + } +} + + +/* +============= +PM_CheckJump +============= +*/ +static qboolean PM_CheckJump( void ) { + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return qfalse; // don't allow jump until all buttons are up + } + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + return qfalse; + } + + // must wait for jump to be released + if ( pm->ps->pm_flags & PMF_JUMP_HELD ) { + // clear upmove so cmdscale doesn't lower running speed + pm->cmd.upmove = 0; + return qfalse; + } + + pml.groundPlane = qfalse; // jumping away + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pm->ps->velocity[2] = JUMP_VELOCITY; + PM_AddEvent( EV_JUMP ); + + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + return qtrue; +} + +/* +============= +PM_CheckWaterJump +============= +*/ +static qboolean PM_CheckWaterJump( void ) { + vec3_t spot; + int cont; + vec3_t flatforward; + + if (pm->ps->pm_time) { + return qfalse; + } + + // check for water jump + if ( pm->waterlevel != 2 ) { + return qfalse; + } + + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + VectorNormalize (flatforward); + + VectorMA (pm->ps->origin, 30, flatforward, spot); + spot[2] += 4; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( !(cont & CONTENTS_SOLID) ) { + return qfalse; + } + + spot[2] += 16; + cont = pm->pointcontents (spot, pm->ps->clientNum ); + if ( cont ) { + return qfalse; + } + + // jump out of water + VectorScale (pml.forward, 200, pm->ps->velocity); + pm->ps->velocity[2] = 350; + + pm->ps->pm_flags |= PMF_TIME_WATERJUMP; + pm->ps->pm_time = 2000; + + return qtrue; +} + +//============================================================================ + + +/* +=================== +PM_WaterJumpMove + +Flying out of the water +=================== +*/ +static void PM_WaterJumpMove( void ) { + // waterjump has no control, but falls + + PM_StepSlideMove( qtrue ); + + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + if (pm->ps->velocity[2] < 0) { + // cancel as soon as we are falling down again + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +static void PM_WaterMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float vel; + + if ( PM_CheckWaterJump() ) { + PM_WaterJumpMove(); + return; + } +#if 0 + // jump = head for surface + if ( pm->cmd.upmove >= 10 ) { + if (pm->ps->velocity[2] > -300) { + if ( pm->watertype == CONTENTS_WATER ) { + pm->ps->velocity[2] = 100; + } else if (pm->watertype == CONTENTS_SLIME) { + pm->ps->velocity[2] = 80; + } else { + pm->ps->velocity[2] = 50; + } + } + } +#endif + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = -60; // sink towards bottom + } else { + for (i=0 ; i<3 ; i++) + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + if ( wishspeed > pm->ps->speed * pm_swimScale ) { + wishspeed = pm->ps->speed * pm_swimScale; + } + + PM_Accelerate (wishdir, wishspeed, pm_wateraccelerate); + + // make sure we can go up slopes easily under water + if ( pml.groundPlane && DotProduct( pm->ps->velocity, pml.groundTrace.plane.normal ) < 0 ) { + vel = VectorLength(pm->ps->velocity); + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + } + + PM_SlideMove( qfalse ); +} + +#ifdef MISSIONPACK +/* +=================== +PM_InvulnerabilityMove + +Only with the invulnerability powerup +=================== +*/ +static void PM_InvulnerabilityMove( void ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + VectorClear(pm->ps->velocity); +} +#endif + +/* +=================== +PM_FlyMove + +Only with the flight powerup +=================== +*/ +static void PM_FlyMove( void ) { + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + + // normal slowdown + PM_Friction (); + + scale = PM_CmdScale( &pm->cmd ); + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = 0; + } else { + for (i=0 ; i<3 ; i++) { + wishvel[i] = scale * pml.forward[i]*pm->cmd.forwardmove + scale * pml.right[i]*pm->cmd.rightmove; + } + + wishvel[2] += scale * pm->cmd.upmove; + } + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + + PM_Accelerate (wishdir, wishspeed, pm_flyaccelerate); + + PM_StepSlideMove( qfalse ); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +static void PM_AirMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + + PM_Friction(); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + for ( i = 0 ; i < 2 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + // not on ground, so little effect on velocity + PM_Accelerate (wishdir, wishspeed, pm_airaccelerate); + + // we may have a ground plane that is very steep, even + // though we don't have a groundentity + // slide along the steep plane + if ( pml.groundPlane ) { + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + +#if 0 + //ZOID: If we are on the grapple, try stair-stepping + //this allows a player to use the grapple to pull himself + //over a ledge + if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) + PM_StepSlideMove ( qtrue ); + else + PM_SlideMove ( qtrue ); +#endif + + PM_StepSlideMove ( qtrue ); +} + +//PKMOD - Ergodic 07/04/01 - Create separate dragon tongue speeds +#define DRAGON_TONGUE_PLAYER_PULL_SPEED 800 + +/* +=================== +PM_GrappleMove + +=================== +*/ +static void PM_GrappleMove( void ) { + vec3_t vel, v; + float vlen; + + VectorScale(pml.forward, -16, v); + VectorAdd(pm->ps->grapplePoint, v, v); + VectorSubtract(v, pm->ps->origin, vel); + vlen = VectorLength(vel); + VectorNormalize( vel ); + + if (vlen <= 100) + VectorScale(vel, 10 * vlen, vel); + else + VectorScale(vel, DRAGON_TONGUE_PLAYER_PULL_SPEED, vel); + + VectorCopy(vel, pm->ps->velocity); + + pml.groundPlane = qfalse; +} + +/* +=================== +PM_WalkMove + +=================== +*/ +static void PM_WalkMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + + if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { + // begin swimming + PM_WaterMove(); + return; + } + + + if ( PM_CheckJump () ) { + // jumped away + if ( pm->waterlevel > 1 ) { + PM_WaterMove(); + } else { + PM_AirMove(); + } + return; + } + + PM_Friction (); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + cmd = pm->cmd; + scale = PM_CmdScale( &cmd ); + + // set the movementDir so clients can rotate the legs for strafing + PM_SetMovementDir(); + + // project moves down to flat plane + pml.forward[2] = 0; + pml.right[2] = 0; + + // project the forward and right directions onto the ground plane + PM_ClipVelocity (pml.forward, pml.groundTrace.plane.normal, pml.forward, OVERCLIP ); + PM_ClipVelocity (pml.right, pml.groundTrace.plane.normal, pml.right, OVERCLIP ); + // + VectorNormalize (pml.forward); + VectorNormalize (pml.right); + + for ( i = 0 ; i < 3 ; i++ ) { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + // when going up or down slopes the wish velocity should Not be zero +// wishvel[2] = 0; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + // clamp the speed lower if ducking + if ( pm->ps->pm_flags & PMF_DUCKED ) { + if ( wishspeed > pm->ps->speed * pm_duckScale ) { + wishspeed = pm->ps->speed * pm_duckScale; + } + } + + // clamp the speed lower if wading or walking on the bottom + if ( pm->waterlevel ) { + float waterScale; + + waterScale = pm->waterlevel / 3.0; + waterScale = 1.0 - ( 1.0 - pm_swimScale ) * waterScale; + if ( wishspeed > pm->ps->speed * waterScale ) { + wishspeed = pm->ps->speed * waterScale; + } + } + + // when a player gets hit, they temporarily lose + // full control, which allows them to be moved a bit + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { + accelerate = pm_airaccelerate; + } else { + accelerate = pm_accelerate; + } + + PM_Accelerate (wishdir, wishspeed, accelerate); + + //Com_Printf("velocity = %1.1f %1.1f %1.1f\n", pm->ps->velocity[0], pm->ps->velocity[1], pm->ps->velocity[2]); + //Com_Printf("velocity1 = %1.1f\n", VectorLength(pm->ps->velocity)); + + if ( ( pml.groundTrace.surfaceFlags & SURF_SLICK ) || pm->ps->pm_flags & PMF_TIME_KNOCKBACK ) { + pm->ps->velocity[2] -= pm->ps->gravity * pml.frametime; + } else { + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + } + + vel = VectorLength(pm->ps->velocity); + + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + + // don't decrease velocity when going up or down a slope + VectorNormalize(pm->ps->velocity); + VectorScale(pm->ps->velocity, vel, pm->ps->velocity); + + // don't do anything if standing still + if (!pm->ps->velocity[0] && !pm->ps->velocity[1]) { + return; + } + + PM_StepSlideMove( qfalse ); + + //Com_Printf("velocity2 = %1.1f\n", VectorLength(pm->ps->velocity)); + +} + + +/* +============== +PM_DeadMove +============== +*/ +static void PM_DeadMove( void ) { + float forward; + + if ( !pml.walking ) { + return; + } + + // extra friction + + forward = VectorLength (pm->ps->velocity); + forward -= 20; + if ( forward <= 0 ) { + VectorClear (pm->ps->velocity); + } else { + VectorNormalize (pm->ps->velocity); + VectorScale (pm->ps->velocity, forward, pm->ps->velocity); + } +} + + +/* +=============== +PM_NoclipMove +=============== +*/ +static void PM_NoclipMove( void ) { + float speed, drop, friction, control, newspeed; + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed; + float scale; + + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + // friction + + speed = VectorLength (pm->ps->velocity); + if (speed < 1) + { + VectorCopy (vec3_origin, pm->ps->velocity); + } + else + { + drop = 0; + + friction = pm_friction*1.5; // extra friction + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*friction*pml.frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + VectorScale (pm->ps->velocity, newspeed, pm->ps->velocity); + } + + // accelerate + scale = PM_CmdScale( &pm->cmd ); + + fmove = pm->cmd.forwardmove; + smove = pm->cmd.rightmove; + + for (i=0 ; i<3 ; i++) + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + wishvel[2] += pm->cmd.upmove; + + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + + PM_Accelerate( wishdir, wishspeed, pm_accelerate ); + + // move + VectorMA (pm->ps->origin, pml.frametime, pm->ps->velocity, pm->ps->origin); +} + +//============================================================================ + +/* +================ +PM_FootstepForSurface + +Returns an event number apropriate for the groundsurface +================ +*/ +static int PM_FootstepForSurface( void ) { + if ( pml.groundTrace.surfaceFlags & SURF_NOSTEPS ) { + return 0; + } + if ( pml.groundTrace.surfaceFlags & SURF_METALSTEPS ) { + return EV_FOOTSTEP_METAL; + } + return EV_FOOTSTEP; +} + + +/* +================= +PM_CrashLand + +Check for hard landings that generate sound events +================= +*/ +static void PM_CrashLand( void ) { + float delta; + float dist; + float vel, acc; + float t; + float a, b, c, den; + + // decide which landing animation to use + if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { + PM_ForceLegsAnim( LEGS_LANDB ); + } else { + PM_ForceLegsAnim( LEGS_LAND ); + } + + pm->ps->legsTimer = TIMER_LAND; + + // calculate the exact velocity on landing + dist = pm->ps->origin[2] - pml.previous_origin[2]; + vel = pml.previous_velocity[2]; + acc = -pm->ps->gravity; + + a = acc / 2; + b = vel; + c = -dist; + + den = b * b - 4 * a * c; + if ( den < 0 ) { + return; + } + t = (-b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta*delta * 0.0001; + + // ducking while falling doubles damage + if ( pm->ps->pm_flags & PMF_DUCKED ) { + delta *= 2; + } + + // never take falling damage if completely underwater + if ( pm->waterlevel == 3 ) { + return; + } + + // reduce falling damage if there is standing water + if ( pm->waterlevel == 2 ) { + delta *= 0.25; + } + if ( pm->waterlevel == 1 ) { + delta *= 0.5; + } + + if ( delta < 1 ) { + return; + } + + //PKMOD - Ergodic 12/29/00 - debug surface flags +// Com_Printf("PM_CrashLand - surfaceFlags>%d<\n", pml.groundTrace.surfaceFlags ); + + // create a local entity event to play the sound + + // SURF_NODAMAGE is used for bounce pads where you don't ever + // want to take damage or play a crunch sound + if ( !(pml.groundTrace.surfaceFlags & SURF_NODAMAGE) ) { + if ( delta > 60 ) { + PM_AddEvent( EV_FALL_FAR ); + } else if ( delta > 40 ) { + // this is a pain grunt, so don't play it if dead + if ( pm->ps->stats[STAT_HEALTH] > 0 ) { + PM_AddEvent( EV_FALL_MEDIUM ); + } + } else if ( delta > 7 ) { + PM_AddEvent( EV_FALL_SHORT ); + } else { + PM_AddEvent( PM_FootstepForSurface() ); + } + } + + // start footstep cycle over + pm->ps->bobCycle = 0; +} + +/* +============= +PM_CheckStuck +============= +*/ +/* +void PM_CheckStuck(void) { + trace_t trace; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask); + if (trace.allsolid) { + //int shit = qtrue; + } +} +*/ + +/* +============= +PM_CorrectAllSolid +============= +*/ +static int PM_CorrectAllSolid( trace_t *trace ) { + int i, j, k; + vec3_t point; + + if ( pm->debugLevel ) { + Com_Printf("%i:allsolid\n", c_pmove); + } + + // jitter around + for (i = -1; i <= 1; i++) { + for (j = -1; j <= 1; j++) { + for (k = -1; k <= 1; k++) { + VectorCopy(pm->ps->origin, point); + point[0] += (float) i; + point[1] += (float) j; + point[2] += (float) k; + pm->trace (trace, point, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( !trace->allsolid ) { + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = *trace; + return qtrue; + } + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + + return qfalse; +} + + +/* +============= +PM_GroundTraceMissed + +The ground trace didn't hit a surface, so we are in freefall +============= +*/ +static void PM_GroundTraceMissed( void ) { + trace_t trace; + vec3_t point; + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) { + // we just transitioned into freefall + if ( pm->debugLevel ) { + Com_Printf("%i:lift\n", c_pmove); + } + + // if they aren't in a jumping animation and the ground is a ways away, force into it + // if we didn't do the trace, the player would be backflipping down staircases + VectorCopy( pm->ps->origin, point ); + point[2] -= 64; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 ) { + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + } + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +PM_GroundTrace +============= +*/ +static void PM_GroundTrace( void ) { + vec3_t point; + trace_t trace; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - 0.25; + + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, pm->tracemask); + pml.groundTrace = trace; + + // do something corrective if the trace starts in a solid... + if ( trace.allsolid ) { + if ( !PM_CorrectAllSolid(&trace) ) + return; + } + + // if the trace didn't hit anything, we are in free fall + if ( trace.fraction == 1.0 ) { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // check if getting thrown off the ground + if ( pm->ps->velocity[2] > 0 && DotProduct( pm->ps->velocity, trace.plane.normal ) > 10 ) { + if ( pm->debugLevel ) { + Com_Printf("%i:kickoff\n", c_pmove); + } + // go into jump animation + if ( pm->cmd.forwardmove >= 0 ) { + PM_ForceLegsAnim( LEGS_JUMP ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( LEGS_JUMPB ); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; + return; + } + + // slopes that are too steep will not be considered onground + if ( trace.plane.normal[2] < MIN_WALK_NORMAL ) { + if ( pm->debugLevel ) { + Com_Printf("%i:steep\n", c_pmove); + } + // FIXME: if they can't slide down the slope, let them + // walk (sharp crevices) + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qtrue; + pml.walking = qfalse; + return; + } + + pml.groundPlane = qtrue; + pml.walking = qtrue; + + // hitting solid ground will end a waterjump + if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) + { + pm->ps->pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND); + pm->ps->pm_time = 0; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + // just hit the ground + if ( pm->debugLevel ) { + Com_Printf("%i:Land\n", c_pmove); + } + + PM_CrashLand(); + + // don't do landing time if we were just going down a slope + if ( pml.previous_velocity[2] < -200 ) { + // don't allow another jump for a little while + pm->ps->pm_flags |= PMF_TIME_LAND; + pm->ps->pm_time = 250; + } + } + + pm->ps->groundEntityNum = trace.entityNum; + + // don't reset the z velocity for slopes +// pm->ps->velocity[2] = 0; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +PM_SetWaterLevel FIXME: avoid this twice? certainly if not moving +============= +*/ +static void PM_SetWaterLevel( void ) { + vec3_t point; + int cont; + int sample1; + int sample2; + + // + // get waterlevel, accounting for ducking + // + pm->waterlevel = 0; + pm->watertype = 0; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] + MINS_Z + 1; + cont = pm->pointcontents( point, pm->ps->clientNum ); + + if ( cont & MASK_WATER ) { + sample2 = pm->ps->viewheight - MINS_Z; + sample1 = sample2 / 2; + + pm->watertype = cont; + pm->waterlevel = 1; + point[2] = pm->ps->origin[2] + MINS_Z + sample1; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ) { + pm->waterlevel = 2; + point[2] = pm->ps->origin[2] + MINS_Z + sample2; + cont = pm->pointcontents (point, pm->ps->clientNum ); + if ( cont & MASK_WATER ){ + pm->waterlevel = 3; + } + } + } + +} + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +static void PM_CheckDuck (void) +{ + trace_t trace; + + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + if ( pm->ps->pm_flags & PMF_INVULEXPAND ) { + // invulnerability sphere has a 42 units radius + VectorSet( pm->mins, -42, -42, -42 ); + VectorSet( pm->maxs, 42, 42, 42 ); + } + else { + VectorSet( pm->mins, -15, -15, MINS_Z ); + VectorSet( pm->maxs, 15, 15, 16 ); + } + pm->ps->pm_flags |= PMF_DUCKED; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + return; + } + pm->ps->pm_flags &= ~PMF_INVULEXPAND; + + pm->mins[0] = -15; + pm->mins[1] = -15; + + pm->maxs[0] = 15; + pm->maxs[1] = 15; + + pm->mins[2] = MINS_Z; + + if (pm->ps->pm_type == PM_DEAD) + { + pm->maxs[2] = -8; + pm->ps->viewheight = DEAD_VIEWHEIGHT; + return; + } + + if (pm->cmd.upmove < 0) + { // duck + pm->ps->pm_flags |= PMF_DUCKED; + } + else + { // stand up if possible + if (pm->ps->pm_flags & PMF_DUCKED) + { + // try to stand up + pm->maxs[2] = 32; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); + if (!trace.allsolid) + pm->ps->pm_flags &= ~PMF_DUCKED; + } + } + + if (pm->ps->pm_flags & PMF_DUCKED) + { + pm->maxs[2] = 16; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + } + else + { + pm->maxs[2] = 32; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + } +} + + + +//=================================================================== + + +/* +=============== +PM_Footsteps +=============== +*/ +static void PM_Footsteps( void ) { + float bobmove; + int old; + qboolean footstep; + + // + // calculate speed and cycle to be used for + // all cyclic walking effects + // + pm->xyspeed = sqrt( pm->ps->velocity[0] * pm->ps->velocity[0] + + pm->ps->velocity[1] * pm->ps->velocity[1] ); + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + PM_ContinueLegsAnim( LEGS_IDLECR ); + } + // airborne leaves position in cycle intact, but doesn't advance + if ( pm->waterlevel > 1 ) { + PM_ContinueLegsAnim( LEGS_SWIM ); + } + return; + } + + // if not trying to move + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { + if ( pm->xyspeed < 5 ) { + pm->ps->bobCycle = 0; // start at beginning of cycle again + if ( pm->ps->pm_flags & PMF_DUCKED ) { + PM_ContinueLegsAnim( LEGS_IDLECR ); + } else { + PM_ContinueLegsAnim( LEGS_IDLE ); + } + } + return; + } + + + footstep = qfalse; + + if ( pm->ps->pm_flags & PMF_DUCKED ) { + bobmove = 0.5; // ducked characters bob much faster + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + PM_ContinueLegsAnim( LEGS_BACKCR ); + } + else { + PM_ContinueLegsAnim( LEGS_WALKCR ); + } + // ducked characters never play footsteps + /* + } else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4; // faster speeds bob faster + footstep = qtrue; + } else { + bobmove = 0.3; + } + PM_ContinueLegsAnim( LEGS_BACK ); + */ + } else { + if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) { + bobmove = 0.4f; // faster speeds bob faster + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + PM_ContinueLegsAnim( LEGS_BACK ); + } + else { + PM_ContinueLegsAnim( LEGS_RUN ); + } + footstep = qtrue; + } else { + bobmove = 0.3f; // walking bobs slow + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + PM_ContinueLegsAnim( LEGS_BACKWALK ); + } + else { + PM_ContinueLegsAnim( LEGS_WALK ); + } + } + } + + // check for footstep / splash sounds + old = pm->ps->bobCycle; + pm->ps->bobCycle = (int)( old + bobmove * pml.msec ) & 255; + + // if we just crossed a cycle boundary, play an apropriate footstep event + if ( ( ( old + 64 ) ^ ( pm->ps->bobCycle + 64 ) ) & 128 ) { + if ( pm->waterlevel == 0 ) { + // on ground will only play sounds if running + if ( footstep && !pm->noFootsteps ) { + PM_AddEvent( PM_FootstepForSurface() ); + } + } else if ( pm->waterlevel == 1 ) { + // splashing + PM_AddEvent( EV_FOOTSPLASH ); + } else if ( pm->waterlevel == 2 ) { + // wading / swimming at surface + PM_AddEvent( EV_SWIM ); + } else if ( pm->waterlevel == 3 ) { + // no sound when completely underwater + + } + } +} + +/* +============== +PM_WaterEvents + +Generate sound events for entering and leaving water +============== +*/ +static void PM_WaterEvents( void ) { // FIXME? + // + // if just entered a water volume, play a sound + // + if (!pml.previous_waterlevel && pm->waterlevel) { + PM_AddEvent( EV_WATER_TOUCH ); + } + + // + // if just completely exited a water volume, play a sound + // + if (pml.previous_waterlevel && !pm->waterlevel) { + PM_AddEvent( EV_WATER_LEAVE ); + } + + // + // check for head just going under water + // + if (pml.previous_waterlevel != 3 && pm->waterlevel == 3) { + PM_AddEvent( EV_WATER_UNDER ); + } + + // + // check for head just coming out of water + // + if (pml.previous_waterlevel == 3 && pm->waterlevel != 3) { + PM_AddEvent( EV_WATER_CLEAR ); + } +} + + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +static void PM_BeginWeaponChange( int weapon ) { + if ( weapon <= WP_NONE || weapon >= WP_NUM_WEAPONS ) { + return; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + return; + } + + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + return; + } + + //PKMOD - Ergodic 04/04/01, save the current weapon +// if ( pm->ps->stats[ STAT_LAST_WEAPON ] != weapon ) + pm->ps->stats[ STAT_LAST_WEAPON ] = pm->ps->weapon; + + //PKMOD - Ergodic 08/23/01 - debug infinite ammo (inactive) +// Com_Printf( "PM_BeginWeaponChange - current>%d<, new>%d<, last>%d<, BT ammo>%d<, generic1>%d<\n", pm->ps->weapon, weapon, pm->ps->stats[ STAT_LAST_WEAPON ], pm->ps->ammo[ WP_BEARTRAP ], pm->ps->generic1 ); + + //PKMOD - Ergodic 03/14/01 - Dragon Deploy:if changing a weapon then remove alternate firing mechanism + if ( pm->ps->generic1 != 0 ) { + int hold_deploy_weapon; + + hold_deploy_weapon = pm->ps->generic1 & 15; + //PKMOD - Ergodic 08/23/01 - check for zero ammo + // if ammo is less then 1 then remove alternate fire + //PKMOD - Ergodic 12/14/01 - don't check deployed gauntlet for out of ammo state + //PKMOD - Ergodic 02/21/04 - don't check for RED or BLUE flags for out of ammo state + if ( ( hold_deploy_weapon != WP_GAUNTLET ) && ( hold_deploy_weapon != PW_REDFLAG ) && ( hold_deploy_weapon != PW_BLUEFLAG ) ) { + + if ( pm->ps->ammo[ hold_deploy_weapon ] < 1 ) + //turn off the alternate fire + pm->ps->generic1 = 0; + } + else { + if ( pm->ps->generic1 & 64 ) //mask all bits off except the toggle bit + //here if toggle bit is set + //turn off the alternate fire + pm->ps->generic1 = 0; + else + //set the toggle bit + pm->ps->generic1 |= 64; + } + } + + PM_AddEvent( EV_CHANGE_WEAPON ); + pm->ps->weaponstate = WEAPON_DROPPING; + //PKMOD changed from 200 to 50 + pm->ps->weaponTime += 50; + //PKMOD + PM_StartTorsoAnim( TORSO_DROP ); +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +static void PM_FinishWeaponChange( void ) { + int weapon; + + weapon = pm->cmd.weapon; + if ( weapon < WP_NONE || weapon >= WP_NUM_WEAPONS ) { + weapon = WP_NONE; + } + + if ( !( pm->ps->stats[STAT_WEAPONS] & ( 1 << weapon ) ) ) { + weapon = WP_NONE; + } + + pm->ps->weapon = weapon; + pm->ps->weaponstate = WEAPON_RAISING; + //PKMOD changed from 250 to 50 + pm->ps->weaponTime += 50; + //PKMOD + PM_StartTorsoAnim( TORSO_RAISE ); + + //PKMOD - Ergodic 08/23/01 - debug infinite ammo (inactive) +// Com_Printf( "PM_FinishWeaponChange - current>%d<, new>%d<, last>%d<, BT ammo>%d<, generic1>%d<\n", pm->ps->weapon, weapon, pm->ps->stats[ STAT_LAST_WEAPON ], pm->ps->ammo[ WP_BEARTRAP ], pm->ps->generic1 ); + +} + + +/* +============== +PM_TorsoAnimation + +============== +*/ +static void PM_TorsoAnimation( void ) { + if ( pm->ps->weaponstate == WEAPON_READY ) { + //PKMOD - Ergodic 09/05/00 use alternate stance for the dragon + if ( ( pm->ps->weapon == WP_GAUNTLET ) || ( pm->ps->weapon == WP_GRAPPLING_HOOK ) ) { + PM_ContinueTorsoAnim( TORSO_STAND2 ); + } else { + PM_ContinueTorsoAnim( TORSO_STAND ); + } + return; + } +} + +/* +============== +PM_Weapon + +Generates weapon events and modifes the weapon counter +============== +*/ +static void PM_Weapon( void ) { + int addTime; +//PKMOD Erodgic 06/27/00 add hold_weapon as temporary variable + int hold_weapon; + + //PKMOD - Ergodic 05/11/01 - allow holding of more than 1 type of + // holdable but only 1 of each kind + // add variables for holdable cycling + int i; + int holdable_index; + + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // ignore if spectator + if ( pm->ps->persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + return; + } + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->ps->weapon = WP_NONE; + return; + } + + // check for item using + if ( pm->cmd.buttons & BUTTON_USE_HOLDABLE ) { + if ( ! ( pm->ps->pm_flags & PMF_USE_ITEM_HELD ) ) { + //PKMOD - Ergodic 05/11/01 - allow holding of more than 1 type of + // holdable but only 1 of each kind + //PKMOD - Ergodic 05/11/01 - modify logic +// if ( bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag == HI_MEDKIT + if ( pm->ps->stats[STAT_ACTIVE_HOLDABLE] == HI_MEDKIT + && pm->ps->stats[STAT_HEALTH] >= (pm->ps->stats[STAT_MAX_HEALTH] + 25) ) { + // don't use medkit if at max health + } else { + //PKMOD - Ergodic 02/26/04 - move this confirmation flag elsewhere to fix multiple Private Bots + //pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + + //PKMOD - Ergodic 05/11/01 - modify logic +// PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag ); + //PKMOD - Ergodic 12/07/01 - if active item is a bot piece then check for all parts + if ( ( pm->ps->stats[STAT_ACTIVE_HOLDABLE] >= HI_BOTLEGS ) && ( pm->ps->stats[STAT_ACTIVE_HOLDABLE] <= HI_BOTHEAD ) ) { + //check for all bot pieces + if ( ( pm->ps->stats[STAT_HOLDABLE_ITEM] & ( 7 << HI_BOTLEGS ) ) != ( 7 << HI_BOTLEGS ) ) { + //PKMOD - Ergodic 01/07/02 - send message to client if not all Private Bot parts are held + PM_AddEvent( EV_INCOMPLETE_PRIVATEBOT ); + return; //if we don't have all the pieces + } + //PKMOD - Ergodic 03/18/02 - check if room for another Private Bot + if ( active_private_bots < MAX_PRIVATE_BOTS ) { + //set the Private Bot event to be handled in g_active + PM_AddEvent( EV_USE_ITEM0 + HI_BOTHEAD ); + pm->ps->stats[STAT_HOLDABLE_ITEM] &= ~( 7 << HI_BOTLEGS ); //remove all Private Bot items + //PKMOD - Ergodic 02/27/04 - do not increment active Private Bot count here + // move increment to g_active + //active_private_bots++; + //PKMOD - Ergodic 02/26/04 - debug Private Bot availability message (inactive) + //Com_Printf("PM_Weapon LT - active_private_bots>%d<, MAX_PRIVATE_BOTS>%d<\n", active_private_bots, MAX_PRIVATE_BOTS ); + + } + else { + //PKMOD - Ergodic 02/26/04 - debug Private Bot availability message (inactive) + //Com_Printf("PM_Weapon GE - active_private_bots>%d<, MAX_PRIVATE_BOTS>%d<\n", active_private_bots, MAX_PRIVATE_BOTS ); + //PKMOD - Ergodic 03/18/02 - send message to client that no more Private Bots are available + PM_AddEvent( EV_NOAVAILABLE_PRIVATEBOTS ); + return; //02/26/04 - exit if at maximum private bots + } + } + else { + //not a bot holdable + PM_AddEvent( EV_USE_ITEM0 + pm->ps->stats[STAT_ACTIVE_HOLDABLE] ); + //PKMOD - Ergodic 05/11/01 - remove the used holdable item +// pm->ps->stats[STAT_HOLDABLE_ITEM] = 0; + pm->ps->stats[STAT_HOLDABLE_ITEM] &= ~(1 << pm->ps->stats[STAT_ACTIVE_HOLDABLE]); + + //PKMOD - Ergodic 02/26/04 - debug Private Bot availability message (inactive) + //Com_Printf("PM_Weapon GE - Non private_bot holdable>%d<, MAX_PRIVATE_BOTS>%d<\n", pm->ps->stats[STAT_ACTIVE_HOLDABLE] ); + } + + //PKMOD - Ergodic 02/26/04 - set confirmation flag here to fix multiple Private Bots message + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + + //reset the active holdable + if ( pm->ps->stats[STAT_HOLDABLE_ITEM] ) { //do we have any more holdables + holdable_index = pm->ps->stats[STAT_ACTIVE_HOLDABLE]; + for (i = 0; i < HI_NUM_HOLDABLE; i++) { + if ( pm->ps->stats[STAT_HOLDABLE_ITEM] & ( 1 << holdable_index ) ) { + pm->ps->stats[STAT_ACTIVE_HOLDABLE] = holdable_index; + break; + } + //set next index, wrap around if at the end of the list + holdable_index += 1; + if ( holdable_index >= HI_NUM_HOLDABLE ) + holdable_index = 1; + } + } + else + pm->ps->stats[STAT_ACTIVE_HOLDABLE] = 0; + + } + return; + } + } else { + pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; + } + + + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + } + + // check for weapon change + // can't change if weapon is firing, but can change + // again if lowering or raising + if ( pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING ) { + if ( pm->ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + + if ( pm->ps->weaponTime > 0 ) { + return; + } + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + if ( pm->ps->weaponstate == WEAPON_RAISING ) { + pm->ps->weaponstate = WEAPON_READY; + //PKMOD - Ergodic 09/05/00 use alternate stance for the dragon + if ( ( pm->ps->weapon == WP_GAUNTLET ) || ( pm->ps->weapon == WP_GRAPPLING_HOOK ) ) { + PM_StartTorsoAnim( TORSO_STAND2 ); + } else { + PM_StartTorsoAnim( TORSO_STAND ); + } + return; + } + + //PKMOD - Ergodic 07/06/00 - recheck out of ammo for PKA Items + // check for out of ammo + if ( ! pm->ps->ammo[ pm->ps->weapon ] ) { + //PKMOD Ergodic 05/30/00 switch to gauntlet for PK Items noammo state + //PKMOD Ergodic 08/24/01 - change from "switch" to "if" logic + hold_weapon = pm->ps->weapon; + if ( ( hold_weapon == WP_BEARTRAP ) || ( hold_weapon == WP_BEANS ) || ( hold_weapon == WP_SENTRY ) || ( hold_weapon == WP_GRAVITY ) ) { + PM_AddEvent( EV_PKA_NOAMMO ); + //PKMOD - Ergodic 06/27/00 physically remove the weapon from inventory + pm->ps->stats[STAT_WEAPONS] &= ~( 1 << hold_weapon ); + //PKMOD Ergodic 06/10/00 cycle to no weapon (empty handed) + pm->ps->weapon = WP_GAUNTLET; + pm->ps->weaponTime += 500; + return; + } + + } + + // check for fire + if ( ! (pm->cmd.buttons & BUTTON_ATTACK) ) { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + + // start the animation even if out of ammo + if ( pm->ps->weapon == WP_GAUNTLET ) { + // the guantlet only "fires" when it actually hits something + if ( !pm->gauntletHit ) { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + PM_StartTorsoAnim( TORSO_ATTACK2 ); + } else { + //PKMOD - Ergodic 09/05/00 use alternate stance for the dragon + if ( pm->ps->weapon == WP_GRAPPLING_HOOK ) { + PM_StartTorsoAnim( TORSO_ATTACK2 ); + } + else { + PM_StartTorsoAnim( TORSO_ATTACK ); + } + } + + pm->ps->weaponstate = WEAPON_FIRING; + + //PKMOD - Ergodic 03/13/01 - if firing weapon then remove alternate firing mechanism +// pm->ps->generic1 = 0; + + // check for out of ammo +//PKMOD - Ergodic 05/18/01 - Airfist has displayable ammo corresponding to airfist_level +// airfist may have "useable" zero ammo + if ( ( ! pm->ps->ammo[ pm->ps->weapon ] ) && ( pm->ps->weapon != WP_AIRFIST ) ) { + //PKMOD Ergodic 05/30/00 switch to gauntlet for PK Items noammo state + //PKMOD Ergodic 08/24/01 - change from "switch" to "if" logic + hold_weapon = pm->ps->weapon; + if ( ( hold_weapon == WP_BEARTRAP ) || ( hold_weapon == WP_BEANS ) || ( hold_weapon == WP_SENTRY ) || ( hold_weapon == WP_GRAVITY ) ) { + PM_AddEvent( EV_PKA_NOAMMO ); + //PKMOD - Ergodic 06/27/00 physically remove the weapon from inventory + pm->ps->stats[STAT_WEAPONS] &= ~( 1 << hold_weapon ); + //PKMOD Ergodic 06/10/00 cycle to no weapon (empty handed) + pm->ps->weapon = WP_NONE; + } + else { + PM_AddEvent( EV_NOAMMO ); + } + pm->ps->weaponTime += 500; + return; + } + + // take an ammo away if not infinite + if ( pm->ps->ammo[ pm->ps->weapon ] != -1 ) { + pm->ps->ammo[ pm->ps->weapon ]--; + //PKMOD - Ergodic 05/18/01 - Airfist has displayable ammo corresponding to airfist_level + // do not decrement ammo past zero + if ( ( pm->ps->weapon == WP_AIRFIST ) && ( pm->ps->ammo[ pm->ps->weapon ] < 0 ) ) + pm->ps->ammo[ pm->ps->weapon ] = 0; + } + + //PKMOD - Ergodic 08/21/01 - debug infinite ammo bug (inactive) +// Com_Printf("PM_Weapon - before PM_AddEvent( EV_FIRE_WEAPON )\n" ); + + + // fire weapon + PM_AddEvent( EV_FIRE_WEAPON ); + + //PKMOD - Ergodic 08/21/01 - debug infinite ammo bug (inactive) +// Com_Printf("PM_Weapon - after PM_AddEvent( EV_FIRE_WEAPON )\n" ); + + + //PKMOD - Ergodic 08/21/01 - decrement ammo in client for pka items + // Note: this code was added to fix int infinite ammo bug + /* + if ( ( pm->ps->weapon == WP_BEARTRAP ) || ( pm->ps->weapon == WP_SENTRY ) || ( pm->ps->weapon == WP_BEANS ) || ( pm->ps->weapon == WP_GRAVITY ) ) { + if ( ! pm->ps->ammo[ pm->ps->weapon ] ) { + //PKMOD Ergodic 05/30/00 switch to gauntlet for PK Items noammo state + hold_weapon = pm->ps->weapon; + PM_AddEvent( EV_PKA_NOAMMO ); + //PKMOD - Ergodic 06/27/00 physically remove the weapon from inventory + pm->ps->stats[STAT_WEAPONS] &= ~( 1 << hold_weapon ); + //PKMOD Ergodic 06/10/00 cycle to no weapon (empty handed) + pm->ps->weapon = WP_GAUNTLET; + pm->ps->weaponTime += 500; + return; + } + } + */ + + + switch( pm->ps->weapon ) { + default: + case WP_GAUNTLET: + addTime = 400; + break; + case WP_LIGHTNING: + addTime = 50; + break; + case WP_SHOTGUN: + addTime = 1000; + break; + case WP_MACHINEGUN: + addTime = 100; + break; + case WP_GRENADE_LAUNCHER: + addTime = 800; + break; + case WP_ROCKET_LAUNCHER: + addTime = 800; + break; + case WP_PLASMAGUN: + addTime = 100; + break; + case WP_RAILGUN: + addTime = 1500; + break; + case WP_BFG: + addTime = 200; + break; + case WP_GRAPPLING_HOOK: + addTime = 400; + break; + //PKMOD + //PKMOD Ergodic - 03/29/01 - reduce GW firing time so it can be fisted more quickly (was 1000) + case WP_GRAVITY: + addTime = 100; + break; + //This is NOT the rate of fire of the Sentry itself + case WP_SENTRY: + addTime = 800; + break; + case WP_BEARTRAP: + addTime = 800; + break; + case WP_BEANS: + addTime = 800; + break; + case WP_CHAINLG: + addTime = 50; + break; +//PKMOD Ergodic - 03/25/2000, reset AirFist firing rates from 50 to 600 +//PKMOD Ergodic - 02/02/04, reset AirFist firing rates from 600 to 500 + case WP_AIRFIST: + addTime = 500; + break; +//PKMOD Ergodic - 07/28/2000, reset nailgun rate 500 to 150 + case WP_NAILGUN: + addTime = 150; + break; +//PKMOD Ergodic - 01/22/2001 add firing delay to exploding shells gun +// before this fix expl shells would fire way too fast + case WP_EXPLODING_SHELLS: + addTime = 500; + break; + //PKMOD + +#ifdef MISSIONPACK + case WP_NAILGUN: + addTime = 1000; + break; + case WP_PROX_LAUNCHER: + addTime = 800; + break; + case WP_CHAINGUN: + addTime = 30; + break; +#endif + + } + +#ifdef MISSIONPACK + if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + addTime /= 1.5; + } + else + if( bg_itemlist[pm->ps->stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { + addTime /= 1.3; + } + else +#endif + if ( pm->ps->powerups[PW_HASTE] ) { + addTime /= 1.3; + } + + pm->ps->weaponTime += addTime; +} + +/* +================ +PM_Animate +================ +*/ + +static void PM_Animate( void ) { + if ( pm->cmd.buttons & BUTTON_GESTURE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GESTURE ); + pm->ps->torsoTimer = TIMER_GESTURE; + PM_AddEvent( EV_TAUNT ); + } +#ifdef MISSIONPACK + } else if ( pm->cmd.buttons & BUTTON_GETFLAG ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GETFLAG ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_GUARDBASE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_GUARDBASE ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_PATROL ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_PATROL ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_FOLLOWME ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_FOLLOWME ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_AFFIRMATIVE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_AFFIRMATIVE); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } + } else if ( pm->cmd.buttons & BUTTON_NEGATIVE ) { + if ( pm->ps->torsoTimer == 0 ) { + PM_StartTorsoAnim( TORSO_NEGATIVE ); + pm->ps->torsoTimer = 600; //TIMER_GESTURE; + } +#endif + } +} + + +/* +================ +PM_DropTimers +================ +*/ +static void PM_DropTimers( void ) { + // drop misc timing counter + if ( pm->ps->pm_time ) { + if ( pml.msec >= pm->ps->pm_time ) { + pm->ps->pm_flags &= ~PMF_ALL_TIMES; + pm->ps->pm_time = 0; + } else { + pm->ps->pm_time -= pml.msec; + } + } + + // drop animation counter + if ( pm->ps->legsTimer > 0 ) { + pm->ps->legsTimer -= pml.msec; + if ( pm->ps->legsTimer < 0 ) { + pm->ps->legsTimer = 0; + } + } + + if ( pm->ps->torsoTimer > 0 ) { + pm->ps->torsoTimer -= pml.msec; + if ( pm->ps->torsoTimer < 0 ) { + pm->ps->torsoTimer = 0; + } + } +} + +/* +================ +PM_UpdateViewAngles + +This can be used as another entry point when only the viewangles +are being updated isntead of a full move +================ +*/ +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { + short temp; + int i; + + if ( ps->pm_type == PM_INTERMISSION || ps->pm_type == PM_SPINTERMISSION) { + return; // no view changes at all + } + + if ( ps->pm_type != PM_SPECTATOR && ps->stats[STAT_HEALTH] <= 0 ) { + return; // no view changes at all + } + + // circularly clamp the angles with deltas + for (i=0 ; i<3 ; i++) { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( i == PITCH ) { + // don't let the player look up or down more than 90 degrees + if ( temp > 16000 ) { + ps->delta_angles[i] = 16000 - cmd->angles[i]; + temp = 16000; + } else if ( temp < -16000 ) { + ps->delta_angles[i] = -16000 - cmd->angles[i]; + temp = -16000; + } + } + ps->viewangles[i] = SHORT2ANGLE(temp); + } + +} + +/* +=============== +PM_NextHoldable + +PKMOD - Ergodic - 05/12/01 +=============== +*/ +void PM_NextHoldable( void ) { + int i; + int holdable_index; + + //PKMOD - Ergodic 05/12/01 - debug non working next holdable (inactive) +// Com_Printf("PM_NextHoldable - start, active>%d<, items>%d<\n", pm->ps->stats[STAT_ACTIVE_HOLDABLE], pm->ps->stats[STAT_HOLDABLE_ITEM] ); + + if ( pm->ps->pm_flags & PMF_FOLLOW ) { + //PKMOD - Ergodic 05/12/01 - debug non working next holdable (inactive) +// Com_Printf("PM_NextHoldable_f - PMF_FOLLOW, return\n" ); + return; + } + + //if no holdable in inventory then exit + if ( !pm->ps->stats[STAT_HOLDABLE_ITEM] ) { + //PKMOD - Ergodic 05/12/01 - debug non working next holdable (inactive) +// Com_Printf("PM_NextHoldable_f - no holdable\n" ); + return; + } + + holdable_index = pm->ps->stats[STAT_ACTIVE_HOLDABLE]; + + for (i = 1; i < HI_NUM_HOLDABLE; i++) { + //set next index, wrap around if at the end of the list + holdable_index = holdable_index + 1; + if ( holdable_index >= HI_NUM_HOLDABLE ) + holdable_index = 1; + + //PKMOD - Ergodic 12/07/01 - Private Bot navigation - always set index to HI_BOTHEAD + if ( ( holdable_index >= HI_BOTLEGS ) && ( holdable_index <= HI_BOTHEAD )) { + if ( pm->ps->stats[STAT_HOLDABLE_ITEM] & ( 7 << HI_BOTLEGS ) ) { + pm->ps->stats[STAT_ACTIVE_HOLDABLE] = HI_BOTHEAD; + break; + } + //PKMOD - Ergodic 12/07/01 - yes, yes, I know - we are wasting 2 cycles on the if statement + } + else if ( pm->ps->stats[STAT_HOLDABLE_ITEM] & ( 1 << holdable_index ) ) { + pm->ps->stats[STAT_ACTIVE_HOLDABLE] = holdable_index; + //PKMOD - Ergodic 05/12/01 - debug non working next holdable (inactive) +// Com_Printf("PM_NextHoldable_f - found holdable, setting>%d<, active>%d<\n", holdable_index, pm->ps->stats[STAT_ACTIVE_HOLDABLE] ); + break; + } + } + +} + + +/* +================ +PmoveSingle + +================ +*/ +void trap_SnapVector( float *v ); + +void PmoveSingle (pmove_t *pmove) { + pm = pmove; + + // this counter lets us debug movement problems with a journal + // by setting a conditional breakpoint fot the previous frame + c_pmove++; + + // clear results + pm->numtouch = 0; + pm->watertype = 0; + pm->waterlevel = 0; + + //PKMOD - Ergodic 05/12/01 - debug buttons (inactive) +// Com_Printf("PmoveSingle - commands>%d<\n", pm->cmd.buttons ); + + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + pm->tracemask &= ~CONTENTS_BODY; // corpses can fly through bodies + } + + // make sure walking button is clear if they are running, to avoid + // proxy no-footsteps cheats + if ( abs( pm->cmd.forwardmove ) > 64 || abs( pm->cmd.rightmove ) > 64 ) { + pm->cmd.buttons &= ~BUTTON_WALKING; + } + + // set the talk balloon flag + if ( pm->cmd.buttons & BUTTON_TALK ) { + pm->ps->eFlags |= EF_TALK; + } else { + pm->ps->eFlags &= ~EF_TALK; + } + + // set the firing flag for continuous beam weapons + if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && pm->ps->pm_type != PM_INTERMISSION + && ( pm->cmd.buttons & BUTTON_ATTACK ) && pm->ps->ammo[ pm->ps->weapon ] ) { + pm->ps->eFlags |= EF_FIRING; + } else { + pm->ps->eFlags &= ~EF_FIRING; + } + + // clear the respawned flag if attack and use are cleared + if ( pm->ps->stats[STAT_HEALTH] > 0 && + !( pm->cmd.buttons & (BUTTON_ATTACK | BUTTON_USE_HOLDABLE) ) ) { + pm->ps->pm_flags &= ~PMF_RESPAWNED; + } + + // if talk button is down, dissallow all other input + // this is to prevent any possible intercept proxy from + // adding fake talk balloons + if ( pmove->cmd.buttons & BUTTON_TALK ) { + // keep the talk button set tho for when the cmd.serverTime > 66 msec + // and the same cmd is used multiple times in Pmove + pmove->cmd.buttons = BUTTON_TALK; + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + } + + //PKMOD - Ergodic 05/12/01 - cycle holdables + if ( pm->cmd.buttons & BUTTON_NEXT_HOLDABLE ) { + if ( !( pm->ps->pm_flags & PMF_NEXTHOLD_HELD ) ) { + //PKMOD - Ergodic 05/12/01 - debug buttons (inactive) +// Com_Printf("PmoveSingle - pm>%d<, pmove>%d<\n", pm->cmd.buttons, pmove->cmd.buttons ); + + PM_NextHoldable(); +// pm->cmd.buttons &= ~BUTTON_NEXT_HOLDABLE; + pm->ps->pm_flags |= PMF_NEXTHOLD_HELD; + } + } + else { + pm->ps->pm_flags &= ~PMF_NEXTHOLD_HELD; + } + + // clear all pmove local vars + memset (&pml, 0, sizeof(pml)); + + // determine the time + pml.msec = pmove->cmd.serverTime - pm->ps->commandTime; + if ( pml.msec < 1 ) { + pml.msec = 1; + } else if ( pml.msec > 200 ) { + pml.msec = 200; + } + pm->ps->commandTime = pmove->cmd.serverTime; + + // save old org in case we get stuck + VectorCopy (pm->ps->origin, pml.previous_origin); + + // save old velocity for crashlanding + VectorCopy (pm->ps->velocity, pml.previous_velocity); + + pml.frametime = pml.msec * 0.001; + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up); + + if ( pm->cmd.upmove < 10 ) { + // not holding jump + pm->ps->pm_flags &= ~PMF_JUMP_HELD; + } + + // decide if backpedaling animations should be used + if ( pm->cmd.forwardmove < 0 ) { + pm->ps->pm_flags |= PMF_BACKWARDS_RUN; + } else if ( pm->cmd.forwardmove > 0 || ( pm->cmd.forwardmove == 0 && pm->cmd.rightmove ) ) { + pm->ps->pm_flags &= ~PMF_BACKWARDS_RUN; + } + + if ( pm->ps->pm_type >= PM_DEAD ) { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + if ( pm->ps->pm_type == PM_SPECTATOR ) { + PM_CheckDuck (); + PM_FlyMove (); + PM_DropTimers (); + return; + } + + if ( pm->ps->pm_type == PM_NOCLIP ) { + PM_NoclipMove (); + PM_DropTimers (); + return; + } + + if (pm->ps->pm_type == PM_FREEZE) { + return; // no movement at all + } + + if ( pm->ps->pm_type == PM_INTERMISSION || pm->ps->pm_type == PM_SPINTERMISSION) { + return; // no movement at all + } + + // set watertype, and waterlevel + PM_SetWaterLevel(); + pml.previous_waterlevel = pmove->waterlevel; + + // set mins, maxs, and viewheight + PM_CheckDuck (); + + // set groundentity + PM_GroundTrace(); + + if ( pm->ps->pm_type == PM_DEAD ) { + PM_DeadMove (); + } + + PM_DropTimers(); + +#ifdef MISSIONPACK + if ( pm->ps->powerups[PW_INVULNERABILITY] ) { + PM_InvulnerabilityMove(); + } else +#endif + if ( pm->ps->powerups[PW_FLIGHT] ) { + // flight powerup doesn't allow jump and has different friction + PM_FlyMove(); + } else if (pm->ps->pm_flags & PMF_GRAPPLE_PULL) { + PM_GrappleMove(); + // We can wiggle a bit + PM_AirMove(); + } else if (pm->ps->pm_flags & PMF_TIME_WATERJUMP) { + PM_WaterJumpMove(); + } else if ( pm->waterlevel > 1 ) { + // swimming + PM_WaterMove(); + } else if ( pml.walking ) { + // walking on ground + PM_WalkMove(); + } else { + // airborne + PM_AirMove(); + } + + PM_Animate(); + + // set groundentity, watertype, and waterlevel + PM_GroundTrace(); + PM_SetWaterLevel(); + + // weapons + PM_Weapon(); + + //PKMOD - Ergodic 04/04/01 - debug "last weapon" (inactive) +// if ( ( rand() % 100 ) > 95 ) +// Com_Printf( "pmove - current>%d<, last>%d<\n", pm->ps->weapon, pm->ps->stats[ STAT_LAST_WEAPON ] ); + + + // torso animation + PM_TorsoAnimation(); + + // footstep events / legs animations + PM_Footsteps(); + + // entering / leaving water splashes + PM_WaterEvents(); + + // snap some parts of playerstate to save network bandwidth + trap_SnapVector( pm->ps->velocity ); +} + + +/* +================ +Pmove + +Can be called by either the server or the client +================ +*/ +void Pmove (pmove_t *pmove) { + int finalTime; + + finalTime = pmove->cmd.serverTime; + + if ( finalTime < pmove->ps->commandTime ) { + return; // should not happen + } + + if ( finalTime > pmove->ps->commandTime + 1000 ) { + pmove->ps->commandTime = finalTime - 1000; + } + + pmove->ps->pmove_framecount = (pmove->ps->pmove_framecount+1) & ((1<ps->commandTime != finalTime ) { + int msec; + + msec = finalTime - pmove->ps->commandTime; + + if ( pmove->pmove_fixed ) { + if ( msec > pmove->pmove_msec ) { + msec = pmove->pmove_msec; + } + } + else { + if ( msec > 66 ) { + msec = 66; + } + } + pmove->cmd.serverTime = pmove->ps->commandTime + msec; + PmoveSingle( pmove ); + + if ( pmove->ps->pm_flags & PMF_JUMP_HELD ) { + pmove->cmd.upmove = 20; + } + } + + //PM_CheckStuck(); + +} + diff --git a/quake3/source/code/game/bg_public.h b/quake3/source/code/game/bg_public.h new file mode 100644 index 0000000..f027b43 --- /dev/null +++ b/quake3/source/code/game/bg_public.h @@ -0,0 +1,1102 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_public.h -- definitions shared by both the server game and client game modules + +// because games can change separately from the main system version, we need a +// second version that must match between game and cgame + +#define GAME_VERSION "baseq3-1" + +//PKMOD - Ergodic 02/01/01 - add variable for PKARENA game version +//PKMOD - Ergodic 03/27/01 - re-version from 2.1b to 2.2 +//PKMOD - Ergodic 04/21/01 - re-version from 2.2 to 2.3 +//PKMOD - Ergodic 04/25/01 - re-version from 2.3 to 2.4 +//PKMOD - Ergodic 06/03/01 - re-version from 2.4 to 2.5 +//PKMOD - Ergodic 07/11/01 - re-version from 2.5 to 2.6 +//PKMOD - Ergodic 07/14/01 - re-version from 2.6 to 2.7 +//PKMOD - Ergodic 07/25/01 - re-version from 2.7 to 2.7t +//PKMOD - Ergodic 07/31/01 - re-version from 2.7t to 2.7 +//PKMOD - Ergodic 08/14/01 - re-version from 2.7 to 2.7F +//PKMOD - Ergodic 08/16/01 - re-version from 2.7F to 2.7G +//PKMOD - Ergodic 08/24/01 - re-version from 2.7G to 2.7H +//PKMOD - Ergodic 08/26/01 - re-version from 2.7H to 2.8 +//PKMOD - Ergodic 09/30/01 - re-version from 2.8 to 2.9a +//PKMOD - Ergodic 11/23/01 - re-version from 2.9a to 2.9b +//PKMOD - Ergodic 12/07/01 - re-version from 2.9b to 2.9c +//PKMOD - Ergodic 01/20/02 - re-version from 2.9c to 2.9d +//PKMOD - Ergodic 02/14/02 - re-version from 2.9d to 2.9e +//PKMOD - Ergodic 05/24/02 - re-version from 2.9e to 2.9f +//PKMOD - Ergodic 08/02/02 - re-version from 2.9f to 2.9g +//PKMOD - Ergodic 09/24/02 - re-version from 2.9g to 2.9h +//PKMOD - Ergodic 10/18/02 - re-version from 2.9h to 2.9i +//PKMOD - Ergodic 10/24/02 - re-version from 2.9i to 2.9j +//PKMOD - Ergodic 11/03/02 - re-version from 2.9j to 2.9k +//PKMOD - Ergodic 11/28/02 - re-version from 2.9k to 2.9L +//PKMOD - Ergodic 12/07/02 - re-version from 2.9L to 2.9m +//PKMOD - Ergodic 12/07/02 - re-version from 2.9m to 2.9n +//PKMOD - Ergodic 12/07/02 - re-version from 2.9n to 3.0 +//PKMOD - Ergodic 05/15/03 - re-version from 3.0 to 3.0a +//PKMOD - Ergodic 09/23/03 - re-version from 3.0a to 3.0b +//PKMOD - Ergodic 09/25/03 - re-version from 3.0b to 3.0c +//PKMOD - Ergodic 12/13/03 - re-version from 3.0c to 3.0d +//PKMOD - Ergodic 12/18/03 - re-version from 3.0d to 3.0e +//PKMOD - Ergodic 01/03/04 - re-version from 3.0e to 3.0f +//PKMOD - Ergodic 01/03/04 - re-version from 3.0f to 3.0g +//PKMOD - Ergodic 02/06/04 - re-version from 3.0g to 3.0h +//PKMOD - Ergodic 02/11/04 - re-version from 3.0h to 3.0i +//PKMOD - Ergodic 02/24/04 - re-version from 3.0i to 3.0j +//PKMOD - Ergodic 02/27/04 - re-version from 3.0j to 3.0k +//PKMOD - Ergodic 03/15/04 - re-version from 3.0k to 3.0m (skip l) +//PKMOD - Ergodic 03/29/04 - re-version from 3.0m to 3.0n +#define PKARENA_VERSION "pkarena 3.0n" + +#define DEFAULT_GRAVITY 800 +#define GIB_HEALTH -40 +#define ARMOR_PROTECTION 0.66 + +#define MAX_ITEMS 256 + +#define RANK_TIED_FLAG 0x4000 + +#define DEFAULT_SHOTGUN_SPREAD 700 +#define DEFAULT_SHOTGUN_COUNT 11 + +#define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection + +#define LIGHTNING_RANGE 768 + +#define SCORE_NOT_PRESENT -9999 // for the CS_SCORES[12] when only one player is present + +#define VOTE_TIME 30000 // 30 seconds before vote times out + +#define MINS_Z -24 +#define DEFAULT_VIEWHEIGHT 26 +#define CROUCH_VIEWHEIGHT 12 +#define DEAD_VIEWHEIGHT -16 + +// +// config strings are a general means of communicating variable length strings +// from the server to all connected clients. +// + +// CS_SERVERINFO and CS_SYSTEMINFO are defined in q_shared.h +#define CS_MUSIC 2 +#define CS_MESSAGE 3 // from the map worldspawn's message field +#define CS_MOTD 4 // g_motd string for server message of the day +#define CS_WARMUP 5 // server time when the match will be restarted +#define CS_SCORES1 6 +#define CS_SCORES2 7 +#define CS_VOTE_TIME 8 +#define CS_VOTE_STRING 9 +#define CS_VOTE_YES 10 +#define CS_VOTE_NO 11 + +#define CS_TEAMVOTE_TIME 12 +#define CS_TEAMVOTE_STRING 14 +#define CS_TEAMVOTE_YES 16 +#define CS_TEAMVOTE_NO 18 + +#define CS_GAME_VERSION 20 +#define CS_LEVEL_START_TIME 21 // so the timer only shows the current level +#define CS_INTERMISSION 22 // when 1, fraglimit/timelimit has been hit and intermission will start in a second or two +#define CS_FLAGSTATUS 23 // string indicating flag status in CTF +#define CS_SHADERSTATE 24 +#define CS_BOTINFO 25 + +#define CS_ITEMS 27 // string of 0's and 1's that tell which items are present +//PKMOD - Ergodic 10/13/00 - add alternate music to hub +#define CS_POSTVOTE_MUSIC 32 +//PKMOD - Ergodic 02/01/01 - add variable for PKARENA game version +#define CS_PKARENA_VERSION 33 + +#define CS_MODELS 34 //PKMOD was 32 +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) +#define CS_PLAYERS (CS_SOUNDS+MAX_SOUNDS) +#define CS_LOCATIONS (CS_PLAYERS+MAX_CLIENTS) +#define CS_PARTICLES (CS_LOCATIONS+MAX_LOCATIONS) + +#define CS_MAX (CS_PARTICLES+MAX_LOCATIONS) + +#if (CS_MAX) > MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +typedef enum { + GT_FFA, // free for all + GT_TOURNAMENT, // one on one tournament + GT_SINGLE_PLAYER, // single player ffa + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_CTF, // capture the flag + GT_1FCTF, + GT_OBELISK, + GT_HARVESTER, + GT_MAX_GAME_TYPE +} gametype_t; + +typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; + +/* +=================================================================================== + +PMOVE MODULE + +The pmove code takes a player_state_t and a usercmd_t and generates a new player_state_t +and some other output data. Used for local prediction on the client game and true +movement on the server game. +=================================================================================== +*/ + +typedef enum { + PM_NORMAL, // can accelerate and turn + PM_NOCLIP, // noclip movement + PM_SPECTATOR, // still run into walls + PM_DEAD, // no acceleration or turning, but free falling + PM_FREEZE, // stuck in place with no control + PM_INTERMISSION, // no movement or status bar + PM_SPINTERMISSION // no movement or status bar +} pmtype_t; + +typedef enum { + WEAPON_READY, + WEAPON_RAISING, + WEAPON_DROPPING, + WEAPON_FIRING +} weaponstate_t; + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 + +//PKMOD - Ergodic 05/12/01 - add flag for next_holdable button being held +#define PMF_NEXTHOLD_HELD 4 + +#define PMF_BACKWARDS_JUMP 8 // go into backwards land +#define PMF_BACKWARDS_RUN 16 // coast down to backwards run +#define PMF_TIME_LAND 32 // pm_time is time before rejump +#define PMF_TIME_KNOCKBACK 64 // pm_time is an air-accelerate only time +#define PMF_TIME_WATERJUMP 256 // pm_time is waterjump +#define PMF_RESPAWNED 512 // clear after attack and jump buttons come up +#define PMF_USE_ITEM_HELD 1024 +#define PMF_GRAPPLE_PULL 2048 // pull towards grapple location +#define PMF_FOLLOW 4096 // spectate following another player +#define PMF_SCOREBOARD 8192 // spectate as a scoreboard +#define PMF_INVULEXPAND 16384 // invulnerability sphere set to full size + +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) + +#define MAXTOUCH 32 +typedef struct { + // state (in / out) + playerState_t *ps; + + // command (in) + usercmd_t cmd; + int tracemask; // collide against these types of surfaces + int debugLevel; // if set, diagnostic output will be printed + qboolean noFootsteps; // if the game is setup for no footsteps by the server + qboolean gauntletHit; // true if a gauntlet attack would actually hit something + + int framecount; + + // results (out) + int numtouch; + int touchents[MAXTOUCH]; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + float xyspeed; + + // for fixed msec Pmove + int pmove_fixed; + int pmove_msec; + + // callbacks to test the world + // these will be different functions during game and cgame + void (*trace)( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask ); + + int (*pointcontents)( const vec3_t point, int passEntityNum ); +} pmove_t; + +// if a full pmove isn't done on the client, you can just update the angles +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ); +void Pmove (pmove_t *pmove); + +//=================================================================================== +//PKMOD - Ergodic 07/10/00 add definition for beartraps attached constant for the +// angles2 co-opt hack. the entitystate_t angles2[beartraps_attached] +// variable will link the beartrap info from the game to the client +#define BEARTRAPS_ATTACHED 0 //this will coop the pitch of angles2 + +//PKMOD - Ergodic 06/18/00 add PKA items status area +// player_state->stats[] indexes +// NOTE: may not have more than 16 +typedef enum { + STAT_HEALTH, + STAT_HOLDABLE_ITEM, +#ifdef MISSIONPACK + STAT_PERSISTANT_POWERUP, +#endif + STAT_WEAPONS, // 16 bit fields + STAT_PKA_ITEMS, // 16 bit fields + STAT_ARMOR, + STAT_DEAD_YAW, // look this direction when dead (FIXME: get rid of?) + STAT_CLIENTS_READY, // bit mask of clients wishing to exit the intermission (FIXME: configstring?) + STAT_MAX_HEALTH, // health / armor limit, changable by handicap + STAT_BEARTRAPS_ATTACHED, //PKMOD - Ergodic 06/30/00, beartraps attached to player + //PKMOD - Ergodic 01/19/02 - reuse STAT_VOTING_MODE field for other purposes +// STAT_VOTING_MODE, //PKMOD - Ergodic 09/24/00, add player voting mode state + STAT_PKA_BITS, //PKMOD - Ergodic 01/19/02, add PKA miscellaneos bits to playerstate + STAT_AIRFIST_LEVEL, //PKMOD - Ergodic 11/15/00, add af_lev to player state + // values (4,3,2,1,0) + STAT_PRIOR_AIRFIST_LEVEL, //PKMOD - Ergodic 11/15/00, holds af level at time of blast + // values (4,3,2,1,0) + STAT_LAST_WEAPON, //PKMOD - Ergodic 04/04/01, holds last weapon + STAT_ACTIVE_HOLDABLE, //PKMOD - Ergodic 05/11/01 - allow holding of more than 1 type of + // holdable but only 1 of each kind + STAT_CLG_SHAFTEE_NUM //PKMOD - Ergodic 12/16/03, hold enitynumber for prime shafted target +} statIndex_t; + + +// player_state->persistant[] indexes +// these fields are the only part of player_state that isn't +// cleared on respawn +// NOTE: may not have more than 16 +typedef enum { + PERS_SCORE, // !!! MUST NOT CHANGE, SERVER AND GAME BOTH REFERENCE !!! + PERS_HITS, // total points damage inflicted so damage beeps can sound on change + PERS_RANK, // player rank or team rank + PERS_TEAM, // player team + PERS_SPAWN_COUNT, // incremented every respawn + PERS_PLAYEREVENTS, // 16 bits that can be flipped for events + PERS_ATTACKER, // clientnum of last damage inflicter + PERS_ATTACKEE_ARMOR, // health/armor of last person we attacked + PERS_KILLED, // count of the number of times you died + // player awards tracking + PERS_IMPRESSIVE_COUNT, // two railgun hits in a row + PERS_EXCELLENT_COUNT, // two successive kills in a short amount of time + PERS_DEFEND_COUNT, // defend awards + PERS_ASSIST_COUNT, // assist awards + PERS_GAUNTLET_FRAG_COUNT, // kills with the guantlet + PERS_CAPTURES, // captures +//PKMOD - Ergodic 08/08/00 add PKA Medal - packed variable + PERS_PAINKILLER_COUNT +//PKMOD - Ergodic 10/11/00 add PKA HUB Flag - packed variable + //PKMOD - Ergodic 12/16/00 - place PERS_HUB_FLAG as first bit of PERS_PAINKILLER_COUNT + // +} persEnum_t; + + +// entityState_t->eFlags +#define EF_DEAD 0x00000001 // don't draw a foe marker over players with EF_DEAD +#ifdef MISSIONPACK +#define EF_TICKING 0x00000002 // used to make players play the prox mine ticking sound +#endif +#define EF_TELEPORT_BIT 0x00000004 // toggled every time the origin abruptly changes +#define EF_AWARD_EXCELLENT 0x00000008 // draw an excellent sprite +#define EF_PLAYER_EVENT 0x00000010 +#define EF_BOUNCE 0x00000010 // for missiles +#define EF_BOUNCE_HALF 0x00000020 // for missiles +#define EF_AWARD_GAUNTLET 0x00000040 // draw a gauntlet sprite +#define EF_NODRAW 0x00000080 // may have an event, but no model (unspawned items) +#define EF_FIRING 0x00000100 // for lightning gun +//PKMOD Ergodic - 07/08/01, remove kamizaki and add lightningstrike to EF_ area +//#define EF_KAMIKAZE 0x00000200 +#define EF_LIGHTNINGSTRIKE 0x00000200 //player shooting the clg has struck an enemy + +#define EF_MOVER_STOP 0x00000400 // will push otherwise +#define EF_AWARD_CAP 0x00000800 // draw the capture sprite +#define EF_TALK 0x00001000 // draw a talk balloon +#define EF_CONNECTION 0x00002000 // draw a connection trouble sprite +#define EF_VOTED 0x00004000 // already cast a vote +#define EF_AWARD_IMPRESSIVE 0x00008000 // draw an impressive sprite +#define EF_AWARD_DEFEND 0x00010000 // draw a defend sprite +#define EF_AWARD_ASSIST 0x00020000 // draw a assist sprite +#define EF_AWARD_DENIED 0x00040000 // denied +#define EF_TEAMVOTED 0x00080000 // already cast a team vote +//PKMOD - Ergodic 08/08/00 - PK Medals +//PKMOD - Ergodic 12/16/00 - was EF_AWARD_PAINKILLER 0x00000800 +#define EF_AWARD_PAINKILLER 0x00100000 // draw a painkiller sprite + +//PKMOD Ergodic - 12/28/00, add general PainKeepArena entity flags +//PKMOD Ergodic - 07/08/01, move lightningstrike to EF_ area +//#define PKAEF_LIGHTNINGSTRIKE 0x00000001 //player shooting the clg has struck an enemy +#define PKAEF_AUTOSENTRYFIRING 0x00000002 //autosentry is firing the guns +//PKMOD Ergodic - 01/11/01, add flag for trap door that causes death will credit +// the activating player +#define PKAEF_DOORADDFRAG 0x00000004 //Activating player will be credited the frag(s) +//PKMOD Ergodic - 01/23/01, add flag for setting the turret portion of the autosentry to die +#define PKAEF_AUTOSENTRYDEATH 0x00000008 //autosentry is set to die +//PKMOD Ergodic - 01/30/01, add flag for setting the turret portion of the autosentry to Free +#define PKAEF_AUTOSENTRYFREE 0x00000010 //autosentry is set to Free (don't die twice) +//PKMOD Ergodic - 08/05/01, add flag for setting the BearTrap to die (used in g_mover) +#define PKAEF_BEARTRAPDIE 0x00000020 //Beartrap is set to die (don't die twice) +//PKMOD Ergodic - 10/25/01, add flag for setting the entity irradiated +#define PKAEF_IRRADIATED 0x00000040 //Entity is irradiated +//PKMOD Ergodic - 11/21/03, add flag for Gravity Well Earthquake sound +#define PKAEF_EARTHQUAKE 0x00000080 //Set when Earthquake Sound is issued +//PKMOD Ergodic - 01/05/04, add flag for beanstoot ent, when it is quaded +#define PKAEF_QUADFART 0x00000100 //Set when Quad Farting is enabled + +//PKMOD - Ergodic 01/19/02 - STAT_PKA_BITS +//PKMOD - Ergodic 02/05/02 - change STAT_PKA_BITS settings from enum type to definition +#define PKA_BITS_PRIVATEBOT 0x00000001 //1 - flag for private bot +//PKMOD - Ergodic 02/05/02 - Add ATTACK Bits for hit sounds +#define PKA_BITS_DEFAULTATTACK 0x00000000 //0 - regular hit sound, 2 - beartrap hit sound, 3 - autosentry hit sound +#define PKA_BITS_BEARTRAPATTACK 0x00000002 //2 - beartrap hit sound +#define PKA_BITS_RADIATEATTACK 0x00000004 //4 - radiate hit sound +#define PKA_BITS_SENTRYATTACK 0x0000000E //E - autosentry hit sound [NOTE: this bitfield should be a complete mask] + + +// NOTE: may not have more than 16 +typedef enum { + PW_NONE, //0 - powerup + + PW_QUAD, //1 - powerup + PW_BATTLESUIT, //2 - powerup + PW_HASTE, //3 - powerup + PW_INVIS, //4 - powerup + PW_REGEN, //5 - powerup + PW_FLIGHT, //6 - powerup + + PW_REDFLAG, //7 - powerup + PW_BLUEFLAG, //8 - powerup + PW_NEUTRALFLAG, //9 - powerup + +//PKMOD - Ergodic 10/13/01 - add powerup timer for radiate effect +// This will redefine the PW_SCOUT area + PW_RADIATE, //10 - powerup +// PW_SCOUT, //10 - powerup +//PKMOD - Ergodic 05/07/02 - add powerup timer for Active Personal Sentry +// This will redefine the PW_GUARD area + PW_PERSENTRY, //11 - powerup +// PW_GUARD, //11 - powerup + PW_DOUBLER, //12 - powerup +// PW_AMMOREGEN, +//PKMOD - Ergodic 06/03/01 - add bean powerup timer for armor countdown immunity + PW_BEANS, //13 - powerup + PW_INVULNERABILITY, //14 - powerup +//PKMOD - Ergodic 08/22/00 - use powerup as vehicle to communicate clg shaft player hit info to cgame + PW_CLGPLAYERHIT, //15 - powerup +//PKMOD - Ergodic 09/20/00 - voting item +//PKMOD - Ergodic 12/16/00 - removed and need to NULL bg_misc entry. field is unused +// PW_VOTING, + + PW_NUM_POWERUPS + +} powerup_t; + +typedef enum { + HI_NONE, + + HI_TELEPORTER, + HI_MEDKIT, + //PKMOD - Ergodic 10/06/01 - add new holdable + HI_RADIATE, + //PKMOD - Ergodic 11/23/01 - add new holdable + HI_PERSENTRY, + //PKMOD - Ergodic 12/01/01 - add new Private Bot holdables + HI_BOTLEGS, + HI_BOTTORSO, + HI_BOTHEAD, + HI_KAMIKAZE, + HI_PORTAL, + HI_INVULNERABILITY, + + HI_NUM_HOLDABLE +} holdable_t; + +//PKMOD - Ergodic 05/14/00 - modify for PK weapons order (later: need to fix weapon 1 cycling) +//PKMOD - Ergodic 06/08/00 - modify for PK weapons order using 2 byte STAT_WEAPONS +//PKMOD - Ergodic 06/09/00 - Currently only 15 weapons means may not need to add expansion byte, just yet +typedef enum { + WP_NONE, + + WP_GAUNTLET, //1 + WP_MACHINEGUN, //2 + WP_SHOTGUN, //3 + WP_AIRFIST, //4 PKMOD -Add Weapons. + WP_NAILGUN, //5 + WP_GRENADE_LAUNCHER, //6 + WP_ROCKET_LAUNCHER, //7 + WP_LIGHTNING, //8 + WP_RAILGUN, //9 + WP_GRAPPLING_HOOK, //10 - this is the last weapon before special PK items + WP_GRAVITY, //11 + WP_SENTRY, //12 + WP_BEARTRAP, //13 PKMOD - Ergodic 06/08/00 Weapon enum #16 + WP_BEANS, //14 - this is the last special PK item + WP_EXPLODING_SHELLS, //15 - 2002-Aug-18: this is the last weapon due to cg_weapons.c weapon cycling rules + + WP_NUM_WEAPONS, + WP_CHAINLG, //never select the chainlg + WP_PLASMAGUN, //never select the plasmagun + WP_BFG //never select the bgf + +} weapon_t; + +//PKMOD - Ergodic 06/18/00 - PKA Items Status +typedef enum { + PKA_NONE, + PKA_BEANS, //1 - flag for active beans + PKA_SHAFTED, //2 - flag for client currently being shafted + PKA_IRRADIATED, //3 - flag for radiated "infected" client + //PKMOD - Ergodic 02/05/02 - Add Bits for activated Personal Sentry + PKA_PERSENTRY_ACTIVE, //4 - Personal Sentry is active on player + //PKMOD - Ergodic 09/14/03 - Add Bits for state when clint is voting for the alternate hub maps + PKA_HUBALT_VOTING //5 - Personal Sentry is active on player +} pka_items_t; + +// reward sounds +typedef enum { + REWARD_BAD, + + REWARD_IMPRESSIVE, + REWARD_EXCELLENT, + REWARD_DENIED, + REWARD_GAUNTLET, + REWARD_PAINKILLER +} reward_t; + +// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) +#define PLAYEREVENT_DENIEDREWARD 0x0001 +#define PLAYEREVENT_GAUNTLETREWARD 0x0002 +#define PLAYEREVENT_HOLYSHIT 0x0004 + +// entityState_t->event values +// entity events are for effects that take place reletive +// to an existing entities origin. Very network efficient. + +// two bits at the top of the entityState->event field +// will be incremented with each change in the event so +// that an identical event started twice in a row can +// be distinguished. And off the value with ~EV_EVENT_BITS +// to retrieve the actual event number +#define EV_EVENT_BIT1 0x00000100 +#define EV_EVENT_BIT2 0x00000200 +#define EV_EVENT_BITS (EV_EVENT_BIT1|EV_EVENT_BIT2) + +#define EVENT_VALID_MSEC 300 + +typedef enum { + EV_NONE, + + EV_FOOTSTEP, + EV_FOOTSTEP_METAL, + EV_FOOTSPLASH, + EV_FOOTWADE, + EV_SWIM, + + EV_STEP_4, + EV_STEP_8, + EV_STEP_12, + EV_STEP_16, + + EV_FALL_SHORT, + EV_FALL_MEDIUM, + EV_FALL_FAR, + + EV_JUMP_PAD, // boing sound at origin, jump sound on player + + EV_JUMP, + EV_WATER_TOUCH, // foot touches + EV_WATER_LEAVE, // foot leaves + EV_WATER_UNDER, // head touches + EV_WATER_CLEAR, // head leaves + + EV_ITEM_PICKUP, // normal item pickups are predictable + EV_GLOBAL_ITEM_PICKUP, // powerup / team sounds are broadcast to everyone + + EV_NOAMMO, + EV_CHANGE_WEAPON, + EV_FIRE_WEAPON, + + EV_USE_ITEM0, + EV_USE_ITEM1, + EV_USE_ITEM2, + EV_USE_ITEM3, + EV_USE_ITEM4, + EV_USE_ITEM5, + EV_USE_ITEM6, + EV_USE_ITEM7, + EV_USE_ITEM8, + EV_USE_ITEM9, + EV_USE_ITEM10, + EV_USE_ITEM11, + EV_USE_ITEM12, + EV_USE_ITEM13, + EV_USE_ITEM14, + EV_USE_ITEM15, + + EV_ITEM_RESPAWN, + EV_ITEM_POP, + EV_PLAYER_TELEPORT_IN, + EV_PLAYER_TELEPORT_OUT, + + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + EV_GLOBAL_TEAM_SOUND, + + EV_BULLET_HIT_FLESH, + EV_BULLET_HIT_WALL, + + EV_MISSILE_HIT, + EV_MISSILE_MISS, + EV_MISSILE_MISS_METAL, + EV_RAILTRAIL, + EV_SHOTGUN, + EV_BULLET, // otherEntity is the shooter + + EV_PAIN, + EV_DEATH1, + EV_DEATH2, + EV_DEATH3, + EV_OBITUARY, + + EV_POWERUP_QUAD, + EV_POWERUP_BATTLESUIT, + EV_POWERUP_REGEN, + + EV_GIB_PLAYER, // gib a previously living player + EV_SCOREPLUM, // score plum + +//#ifdef MISSIONPACK + EV_PROXIMITY_MINE_STICK, + EV_PROXIMITY_MINE_TRIGGER, + EV_KAMIKAZE, // kamikaze explodes + EV_OBELISKEXPLODE, // obelisk explodes + EV_OBELISKPAIN, // obelisk is in pain + EV_INVUL_IMPACT, // invulnerability sphere impact + EV_JUICED, // invulnerability juiced effect + EV_LIGHTNINGBOLT, // lightning bolt bounced of invulnerability sphere +//#endif + + EV_DEBUG_LINE, + EV_STOPLOOPINGSOUND, + EV_TAUNT, + EV_TAUNT_YES, + EV_TAUNT_NO, + EV_TAUNT_FOLLOWME, + EV_TAUNT_GETFLAG, + EV_TAUNT_GUARDBASE, + EV_TAUNT_PATROL, + // + // PKMOD - Ergodic 05/22/00 Events + // + EV_BEARTRAP_DIE, + EV_BEARTRAP_SNAP, //PKMOD - Ergodic 07/01/00 + EV_CHAINLIGHTNING_STRIKE, //PKMOD - Ergodic 08/22/00 + EV_GRAVITY_RELEASED, //PKMOD - Ergodic 07/11/00 + EV_LIGHTNING_FX, //PKMOD - Ergodic 07/19/00 + EV_BEARTRAP_DROP, //PKMOD - Ergodic 08/07/00 + EV_GRAVITYWELL_SUCK, //PKMOD - Ergodic 09/06/00 gravity well item suck sounds from Mongusta + + EV_PKA_NOAMMO, //PKMOD - Ergodic 05/30/00 + EV_ITEM_REMOVE, //PKMOD - Ergodic 11/16/00 - add target_remove code + EV_GLOBAL_ITEM_REMOVE, //PKMOD - Ergodic 11/20/00 - add target_remove code (powerups) + + EV_AUTOSENTRY_DROP, //PKMOD - Ergodic 11/22/00 + EV_AUTOSENTRY_DIE, //PKMOD - Ergodic 11/22/00 + EV_SHOOTER_LIGHTNING, //PKMOD - Ergodic 12/06/00 - special lightning shooter event + + //PKMOD - Ergodic 12/14/00 - add autosentry code for greater tracability + EV_AUTOSENTRY_HIT_WALL, + EV_AUTOSENTRY_HIT_FLESH, + + //PKMOD - Ergodic 12/26/00 add Beans fart noises from Mongusta + EV_BEANS_TOOT, + //PKMOD - Ergodic 01/13/01 - add autosentry fire sounds from mongusta + EV_AUTOSENTRY_FIRE, + //PKMOD - Ergodic 01/15/01 - add Lightning Discharge in water + EV_LIGHTNING_WATER_DISCHARGE, + //PKMOD - Ergodic 01/21/01 - exploding shells debug model + EV_COORD, + //PKMOD - Ergodic 03/26/01 - add autosentry ping sound + EV_AUTOSENTRY_PING, + //PKMOD - Ergodic 07/03/01 ChainLightning reflect sounds + EV_CHAINLIGHTNING_REFLECT, + //PKMOD - Ergodic 12/05/01 - Radiate Item & Radiate Player events + EV_ITEM_RADIATE, + EV_PLAYER_RADIATE, + //PKMOD - Ergodic 01/07/02 - send message to client if not all Private Bot parts are held + EV_INCOMPLETE_PRIVATEBOT, + //PKMOD - Ergodic 02/07/02 - send message to client if Private Bot is completed + EV_COMPLETED_PRIVATEBOT, + //PKMOD - Ergodic 02/10/02 - send FRAG message to Private Bot's owner + EV_PRIVATEBOT_FRAG, + //PKMOD - Ergodic 03/18/02 - send message to client that no more Private Bots are available + EV_NOAVAILABLE_PRIVATEBOTS, + //PKMOD - Ergodic 06/09/02 - create the teleport flash for the personal sentry (teleport in sound) + EV_TELE_IN_PERSONALSENTRY, + //PKMOD - Ergodic 06/12/02 - generate sound effect for firing Personal Sentry + EV_FIRE_PERSONALSENTRY, + //PKMOD - Ergodic 08/03/02 - create the teleport flash for the personal sentry (teleport out sound) + EV_TELE_OUT_PERSONALSENTRY, + //PKMOD - Ergodic 11/20/03 - create earthquake sound for out of range gravity well + //EV_EARTHQUAKE, + //PKMOD - Ergodic 12/06/03 - create charge up sound for CLG striking Beartrap or Autosentry + EV_CHAINLIGHTNING_CHARGE_UP, + //PKMOD - Ergodic 01/05/04 - add quad farting logic for differing CG graphic sequence + EV_QUADBEANS_TOOT +} entity_event_t; + + +typedef enum { + GTS_RED_CAPTURE, + GTS_BLUE_CAPTURE, + GTS_RED_RETURN, + GTS_BLUE_RETURN, + GTS_RED_TAKEN, + GTS_BLUE_TAKEN, + GTS_REDOBELISK_ATTACKED, + GTS_BLUEOBELISK_ATTACKED, + GTS_REDTEAM_SCORED, + GTS_BLUETEAM_SCORED, + GTS_REDTEAM_TOOK_LEAD, + GTS_BLUETEAM_TOOK_LEAD, + GTS_TEAMS_ARE_TIED, + GTS_KAMIKAZE +} global_team_sound_t; + +// animations +typedef enum { + BOTH_DEATH1, + BOTH_DEAD1, + BOTH_DEATH2, + BOTH_DEAD2, + BOTH_DEATH3, + BOTH_DEAD3, + + TORSO_GESTURE, + + TORSO_ATTACK, + TORSO_ATTACK2, + + TORSO_DROP, + TORSO_RAISE, + + TORSO_STAND, + TORSO_STAND2, + + LEGS_WALKCR, + LEGS_WALK, + LEGS_RUN, + LEGS_BACK, + LEGS_SWIM, + + LEGS_JUMP, + LEGS_LAND, + + LEGS_JUMPB, + LEGS_LANDB, + + LEGS_IDLE, + LEGS_IDLECR, + + LEGS_TURN, + + TORSO_GETFLAG, + TORSO_GUARDBASE, + TORSO_PATROL, + TORSO_FOLLOWME, + TORSO_AFFIRMATIVE, + TORSO_NEGATIVE, + + MAX_ANIMATIONS, + + LEGS_BACKCR, + LEGS_BACKWALK, + FLAG_RUN, + FLAG_STAND, + FLAG_STAND2RUN, + + MAX_TOTALANIMATIONS +} animNumber_t; + + +typedef struct animation_s { + int firstFrame; + int numFrames; + int loopFrames; // 0 to numFrames + int frameLerp; // msec between frames + int initialLerp; // msec to get to first frame + int reversed; // true if animation is reversed + int flipflop; // true if animation should flipflop back to base +} animation_t; + + +// flip the togglebit every time an animation +// changes so a restart of the same anim can be detected +#define ANIM_TOGGLEBIT 128 + + +typedef enum { + TEAM_FREE, + TEAM_RED, + TEAM_BLUE, + TEAM_SPECTATOR, + + TEAM_NUM_TEAMS +} team_t; + +// Time between location updates +#define TEAM_LOCATION_UPDATE_TIME 1000 + +// How many players on the overlay +#define TEAM_MAXOVERLAY 32 + +//team task +typedef enum { + TEAMTASK_NONE, + TEAMTASK_OFFENSE, + TEAMTASK_DEFENSE, + TEAMTASK_PATROL, + TEAMTASK_FOLLOW, + TEAMTASK_RETRIEVE, + TEAMTASK_ESCORT, + TEAMTASK_CAMP +} teamtask_t; + +//PKMOD - Ergodic 06/05/03 - list order must match modNames in g_combat.c +/*PKMOD - Ergodic 06/05/03 - removed: + MOD_A2K, + MOD_EMPNUKE, + MOD_HARPOON, + MOD_CHAINLG, +*/ +// means of death +typedef enum { + MOD_UNKNOWN, + MOD_SHOTGUN, + MOD_GAUNTLET, + MOD_MACHINEGUN, + MOD_GRENADE, + MOD_GRENADE_SPLASH, + MOD_ROCKET, + MOD_ROCKET_SPLASH, + MOD_PLASMA, + MOD_PLASMA_SPLASH, + MOD_RAILGUN, + MOD_LIGHTNING, + MOD_BFG, + MOD_BFG_SPLASH, + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_FALLING, + MOD_SUICIDE, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT, + MOD_GRAPPLE, + //PKMOD -Add Weapons. + MOD_GRAVITY, + MOD_SENTRY, + MOD_BEARTRAP, + MOD_AIRFIST, + MOD_NAILGUN, + MOD_NAIL, //PKMOD - Ergodic 08/01/00 + MOD_EXPLODING_SHELLS, + MOD_EXPLODING_SHELLS_SPLASH, + //PKMOD - Ergodic 12/05/00 - add new types of PKA shooters + MOD_SHOOTER_LIGHTNING, + //PKMOD - Ergodic 01/11/01 - give frag credit to activator of CRUSH Death + MOD_CRUSH_CREDIT, + //PKMOD - Ergodic 01/13/01 - exploding autosentry will produce splash damage + MOD_SENTRY_SPLASH, + //PKMOD - Ergodic 01/15/01 - add Lightning Discharge in water + MOD_LIGHTNING_WATER_DISCHARGE, + //PKMOD - Ergodic 02/01/01 - add can of beans toot damage + MOD_BEANS_BLAST, + //PKMOD - Ergodic 07/02/01 - add reverse damage on lightning from autosentry + MOD_REVERSE_LIGHTNING, + //PKMOD - Ergodic 10/29/01 - add holdable radiation death + MOD_RADIATION, + //PKMOD - Ergodic 06/08/02 - add personal sentry death + MOD_PERSONALSENTRY, + //PKMOD - Ergodic 10/23/02 - Create new Means of Death for the Dragon Blade + MOD_DRAGONBLADE, + //PKMOD - Ergodic 06/06/03 - Create new Means of Death for the Private Bot for logging use in games.log + MOD_PRIVATEBOT, + //PKMOD - Ergodic 01/05/04 - add QUAD FART beans toot damage + MOD_QUADBEANS_BLAST +} meansOfDeath_t; + + +//--------------------------------------------------------- + +// gitem_t->type +typedef enum { + IT_BAD, + IT_WEAPON, // EFX: rotate + upscale + minlight + IT_AMMO, // EFX: rotate + IT_ARMOR, // EFX: rotate + minlight + IT_HEALTH, // EFX: static external sphere + rotating internal + IT_POWERUP, // instant on, timer based + // EFX: rotate + external ring that rotates + IT_HOLDABLE, // single use, holdable item + // EFX: rotate + bob + IT_PERSISTANT_POWERUP, + IT_TEAM, + IT_VOTING, //PKMOD - Ergodic 09/20/00 - Voting Entity + IT_PKARENA_ACTIVE // EFX: No Rotation +} itemType_t; + +#define MAX_ITEM_MODELS 4 + +typedef struct gitem_s { + char *classname; // spawning name + char *pickup_sound; + char *world_model[MAX_ITEM_MODELS]; + + char *icon; + char *pickup_name; // for printing on pickup + + int quantity; // for ammo how much, or duration of powerup + itemType_t giType; // IT_* flags + + int giTag; + + char *precaches; // string of all models and images this item will use + char *sounds; // string of all sounds this item will use +} gitem_t; + +// included in both the game dll and the client +extern gitem_t bg_itemlist[]; +extern int bg_numItems; + +gitem_t *BG_FindItem( const char *pickupName ); +gitem_t *BG_FindItemForWeapon( weapon_t weapon ); +gitem_t *BG_FindItemForPowerup( powerup_t pw ); +gitem_t *BG_FindItemForHoldable( holdable_t pw ); +#define ITEM_INDEX(x) ((x)-bg_itemlist) + +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ); + + +// g_dmflags->integer flags +#define DF_NO_FALLING 8 +#define DF_FIXED_FOV 16 +#define DF_NO_FOOTSTEPS 32 + +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE) + + +// +// entityState_t->eType +// +typedef enum { + ET_GENERAL, // 0 + ET_PLAYER, // 1 + ET_ITEM, // 2 + ET_MISSILE, // 3 + ET_MOVER, // 4 + ET_BEAM, // 5 + ET_PORTAL, // 6 + ET_SPEAKER, // 7 + ET_PUSH_TRIGGER, // 8 + ET_TELEPORT_TRIGGER, // 9 + ET_INVISIBLE, // 10 + ET_GRAPPLE, // 11 - grapple hooked on wall + ET_TEAM, // 12 + + //PKMOD Ergodic - 05/29/2000, add PKMOD's event type + ET_BEARTRAP, // 13 + //PKMOD Ergodic - 06/11/2000, add PKMOD's event type + ET_BEARTRAP_FOLLOW, // 14 + //PKMOD Ergodic - 07/01/2000, add PKMOD's event type + ET_GRAVITY_WELL, // 15 + //PKMOD Ergodic - 07/12/2000, add chain lightning event type + ET_CHAIN_LIGHTNING, // 16 + //PKMOD - Ergodic 07/16/00 special spawn functions (lightning_fx) + ET_LIGHTNING_FX, // 17 + //PKMOD - Ergodic 08/03/00 event type for nail entities + ET_NAIL, // 18 + //PKMOD - Ergodic 11/15/00 add functionality to make trigger_push silent + ET_QUIET_TRIGGER, // 19 + //PKMOD - Ergodic 01/29/01 autosentry entries must be contiguous and in order + // Do not change the order of ET_AUTOSENTRY, ET_AUTOSENTRY_DEPLOY, ET_AUTOSENTRY_DEPLOY, + // and ET_AUTOSENTRY_TURRET this will effect missile and hitscan weapons + // //g_weapons: shotgun, railgun, machinegun, and bullets + //PKMOD - Ergodic 11/22/00 add autosentry launch event type + ET_AUTOSENTRY_LAUNCH, // 20 + //PKMOD Ergodic - 11/26/2000, add deploy autosentry entity type + ET_AUTOSENTRY_DEPLOY, // 21 + //PKMOD Ergodic - 12/02/2000, add split autosentry entity types + ET_AUTOSENTRY_BASE, // 22 + ET_AUTOSENTRY_TURRET, // 23 + //PKMOD - Ergodic 03/14/01 - add dragon deployable weapon fire + ET_DRAGON_DEPLOY, // 24 + //PKMOD - Ergodic 06/07/01 - add ZOMBIE entity type + ET_ZOMBIE, // 25 + //PKMOD - Ergodic 08/02/01 - create a new entity type for door_trigger + // fixes the bug of dragon deploy hitting doors + ET_DOOR_TRIGGER, // 26 + //PKMOD - Ergodic 08/02/01 - create a new entity type for trigger_multiple + // fixes the bug of dragon deploy hitting doors + ET_TRIGGER_MULTIPLE, // 27 + //PKMOD - Ergodic 06/09/02 - create a new entity type for the active personal sentry + ET_PERSONALSENTRY, //28 + //PKMOD - Ergodic 08/20/03 - After a new shader beam was added to the CLG, we want to + // differentiate between shooter_Lightning(green) and CLG lightning(red) + ET_SHOOTER_LIGHTNING, //29 + + + ET_EVENTS, // 30 - any of the EV_* events can be added freestanding + // by setting eType to ET_EVENTS + eventNum + // this avoids having to set eFlags and eventNum + //PKMOD Ergodic - 03/29/2000, add airfist event type + ET_AIRFIST // 31 + +} entityType_t; + + + +void BG_EvaluateTrajectory( const trajectory_t *tr, int atTime, vec3_t result ); +void BG_EvaluateTrajectoryDelta( const trajectory_t *tr, int atTime, vec3_t result ); + +void BG_AddPredictableEventToPlayerstate( int newEvent, int eventParm, playerState_t *ps ); + +void BG_TouchJumpPad( playerState_t *ps, entityState_t *jumppad ); + +void BG_PlayerStateToEntityState( playerState_t *ps, entityState_t *s, qboolean snap ); +void BG_PlayerStateToEntityStateExtraPolate( playerState_t *ps, entityState_t *s, int time, qboolean snap ); + +qboolean BG_PlayerTouchesItem( playerState_t *ps, entityState_t *item, int atTime ); + + +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 1024 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 1024 +#define MAX_BOTS_TEXT 8192 + +//PKMOD - Ergodic 03/18/01 - Gravity Well visual paramters +//PKMOD - Ergodic 03/25/01 - add Gravity Well micro contractions + //++++++++++++++++++++++ + // First Expansion + //++++++++++++++++++++++ +#define GWELL_EXPAND_STARTTIME_1 500 +#define GWELL_EXPAND_ENDTIME_1 2000 +#define GWELL_EXPAND_FADETIME_1 1500 +#define GWELL_ORB_RADIUS_1 6 + + //++++++++++++++++++++++ + // First Contraction + //++++++++++++++++++++++ +#define GWELL_CONTRACTION_STARTTIME_1 2000 +#define GWELL_CONTRACTION_ENDTIME_1 2500 +#define GWELL_CONTRACTION_FADETIME_1 2250 + + //++++++++++++++++++++++ + // Second Expansion + //++++++++++++++++++++++ +#define GWELL_EXPAND_STARTTIME_2 2500 +#define GWELL_EXPAND_ENDTIME_2 4000 +#define GWELL_EXPAND_FADETIME_2 3500 +#define GWELL_ORB_RADIUS_2 6 + + //++++++++++++++++++++++ + // Second Contraction + //++++++++++++++++++++++ +#define GWELL_CONTRACTION_STARTTIME_2 4000 +#define GWELL_CONTRACTION_ENDTIME_2 4500 +#define GWELL_CONTRACTION_FADETIME_2 4250 + + //++++++++++++++++++++++ + // Third Expansion + //++++++++++++++++++++++ +#define GWELL_EXPAND_STARTTIME_3 4500 +#define GWELL_EXPAND_ENDTIME_3 6000 +#define GWELL_EXPAND_FADETIME_3 5500 +#define GWELL_ORB_RADIUS_3 6 + + //++++++++++++++++++++++ + // Third Contraction + //++++++++++++++++++++++ +#define GWELL_CONTRACTION_STARTTIME_3 6000 +#define GWELL_CONTRACTION_ENDTIME_3 8000 +#define GWELL_CONTRACTION_FADETIME_3 7500 + +#define GWELL_SHOCKWAVE_MAXRADIUS 1000 +#define GWELL_SHOCKWAVE2_MAXRADIUS 1000 + +// Kamikaze + +// 1st shockwave times +#define KAMI_SHOCKWAVE_STARTTIME 0 +#define KAMI_SHOCKWAVEFADE_STARTTIME 1500 +#define KAMI_SHOCKWAVE_ENDTIME 2000 +// explosion/implosion times +#define KAMI_EXPLODE_STARTTIME 250 +#define KAMI_IMPLODE_STARTTIME 2000 +#define KAMI_IMPLODE_ENDTIME 2250 +// 2nd shockwave times +#define KAMI_SHOCKWAVE2_STARTTIME 2000 +#define KAMI_SHOCKWAVE2FADE_STARTTIME 2500 +#define KAMI_SHOCKWAVE2_ENDTIME 3000 +// radius of the models without scaling +#define KAMI_SHOCKWAVEMODEL_RADIUS 88 +#define KAMI_BOOMSPHEREMODEL_RADIUS 72 +// maximum radius of the models during the effect +#define KAMI_SHOCKWAVE_MAXRADIUS 1320 +#define KAMI_BOOMSPHERE_MAXRADIUS 720 +#define KAMI_SHOCKWAVE2_MAXRADIUS 704 + +//PKMOD - Ergodic 09/26/2000, hub voting entities +//PKMOD - Ergodic 09/15/2003, increase Index from 25 - 35, and increase name NAME from 50 to 70 +#define MAX_HUB_INDEX 35 //maximum voting_images in the HUB +#define MAX_HUB_NAME 70 //maximum size of mapname and maptitle + +#define MAX_HUB_DISPLAY_NAME 40 //03/29/04 - maximum name size that is displayed in voting UI menu + +typedef struct ghubInfo_s { + int map_votes; + char map_name[MAX_HUB_NAME]; + char map_title[MAX_HUB_NAME]; + int map_shader_index; +} ghubInfo_t; + +//PKMOD - Ergodic 09/27/2000, included in both the game dll and the client +extern int Hub_Index; //points to last Hub element +extern ghubInfo_t hubInfo[]; + +//PKMOD - Ergodic 09/27/2000, included in both the game dll and the client +typedef struct ghubsort_s { + int map_votes; + int hub_index; +} ghubsort_t; + +//PKMOD - Ergodic 10/13/01 - define constants for new holdables +#define PLAYER_RADIATE_TIME 25 + +//PKMOD - Ergodic 05/07/02 - define constants for new holdables +#define PLAYER_PERSENTRY_TIME 25 + +//PKMOD - Ergodic 12/12/01 - PRIVATE BOT definitions +#define MAX_PRIVATE_BOTS 5 //augment sv_maxclients +//PKMOD - Ergodic 01/13/02 - PRIVATE BOT definitions +extern int active_private_bots; //number of active Private Bots + +//PKMOD - Ergodic 10/10/03 - Maximum charge to the Beartrap or Autosentry to make it invisible +//PKMOD - Ergodic 12/13/03 - change value from 70 to 50 +//PKMOD - Ergodic 03/17/04 - change value from 50 to 25 +#define MAX_INVISIBILITY_CHARGE 25 + + diff --git a/quake3/source/code/game/bg_slidemove.c b/quake3/source/code/game/bg_slidemove.c new file mode 100644 index 0000000..1f09492 --- /dev/null +++ b/quake3/source/code/game/bg_slidemove.c @@ -0,0 +1,305 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_slidemove.c -- part of bg_pmove functionality + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +/* + +input: origin, velocity, bounds, groundPlane, trace function + +output: origin, velocity, impacts, stairup boolean + +*/ + +/* +================== +PM_SlideMove + +Returns qtrue if the velocity was clipped in some way +================== +*/ +#define MAX_CLIP_PLANES 5 +qboolean PM_SlideMove( qboolean gravity ) { + int bumpcount, numbumps; + vec3_t dir; + float d; + int numplanes; + vec3_t planes[MAX_CLIP_PLANES]; + vec3_t primal_velocity; + vec3_t clipVelocity; + int i, j, k; + trace_t trace; + vec3_t end; + float time_left; + float into; + vec3_t endVelocity; + vec3_t endClipVelocity; + + numbumps = 4; + + VectorCopy (pm->ps->velocity, primal_velocity); + + if ( gravity ) { + VectorCopy( pm->ps->velocity, endVelocity ); + endVelocity[2] -= pm->ps->gravity * pml.frametime; + pm->ps->velocity[2] = ( pm->ps->velocity[2] + endVelocity[2] ) * 0.5; + primal_velocity[2] = endVelocity[2]; + if ( pml.groundPlane ) { + // slide along the ground plane + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + + time_left = pml.frametime; + + // never turn against the ground plane + if ( pml.groundPlane ) { + numplanes = 1; + VectorCopy( pml.groundTrace.plane.normal, planes[0] ); + } else { + numplanes = 0; + } + + // never turn against original velocity + VectorNormalize2( pm->ps->velocity, planes[numplanes] ); + numplanes++; + + for ( bumpcount=0 ; bumpcount < numbumps ; bumpcount++ ) { + + // calculate position we are trying to move to + VectorMA( pm->ps->origin, time_left, pm->ps->velocity, end ); + + // see if we can make it there + pm->trace ( &trace, pm->ps->origin, pm->mins, pm->maxs, end, pm->ps->clientNum, pm->tracemask); + + if (trace.allsolid) { + // entity is completely trapped in another solid + pm->ps->velocity[2] = 0; // don't build up falling damage, but allow sideways acceleration + return qtrue; + } + + if (trace.fraction > 0) { + // actually covered some distance + VectorCopy (trace.endpos, pm->ps->origin); + } + + if (trace.fraction == 1) { + break; // moved the entire distance + } + + // save entity for contact + PM_AddTouchEnt( trace.entityNum ); + + time_left -= time_left * trace.fraction; + + if (numplanes >= MAX_CLIP_PLANES) { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + for ( i = 0 ; i < numplanes ; i++ ) { + if ( DotProduct( trace.plane.normal, planes[i] ) > 0.99 ) { + VectorAdd( trace.plane.normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + if ( i < numplanes ) { + continue; + } + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; + + // + // modify velocity so it parallels all of the clip planes + // + + // find a plane that it enters + for ( i = 0 ; i < numplanes ; i++ ) { + into = DotProduct( pm->ps->velocity, planes[i] ); + if ( into >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // see how hard we are hitting things + if ( -into > pml.impactSpeed ) { + pml.impactSpeed = -into; + } + + // slide along the plane + PM_ClipVelocity (pm->ps->velocity, planes[i], clipVelocity, OVERCLIP ); + + // slide along the plane + PM_ClipVelocity (endVelocity, planes[i], endClipVelocity, OVERCLIP ); + + // see if there is a second plane that the new move enters + for ( j = 0 ; j < numplanes ; j++ ) { + if ( j == i ) { + continue; + } + if ( DotProduct( clipVelocity, planes[j] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // try clipping the move to the plane + PM_ClipVelocity( clipVelocity, planes[j], clipVelocity, OVERCLIP ); + PM_ClipVelocity( endClipVelocity, planes[j], endClipVelocity, OVERCLIP ); + + // see if it goes back into the first clip plane + if ( DotProduct( clipVelocity, planes[i] ) >= 0 ) { + continue; + } + + // slide the original velocity along the crease + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, pm->ps->velocity ); + VectorScale( dir, d, clipVelocity ); + + CrossProduct (planes[i], planes[j], dir); + VectorNormalize( dir ); + d = DotProduct( dir, endVelocity ); + VectorScale( dir, d, endClipVelocity ); + + // see if there is a third plane the the new move enters + for ( k = 0 ; k < numplanes ; k++ ) { + if ( k == i || k == j ) { + continue; + } + if ( DotProduct( clipVelocity, planes[k] ) >= 0.1 ) { + continue; // move doesn't interact with the plane + } + + // stop dead at a tripple plane interaction + VectorClear( pm->ps->velocity ); + return qtrue; + } + } + + // if we have fixed all interactions, try another move + VectorCopy( clipVelocity, pm->ps->velocity ); + VectorCopy( endClipVelocity, endVelocity ); + break; + } + } + + if ( gravity ) { + VectorCopy( endVelocity, pm->ps->velocity ); + } + + // don't change velocity if in a timer (FIXME: is this correct?) + if ( pm->ps->pm_time ) { + VectorCopy( primal_velocity, pm->ps->velocity ); + } + + return ( bumpcount != 0 ); +} + +/* +================== +PM_StepSlideMove + +================== +*/ +void PM_StepSlideMove( qboolean gravity ) { + vec3_t start_o, start_v; + vec3_t down_o, down_v; + trace_t trace; +// float down_dist, up_dist; +// vec3_t delta, delta2; + vec3_t up, down; + float stepSize; + + VectorCopy (pm->ps->origin, start_o); + VectorCopy (pm->ps->velocity, start_v); + + if ( PM_SlideMove( gravity ) == 0 ) { + return; // we got exactly where we wanted to go first try + } + + VectorCopy(start_o, down); + down[2] -= STEPSIZE; + pm->trace (&trace, start_o, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + VectorSet(up, 0, 0, 1); + // never step up when you still have up velocity + if ( pm->ps->velocity[2] > 0 && (trace.fraction == 1.0 || + DotProduct(trace.plane.normal, up) < 0.7)) { + return; + } + + VectorCopy (pm->ps->origin, down_o); + VectorCopy (pm->ps->velocity, down_v); + + VectorCopy (start_o, up); + up[2] += STEPSIZE; + + // test the player position if they were a stepheight higher + pm->trace (&trace, start_o, pm->mins, pm->maxs, up, pm->ps->clientNum, pm->tracemask); + if ( trace.allsolid ) { + if ( pm->debugLevel ) { + Com_Printf("%i:bend can't step\n", c_pmove); + } + return; // can't step up + } + + stepSize = trace.endpos[2] - start_o[2]; + // try slidemove from this position + VectorCopy (trace.endpos, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + + PM_SlideMove( gravity ); + + // push down the final amount + VectorCopy (pm->ps->origin, down); + down[2] -= stepSize; + pm->trace (&trace, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, pm->tracemask); + if ( !trace.allsolid ) { + VectorCopy (trace.endpos, pm->ps->origin); + } + if ( trace.fraction < 1.0 ) { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + +#if 0 + // if the down trace can trace back to the original position directly, don't step + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, start_o, pm->ps->clientNum, pm->tracemask); + if ( trace.fraction == 1.0 ) { + // use the original move + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + if ( pm->debugLevel ) { + Com_Printf("%i:bend\n", c_pmove); + } + } else +#endif + { + // use the step move + float delta; + + delta = pm->ps->origin[2] - start_o[2]; + if ( delta > 2 ) { + if ( delta < 7 ) { + PM_AddEvent( EV_STEP_4 ); + } else if ( delta < 11 ) { + PM_AddEvent( EV_STEP_8 ); + } else if ( delta < 15 ) { + PM_AddEvent( EV_STEP_12 ); + } else { + PM_AddEvent( EV_STEP_16 ); + } + } + if ( pm->debugLevel ) { + Com_Printf("%i:stepped\n", c_pmove); + } + } +} + diff --git a/quake3/source/code/game/botlib.h b/quake3/source/code/game/botlib.h new file mode 100644 index 0000000..39c92fe --- /dev/null +++ b/quake3/source/code/game/botlib.h @@ -0,0 +1,496 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: botlib.h + * + * desc: bot AI library + * + * $Archive: /source/code/game/botai.h $ + * + *****************************************************************************/ + +#define BOTLIB_API_VERSION 2 + +struct aas_clientmove_s; +struct aas_entityinfo_s; +struct aas_areainfo_s; +struct aas_altroutegoal_s; +struct aas_predictroute_s; +struct bot_consolemessage_s; +struct bot_match_s; +struct bot_goal_s; +struct bot_moveresult_s; +struct bot_initmove_s; +struct weaponinfo_s; + +#define BOTFILESBASEFOLDER "botfiles" +//debug line colors +#define LINECOLOR_NONE -1 +#define LINECOLOR_RED 1//0xf2f2f0f0L +#define LINECOLOR_GREEN 2//0xd0d1d2d3L +#define LINECOLOR_BLUE 3//0xf3f3f1f1L +#define LINECOLOR_YELLOW 4//0xdcdddedfL +#define LINECOLOR_ORANGE 5//0xe0e1e2e3L + +//Print types +#define PRT_MESSAGE 1 +#define PRT_WARNING 2 +#define PRT_ERROR 3 +#define PRT_FATAL 4 +#define PRT_EXIT 5 + +//console message types +#define CMS_NORMAL 0 +#define CMS_CHAT 1 + +//botlib error codes +#define BLERR_NOERROR 0 //no error +#define BLERR_LIBRARYNOTSETUP 1 //library not setup +#define BLERR_INVALIDENTITYNUMBER 2 //invalid entity number +#define BLERR_NOAASFILE 3 //no AAS file available +#define BLERR_CANNOTOPENAASFILE 4 //cannot open AAS file +#define BLERR_WRONGAASFILEID 5 //incorrect AAS file id +#define BLERR_WRONGAASFILEVERSION 6 //incorrect AAS file version +#define BLERR_CANNOTREADAASLUMP 7 //cannot read AAS file lump +#define BLERR_CANNOTLOADICHAT 8 //cannot load initial chats +#define BLERR_CANNOTLOADITEMWEIGHTS 9 //cannot load item weights +#define BLERR_CANNOTLOADITEMCONFIG 10 //cannot load item config +#define BLERR_CANNOTLOADWEAPONWEIGHTS 11 //cannot load weapon weights +#define BLERR_CANNOTLOADWEAPONCONFIG 12 //cannot load weapon config + +//action flags +#define ACTION_ATTACK 0x0000001 +#define ACTION_USE 0x0000002 +#define ACTION_RESPAWN 0x0000008 +#define ACTION_JUMP 0x0000010 +#define ACTION_MOVEUP 0x0000020 +#define ACTION_CROUCH 0x0000080 +#define ACTION_MOVEDOWN 0x0000100 +#define ACTION_MOVEFORWARD 0x0000200 +#define ACTION_MOVEBACK 0x0000800 +#define ACTION_MOVELEFT 0x0001000 +#define ACTION_MOVERIGHT 0x0002000 +#define ACTION_DELAYEDJUMP 0x0008000 +#define ACTION_TALK 0x0010000 +#define ACTION_GESTURE 0x0020000 +#define ACTION_WALK 0x0080000 +#define ACTION_AFFIRMATIVE 0x0100000 +#define ACTION_NEGATIVE 0x0200000 +#define ACTION_GETFLAG 0x0800000 +#define ACTION_GUARDBASE 0x1000000 +#define ACTION_PATROL 0x2000000 +#define ACTION_FOLLOWME 0x8000000 + +//the bot input, will be converted to an usercmd_t +typedef struct bot_input_s +{ + float thinktime; //time since last output (in seconds) + vec3_t dir; //movement direction + float speed; //speed in the range [0, 400] + vec3_t viewangles; //the view angles + int actionflags; //one of the ACTION_? flags + int weapon; //weapon to use +} bot_input_t; + +#ifndef BSPTRACE + +#define BSPTRACE + +//bsp_trace_t hit surface +typedef struct bsp_surface_s +{ + char name[16]; + int flags; + int value; +} bsp_surface_t; + +//remove the bsp_trace_s structure definition l8r on +//a trace is returned when a box is swept through the world +typedef struct bsp_trace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact + float exp_dist; // expanded plane distance + int sidenum; // number of the brush side hit + bsp_surface_t surface; // the hit point surface + int contents; // contents on other side of surface hit + int ent; // number of entity hit +} bsp_trace_t; + +#endif // BSPTRACE + +//entity state +typedef struct bot_entitystate_s +{ + int type; // entity type + int flags; // entity flags + vec3_t origin; // origin of the entity + vec3_t angles; // angles of the model + vec3_t old_origin; // for lerping + vec3_t mins; // bounding box minimums + vec3_t maxs; // bounding box maximums + int groundent; // ground entity + int solid; // solid type + int modelindex; // model used + int modelindex2; // weapons, CTF flags, etc + int frame; // model frame number + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; // even parameter + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT +} bot_entitystate_t; + +//bot AI library exported functions +typedef struct botlib_import_s +{ + //print messages from the bot library + void (QDECL *Print)(int type, char *fmt, ...); + //trace a bbox through the world + void (*Trace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask); + //trace a bbox against a specific entity + void (*EntityTrace)(bsp_trace_t *trace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int entnum, int contentmask); + //retrieve the contents at the given point + int (*PointContents)(vec3_t point); + //check if the point is in potential visible sight + int (*inPVS)(vec3_t p1, vec3_t p2); + //retrieve the BSP entity data lump + char *(*BSPEntityData)(void); + // + void (*BSPModelMinsMaxsOrigin)(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); + //send a bot client command + void (*BotClientCommand)(int client, char *command); + //memory allocation + void *(*GetMemory)(int size); // allocate from Zone + void (*FreeMemory)(void *ptr); // free memory from Zone + int (*AvailableMemory)(void); // available Zone memory + void *(*HunkAlloc)(int size); // allocate from hunk + //file system access + int (*FS_FOpenFile)( const char *qpath, fileHandle_t *file, fsMode_t mode ); + int (*FS_Read)( void *buffer, int len, fileHandle_t f ); + int (*FS_Write)( const void *buffer, int len, fileHandle_t f ); + void (*FS_FCloseFile)( fileHandle_t f ); + int (*FS_Seek)( fileHandle_t f, long offset, int origin ); + //debug visualisation stuff + int (*DebugLineCreate)(void); + void (*DebugLineDelete)(int line); + void (*DebugLineShow)(int line, vec3_t start, vec3_t end, int color); + // + int (*DebugPolygonCreate)(int color, int numPoints, vec3_t *points); + void (*DebugPolygonDelete)(int id); +} botlib_import_t; + +typedef struct aas_export_s +{ + //----------------------------------- + // be_aas_entity.h + //----------------------------------- + void (*AAS_EntityInfo)(int entnum, struct aas_entityinfo_s *info); + //----------------------------------- + // be_aas_main.h + //----------------------------------- + int (*AAS_Initialized)(void); + void (*AAS_PresenceTypeBoundingBox)(int presencetype, vec3_t mins, vec3_t maxs); + float (*AAS_Time)(void); + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + int (*AAS_PointAreaNum)(vec3_t point); + int (*AAS_PointReachabilityAreaIndex)( vec3_t point ); + int (*AAS_TraceAreas)(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + int (*AAS_BBoxAreas)(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); + int (*AAS_AreaInfo)( int areanum, struct aas_areainfo_s *info ); + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + int (*AAS_PointContents)(vec3_t point); + int (*AAS_NextBSPEntity)(int ent); + int (*AAS_ValueForBSPEpairKey)(int ent, char *key, char *value, int size); + int (*AAS_VectorForBSPEpairKey)(int ent, char *key, vec3_t v); + int (*AAS_FloatForBSPEpairKey)(int ent, char *key, float *value); + int (*AAS_IntForBSPEpairKey)(int ent, char *key, int *value); + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + int (*AAS_AreaReachability)(int areanum); + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + int (*AAS_AreaTravelTimeToGoalArea)(int areanum, vec3_t origin, int goalareanum, int travelflags); + int (*AAS_EnableRoutingArea)(int areanum, int enable); + int (*AAS_PredictRoute)(struct aas_predictroute_s *route, int areanum, vec3_t origin, + int goalareanum, int travelflags, int maxareas, int maxtime, + int stopevent, int stopcontents, int stoptfl, int stopareanum); + //-------------------------------------------- + // be_aas_altroute.c + //-------------------------------------------- + int (*AAS_AlternativeRouteGoals)(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + struct aas_altroutegoal_s *altroutegoals, int maxaltroutegoals, + int type); + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + int (*AAS_Swimming)(vec3_t origin); + int (*AAS_PredictClientMovement)(struct aas_clientmove_s *move, + int entnum, vec3_t origin, + int presencetype, int onground, + vec3_t velocity, vec3_t cmdmove, + int cmdframes, + int maxframes, float frametime, + int stopevent, int stopareanum, int visualize); +} aas_export_t; + +typedef struct ea_export_s +{ + //ClientCommand elementary actions + void (*EA_Command)(int client, char *command ); + void (*EA_Say)(int client, char *str); + void (*EA_SayTeam)(int client, char *str); + // + void (*EA_Action)(int client, int action); + void (*EA_Gesture)(int client); + void (*EA_Talk)(int client); + void (*EA_Attack)(int client); + void (*EA_Use)(int client); + void (*EA_Respawn)(int client); + void (*EA_MoveUp)(int client); + void (*EA_MoveDown)(int client); + void (*EA_MoveForward)(int client); + void (*EA_MoveBack)(int client); + void (*EA_MoveLeft)(int client); + void (*EA_MoveRight)(int client); + void (*EA_Crouch)(int client); + + void (*EA_SelectWeapon)(int client, int weapon); + void (*EA_Jump)(int client); + void (*EA_DelayedJump)(int client); + void (*EA_Move)(int client, vec3_t dir, float speed); + void (*EA_View)(int client, vec3_t viewangles); + //send regular input to the server + void (*EA_EndRegular)(int client, float thinktime); + void (*EA_GetInput)(int client, float thinktime, bot_input_t *input); + void (*EA_ResetInput)(int client); +} ea_export_t; + +typedef struct ai_export_s +{ + //----------------------------------- + // be_ai_char.h + //----------------------------------- + int (*BotLoadCharacter)(char *charfile, float skill); + void (*BotFreeCharacter)(int character); + float (*Characteristic_Float)(int character, int index); + float (*Characteristic_BFloat)(int character, int index, float min, float max); + int (*Characteristic_Integer)(int character, int index); + int (*Characteristic_BInteger)(int character, int index, int min, int max); + void (*Characteristic_String)(int character, int index, char *buf, int size); + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + int (*BotAllocChatState)(void); + void (*BotFreeChatState)(int handle); + void (*BotQueueConsoleMessage)(int chatstate, int type, char *message); + void (*BotRemoveConsoleMessage)(int chatstate, int handle); + int (*BotNextConsoleMessage)(int chatstate, struct bot_consolemessage_s *cm); + int (*BotNumConsoleMessages)(int chatstate); + void (*BotInitialChat)(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotNumInitialChats)(int chatstate, char *type); + int (*BotReplyChat)(int chatstate, char *message, int mcontext, int vcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7); + int (*BotChatLength)(int chatstate); + void (*BotEnterChat)(int chatstate, int client, int sendto); + void (*BotGetChatMessage)(int chatstate, char *buf, int size); + int (*StringContains)(char *str1, char *str2, int casesensitive); + int (*BotFindMatch)(char *str, struct bot_match_s *match, unsigned long int context); + void (*BotMatchVariable)(struct bot_match_s *match, int variable, char *buf, int size); + void (*UnifyWhiteSpaces)(char *string); + void (*BotReplaceSynonyms)(char *string, unsigned long int context); + int (*BotLoadChatFile)(int chatstate, char *chatfile, char *chatname); + void (*BotSetChatGender)(int chatstate, int gender); + void (*BotSetChatName)(int chatstate, char *name, int client); + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + void (*BotResetGoalState)(int goalstate); + void (*BotResetAvoidGoals)(int goalstate); + void (*BotRemoveFromAvoidGoals)(int goalstate, int number); + void (*BotPushGoal)(int goalstate, struct bot_goal_s *goal); + void (*BotPopGoal)(int goalstate); + void (*BotEmptyGoalStack)(int goalstate); + void (*BotDumpAvoidGoals)(int goalstate); + void (*BotDumpGoalStack)(int goalstate); + void (*BotGoalName)(int number, char *name, int size); + int (*BotGetTopGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotGetSecondGoal)(int goalstate, struct bot_goal_s *goal); + int (*BotChooseLTGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags); + int (*BotChooseNBGItem)(int goalstate, vec3_t origin, int *inventory, int travelflags, + struct bot_goal_s *ltg, float maxtime); + int (*BotTouchingGoal)(vec3_t origin, struct bot_goal_s *goal); + int (*BotItemGoalInVisButNotVisible)(int viewer, vec3_t eye, vec3_t viewangles, struct bot_goal_s *goal); + int (*BotGetLevelItemGoal)(int index, char *classname, struct bot_goal_s *goal); + int (*BotGetNextCampSpotGoal)(int num, struct bot_goal_s *goal); + int (*BotGetMapLocationGoal)(char *name, struct bot_goal_s *goal); + float (*BotAvoidGoalTime)(int goalstate, int number); + void (*BotSetAvoidGoalTime)(int goalstate, int number, float avoidtime); + void (*BotInitLevelItems)(void); + void (*BotUpdateEntityItems)(void); + int (*BotLoadItemWeights)(int goalstate, char *filename); + void (*BotFreeItemWeights)(int goalstate); + void (*BotInterbreedGoalFuzzyLogic)(int parent1, int parent2, int child); + void (*BotSaveGoalFuzzyLogic)(int goalstate, char *filename); + void (*BotMutateGoalFuzzyLogic)(int goalstate, float range); + int (*BotAllocGoalState)(int client); + void (*BotFreeGoalState)(int handle); + //----------------------------------- + // be_ai_move.h + //----------------------------------- + void (*BotResetMoveState)(int movestate); + void (*BotMoveToGoal)(struct bot_moveresult_s *result, int movestate, struct bot_goal_s *goal, int travelflags); + int (*BotMoveInDirection)(int movestate, vec3_t dir, float speed, int type); + void (*BotResetAvoidReach)(int movestate); + void (*BotResetLastAvoidReach)(int movestate); + int (*BotReachabilityArea)(vec3_t origin, int testground); + int (*BotMovementViewTarget)(int movestate, struct bot_goal_s *goal, int travelflags, float lookahead, vec3_t target); + int (*BotPredictVisiblePosition)(vec3_t origin, int areanum, struct bot_goal_s *goal, int travelflags, vec3_t target); + int (*BotAllocMoveState)(void); + void (*BotFreeMoveState)(int handle); + void (*BotInitMoveState)(int handle, struct bot_initmove_s *initmove); + void (*BotAddAvoidSpot)(int movestate, vec3_t origin, float radius, int type); + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + int (*BotChooseBestFightWeapon)(int weaponstate, int *inventory); + void (*BotGetWeaponInfo)(int weaponstate, int weapon, struct weaponinfo_s *weaponinfo); + int (*BotLoadWeaponWeights)(int weaponstate, char *filename); + int (*BotAllocWeaponState)(void); + void (*BotFreeWeaponState)(int weaponstate); + void (*BotResetWeaponState)(int weaponstate); + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + int (*GeneticParentsAndChildSelection)(int numranks, float *ranks, int *parent1, int *parent2, int *child); +} ai_export_t; + +//bot AI library imported functions +typedef struct botlib_export_s +{ + //Area Awareness System functions + aas_export_t aas; + //Elementary Action functions + ea_export_t ea; + //AI functions + ai_export_t ai; + //setup the bot library, returns BLERR_ + int (*BotLibSetup)(void); + //shutdown the bot library, returns BLERR_ + int (*BotLibShutdown)(void); + //sets a library variable returns BLERR_ + int (*BotLibVarSet)(char *var_name, char *value); + //gets a library variable returns BLERR_ + int (*BotLibVarGet)(char *var_name, char *value, int size); + + //sets a C-like define returns BLERR_ + int (*PC_AddGlobalDefine)(char *string); + int (*PC_LoadSourceHandle)(const char *filename); + int (*PC_FreeSourceHandle)(int handle); + int (*PC_ReadTokenHandle)(int handle, pc_token_t *pc_token); + int (*PC_SourceFileAndLine)(int handle, char *filename, int *line); + + //start a frame in the bot library + int (*BotLibStartFrame)(float time); + //load a new map in the bot library + int (*BotLibLoadMap)(const char *mapname); + //entity updates + int (*BotLibUpdateEntity)(int ent, bot_entitystate_t *state); + //just for testing + int (*Test)(int parm0, char *parm1, vec3_t parm2, vec3_t parm3); +} botlib_export_t; + +//linking of bot library +botlib_export_t *GetBotLibAPI( int apiVersion, botlib_import_t *import ); + +/* Library variables: + +name: default: module(s): description: + +"basedir" "" l_utils.c base directory +"gamedir" "" l_utils.c game directory +"cddir" "" l_utils.c CD directory + +"log" "0" l_log.c enable/disable creating a log file +"maxclients" "4" be_interface.c maximum number of clients +"maxentities" "1024" be_interface.c maximum number of entities +"bot_developer" "0" be_interface.c bot developer mode + +"phys_friction" "6" be_aas_move.c ground friction +"phys_stopspeed" "100" be_aas_move.c stop speed +"phys_gravity" "800" be_aas_move.c gravity value +"phys_waterfriction" "1" be_aas_move.c water friction +"phys_watergravity" "400" be_aas_move.c gravity in water +"phys_maxvelocity" "320" be_aas_move.c maximum velocity +"phys_maxwalkvelocity" "320" be_aas_move.c maximum walk velocity +"phys_maxcrouchvelocity" "100" be_aas_move.c maximum crouch velocity +"phys_maxswimvelocity" "150" be_aas_move.c maximum swim velocity +"phys_walkaccelerate" "10" be_aas_move.c walk acceleration +"phys_airaccelerate" "1" be_aas_move.c air acceleration +"phys_swimaccelerate" "4" be_aas_move.c swim acceleration +"phys_maxstep" "18" be_aas_move.c maximum step height +"phys_maxsteepness" "0.7" be_aas_move.c maximum floor steepness +"phys_maxbarrier" "32" be_aas_move.c maximum barrier height +"phys_maxwaterjump" "19" be_aas_move.c maximum waterjump height +"phys_jumpvel" "270" be_aas_move.c jump z velocity +"phys_falldelta5" "40" be_aas_move.c +"phys_falldelta10" "60" be_aas_move.c +"rs_waterjump" "400" be_aas_move.c +"rs_teleport" "50" be_aas_move.c +"rs_barrierjump" "100" be_aas_move.c +"rs_startcrouch" "300" be_aas_move.c +"rs_startgrapple" "500" be_aas_move.c +"rs_startwalkoffledge" "70" be_aas_move.c +"rs_startjump" "300" be_aas_move.c +"rs_rocketjump" "500" be_aas_move.c +"rs_bfgjump" "500" be_aas_move.c +"rs_jumppad" "250" be_aas_move.c +"rs_aircontrolledjumppad" "300" be_aas_move.c +"rs_funcbob" "300" be_aas_move.c +"rs_startelevator" "50" be_aas_move.c +"rs_falldamage5" "300" be_aas_move.c +"rs_falldamage10" "500" be_aas_move.c +"rs_maxjumpfallheight" "450" be_aas_move.c + +"max_aaslinks" "4096" be_aas_sample.c maximum links in the AAS +"max_routingcache" "4096" be_aas_route.c maximum routing cache size in KB +"forceclustering" "0" be_aas_main.c force recalculation of clusters +"forcereachability" "0" be_aas_main.c force recalculation of reachabilities +"forcewrite" "0" be_aas_main.c force writing of aas file +"aasoptimize" "0" be_aas_main.c enable aas optimization +"sv_mapChecksum" "0" be_aas_main.c BSP file checksum +"bot_visualizejumppads" "0" be_aas_reach.c visualize jump pads + +"bot_reloadcharacters" "0" - reload bot character files +"ai_gametype" "0" be_ai_goal.c game type +"droppedweight" "1000" be_ai_goal.c additional dropped item weight +"weapindex_rocketlauncher" "5" be_ai_move.c rl weapon index for rocket jumping +"weapindex_bfg10k" "9" be_ai_move.c bfg weapon index for bfg jumping +"weapindex_grapple" "10" be_ai_move.c grapple weapon index for grappling +"entitytypemissile" "3" be_ai_move.c ET_MISSILE +"offhandgrapple" "0" be_ai_move.c enable off hand grapple hook +"cmd_grappleon" "grappleon" be_ai_move.c command to activate off hand grapple +"cmd_grappleoff" "grappleoff" be_ai_move.c command to deactivate off hand grapple +"itemconfig" "items.c" be_ai_goal.c item configuration file +"weaponconfig" "weapons.c" be_ai_weap.c weapon configuration file +"synfile" "syn.c" be_ai_chat.c file with synonyms +"rndfile" "rnd.c" be_ai_chat.c file with random strings +"matchfile" "match.c" be_ai_chat.c file with match strings +"nochat" "0" be_ai_chat.c disable chats +"max_messages" "1024" be_ai_chat.c console message heap size +"max_weaponinfo" "32" be_ai_weap.c maximum number of weapon info +"max_projectileinfo" "32" be_ai_weap.c maximum number of projectile info +"max_iteminfo" "256" be_ai_goal.c maximum number of item info +"max_levelitems" "256" be_ai_goal.c maximum number of level items + +*/ + diff --git a/quake3/source/code/game/chars.h b/quake3/source/code/game/chars.h new file mode 100644 index 0000000..d4a877b --- /dev/null +++ b/quake3/source/code/game/chars.h @@ -0,0 +1,135 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +//=========================================================================== +// +// Name: chars.h +// Function: bot characteristics +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-09-08 +// Last update: 2000-09-07 PKMOD Ergodic - PainkeepArena Integration +// 2000-10-16 PKMOD Ergodic, fixed characteristic 48, af and walker bug +// Tab Size: 4 (real tabs) +//=========================================================================== + + +//======================================================== +//======================================================== +//name +#define CHARACTERISTIC_NAME 0 //string +//gender of the bot +#define CHARACTERISTIC_GENDER 1 //string ("male", "female", "it") +//attack skill +// > 0.0 && < 0.2 = don't move +// > 0.3 && < 1.0 = aim at enemy during retreat +// > 0.0 && < 0.4 = only move forward/backward +// >= 0.4 && < 1.0 = circle strafing +// > 0.7 && < 1.0 = random strafe direction change +#define CHARACTERISTIC_ATTACK_SKILL 2 //float [0, 1] +//weapon weight file +#define CHARACTERISTIC_WEAPONWEIGHTS 3 //string +//view angle difference to angle change factor +#define CHARACTERISTIC_VIEW_FACTOR 4 //float <0, 1] +//maximum view angle change +#define CHARACTERISTIC_VIEW_MAXCHANGE 5 //float [1, 360] +//reaction time in seconds +#define CHARACTERISTIC_REACTIONTIME 6 //float [0, 5] +//accuracy when aiming +#define CHARACTERISTIC_AIM_ACCURACY 7 //float [0, 1] +//weapon specific aim accuracy +#define CHARACTERISTIC_AIM_ACCURACY_MACHINEGUN 8 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_SHOTGUN 9 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_ROCKETLAUNCHER 10 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_GRENADELAUNCHER 11 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_LIGHTNING 12 +#define CHARACTERISTIC_AIM_ACCURACY_PLASMAGUN 13 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_RAILGUN 14 +#define CHARACTERISTIC_AIM_ACCURACY_BFG10K 15 //float [0, 1] +//skill when aiming +// > 0.0 && < 0.9 = aim is affected by enemy movement +// > 0.4 && <= 0.8 = enemy linear leading +// > 0.8 && <= 1.0 = enemy exact movement leading +// > 0.5 && <= 1.0 = prediction shots when enemy is not visible +// > 0.6 && <= 1.0 = splash damage by shooting nearby geometry +#define CHARACTERISTIC_AIM_SKILL 16 //float [0, 1] +//weapon specific aim skill +#define CHARACTERISTIC_AIM_SKILL_ROCKETLAUNCHER 17 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_GRENADELAUNCHER 18 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_PLASMAGUN 19 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_BFG10K 20 //float [0, 1] +//======================================================== +//chat +//======================================================== +//file with chats +#define CHARACTERISTIC_CHAT_FILE 21 //string +//name of the chat character +#define CHARACTERISTIC_CHAT_NAME 22 //string +//characters per minute type speed +#define CHARACTERISTIC_CHAT_CPM 23 //integer [1, 4000] +//tendency to insult/praise +#define CHARACTERISTIC_CHAT_INSULT 24 //float [0, 1] +//tendency to chat misc +#define CHARACTERISTIC_CHAT_MISC 25 //float [0, 1] +//tendency to chat at start or end of level +#define CHARACTERISTIC_CHAT_STARTENDLEVEL 26 //float [0, 1] +//tendency to chat entering or exiting the game +#define CHARACTERISTIC_CHAT_ENTEREXITGAME 27 //float [0, 1] +//tendency to chat when killed someone +#define CHARACTERISTIC_CHAT_KILL 28 //float [0, 1] +//tendency to chat when died +#define CHARACTERISTIC_CHAT_DEATH 29 //float [0, 1] +//tendency to chat when enemy suicides +#define CHARACTERISTIC_CHAT_ENEMYSUICIDE 30 //float [0, 1] +//tendency to chat when hit while talking +#define CHARACTERISTIC_CHAT_HITTALKING 31 //float [0, 1] +//tendency to chat when bot was hit but didn't dye +#define CHARACTERISTIC_CHAT_HITNODEATH 32 //float [0, 1] +//tendency to chat when bot hit the enemy but enemy didn't dye +#define CHARACTERISTIC_CHAT_HITNOKILL 33 //float [0, 1] +//tendency to randomly chat +#define CHARACTERISTIC_CHAT_RANDOM 34 //float [0, 1] +//tendency to reply +#define CHARACTERISTIC_CHAT_REPLY 35 //float [0, 1] +//======================================================== +//movement +//======================================================== +//tendency to crouch +#define CHARACTERISTIC_CROUCHER 36 //float [0, 1] +//tendency to jump +#define CHARACTERISTIC_JUMPER 37 //float [0, 1] +//tendency to walk +#define CHARACTERISTIC_WALKER 48 //float [0, 1] +//tendency to jump using a weapon +#define CHARACTERISTIC_WEAPONJUMPING 38 //float [0, 1] +//tendency to use the grapple hook when available +#define CHARACTERISTIC_GRAPPLE_USER 39 //float [0, 1] //use this!! +//======================================================== +//goal +//======================================================== +//item weight file +#define CHARACTERISTIC_ITEMWEIGHTS 40 //string +//the aggression of the bot +#define CHARACTERISTIC_AGGRESSION 41 //float [0, 1] +//the self preservation of the bot (rockets near walls etc.) +#define CHARACTERISTIC_SELFPRESERVATION 42 //float [0, 1] +//how likely the bot is to take revenge +#define CHARACTERISTIC_VENGEFULNESS 43 //float [0, 1] //use this!! +//tendency to camp +#define CHARACTERISTIC_CAMPER 44 //float [0, 1] +//======================================================== +//======================================================== +//tendency to get easy frags +#define CHARACTERISTIC_EASY_FRAGGER 45 //float [0, 1] +//how alert the bot is (view distance) +#define CHARACTERISTIC_ALERTNESS 46 //float [0, 1] +//how much the bot fires it's weapon +#define CHARACTERISTIC_FIRETHROTTLE 47 //float [0, 1] + +//PKMOD - Ergodic - 09/07/00 Add PKA weapon specific definitions +#define CHARACTERISTIC_AIM_ACCURACY_AIRFIST 49 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_AIRFIST 50 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_NAILGUN 51 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_NAILGUN 52 //float [0, 1] +#define CHARACTERISTIC_AIM_ACCURACY_BEARTRAP 53 //float [0, 1] +#define CHARACTERISTIC_AIM_SKILL_BEARTRAP 54 //float [0, 1] + + diff --git a/quake3/source/code/game/g_active.c b/quake3/source/code/game/g_active.c new file mode 100644 index 0000000..6bad263 --- /dev/null +++ b/quake3/source/code/game/g_active.c @@ -0,0 +1,1272 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +#include "g_local.h" + + +/* +=============== +G_DamageFeedback + +Called just before a snapshot is sent to the given player. +Totals up all damage and generates both the player_state_t +damage values to that client for pain blends and kicks, and +global pain sound events for all clients. +=============== +*/ +void P_DamageFeedback( gentity_t *player ) { + gclient_t *client; + float count; + vec3_t angles; + + client = player->client; + if ( client->ps.pm_type == PM_DEAD ) { + return; + } + + // total points of damage shot at the player this frame + count = client->damage_blood + client->damage_armor; + if ( count == 0 ) { + return; // didn't take any damage + } + + if ( count > 255 ) { + count = 255; + } + + // send the information to the client + + // world damage (falling, slime, etc) uses a special code + // to make the blend blob centered instead of positional + if ( client->damage_fromWorld ) { + client->ps.damagePitch = 255; + client->ps.damageYaw = 255; + + client->damage_fromWorld = qfalse; + } else { + vectoangles( client->damage_from, angles ); + client->ps.damagePitch = angles[PITCH]/360.0 * 256; + client->ps.damageYaw = angles[YAW]/360.0 * 256; + } + + // play an apropriate pain sound + if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) ) { + player->pain_debounce_time = level.time + 700; + G_AddEvent( player, EV_PAIN, player->health ); + client->ps.damageEvent++; + } + + + client->ps.damageCount = count; + + // + // clear totals + // + client->damage_blood = 0; + client->damage_armor = 0; + client->damage_knockback = 0; +} + + + +/* +============= +P_WorldEffects + +Check for lava / slime contents and drowning +============= +*/ +void P_WorldEffects( gentity_t *ent ) { + qboolean envirosuit; + int waterlevel; + + if ( ent->client->noclip ) { + ent->client->airOutTime = level.time + 12000; // don't need air + return; + } + + waterlevel = ent->waterlevel; + + envirosuit = ent->client->ps.powerups[PW_BATTLESUIT] > level.time; + + // + // check for drowning + // + if ( waterlevel == 3 ) { + // envirosuit give air + if ( envirosuit ) { + ent->client->airOutTime = level.time + 10000; + } + + // if out of air, start drowning + if ( ent->client->airOutTime < level.time) { + // drown! + ent->client->airOutTime += 1000; + if ( ent->health > 0 ) { + // take more damage the longer underwater + ent->damage += 2; + if (ent->damage > 15) + ent->damage = 15; + + // play a gurp sound instead of a normal pain sound + if (ent->health <= ent->damage) { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("*drown.wav")); + } else if (rand()&1) { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp1.wav")); + } else { + G_Sound(ent, CHAN_VOICE, G_SoundIndex("sound/player/gurp2.wav")); + } + + // don't play a normal pain sound + ent->pain_debounce_time = level.time + 200; + + G_Damage (ent, NULL, NULL, NULL, NULL, + ent->damage, DAMAGE_NO_ARMOR, MOD_WATER); + } + } + } else { + ent->client->airOutTime = level.time + 12000; + ent->damage = 2; + } + + // + // check for sizzle damage (move to pmove?) + // + if (waterlevel && + (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + if (ent->health > 0 + && ent->pain_debounce_time <= level.time ) { + + if ( envirosuit ) { + G_AddEvent( ent, EV_POWERUP_BATTLESUIT, 0 ); + } else { + if (ent->watertype & CONTENTS_LAVA) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 30*waterlevel, 0, MOD_LAVA); + } + + if (ent->watertype & CONTENTS_SLIME) { + G_Damage (ent, NULL, NULL, NULL, NULL, + 10*waterlevel, 0, MOD_SLIME); + } + } + } + } +} + + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) { +#ifdef MISSIONPACK + if( ent->s.eFlags & EF_TICKING ) { + ent->client->ps.loopSound = G_SoundIndex( "sound/weapons/proxmine/wstbtick.wav"); + } + else +#endif + if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + ent->client->ps.loopSound = level.snd_fry; + } else { + ent->client->ps.loopSound = 0; + } +} + + + +//============================================================== + +/* +============== +ClientImpacts +============== +*/ +void ClientImpacts( gentity_t *ent, pmove_t *pm ) { + int i, j; + trace_t trace; + gentity_t *other; + + memset( &trace, 0, sizeof( trace ) ); + for (i=0 ; inumtouch ; i++) { + for (j=0 ; jtouchents[j] == pm->touchents[i] ) { + break; + } + } + if (j != i) { + continue; // duplicated + } + other = &g_entities[ pm->touchents[i] ]; + + //PKMOD - Ergodic 01/24/01 - debug inactive +// Com_Printf("ClientImpacts - other->classname>%s<\n", other->classname ); + + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, other, &trace ); + } + + if ( !other->touch ) { + continue; + } + + other->touch( other, ent, &trace ); + } + +} + +/* +============ +G_TouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_TouchTriggers( gentity_t *ent ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs; + static vec3_t range = { 40, 40, 52 }; + + if ( !ent->client ) { + return; + } + + // dead clients don't activate triggers! + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { + return; + } + + VectorSubtract( ent->client->ps.origin, range, mins ); + VectorAdd( ent->client->ps.origin, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->absmin, because that has a one unit pad + VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); + VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); + + for ( i=0 ; itouch && !ent->touch ) { + continue; + } + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) { + continue; + } + + // ignore most entities if a spectator + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( hit->s.eType != ET_TELEPORT_TRIGGER && + // this is ugly but adding a new ET_? type will + // most likely cause network incompatibilities + hit->touch != Touch_DoorTrigger) { + continue; + } + } + + // use seperate code for determining if an item is picked up + // so you don't have to actually contact its bounding box + //PKA - Ergodic 05/30/00, modify for touching an active beartrap + if ( hit->s.eType == ET_ITEM || hit->s.eType == ET_BEARTRAP) { + if ( !BG_PlayerTouchesItem( &ent->client->ps, &hit->s, level.time ) ) { + continue; + } + } else { + if ( !trap_EntityContact( mins, maxs, hit ) ) { + continue; + } + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->touch ) { + hit->touch (hit, ent, &trace); + } + + if ( ( ent->r.svFlags & SVF_BOT ) && ( ent->touch ) ) { + ent->touch( ent, hit, &trace ); + } + } + + // if we didn't touch a jump pad this pmove frame + if ( ent->client->ps.jumppad_frame != ent->client->ps.pmove_framecount ) { + ent->client->ps.jumppad_frame = 0; + ent->client->ps.jumppad_ent = 0; + } +} + +/* +================= +SpectatorThink +================= +*/ +void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) { + pmove_t pm; + gclient_t *client; + + client = ent->client; + + if ( client->sess.spectatorState != SPECTATOR_FOLLOW ) { + client->ps.pm_type = PM_SPECTATOR; + client->ps.speed = 400; // faster than normal + + // set up for pmove + memset (&pm, 0, sizeof(pm)); + pm.ps = &client->ps; + pm.cmd = *ucmd; + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; // spectators can fly through bodies + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + + // perform a pmove + Pmove (&pm); + // save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + G_TouchTriggers( ent ); + trap_UnlinkEntity( ent ); + } + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + + // attack button cycles through spectators + if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { + Cmd_FollowCycle_f( ent, 1 ); + } +} + + + +/* +================= +ClientInactivityTimer + +Returns qfalse if the client is dropped +================= +*/ +qboolean ClientInactivityTimer( gclient_t *client ) { + if ( ! g_inactivity.integer ) { + // give everyone some time, so if the operator sets g_inactivity during + // gameplay, everyone isn't kicked + client->inactivityTime = level.time + 60 * 1000; + client->inactivityWarning = qfalse; + } else if ( client->pers.cmd.forwardmove || + client->pers.cmd.rightmove || + client->pers.cmd.upmove || + (client->pers.cmd.buttons & BUTTON_ATTACK) ) { + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->inactivityWarning = qfalse; + } else if ( !client->pers.localClient ) { + if ( level.time > client->inactivityTime ) { + trap_DropClient( client - level.clients, "Dropped due to inactivity" ); + return qfalse; + } + if ( level.time > client->inactivityTime - 10000 && !client->inactivityWarning ) { + client->inactivityWarning = qtrue; + trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); + } + } + return qtrue; +} + +/* +================== +ClientTimerActions + +Actions that happen once a second +================== +*/ +void ClientTimerActions( gentity_t *ent, int msec ) { + gclient_t *client; +#ifdef MISSIONPACK + int maxHealth; +#endif + + client = ent->client; + client->timeResidual += msec; + + while ( client->timeResidual >= 1000 ) { + client->timeResidual -= 1000; + + // regenerate +#ifdef MISSIONPACK + if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + maxHealth = client->ps.stats[STAT_MAX_HEALTH] / 2; + } + else if ( client->ps.powerups[PW_REGEN] ) { + maxHealth = client->ps.stats[STAT_MAX_HEALTH]; + } + else { + maxHealth = 0; + } + if( maxHealth ) { + if ( ent->health < maxHealth ) { + ent->health += 15; + if ( ent->health > maxHealth * 1.1 ) { + ent->health = maxHealth * 1.1; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } else if ( ent->health < maxHealth * 2) { + ent->health += 5; + if ( ent->health > maxHealth * 2 ) { + ent->health = maxHealth * 2; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } +#else + if ( client->ps.powerups[PW_REGEN] ) { + if ( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { + ent->health += 15; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 1.1; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } else if ( ent->health < client->ps.stats[STAT_MAX_HEALTH] * 2) { + ent->health += 5; + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 2 ) { + ent->health = client->ps.stats[STAT_MAX_HEALTH] * 2; + } + G_AddEvent( ent, EV_POWERUP_REGEN, 0 ); + } +#endif + //PKMOD - Ergodic 06/19/00 REGEN will turn off the beans status flag + ent->client->ps.stats[STAT_PKA_ITEMS] &= ~( 1 << PKA_BEANS ); + } else { + // count down health when over max + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { + //PKMOD - Ergodic 06/19/00 only count down health if beans flag is off + if (! ( ent->client->ps.stats[STAT_PKA_ITEMS] & ( 1 << PKA_BEANS ) ) ) { + ent->health--; + } + } + else { + //PKMOD - Ergodic 01/07/04 - add quad farting logic... + // if quaded do not turn off beans fart effect until toot_think is over + if ( !( ent->client->ps.powerups[PW_QUAD] > 0 ) ) { + //PKMOD - Ergodic 06/19/00 Health not at max will turn off the beans status flag + ent->client->ps.stats[STAT_PKA_ITEMS] &= ~( 1 << PKA_BEANS ); + } + } + } + + // count down armor when over max + if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { + //PKMOD - Ergodic 06/03/01 - add bean powerup timer for armor countdown immunity, + // only decrement armor if bean powerup is not active + if ( !client->ps.powerups[PW_BEANS] ) + client->ps.stats[STAT_ARMOR]--; + } + } +#ifdef MISSIONPACK + if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { + int w, max, inc, t, i; + int weapList[]={WP_MACHINEGUN,WP_SHOTGUN,WP_GRENADE_LAUNCHER,WP_ROCKET_LAUNCHER,WP_LIGHTNING,WP_RAILGUN,WP_PLASMAGUN,WP_BFG,WP_NAILGUN,WP_PROX_LAUNCHER,WP_CHAINGUN}; + int weapCount = sizeof(weapList) / sizeof(int); + // + for (i = 0; i < weapCount; i++) { + w = weapList[i]; + + switch(w) { + case WP_MACHINEGUN: max = 50; inc = 4; t = 1000; break; + case WP_SHOTGUN: max = 10; inc = 1; t = 1500; break; + case WP_GRENADE_LAUNCHER: max = 10; inc = 1; t = 2000; break; + case WP_ROCKET_LAUNCHER: max = 10; inc = 1; t = 1750; break; + case WP_LIGHTNING: max = 50; inc = 5; t = 1500; break; + case WP_RAILGUN: max = 10; inc = 1; t = 1750; break; + case WP_PLASMAGUN: max = 50; inc = 5; t = 1500; break; + case WP_BFG: max = 10; inc = 1; t = 4000; break; + case WP_NAILGUN: max = 10; inc = 1; t = 1250; break; + case WP_PROX_LAUNCHER: max = 5; inc = 1; t = 2000; break; + case WP_CHAINGUN: max = 100; inc = 5; t = 1000; break; + default: max = 0; inc = 0; t = 1000; break; + } + client->ammoTimes[w] += msec; + if ( client->ps.ammo[w] >= max ) { + client->ammoTimes[w] = 0; + } + if ( client->ammoTimes[w] >= t ) { + while ( client->ammoTimes[w] >= t ) + client->ammoTimes[w] -= t; + client->ps.ammo[w] += inc; + if ( client->ps.ammo[w] > max ) { + client->ps.ammo[w] = max; + } + } + } + } +#endif +} + +/* +==================== +ClientIntermissionThink +==================== +*/ +void ClientIntermissionThink( gclient_t *client ) { + client->ps.eFlags &= ~EF_TALK; + client->ps.eFlags &= ~EF_FIRING; + + // the level will exit when everyone wants to or after timeouts + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = client->pers.cmd.buttons; + if ( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) { + // this used to be an ^1 but once a player says ready, it should stick + client->readyToExit = 1; + } +} + + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +void ClientEvents( gentity_t *ent, int oldEventSequence ) { + int i, j; + int event; + gclient_t *client; + int damage; + vec3_t dir; + vec3_t origin, angles; +// qboolean fired; + gitem_t *item; + gentity_t *drop; + + client = ent->client; + + if ( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) { + oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; + } + for ( i = oldEventSequence ; i < client->ps.eventSequence ; i++ ) { + event = client->ps.events[ i & (MAX_PS_EVENTS-1) ]; + + switch ( event ) { + case EV_FALL_MEDIUM: + case EV_FALL_FAR: + if ( ent->s.eType != ET_PLAYER ) { + break; // not in the player model + } + if ( g_dmflags.integer & DF_NO_FALLING ) { + break; + } + if ( event == EV_FALL_FAR ) { + damage = 10; + } else { + damage = 5; + } + VectorSet (dir, 0, 0, 1); + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); + break; + + case EV_FIRE_WEAPON: + FireWeapon( ent ); + break; + + case EV_USE_ITEM1: // teleporter + // drop flags in CTF + item = NULL; + j = 0; + + if ( ent->client->ps.powerups[ PW_REDFLAG ] ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + j = PW_REDFLAG; + } else if ( ent->client->ps.powerups[ PW_BLUEFLAG ] ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + j = PW_BLUEFLAG; + } else if ( ent->client->ps.powerups[ PW_NEUTRALFLAG ] ) { + item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); + j = PW_NEUTRALFLAG; + } + + if ( item ) { + drop = Drop_Item( ent, item, 0 ); + // decide how many seconds it has left + drop->count = ( ent->client->ps.powerups[ j ] - level.time ) / 1000; + if ( drop->count < 1 ) { + drop->count = 1; + } + + ent->client->ps.powerups[ j ] = 0; + } + +#ifdef MISSIONPACK + if ( g_gametype.integer == GT_HARVESTER ) { + if ( ent->client->ps.generic1 > 0 ) { + if ( ent->client->sess.sessionTeam == TEAM_RED ) { + item = BG_FindItem( "Blue Cube" ); + } else { + item = BG_FindItem( "Red Cube" ); + } + if ( item ) { + for ( j = 0; j < ent->client->ps.generic1; j++ ) { + drop = Drop_Item( ent, item, 0 ); + if ( ent->client->sess.sessionTeam == TEAM_RED ) { + drop->spawnflags = TEAM_BLUE; + } else { + drop->spawnflags = TEAM_RED; + } + } + } + ent->client->ps.generic1 = 0; + } + } +#endif + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + //PKMOD - Ergodic 12/17/00 - place PERS_HUB_FLAG as first bit of PERS_PAINKILLER_COUNT + SelectSpawnPoint( ent->client->ps.origin, origin, angles, ( client->ps.persistant[PERS_PAINKILLER_COUNT] & 1 ) ); + //PKMOD - Ergodic 06/29/01 - debug where Personal Teleport occurs (inactive) +// Com_Printf( "ClientEvents - Personal teleport activated\n" ); + + TeleportPlayer( ent, origin, angles ); + break; + + case EV_USE_ITEM2: // medkit + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH] + 25; + + break; + + //PKMOD - Ergodic 10/13/01 - add new holdable + // radiate holdable will act like a powerup + case EV_USE_ITEM3: // radiate + if ( !ent->client->ps.powerups[ PW_RADIATE ] ) { + // round timing to seconds to make multiple powerup timers + // count in sync + ent->client->ps.powerups[ PW_RADIATE ] = level.time - ( level.time % 1000 ); + } + + ent->client->ps.powerups[ PW_RADIATE ] += PLAYER_RADIATE_TIME * 1000; + + break; + + //PKMOD - Ergodic 05/07/02 - add new holdable + // Personal Sentry will act like a powerup + case EV_USE_ITEM4: // radiate + + if ( !ent->client->ps.powerups[ PW_PERSENTRY ] ) { + // round timing to seconds to make multiple powerup timers + // count in sync + ent->client->ps.powerups[ PW_PERSENTRY ] = level.time - ( level.time % 1000 ); + //PKMOD - Ergodic 06/08/02 - setup personal sentry driver + G_AddPersonalSentry( ent ); + } + + ent->client->ps.powerups[ PW_PERSENTRY ] += PLAYER_PERSENTRY_TIME * 1000; + +// G_ActivatePersentry( ent ); + + break; + + + //PKMOD - Ergodic 12/07/01 - add new holdable + // Private Bot holdable + case EV_USE_ITEM7: //BOT HEAD + //PKMOD - Ergodic 02/26/04 - debug Private Bot (inactive) + //Com_Printf( "ClientEvents - EV_USE_ITEM7 - Private Bot (Head), active_private_bots>%d<\n", active_private_bots ); + //PKMOD - Ergodic 02/27/042 - check if room for another Private Bot + if ( active_private_bots < MAX_PRIVATE_BOTS ) { + //PKMOD - Ergodic 02/27/04 - increment active Private Bot counter + active_private_bots++; + //PKMOD - Ergodic 01/06/02 - create private bot + G_AddPrivateBot( ent ); + } + break; + + default: + break; + } + } + +} + +#ifdef MISSIONPACK +/* +============== +StuckInOtherClient +============== +*/ +static int StuckInOtherClient(gentity_t *ent) { + int i; + gentity_t *ent2; + + ent2 = &g_entities[0]; + for ( i = 0; i < MAX_CLIENTS; i++, ent2++ ) { + if ( ent2 == ent ) { + continue; + } + if ( !ent2->inuse ) { + continue; + } + if ( !ent2->client ) { + continue; + } + if ( ent2->health <= 0 ) { + continue; + } + // + if (ent2->r.absmin[0] > ent->r.absmax[0]) + continue; + if (ent2->r.absmin[1] > ent->r.absmax[1]) + continue; + if (ent2->r.absmin[2] > ent->r.absmax[2]) + continue; + if (ent2->r.absmax[0] < ent->r.absmin[0]) + continue; + if (ent2->r.absmax[1] < ent->r.absmin[1]) + continue; + if (ent2->r.absmax[2] < ent->r.absmin[2]) + continue; + return qtrue; + } + return qfalse; +} +#endif + +void BotTestSolid(vec3_t origin); + +/* +============== +SendPendingPredictableEvents +============== +*/ +void SendPendingPredictableEvents( playerState_t *ps ) { + gentity_t *t; + int event, seq; + int extEvent, number; + + // if there are still events pending + if ( ps->entityEventSequence < ps->eventSequence ) { + // create a temporary entity for this event which is sent to everyone + // except the client who generated the event + seq = ps->entityEventSequence & (MAX_PS_EVENTS-1); + event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); + // set external event to zero before calling BG_PlayerStateToEntityState + extEvent = ps->externalEvent; + ps->externalEvent = 0; + // create temporary entity for event + t = G_TempEntity( ps->origin, event ); + number = t->s.number; + BG_PlayerStateToEntityState( ps, &t->s, qtrue ); + t->s.number = number; + t->s.eType = ET_EVENTS + event; + t->s.eFlags |= EF_PLAYER_EVENT; + t->s.otherEntityNum = ps->clientNum; + // send to everyone except the client who generated the event + t->r.svFlags |= SVF_NOTSINGLECLIENT; + t->r.singleClient = ps->clientNum; + // set back external event + ps->externalEvent = extEvent; + } +} + +/* +============== +ClientThink + +This will be called once for each client frame, which will +usually be a couple times for each server frame on fast clients. + +If "g_synchronousClients 1" is set, this will be called exactly +once for each server frame, which makes for smooth demo recording. +============== +*/ +void ClientThink_real( gentity_t *ent ) { + gclient_t *client; + pmove_t pm; + int oldEventSequence; + int msec; + usercmd_t *ucmd; + + client = ent->client; + + // don't think if the client is not yet connected (and thus not yet spawned in) + if (client->pers.connected != CON_CONNECTED) { + return; + } + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + // sanity check the command time to prevent speedup cheating + if ( ucmd->serverTime > level.time + 200 ) { + ucmd->serverTime = level.time + 200; +// G_Printf("serverTime <<<<<\n" ); + } + if ( ucmd->serverTime < level.time - 1000 ) { + ucmd->serverTime = level.time - 1000; +// G_Printf("serverTime >>>>>\n" ); + } + + msec = ucmd->serverTime - client->ps.commandTime; + // following others may result in bad times, but we still want + // to check for follow toggles + if ( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) { + return; + } + if ( msec > 200 ) { + msec = 200; + } + + if ( pmove_msec.integer < 8 ) { + trap_Cvar_Set("pmove_msec", "8"); + } + else if (pmove_msec.integer > 33) { + trap_Cvar_Set("pmove_msec", "33"); + } + + if ( pmove_fixed.integer || client->pers.pmoveFixed ) { + ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + //if (ucmd->serverTime - client->ps.commandTime <= 0) + // return; + } + + // + // check for exiting intermission + // + if ( level.intermissiontime ) { + ClientIntermissionThink( client ); + return; + } + + // spectators don't do much + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + return; + } + SpectatorThink( ent, ucmd ); + return; + } + + // check for inactivity timer, but never drop the local client of a non-dedicated server + if ( !ClientInactivityTimer( client ) ) { + return; + } + + // clear the rewards if time + if ( level.time > client->rewardTime ) { + //PKMOD - Ergodic 08/08/00 add reset for PAINKILLER awarded + client->ps.eFlags &= ~(EF_AWARD_IMPRESSIVE | EF_AWARD_EXCELLENT | EF_AWARD_GAUNTLET | EF_AWARD_PAINKILLER ); + } + + if ( client->noclip ) { + client->ps.pm_type = PM_NOCLIP; + } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + client->ps.pm_type = PM_DEAD; + } else { + client->ps.pm_type = PM_NORMAL; + } + + //PKMOD - Ergodic 04/16/01 - check for special gravity + if ( ent->pka_gravity_time > level.time ) { + client->ps.gravity = ent->pka_gravity; + //PKMOD - Ergodic 04/17/01 - add local gravity trtype + ent->s.pos.trType = TR_LOCAL_GRAVITY; + ent->s.pos.trDuration = ent->pka_gravity; + } + else { + client->ps.gravity = g_gravity.value; + //PKMOD - Ergodic 04/17/01 - restore regular gravity trtype + ent->s.pos.trType = TR_GRAVITY; + } + + // set speed + client->ps.speed = g_speed.value; + +#ifdef MISSIONPACK + if( bg_itemlist[client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + client->ps.speed *= 1.5; + } + else +#endif + if ( client->ps.powerups[PW_HASTE] ) { + client->ps.speed *= 1.3; + } + + // Let go of the hook if we aren't firing + if ( client->ps.weapon == WP_GRAPPLING_HOOK && + client->hook && !( ucmd->buttons & BUTTON_ATTACK ) ) { + //PKMOD - Ergodic 03/01/01 - debug (inactive) +// Com_Printf( "ClientThink_real - calling activate_dragon_deploy, weapon:%d\n", client->hook->s.generic1 & 15 ); + + //PKMOD - Ergodic 03/25/01 - add dragon deployable weapon fire + //PKMOD - Ergodic 03/28/01 - remove this logic (for beta 2.2) + //PKMOD - Ergodic 06/22/01 - re-add the logic + if ( client->hook->s.generic1 != 0 ) { + //PKMOD - Ergodic 12/20/01 - modify activate_dragon_deploy call to add struck entity + //PKMOD - Ergodic 01/26/02 - modify activate_dragon_deploy call to add bytedir for gauntlet + activate_dragon_deploy ( client->hook, NULL, 0 ); + //PKMOD - Ergodic 06/22/01 - do not return yet - need Weapon_HookFree? +// return; + } + Weapon_HookFree(client->hook); + } + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset (&pm, 0, sizeof(pm)); + + // check for the hit-scan gauntlet, don't let the action + // go through as an attack unless it actually hits something + if ( client->ps.weapon == WP_GAUNTLET && !( ucmd->buttons & BUTTON_TALK ) && + ( ucmd->buttons & BUTTON_ATTACK ) && client->ps.weaponTime <= 0 ) { + pm.gauntletHit = CheckGauntletAttack( ent ); + } + + if ( ent->flags & FL_FORCE_GESTURE ) { + ent->flags &= ~FL_FORCE_GESTURE; + ent->client->pers.cmd.buttons |= BUTTON_GESTURE; + } + +#ifdef MISSIONPACK + // check for invulnerability expansion before doing the Pmove + if (client->ps.powerups[PW_INVULNERABILITY] ) { + if ( !(client->ps.pm_flags & PMF_INVULEXPAND) ) { + vec3_t mins = { -42, -42, -42 }; + vec3_t maxs = { 42, 42, 42 }; + vec3_t oldmins, oldmaxs; + + VectorCopy (ent->r.mins, oldmins); + VectorCopy (ent->r.maxs, oldmaxs); + // expand + VectorCopy (mins, ent->r.mins); + VectorCopy (maxs, ent->r.maxs); + trap_LinkEntity(ent); + // check if this would get anyone stuck in this player + if ( !StuckInOtherClient(ent) ) { + // set flag so the expanded size will be set in PM_CheckDuck + client->ps.pm_flags |= PMF_INVULEXPAND; + } + // set back + VectorCopy (oldmins, ent->r.mins); + VectorCopy (oldmaxs, ent->r.maxs); + trap_LinkEntity(ent); + } + } +#endif + + pm.ps = &client->ps; + + //PKMOD - Ergodic 05/12/01 - debug buttons (inactive) +// Com_Printf( "ClientThink_real - button: was>%d<, now>%d<\n", pm.cmd.buttons, ucmd->buttons ); + pm.cmd = *ucmd; + + if ( pm.ps->pm_type == PM_DEAD ) { + pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; + } + else if ( ent->r.svFlags & SVF_BOT ) { + pm.tracemask = MASK_PLAYERSOLID | CONTENTS_BOTCLIP; + } + else { + pm.tracemask = MASK_PLAYERSOLID; + } + pm.trace = trap_Trace; + pm.pointcontents = trap_PointContents; + pm.debugLevel = g_debugMove.integer; + pm.noFootsteps = ( g_dmflags.integer & DF_NO_FOOTSTEPS ) > 0; + + pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; + pm.pmove_msec = pmove_msec.integer; + + VectorCopy( client->ps.origin, client->oldOrigin ); + +#ifdef MISSIONPACK + if (level.intermissionQueued != 0 && g_singlePlayer.integer) { + if ( level.time - level.intermissionQueued >= 1000 ) { + pm.cmd.buttons = 0; + pm.cmd.forwardmove = 0; + pm.cmd.rightmove = 0; + pm.cmd.upmove = 0; + if ( level.time - level.intermissionQueued >= 2000 && level.time - level.intermissionQueued <= 2500 ) { + trap_SendConsoleCommand( EXEC_APPEND, "centerview\n"); + } + ent->client->ps.pm_type = PM_SPINTERMISSION; + } + } + Pmove (&pm); +#else + //PKMOD - Ergodic 04/05/01 - debug (inactive) +// Com_Printf( "ClientThink_real - calling pmove\n" ); + Pmove (&pm); +#endif + + // save results of pmove + if ( ent->client->ps.eventSequence != oldEventSequence ) { + ent->eventTime = level.time; + } + if (g_smoothClients.integer) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + SendPendingPredictableEvents( &ent->client->ps ); + + if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { + client->fireHeld = qfalse; // for grapple + //PKMOD - Ergodic 03/09/01 - if not firing then turn off the clg shafting flag + //PKMOD - Ergodic 07/08/01 - should be the client flag and not the entity flag +// ent->s.eFlags &= ~EF_LIGHTNINGSTRIKE; + client->ps.eFlags &= ~EF_LIGHTNINGSTRIKE; + } + + //PKMOD - Ergodic 03/29/2000, reset set the AirFist_Level + //PKMOD - Ergodic 11/15/00, move airfist level to playerstate + // airfist levels will be (4,3,2,1,0) + //PKMOD - Ergodic 05/18/01 - Airfist has displayable ammo corresponding to airfist_level + if ( ent->client->ps.stats[STAT_AIRFIST_LEVEL] < 4 ) { + if (ent->AirFist_ResetTime < level.time) { + ent->client->ps.stats[STAT_AIRFIST_LEVEL]++; + ent->AirFist_ResetTime = level.time + AIRFIST_RESET_WAIT; + //PKMOD - Ergodic 05/18/01 - Airfist has displayable ammo corresponding to airfist_level + if ( ent->client->ps.ammo[WP_AIRFIST] < 4 ) { + ent->client->ps.ammo[WP_AIRFIST]++; + } + } + } + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + VectorCopy (pm.mins, ent->r.mins); + VectorCopy (pm.maxs, ent->r.maxs); + + ent->waterlevel = pm.waterlevel; + ent->watertype = pm.watertype; + + // execute client events + ClientEvents( ent, oldEventSequence ); + + // link entity now, after any personal teleporters have been used + trap_LinkEntity (ent); + if ( !ent->client->noclip ) { + G_TouchTriggers( ent ); + } + + // NOTE: now copy the exact origin over otherwise clients can be snapped into solid + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + + //test for solid areas in the AAS file + BotTestAAS(ent->r.currentOrigin); + + // touch other objects + ClientImpacts( ent, &pm ); + + // save results of triggers and client events + if (ent->client->ps.eventSequence != oldEventSequence) { + ent->eventTime = level.time; + } + + // swap and latch button actions + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + client->latched_buttons |= client->buttons & ~client->oldbuttons; + + // check for respawning + if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + // wait for the attack button to be pressed + if ( level.time > client->respawnTime ) { + // forcerespawn is to prevent users from waiting out powerups + if ( g_forcerespawn.integer > 0 && + ( level.time - client->respawnTime ) > g_forcerespawn.integer * 1000 ) { + respawn( ent ); + return; + } + + // pressing attack or use is the normal respawn method + if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { + respawn( ent ); + } + } + return; + } + + // perform once-a-second actions + ClientTimerActions( ent, msec ); +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum ) { + gentity_t *ent; + + ent = g_entities + clientNum; + trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); + + // mark the time we got info, so we can display the + // phone jack if they don't get any for a while + ent->client->lastCmdTime = level.time; + + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { + ClientThink_real( ent ); + } +} + + +void G_RunClient( gentity_t *ent ) { + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { + return; + } + ent->client->pers.cmd.serverTime = level.time; + ClientThink_real( ent ); +} + + +/* +================== +SpectatorClientEndFrame + +================== +*/ +void SpectatorClientEndFrame( gentity_t *ent ) { + gclient_t *cl; + + // if we are doing a chase cam or a remote view, grab the latest info + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + int clientNum, flags; + + clientNum = ent->client->sess.spectatorClient; + + // team follow1 and team follow2 go to whatever clients are playing + if ( clientNum == -1 ) { + clientNum = level.follow1; + } else if ( clientNum == -2 ) { + clientNum = level.follow2; + } + if ( clientNum >= 0 ) { + cl = &level.clients[ clientNum ]; + if ( cl->pers.connected == CON_CONNECTED && cl->sess.sessionTeam != TEAM_SPECTATOR ) { + flags = (cl->ps.eFlags & ~(EF_VOTED | EF_TEAMVOTED)) | (ent->client->ps.eFlags & (EF_VOTED | EF_TEAMVOTED)); + ent->client->ps = cl->ps; + ent->client->ps.pm_flags |= PMF_FOLLOW; + ent->client->ps.eFlags = flags; + return; + } else { + // drop them to free spectators unless they are dedicated camera followers + if ( ent->client->sess.spectatorClient >= 0 ) { + ent->client->sess.spectatorState = SPECTATOR_FREE; + ClientBegin( ent->client - level.clients ); + } + } + } + } + + if ( ent->client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + ent->client->ps.pm_flags |= PMF_SCOREBOARD; + } else { + ent->client->ps.pm_flags &= ~PMF_SCOREBOARD; + } +} + +/* +============== +ClientEndFrame + +Called at the end of each server frame for each connected client +A fast client will have multiple ClientThink for each ClientEdFrame, +while a slow client may have multiple ClientEndFrame between ClientThink. +============== +*/ +void ClientEndFrame( gentity_t *ent ) { + int i; + clientPersistant_t *pers; + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + SpectatorClientEndFrame( ent ); + return; + } + + pers = &ent->client->pers; + + // turn off any expired powerups + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if ( ent->client->ps.powerups[ i ] < level.time ) { + ent->client->ps.powerups[ i ] = 0; + } + } + +#ifdef MISSIONPACK + // set powerup for player animation + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_GUARD ) { + ent->client->ps.powerups[PW_GUARD] = level.time; + } + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_SCOUT ) { + ent->client->ps.powerups[PW_SCOUT] = level.time; + } + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_DOUBLER ) { + ent->client->ps.powerups[PW_DOUBLER] = level.time; + } + if( bg_itemlist[ent->client->ps.stats[STAT_PERSISTANT_POWERUP]].giTag == PW_AMMOREGEN ) { + ent->client->ps.powerups[PW_AMMOREGEN] = level.time; + } + if ( ent->client->invulnerabilityTime > level.time ) { + ent->client->ps.powerups[PW_INVULNERABILITY] = level.time; + } +#endif + + // save network bandwidth +#if 0 + if ( !g_synchronousClients->integer && ent->client->ps.pm_type == PM_NORMAL ) { + // FIXME: this must change eventually for non-sync demo recording + VectorClear( ent->client->ps.viewangles ); + } +#endif + + // + // If the end of unit layout is displayed, don't give + // the player any normal movement attributes + // + if ( level.intermissiontime ) { + return; + } + + // burn from lava, etc + P_WorldEffects (ent); + + // apply all the damage taken this frame + P_DamageFeedback (ent); + + // add the EF_CONNECTION flag if we haven't gotten commands recently + if ( level.time - ent->client->lastCmdTime > 1000 ) { + ent->s.eFlags |= EF_CONNECTION; + } else { + ent->s.eFlags &= ~EF_CONNECTION; + } + + ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... + + G_SetClientSound (ent); + + // set the latest infor + if (g_smoothClients.integer) { + BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + SendPendingPredictableEvents( &ent->client->ps ); + + // set the bit for the reachability area the client is currently in +// i = trap_AAS_PointReachabilityAreaIndex( ent->client->ps.origin ); +// ent->client->areabits[i >> 3] |= 1 << (i & 7); +} + + diff --git a/quake3/source/code/game/g_arenas.c b/quake3/source/code/game/g_arenas.c new file mode 100644 index 0000000..d3c412d --- /dev/null +++ b/quake3/source/code/game/g_arenas.c @@ -0,0 +1,380 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// +// g_arenas.c +// + +#include "g_local.h" + + +gentity_t *podium1; +gentity_t *podium2; +gentity_t *podium3; + + +/* +================== +UpdateTournamentInfo +================== +*/ +void UpdateTournamentInfo( void ) { + int i; + gentity_t *player; + int playerClientNum; + int n, accuracy, perfect, msglen; + int buflen; +#ifdef MISSIONPACK // bk001205 + int score1, score2; + qboolean won; +#endif + char buf[32]; + char msg[MAX_STRING_CHARS]; + //PKMOD - Ergodic 02/02/04 - hold Winner Flag + int winner; + + // find the real player + player = NULL; + for (i = 0; i < level.maxclients; i++ ) { + player = &g_entities[i]; + if ( !player->inuse ) { + continue; + } + if ( !( player->r.svFlags & SVF_BOT ) ) { + break; + } + } + // this should never happen! + if ( !player || i == level.maxclients ) { + return; + } + playerClientNum = i; + + CalculateRanks(); + + if ( level.clients[playerClientNum].sess.sessionTeam == TEAM_SPECTATOR ) { +#ifdef MISSIONPACK + Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); +#else + Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); +#endif + } + else { + if( player->client->accuracy_shots ) { + accuracy = player->client->accuracy_hits * 100 / player->client->accuracy_shots; + } + else { + accuracy = 0; + } +#ifdef MISSIONPACK + won = qfalse; + if (g_gametype.integer >= GT_CTF) { + score1 = level.teamScores[TEAM_RED]; + score2 = level.teamScores[TEAM_BLUE]; + if (level.clients[playerClientNum].sess.sessionTeam == TEAM_RED) { + won = (level.teamScores[TEAM_RED] > level.teamScores[TEAM_BLUE]); + } else { + won = (level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED]); + } + } else { + if (&level.clients[playerClientNum] == &level.clients[ level.sortedClients[0] ]) { + won = qtrue; + score1 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + score2 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; + } else { + score2 = level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE]; + score1 = level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE]; + } + } + if (won && player->client->ps.persistant[PERS_KILLED] == 0) { + perfect = 1; + } else { + perfect = 0; + } + + if (&level.clients[playerClientNum] == &level.clients[ level.sortedClients[0] ]) { + + Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, + player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT],player->client->ps.persistant[PERS_DEFEND_COUNT], + player->client->ps.persistant[PERS_ASSIST_COUNT], player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], player->client->ps.persistant[PERS_SCORE], + perfect, score1, score2, level.time, player->client->ps.persistant[PERS_CAPTURES] ); + +#else + //PKMOD - Ergodic 02/02/04 - calculate winner + if (&level.clients[playerClientNum] == &level.clients[ level.sortedClients[0] ]) { + winner = 1; + } else { + winner = 0; + } + + perfect = ( level.clients[playerClientNum].ps.persistant[PERS_RANK] == 0 && player->client->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; + //PKMOD - Ergodic 08/08/00 PAINKILLER awarded after every 10 PKitem kills + //PKMOD - Ergodic 09/04/00 move painkiller to end of parameters + //PKMOD - Ergodic 12/17/00 - place PERS_HUB_FLAG as first bit of PERS_PAINKILLER_COUNT + // remove the lowest bit of PERS_PAINKILLER_COUNT + //PKMOD - Ergodic 02/02/04 - modify SP postgame argument list to sync with ui_atoms.c + Com_sprintf( msg, sizeof(msg), "postgame %i %i %i %i %i %i %i %i %i %i %i", level.numNonSpectatorClients, playerClientNum, accuracy, + player->client->ps.persistant[PERS_IMPRESSIVE_COUNT], player->client->ps.persistant[PERS_EXCELLENT_COUNT], + player->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], perfect, + (player->client->ps.persistant[PERS_PAINKILLER_COUNT] >> 1) / 10, player->client->ps.persistant[PERS_SCORE], + level.time, winner ); +#endif + } + + msglen = strlen( msg ); + for( i = 0; i < level.numNonSpectatorClients; i++ ) { + n = level.sortedClients[i]; + Com_sprintf( buf, sizeof(buf), " %i %i %i", n, level.clients[n].ps.persistant[PERS_RANK], level.clients[n].ps.persistant[PERS_SCORE] ); + buflen = strlen( buf ); + if( msglen + buflen + 1 >= sizeof(msg) ) { + break; + } + strcat( msg, buf ); + } + trap_SendConsoleCommand( EXEC_APPEND, msg ); +} + + +static gentity_t *SpawnModelOnVictoryPad( gentity_t *pad, vec3_t offset, gentity_t *ent, int place ) { + gentity_t *body; + vec3_t vec; + vec3_t f, r, u; + + body = G_Spawn(); + if ( !body ) { + G_Printf( S_COLOR_RED "ERROR: out of gentities\n" ); + return NULL; + } + + body->classname = ent->client->pers.netname; + body->client = ent->client; + body->s = ent->s; + body->s.eType = ET_PLAYER; // could be ET_INVISIBLE + body->s.eFlags = 0; // clear EF_TALK, etc + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + body->s.event = 0; + body->s.pos.trType = TR_STATIONARY; + body->s.groundEntityNum = ENTITYNUM_WORLD; + body->s.legsAnim = LEGS_IDLE; + body->s.torsoAnim = TORSO_STAND; + if( body->s.weapon == WP_NONE ) { + body->s.weapon = WP_MACHINEGUN; + } + //PKMOD - Ergodic 09/20/00 - add proper stance for dragon carrier + if ( ( body->s.weapon == WP_GAUNTLET) || ( body->s.weapon == WP_GRAPPLING_HOOK ) ){ + body->s.torsoAnim = TORSO_STAND2; + } + body->s.event = 0; + body->r.svFlags = ent->r.svFlags; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_BODY; + body->r.ownerNum = ent->r.ownerNum; + body->takedamage = qfalse; + + VectorSubtract( level.intermission_origin, pad->r.currentOrigin, vec ); + vectoangles( vec, body->s.apos.trBase ); + body->s.apos.trBase[PITCH] = 0; + body->s.apos.trBase[ROLL] = 0; + + AngleVectors( body->s.apos.trBase, f, r, u ); + VectorMA( pad->r.currentOrigin, offset[0], f, vec ); + VectorMA( vec, offset[1], r, vec ); + VectorMA( vec, offset[2], u, vec ); + + G_SetOrigin( body, vec ); + + trap_LinkEntity (body); + + body->count = place; + + return body; +} + + +static void CelebrateStop( gentity_t *player ) { + int anim; + + //PKMOD - Ergodic 09/20/00 - add proper stance for dragon carrier + if ( ( player->s.weapon == WP_GAUNTLET) || ( player->s.weapon == WP_GRAPPLING_HOOK ) ){ + anim = TORSO_STAND2; + } + else { + anim = TORSO_STAND; + } + player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; +} + + +#define TIMER_GESTURE (34*66+50) +static void CelebrateStart( gentity_t *player ) { + player->s.torsoAnim = ( ( player->s.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | TORSO_GESTURE; + player->nextthink = level.time + TIMER_GESTURE; + player->think = CelebrateStop; + + /* + player->client->ps.events[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = EV_TAUNT; + player->client->ps.eventParms[player->client->ps.eventSequence & (MAX_PS_EVENTS-1)] = 0; + player->client->ps.eventSequence++; + */ + G_AddEvent(player, EV_TAUNT, 0); +} + + +static vec3_t offsetFirst = {0, 0, 74}; +static vec3_t offsetSecond = {-10, 60, 54}; +static vec3_t offsetThird = {-19, -60, 45}; + +static void PodiumPlacementThink( gentity_t *podium ) { + vec3_t vec; + vec3_t origin; + vec3_t f, r, u; + + podium->nextthink = level.time + 100; + + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors( level.intermission_angle, vec, NULL, NULL ); + AngleVectorsForward( level.intermission_angle, vec ); + VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); + origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); + G_SetOrigin( podium, origin ); + + if( podium1 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium1->s.apos.trBase ); + podium1->s.apos.trBase[PITCH] = 0; + podium1->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium1->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetFirst[0], f, vec ); + VectorMA( vec, offsetFirst[1], r, vec ); + VectorMA( vec, offsetFirst[2], u, vec ); + + G_SetOrigin( podium1, vec ); + } + + if( podium2 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium2->s.apos.trBase ); + podium2->s.apos.trBase[PITCH] = 0; + podium2->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium2->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetSecond[0], f, vec ); + VectorMA( vec, offsetSecond[1], r, vec ); + VectorMA( vec, offsetSecond[2], u, vec ); + + G_SetOrigin( podium2, vec ); + } + + if( podium3 ) { + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + vectoangles( vec, podium3->s.apos.trBase ); + podium3->s.apos.trBase[PITCH] = 0; + podium3->s.apos.trBase[ROLL] = 0; + + AngleVectors( podium3->s.apos.trBase, f, r, u ); + VectorMA( podium->r.currentOrigin, offsetThird[0], f, vec ); + VectorMA( vec, offsetThird[1], r, vec ); + VectorMA( vec, offsetThird[2], u, vec ); + + G_SetOrigin( podium3, vec ); + } +} + + +static gentity_t *SpawnPodium( void ) { + gentity_t *podium; + vec3_t vec; + vec3_t origin; + + podium = G_Spawn(); + if ( !podium ) { + return NULL; + } + + podium->classname = "podium"; + podium->s.eType = ET_GENERAL; + podium->s.number = podium - g_entities; + podium->clipmask = CONTENTS_SOLID; + podium->r.contents = CONTENTS_SOLID; + podium->s.modelindex = G_ModelIndex( SP_PODIUM_MODEL ); + + //PKMOD - Ergodic 02/14/02 - optimize AngeVectors call when only "FORWARD" is needed +// AngleVectors( level.intermission_angle, vec, NULL, NULL ); + AngleVectorsForward( level.intermission_angle, vec ); + VectorMA( level.intermission_origin, trap_Cvar_VariableIntegerValue( "g_podiumDist" ), vec, origin ); + origin[2] -= trap_Cvar_VariableIntegerValue( "g_podiumDrop" ); + G_SetOrigin( podium, origin ); + + VectorSubtract( level.intermission_origin, podium->r.currentOrigin, vec ); + podium->s.apos.trBase[YAW] = vectoyaw( vec ); + trap_LinkEntity (podium); + + podium->think = PodiumPlacementThink; + podium->nextthink = level.time + 100; + return podium; +} + + +/* +================== +SpawnModelsOnVictoryPads +================== +*/ +void SpawnModelsOnVictoryPads( void ) { + gentity_t *player; + gentity_t *podium; + + podium1 = NULL; + podium2 = NULL; + podium3 = NULL; + + podium = SpawnPodium(); + + player = SpawnModelOnVictoryPad( podium, offsetFirst, &g_entities[level.sortedClients[0]], + level.clients[ level.sortedClients[0] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + player->nextthink = level.time + 2000; + player->think = CelebrateStart; + podium1 = player; + } + + player = SpawnModelOnVictoryPad( podium, offsetSecond, &g_entities[level.sortedClients[1]], + level.clients[ level.sortedClients[1] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + podium2 = player; + } + + if ( level.numNonSpectatorClients > 2 ) { + player = SpawnModelOnVictoryPad( podium, offsetThird, &g_entities[level.sortedClients[2]], + level.clients[ level.sortedClients[2] ].ps.persistant[PERS_RANK] &~ RANK_TIED_FLAG ); + if ( player ) { + podium3 = player; + } + } +} + + +/* +=============== +Svcmd_AbortPodium_f +=============== +*/ +void Svcmd_AbortPodium_f( void ) { + if( g_gametype.integer != GT_SINGLE_PLAYER ) { + return; + } + + if( podium1 ) { + podium1->nextthink = level.time; + podium1->think = CelebrateStop; + } +} diff --git a/quake3/source/code/game/g_bot.c b/quake3/source/code/game/g_bot.c new file mode 100644 index 0000000..98ee615 --- /dev/null +++ b/quake3/source/code/game/g_bot.c @@ -0,0 +1,1329 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// g_bot.c + +#include "g_local.h" + + +static int g_numBots; +static char *g_botInfos[MAX_BOTS]; + + +int g_numArenas; +static char *g_arenaInfos[MAX_ARENAS]; + + +#define BOT_BEGIN_DELAY_BASE 2000 +#define BOT_BEGIN_DELAY_INCREMENT 1500 + +#define BOT_SPAWN_QUEUE_DEPTH 16 + +typedef struct { + int clientNum; + int spawnTime; +} botSpawnQueue_t; + +//static int botBeginDelay = 0; // bk001206 - unused, init +static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; + +vmCvar_t bot_minplayers; + +extern gentity_t *podium1; +extern gentity_t *podium2; +extern gentity_t *podium3; + +float trap_Cvar_VariableValue( const char *var_name ) { + char buf[128]; + + trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); + return atof(buf); +} + + + +/* +=============== +G_ParseInfos +=============== +*/ +int G_ParseInfos( char *buf, int max, char *infos[] ) { + char *token; + int count; + char key[MAX_TOKEN_CHARS]; + char info[MAX_INFO_STRING]; + + count = 0; + + while ( 1 ) { + token = COM_Parse( &buf ); + if ( !token[0] ) { + break; + } + if ( strcmp( token, "{" ) ) { + Com_Printf( "Missing { in info file\n" ); + break; + } + + if ( count == max ) { + Com_Printf( "Max infos exceeded\n" ); + break; + } + + info[0] = '\0'; + while ( 1 ) { + token = COM_ParseExt( &buf, qtrue ); + if ( !token[0] ) { + Com_Printf( "Unexpected end of info file\n" ); + break; + } + if ( !strcmp( token, "}" ) ) { + break; + } + Q_strncpyz( key, token, sizeof( key ) ); + + token = COM_ParseExt( &buf, qfalse ); + if ( !token[0] ) { + strcpy( token, "" ); + } + Info_SetValueForKey( info, key, token ); + } + //NOTE: extra space for arena number + infos[count] = G_Alloc(strlen(info) + strlen("\\num\\") + strlen(va("%d", MAX_ARENAS)) + 1); + if (infos[count]) { + strcpy(infos[count], info); + count++; + } + } + return count; +} + +/* +=============== +G_LoadArenasFromFile +=============== +*/ +static void G_LoadArenasFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_ARENAS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_ARENAS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_ARENAS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numArenas += G_ParseInfos( buf, MAX_ARENAS - g_numArenas, &g_arenaInfos[g_numArenas] ); +} + +/* +=============== +G_LoadArenas +=============== +*/ +static void G_LoadArenas( void ) { + int numdirs; + vmCvar_t arenasFile; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + + g_numArenas = 0; + + trap_Cvar_Register( &arenasFile, "g_arenasFile", "", CVAR_INIT|CVAR_ROM ); + if( *arenasFile.string ) { + G_LoadArenasFromFile(arenasFile.string); + } + else { + G_LoadArenasFromFile("scripts/arenas.txt"); + } + + // get all arenas from .arena files + numdirs = trap_FS_GetFileList("scripts", ".arena", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadArenasFromFile(filename); + } + trap_Printf( va( "%i arenas parsed\n", g_numArenas ) ); + + for( n = 0; n < g_numArenas; n++ ) { + Info_SetValueForKey( g_arenaInfos[n], "num", va( "%i", n ) ); + } +} + + +/* +=============== +G_GetArenaInfoByNumber +=============== +*/ +const char *G_GetArenaInfoByMap( const char *map ) { + int n; + + for( n = 0; n < g_numArenas; n++ ) { + if( Q_stricmp( Info_ValueForKey( g_arenaInfos[n], "map" ), map ) == 0 ) { + return g_arenaInfos[n]; + } + } + + return NULL; +} + + +/* +================= +PlayerIntroSound +================= +*/ +static void PlayerIntroSound( const char *modelAndSkin ) { + char model[MAX_QPATH]; + char *skin; + + Q_strncpyz( model, modelAndSkin, sizeof(model) ); + skin = Q_strrchr( model, '/' ); + if ( skin ) { + *skin++ = '\0'; + } + else { + skin = model; + } + + if( Q_stricmp( skin, "default" ) == 0 ) { + skin = model; + } + + trap_SendConsoleCommand( EXEC_APPEND, va( "play sound/player/announce/%s.wav\n", skin ) ); +} + +/* +=============== +G_AddRandomBot +=============== +*/ +void G_AddRandomBot( int team ) { + int i, n, num; + float skill; + char *value, netname[36], *teamstr; + gclient_t *cl; + + num = 0; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= g_maxclients.integer) { + num++; + } + } + num = random() * num; + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + // + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + if ( !Q_stricmp( value, cl->pers.netname ) ) { + break; + } + } + if (i >= g_maxclients.integer) { + num--; + if (num <= 0) { + skill = trap_Cvar_VariableValue( "g_spSkill" ); + if (team == TEAM_RED) teamstr = "red"; + else if (team == TEAM_BLUE) teamstr = "blue"; + else teamstr = ""; + strncpy(netname, value, sizeof(netname)-1); + netname[sizeof(netname)-1] = '\0'; + Q_CleanStr(netname); + + //PKMOD - Ergodic 01/14/02 - debug bot's restart (inactive) +// Com_Printf("G_AddRandomBot - >%s<\n", netname ); + + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f %s %i\n", netname, skill, teamstr, 0) ); + return; + } + } + } +} + +/* +=============== +G_RemoveRandomBot +=============== +*/ +int G_RemoveRandomBot( int team ) { + int i; + char netname[36]; + gclient_t *cl; + + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + + //PKMOD - Ergodic 12/16/02 - Never remove a Private Bot + // 02/29/04 - NOTE: this works for SP games but not Client/Server games + if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_PRIVATEBOT ) { + continue; + } + + strcpy(netname, cl->pers.netname); + Q_CleanStr(netname); + + //PKMOD - Ergodic 02/29/04 - debug PB removal on client/server games (inactive) + //Com_Printf( "G_RemoveRandomBot - removing i>%d<, i_flags>%d<, cl->ps.clientNum>%d<, clientNum_flags>%d<, netname>%s<\n", i, g_entities[i].r.svFlags, cl->ps.clientNum, g_entities[cl->ps.clientNum].r.svFlags, netname ); + //G_LogPrintf( "G_RemoveRandomBot - removing i>%d<, i_flags>%d<, cl->ps.clientNum>%d<, clientNum_flags>%d<, netname>%s<\n", i, g_entities[i].r.svFlags, cl->ps.clientNum, g_entities[cl->ps.clientNum].r.svFlags, netname ); + + trap_SendConsoleCommand( EXEC_INSERT, va("kick %s\n", netname) ); + return qtrue; + } + return qfalse; +} + +/* +=============== +G_CountHumanPlayers +=============== +*/ +int G_CountHumanPlayers( int team ) { + int i, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + num++; + } + return num; +} + +/* +=============== +G_CountBotPlayers +=============== +*/ +int G_CountBotPlayers( int team ) { + int i, n, num; + gclient_t *cl; + + num = 0; + for ( i=0 ; i< g_maxclients.integer ; i++ ) { + cl = level.clients + i; + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + if ( !(g_entities[cl->ps.clientNum].r.svFlags & SVF_BOT) ) { + continue; + } + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + //PKMOD - Ergodic 01/21/02 - don't count Private Bots (inactive) + //PKMOD - Ergodic 07/21/02 - don't count Private Bots, now active + if ( g_entities[cl->ps.clientNum].r.svFlags & SVF_PRIVATEBOT ) { + continue; + } + num++; + } + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + num++; + } + + return num; +} + +/* +=============== +G_CheckMinimumPlayers +=============== +*/ +void G_CheckMinimumPlayers( void ) { + int minplayers; + int humanplayers, botplayers; + static int checkminimumplayers_time; + + if (level.intermissiontime) return; + //only check once each 10 seconds + if (checkminimumplayers_time > level.time - 10000) { + return; + } + checkminimumplayers_time = level.time; + trap_Cvar_Update(&bot_minplayers); + minplayers = bot_minplayers.integer; + if (minplayers <= 0) return; + + if (g_gametype.integer >= GT_TEAM) { + if (minplayers >= g_maxclients.integer / 2) { + minplayers = (g_maxclients.integer / 2) -1; + } + + humanplayers = G_CountHumanPlayers( TEAM_RED ); + botplayers = G_CountBotPlayers( TEAM_RED ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_RED ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_RED ); + } + // + humanplayers = G_CountHumanPlayers( TEAM_BLUE ); + botplayers = G_CountBotPlayers( TEAM_BLUE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_BLUE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_BLUE ); + } + } + else if (g_gametype.integer == GT_TOURNAMENT ) { + if (minplayers >= g_maxclients.integer) { + minplayers = g_maxclients.integer-1; + } + humanplayers = G_CountHumanPlayers( -1 ); + botplayers = G_CountBotPlayers( -1 ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + // try to remove spectators first + if (!G_RemoveRandomBot( TEAM_SPECTATOR )) { + // just remove the bot that is playing + G_RemoveRandomBot( -1 ); + } + } + } + else if (g_gametype.integer == GT_FFA) { + if (minplayers >= g_maxclients.integer) { + minplayers = g_maxclients.integer-1; + } + humanplayers = G_CountHumanPlayers( TEAM_FREE ); + botplayers = G_CountBotPlayers( TEAM_FREE ); + // + if (humanplayers + botplayers < minplayers) { + G_AddRandomBot( TEAM_FREE ); + } else if (humanplayers + botplayers > minplayers && botplayers) { + G_RemoveRandomBot( TEAM_FREE ); + } + } +} + +/* +=============== +G_CheckBotSpawn +=============== +*/ +void G_CheckBotSpawn( void ) { + int n; + char userinfo[MAX_INFO_VALUE]; + + G_CheckMinimumPlayers(); + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("G_CheckBotSpawn - before client begin\n" ); + + ClientBegin( botSpawnQueue[n].clientNum ); + + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("G_CheckBotSpawn - after client begin\n" ); + + botSpawnQueue[n].spawnTime = 0; + + if( g_gametype.integer == GT_SINGLE_PLAYER ) { + trap_GetUserinfo( botSpawnQueue[n].clientNum, userinfo, sizeof(userinfo) ); + PlayerIntroSound( Info_ValueForKey (userinfo, "model") ); + } + } +} + + +/* +=============== +AddBotToSpawnQueue +=============== +*/ +static void AddBotToSpawnQueue( int clientNum, int delay ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + botSpawnQueue[n].spawnTime = level.time + delay; + botSpawnQueue[n].clientNum = clientNum; + return; + } + } + + G_Printf( S_COLOR_YELLOW "Unable to delay spawn\n" ); + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("AddBotToSpawnQueue - before client begin\n" ); + + ClientBegin( clientNum ); + + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("AddBotToSpawnQueue - after client begin\n" ); + +} + + +/* +=============== +G_RemoveQueuedBotBegin + +Called on client disconnect to make sure the delayed spawn +doesn't happen on a freed index +=============== +*/ +void G_RemoveQueuedBotBegin( int clientNum ) { + int n; + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( botSpawnQueue[n].clientNum == clientNum ) { + botSpawnQueue[n].spawnTime = 0; + return; + } + } +} + + +/* +=============== +G_BotConnect +=============== +*/ +qboolean G_BotConnect( int clientNum, qboolean restart ) { + bot_settings_t settings; + char userinfo[MAX_INFO_STRING]; + + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + + Q_strncpyz( settings.characterfile, Info_ValueForKey( userinfo, "characterfile" ), sizeof(settings.characterfile) ); + settings.skill = atof( Info_ValueForKey( userinfo, "skill" ) ); + Q_strncpyz( settings.team, Info_ValueForKey( userinfo, "team" ), sizeof(settings.team) ); + + if (!BotAISetupClient( clientNum, &settings, restart )) { + trap_DropClient( clientNum, "BotAISetupClient failed" ); + return qfalse; + } + + return qtrue; +} + + +/* +=============== +G_AddBot +=============== +*/ +static void G_AddBot( const char *name, float skill, const char *team, int delay, char *altname) { + int clientNum; + char *botinfo; + gentity_t *bot; + char *key; + char *s; + char *botname; + char *model; + char *headmodel; + char userinfo[MAX_INFO_STRING]; + + //PKMOD - Ergodic 02/19/04 - debug bot's userinfo (inactive) + //Com_Printf("G_AddBot - >%s<\n", name ); + + // get the botinfo from bots.txt + botinfo = G_GetBotInfoByName( name ); + if ( !botinfo ) { + G_Printf( S_COLOR_RED "Error: Bot '%s' not defined\n", name ); + return; + } + + // create the bot's userinfo + userinfo[0] = '\0'; + + botname = Info_ValueForKey( botinfo, "funname" ); + if( !botname[0] ) { + botname = Info_ValueForKey( botinfo, "name" ); + } + // check for an alternative name + if (altname && altname[0]) { + botname = altname; + } + Info_SetValueForKey( userinfo, "name", botname ); + Info_SetValueForKey( userinfo, "rate", "25000" ); + Info_SetValueForKey( userinfo, "snaps", "20" ); + Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); + + if ( skill >= 1 && skill < 2 ) { + Info_SetValueForKey( userinfo, "handicap", "50" ); + } + else if ( skill >= 2 && skill < 3 ) { + Info_SetValueForKey( userinfo, "handicap", "70" ); + } + else if ( skill >= 3 && skill < 4 ) { + Info_SetValueForKey( userinfo, "handicap", "90" ); + } + + key = "model"; + model = Info_ValueForKey( botinfo, key ); + if ( !*model ) { + model = "visor/default"; + } + Info_SetValueForKey( userinfo, key, model ); + key = "team_model"; + Info_SetValueForKey( userinfo, key, model ); + + key = "headmodel"; + headmodel = Info_ValueForKey( botinfo, key ); + if ( !*headmodel ) { + headmodel = model; + } + Info_SetValueForKey( userinfo, key, headmodel ); + key = "team_headmodel"; + Info_SetValueForKey( userinfo, key, headmodel ); + + key = "gender"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "male"; + } + Info_SetValueForKey( userinfo, "sex", s ); + + key = "color1"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "4"; + } + Info_SetValueForKey( userinfo, key, s ); + + key = "color2"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "5"; + } + Info_SetValueForKey( userinfo, key, s ); + + s = Info_ValueForKey(botinfo, "aifile"); + if (!*s ) { + trap_Printf( S_COLOR_RED "Error: bot has no aifile specified\n" ); + return; + } + + // have the server allocate a client slot + clientNum = trap_BotAllocateClient(); + if ( clientNum == -1 ) { + G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); + G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); + return; + } + + // initialize the bot settings + if( !team || !*team ) { + if( g_gametype.integer >= GT_TEAM ) { + if( PickTeam(clientNum) == TEAM_RED) { + team = "red"; + } + else { + team = "blue"; + } + } + else { + team = "red"; + } + } + Info_SetValueForKey( userinfo, "characterfile", Info_ValueForKey( botinfo, "aifile" ) ); + Info_SetValueForKey( userinfo, "skill", va( "%5.2f", skill ) ); + Info_SetValueForKey( userinfo, "team", team ); + + bot = &g_entities[ clientNum ]; + bot->r.svFlags |= SVF_BOT; + //PKMOD - Ergodic 03/07/02 - clear Private Bot flag + bot->r.svFlags &= ~SVF_PRIVATEBOT; + + bot->inuse = qtrue; + + //PKMOD - Ergodic 01/06/02 - debug bot's userinfo +// Com_Printf("G_AddBot - clientNum is>%d<\n", clientNum ); +// Com_Printf("G_AddBot - g_PrivateBotSkill.integer>%d<\n", g_PrivateBotSkill.integer ); +// Com_Printf("G_AddBot - g_PrivateBotSkill.value>%1.2f<\n", g_PrivateBotSkill.value ); +// Com_Printf("G_AddBot - aifile>%s<\n", Info_ValueForKey( botinfo, "aifile" )); +// Com_Printf("G_AddBot - userinfo>%s<\n", userinfo); +// Info_SetValueForKey( userinfo, "legsmodel", "hunter/default" ); +// Info_SetValueForKey( userinfo, "headmodel", "visor/painkiller" ); +// Com_Printf("G_AddBot - userinfo>%s<\n", userinfo); + + // register the userinfo + trap_SetUserinfo( clientNum, userinfo ); + + // have it connect to the game as a normal client + if ( ClientConnect( clientNum, qtrue, qtrue ) ) { + return; + } + + if( delay == 0 ) { + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("G_AddBot - before client begin\n" ); + + ClientBegin( clientNum ); + + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("G_AddBot - after client begin\n" ); + + return; + } + + AddBotToSpawnQueue( clientNum, delay ); +} + + +/* +=============== +Svcmd_AddBot_f +=============== +*/ +void Svcmd_AddBot_f( void ) { + float skill; + int delay; + char name[MAX_TOKEN_CHARS]; + char altname[MAX_TOKEN_CHARS]; + char string[MAX_TOKEN_CHARS]; + char team[MAX_TOKEN_CHARS]; + + // are bots enabled? + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + // name + trap_Argv( 1, name, sizeof( name ) ); + if ( !name[0] ) { + trap_Printf( "Usage: Addbot [skill 1-5] [team] [msec delay] [altname]\n" ); + return; + } + + // skill + trap_Argv( 2, string, sizeof( string ) ); + if ( !string[0] ) { + skill = 4; + } + else { + skill = atof( string ); + } + + // team + trap_Argv( 3, team, sizeof( team ) ); + + // delay + trap_Argv( 4, string, sizeof( string ) ); + if ( !string[0] ) { + delay = 0; + } + else { + delay = atoi( string ); + } + + // alternative name + trap_Argv( 5, altname, sizeof( altname ) ); + + G_AddBot( name, skill, team, delay, altname ); + + // if this was issued during gameplay and we are playing locally, + // go ahead and load the bot's media immediately + if ( level.time - level.startTime > 1000 && + trap_Cvar_VariableIntegerValue( "cl_running" ) ) { + trap_SendServerCommand( -1, "loaddefered\n" ); // FIXME: spelled wrong, but not changing for demo + } +} + +/* +====================================================================== + +PKMOD - Ergodic 01/13/02 - Private Bot Driver entity + - verifys the status of client owner + - extinguish private bot at end of duration + +Note: ent->parent = Private Bot + ent->parent->parent = owner of Private Bot + +====================================================================== +*/ +void botdriverThink (gentity_t *ent) { + + //remove driver entity if Private Bot is disconnected + if (!strcmp( ent->parent->classname,"disconnected" ) || ( ent->parent->client->pers.connected == CON_DISCONNECTED )) { + G_FreeEntity( ent ); + return; + } + + //remove Private Bot if owner has disconnected + if (!strcmp( ent->parent->parent->classname,"disconnected" ) || ( ent->parent->parent->client->pers.connected == CON_DISCONNECTED )) { +// trap_SendConsoleCommand( EXEC_APPEND, va("clientkick %i\n", ent->parent->client->ps.clientNum ) ); + + //PKMOD - Ergodic 02/27/04 - Reset the Private Bot flag + // This flag may cause next clients that start in the same area to be unable to join + // in the server + ent->parent->r.svFlags &= ~SVF_PRIVATEBOT; + + //PKMOD - Ergodic 01/19/02 - use trap_DropClient instead of clientkick + trap_DropClient( ent->parent->client->ps.clientNum, "has lost faith in his Leader" ); + //PKMOD - Ergodic 03/18/02 - decrement the Private Bot Counter + active_private_bots--; + G_FreeEntity( ent ); + return; + } + + //remove Private Bot if owner's status has changed + if ( ent->s.modelindex != ent->parent->parent->client->sess.sessionTeam ) { +// trap_SendConsoleCommand( EXEC_APPEND, va("clientkick %i\n", ent->parent->client->ps.clientNum ) ); + + //PKMOD - Ergodic 02/27/04 - Reset the Private Bot flag + // This flag may cause next clients that start in the same area to be unable to join + // in the server + ent->parent->r.svFlags &= ~SVF_PRIVATEBOT; + + //PKMOD - Ergodic 01/19/02 - use trap_DropClient instead of clientkick + trap_DropClient( ent->parent->client->ps.clientNum, "was Betrayed by his Leader" ); + //PKMOD - Ergodic 03/18/02 - decrement the Private Bot Counter + active_private_bots--; + G_FreeEntity( ent ); + return; + } + + if ( ent->wait > level.time ) { + ent->eventTime = level.time; + ent->nextthink = level.time + 300; + trap_LinkEntity( ent ); + } + else { +// trap_SendConsoleCommand( EXEC_APPEND, va("clientkick %i\n", ent->parent->client->ps.clientNum ) ); + + //PKMOD - Ergodic 02/27/04 - Reset the Private Bot flag + // This flag may cause next clients that start in the same area to be unable to join + // in the server + ent->parent->r.svFlags &= ~SVF_PRIVATEBOT; + + //PKMOD - Ergodic 01/19/02 - use trap_DropClient instead of clientkick + trap_DropClient( ent->parent->client->ps.clientNum, "has completed his mission!" ); + //PKMOD - Ergodic 03/18/02 - decrement the Private Bot Counter + active_private_bots--; + G_FreeEntity( ent ); + } +} + +#define PRIVATE_BOT_DURATION 40000 //40 seconds + +/* +=============== +G_AddPrivateBot + +PKMOD - Ergodic 01/06/02 - add "Private Bot" to the game +=============== +*/ +void G_AddPrivateBot( gentity_t *owner ) { + int clientNum; +// char *botinfo; + gentity_t *bot; + char userinfo[MAX_INFO_STRING]; + float skill; + char *team; + char model[MAX_INFO_STRING]; + char headmodel[MAX_INFO_STRING]; + char sex[MAX_INFO_STRING]; + char ownerinfo[MAX_INFO_STRING]; + //PKMOD - Ergodic 01/13/02 - add driver entity for "Private Bot" + gentity_t *bot_driver; + //PKMOD - Ergodic 04/02/02 - owner's cells will add to Private Bot's duration + int cells; + + // create the bot's userinfo + userinfo[0] = '\0'; + + Info_SetValueForKey( userinfo, "name", "PrivateBot" ); + Info_SetValueForKey( userinfo, "rate", "25000" ); + Info_SetValueForKey( userinfo, "snaps", "20" ); + + skill = g_PrivateBotSkill.value; + + //PKMOD - Ergodic 01/10/02 - debug bot's skill (inactive) +// Com_Printf("G_AddPrivateBot - skill>%5.2f<\n", skill ); + + Info_SetValueForKey( userinfo, "skill", va("%1.2f", skill) ); + + if ( skill >= 1 && skill < 2 ) { + Info_SetValueForKey( userinfo, "handicap", "50" ); + } + else if ( skill >= 2 && skill < 3 ) { + Info_SetValueForKey( userinfo, "handicap", "70" ); + } + else if ( skill >= 3 && skill < 4 ) { + Info_SetValueForKey( userinfo, "handicap", "90" ); + } + + //PKMOD - Ergodic 03/06/02 - get owner info + trap_GetConfigstring(CS_PLAYERS+owner->client->ps.clientNum, ownerinfo, sizeof(ownerinfo)); + +// Info_SetValueForKey( userinfo, "model", "visor/painkeep" ); + //PKMOD - Ergodic 03/06/02 - set Private Bot's model and skin to mimic owners + //model + strcpy(model, Info_ValueForKey( ownerinfo, "model" )); + if ( !*model ) { + strcpy(model, "visor/default"); + } + Info_SetValueForKey( userinfo, "model", model ); + +// Info_SetValueForKey( userinfo, "headmodel", "visor/painkeep" ); + //PKMOD - Ergodic 03/07/02 - set Private Bot's model and skin to mimic owners + //headmodel + strcpy(headmodel, Info_ValueForKey( ownerinfo, "headmodel" )); + if ( !*headmodel ) { //if headmodel is not set then default to model + strcpy(headmodel, model); + } + Info_SetValueForKey( userinfo, "headmodel", headmodel ); + + //PKMOD - Ergodic 03/07/02 - set Private Bot's model and skin to mimic owners + //headmodel +// Info_SetValueForKey( userinfo, "sex", "male" ); + strcpy(sex, Info_ValueForKey( ownerinfo, "sex" )); + if ( !*sex ) { //if sex is not set then default to male (Biased?... hmmmm) + strcpy(sex, "male"); + } + Info_SetValueForKey( userinfo, "sex", sex ); + + Info_SetValueForKey( userinfo, "color1", "3" ); + Info_SetValueForKey( userinfo, "color2", "3" ); + + //PKMOD - Ergodic 01/09/02 - add info to structure so Private Bot will not appear in scoreboard + Info_SetValueForKey( userinfo, "privateBot", "1" ); + + // have the server allocate a client slot + clientNum = trap_BotAllocateClient(); + if ( clientNum == -1 ) { + G_Printf( S_COLOR_RED "Unable to add bot. All player slots are in use.\n" ); + G_Printf( S_COLOR_RED "Start server with more 'open' slots (or check setting of sv_maxclients cvar).\n" ); + return; + } + + // initialize the bot team settings + if( g_gametype.integer >= GT_TEAM ) { + // same team, if the flag at base, check to he has the enemy flag + if (owner->client->sess.sessionTeam == TEAM_RED) + team = "red"; + else + team = "blue"; + } + + Info_SetValueForKey( userinfo, "characterfile", "bots/privatebot_c.c" ); + Info_SetValueForKey( userinfo, "team", team ); + + bot = &g_entities[ clientNum ]; + bot->r.svFlags |= SVF_BOT; + bot->r.svFlags |= SVF_PRIVATEBOT; + bot->inuse = qtrue; + + //set the owner + //PKMOD - Ergodic 01/10/02 - if owner is a Private Bot then set owner to the originating player + if ( owner->r.svFlags & SVF_PRIVATEBOT ) + bot->parent = owner->parent; + else + bot->parent = owner; + + //PKMOD - Ergodic 01/09/02 - debug bot's userinfo (inactive) +// Com_Printf("G_AddPrivateBot - userinfo>%s<\n", userinfo); + + // register the userinfo + trap_SetUserinfo( clientNum, userinfo ); + + //PKMOD - Ergodic 04/02/02 - calculate cells + cells = owner->client->ps.ammo[ WP_LIGHTNING ] / 10; + //update lightning ammo + owner->client->ps.ammo[ WP_LIGHTNING ] -= cells * 10; + + //PKMOD - Ergodic 01/13/02 - establish driver entity parameters for "Private Bot" + bot_driver = G_Spawn(); + bot_driver->classname = "PrivateBotDriver"; + bot_driver->s.eType = ET_GENERAL; + bot_driver->r.ownerNum = bot->s.number; + bot_driver->parent = bot; + bot_driver->s.modelindex = bot->parent->client->sess.sessionTeam; + bot_driver->think = botdriverThink; + //PKMOD - Ergodic 04/03/02 - add "cell" time to Private Bot duration + bot_driver->wait = level.time + PRIVATE_BOT_DURATION + 4000 * random() + 1000 * cells; + bot_driver->nextthink = level.time + 300; + bot_driver->eventTime = level.time; + + //PKMOD - Ergodic 03/15/04 - debug bot's restart (inactive) +// Com_Printf("G_AddPrivateBot - before client connect\n" ); +// G_LogPrintf( "G_AddPrivateBot - before client connect\n" ); + + // have it connect to the game as a normal client + if ( ClientConnect( clientNum, qtrue, qtrue ) ) { + //PKMOD - Ergodic 03/15/04 - debug bot's restart (inactive) +// Com_Printf("G_AddPrivateBot - premature leaving of procedure\n" ); +// G_LogPrintf("G_AddPrivateBot - premature leaving of procedure\n" ); + return; + } + + //PKMOD - Ergodic 03/15/04 - debug bot's restart (inactive) +// Com_Printf("G_AddPrivateBot - before client begin\n" ); +// G_LogPrintf("G_AddPrivateBot - before client begin\n" ); + + ClientBegin( clientNum ); + + //PKMOD - Ergodic 01/19/02 - set bot's playerstate for use on cgame side + //PKMOD - Ergodic 02/05/02 - change STAT_PKA_BITS settings from enum type to definition + bot->client->ps.stats[STAT_PKA_BITS] = PKA_BITS_PRIVATEBOT; + + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("G_AddPrivateBot - after client begin\n" ); + +} + + +/* +=============== +Svcmd_BotList_f +=============== +*/ +void Svcmd_BotList_f( void ) { + int i; + char name[MAX_TOKEN_CHARS]; + char funname[MAX_TOKEN_CHARS]; + char model[MAX_TOKEN_CHARS]; + char aifile[MAX_TOKEN_CHARS]; + + trap_Printf("^1name model aifile funname\n"); + for (i = 0; i < g_numBots; i++) { + strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); + if ( !*name ) { + strcpy(name, "UnnamedPlayer"); + } + strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); + if ( !*funname ) { + strcpy(funname, ""); + } + strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); + if ( !*model ) { + strcpy(model, "visor/default"); + } + strcpy(aifile, Info_ValueForKey( g_botInfos[i], "aifile")); + if (!*aifile ) { + strcpy(aifile, "bots/default_c.c"); + } + trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, aifile, funname)); + } +} + + +/* +=============== +G_SpawnBots +=============== +*/ +static void G_SpawnBots( char *botList, int baseDelay ) { + char *bot; + char *p; + float skill; + int delay; + char bots[MAX_INFO_VALUE]; + + podium1 = NULL; + podium2 = NULL; + podium3 = NULL; + + skill = trap_Cvar_VariableValue( "g_spSkill" ); + if( skill < 1 ) { + trap_Cvar_Set( "g_spSkill", "1" ); + skill = 1; + } + else if ( skill > 5 ) { + trap_Cvar_Set( "g_spSkill", "5" ); + skill = 5; + } + + Q_strncpyz( bots, botList, sizeof(bots) ); + p = &bots[0]; + delay = baseDelay; + while( *p ) { + //skip spaces + while( *p && *p == ' ' ) { + p++; + } + if( !p ) { + break; + } + + // mark start of bot name + bot = p; + + // skip until space of null + while( *p && *p != ' ' ) { + p++; + } + if( *p ) { + *p++ = 0; + } + + // we must add the bot this way, calling G_AddBot directly at this stage + // does "Bad Things" + + //PKMOD - Ergodic 01/14/02 - debug bot's restart (inactive) +// Com_Printf("G_SpawnBots - >%s<\n", bot ); + + trap_SendConsoleCommand( EXEC_INSERT, va("addbot %s %f free %i\n", bot, skill, delay) ); + + delay += BOT_BEGIN_DELAY_INCREMENT; + } +} + + +/* +=============== +G_LoadBotsFromFile +=============== +*/ +static void G_LoadBotsFromFile( char *filename ) { + int len; + fileHandle_t f; + char buf[MAX_BOTS_TEXT]; + + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( !f ) { + trap_Printf( va( S_COLOR_RED "file not found: %s\n", filename ) ); + return; + } + if ( len >= MAX_BOTS_TEXT ) { + trap_Printf( va( S_COLOR_RED "file too large: %s is %i, max allowed is %i", filename, len, MAX_BOTS_TEXT ) ); + trap_FS_FCloseFile( f ); + return; + } + + trap_FS_Read( buf, len, f ); + buf[len] = 0; + trap_FS_FCloseFile( f ); + + g_numBots += G_ParseInfos( buf, MAX_BOTS - g_numBots, &g_botInfos[g_numBots] ); +} + +/* +=============== +G_LoadBots +=============== +*/ +static void G_LoadBots( void ) { + vmCvar_t botsFile; + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i; + int dirlen; + + if ( !trap_Cvar_VariableIntegerValue( "bot_enable" ) ) { + return; + } + + g_numBots = 0; + + trap_Cvar_Register( &botsFile, "g_botsFile", "", CVAR_INIT|CVAR_ROM ); + if( *botsFile.string ) { + //PKMOD - Ergodic 01/14/02 - debug bot's restart (inactive) +// Com_Printf("G_LoadBots - botsFile>%s<\n", botsFile.string ); + + G_LoadBotsFromFile(botsFile.string); + } + else { + G_LoadBotsFromFile("scripts/bots.txt"); + } + + // get all bots from .bot files + numdirs = trap_FS_GetFileList("scripts", ".bot", dirlist, 1024 ); + dirptr = dirlist; + for (i = 0; i < numdirs; i++, dirptr += dirlen+1) { + dirlen = strlen(dirptr); + strcpy(filename, "scripts/"); + strcat(filename, dirptr); + G_LoadBotsFromFile(filename); + } + trap_Printf( va( "%i bots parsed\n", g_numBots ) ); +} + + + +/* +=============== +G_GetBotInfoByNumber +=============== +*/ +char *G_GetBotInfoByNumber( int num ) { + if( num < 0 || num >= g_numBots ) { + trap_Printf( va( S_COLOR_RED "Invalid bot number: %i\n", num ) ); + return NULL; + } + return g_botInfos[num]; +} + + +/* +=============== +G_GetBotInfoByName +=============== +*/ +char *G_GetBotInfoByName( const char *name ) { + int n; + char *value; + + for ( n = 0; n < g_numBots ; n++ ) { + value = Info_ValueForKey( g_botInfos[n], "name" ); + if ( !Q_stricmp( value, name ) ) { + return g_botInfos[n]; + } + } + + return NULL; +} + +/* +=============== +G_InitBots +=============== +*/ +void G_InitBots( qboolean restart ) { + int fragLimit; + int timeLimit; + const char *arenainfo; + char *strValue; + //PKMOD - Ergodic 11/07/00 - add logic to enable hub limits + char *hub_flagValue; + int basedelay; + char map[MAX_QPATH]; + char serverinfo[MAX_INFO_STRING]; + + G_LoadBots(); + G_LoadArenas(); + + trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); + + if( g_gametype.integer == GT_SINGLE_PLAYER ) { + trap_GetServerinfo( serverinfo, sizeof(serverinfo) ); + Q_strncpyz( map, Info_ValueForKey( serverinfo, "mapname" ), sizeof(map) ); + arenainfo = G_GetArenaInfoByMap( map ); + if ( !arenainfo ) { + return; + } + + //PKMOD - Ergodic 11/07/00 - add logic to enable hub limits + hub_flagValue = Info_ValueForKey( arenainfo, "hub_flag" ); + + if ( ! strcmp( hub_flagValue, "1" ) ) { + strValue = Info_ValueForKey( arenainfo, "hub_fraglimit" ); + } + else { + strValue = Info_ValueForKey( arenainfo, "fraglimit" ); + } + + fragLimit = atoi( strValue ); + if ( fragLimit ) { + trap_Cvar_Set( "fraglimit", strValue ); + } + else { + trap_Cvar_Set( "fraglimit", "0" ); + } + + + //PKMOD - Ergodic 11/07/00 - add logic to enable hub limits + if ( ! strcmp( hub_flagValue, "1" ) ) { + strValue = Info_ValueForKey( arenainfo, "hub_timelimit" ); + } + else { + strValue = Info_ValueForKey( arenainfo, "timelimit" ); + } + + timeLimit = atoi( strValue ); + if ( timeLimit ) { + trap_Cvar_Set( "timelimit", strValue ); + } + else { + trap_Cvar_Set( "timelimit", "0" ); + } + + if ( !fragLimit && !timeLimit ) { + trap_Cvar_Set( "fraglimit", "10" ); + trap_Cvar_Set( "timelimit", "0" ); + } + + basedelay = BOT_BEGIN_DELAY_BASE; + strValue = Info_ValueForKey( arenainfo, "special" ); + if( Q_stricmp( strValue, "training" ) == 0 ) { + basedelay += 10000; + } + + //PKMOD - Ergodic 01/14/02 - debug bot's restart (inactive) +// Com_Printf("G_InitBots - spawning bots>%s<\n", Info_ValueForKey( arenainfo, "bots" ) ); + + if( !restart ) { + G_SpawnBots( Info_ValueForKey( arenainfo, "bots" ), basedelay ); + } + } +} diff --git a/quake3/source/code/game/g_client.c b/quake3/source/code/game/g_client.c new file mode 100644 index 0000000..9f60384 --- /dev/null +++ b/quake3/source/code/game/g_client.c @@ -0,0 +1,2008 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#include "g_local.h" + +// g_client.c -- client functions that don't happen every frame + +static vec3_t playerMins = {-15, -15, -24}; +static vec3_t playerMaxs = {15, 15, 32}; + +//PKMOD - Ergodic 10/14/00 - qsort primative for hub votes +/* +============= +SortHub + +============= +*/ +int QDECL SortHub( const void *a, const void *b ) { + ghubsort_t *ca, *cb; + + ca = (ghubsort_t *)a; + cb = (ghubsort_t *)b; + + // then sort by score + if ( ca->map_votes > cb->map_votes ) { + return -1; + } + if ( ca->map_votes < cb->map_votes ) { + return 1; + } + return 0; +} + + +//PKMOD - Ergodic 10/12/00 - tally and broadcast hub vote poll +void func_hubvote_think( gentity_t *self ) { + ghubsort_t temp_hubInfo[MAX_HUB_INDEX]; + int indx; + int winner; + + gentity_t *ent; + char entry[1024]; + char string[1400]; + int stringlength; + int j; + int cnt; + char *s; + int mins, seconds, tens; + int msec; + + //PKMOD - Ergodic 10/12/00 - copy hub info to temp hub info + //PKMOD - Ergodic 09/16/03 - add 4 to Hub_Index for alternate Hub Maps cvars + for ( indx = 0; indx < ( Hub_Index + 4 ); indx++) { + temp_hubInfo[indx].map_votes = hubInfo[indx].map_votes; + temp_hubInfo[indx].hub_index = indx; + } + + //10/14/00 employ qsort function + //PKMOD - Ergodic 09/16/03 - add 4 to Hub_Index for alternate Hub Maps cvars + qsort( temp_hubInfo, Hub_Index + 4, sizeof(temp_hubInfo[0]), SortHub ); + + //PKMOD - Ergodic 10/12/00 - debug - hub temp dump inactive +// for ( indx = 0; indx < Hub_Index; indx++) { +// Com_Printf("func_hubvote_think - temphub: indx:%d, votes:%d\n", indx, temp_hubInfo[indx].map_votes ); +// } + + //add logic for hubinfo_overlay + string[0] = 0; + stringlength = 0; + cnt = 0; + + //determine first, second, third places in the hub voting + //PKMOD - Ergodic 09/16/03 - add 4 to Hub_Index for alternate Hub Maps cvars + for ( indx = 0; indx < ( Hub_Index + 4 ) && indx < 3; indx++) { + if ( temp_hubInfo[indx].map_votes > 0 ) { + Com_sprintf (entry, sizeof(entry), " %i %s ", temp_hubInfo[indx].map_votes, hubInfo[temp_hubInfo[indx].hub_index].map_name ); + j = strlen(entry); + strcpy (string + stringlength, entry); + stringlength += j; + cnt++; + } + } + + //11/01/00 add HUB timelimit limit message + if ( g_hub_timelimit.integer > 0 ) { + msec = g_hub_timelimit.integer * 60 * 1000 - (level.time - level.startTime); + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + s = va( "%i:%i%i", mins, tens, seconds ); + + Com_sprintf (entry, sizeof(entry), " Time: %s ", s ); + } + else { + Com_sprintf (entry, sizeof(entry), " Time: N/A " ); + } + j = strlen(entry); + strcpy (string + stringlength, entry); + stringlength += j; + cnt++; + + //add logic for hubinfo_overlay 10/13/00 + for (indx = 0; indx < g_maxclients.integer; indx++) { + ent = g_entities + indx; + //PKMOD - Ergodic 12/17/00 - place PERS_HUB_FLAG as first bit of PERS_PAINKILLER_COUNT + if ( ( ent->client->ps.persistant[PERS_PAINKILLER_COUNT] & 1 ) == 1 ) { + //PKMOD - Ergodic 10/12/00 - debug inactive +// Com_Printf("func_hubvote_think:>hubinfo_pka %i %s<\n", cnt, string ); + + trap_SendServerCommand( ent-g_entities, va("hubinfo_pka %2i - %s", cnt, string) ); + } + } + + //10/14/00 - set the nextmap to leading map + //Determine if there are any ties + cnt = 1; + winner = 0; + if ( Hub_Index != 1 ) { + //PKMOD - Ergodic 09/16/03 - add 4 to Hub_Index for alternate Hub Maps cvars + //PKMOD - Ergodic 01/05/04 - upon ties only pick the standard hub maps so ... + // DO NOT add 4 to Hub_Index + for ( indx = 1; indx < Hub_Index; indx++ ) { + if ( temp_hubInfo[0].map_votes != temp_hubInfo[indx].map_votes ) + break; + cnt++; + } + + if ( cnt > 1 ) + winner = rand() % cnt; + } + + //PKMOD - Ergodic 10/12/00 - debug inactive +// Com_Printf("func_hubvote_think - winner:%d\n", winner ); + //PKMOD - Ergodic 12/07/00 - add return to hub functionality + //PKMOD - Ergodic 12/08/00 - fix hub return functionality +// trap_Cvar_Set( "nextmap", va("map %s; set nextmap vstr hub", hubInfo[temp_hubInfo[winner].hub_index].map_name ) ); + //PKMOD - Ergodic 01/04/04 - Change hub map name from "pkahub" to hub_30 + trap_Cvar_Set( "hubmap", va("map %s; set nextmap map hub_30", hubInfo[temp_hubInfo[winner].hub_index].map_name ) ); + trap_Cvar_Set( "nextmap", va("vstr hubmap" ) ); + + // set time before next firing + self->nextthink = level.time + 3000; //every 3 seconds +} + +//PKMOD - Ergodic 10/12/00 - spawn tally and broadcast entity +void spawn_hubvote ( void ) { + gentity_t *hubvote; + + hubvote = G_Spawn(); + hubvote->classname = "func_hubvote"; + hubvote->nextthink = level.time + 5000; //first schedule is 5 seconds + hubvote->think = func_hubvote_think; + hubvote->r.svFlags = SVF_NOCLIENT; +//PKMOD - Ergodic 10/14/00 - add limit rules... +// Hub detected so set the fraglimits and timelimits, +// the cvars hub_fraglimit and hub_timelimit will override +// the standard cvars fraglimit and timelimit +// g_fraglimit.integer = g_hub_fraglimit.integer; +// g_timelimit.integer = g_hub_timelimit.integer; + //PKMOD - Ergodic 11/07/00 - use hub flag to determine whether in hub or not + //PKMOD - Ergodic 11/07/00 - set hub flag to one + //PKMOD - Ergodic 12/25/00 - name change "g_hub_flag" to "hub_flag" + trap_Cvar_Set( "hub_flag", "1" ); + +} + + +//PKMOD - Ergodic 10/12/00 - add post vote hub deathmath spawn function +/*QUAKED info_player_postvote (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_postvote( gentity_t *ent ) { + int i; + + G_SpawnInt( "nobots", "0", &i); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } + +} + + +/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for deathmatch games. +The first time a player enters the game, they will be at an 'initial' spot. +Targets will be fired when someone spawns in on them. +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_deathmatch( gentity_t *ent ) { + int i; + + G_SpawnInt( "nobots", "0", &i); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } + + //PKMOD - Ergodic 07/25/01 - store the entity into the Respawn Position structure + Respawn_Positions[ Respawn_Position_Index ] = ent; + Respawn_Position_Index++; + if ( Respawn_Position_Index > MAX_RESPAWN_POSITION_INDEX ) + Respawn_Position_Index = MAX_RESPAWN_POSITION_INDEX; +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +equivelant to info_player_deathmatch +*/ +void SP_info_player_start(gentity_t *ent) { + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) { + +} + + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spot->s.origin, playerMins, mins ); + VectorAdd( spot->s.origin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + if ( hit->client) { + return qtrue; + } + + } + + return qfalse; +} + +//PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from, int client_hub_flag ) { + gentity_t *spot; + vec3_t delta; + float dist, nearestDist; + gentity_t *nearestSpot; + + nearestDist = 999999; + nearestSpot = NULL; + spot = NULL; + + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + if ( client_hub_flag ) { + //if client has voted... + while ((spot = G_Find (spot, FOFS(classname), "info_player_postvote")) != NULL) { + VectorSubtract( spot->s.origin, from, delta ); + dist = VectorLength( delta ); + if ( dist < nearestDist ) { + nearestDist = dist; + nearestSpot = spot; + } + } + } + else { + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + VectorSubtract( spot->s.origin, from, delta ); + dist = VectorLength( delta ); + if ( dist < nearestDist ) { + nearestDist = dist; + nearestSpot = spot; + } + } + } + + return nearestSpot; +} + + +//PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectRandomDeathmatchSpawnPoint( int client_hub_flag ) { + gentity_t *spot; + int count; + int selection; + gentity_t *spots[MAX_SPAWN_POINTS]; + + count = 0; + spot = NULL; + + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + if ( client_hub_flag ) { + //if client has voted... + while ((spot = G_Find (spot, FOFS(classname), "info_player_postvote")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + spots[ count ] = spot; + count++; + } + } else { + //if client default respawn or not voted... + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + spots[ count ] = spot; + count++; + } + } + + if ( !count ) { // no spots that won't telefrag + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + if ( client_hub_flag ) { + return G_Find( NULL, FOFS(classname), "info_player_postvote"); + } + else { + return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + } + } + + selection = rand() % count; + return spots[ selection ]; +} + +/* +=========== +SelectRandomDistantSpawnPoint + +Chooses a player start, deathmatch start, etc + +PKMOD - Ergodic 05/09/01 - add to reduce spawn fragging +============ +*/ +#define DISTANTSPAWNPOINTS 6 +gentity_t *SelectRandomDistantSpawnPoint ( vec3_t origin ) { + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[DISTANTSPAWNPOINTS]; + gentity_t *list_spot[DISTANTSPAWNPOINTS]; + int numSpots, rnd, i, j; + + numSpots = 0; + spot = NULL; + + for (i = 0; i < DISTANTSPAWNPOINTS; i++) + list_dist[DISTANTSPAWNPOINTS] = 0; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + + //store the first location + if ( !numSpots ) { + //insert new entry into slot + list_dist[numSpots] = dist; + list_spot[numSpots] = spot; + numSpots++; + continue; + } + + VectorSubtract( spot->s.origin, origin, delta ); + dist = VectorLength( delta ); + + for (i = 0; i < numSpots; i++) { + if ( dist > list_dist[i] ) { + // found a distant spawn point + + //open a slot, move subordinate list down + //m + for (j = i + 1; j < numSpots; j++) { + list_dist[j] = list_dist[j - 1]; + list_spot[j] = list_spot[j - 1]; + } + + //insert new entry into slot + list_dist[i] = dist; + list_spot[i] = spot; + + numSpots++; + if (numSpots > DISTANTSPAWNPOINTS) + numSpots = DISTANTSPAWNPOINTS; + + break; //exit from "for (i" loop + } + } + } + + if (!numSpots) { + spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + //PKMOD - Ergodic 05/09/01 - debug respawning in same place (inactive) +// Com_Printf("SelectRandomDistantSpawnPoint:!numSpots - location>%s<\n", vtos(spot->s.origin) ); + if (!spot) + G_Error( "Couldn't find a distant spawn point" ); +// VectorCopy (spot->s.origin, origin); +// origin[2] += 9; +// VectorCopy (spot->s.angles, angles); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random() * numSpots; + + //PKMOD - Ergodic 05/09/01 - debug respawning in same place (inactive) +// Com_Printf("SelectRandomDistantSpawnPoint - rnd>%d<, location>%s<\n", rnd, vtos(list_spot[rnd]->s.origin) ); +// Com_Printf("SelectRandomDistantSpawnPoint - rnd>%d<\n", rnd ); + +// VectorCopy (list_spot[rnd]->s.origin, origin); +// origin[2] += 9; +// VectorCopy (list_spot[rnd]->s.angles, angles); + + return list_spot[rnd]; +} + +/* +=========== +SelectClosestSpawnPoint + +Chooses a player start, deathmatch start, etc + +Note: Respawn spot will be selected regardless of telefrag potential + Respawn spot will be selected regardles of spawn point flags (FL_NO_BOTS, FL_NO_HUMANS) + +PKMOD - Ergodic 01/10/02 - add for Prvate Bot spawninng in DM play only +============ +*/ +gentity_t *SelectClosestSpawnPoint ( vec3_t target_origin, vec3_t origin, vec3_t angles ) { + gentity_t *spot; + vec3_t delta; + float dist; + float hold_dist; + gentity_t *hold_spot; + int i; + qboolean firstspot = qtrue; + + for ( i = 0; i < Respawn_Position_Index; i++ ) { + + spot = Respawn_Positions[i]; + + VectorSubtract( Respawn_Positions[i]->s.origin, target_origin, delta ); + dist = VectorLengthSquared( delta ); + //store the first location + if ( firstspot ) { + //insert new entry into slot + hold_dist = dist; + hold_spot = Respawn_Positions[i]; + firstspot = qfalse; + continue; + } + + if ( dist < hold_dist ) { + // found a closer spawn point + hold_dist = dist; + hold_spot = Respawn_Positions[i]; + } + } + + VectorCopy (hold_spot->s.origin, origin); + origin[2] += 9; + VectorCopy (hold_spot->s.angles, angles); + + return hold_spot; +} + +/* +=========== +SelectRandomFurthestSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[64]; + gentity_t *list_spot[64]; + int numSpots, rnd, i, j; + + numSpots = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + VectorSubtract( spot->s.origin, avoidPoint, delta ); + dist = VectorLength( delta ); + for (i = 0; i < numSpots; i++) { + if ( dist > list_dist[i] ) { + if ( numSpots >= 64 ) + numSpots = 64-1; + for (j = numSpots; j > i; j--) { + list_dist[j] = list_dist[j-1]; + list_spot[j] = list_spot[j-1]; + } + list_dist[i] = dist; + list_spot[i] = spot; + numSpots++; + if (numSpots > 64) + numSpots = 64; + break; + } + } + if (i >= numSpots && numSpots < 64) { + list_dist[numSpots] = dist; + list_spot[numSpots] = spot; + numSpots++; + } + } + if (!numSpots) { + spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + if (!spot) + G_Error( "Couldn't find a spawn point" ); + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random() * (numSpots / 2); + + VectorCopy (list_spot[rnd]->s.origin, origin); + origin[2] += 9; + VectorCopy (list_spot[rnd]->s.angles, angles); + + return list_spot[rnd]; +} + +/* +=========== +SelectRandomDistantSpawnPoint2 + +Chooses a player start, deathmatch start, etc + +PKMOD - Ergodic 07/25/01 - add to reduce spawn fragging +============ +*/ +#define DISTANTSPAWNPOINTS2 6 +gentity_t *SelectRandomDistantSpawnPoint2 ( vec3_t origin ) { + gentity_t *spot; + vec3_t delta; + float dist; + float list_dist[DISTANTSPAWNPOINTS2 + 1]; + gentity_t *list_spot[DISTANTSPAWNPOINTS2 + 1]; + int numSpots, rnd, i, j, k; + + numSpots = 0; + spot = NULL; + + for (i = 0; i < DISTANTSPAWNPOINTS2; i++) + list_dist[DISTANTSPAWNPOINTS2] = 0; + + for ( i = 0; i < Respawn_Position_Index; i++ ) { + + spot = Respawn_Positions[i]; + + if ( SpotWouldTelefrag( Respawn_Positions[i] ) ) { + continue; + } + + VectorSubtract( Respawn_Positions[i]->s.origin, origin, delta ); + dist = VectorLengthSquared( delta ); + //store the first location + if ( !numSpots ) { + //insert new entry into slot + list_dist[numSpots] = dist; + list_spot[numSpots] = Respawn_Positions[i]; + numSpots++; + continue; + } + + for (j = 0; j < numSpots; j++) { + if ( dist > list_dist[j] ) { + // found a distant spawn point + + numSpots++; + if (numSpots > DISTANTSPAWNPOINTS) + numSpots = DISTANTSPAWNPOINTS; + + //open a slot, move subordinate list down + // move from end of list to start + for (k = numSpots; k > j; k--) { + if ( k <= numSpots ) { + list_dist[k] = list_dist[k - 1]; + list_spot[k] = list_spot[k - 1]; + } + } + + //insert new entry into slot + list_dist[j] = dist; + list_spot[j] = Respawn_Positions[i]; + + + break; //exit from "for (j" loop + } + } + } + + if (!numSpots) { + spot = G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + //PKMOD - Ergodic 05/09/01 - debug respawning in same place (inactive) +// Com_Printf("SelectRandomDistantSpawnPoint:!numSpots - location>%s<\n", vtos(spot->s.origin) ); + if (!spot) + G_Error( "Couldn't find a distant spawn point" ); +// VectorCopy (spot->s.origin, origin); +// origin[2] += 9; +// VectorCopy (spot->s.angles, angles); + return spot; + } + + // select a random spot from the spawn points furthest away + rnd = random() * numSpots; + + //PKMOD - Ergodic 05/09/01 - debug respawning in same place (inactive) +// Com_Printf("SelectRandomDistantSpawnPoint - rnd>%d<, location>%s<\n", rnd, vtos(list_spot[rnd]->s.origin) ); +// Com_Printf("SelectRandomDistantSpawnPoint - rnd>%d<\n", rnd ); + +// VectorCopy (list_spot[rnd]->s.origin, origin); +// origin[2] += 9; +// VectorCopy (list_spot[rnd]->s.angles, angles); + + return list_spot[rnd]; +} + + +//PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles, int client_hub_flag ) { + gentity_t *spot; + gentity_t *nearestSpot; + + //PKMOD - Ergodic 05/10/01 - add failsafe for initial spawns + int spawn_count; + + //PKMOD - Ergodic 05/10/01 - debug initial spawn logic (inactive) +// Com_Printf("SelectSpawnPoint - avoidPoint>%s<, origin>%s<, client_hub_flag>%d<\n", vtos(avoidPoint), vtos(origin), client_hub_flag ); + + //PKMOD - Ergodic 05/09/01 - add distant spawn logic + if ( client_hub_flag ) { + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint, client_hub_flag ); + + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + spot = SelectRandomDeathmatchSpawnPoint ( client_hub_flag ); + if ( spot == nearestSpot ) { + // roll again if it would be real close to point of death + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + spot = SelectRandomDeathmatchSpawnPoint ( client_hub_flag ); + if ( spot == nearestSpot ) { + // last try + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + spot = SelectRandomDeathmatchSpawnPoint ( client_hub_flag ); + } + } + } + else { + //PKMOD - Ergodic 07/24/01 - DEBUG the quake3 acess violation failure + // do not call distant respawn code +// spot = SelectRandomDistantSpawnPoint ( avoidPoint ); + //PKMOD - Ergodic 07/25/01 - try new optimized distant respawn code +// spot = SelectRandomDeathmatchSpawnPoint ( client_hub_flag ); + spot = SelectRandomDistantSpawnPoint2 ( avoidPoint ); + + //PKMOD - Ergodic 05/10/01 - add failsafe for initial spawns + if ( !spot ) { + spawn_count = 3; + while ( ( !spot ) && (spawn_count > 0) ) { + spot = SelectRandomDeathmatchSpawnPoint ( client_hub_flag ); + spawn_count--; + } + } + } + + // find a single player start spot + if (!spot) { + G_Error( "SelectSpawnPoint: Couldn't find a spawn point" ); + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +} + +//PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted +/* +=========== +SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles, int client_hub_flag ) { + gentity_t *spot; + + spot = NULL; + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + if ( client_hub_flag ) { + //if client has voted... + while ((spot = G_Find (spot, FOFS(classname), "info_player_postvote")) != NULL) { + if ( spot->spawnflags & 1 ) { + break; + } + } + } else { + //if client default respawn or not voted... + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( spot->spawnflags & 1 ) { + break; + } + } + } + + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + if ( !spot || SpotWouldTelefrag( spot ) ) { + return SelectSpawnPoint( vec3_origin, origin, angles, client_hub_flag ); + } + + VectorCopy (spot->s.origin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +} + +/* +=========== +SelectSpectatorSpawnPoint + +============ +*/ +gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { + FindIntermissionPoint(); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + +/* +=============== +InitBodyQue +=============== +*/ +void InitBodyQue (void) { + int i; + gentity_t *ent; + + level.bodyQueIndex = 0; + for (i=0; iclassname = "bodyque"; + ent->neverFree = qtrue; + level.bodyQue[i] = ent; + } +} + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +void BodySink( gentity_t *ent ) { + if ( level.time - ent->timestamp > 6500 ) { + // the body ques are never actually freed, they are just unlinked + trap_UnlinkEntity( ent ); + ent->physicsObject = qfalse; + return; + } + ent->nextthink = level.time + 100; + ent->s.pos.trBase[2] -= 1; +} + +/* +============= +CopyToBodyQue + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +void CopyToBodyQue( gentity_t *ent ) { +#ifdef MISSIONPACK + gentity_t *e; + int i; +#endif + gentity_t *body; + int contents; + + trap_UnlinkEntity (ent); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( ent->s.origin, -1 ); + //PKMOD - Ergodic 12/29/00 - debug surface flags inactive +// Com_Printf("CopyToBodyQue - contents>%d<\n", contents ); + + if ( contents & CONTENTS_NODROP ) { + return; + } + + // grab a body que and cycle to the next one + body = level.bodyQue[ level.bodyQueIndex ]; + level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; + + trap_UnlinkEntity (body); + + body->s = ent->s; + body->s.eFlags = EF_DEAD; // clear EF_TALK, etc +#ifdef MISSIONPACK + if ( ent->s.eFlags & EF_KAMIKAZE ) { + body->s.eFlags |= EF_KAMIKAZE; + + // check if there is a kamikaze timer around for this owner + for (i = 0; i < MAX_GENTITIES; i++) { + e = &g_entities[i]; + if (!e->inuse) + continue; + if (e->activator != ent) + continue; + if (strcmp(e->classname, "kamikaze timer")) + continue; + e->activator = body; + break; + } + } +#endif + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { + body->s.pos.trType = TR_GRAVITY; + body->s.pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); + } else { + body->s.pos.trType = TR_STATIONARY; + } + body->s.event = 0; + + // change the animation to the last-frame only, so the sequence + // doesn't repeat anew for the body + switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) { + case BOTH_DEATH1: + case BOTH_DEAD1: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; + break; + case BOTH_DEATH2: + case BOTH_DEAD2: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; + break; + case BOTH_DEATH3: + case BOTH_DEAD3: + default: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; + break; + } + + body->r.svFlags = ent->r.svFlags; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_CORPSE; + body->r.ownerNum = ent->s.number; + + body->nextthink = level.time + 5000; + body->think = BodySink; + + body->die = body_die; + + // don't take more damage if already gibbed + if ( ent->health <= GIB_HEALTH ) { + body->takedamage = qfalse; + } else { + body->takedamage = qtrue; + } + + + VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); + trap_LinkEntity (body); +} + +//====================================================================== + + +/* +================== +SetClientViewAngle + +================== +*/ +void SetClientViewAngle( gentity_t *ent, vec3_t angle ) { + int i; + + // set the delta angle + for (i=0 ; i<3 ; i++) { + int cmdAngle; + + cmdAngle = ANGLE2SHORT(angle[i]); + ent->client->ps.delta_angles[i] = cmdAngle - ent->client->pers.cmd.angles[i]; + } + VectorCopy( angle, ent->s.angles ); + VectorCopy (ent->s.angles, ent->client->ps.viewangles); +} + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) { + gentity_t *tent; + + CopyToBodyQue (ent); + ClientSpawn(ent); + + // add a teleportation effect + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; +} + +/* +================ +TeamCount + +Returns number of players on a team +================ +*/ +team_t TeamCount( int ignoreClientNum, int team ) { + int i; + int count = 0; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( i == ignoreClientNum ) { + continue; + } + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + count++; + } + } + + return count; +} + +/* +================ +TeamLeader + +Returns the client number of the team leader +================ +*/ +int TeamLeader( int team ) { + int i; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == team ) { + if ( level.clients[i].sess.teamLeader ) + return i; + } + } + + return -1; +} + + +/* +================ +PickTeam + +================ +*/ +team_t PickTeam( int ignoreClientNum ) { + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = TeamCount( ignoreClientNum, TEAM_BLUE ); + counts[TEAM_RED] = TeamCount( ignoreClientNum, TEAM_RED ); + + if ( counts[TEAM_BLUE] > counts[TEAM_RED] ) { + return TEAM_RED; + } + if ( counts[TEAM_RED] > counts[TEAM_BLUE] ) { + return TEAM_BLUE; + } + // equal team count, so join the team with the lowest score + if ( level.teamScores[TEAM_BLUE] > level.teamScores[TEAM_RED] ) { + return TEAM_RED; + } + return TEAM_BLUE; +} + +/* +=========== +ForceClientSkin + +Forces a client's skin (for teamplay) +=========== +*/ +/* +static void ForceClientSkin( gclient_t *client, char *model, const char *skin ) { + char *p; + + if ((p = Q_strrchr(model, '/')) != 0) { + *p = 0; + } + + Q_strcat(model, MAX_QPATH, "/"); + Q_strcat(model, MAX_QPATH, skin); +} +*/ + +/* +=========== +ClientCheckName +============ +*/ +static void ClientCleanName( const char *in, char *out, int outSize ) { + int len, colorlessLen; + char ch; + char *p; + int spaces; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + while( 1 ) { + ch = *in++; + if( !ch ) { + break; + } + + // don't allow leading spaces + if( !*p && ch == ' ' ) { + continue; + } + + // check colors + if( ch == Q_COLOR_ESCAPE ) { + // solo trailing carat is not a color prefix + if( !*in ) { + break; + } + + // don't allow black in a name, period + if( ColorIndex(*in) == 0 ) { + in++; + continue; + } + + // make sure room in dest for both chars + if( len > outSize - 2 ) { + break; + } + + *out++ = ch; + *out++ = *in++; + len += 2; + continue; + } + + // don't allow too many consecutive spaces + if( ch == ' ' ) { + spaces++; + if( spaces > 3 ) { + continue; + } + } + else { + spaces = 0; + } + + if( len > outSize - 1 ) { + break; + } + + *out++ = ch; + colorlessLen++; + len++; + } + *out = 0; + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) { + Q_strncpyz( p, "UnnamedPlayer", outSize ); + } +} + + +/* +=========== +ClientUserInfoChanged + +Called from ClientConnect when the player first connects and +directly by the server system when the player updates a userinfo variable. + +The game can override any of the settings and call trap_SetUserinfo +if desired. +============ +*/ +void ClientUserinfoChanged( int clientNum ) { + gentity_t *ent; + int teamTask, teamLeader, team, health; + char *s; + char model[MAX_QPATH]; + char headModel[MAX_QPATH]; + char oldname[MAX_STRING_CHARS]; + gclient_t *client; + char c1[MAX_INFO_STRING]; + char c2[MAX_INFO_STRING]; + char redTeam[MAX_INFO_STRING]; + char blueTeam[MAX_INFO_STRING]; + char userinfo[MAX_INFO_STRING]; + //PKMOD - Ergodic 01/09/02 - create a holder for private bot + char pb[MAX_INFO_STRING]; + + ent = g_entities + clientNum; + client = ent->client; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if ( !Info_Validate(userinfo) ) { + strcpy (userinfo, "\\name\\badinfo"); + } + + // check for local client + s = Info_ValueForKey( userinfo, "ip" ); + if ( !strcmp( s, "localhost" ) ) { + client->pers.localClient = qtrue; + } + + // check the item prediction + s = Info_ValueForKey( userinfo, "cg_predictItems" ); + if ( !atoi( s ) ) { + client->pers.predictItemPickup = qfalse; + } else { + client->pers.predictItemPickup = qtrue; + } + + // set name + Q_strncpyz ( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey (userinfo, "name"); + ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + Q_strncpyz( client->pers.netname, "scoreboard", sizeof(client->pers.netname) ); + } + } + + if ( client->pers.connected == CON_CONNECTED ) { + if ( strcmp( oldname, client->pers.netname ) ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " renamed to %s\n\"", oldname, + client->pers.netname) ); + } + } + + // set max health +#ifdef MISSIONPACK + if (client->ps.powerups[PW_GUARD]) { + client->pers.maxHealth = 200; + } else { + health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + client->pers.maxHealth = health; + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } + } +#else + health = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + client->pers.maxHealth = health; + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } +#endif + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + + // set model + if( g_gametype.integer >= GT_TEAM ) { + Q_strncpyz( model, Info_ValueForKey (userinfo, "team_model"), sizeof( model ) ); + Q_strncpyz( headModel, Info_ValueForKey (userinfo, "team_headmodel"), sizeof( headModel ) ); + } else { + Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); + Q_strncpyz( headModel, Info_ValueForKey (userinfo, "headmodel"), sizeof( headModel ) ); + } + + // bots set their team a few frames later + if (g_gametype.integer >= GT_TEAM && g_entities[clientNum].r.svFlags & SVF_BOT) { + s = Info_ValueForKey( userinfo, "team" ); + if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { + team = TEAM_RED; + } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { + team = TEAM_BLUE; + } else { + // pick the team with the least number of players + team = PickTeam( clientNum ); + } + } + else { + team = client->sess.sessionTeam; + } + +/* NOTE: all client side now + + // team + switch( team ) { + case TEAM_RED: + ForceClientSkin(client, model, "red"); +// ForceClientSkin(client, headModel, "red"); + break; + case TEAM_BLUE: + ForceClientSkin(client, model, "blue"); +// ForceClientSkin(client, headModel, "blue"); + break; + } + // don't ever use a default skin in teamplay, it would just waste memory + // however bots will always join a team but they spawn in as spectator + if ( g_gametype.integer >= GT_TEAM && team == TEAM_SPECTATOR) { + ForceClientSkin(client, model, "red"); +// ForceClientSkin(client, headModel, "red"); + } +*/ + +#ifdef MISSIONPACK + if (g_gametype.integer >= GT_TEAM) { + client->pers.teamInfo = qtrue; + } else { + s = Info_ValueForKey( userinfo, "teamoverlay" ); + if ( ! *s || atoi( s ) != 0 ) { + client->pers.teamInfo = qtrue; + } else { + client->pers.teamInfo = qfalse; + } + } +#else + // teamInfo + s = Info_ValueForKey( userinfo, "teamoverlay" ); + if ( ! *s || atoi( s ) != 0 ) { + client->pers.teamInfo = qtrue; + } else { + client->pers.teamInfo = qfalse; + } +#endif + /* + s = Info_ValueForKey( userinfo, "cg_pmove_fixed" ); + if ( !*s || atoi( s ) == 0 ) { + client->pers.pmoveFixed = qfalse; + } + else { + client->pers.pmoveFixed = qtrue; + } + */ + + // team task (0 = none, 1 = offence, 2 = defence) + teamTask = atoi(Info_ValueForKey(userinfo, "teamtask")); + // team Leader (1 = leader, 0 is normal player) + teamLeader = client->sess.teamLeader; + + // colors + strcpy(c1, Info_ValueForKey( userinfo, "color1" )); + strcpy(c2, Info_ValueForKey( userinfo, "color2" )); + + strcpy(redTeam, Info_ValueForKey( userinfo, "g_redteam" )); + strcpy(blueTeam, Info_ValueForKey( userinfo, "g_blueteam" )); + + //PKMOD - Ergodic 01/09/02 - set holder for private bot + strcpy(pb, Info_ValueForKey( userinfo, "privateBot" )); + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + if ( ent->r.svFlags & SVF_BOT ) { + //PKMOD - Ergodic 01/09/02 - update for Private Bot (pb) value + s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d\\pb\\%s", + client->pers.netname, team, model, headModel, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, + Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader, pb ); + + //PKMOD - Ergodic 01/10/02 - debug bot's userinfo (inactive) +// Com_Printf("ClientUserinfoChanged - userinfo>%s<\n", userinfo); +// Com_Printf("ClientUserinfoChanged - s>%s<\n", s); + } else { + //PKMOD - Ergodic 01/09/02 - update for Private Bot (pb) value + s = va("n\\%s\\t\\%i\\model\\%s\\hmodel\\%s\\g_redteam\\%s\\g_blueteam\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d\\pb\\0", + client->pers.netname, client->sess.sessionTeam, model, headModel, redTeam, blueTeam, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader); + } + + trap_SetConfigstring( CS_PLAYERS+clientNum, s ); + + // this is not the userinfo, more like the configstring actually + G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); +} + + +/* +=========== +ClientConnect + +Called when a player begins connecting to the server. +Called again for every map change or tournement restart. + +The session information will be valid after exit. + +Return NULL if the client should be allowed, otherwise return +a string with the reason for denial. + +Otherwise, the client will be sent the current gamestate +and will eventually get to ClientBegin. + +firstTime will be qtrue the very first time a client connects +to the server machine, but qfalse on map changes and tournement +restarts. +============ +*/ +char *ClientConnect( int clientNum, qboolean firstTime, qboolean isBot ) { + char *value; +// char *areabits; + gclient_t *client; + char userinfo[MAX_INFO_STRING]; + gentity_t *ent; + + ent = &g_entities[ clientNum ]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // IP filtering + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 + // recommanding PB based IP / GUID banning, the builtin system is pretty limited + // check to see if they are on the banned IP list + //PKMOD - Ergodic 01/09/02 - debug bot's userinfo (inactive) +// Com_Printf("ClientConnect - userinfo>%s<\n", userinfo); + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if ( G_FilterPacket( value ) ) { + return "You are banned from this server."; + } + + // we don't check password for bots and local client + // NOTE: local client <-> "ip" "localhost" + // this means this client is not running in our current process + if ( !( ent->r.svFlags & SVF_BOT ) && (strcmp(value, "localhost") != 0)) { + // check for a password + value = Info_ValueForKey (userinfo, "password"); + if ( g_password.string[0] && Q_stricmp( g_password.string, "none" ) && + strcmp( g_password.string, value) != 0) { + return "Invalid password"; + } + } + + //PKMOD - Ergodic 10/21/02 - prevent client connecting as a Private Bot + if ( !isBot ) { + ent->r.svFlags &= ~SVF_BOT; + //PKMOD - Ergodic 10/27/02 - do both BOT settings, now SVF_PRIVATEBOT... + ent->r.svFlags &= ~SVF_PRIVATEBOT; + } + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + +// areabits = client->areabits; + + memset( client, 0, sizeof(*client) ); + + client->pers.connected = CON_CONNECTING; + + // read or initialize the session data + if ( firstTime || level.newSession ) { + //PKMOD - Ergodic 01/21/02 - modify call to pass in Private Bot status + G_InitSessionData( client, userinfo, ent->r.svFlags & SVF_PRIVATEBOT ); + } + + G_ReadSessionData( client ); + + /* + //PKMOD - Ergodic 02/13/04 - test private bot re-entry into map + if ( client->sess.sessionPrivateBot ) { + //PKMOD - Ergodic 02/19/04 - no need to drop the client - just returning with text will remove Private Bot + // NOTE: This code is here for failsafe and will not execute since Private Bot + // is removed in g_main.c : G_ShutdownGame. +//PKMOD - Ergodic 03/15/04 - debug bot's restart +Com_Printf("ClientConnect - (2) returned after private bot session detected\n" ); + return "Private Bot is removed for Basic Training"; //02/20/04 + } + */ + + if( isBot ) { + ent->r.svFlags |= SVF_BOT; + ent->inuse = qtrue; + if( !G_BotConnect( clientNum, !firstTime ) ) { + return "BotConnectfailed"; + } + } + + // get and distribute relevent paramters + G_LogPrintf( "ClientConnect: %i\n", clientNum ); + ClientUserinfoChanged( clientNum ); + + // don't do the "xxx connected" messages if they were caried over from previous level + if ( firstTime ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " connected\n\"", client->pers.netname) ); + } + + if ( g_gametype.integer >= GT_TEAM && + client->sess.sessionTeam != TEAM_SPECTATOR ) { + BroadcastTeamChange( client, -1 ); + } + + //PKMOD - Ergodic 01/24/02 - debug bot's status bits (inactive) +// Com_Printf("ClientConnect - before CalculateRanks\n" ); + + // count current clients and rank for scoreboard + CalculateRanks(); + + //PKMOD - Ergodic 01/24/02 - debug bot's status bits (inactive) +// Com_Printf("ClientConnect - after CalculateRanks\n" ); + + // for statistics +// client->areabits = areabits; +// if ( !client->areabits ) +// client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 ); + + return NULL; +} + +//PKMOD - Ergodic 10/11/00 - update client PERS with voting information (set to zero) +/* +=========== +ClientBegin + +called when a client has finished connecting, and is ready +to be placed into the level. This will happen every level load, +and on transition between teams, but doesn't happen on respawns +============ +*/ +void ClientBegin( int clientNum ) { + gentity_t *ent; + gclient_t *client; + gentity_t *tent; + int flags; + + //PKMOD - Ergodic 02/19/04 - debug bot's restart (inactive) + //Com_Printf("ClientBegin - client>%d<, svFlags>%d<, PrivateBot?>%d<\n", clientNum, g_entities[clientNum].r.svFlags, g_entities[clientNum].r.svFlags & SVF_PRIVATEBOT ); + + + ent = g_entities + clientNum; + + client = level.clients + clientNum; + + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + //PKMOD - Ergodic 01/15/02 - debug bot's restart (inactive) +// Com_Printf("ClientBegin - persistant[PERS_KILLED]>%d<\n", client->ps.persistant[PERS_KILLED] ); + + client->pers.connected = CON_CONNECTED; + client->pers.enterTime = level.time; + client->pers.teamState.state = TEAM_BEGIN; + + // save eflags around this, because changing teams will + // cause this to happen with a valid entity, and we + // want to make sure the teleport bit is set right + // so the viewpoint doesn't interpolate through the + // world to the new position + flags = client->ps.eFlags; + memset( &client->ps, 0, sizeof( client->ps ) ); + client->ps.eFlags = flags; + + // locate ent at a spawn point + ClientSpawn( ent ); + + if ( client->sess.sessionTeam != TEAM_SPECTATOR ) { + // send event + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + tent->s.clientNum = ent->s.clientNum; + + if ( g_gametype.integer != GT_TOURNAMENT ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname) ); + } + } + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // count current clients and rank for scoreboard + CalculateRanks(); + + //PKMOD - Ergodic 10/11/00 - set the hub voting flag + //PKMOD - Ergodic 12/17/00 - place PERS_HUB_FLAG as first bit of PERS_PAINKILLER_COUNT + // Mask off lowest bit + client->ps.persistant[PERS_PAINKILLER_COUNT] &= 0x7FFFFFFE; + + //PKMOD - Ergodic 01/18/02 - drop Private Bot upon entry + //PKMOD - Ergodic 12/19/02 - emove this code - code will cause PB to not initialize + // properly in Online game. + // This removal code will be executed int he shutdown function + //if ( client->sess.sessionPrivateBot ) { + //PKMOD - Ergodic 01/09/02 - debug bot's userinfo (inactive) +// Com_Printf("ClientBegin - dropping client>%d<\n", clientNum ); + // trap_DropClient( clientNum, "Private Bot returning to base" ); + //} + +} + +//PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted +/* +=========== +ClientSpawn + +Called every time a client is placed fresh in the world: +after the first ClientBegin, and after each respawn +Initializes all non-persistant parts of playerState +============ +*/ +void ClientSpawn(gentity_t *ent) { + int index; + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + int i; + clientPersistant_t saved; + clientSession_t savedSess; + int persistant[MAX_PERSISTANT]; + gentity_t *spawnPoint; + int flags; + int savedPing; +// char *savedAreaBits; + int accuracy_hits, accuracy_shots; +// int savedEvents[MAX_PS_EVENTS]; //PKMOD -Ergodic 12/19/02 - unsed variable - Unlagged? + int eventSequence; + char userinfo[MAX_INFO_STRING]; + + index = ent - g_entities; + client = ent->client; + + //PKMOD - Ergodic 01/17/00 - if Bot in HUB then force postvote respawn + // Bots do not navigate the hub very well, bots usually fight instead of vote + // we will just place them in the postvote area as a default and leave the voting + // to the human players + if ( ( ent->r.svFlags & SVF_BOT ) && ( hub_flag.integer == 1 ) ) { + client->ps.persistant[PERS_PAINKILLER_COUNT] |= 1; + } + + // find a spawn point + // do it before setting health back up, so farthest + // ranging doesn't count this client + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) { + spawnPoint = SelectSpectatorSpawnPoint ( + spawn_origin, spawn_angles); + } else if (g_gametype.integer == GT_CTF) { + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + //PKMOD - Ergodic 12/17/00 - place PERS_HUB_FLAG as first bit of PERS_PAINKILLER_COUNT + spawnPoint = SelectCTFSpawnPoint ( + client->sess.sessionTeam, + client->pers.teamState.state, + spawn_origin, spawn_angles, ( client->ps.persistant[PERS_PAINKILLER_COUNT] & 1 ) ); + } else { + do { + //PKMOD - Ergodic 01/10/02 - spawn Private Bot close to owner (DM only feature) + if ( ent->r.svFlags & SVF_PRIVATEBOT ) { + spawnPoint = SelectClosestSpawnPoint( ent->parent->r.currentOrigin, spawn_origin, spawn_angles ); + break; + } + else { + // the first spawn should be at a good looking spot + if ( !client->pers.initialSpawn && client->pers.localClient ) { + client->pers.initialSpawn = qtrue; + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + //PKMOD - Ergodic 12/17/00 - place PERS_HUB_FLAG as first bit of PERS_PAINKILLER_COUNT + spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles, ( client->ps.persistant[PERS_PAINKILLER_COUNT] & 1 ) ); + } else { + // don't spawn near existing origin if possible + //PKMOD - Ergodic 10/12/00 - add "client_hub_flag" argument - 1:voted, 0:default/not-voted + //PKMOD - Ergodic 12/17/00 - place PERS_HUB_FLAG as first bit of PERS_PAINKILLER_COUNT + spawnPoint = SelectSpawnPoint ( client->ps.origin, spawn_origin, spawn_angles, ( client->ps.persistant[PERS_PAINKILLER_COUNT] & 1 ) ); + } + } + // Tim needs to prevent bots from spawning at the initial point + // on q3dm0... + if ( ( spawnPoint->flags & FL_NO_BOTS ) && ( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + // just to be symetric, we have a nohumans option... + if ( ( spawnPoint->flags & FL_NO_HUMANS ) && !( ent->r.svFlags & SVF_BOT ) ) { + continue; // try again + } + + break; + + } while ( 1 ); + } + client->pers.teamState.state = TEAM_ACTIVE; + + //PKMOD Ergodic - 07/08/01, remove kamizaki entity flag + // always clear the kamikaze flag +// ent->s.eFlags &= ~EF_KAMIKAZE; + + // toggle the teleport bit so the client knows to not lerp + // and never clear the voted flag + flags = ent->client->ps.eFlags & (EF_TELEPORT_BIT | EF_VOTED | EF_TEAMVOTED); + flags ^= EF_TELEPORT_BIT; + + // clear everything but the persistant data + + saved = client->pers; + savedSess = client->sess; + savedPing = client->ps.ping; +// savedAreaBits = client->areabits; + accuracy_hits = client->accuracy_hits; + accuracy_shots = client->accuracy_shots; + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + persistant[i] = client->ps.persistant[i]; + } + eventSequence = client->ps.eventSequence; + + memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? + + client->pers = saved; + client->sess = savedSess; + client->ps.ping = savedPing; +// client->areabits = savedAreaBits; + client->accuracy_hits = accuracy_hits; + client->accuracy_shots = accuracy_shots; + client->lastkilled_client = -1; + + for ( i = 0 ; i < MAX_PERSISTANT ; i++ ) { + client->ps.persistant[i] = persistant[i]; + } + client->ps.eventSequence = eventSequence; + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; + + client->airOutTime = level.time + 12000; + + trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); + + //PKMOD - Ergodic 01/09/02 - debug bot's userinfo (inactive) +// Com_Printf("ClientSpawn - userinfo>%s<\n", userinfo); + + // set max health + client->pers.maxHealth = atoi( Info_ValueForKey( userinfo, "handicap" ) ); + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > 100 ) { + client->pers.maxHealth = 100; + } + // clear entity values + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + client->ps.eFlags = flags; + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[index]; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "player"; + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_PLAYERSOLID; + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->flags = 0; + + //PKMOD - Ergodic - 03/29/2000, set the respawn values for AirFist +// ent->AirFist_Level = 100; + //PKMOD - Ergodic 11/15/00, move airfist level to playerstate + // airfist levels will be (4,3,2,1,0) + ent->client->ps.stats[STAT_AIRFIST_LEVEL] = 4; + + //PKMOD - Ergodic - 09/09/2000, set the respawn values for Lightning_Strike + //PKMOD - Ergodic - 12/28/2000, created new structure for PKA flags + // set all flags to zero +// ent->Lightning_Strike = 0; + ent->pka_flags = 0; + + //PKMOD - Ergodic - 05/26/2000, set the respawn values for BearTrap + //PKMOD Ergodic - 04/07/01 - rename variable to add autosentry + ent->BearTrap_Autosentry_ImmuneTime = level.time + 2000; // 2 seconds of immunity, quick run! + + //PKMOD - Ergodic 06/19/00 set the pka_items flag to 0 + ent->client->ps.stats[STAT_PKA_ITEMS] = 0; + + //PKMOD - Ergodic 06/30/00 - reset counter variable for number of beartraps attached to the player + ent->client->ps.stats[STAT_BEARTRAPS_ATTACHED] = 0; + + //PKMOD - Ergodic 03/13/01 - clear the dragon deploy variable + ent->client->ps.generic1 = 0; + + //PKMOD - Ergodic 04/16/01 - reset special gravity + ent->pka_gravity_time = 0; + ent->pka_gravity = g_gravity.value; + + + VectorCopy (playerMins, ent->r.mins); + VectorCopy (playerMaxs, ent->r.maxs); + + client->ps.clientNum = index; + + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ); + + //PKMOD - Ergodic 12/02/01 - test bots using grapple - TO BE REMOVED LATER + //PKMOD - Ergodic 08/06/02 - remove automatic Dragon from strtup inventory +// client->ps.stats[STAT_WEAPONS] = ( 1 << WP_MACHINEGUN ) | ( 1 << WP_GRAPPLING_HOOK ); + + if ( g_gametype.integer == GT_TEAM ) { + client->ps.ammo[WP_MACHINEGUN] = 50; + } else { + client->ps.ammo[WP_MACHINEGUN] = 100; + } + + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GAUNTLET ); + +//PKMOD -Enable Grapple. This version automatically gives you the grapple (disabled 07/05/00) +// client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK); +//PKMOD - Ergodic 05/17/00 - remove automatic gravity well (disabled 07/05/00) +// client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAVITY); + //PKMOD + + client->ps.ammo[WP_GAUNTLET] = -1; + client->ps.ammo[WP_GRAPPLING_HOOK] = -1; + + // health will count down towards max_health + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] + 25; + + //PKMOD - Ergodic 01/07/02 - Private Bot will be enhanced + if ( ent->r.svFlags & SVF_PRIVATEBOT ) { + //max out health + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 2; + //max out armor + client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_MAX_HEALTH] * 2; + //give some weapon(s) + //PKMOD - Ergodic 01/24/02 - add to inventory with |= not set with = + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_GRAPPLING_HOOK ); + + //give 1 high-powered weapon + //PKMOD - Ergodic 01/05/03 - remove rail from PB selection + // Scale selection so that HIGH powered weapons are less frequently + // obtained than lower powered weapons + // Nailgun ~50% + // Rocket Launcher ~30% + // Lightning Gun ~20% + i = rand() % 100; //0,1,2...97,98,99 + if ( i < 50 ) { + //Give a Nail Gun + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_NAILGUN ); + client->ps.ammo[WP_NAILGUN] = 135; + } else if ( i < 80 ) { + //Give a Rocket Launcher + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_ROCKET_LAUNCHER ); + client->ps.ammo[WP_ROCKET_LAUNCHER] = 25; + } else { + //Give a Lightning Gun + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_LIGHTNING ); + client->ps.ammo[WP_LIGHTNING] = 180; + } + } + + G_SetOrigin( ent, spawn_origin ); + VectorCopy( spawn_origin, client->ps.origin ); + + // the respawned flag will be cleared after the attack and jump keys come up + client->ps.pm_flags |= PMF_RESPAWNED; + + trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); + SetClientViewAngle( ent, spawn_angles ); + + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + + } else { + G_KillBox( ent ); + trap_LinkEntity (ent); + + // force the base weapon up + client->ps.weapon = WP_MACHINEGUN; + client->ps.weaponstate = WEAPON_READY; + + } + + // don't allow full run speed for a bit + client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + client->ps.pm_time = 100; + + client->respawnTime = level.time; + client->inactivityTime = level.time + g_inactivity.integer * 1000; + client->latched_buttons = 0; + + // set default animations + client->ps.torsoAnim = TORSO_STAND; + client->ps.legsAnim = LEGS_IDLE; + + if ( level.intermissiontime ) { + MoveClientToIntermission( ent ); + } else { + // fire the targets of the spawn point + G_UseTargets( spawnPoint, ent ); + + // select the highest weapon number available, after any + // spawn given items have fired + client->ps.weapon = 1; + //PKMOD -Enable Grapple. This version automatically gives you the grapple + // -1 changed to -2 in "for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) {" + for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { + if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { + client->ps.weapon = i; + break; + } + } + } + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + client->ps.commandTime = level.time - 100; + ent->client->pers.cmd.serverTime = level.time; + ClientThink( ent-g_entities ); + + // positively link the client, even if the command times are weird + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); + trap_LinkEntity( ent ); + } + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); +} + + +/* +=========== +ClientDisconnect + +Called when a player drops from the server. +Will not be called between levels. + +This should NOT be called directly by any game logic, +call trap_DropClient(), which will call this and do +server system housekeeping. +============ +*/ +void ClientDisconnect( int clientNum ) { + gentity_t *ent; + gentity_t *tent; + int i; + + // cleanup if we are kicking a bot that + // hasn't spawned yet + G_RemoveQueuedBotBegin( clientNum ); + + ent = g_entities + clientNum; + if ( !ent->client ) { + return; + } + + // stop any following clients + for ( i = 0 ; i < level.maxclients ; i++ ) { + if ( level.clients[i].sess.sessionTeam == TEAM_SPECTATOR + && level.clients[i].sess.spectatorState == SPECTATOR_FOLLOW + && level.clients[i].sess.spectatorClient == clientNum ) { + StopFollowing( &g_entities[i] ); + } + } + + // send effect if they were completely connected + if ( ent->client->pers.connected == CON_CONNECTED + && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = ent->s.clientNum; + + // They don't get to take powerups with them! + // Especially important for stuff like CTF flags + TossClientItems( ent ); +#ifdef MISSIONPACK + TossClientPersistantPowerups( ent ); + if( g_gametype.integer == GT_HARVESTER ) { + TossClientCubes( ent ); + } +#endif + + } + + G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); + + //PKMOD - Ergodic 01/22/02 - debug disconnecting private bot in tournament game (inactive) +// Com_Printf( "ClientDisconnect: clientNum>%d<, svFlags>%d<\n", clientNum, ent->r.svFlags ); +// if ( ent->r.svFlags & SVF_PRIVATEBOT ) +// Com_Printf( "ClientDisconnect: private bot disconnecting clientNum>%d<\n", clientNum ); +// else +// Com_Printf( "ClientDisconnect: NOT private bot disconnecting clientNum>%d<\n", clientNum ); + + + + //PKMOD - Ergodic 02/11/04 - if cvar is set then do not count leaving the game as a win for the + // other player + if ( g_pkatourneyrules.integer == 0 ) { + // if we are playing in tourney mode and losing, give a win to the other player + if ( (g_gametype.integer == GT_TOURNAMENT ) + && !level.intermissiontime + && !level.warmupTime && ( level.sortedClients[1] == clientNum ) && + //PKMOD - Ergodic 01/22/02 - disregard Private Bot disconnects in tournament play + !( ent->r.svFlags & SVF_PRIVATEBOT ) ) { + level.clients[ level.sortedClients[0] ].sess.wins++; + ClientUserinfoChanged( level.sortedClients[0] ); + } + } + + trap_UnlinkEntity (ent); + ent->s.modelindex = 0; + ent->inuse = qfalse; + ent->classname = "disconnected"; + ent->client->pers.connected = CON_DISCONNECTED; + ent->client->ps.persistant[PERS_TEAM] = TEAM_FREE; + ent->client->sess.sessionTeam = TEAM_FREE; + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + CalculateRanks(); + + if ( ent->r.svFlags & SVF_BOT ) { + BotAIShutdownClient( clientNum, qfalse ); + } +} + diff --git a/quake3/source/code/game/g_cmds.c b/quake3/source/code/game/g_cmds.c new file mode 100644 index 0000000..94bf5b3 --- /dev/null +++ b/quake3/source/code/game/g_cmds.c @@ -0,0 +1,1953 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#include "g_local.h" + +#include "../../ui/menudef.h" // for the voice chats + +/* +================== +DeathmatchScoreboardMessage + +================== +*/ +void DeathmatchScoreboardMessage( gentity_t *ent ) { + char entry[1024]; + char string[1400]; + int stringlength; + int i, j; + gclient_t *cl; + int numSorted, scoreFlags, accuracy, perfect; + + // send the latest information on all clients + string[0] = 0; + stringlength = 0; + scoreFlags = 0; + + numSorted = level.numConnectedClients; + + for (i=0 ; i < numSorted ; i++) { + int ping; + + cl = &level.clients[level.sortedClients[i]]; + + if ( cl->pers.connected == CON_CONNECTING ) { + ping = -1; + } else { + ping = cl->ps.ping < 999 ? cl->ps.ping : 999; + } + + if( cl->accuracy_shots ) { + accuracy = cl->accuracy_hits * 100 / cl->accuracy_shots; + } + else { + accuracy = 0; + } + perfect = ( cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0 ) ? 1 : 0; + + //PKMOD - Ergodic 02/28/04 - add PainKiller medal awards count after per_captures + //PKMOD - Ergodic 03/16/04 - fix painkiller medal error: add another "%i" for painkiller argument + Com_sprintf (entry, sizeof(entry), + " %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i], + cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime)/60000, + scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy, + cl->ps.persistant[PERS_IMPRESSIVE_COUNT], + cl->ps.persistant[PERS_EXCELLENT_COUNT], + cl->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], + cl->ps.persistant[PERS_DEFEND_COUNT], + cl->ps.persistant[PERS_ASSIST_COUNT], + perfect, + cl->ps.persistant[PERS_CAPTURES], + //PKMOD - Ergodic 03/16/04 - fix painkiller medal count, painkiller is packed so take only the count + //cl->ps.persistant[PERS_PAINKILLER_COUNT]); + (cl->ps.persistant[PERS_PAINKILLER_COUNT] >> 1) / 10 ); + j = strlen(entry); + if (stringlength + j > 1024) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i, + level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], + string ) ); +} + + +/* +================== +Cmd_Score_f + +Request current scoreboard information +================== +*/ +void Cmd_Score_f( gentity_t *ent ) { + DeathmatchScoreboardMessage( ent ); +} + + + +/* +================== +CheatsOk +================== +*/ +qboolean CheatsOk( gentity_t *ent ) { + if ( !g_cheats.integer ) { + trap_SendServerCommand( ent-g_entities, va("print \"Cheats are not enabled on this server.\n\"")); + return qfalse; + } + if ( ent->health <= 0 ) { + trap_SendServerCommand( ent-g_entities, va("print \"You must be alive to use this command.\n\"")); + return qfalse; + } + return qtrue; +} + + +/* +================== +ConcatArgs +================== +*/ +char *ConcatArgs( int start ) { + int i, c, tlen; + static char line[MAX_STRING_CHARS]; + int len; + char arg[MAX_STRING_CHARS]; + + len = 0; + c = trap_Argc(); + for ( i = start ; i < c ; i++ ) { + trap_Argv( i, arg, sizeof( arg ) ); + tlen = strlen( arg ); + if ( len + tlen >= MAX_STRING_CHARS - 1 ) { + break; + } + memcpy( line + len, arg, tlen ); + len += tlen; + if ( i != c - 1 ) { + line[len] = ' '; + len++; + } + } + + line[len] = 0; + + return line; +} + +/* +================== +SanitizeString + +Remove case and control characters +================== +*/ +void SanitizeString( char *in, char *out ) { + while ( *in ) { + if ( *in == 27 ) { + in += 2; // skip color code + continue; + } + if ( *in < 32 ) { + in++; + continue; + } + *out++ = tolower( *in++ ); + } + + *out = 0; +} + +/* +================== +ClientNumberFromString + +Returns a player number for either a number or name string +Returns -1 if invalid +================== +*/ +int ClientNumberFromString( gentity_t *to, char *s ) { + gclient_t *cl; + int idnum; + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + + // numeric values are just slot numbers + if (s[0] >= '0' && s[0] <= '9') { + idnum = atoi( s ); + if ( idnum < 0 || idnum >= level.maxclients ) { + trap_SendServerCommand( to-g_entities, va("print \"Bad client slot: %i\n\"", idnum)); + return -1; + } + + cl = &level.clients[idnum]; + if ( cl->pers.connected != CON_CONNECTED ) { + trap_SendServerCommand( to-g_entities, va("print \"Client %i is not active\n\"", idnum)); + return -1; + } + return idnum; + } + + // check for a name match + SanitizeString( s, s2 ); + for ( idnum=0,cl=level.clients ; idnum < level.maxclients ; idnum++,cl++ ) { + if ( cl->pers.connected != CON_CONNECTED ) { + continue; + } + SanitizeString( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) { + return idnum; + } + } + + trap_SendServerCommand( to-g_entities, va("print \"User %s is not on the server\n\"", s)); + return -1; +} + +/* +================== +Cmd_Give_f + +Give items to a client +================== +*/ +void Cmd_Give_f (gentity_t *ent) +{ + char *name; + gitem_t *it; + int i; + qboolean give_all; + gentity_t *it_ent; + trace_t trace; + + if ( !CheatsOk( ent ) ) { + return; + } + + name = ConcatArgs( 1 ); + + if (Q_stricmp(name, "all") == 0) + give_all = qtrue; + else + give_all = qfalse; + + if (give_all || Q_stricmp( name, "health") == 0) + { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "weapons") == 0) + { + ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - ( 1 << WP_NONE ); + //PKMOD - Ergodic 06/13/00 Give the grapple hook when asked +// ( 1 << WP_GRAPPLING_HOOK ) - ( 1 << WP_NONE ); + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { + //PKMOD - Ergodic 07/07/01 - don't max out on PKA weapons + // fire the specific weapon + switch( i ) { + case WP_GAUNTLET: + ent->client->ps.ammo[i] = -1; + break; + case WP_GRAPPLING_HOOK: + ent->client->ps.ammo[i] = -1; + break; + + case WP_GRAVITY : + ent->client->ps.ammo[i] = 1; + break; + case WP_SENTRY : + ent->client->ps.ammo[i] = 3; + break; + case WP_BEARTRAP : + ent->client->ps.ammo[i] = 3; + break; + case WP_AIRFIST : + ent->client->ps.ammo[i] = 4; + break; + case WP_EXPLODING_SHELLS: + ent->client->ps.ammo[i] = 10; + break; + case WP_BEANS: + ent->client->ps.ammo[i] = 1; + break; + + default: + ent->client->ps.ammo[i] = 200; + break; + } + + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + ent->client->ps.stats[STAT_ARMOR] = 200; + + if (!give_all) + return; + } + + if (Q_stricmp(name, "excellent") == 0) { + ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++; + return; + } + if (Q_stricmp(name, "impressive") == 0) { + ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; + return; + } + if (Q_stricmp(name, "gauntletaward") == 0) { + ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++; + return; + } + if (Q_stricmp(name, "defend") == 0) { + ent->client->ps.persistant[PERS_DEFEND_COUNT]++; + return; + } + if (Q_stricmp(name, "assist") == 0) { + ent->client->ps.persistant[PERS_ASSIST_COUNT]++; + return; + } + + // spawn a specific item right on the player + if ( !give_all ) { + it = BG_FindItem (name); + if (!it) { + return; + } + + it_ent = G_Spawn(); + VectorCopy( ent->r.currentOrigin, it_ent->s.origin ); + it_ent->classname = it->classname; + G_SpawnItem (it_ent, it); + FinishSpawningItem(it_ent ); + memset( &trace, 0, sizeof( trace ) ); + Touch_Item (it_ent, ent, &trace); + if (it_ent->inuse) { + G_FreeEntity( it_ent ); + } + } +} + + +/* +================== +Cmd_God_f + +Sets client to godmode + +argv(0) god +================== +*/ +void Cmd_God_f (gentity_t *ent) +{ + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_GODMODE; + if (!(ent->flags & FL_GODMODE) ) + msg = "godmode OFF\n"; + else + msg = "godmode ON\n"; + + trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); +} + + +/* +================== +Cmd_Notarget_f + +Sets client to notarget + +argv(0) notarget +================== +*/ +void Cmd_Notarget_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + ent->flags ^= FL_NOTARGET; + if (!(ent->flags & FL_NOTARGET) ) + msg = "notarget OFF\n"; + else + msg = "notarget ON\n"; + + trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); +} + + +/* +================== +Cmd_Noclip_f + +argv(0) noclip +================== +*/ +void Cmd_Noclip_f( gentity_t *ent ) { + char *msg; + + if ( !CheatsOk( ent ) ) { + return; + } + + if ( ent->client->noclip ) { + msg = "noclip OFF\n"; + } else { + msg = "noclip ON\n"; + } + ent->client->noclip = !ent->client->noclip; + + trap_SendServerCommand( ent-g_entities, va("print \"%s\"", msg)); +} + + +/* +================== +Cmd_LevelShot_f + +This is just to help generate the level pictures +for the menus. It goes to the intermission immediately +and sends over a command to the client to resize the view, +hide the scoreboard, and take a special screenshot +================== +*/ +void Cmd_LevelShot_f( gentity_t *ent ) { + if ( !CheatsOk( ent ) ) { + return; + } + + // doesn't work in single player + if ( g_gametype.integer != 0 ) { + trap_SendServerCommand( ent-g_entities, + "print \"Must be in g_gametype 0 for levelshot\n\"" ); + return; + } + + BeginIntermission(); + trap_SendServerCommand( ent-g_entities, "clientLevelShot" ); +} + + +/* +================== +Cmd_LevelShot_f + +This is just to help generate the level pictures +for the menus. It goes to the intermission immediately +and sends over a command to the client to resize the view, +hide the scoreboard, and take a special screenshot +================== +*/ +void Cmd_TeamTask_f( gentity_t *ent ) { + char userinfo[MAX_INFO_STRING]; + char arg[MAX_TOKEN_CHARS]; + int task; + int client = ent->client - level.clients; + + if ( trap_Argc() != 2 ) { + return; + } + trap_Argv( 1, arg, sizeof( arg ) ); + task = atoi( arg ); + + trap_GetUserinfo(client, userinfo, sizeof(userinfo)); + Info_SetValueForKey(userinfo, "teamtask", va("%d", task)); + trap_SetUserinfo(client, userinfo); + ClientUserinfoChanged(client); +} + + + +/* +================= +Cmd_Kill_f +================= +*/ +void Cmd_Kill_f( gentity_t *ent ) { + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + return; + } + if (ent->health <= 0) { + return; + } + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; + player_die (ent, ent, ent, 100000, MOD_SUICIDE); +} + +/* +================= +BroadCastTeamChange + +Let everyone know about a team change +================= +*/ +void BroadcastTeamChange( gclient_t *client, int oldTeam ) +{ + if ( client->sess.sessionTeam == TEAM_RED ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the red team.\n\"", + client->pers.netname) ); + } else if ( client->sess.sessionTeam == TEAM_BLUE ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the blue team.\n\"", + client->pers.netname)); + } else if ( client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the spectators.\n\"", + client->pers.netname)); + } else if ( client->sess.sessionTeam == TEAM_FREE ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the battle.\n\"", + client->pers.netname)); + } +} + +/* +================= +SetTeam +================= +*/ +void SetTeam( gentity_t *ent, char *s ) { + int team, oldTeam; + gclient_t *client; + int clientNum; + spectatorState_t specState; + int specClient; + int teamLeader; + + // + // see what change is requested + // + client = ent->client; + + clientNum = client - level.clients; + specClient = 0; + specState = SPECTATOR_NOT; + if ( !Q_stricmp( s, "scoreboard" ) || !Q_stricmp( s, "score" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_SCOREBOARD; + } else if ( !Q_stricmp( s, "follow1" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FOLLOW; + specClient = -1; + } else if ( !Q_stricmp( s, "follow2" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FOLLOW; + specClient = -2; + } else if ( !Q_stricmp( s, "spectator" ) || !Q_stricmp( s, "s" ) ) { + team = TEAM_SPECTATOR; + specState = SPECTATOR_FREE; + } else if ( g_gametype.integer >= GT_TEAM ) { + // if running a team game, assign player to one of the teams + specState = SPECTATOR_NOT; + if ( !Q_stricmp( s, "red" ) || !Q_stricmp( s, "r" ) ) { + team = TEAM_RED; + } else if ( !Q_stricmp( s, "blue" ) || !Q_stricmp( s, "b" ) ) { + team = TEAM_BLUE; + } else { + // pick the team with the least number of players + team = PickTeam( clientNum ); + } + + if ( g_teamForceBalance.integer ) { + int counts[TEAM_NUM_TEAMS]; + + counts[TEAM_BLUE] = TeamCount( ent->client->ps.clientNum, TEAM_BLUE ); + counts[TEAM_RED] = TeamCount( ent->client->ps.clientNum, TEAM_RED ); + + // We allow a spread of two + if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) { + trap_SendServerCommand( ent->client->ps.clientNum, + "cp \"Red team has too many players.\n\"" ); + return; // ignore the request + } + if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) { + trap_SendServerCommand( ent->client->ps.clientNum, + "cp \"Blue team has too many players.\n\"" ); + return; // ignore the request + } + + // It's ok, the team we are switching to has less or same number of players + } + + } else { + // force them to spectators if there aren't any spots free + team = TEAM_FREE; + } + + // override decision if limiting the players + if ( (g_gametype.integer == GT_TOURNAMENT) + && level.numNonSpectatorClients >= 2 ) { + team = TEAM_SPECTATOR; + } else if ( g_maxGameClients.integer > 0 && + level.numNonSpectatorClients >= g_maxGameClients.integer ) { + team = TEAM_SPECTATOR; + } + + // + // decide if we will allow the change + // + oldTeam = client->sess.sessionTeam; + if ( team == oldTeam && team != TEAM_SPECTATOR ) { + return; + } + + // + // execute the team change + // + + // if the player was dead leave the body + if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + CopyToBodyQue(ent); + } + + // he starts at 'base' + client->pers.teamState.state = TEAM_BEGIN; + if ( oldTeam != TEAM_SPECTATOR ) { + // Kill him (makes sure he loses flags, etc) + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + player_die (ent, ent, ent, 100000, MOD_SUICIDE); + + } + // they go to the end of the line for tournements + if ( team == TEAM_SPECTATOR ) { + client->sess.spectatorTime = level.time; + } + + client->sess.sessionTeam = team; + client->sess.spectatorState = specState; + client->sess.spectatorClient = specClient; + + client->sess.teamLeader = qfalse; + if ( team == TEAM_RED || team == TEAM_BLUE ) { + teamLeader = TeamLeader( team ); + // if there is no team leader or the team leader is a bot and this client is not a bot + if ( teamLeader == -1 || ( !(g_entities[clientNum].r.svFlags & SVF_BOT) && (g_entities[teamLeader].r.svFlags & SVF_BOT) ) ) { + SetLeader( team, clientNum ); + } + } + // make sure there is a team leader on the team the player came from + if ( oldTeam == TEAM_RED || oldTeam == TEAM_BLUE ) { + CheckTeamLeader( oldTeam ); + } + + BroadcastTeamChange( client, oldTeam ); + + // get and distribute relevent paramters + ClientUserinfoChanged( clientNum ); + + ClientBegin( clientNum ); +} + +/* +================= +StopFollowing + +If the client being followed leaves the game, or you just want to drop +to free floating spectator mode +================= +*/ +void StopFollowing( gentity_t *ent ) { + ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; + ent->client->sess.sessionTeam = TEAM_SPECTATOR; + ent->client->sess.spectatorState = SPECTATOR_FREE; + ent->client->ps.pm_flags &= ~PMF_FOLLOW; + ent->r.svFlags &= ~SVF_BOT; + ent->client->ps.clientNum = ent - g_entities; +} + +/* +================= +Cmd_Team_f +================= +*/ +void Cmd_Team_f( gentity_t *ent ) { + int oldTeam; + char s[MAX_TOKEN_CHARS]; + + if ( trap_Argc() != 2 ) { + oldTeam = ent->client->sess.sessionTeam; + switch ( oldTeam ) { + case TEAM_BLUE: + trap_SendServerCommand( ent-g_entities, "print \"Blue team\n\"" ); + break; + case TEAM_RED: + trap_SendServerCommand( ent-g_entities, "print \"Red team\n\"" ); + break; + case TEAM_FREE: + trap_SendServerCommand( ent-g_entities, "print \"Free team\n\"" ); + break; + case TEAM_SPECTATOR: + trap_SendServerCommand( ent-g_entities, "print \"Spectator team\n\"" ); + break; + } + return; + } + + if ( ent->client->switchTeamTime > level.time ) { + trap_SendServerCommand( ent-g_entities, "print \"May not switch teams more than once per 5 seconds.\n\"" ); + return; + } + + + //PKMOD - Ergodic 02/11/04 - if cvar is set then do not count joining spectators as a loss + if ( g_pkatourneyrules.integer == 0 ) { + // if they are playing a tournement game, count as a loss + if ( (g_gametype.integer == GT_TOURNAMENT ) + && ent->client->sess.sessionTeam == TEAM_FREE ) { + ent->client->sess.losses++; + } + } + + trap_Argv( 1, s, sizeof( s ) ); + + SetTeam( ent, s ); + + ent->client->switchTeamTime = level.time + 5000; +} + + +/* +================= +Cmd_Follow_f +================= +*/ +void Cmd_Follow_f( gentity_t *ent ) { + int i; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc() != 2 ) { + if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { + StopFollowing( ent ); + } + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + i = ClientNumberFromString( ent, arg ); + if ( i == -1 ) { + return; + } + + // can't follow self + if ( &level.clients[ i ] == ent->client ) { + return; + } + + // can't follow another spectator + if ( level.clients[ i ].sess.sessionTeam == TEAM_SPECTATOR ) { + return; + } + + + //PKMOD - Ergodic 02/11/04 - if cvar is set then do not count joining spectators as a loss + if ( g_pkatourneyrules.integer == 0 ) { + // if they are playing a tournement game, count as a loss + if ( (g_gametype.integer == GT_TOURNAMENT ) + && ent->client->sess.sessionTeam == TEAM_FREE ) { + ent->client->sess.losses++; + } + } + + // first set them to spectator + if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { + SetTeam( ent, "spectator" ); + } + + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + ent->client->sess.spectatorClient = i; +} + +/* +================= +Cmd_FollowCycle_f +================= +*/ +void Cmd_FollowCycle_f( gentity_t *ent, int dir ) { + int clientnum; + int original; + + //PKMOD - Ergodic 02/11/04 - if cvar is set then do not count joining spectators as a loss + if ( g_pkatourneyrules.integer == 0 ) { + // if they are playing a tournement game, count as a loss + if ( (g_gametype.integer == GT_TOURNAMENT ) + && ent->client->sess.sessionTeam == TEAM_FREE ) { + ent->client->sess.losses++; + } + } + + // first set them to spectator + if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) { + SetTeam( ent, "spectator" ); + } + + if ( dir != 1 && dir != -1 ) { + G_Error( "Cmd_FollowCycle_f: bad dir %i", dir ); + } + + clientnum = ent->client->sess.spectatorClient; + original = clientnum; + do { + clientnum += dir; + if ( clientnum >= level.maxclients ) { + clientnum = 0; + } + if ( clientnum < 0 ) { + clientnum = level.maxclients - 1; + } + + // can only follow connected clients + if ( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) { + continue; + } + + // can't follow another spectator + if ( level.clients[ clientnum ].sess.sessionTeam == TEAM_SPECTATOR ) { + continue; + } + + // this is good, we can use it + ent->client->sess.spectatorClient = clientnum; + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + return; + } while ( clientnum != original ); + + // leave it where it was +} + + +/* +================== +G_Say +================== +*/ + +static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message ) { + if (!other) { + return; + } + if (!other->inuse) { + return; + } + if (!other->client) { + return; + } + if ( other->client->pers.connected != CON_CONNECTED ) { + return; + } + + //PKMOD - Ergodic 03/29/02 - permit say_team from owner to Private Bot + //PKMOD - Ergodic 11/13/02 - add SAY_TEAMBYID for PKA UI Orders + //PKMOD - Ergodic 12/16/02 - change logic around to Private Bot can receive orders +// if ( ( mode != SAY_TEAM && mode != SAY_TEAMBYID ) || !PrivateBotOwnerTeam( ent, other ) ) { + if ( ( mode == SAY_TEAM ) || ( mode == SAY_TEAMBYID ) ) { + if ( !PrivateBotOwnerTeam( ent, other ) ) { + //here if not a Private Bot, Check for teammate + //PKMOD - Ergodic 11/13/02 - debug command (inactive) + //Com_Printf("G_SayTo - in IF if clause\n" ); + if ( !OnSameTeam(ent, other) ) { + return; + } + } + + //PKMOD - Ergodic 02/11/04 - if cvar is set then allow for spectators chatting to be + // seen by the players + if ( g_pkatourneychat.integer == 0 ) { + // no chatting to players in tournements + if ( (g_gametype.integer == GT_TOURNAMENT ) + && other->client->sess.sessionTeam == TEAM_FREE + && ent->client->sess.sessionTeam != TEAM_FREE ) { + return; + } + } + } + + trap_SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\"", + ( mode == SAY_TEAM || mode == SAY_TEAMBYID ) ? "tchat" : "chat", + name, Q_COLOR_ESCAPE, color, message)); + + //PKMOD - Ergodic 11/13/02 - debug command (inactive) + //Com_Printf("G_SayTo - server command >%s \"%s%c%c%s\"<\n", + // ( mode == SAY_TEAM || mode == SAY_TEAMBYID ) ? "tchat" : "chat", + // name, Q_COLOR_ESCAPE, color, message ); +} + +#define EC "\x19" + +void G_Say( gentity_t *ent, gentity_t *target, int mode, const char *chatText ) { + int j; + gentity_t *other; + int color; + char name[64]; + // don't let text be too long for malicious reasons + char text[MAX_SAY_TEXT]; + char location[64]; + + //PKMOD - Ergodic 12/16/02 - Let's not do this since Private Bot will wuse SAY_TEAM COMMANDS +// if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { +// mode = SAY_ALL; +// } + + switch ( mode ) { + default: + case SAY_ALL: + G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, chatText ); + Com_sprintf (name, sizeof(name), "%s%c%c"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + color = COLOR_GREEN; + break; + case SAY_TEAMBYID: + //PKMOD - Ergodic 11/13/02 - debug say_teambyid (inactive) + //Com_Printf( "G_Say - say_teambyid: %s: %s\n", ent->client->pers.netname, chatText ); + G_LogPrintf( "say_teambyid: %s: %s\n", ent->client->pers.netname, chatText ); + if (Team_GetLocationMsg(ent, location, sizeof(location))) + Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC") (%s)"EC": ", + ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location); + else + Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ", + ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + color = COLOR_CYAN; + break; + case SAY_TEAM: + G_LogPrintf( "sayteam: %s: %s\n", ent->client->pers.netname, chatText ); + if (Team_GetLocationMsg(ent, location, sizeof(location))) + Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC") (%s)"EC": ", + ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location); + else + Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ", + ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + color = COLOR_CYAN; + break; + case SAY_TELL: + if (target && g_gametype.integer >= GT_TEAM && + target->client->sess.sessionTeam == ent->client->sess.sessionTeam && + Team_GetLocationMsg(ent, location, sizeof(location))) + Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"] (%s)"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location ); + else + Com_sprintf (name, sizeof(name), EC"[%s%c%c"EC"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + color = COLOR_MAGENTA; + break; + } + + Q_strncpyz( text, chatText, sizeof(text) ); + + if ( target ) { + G_SayTo( ent, target, mode, color, name, text ); + return; + } + + // echo the text to the console + if ( g_dedicated.integer ) { + G_Printf( "%s%s\n", name, text); + } + + // send it to all the apropriate clients + for (j = 0; j < level.maxclients; j++) { + other = &g_entities[j]; + G_SayTo( ent, other, mode, color, name, text ); + } +} + + +/* +================== +Cmd_Say_f +================== +*/ +static void Cmd_Say_f( gentity_t *ent, int mode, qboolean arg0 ) { + char *p; + + if ( trap_Argc () < 2 && !arg0 ) { + return; + } + + if (arg0) + { + p = ConcatArgs( 0 ); + } + else + { + p = ConcatArgs( 1 ); + } + + G_Say( ent, NULL, mode, p ); +} + +/* +================== +Cmd_Tell_f +PKMOD - Ergodic 11/13/02 - add ClientNum addressing to say_team +================== +*/ +static void Cmd_say_teambyid_f( gentity_t *ent ) { + int targetNum; + gentity_t *target; + char *p; + char arg[MAX_TOKEN_CHARS]; + char nameandtext[MAX_STRING_TOKENS]; + + if ( trap_Argc () < 2 ) { + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + + //PKMOD - Ergodic 11/13/02 - debug say_teambyid (inactive) + //Com_Printf( "Cmd_say_teambyid_f - arg(1)>%s<\n", arg[0] ); + + targetNum = atoi( arg ); + if ( targetNum < 0 || targetNum >= level.maxclients ) { + return; + } + + target = &g_entities[targetNum]; + if ( !target || !target->inuse || !target->client ) { + return; + } + + //store the target's name + Q_strncpyz( nameandtext, target->client->pers.netname, sizeof( nameandtext ) ); + + //store the message text + p = ConcatArgs( 2 ); + + //bui,d the name and message pair + strcat( nameandtext, " " ); + strcat( nameandtext, p ); + + + //PKMOD - Ergodic 11/13/02 - debug say_teambyid (inactive) + //Com_Printf( "Cmd_say_teambyid_f - %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, nameandtext ); + + //PKMOD - Ergodic 11/13/02 - logging is done in G_Say + //G_LogPrintf( "say_teambyid: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, nameandtext ); + G_Say( ent, target, SAY_TEAMBYID, nameandtext ); + // don't tell to the player self if it was already directed to this player + // also don't send the chat back to a bot + if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { + G_Say( ent, ent, SAY_TEAMBYID, nameandtext ); + } +} + + + +/* +================== +Cmd_Tell_f +================== +*/ +static void Cmd_Tell_f( gentity_t *ent ) { + int targetNum; + gentity_t *target; + char *p; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc () < 2 ) { + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + targetNum = atoi( arg ); + if ( targetNum < 0 || targetNum >= level.maxclients ) { + return; + } + + target = &g_entities[targetNum]; + if ( !target || !target->inuse || !target->client ) { + return; + } + + p = ConcatArgs( 2 ); + + G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p ); + G_Say( ent, target, SAY_TELL, p ); + // don't tell to the player self if it was already directed to this player + // also don't send the chat back to a bot + if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { + G_Say( ent, ent, SAY_TELL, p ); + } +} + + +static void G_VoiceTo( gentity_t *ent, gentity_t *other, int mode, const char *id, qboolean voiceonly ) { + int color; + char *cmd; + + if (!other) { + return; + } + if (!other->inuse) { + return; + } + if (!other->client) { + return; + } + if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { + return; + } + // no chatting to players in tournements + if ( (g_gametype.integer == GT_TOURNAMENT )) { + return; + } + + if (mode == SAY_TEAM) { + color = COLOR_CYAN; + cmd = "vtchat"; + } + else if (mode == SAY_TELL) { + color = COLOR_MAGENTA; + cmd = "vtell"; + } + else { + color = COLOR_GREEN; + cmd = "vchat"; + } + + trap_SendServerCommand( other-g_entities, va("%s %d %d %d %s", cmd, voiceonly, ent->s.number, color, id)); +} + +void G_Voice( gentity_t *ent, gentity_t *target, int mode, const char *id, qboolean voiceonly ) { + int j; + gentity_t *other; + + if ( g_gametype.integer < GT_TEAM && mode == SAY_TEAM ) { + mode = SAY_ALL; + } + + if ( target ) { + G_VoiceTo( ent, target, mode, id, voiceonly ); + return; + } + + // echo the text to the console + if ( g_dedicated.integer ) { + G_Printf( "voice: %s %s\n", ent->client->pers.netname, id); + } + + // send it to all the apropriate clients + for (j = 0; j < level.maxclients; j++) { + other = &g_entities[j]; + G_VoiceTo( ent, other, mode, id, voiceonly ); + } +} + +/* +================== +Cmd_Voice_f +================== +*/ +static void Cmd_Voice_f( gentity_t *ent, int mode, qboolean arg0, qboolean voiceonly ) { + char *p; + + if ( trap_Argc () < 2 && !arg0 ) { + return; + } + + if (arg0) + { + p = ConcatArgs( 0 ); + } + else + { + p = ConcatArgs( 1 ); + } + + G_Voice( ent, NULL, mode, p, voiceonly ); +} + +/* +================== +Cmd_VoiceTell_f +================== +*/ +static void Cmd_VoiceTell_f( gentity_t *ent, qboolean voiceonly ) { + int targetNum; + gentity_t *target; + char *id; + char arg[MAX_TOKEN_CHARS]; + + if ( trap_Argc () < 2 ) { + return; + } + + trap_Argv( 1, arg, sizeof( arg ) ); + targetNum = atoi( arg ); + if ( targetNum < 0 || targetNum >= level.maxclients ) { + return; + } + + target = &g_entities[targetNum]; + if ( !target || !target->inuse || !target->client ) { + return; + } + + id = ConcatArgs( 2 ); + + G_LogPrintf( "vtell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, id ); + G_Voice( ent, target, SAY_TELL, id, voiceonly ); + // don't tell to the player self if it was already directed to this player + // also don't send the chat back to a bot + if ( ent != target && !(ent->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent, SAY_TELL, id, voiceonly ); + } +} + + +/* +================== +Cmd_VoiceTaunt_f +================== +*/ +static void Cmd_VoiceTaunt_f( gentity_t *ent ) { + gentity_t *who; + int i; + + if (!ent->client) { + return; + } + + // insult someone who just killed you + if (ent->enemy && ent->enemy->client && ent->enemy->client->lastkilled_client == ent->s.number) { + // i am a dead corpse + if (!(ent->enemy->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent->enemy, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); + } + if (!(ent->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse ); + } + ent->enemy = NULL; + return; + } + // insult someone you just killed + if (ent->client->lastkilled_client >= 0 && ent->client->lastkilled_client != ent->s.number) { + who = g_entities + ent->client->lastkilled_client; + if (who->client) { + // who is the person I just killed + if (who->client->lasthurt_mod == MOD_GAUNTLET) { + if (!(who->r.svFlags & SVF_BOT)) { + G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); // and I killed them with a gauntlet + } + if (!(ent->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse ); + } + } else { + if (!(who->r.svFlags & SVF_BOT)) { + G_Voice( ent, who, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); // and I killed them with something else + } + if (!(ent->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent, SAY_TELL, VOICECHAT_KILLINSULT, qfalse ); + } + } + ent->client->lastkilled_client = -1; + return; + } + } + + if (g_gametype.integer >= GT_TEAM) { + // praise a team mate who just got a reward + for(i = 0; i < MAX_CLIENTS; i++) { + who = g_entities + i; + if (who->client && who != ent && who->client->sess.sessionTeam == ent->client->sess.sessionTeam) { + if (who->client->rewardTime > level.time) { + if (!(who->r.svFlags & SVF_BOT)) { + G_Voice( ent, who, SAY_TELL, VOICECHAT_PRAISE, qfalse ); + } + if (!(ent->r.svFlags & SVF_BOT)) { + G_Voice( ent, ent, SAY_TELL, VOICECHAT_PRAISE, qfalse ); + } + return; + } + } + } + } + + // just say something + G_Voice( ent, NULL, SAY_ALL, VOICECHAT_TAUNT, qfalse ); +} + + + +static char *gc_orders[] = { + "hold your position", + "hold this position", + "come here", + "cover me", + "guard location", + "search and destroy", + "report" +}; + +void Cmd_GameCommand_f( gentity_t *ent ) { + int player; + int order; + char str[MAX_TOKEN_CHARS]; + + trap_Argv( 1, str, sizeof( str ) ); + player = atoi( str ); + trap_Argv( 2, str, sizeof( str ) ); + order = atoi( str ); + + if ( player < 0 || player >= MAX_CLIENTS ) { + return; + } + if ( order < 0 || order > sizeof(gc_orders)/sizeof(char *) ) { + return; + } + G_Say( ent, &g_entities[player], SAY_TELL, gc_orders[order] ); + G_Say( ent, ent, SAY_TELL, gc_orders[order] ); +} + +/* +================== +Cmd_Where_f +================== +*/ +void Cmd_Where_f( gentity_t *ent ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) ); +} + +static const char *gameNames[] = { + "Free For All", + "Tournament", + "Single Player", + "Team Deathmatch", + "Capture the Flag", + "One Flag CTF", + "Overload", + "Harvester" +}; + +/* +================== +Cmd_CallVote_f +================== +*/ +void Cmd_CallVote_f( gentity_t *ent ) { + int i; + char arg1[MAX_STRING_TOKENS]; + char arg2[MAX_STRING_TOKENS]; + + if ( !g_allowVote.integer ) { + trap_SendServerCommand( ent-g_entities, "print \"Voting not allowed here.\n\"" ); + return; + } + + if ( level.voteTime ) { + trap_SendServerCommand( ent-g_entities, "print \"A vote is already in progress.\n\"" ); + return; + } + if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) { + trap_SendServerCommand( ent-g_entities, "print \"You have called the maximum number of votes.\n\"" ); + return; + } + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" ); + return; + } + + // make sure it is a valid command to vote on + trap_Argv( 1, arg1, sizeof( arg1 ) ); + trap_Argv( 2, arg2, sizeof( arg2 ) ); + + if( strchr( arg1, ';' ) || strchr( arg2, ';' ) ) { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); + return; + } + + if ( !Q_stricmp( arg1, "map_restart" ) ) { + } else if ( !Q_stricmp( arg1, "nextmap" ) ) { + } else if ( !Q_stricmp( arg1, "map" ) ) { + } else if ( !Q_stricmp( arg1, "g_gametype" ) ) { + } else if ( !Q_stricmp( arg1, "kick" ) ) { + } else if ( !Q_stricmp( arg1, "clientkick" ) ) { + } else if ( !Q_stricmp( arg1, "g_doWarmup" ) ) { + } else if ( !Q_stricmp( arg1, "timelimit" ) ) { + } else if ( !Q_stricmp( arg1, "fraglimit" ) ) { + } else { + trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); + trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map , g_gametype , kick , clientkick , g_doWarmup, timelimit