diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..081c55d --- /dev/null +++ b/BUGS @@ -0,0 +1,4 @@ +- On Solaris/SPARC gcc optimizations higher than -O0 currently lead + to a segfault + +https://bugzilla.icculus.org/ for more. diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000..98443f3 --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,281 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e9c0d95 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,3150 @@ +2008-04-04 Various contributors + + Solaris fixes + + Replace vsprintf function in bg_lib.c with vsnprintf implementation started by Patrick Powell + + Compile bg_* files separately for each game module, as originally intended + + Write q3config_server.cfg for the server, to avoid reseting client variables + after running a dedicated server + + Split image decoders into their own files + + OS X build updates for most compatibility + + Slackbuild + + Detect available resolutions and offer them in the in game menus + + A few botlib fixes + + Fix poppy captured audio when recording videos + + Extend console logging on crash errors + + Merge *BSD platform definitions in q_platform.h + + IRIX support + + Remove all the old bat/sh QVM building scripts + + Make master server used client configurable (cl_master) + + Fix to QVM compilation on big endian architectures + + OpenBSD support + + Autocomplete key names + + Don't build client command completion on the dedicated server + + Don't apply colour escape chars on input fields + + Rewrite of the win32 dedicated console + + Improved Makefile startup time + + Build dedicated server binary on Windows + + Bump Q3_VERSION to 1.35 + + Replacement of platform specific backends with a generic SDL one + + Merge win_net.c and unix_net.c to net_ip.c + + Demote input related console information to developer only so that it doesn't + spam the console every time input settings are changed + + PNG texture support + + Cleanup of tabulation in R_LoadImage + + Fixes to console scrolling + + New x86_64 vm that doesn't use gas + + Early out AABB collision optimisation + + Generate QVM dependicies in a better way + + Build process is quieter + + Replace horrendously long list of Makefile build rules with set of inference rules + + Allow CC to be overridden externally to the Makefile + + Move storage of console history from a cvar to a file in order to alleviate + security concerns + + Fix bug where transparent surfaces wouldn't draw over skyboxes + + Add input sanitising to various sound playing functions called from mods + + Explicitly set OpenAL distance attenuation model + + Increased the number of registers used for the opStack in the PPC vm from + 12 to 16 + + Fix endian issue in MDR loading + + Add cURL support for HTTP/FTP downloading + + Disable video command when not playing back a demo + + Print the SVN version string in Com_Init() + + OpenAL device enumeration support + + Fix 100% CPU usage on idle dedicated servers + + Windows home directory support + + Improve correctness of AVI files created by video command + + Better SDL joystick support + + sv_minRate + + [cl|sv]_packetdelay + + Various security fixes + + Fix JIT compiler code execution on NX-protected win32 OS + + Fix r_overBrightBits variable getting ignored on Linux + + cl_guid for semi-reliable server authentication + + Anisotropic texture filtering + + Video export doesn't crap out with sv_pure 1 anymore + + Video export doesn't crap out when writing > 2Gb files anymore + + Fix to a bug where servers with long uptimes (~27 days) would consume 100% + CPU if the running game did not set the nextmap cvar + + Some OSes no longer requires a vid_restart when changing r_fullscreen + +2006-01-24 Various contributors + + Persistent console history + + Added code to sleep a bit when q3 has no focus and sleep a lot when it's + minimised (SDL only) + + Cull excess speaker entities when using OpenAL + + Fix the operation of the delete key in *nix + + Only check the checksum on baseq3 pak0.pk3 + + Overhaul of console autocompletion + - No longer does weird stuff like move the cursor inappropriately + - Autocomplete works with compound commands + - Special autocomplete on some commands e.g. \map, \demo + - Removed various hacks used to counter the original autocomplete code + + Fixed the ability to disable Ogg Vorbis + + s/i686/i586/ - see bug #2578 + + Some sloppily coded mods call the Q3 sound API with NaNs -- sanitise this + + Removed advertising clause from BSD license as per mailing list discussion + + "make distclean" now does what you'd expect + + "make clean toolsclean" now does what "make distclean" did before + + GPL MD4 implementation + +2006-01-16 Various contributors + + Move code/unix/Makefile to ./Makefile + + x86 OS X support + + "quake3" shell script as shipped with 1.32 (on linux) no longer needed + + Ogg codec support from Joerg Dietrich + + Fix to the gcc4/-O0 x86 JIT compiler bug + + Up the defaults for zone and hunk memory since some mods (UT) have large + memory requirements that will have increased versus 1.32b due to some of the + alignment fixes + + Dependency generation for the .asm files + + Remove FS_SetRestrictions + + Add FS_CheckPak0 for better error messages where dumb users are involved + + Added cl_autoRecordDemo, which when enabled automatically records a new demo + on each map change + + Only display the g_synchronousClients warning when it's appropriate + + Remove custom memcpy/memset code + + AVI video output + - Uses motion jpeg codec by default + - Use cl_aviFrameRate to set a framerate + - \video [filename] to start capture + - \stopvideo to stop capture + - Audio capture is a bit ropey + + General Makefile improvements + + Support for MinGW cross compilation + + NetBSD support from optical + + x86_64 JIT bytecode compiler no longer disabled by default + + msvc project files updated and moved to win32/msvc + + Various alignment fixes + + Solaris (x86 and sparc) support from Vincent S. Cojot + + Fixed Altivec-based mesh rendering + + Ditch Mac OS 9 support + + Added a Makefile option USE_LOCAL_HEADERS which can be disabled to use system + headers if desired + + Detection of Altivec on Mac OS X + + SMP support with sdl_glimp.c on Mac OS X. + + Add "very high quality" option (patch from Pascal de Bruijn) + + Support for RIFF files with zero length data chunks (yes they exist, and yes, + they're legal) + + Support for ccache. If you want it, add USE_CCACHE=1 to Makefile.local + + Mac OS X now uses SDL backend, all Objective C removed + + Partial implementation of FS_Seek for files in pk3s + + Implementation of r_dlightBacks from Shane Isley + + OpenAL support, from BlackAura aka Stuart Dalton + + An abstract codec system, simplifying support for new formats + + Ignore in_dgamouse setting if dga isn't available + + Removed hard coded mouse acceleration in *nix input code + + Basically rewrote the lcc Makefile to be more sane + + Removed various bits of lcc that weren't built/needed + + General portability improvements + + Various variables added that aid packaging, from vapier + + Centralise architecture defines in q_platform.h + + Replaced a bunch of inline and __inline with ID_INLINE + + Replaced a bunch of __i386__ with id386 + + General tidy up of asm preprocessor decisions + + Removed C_ONLY from the dedicated server build + + Removed rule to build C++ (for splines) from the Makefile + + General decrufting + + Split USE_SDL into USE_SDL_VIDEO and USE_SDL_AUDIO + + Various assorted bug fixes + +2005-10-29 Various contributors + + nasm syntax asm ported to gas + + Disabled-by-default MD4 support + + cons build system removed + + Better FreeBSD support + + Makefile generates dependencies + + Some SDL sound tweaks + + qvm build tools and qvms are now built with the rest of the binaries + + q3asm-turbo from Phaethon + + Moved various displaced c and h files into more appropriate places + + A shitload (can I say shit?) of bug fixes -- see the svn log for details + +2005-09-22 Tim Angus + + MinGW port + +2005-09-?? + + SDL Stuff (icculus) + + x86_64 (ludwig von angstenheimer) + + patches from a cast of thousands + +2004-05-22 Timothee Besset + + updated the xcode project from Apple's version + now with the latest vm_ppc code + +2004-05-21 Timothee Besset + + fixed the Linux build to compile again on sid (glext.h and gcc3 warnings) + + 2 weeks ago, hacked up the source to compile on panther / xcode 1.1 + several cleanups were needed, and VM support seems broke (hangs or crashes) + + got altivec optimisations from Apple (Kenneth Dyke) + merged back in + + looks like with the new code merge the VM support is back in and working + +2003-09-15 Timothee Besset + + import Q3 java master code, cleanups on monster + +2003-08-31 Timothee Besset + + loki_setup hell + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=626 + http://zerowing.idsoftware.com/linux/q3a/index.html#glibc + text mode installer in loki_setup image built on Mandrake 7.2 crashes on + some glibc 2.3 systems such as RH9 etc. need to move to a different + version of the installer, and update old installers to keep them still + 'installing' moving to build the setup binaries on Debian Woody systems + (glibc 2.2, text mode installer will no longer work on 2.1 systems) hacked + together a new setup, using setup tree from RTCW. would need a complete + revamp if a new full setup with new binaries is needed + +2003-07-17 Timothee Besset + + new cvsreport, testing per-module config + +2003-01-19 Timothee Besset + + building on both gcc 2.x and 3.x + added conf modules to check gcc version + ccache support + +2003-01-13 Timothee Besset + + tweaking around for gcc 3.x build + edit Conscript to change the compiler + +2002-12-16 Timothee Besset + + added pbEmit class to auth code, emit CD keys to local PB master + +2002-11-14 Timothee Besset + + up to latest makeself.sh + + add both quake3.x86 and quake3-smp.x86 to setup + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=573 + console setup crash / glibc 2.3 (Debian Sid) + investigated, put together a workaround + +2002-11-5 Timothee Besset + + Linux building both smp and non-smp again. Will have to put both in setup + + added in_subframe to toggle X subframe event handling + + reworked the timing code to be more reliable + + cleaned up dgamouse/in_mouse code, removed unnecessary dgamouse var + + made the mouse grabbing an in_nograb cvar, no longer a compile time option + in_nograb 1 force in_dgamouse 0 and r_fullscreen 0 (any of those two will b0rk) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=565 + mouse issues on Suze 8.1 - related to subframe event timing + added code chunk to detect broken X timing and disable subframe + + tweaked the subframe/X bug workaround to be less paranoid + +2002-10-28 Timothee Besset + + no longer blocking demo recording if g_synchronous clients != 0 + only sending out a warning (everyone does g_sync 1 ; record ; g_sync 0) + +2002-10-21 Timothee Besset + + building final mod sdk setups (added lcc bins, added link to q3asm-turbo in readme) + +2002-10-8 Timothee Besset + + quickfix cl_maxpackets > 125 brings back to 100 + +1.32 release --- + +2002-10-7 Timothee Besset + + made the 'demo' command case-insensitive on extension match (it was confused by demo FOUR.DM_68) + + mouse wheel scrolling with in_mouse 1 + window mode was not working, fixed (DI didn't catch) + + removing on-the-fly pk3 build from Linux setup, using the finalized ones now + added 'pk3' option to cons for toggle of pk3 building + +2002-10-5 Timothee Besset + + updated win32 mod sdk (in win32/mod-sdk-setup) + added q3asm and lcc source + updated the .bat to build VMs + +2002-10-3 Timothee Besset + + linux mod sdk, wrote the bulk of the scripts + +2002-9-30 Timothee Besset + + ATVI Quake 3 1.32 Patch #9 + rolling back to the way it was before, leaving 1v1 force vote exploit, the fix was worse than the bug + from comment on bug #9 in tracker: + + actually the fix is worse than the original bug + + after the fix, voting when you are alone on the server was no longer working + it was kinda intended in the fix, that you would have to be at least two to pass a vote .. but + it is an oversight. + + calling a vote in a 1v1 game against a bot fails immediately + (calling a vote in any situation where there's only 1 live player fails) + + Say a server's running some lame custom map that you have but a friend doesn't. You can't go + on the server and change it to the map you want to play, so he ends up having to auto-dl it at + 8K a second just so you can switch from it. + + This particular 2 clients, vote / quit exploit would involve too many changes to fix properly. + I am reverting back to the old version, and leaving as WNF + +2002-9-29 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=50 + added Wheel support to the DirectInput code IN_DIMouse (in_mouse 1) + tweaked the Wheel mouse reading for in_mouse -1 (old win32 input code) + handle correctly when zDelta is > 120 + provide a in_logitechbug cvar to handle buggy Logitech MouseWare driver sending wheel events twice + +2002-9-26 Timothee Besset + + ATVI Quake 3 1.32 Patch #38 + adding trap_SetPbClStatus, reliably checks for PB presence before enabling PB in UI + +2002-9-25 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=551 + SVF_CLIENTMASK, fixed a typo + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=555 + pushed cl_maxpackets upper limit to 125 (from 100) per CPMA Arqon's request + +2002-9-24 Timothee Besset + + ATVI Quake 3 1.32 Patch #33 + PB reporting sv_paused cvar hacked, fixed SV_CheckPaused to use a Cvar_Set + + ATVI Quake 3 1.32 Patch #24 + added [skipnotify] from RTCW, use to display in the console only, but not on client screen + (also fixes pb_msgprefix and pb_sv_msgprefix) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=553 + using correct error message if listen server starting as cl_punkbuster 0 sv_punkbuster 1 + + ATVI Quake 3 1.32 Patch #35 + text auto wrap in UI code was eating the last word if it was wrapping + fixed in Q3 and TA UI (this bug could have affected the server print message also) + + some updates to the win32 cons post-build process + +2002-9-21 Timothee Besset + + adding bspc cons build script + +2002-9-19 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=552 + disconnect reason is transmitted in the disconnect command and processed into com_errorMessage + (similar to RTCW behaviour) + added UI for com_errorMessage cvar in baseq3/, if client is kicked/dropped/disconnected for whatever reason + (this is already functional in TA) + + ATVI Quake 3 1.32 Patch #9 + failing vote if there's only one voting client (fixes exploit on 2-player server where one player votes then disconnects, forcing vote to pass) + + ATVI Quake 3 1.32 Patch #5 + removed the userInfoChanged message (was a debugging leftover) + + ATVI Quake 3 1.32 Patch #18 + rcon was not properly fixed yet, this only showed up for PB commands + changed the rcon parsing again to be more reliable + +2002-9-18 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=549 + the demo command has a list of compatible protocols, it will loop through 66 67 68 + you can do '/demo four' and it will try four.dm_66 four.dm_67 four.dm_68 + or you can explicitely give a '/demo demoname.dm_??' + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=551 + added SVF_CLIENTMASK (0x00000002), works only with <= 32 players + set bitmask of players to which send entity + +2002-9-17 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=550 + rcon bug fix + + some scons updates for win32 (post build) + + 1.32rc2 + +2002-9-06 Timothee Besset + + updated completely the setup system: + fixed cons stuff to build setup with cons -- release setup + working from new setup codebase with some custom patches: + https://bugzilla.icculus.org/show_bug.cgi?id=52 + https://bugzilla.icculus.org/show_bug.cgi?id=53 + checked that BSD support was still in (brandelfing and symlinks) .. will have to get tester feedback + bumped version to 1.32rc1 + TODO: update the windows .VCT (standalone setup and auto-update) + +2002-9-04 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + backport from RTCW 1.4 code + rcon commands where sent after being tokenized and rebuilt + that was breaking any quoting, for instance 'rcon g_motd "hooka pooka"' + added Cmd_Cmd() to retrieve the un-tokenized command and transmit as is on both ends + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=542 + b0rked text wrapping in connect screen + was a missing sizeScale in q3_ui/, and a bad param in ui/ + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=540 + backport fix to pk3 reordering, happens when clearing the references, bad order from connection may break stuff + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=527 + TA ui/, quickfix to netSource (mod stuff, doesn't affect TA) + + cleaned up broken old DO_WIN32 stuff in cons scripts + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=526 + typo in models2.shader + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=443 + Linux client: sub-frame timing of key/mouse events + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=453 + added mousewheel support: wheel to scroll, ctrl+wheel to scroll faster, shift+wheel to scroll history + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=545 + bumped server count to 4096 + + keep around: __asm__ __volatile__ ("int $0x03"); + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=516 + moved screenshots to backend with a new RC_SCREENSHOT render command + fixes the r_smp 1 garbled screenshots + +2002-8-29 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=539 + new VM code from Raven's Sof2 + + cons / qvmtools build system fixes + + had to get a new qe3.ico again (resource compiler error) + http://vasin.hypermart.net/eei.htm + + updated, basic testing on win32, merging back in trunk + + merged bug-539 branch back into trunk, officialize the new VM code + +2002-8-26 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=472 + linux client: handle ctrl+space situations (could leave space locked on + space not working with ctrl on) + + update the build system, build q3lcc and q3asm etc. on demand + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=62 + fixed invisible players/entities + +2002-8-23 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=536 + fixing donedl being ignored after autodl if map_restart'ed (propagate from RTCW) + ignoring multiple map_restart (propagated from RTCW) + + reworked the server 'client text ignored' message to only trigger when there's actually a message that doesn't get to the game VM + +2002-8-18 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=528 + ydnar: reorg bits in the drawsurf sort index, push MAX_SHADERS to 2^12 + + commented out some debug stuff in java auth server + + added FAQ item with Linux & BSD patch to handle broadcast on multiple interfaces + +2002-8-15 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=534 + fixing rcon being broken on NT/XP with > 23 days uptime (or so) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=525 + changed the rcon buffer size to avoid overflows and dropping part of the message + +2002-8-14 Timothee Besset + + hacked in some experimental win32 stuff to the cons files + (win32 recognition and pk3 installs .. very very experimental but I needed it for win32 dev) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=521 + ui/ and q3_ui/ : added text auto wrapping in the connection screen drawing (server message) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=531 + removed the MPlayer stuff from the server browsers + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=505 + enabled back the ignore if protocol is != (fixes Wolf servers showing in browser) + +2002-8-10 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 + propagated IP banning fix from RTCW + +2002-8-08 Timothee Besset + + propagate additional sv_lanForceRate fix from RTCW + +2002-8-07 Timothee Besset + + added trap_FS_Seek + +2002-8-05 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=50 + fixed the DI mouse init procedure + +2002-8-05 Timothee Besset + + removed sv_allowanonymous, was dummy and polluting the serverinfo + (sv_allowanonymous was designed to flag wether server was public or not, but that's replaced by g_needpass) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=514 + sv_strictAuth (default 1): server variable to control wether strict CDKEY auth should be performed + this is required if you want reliable cl_guid on the server + extended the getIpAuthorize (server->auth message) syntax + sending the fs_game at all times (default 'baseq3'), dummy sv_allowAnonymous 0, strict auth flag + NOTE: 1.31 server on baseq3 sends a getIpAuthorize packet like: + processing packet: getIpAuthorize -1230824753 217.128.77.195 0 + the auth server will mistakenly read fs_game as '0' + + TAGGED the master / auth source as pre-1_32 + will need to go back to this to comment out all my debugging crap + +2002-8-04 Timothee Besset + + cleaned master server stuff, client was prompting master.quake3arena.com, + server was sending heartbeats to master3.idsoftware.com + both point to 192.246.40.56, unified to master.quake3arena.com + the MPlayer master, master.quake3world.com doesn't exist anymore, switched it to master.quake3arena.com + +2002-8-02 Timothee Besset + + added auth server source, reorganized + + auth server name / master key optionally set on command line for master and auth servers + + auth and master config in build system + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=524 + changed default GL driver from libGL.so to libGL.so.1 + see LSB 1.2 spec: http://www.linuxbase.org/spec/refspecs/LSB_1.2.0/gLSB/libgl.html + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=480 + applying the 'no cp command' experimental fix for beta phase + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=462 + backported from RTCW, fix to packet fragmenting emission + FIXME: there is some verbose code that we have to take out in the final version (grep for #462) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + backported from RTCW, don't get dropped if the server changes map while connecting (ignore outdated cp) + + PROTOCOL BUMPED TO 68 + +2002-8-01 Timothee Besset + + Linux: dedicated build was not setting up signal handler like the full client does + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=522 + SplashDamage bugfix, now clearing client gentity before GAME_INIT call (instead of after) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=498 + fixed NET_AdrToString to print the port as unsigned int (for ports > 1^^15, was showing negative) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=501 + maintain IP in userinfo sent to game + + checking in master server source + +2002-7-31 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=513 + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=506 + porting fix from RTCW codebase. client re-orders its pk3s to scan in the same order than the server + this eliminates several 'Invalid .PK3 file referenced' situations (caused by client not referencing the same thing as server) + + fixed border remnants in ta ui + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=517 + ERR_DROP if PB client off / server on conflict when starting local server + + quickfix to q3 ui / punkbuster detect in server browser + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=458 + code fix, no more taunt spam + + cons install of PB .so files + + correct MOD_KAMIKAZE and MOD_JUICED in TA games.log + +2002-7-29 Timothee Besset + + q3 ui: completed confirmation prompts and messages (added UI_ConfirmMenu_Style & UI_Message) + + ta ui: backported 'conditionalopen' from RTCW (conditionalopen ) + + ta ui: confirmation prompt for punkbuster enable/disable etc. + + added the win32 DLLs to pb/win32/ + +2002-7-28 Timothee Besset + + ta ui: sv_punkbuster in StartServer menu + + ta ui: added cl_punkbuster in server browser + + view filters are in a modal dialog + + new files: filter.menu menus.txt (pak3.pk3 updated) + + fix broken link in Linux FAQ + +2002-7-27 Timothee Besset + + ta ui: PB display in the browser, in its additional tab, with sorting + +2002-7-26 Timothee Besset + + PB UI: for baseq3/ AND missionpack/ + q3_ui: Punkbuster: Enable/Disable in server broswer (cl_punkbuster) + q3_ui: PB logo, PB Yes/No in browser (TODO: validate this to be working) + q3_ui: added sv_punkbuster toggle in start server menu + + automated building of the new PK3s, unix/Conscript-pk3 + +2002-7-25 Timothee Besset + + added PB build scripts on Linux, fixed the Linux build + +2002-7-12 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=511 + fixing re.SetColor crash for widescreen displays (q3dm11) + was calling to the renderer while not registered + +2002-6-19 Timothee Besset + + r_roundImagesDown 0 + map q3dm16 -> tr_image.c ResampleTexture crash + buffer overflow because of resample to 2048x.. + xian_q3dm12_leftwall4fin.jpg 1152x384 + bumped one buffer byte p1[1024] -> byte p1[2048], added a safe check + +2002-6-14 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 + propagate a renderer fix from RTCW. fixes a one-frame visual glitch when mod code + registers a shader after drawsurfaces are generated but before the frame is rendered + +2002-6-12 Timothee Besset + + added cons and pcons to unix/, updated the build script + +2002-5-24 Timothee Besset + towards a new Q3 release? + some bug fixes require protocol change, or mod code/mod interface change to be fixed properly + this is a biz decision, dunno yet if we are going to want a new protocol (probably not) + -> have to create a branch for the 1.31b, i.e. backwards compatible with 1.31 'Stable-1_31' + and put the 1.32 specific / protocol changes on trunk + no telling what will go in SOS in the end .. probably 1.32 + +2002-5-5 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=491 + adding a sv_lanForceRate (defaults to 1) to turn on/off server forcing rate of LAN clients + (only affects LAN dedicated clients - dedicated 1, default behaviour forces LAN clients to 99999 rate) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=470 + fixing potential overflows with cl_cdkey (propagated from RTCW) + + cons-based build system (imported from Wolf, was partly written for mod tools release already) + building with SMP on by default + + better #ifdef SMP handling ('disabled at compile time' message) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=494 + Q_vsnprintf for vsprintf calls in the core + not putting this in game code as we'd need a vsnprintf implementation in bg_lib.c + +2002-4-5 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=462 + taking out the fix which was found broken and incomplete + +2002-8-4 Timothee Besset + + adding NO_MOUSEGRAB define (select in the Makefile) + +2002-2-4 Timothee Besset + + applying Gareth's SMP patch + + count number of CPUs (Sys_ProcessorCount in unix_shared.c), default r_smp appropriately + + bumping version to 1.32 + + if XInitThreads fails, set r_smp to zero + +2002-28-2 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=462 + send potential remaining fragmented packets before sending a gamestate + +2002-26-2 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=455 + removed old libMesaVoodooGL.so loading code + Voodoo cards should use XF4/DRI, that load code was outdated and confusing people with broken OpenGL + +2002-16-1 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=441 + adding brandelf calls to the setup building process so that our binaries run on BSD + +2002-1-1 Timothee Besset + + updated FAQ with BSD info (bug #441) + + FAQ update on CLIENT_UNKNOWN_TO_AUTH + + FAQ update for proper strace usage + +2001-12-12 Timothee Besset + + Q3 1.31 point release + updating build_setup.sh to new pk3 files + (baseq3/pak7.pk3 missionpack/pak2.pk3) + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=395 + adding quake3.xpm icon, and modified the setup accordingly to put symlinks + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=390 + ignoring SIGTTIN SIGTTOU + +2001-06-12 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=402 + bug with full scene + +2001-04-12 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=398 + cg_bobup cheat protect + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=399 + fixed Setup > System > Driver info crash + + checked in code/spank.sh script, perform checksuming + +2001-18-09 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371 + propagating sound code fixes from Wolf to Q3 + +2001-11-08 Timothee Besset + + setup script was still broken, damn shell expansion + the exit code for Q3 was always zero instead of $? + propagating the fix to Wolf + +2001-11-04 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=382 + modified challenge code for motd to be truly random + +2001-10-31 Timothee Besset + Moved updated q3asm and lcc source at the toplevel, MissionPack/q3asm + and MissionPack/lcc + +2001-10-29 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=381 + build system is now functional + +2001-10-21 Timothee Besset + + updated Sys_LoadDll code on linux to search in the following order: + #1 current directory + #2 fs_homepath + #3 fs_basepath + this was needed to make mod developement easier + +2001-10-09 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=51 + the code to buffer the redirection was in there but disabled? (Com_Printf) + enabled it back + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=52 + connection issues / userinfo + client side fix, instead of sending 'connect ' packet + we now send 'connect ""' + +2001-10-08 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=371 + added a PROT_READ to the mmap call + this was needed to go around a bug in glibc i586 i686, memset doing read access + since the audio_fd is opened O_RDWD this is harmless to Q3 + +2001-10-07 Timothee Besset + + updating from SOS + S_WriteLinearBlastStereo16 C/asm is back in snd_mix.c (Graeme) + r_showtris r_shownormal cheat protections + + Sys_LoadDll changes: + removing -debug search when loading native dlls + changing the fatal aborts when not finding native from release only to debug only (was a misfeature) + used to search in cd_path which is bogus, now searching in pwd if basepath fails + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=275 + fixed r_fullbright not being cheat protected / was a CVAR_LATCHED|CVAR_CHEAT issue + +2001-09-06 Timothee Besset + + updated from SOS, some changes to qcommon/unzip.c (statics) + +2001-08-27 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=3 + Added some code in CL_InitDownloads to use FS_ComparePaks and print out information about server-referenced paks that are missing on the client. It is a first step, allows to get precise information about what can cause a connection to fail (typically when the user is sent back to the main screen). + +2001-08-22 Timothee Besset + + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=86 + fixed sound bug (with Graeme hints) + +2001-08-20 Timothee Besset + + made sure Sys_Printf doesn't get into an endless loop if logfile is on + fixed qconsole.log issues, +set logfile 1 +set fs_debug 1 was crashing (any OS) + fixed logfile 1 / ttycon 1 issue, didn't exit properly (same endless looping) + also fixes an issue reported by q3f team + + changed rcon commands from Com_DPrintf to Com_Printf so that they show up in the console + (with IP information) + +2001-08-19 Timothee Besset + + fixed https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=91 + (autodownload toggle in q3 ui) + + fixed https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=76 + g_password issue + + fixed https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=93 + cheat protecting r_lodCurveError + + wontfix https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=92 + +2001-08-18 Timothee Besset + + more fixes to the 7-button mouse code (linux only) + + updated faq about gamma slider + + added "servers don't show up in ingame browser" to faq + + added Alt+Enter toggle for fullscreen/windowed (linux) + +2001-08-16 Timothee Besset + reconfiguring CVS repository to give access to Gareth + + testin gareth's access + +2001-08-03 Timothee Besset + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=85 + fixes in the setup code for older bash versions + +2001-08-02 Timothee Besset + * commented out assembly implementation of S_WriteLinearBlastStereo16, using modified C implementation from Zaphod + need to check performance: https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=88 + * finished const declarations in CG_Trace calls, was needed in pmove_t declaration and some other functions + cgame/cg_local.h : CG_trace trap_CM_BoxTrace + game/bg_public.h : using const in pmove_t trace functions prototypes + (fixes gcc warnings: assignment from incompatible pointer type) + +2001-07-26 Timothee Besset + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=78 + mapped K_MOUSE4 K_MOUSE5 + +2001-07-23 Timothee Besset + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=5 + more fixes, handling meta characters and various kinds of backspace + +2001-07-22 Timothee Besset + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=5 + after testing feedback, fixed more stuff: + better backspace, works with putty and potentially more terminals + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=51 + band aid fix to rcon status, incresed MAX_PUSHED_EVENTS from 256 to 1024 + (adds 28kb of mem requirements) + +2001-07-21 Timothee Besset + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=2 + using XF86 Gamma extension to set the gamma in game from the menus + (previous behaviour was to set /r_gamma and restart, renderer relying on s_gammatable) + restoring initial gamma on GLimp_ShutDown + +2001-07-19 Timothee Besset + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=5 + first usable version of dedicated console + added history and completion functionality + ready for some testing + still some TODOs and FIXMEs: + keep the currently edited line when going back from history exploration + edit the current line with cursor, insert mode etc. + +2001-07-18 Timothee Besset + * starting TAB completion and history for the dedicated server (tty console) + removed Sys_ConsoleOutput (unused) + removing bogus nostdout variable + cleanup of a big chunk of code that Bernd commented out and scheduled for deletion + moved completion code from client/cl_keys.c stuff into qcommon/common.c, Field_CompleteCommand(field_t*) + +2001-07-13 Timothee Besset + * fixed https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8 + screenshots overwrites + * fixed https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=31 + DOUBLE SIGNAL FAULT + +2001-07-11 Timothee Besset + * fix for french keybards / console toggle / bound to XK_twosuperior + +2001-07-10 Timothee Besset + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=19 + cleanup of the keyboard code, adding com_developer message in case XLookupString would fail + +2001-07-10 Timothee Besset + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=33 + using our custom handlers for X errors, should make things more robust + (X docs say some X errors are not fatal, but the default X handler exits the app anyway) + +2001-07-08 Timothee Besset + * https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=19 + keyboard state issues, fixed the sticking with ctrl key (thks relnev) + +2001-07-07 Timothee Besset + * closing https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=13 + the fixes to bug #9 solved this one too + * checking in to SOS + +2001-07-05 Timothee Besset + * work on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=9 + filesystem code changes: + updated the documentation in files.c to the current system + added correct fs_homepath fs_basepath fs_cdpath scanning to FS_SV_FOpenFileRead + (fixes description.txt not found, and probably a few other linux issues) + +2001-06-29 Timothee Besset + * fixed setup issues (graphical/console) + https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=6 + +2001-06-26 Timothee Besset + * bug tracker is online at https://zerowing.idsoftware.com/bugzilla + authentication, use login: bugs password: b00gies + for now, using it as the linux bug tracker, possible use for more OSes and programs if anyone is interested. + * tweaked the graphical setup to send to bugs@idsoftware.com on errors instead of support@lokigames.com + +2001-06-19 Timothee Besset + * fixed generated launch script /usr/local/bin/quake3, exit $* should be exit + +2001-06-18 Timothee Besset + * rebuilt 1.29f setups, released as 1.29f-beta1 'Q3 1.29f linux-i386 Jun 19 2001' + +2001-06-10 Timothee Besset + * rebuilt against PR source, packaged 1.29b setups + +2001-05-25 Timothee Besset + * graphical setup, based on Loki's setup tool (GPL) + +2001-05-22 Timothee Besset + * changed fs_basepath to fs_homepath, according to Graeme's changes (probably missed this change?) + this fixes the q3key prompting at each game startup + +2001-05-20 Timothee Besset + * rebuilding 1.28b, various fixes on linux build: + - SetProgramPath was renamed to Sys_SetDefaultCDPath in unix_shared.c + updated unix_main.c accordingly + - some prototypes in qgl.h are guarded by #ifndef GL_VERSION_1_2 (ARB extentions) + those prototypes are needed by linux_glimp for importing functions and casting, added a #ifdef __linux__ + (not a clean solution) + - game/q_shared.h + little endian / big endian functions have been added + gcc generates warnings about functions being unused .. inlined them + - cgame/cg_marks.c + // TTimo + // gcc warning: might be used uninitialized + float sInc = 0.0; + float s = 0.0; + +2001-05-15 Timothee Besset + * fixes to linux Makefile for bspc 2.1h + * various updates to 1.28b on linux + +2001-05-09 Timothee Besset + + * R. Duffy reverted game/bg_pmove.c PM_CheckDuck, was a merging screup on my side + * updated setup to 1.27z, removed the .so from the setup distribution (they were in 1.27g because of issues) + FIXME: gotta get pk3's first + +2001-05-04 Timothee Besset + + * fixes to gcc, building RC for 1.27s + +2001-05-01 Timothee Besset + + * added qcommon/huffman.c to the Makefile + * gcc -Wall: + commenting out + CL_Netchan_Encode CL_Netchan_Decode (cl_net_chan.c) + Netchan_ScramblePacket Netchan_UnScramblePacket (net_chan.c) + SV_Netchan_Encode SV_Netchan_Decode (sv_net_chan.c) + +2001-04-26 Timothee Besset + + * fixed dedicated server crash when entering the VM_COMPILED qagame on a mod (some statics lacked initialization) + +2001-04-25 Timothee Besset + + * added $(Q3POBJ) to clean target (cleanup of platform-dependent objects) + * more make clean improvements + +2001-04-23 Timothee Besset + + * cleanup the mod selection code, remove duplicates + * some issues with release builds, my main developement box doesn't build stable binaries with release settings + removing -fomit-frame-pointer seems to fix (there's probably a performance hit) + see OMIT-FRAME-POINTER.txt + +2001-04-13 Timothee Besset + + * checked in a first set of merged files + +2001-04-06 Timothee Besset + + * merged back the core linux parts to make 1.27g linux build from the Source Safe tree again + +2001-02-27 Bernd Kreimeier + + * CVS: tag with changes as of today + cvs tag id1-27j-loki01027 + + * code/qcommon/msg.c: numFields loop (SOS). + * code/qcommon/files.c: ue Q_stricmp (SOS uses stricmp, was strcmp). + * code/game/q_shared.h (Q3_VERSION): 1.27j. Also + MAX_STRING_TOKENS upped from 256 to 1024 (SOS). + + * code/server/sv_snapshot.c (SV_AddEntitiesVisibleFromPoint): see below. + * code/game/g_public.h (SVF_NOTSINGLECLIENT): added (SOS). + + * code/server/sv_ccmds.c: see below. + * code/game/g_main.c: g_gametype cvar now userinfo (SOS). + + * code/game/g_active.c (SendPendingPredictableEvents): new (SOS). + * code/game/bg_misc.c: new SOS (sos010227) + + * SOS: new update sos010227. + +2001-02-22 Bernd Kreimeier + + * CVS: now in sync with last SOS and cleanup up + cvs tag id1-27i-loki01022 + + * code/ui/ui_shared.c: below. + * code/ui/ui_main.c: leftover code! + * code/server/sv_world.c: below. + * code/server/sv_snapshot.c: below. + * code/server/sv_init.c: below. + * code/server/sv_game.c: below. + * code/server/sv_client.c: below. + * code/server/sv_ccmds.c: below. + * code/server/sv_bot.c: below. + * code/server/server.h: below. + * code/renderer/tr_surface.c: below. + * code/renderer/tr_shader.c: changed assert to early return. + * code/renderer/tr_shade_calc.c: below. + * code/renderer/tr_shade.c: below. + * code/renderer/tr_scene.c: below. + * code/renderer/tr_mesh.c: below. + * code/renderer/tr_local.h: below. + * code/qcommon/vm_x86.c: cleanup. + * code/qcommon/vm.c: below. + * code/qcommon/unzip.c: below. + * code/qcommon/qcommon.h: below. + * code/qcommon/files.c: below. + * code/qcommon/cvar.c: cleanup. + +2001-02-21 Bernd Kreimeier + + * code/qcommon/common.c: cleanup. + * code/qcommon/cm_trace.c: cleanup. + * code/qcommon/cm_patch.c: cleanup. + * code/qcommon/cm_public.h: cleanup. + * code/game/q_shared.h: cleanup. + * code/game/q_shared.c: cleanup. + * code/game/q_math.c: cleanup. + * code/game/g_syscalls.asm: changed (once more) floor,ceil etc. + * code/game/g_spawn.c: cleanup. + * code/game/g_session.c: cleanup. + * code/game/g_cmds.c: cleanup. + * code/game/g_client.c: cleanup. + * code/game/g_arenas.c: cleanup. + * code/game/bg_slidemove.c: cleanup. + * code/game/bg_pmove.c (PM_CheckDuck): old call to trace? + * code/game/bg_misc.c: cleanup. + * code/game/be_aas.h: dead code. + * code/game/ai_dmq3.c: cleanup. One clear/copy switched? + * code/game/ai_dmnet.c: more //*/. Why oh why not DEBUG.... + + * code/client/snd_mix.c: below. + * code/client/snd_dma.c: below. + * code/client/keys.h: cleanup. + TODO: #error in q3_ui/keycodes.h ? + + * code/client/client.h: cleanup. + * code/client/cl_main.c: misplaced bracket. Cleanup. + * code/client/cl_keys.c: below. + * code/client/cl_cin.c: below. + * code/client/cl_cgame.c: cleanup. + TODO: define assert for Win32 or guard my assertions. + + * code/cgame/cg_syscalls.c: below. + * code/cgame/cg_servercmds.c: below. + * code/cgame/cg_players.c: cleanup. + + * code/cgame/cg_newdraw.c: remember to diff against cg_newDraw.c + in SOS (mixed case). + TODO: get id to use cg_newdraw.c, and to remove cg_newDraw.c/cpp. + + * code/cgame/cg_main.c: below. + * code/cgame/cg_local.h: below. + * code/cgame/cg_event.c: below. + * code/cgame/cg_drawtools.c: below. + * code/cgame/cg_draw.c: cleanup. + * code/cgame/cg_consolecmds.c: dead code. + * code/bspc/qbsp.h: below. + * code/bspc/l_poly.c: below. + * code/bspc/l_math.c: cleanup. + * code/bspc/bspc.c: cleanup. + * code/bspc/be_aas_bspc.c: cleanup. + * code/bspc/aas_map.c: kept comments - merge loss at their end? + * code/bspc/aas_file.c: cleanup. + + * code/botlib/be_interface.c: this file is plain impossible. There + are layers of code made dead with /* */ and the resurrected by + //* or // /* or variations of this. I reverted to exact mirror + image of SOS to be sure - short of removing it's too easy to mistake + live code for dead one. + Later: have to change 5 occurences to avoid gcc complaints about + nested comment tokens. + TODO: somebody please get rid of the cruft in here. + + * code/botlib/be_ai_move.c: redundant typedef. + * code/botlib/be_ai_chat.c: assertions on signed string index. + Note: this is not in my ChangeLog - ouch. + TODO: use gcc -fsigned-char on all platsforms to enforce Win32 + TODO behavior (PPC has a default unsigned char, Intel has not). + * code/botlib/be_aas_sample.c (AAS_TraceClientBBox): one code block + was placed in different location, and one FPE hack not used. I would + expect that divide by zero will still occur here. + + * code/botlib/be_aas_reach.c: below. + * code/botlib/be_aas_cluster.c: cleanup. + * CVS: the last tag (below) marks the version with a lot of history + and additional comments. I am now bringing the codebase in sync with + SOS as of yesterday, cleaning out comments, dead code and other + differences to minimize a diff - in a valiant if futile attempt to + roll back changes into the id codebase. + Note: I ignore the $SOS$ - these are unfortunate but will change + in the same awkward way at their end. + Note: I stick to #if 0 instead of C comments around dead code id + kept (nested comments issue). The commentary is changed to sosYYMMDD + and includes the token DEAD. + +2001-02-20 Bernd Kreimeier + + * CVS: update, then tag current version as + cvs tag id1-27i-loki010219 + + * SOS: patched up to sos010219. + + * code/qcommon/cm_trace.c (CM_Trace): fabs on sphere offsets (SOS). + * code/game/bg_slidemove.c (PM_StepSlideMove): stepSize vs. STEPSIZE (SOS). + * code/game/bg_pmove.c (PM_CheckDuck): fix in stand up check (SOS). + * code/bspc/bspc.c (main): -capsule (SOS). + * code/bspc/qbsp.h: below (SOS). + * code/bspc/be_aas_bspc.c (capsule_collision): added (SOS). + * code/bspc/aas_map.c (CapsuleOriginDistanceFromPlane): added and used (SOS). + * code/bspc/aas_file.c (AAS_WriteAASFile): removed diagnostics recently + added. No matter how long you wait, they'll always get you ;-). + * code/botlib/be_aas_cluster.c: enabled LogWrites, different flood (SOS). + + * SOS: patching up to snapshot sos010219. + Note: For brevity, I use as marker sosYYMMDD now instead of bkYYMMDD, to + distinguish from changes not in SOS. + + * CVS: tagged current version before patching up with SOS. + cvs tag id1-27i-loki010216-bsd + +2001-02-16 Bernd Kreimeier + + * code/server/sv_init.c: DLL_ONLY sets sv_pure to 0 and ROM. + TODO: determine good sv_pure policy for DLL-only servers. + + * code/renderer/tr_shade_calc.c: my_ftol implementation (BSD). + + * code/unix/Makefile: FreeBSD sections. + TODO: include target-specific Make-freebsd etc., + include a Make-local not in CVS for build preferences, + and generally clean up this mess. + * code/unix/unix_glw.h: guard #error + * code/unix/linux_snd.c: soundcard.h location (BSD). + * code/unix/linux_glimp.c: guard system headers. + Later: added Joystick stubs. + Note: linux_ etc. prefixes start to loose meaning as we + re-use most of this on UNIXes anyway. I didn't use Raf's + freebsd_joystick.c but instead put generic stubs here. + TODO: introduce generic -DNO_JOYSTICK flag. + * code/renderer/tr_local.h: my_ftol guard. + * code/renderer/qgl.h: FreeBSD guards. + * code/qcommon/vm_x86.c: sys/types include on FreeBSD. + * code/qcommon/md4.c: Win32 pragma guard. + * code/qcommon/common.c: Com_Memcpy/Memset external. + * code/game/q_shared.h: added FreeBSD defines. + * code/game/q_math.c (BoxOnPlaneSide): FreeBSD conditional. + TODO: check whether we have/need the assembly version anyway. + * code/client/snd_mix.c: use C fallback on FreeBSD. + Note: all of the above changes from the original port by Rafael Barrero. + + * CVS: tagged current version before merging FreeBSD related changes. + cvs tag id1-27i-loki010215-ppc + +2001-02-15 Bernd Kreimeier + + * code/unix/Makefile: BSD related changes. + * code/cgame/cg_draw.c: hacked phone jack rendering check for Debug. + TODO: finish Debug, fix CG_DrawDisconnect !!! + + * code/unix/vm_x86.c: error on compile attempts. Fight redundancy! + * code/qcommon/vm_x86.c (VM_CallCompiled): dummy for linkage on PPC. + Note: DLL_ONLY is the global Makefile option for DLL-only builts. + Currently only executed on Linux. + * code/unix/unix_main.c: *ppc postfix for DLLs. Ignored the changes + to redundant code (have to remove the unused Un/LoadDll/API calls). + * code/server/sv_game.c (VMA): changed macro (see below). PPC. + * code/qcommon/vm.c (VM_DllSyscall): see lengthy commentary by Ryan. + The existing VM code makes certain assumptions about the layout of + varargs on the stack, which fall apart with call conventions that + don't even put all parameters on the stack (gcc on PPC, register-rich). + Using a dedicated memory area as our own stack. This should actually + be the default behavior. + Later: make vm_* cvars INIT/ROM for DLL_ONLY target. + + * code/qcommon/common.c: PPC change (from Ryan Gordon). + +2001-02-07 Bernd Kreimeier + + * code/unix/unix_main.c: disabled FPE for debug for the time + being (that is, until I can figure out + Program received signal SIGFPE, Arithmetic exception. + RB_BeginSurface (shader=0x449572e0, fogNum=0) at ..//renderer/tr_shade.c:307 + 307 tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset; + without any NaN's involved. + TODO: unmask other FPE's selectively (see Mike's Tribes2, no getenv though). + +2001-02-06 Bernd Kreimeier + + * SOS: up to date with todays snapshot. + Note: got the date wrong, comment used was bk010205. Duh. + + * code/server/sv_snapshot.c (SV_UpdateServerCommandsToClient): below. + * code/server/sv_main.c (SV_ReplacePendingServerCommands): new (SOS). + * code/server/server.h: reliableSent (SOS). + + * code/renderer/tr_shade.c (ProjectDlightTexture): see below. + * code/renderer/tr_scene.c: see below. + * code/renderer/tr_public.h: see below (SOS). + * code/renderer/tr_local.h: additive light support (SOS). + + * code/qcommon/cm_trace.c (CM_Trace): new tw.sphere.use branch (SOS). + + * code/game/g_spawn.c: notta, notq3a entities (SOS). + * code/game/ai_dmq3.c: MAX_ACTIVATEAREAS search (SOS). + + * code/client/cl_cgame.c: see below. + * code/cgame/cg_syscalls.c (trap_R_AddAdditiveLightToScene): below. + * code/cgame/cg_syscalls.asm: see below (trap_R_AddAdditiveLightToScene). + * code/cgame/cg_public.h: CG_R_ADDADDITIVELIGHTTOSCENE (SOS). + + * code/bspc/l_math.c: new VectorLengthSquared, removed rotate/matrix (SOS). + * code/bspc/bspc.c (BSPC_VERSION): was 2.1e, now? + * code/bspc/be_aas_bspc.c (BotImport_Trace): CM_BoxTrace sig. (SOS). + * code/bspc/aas_file.c (AAS_WriteAASFile): SOS. + * code/botlib/be_aas_sample.c (AAS_DeAllocAASLink): SOS. + + * code/unix/unix_main.c (Sys_LoadDll): do not load from installdir + in NDEBUG (confusing relic from old Makefile). Postfix -debug.so + for debug binaries to let both builds coexist. + + * code/unix/Makefile: updated install targets and VERSION. + + * Win32: build from SOS snapshot. + Note: Unix CR/LF in *.dsw/*.dsp fucks up MSVC++. + +2001-02-02 Bernd Kreimeier + + * SOS: all changes up to today. + + * code/server/sv_init.c (SV_TouchCGame): added. Also memset + on reallocated client data (SOS). + * code/qcommon/qcommon.h: see below. + * code/qcommon/cvar.c (Cvar_SetLatched): new (SOS). + * code/qcommon/cm_trace.c: more sphere test fixes (SOS). + Note: SOS encryption key expired and updated by MrElusive. + + * code/qcommon/cm_patch.c (CM_TraceThroughPatchCollide): + fix from MrElusive, fall through curved corner floors (q3dm17). + Later: also in SOS (so is shadow FPE fix). + + * Win32: can't get an unadulterated SOS snapshot to build. + First, fix CR/LF back again (Linux client converts all). + find . -name '*.ds*' -print + alias dos2unix='recode ibmpc..lat1' + alias unix2dos='recode lat1..ibmpc' + Next, find a *.dws that works? Nope, no cigar. + +2001-02-01 Bernd Kreimeier + + * Win32: have to update dsp/dsw/etc. files in CVS, too. + + * CVS: tag previous version before update + cvs tag id1-27h-loki010131-beta3 + + * code/game/q_math.c (Q_rsqrt): guard, #ifndef __linux__ + for assert (for Win32 build). + TODO: assert replacement for Win32? + * code/q3_ui/ui_qmenu.c: see below. + * code/q3_ui/ui_players.c: see below. + * code/q3_ui/ui_controls2.c: float const with f postfix + Note: Win32 C4305 warning. Somebody at id has been doing + a lot of these recently as well... + + * code/cgame/cg_players.c (CG_PlayerShadow): applied fix by + MrElusive, removed FPE hack (player shadows on zero mormals). + Prolly in this evenings' CVS. + + * code/server/sv_game.c: new signatures (capsule again). + * code/server/server.h: new signatures (SV_Trace,ClipToEntity). + * code/server/sv_bot.c: new signatures (above). + * code/qcommon/cm_trace.c: a truckload of changes. Math + code added before moved upwards. Capsule traces added all + over the place, old box traces moved in conditional + branches, functions renamed and wrapped. Eliminated some + of the previous' versions deadcode to keep diffs smaller. + TODO: once a point release is out and reasonably bug + TODO free, remove // bkYYMMDD annotations where SOS related. + + * code/qcommon/cm_public.h: new signatures in prototypes. + * code/qcommon/cm_patch.c: dead code re-enabled, new + sections (conditional branches for spheres) added to + several trace functions. + * code/qcommon/cm_local.h (CAPSULE_MODEL_HANDLE): added. + * code/qcommon/cm_load.c (CM_TempBoxModel): capsules. + + * code/game/q_shared.h (Q3_VERSION): 1.27i now (new QVM traps). + + * code/game/g_syscalls.asm: see below. + * code/game/g_public.h (SVF_CAPSULE): added (SOS). Also + G_ entry poiints for capsule traces. + + * code/client/cl_cgame.c: see below. + * code/cgame/cg_syscalls.c: see below. + * code/cgame/cg_syscalls.asm: see below. + * code/cgame/cg_public.h: new capsule trace code (SOS). + +2001-01-31 Bernd Kreimeier + + * Win32: test compile (WinCVS, MSVC++). Have to guard isnan. + Note: too much shit going on.... + +2001-01-30 Bernd Kreimeier + + * CVS: update for patching up (pre-1.27i). + + * SOS: new changes (new collision detection primitives). + Now Version 1.27i. + TODO: start testing using DLL's (QVM code is out of sync). + +2001-01-25 Bernd Kreimeier + + * SOS: caught up till today (below). + * code/qcommon/cm_trace.c: new functions added: RotatePoint, + TransposeMatrix, CreateRotationMatrix (SOS). + (CM_TransformedBoxTrace): new rotation code used here. + + * code/q3_ui/ui_demo2.c: sizeof(extension). SOS. + * code/game/g_cmds.c (G_SayTo): CON_CONNECTED. + * code/game/ai_main.c: HOOK added (SOS). + * code/botlib/be_aas_move.c (AAS_HorizontalVelocityForJump): + correct fix for FPE occuring (SOS). + * code/game/ai_dmq3.c: initmove.viewoffset (SOS). + + * code/game/q_math.c: guard asser/isnan with Q3_VM (q3asm). + TODO: define Com_Error based assert macro? NDEBUG? + +2001-01-24 Bernd Kreimeier + + * code/server/sv_ccmds.c (SV_MapRestart_f): some debug. + TODO: map_restart 0 disconnects external client in 1.27h? + + * code/renderer/tr_image.c (LoadTGA): added some commentary + and dead code based on fixes from GtkRadiant (Leonardo found + flipped TGA's). + +2001-01-23 Bernd Kreimeier + + * BETA3: finished testing, ready to upload to id FTP. + Later: neither the FreeBSD beta not the Linux Beta3 + uloaded. Beta2 not yet released, and clients get + disconnected with Beta2 and Beta3 on SV_MapRestart_f. + +2001-01-22 Bernd Kreimeier + + * code/client/cl_main.c (CL_InitDownloads): undid yesterday (SOS). + * code/botlib/be_aas_sample.c (AAS_DeAllocAASLink): guard print (SOS). + * code/server/sv_client.c (SV_DirectConnect): VM_Call disconnect (SOS). + * code/qcommon/files.c (FS_ListFilteredFiles): trailing slashes (SOS). + * code/game/g_cmds.c (SetTeam): print change (SOS). + Note: the above plus VectorClear(v1) (below) are todays SOS changes. + + * code/cgame/cg_players.c (CG_PlayerShadow): ignore bogus + (all zero) planes. This caused FPE in ProjectPointOnPlane. + TODO: why does trace return zero normal planes? + Note: gdb seems totally at loss with vec3_t arrays.... + + * code/botlib/be_aas_sample.c (AAS_TraceAreas): FPE. + NaN in uninitialized v1 that wasn't supposed to be referred + to in this branch. + + * code/botlib/be_aas_move.c (AAS_HorizontalVelocityForJump): + FPE divide by zero (zero zvel, zero t) for jump estimates. + + * code/client/cl_main.c (CL_Frame):1856. uivm==NULL on + client after server crashed. + TODO: check that uivm always non-NULL for client. + TODO: do setenv(FX_NO_SIGNALS) to avoid exit errors... + + * code/unix/linux_glimp.c (GLW_SetMode): added "Indirect" + Mesa token to software rendering detection. Reworded error + output and added drivername. + TODO: measure framerate instead? + +2001-01-21 Bernd Kreimeier + + * SOS: caught up with changes up until today. + + * code/server/sv_init.c (SV_SetConfigstring): gentity != NULL + + * code/server/sv_client.c: connect to "{all bots" server. + * code/renderer/tr_init.c: JPEG extension on screenshots + * code/qcommon/files.c: modes based on mods, fs_basegame + + * code/q3_ui/ui_demo2.c: dm3 extension (demo names, protocol). + + * code/game/g_client.c: savedEvents[] removed. + * code/game/bg_misc.c: event sequence fixes. + * code/client/snd_dma.c (S_StopBackgroundTrack): different use. + * code/client/cl_main.c: demo file handling changed (names). + Also CL_InitDownloads: always next download. + + * code/cgame/cg_servercmds.c: cg_thirdPerson. + * code/cgame/cg_weapons.c: see below. + Also CG_ShotgunPattern: different call (seed parameter). + + * code/cgame/cg_main.c: see below. + * code/cgame/cg_local.h: new cg_noProjectileTrail Cvar. + * code/cgame/cg_effects.c (CG_BubbleTrail): early out (above). + + * code/bspc/l_poly.c (BOGUS_RANGE): increased. + * code/bspc/bspc.c: applied patch up to "2.1e" + +2001-01-18 Bernd Kreimeier + + * code/ui/ui_main.c: below. + * code/q3_ui/ui_main.c: UI_HASUNIQUECDKEY comment. + Note: mods have to return qfalse. See Bug #2890 in Fenris. + +2001-01-17 Bernd Kreimeier + + * BETA2: finished testing, uploaded to id's FTP for release. + +2001-01-16 Bernd Kreimeier + + * CVS: checking in preparation for Beta2. + cvs tag id1-27h-loki010116-beta2 + + * SOS: new bspc "2.1e". No change on 1.27h. + + * TEST: patch-up seems to work fine. No new files have been added + to the linkage (i.e. the ft2/ files now added), so we might not be + feature complete. + + * code/game/g_active.c (ClientThink_real): id MISSIONPACK + conditional in addition to the ones I added earlier. + * code/qcommon/files.c: REJECT. Linux hack for userdir threw it off. + * code/qcommon/unzip.c: REJECT. CRC-32 section removed. + Later: unused tempB + + * code/q3_ui/ui_syscalls.asm: REJECT. Start/StopBackgroundTrack. + * code/ui/ui_syscalls.asm: REJECT. syscalls ids from 1.27h + as of SOS (floor/ceil - will this ever get straightened out) + * code/win32/win_input.c: REJECT. g_pMouse edit. + * ui/menus.txt: REJECT. Replaced with 1.27h version. + Note: some more due to $SOS$. + + * ui/: new scripts. + cinematicmenu.menu, demo_quit.menu, ingame.txt, serverinfo_old.menu + vid_restart.menu + + * code/ft2/ttconfig.h: below. + * code/ft2/sfconfig.h: below. + * code/ft2/pstables.h: below. + * code/ft2/psnames.c: below. + * code/ft2/psdriver.h/c: below. + * code/ft2/keys.h: below. + * code/ft2/ftbbox.c: new in 1.27h + + * code/cgame/cg_newdraw.c: beware: cg_newDraw.c gets lost in diff easily. + + * code/cgame/cg_rankings.c: file removed from SOS. + +2001-01-15 Bernd Kreimeier + + * Patch-up: patching up from RC4 to 1.27h current. + No changes since 010112 snapshot. + ln -s sos010112/ work + diff -urbB sos001204-rc4/ work > work.diff + ln -s cvs-1.27g/ work + patch -p0 < work.diff > work.patch + find cvs1.27g/ -name '*.rej' -print + + * SOS: adding the remaining SOS snapshots to CVS. + cvs import Quake3_sos sos001211 pr1-27g-win32-001211 + Note: at this point id warned about repository corruption. + Watch out for the syscall stuff in particular. + cvs import Quake3_sos sos010104 pr1-27g-win32-010104 + cvs import Quake3_sos sos010108 pr1-27h-win32-010108 + cvs import Quake3_sos sos010110 pr1-27h-win32-010110 + cvs import Quake3_sos sos010112 pr1-27h-win32-010112 + Note: the first 1.27h might be the public (server only) + beta released, the second one was post release. Beware + of source files added and removed (botlib headers, FT2). + Note: why so late? Don't ask... + +2001-01-08 Bernd Kreimeier + + * SOS: id's working up to 1.27h (server side fix for + Guard exploit seems to force earlier release). Updating + CVS (most of the changes are debug code put in and + then disabled, plus some fixes as below). Next patching + up to current SOS. + +2001-01-07 Bernd Kreimeier + + * Makefile: need to rework this for multiple platforms. + We also need null/null_vm.c for platforms where we don't + have JIT (assembly emit). + +2001-01-04 Bernd Kreimeier + + * code/q3_ui/ui_connect.c (UI_DisplayDownloadInfo): time + information for current (vs. start of download) is wrong, + thus negative 1 "estimated time", as well as transfer + rate just negative downloadSize. Not fixed. + + * code/unix/unix_main.c (Sys_ParseArgs): added. + Note: for support/us, to identify builts. This is only + a skeleton right now - if I ever feel the need to support + more than "-v" and "--version" I'll have to flesh this out. + + * code/unix/linux_glimp.c (signal_handler): see below. + * code/unix/unix_main.c (Sys_Exit): added an abstraction + layer for exit/_exit/assert/raise issues. + Note: need both a better debug/backtrace handling, and + have to find a way to determine why/where the alleged + startup/exit errors happen... + +2001-01-03 Bernd Kreimeier + + * code/game/g_mem.c (G_Alloc): ERR_DROP initiated by + addbot commands for large sv_maxclients, allegedly + caused segfaults in 1.17. Not reproducible. + TODO: recover more gracefully from failure to add bot? + + * code/renderer/tr_light.c (R_LightForPoint): Tim Angus + reports a crashbug with nolight maps. Also assertion in + R_SetupEntityLightingGrid, might want conditional there. + DONE: fixed crash on LightForPoint for nolight maps. + + * code/qcommon/qcommon.h: NUM_SERVER_PORTS. A feature + request to increase this, or make it more flexible + otherwise (Fenris). + TODO: id decision on more flexible NUM_SERVER_PORTS. + +2001-01-02 Bernd Kreimeier + + * code/unix/snapvector.nasm: fixed FPU bit (the current + one had reserved bits off, behavior should not change). + * code/qcommon/vm_x86.c: fixed symbols (below). + * code/unix/ftol.nasm: FPU bits weren't correct (duh). + DONE: shoot-though floor (q3dm5) + DONE: cursor-in-rect off (TA/Player model selection) + Note: in gdb, "disassemble " is your friend. + + * code/cgame/cg_public.h: CG_MEMSET is set to 100. In + cg_syscalls.asm it's 101. If I change it I get Bad trap 100 + from the cgame VM code, so the 1.27g "official" VM code + uses it. + + * code/unix/linux_common.c: have to fall back to C, the + current assembly is buggy... + * code/unix/Makefile (linux_common.o): added. + Later: also for dedicated. Less portable this way. + TODO: C_ONLY for dedicated on non-i386 only? + + * code/qcommon/common.c: do not use memcpy/memset under Linux. + * code/unix/linux_common.c: added Andrew's assembly port. + TODO: C_ONLY for Com_Memset/Memcpy? Conditionals are fubared. + + * code/qcommon/vm.c (VM_Init): use Win32 defaults (do not + use DLL's by default). This exposes DLL rounding errors + (damage through floors), and we don't want DLL's used by + default anyway. + TODO: why vm_ui default of 1? + + * code/botlib/l_precomp.c (SourceWarning): removed assert. + + * code/game/bg_lib.c (acos): defined, but we don't actually + use it except where the cg_syscalls.asm trap is used. + + * code/game/g_public.h: missing lots of trap tokens. + * code/game/g_syscalls.c: missing lots of traps. + * code/game/g_syscalls.asm: more inconsistent hooks, were: + equ floor -111 + equ ceil -112 + equ testPrintInt -113 + equ testPrintFloat -114 + now changed to match cg_syscalls. + Note: fixed this in UI earlier, how did this slip through + the diffs against SOS? + + * code/game/g_syscalls.c: no acos hook. + * code/cgame/cg_syscalls.c: no acos hook. + * code/cgame/cg_syscalls.asm: has acos hook as -112 + Note: report from Tim Angus. The acos function is in bg_lib.c + which is linked only into ui (not q3_ui). That means we are + using libc acos right now? + Note: QVM traps are negative? + + * BSD/Irix: tagged current CVS (not all of the below) as + cvs tag id1-27g-loki010102-bsd1 + for BSD work (Rafael Barrero). Also be used for Irix update. + +2001-01-01 Bernd Kreimeier + + * SOS: adding the remaining SOS snapshots to CVS. + cvs import Quake3_sos sos001201-rc3 pr1-27f-win32-001201-rc3 + cvs import Quake3_sos sos001202 pr1-27f-win32-001202 + cvs import Quake3_sos sos001204 pr1-27g-win32-001204-rc4 + This is the codebase to which the Linux branch has been patched + up. I can't verify whether this is identical to the RC4 codebase + as the tag doesn't work (but can check against the ZIP file..) + cvs import Quake3_sos sos001211 pr1-27g-win32-001211 + The above snapshot contains a (post-release?) fix to ui_syscalls + in ui/ and q3_ui/. This change has been used in Linux (Beta1 and + above). At this point, id discouraged further use of SOS due to + repository corruption on their end. No further snapshots were + taken since. + + * Fenris: since the release of the Beta1 bugs have been + maintained at http://fenris.lokigames.com/. I am going to + list issues here as they get fixed. + +2000-12-21 Bernd Kreimeier + + * code/renderer/tr_font.c: graceful silence with old mods? + * code/botlib/l_precomp.c (SourceWarning): graceful exit if old mod? + +2000-12-20 Bernd Kreimeier + + * code/server/sv_ccmds.c (SV_MapRestart_f): see below. + * code/qcommon/vm.c: currentVM is 0x0 in VM_ArgPtr. + In VM_Call, oldVM was NULL - made conditional the + reset of currentVM to oldVM. + +2000-12-18 Bernd Kreimeier + + * BETA1: closed Linux beta release. Stripped debug + and release binaries, DLL's, and pak4.pk3. CVS checkin, + will be tagged as + cvs tag id1-27g-loki001218-beta1 + Later: id added a pak5.pk3 to the Win32 point release, + added this to the BETA1 best. + + * code/qcommon/vm_x86.c: C37F. + * code/unix/snapvector.nasm: C37F. + Note: short of any real evidence, I gamble and use max. + precision (as well as default Linux precision, but NOT + Win32 precision). It seems that precision change is not + really an issue (despite Graeme's claim that the cursor + in the menu was/is off). I also pick the roundiung behavior + that is seemingly used by ANSI and gcc (but possibly not + Win32 _ftol depending on build). + +2000-12-15 Bernd Kreimeier + + * code/unix/Makefile: added snapvector.o + * code/unix/unix_shared.c: #if 0'ed the old snapvector code. + * code/unix/snapvector.nasm (Sys_SnapVectorCW): two new + assembly functions from AndrewH that explicitely set the + FPU control word to convert vec3_t, to ensure cross-platform + behavior for both DLL and QVM. + + * code/unix/ftol.nasm (Q_ftolC37F): for globals. + + * code/unix/unix_main.c: took out global FPU manipulation. + For clarity this should be VM only. + * code/qcommon/vm_x86.c: added prototypes for the ftol + library. To select a specific behavior for the entire VM, + set ftolPtr accordingly. + Later: the GCC ftol function of course affect the stack + (there is no "declspec naked"). The problem seems to be + that the VM never handles the stack in a way compatible + to regular gcc C functions. For some odd reason _ftol seems + to do the right thing under Win32. All 4 control words + implemented at the moment work just fine with the menus. + + * code/unix/ftol.nasm: added a small library of "safe" qftol + variations that explicitely set the control word to the + relevant (4) possibilities. + +2000-12-13 Bernd Kreimeier + + * code/qcommon/vm_x86.c: an entire day spent trying to nail + the ftol issues. It breaks down like this: id used to use + an unsafe (no setting FPU control word) fistp. That seemingly + caused subtle physics bugs which nobody cared about in 1.17. + They then changed the UI code, and ran into the UI bugs: + menu entries shifted to the right, fonts vanishing. Then + they switched to using _ftol. Then they had to reproduce + the old behavior for the physics code due to public outrage. + My original port used a simple (long)float cast, which gcc + seemingly compiles to code that does OR 0C00 on whatever + current control word (precision unchanged). This breaks the + menus. If I use the unprotected fistp instead, which should + (Linux 037F default) use "nearest/even", then my menus are + correct. That would mean Win32 _ftol in id's compile does + the same, only that would require /qifist or some equivalent + compile flag, which I can't find. Two disassemblies of _ftol + I got from others showed OR 0C00 as part of the default (ANSI) + behavior. + +2000-12-13 Bernd Kreimeier + + * code/game/bg_pmove.c (PmoveSingle): trap_SnapVector. + The one true and single call to snap velocity. + Note: bspc/map.c:void SnapVector(vec3_t normal) + qcommon/cm_patch.c:void CM_SnapVector(vec3_t normal) + game/q_shared.h: #define SnapVector(v) {v[0]=((int)(v[0]));... + + * code/client/cl_cgame.c: CG_SNAPVECTOR. + * code/server/sv_game.c: G_SNAPVECTOR. + Note: these go through trap_SnapVector in syscalls. + + * code/unix/unix_shared.c (Sys_SnapVector): sticking to + old Linux version for now... + * code/win32/win_shared.c (Sys_SnapVector): changed. + Note: Graeme points out this was changed to fix ftol + artifacts? + TODO: calculate errors for various ftol variants... + + * code/qcommon/vm_x86.c: both the old fistp code (1.17) + and the new qftol function apparatently work. Using the + ftol.nasm code for now. + + * code/unix/Makefile: DO_NASM and ftol.o. + + * code/unix/ftol.nasm (qftol): created from Mike's SoF + replacements, with Andrew's help to satify the VM + stack/call requirements. + TODO: use Q_ftol herein to replace myftol elsewhere. + + * code/unix/unix_main.c (Sys_ConfigureFPU): SIGFPE. + TODO: divide by zero in botlib. Disable this for now. + Note: we can't introduce calculation differences between + versions, so fixing these will have to wait. + + * code/qcommon/vm_x86.c: two new lines in Win32 branch + missing from Linux assembly in AsmCall: + mov eax, dword ptr [edi] + and eax, [callMask] + Added, doesn't seem to affect UI etc. bugs. + Later: no FTOL_PTR, use fistp non-IEEE assembly as in old + version. This seems to work for Q3 and TA, while qftol + (simple cast) does not - for Win32 Graeme says the reverse + is true. + + * code/qcommon/vm_x86_old.c: used the old cvs-1.17 version. + Two fixes (Hunk_Alloc, Com_Memcpy), and it works: + +set vm_game 2 +set vm_ui 2 +set vm_cgame 2 + UI, cgame and game w/o apparent problems. + +2000-12-12 Bernd Kreimeier + + * code/unix/Makefile: cleanup of redundant flags. + Removed bogus MALLOC_CHECK (note to self: export MALLOC_CHECK_=2). + Also DO_SHLIB_CC on all UI DLL's. + Added and removed DEBUG_VM flag. + TODO: figure out whether Zoid did UI this way intentionally. + Note: this seemingly fixed the botimport problem, although + most of the changes were just redundant CFLAGS removed. Given + our wanker toolchain, should have been more paranoid. All + DLL's can now be used w/o apparent problems. + + * code/server/sv_main.c: gvm init. + * code/server/sv_game.c: gvm assertions. + * code/unix/unix_main.c (Sys_LoadDll): print vmMain + Note: top no avail. There is some odd ld/gdb problem here + that prevents examining globals and obfuscates part of + the stack between VM_Call and lower level code, through + G_InitGame. This is not just DLL's being loaded and unloaded. + Wromg flags during build? The vmCvar for "bot_developer" + ends up overlapping global botimport in memory, which + thus zero-fills part of the function pointer table. + + * code/server/sv_bot.c (SV_BotInitBotLib): this (by way of + GetBotLibAPI) is responsible for setting botimport, which, + if using the game DLL, is not properly set up. Called in + SV_Init(). + + * code/game/q_shared.c: Q_strncpyz does zero padding (duh). + Note: calls strncpy, which does a zero fill up to destsize. + If destsize exceeds memory size, zero padding will overwrite + adjacent memory. Suspicion was this happend to botimport. + + * code/qcommon/cvar.c: possible problem in Q_strncpyz call. + + * code/botlib/be_ai_weap.c (weaponinfo_fields): made this static. + Note: it seems that the "number" string got replaced by + p def.fields[0] + {name = 0x40000000 "\177ELF\001\001\001", offset = 2, type = 50, .. + Memory corruption? + + * code/game/inv.h (WEAPONINDEX_GAUNTLET): defined here. + * botfiles/weapons.c (Gauntlet): the baseq3/qagamei386.so parser + breaks here: + number WEAPONINDEX_GAUNTLET + * code/botlib/l_precomp.c (SourceWarning): added assertion to + trap botlib parsing problem.. + + * RC1: for beta test. Using my own vm/ui.qvm files in this case. + TODO: Setup with nouninstall. + TODO: fix game DLL/ botlib setup problem (so all DLL's work) + TODO: SIGFPE + TODO: profile? + + * code/unix/Makefile (ai_vcmd.o): added to game DLL linkage. + How the fuck did this happen? + DONE: "qagamei386.so: undefined symbol: BotVoiceChat_Defend" + + * TEST: +set vm_ui 2 (vm_x86, not interpreter). Breaks! + Further: qagame had undefined, but seemingly gets reloaded + second try (I hate the Linux linker). + * TODO: never reload fail DLL, abort engine + + +2000-12-11 Bernd Kreimeier + + * TEST: recompile QVM/DLL and executable to test new UI code. + The UI QVMs from the paks still do not work. + + * SOS: changes in UI code! + * code/q3_ui/ui_public.h: this file is deprecated + Note: e.g. it does not contain the background track calls. + * code/ui/ui_public.h: the uiImport_t enum here determines + the values. + * code/ui/ui_syscalls.asm: same as q3_ui now, were: + equ floor -111 + equ ceil -112 + * code/q3_ui/ui_syscalls.asm: these are now switched, were: + equ trap_S_StartBackgroundTrack -63 + equ trap_S_StopBackgroundTrack -64 + The new values match the ui/ equivalent. Also, floor (-108) + and ceil (-109) are different in ui/. + + * CVS: going to check in this snapshot and tag it as + cvs tag id1-27g-loki001209-rc4 + Presumed equivalent to SOS tag "1.27g RC4" (master). As I can't + obtain the tagged code using SOS (neither Win32 nor Linux client) + I can only guess. + + * TEST: use my own VM code, ion baseq3/vm/*.qvm and missionpack/vm/. + This works - in other words, the menu bug seems in the UI code, and + is fixed in my codebase. + + * TEST: make release. + Note: I can postpone DLL specific problems. Bad performance is not + as important as outright bugs. Thus the UI QVM issue is the only + one that stops me from creating an RC. + TODO: Q3 UI QVM code from pak file does not work (neither does TA). + TODO: sound with video playback still awful. Threaded sound, I guess. + TODO: ERROR: couldn't open demos/DEMO002.dm3.dm_48 (same demo001.dm3.dm_48) + + TODO: do not show Q3 demos in TA menu? + TODO: new demos for Q3? Or at least error message? + + * code/game/bg_lib.c: itrinsics excluded by Q3_VM (another -O + compile). Uninitialized variable. + * code/unix/Makefile: -O for uninit on patched code. Also shortcuts. + TODO: DC_ONLY seems an obsolete flag, used in Makefiles, not source. + + * TEST: +set sv_pure 0 +set vm_game 1 +set vm_cgame 1 +set vm_ui 0 + Turns out that the pak0.pk3 UI QVM code is seemingly broken in TA + and Q3, but my UI DLL is not. In reverse, the QVM game/cgame for + Q3 seems to work quite well (including bots). The TA game/cgame + also works, including botlib init. + TODO: BotLib Init using game DLL gives: + TODO: Error: file weapons.c, line 38: unknown structure field number + TODO: Fatal: couldn't load the weapon config + TODO: Error: BotLoadMap: bot library used before being setup + + * TEST: checked the rc4winstlr.zip CD tree against + my test install. baseq3/pak4.pl3 and missionpack/pak0.pk3 + are identical, but I finally recognized that there was + a missionpack/pak1.pk3 not in the final install - left over + from an earlier update from id. Doesn't seem to affect the + DLL based runs at all. + Note: I still do not have the final CD snapshot Robert + promised me mid last week, they haven't even fixed the + FTP account they took down. Communication with id is as + abyssmal as ever. + +2000-12-08 Bernd Kreimeier + + * TEST: running with RC4 data files. + TODO: "bot library used before setup" (Q3+TA) + TODO: Q3 old mods wreak havoc (graceful bounce) + TODO: supress "FreeType code not available" in renderer + TODO: can't move in Q3 + TODO: items flicker in Q3 + TODO: no decals in Q3 + TODO: VM UI code still broken (Q3+TA) + TODO: sound code is awful + TODO: video playback inferior to earlier builds + + * code/q3_ui/ui_local.h: prototype trap_VerifyCDKey(..) + * code/game/g_active.c ( StuckInOtherClient): TA only. + * code/cgame/cg_draw.c: 4x unbalanced `#endif' - from patch? + * code/null/null_client.c (CL_CDKeyValidate): dummy added. + * code/qcommon/common.c: Q_acos missing, changed conditionals + + * code/qcommon/vm_x86.c: unreacheable _asm instruction that + gcc doesn't quite like... #if 0'ed for now + TODO: understand _asm { mov eax,[ebx] }, fix it for gcc + + * TEST: compile... + + * code/ui/ui_main.c: full REJECT. Manual merge. + Note: preserved debug_protocol lines, who knows what it's good for. + + * code/qcommon/files.c: REJECT. SafeMode, demo server FS_Restart. + + * code/client/snd_mem.c: REJECT: $SOS$. + * code/client/snd_dma.c: REJECT: $SOS$. + * code/client/cl_cin.c: REJECT. com_timescale, $SOS$. + + * code/cgame/cg_draw.c: REJECT. Lots, but virtually all either + float postfix (on some, not all places), or #ifndef MISSIONPACK + that I had already put in during -Werror (conditional unused). + + * code/cgame/cg_consolecmds.c: REJECT. id commented unused code + that I had #if 0'ed earlier. + + * code/game/: three REJECT for $SOS$. + * code/botlib/: lots REJECT for $SOS$. + + * Patch: patching up from demo source. + ln -s sos001204-rc4 work + diff -urbB sos001122-demo/ work > work.diff + ln -s cvs-1.27b/ work + patch -p0 < work.diff > work.patch + find cvs1.27b/ -name '*.rej' -print + + * CVS: going to check in this snapshot and tag it as + cvs tag id1-27b-loki001208-demo + Then patching up to RC4, as of sos001204-rc4 (no changes since, + should be equivalent to SOS tag "1.27g RC4" (raduffy), i.e. master. + + * TEST: installed demota/ from Win32 distribution. Binary + fails claiming "Corrupted pak0.pk3". Abandoned. + Note: a Linux demo for Q3TA has no priority. Most important is + the Q3A point release in time for Q3TA hitting shelves, followed + by testing for Q3TA. The source is in CVS and tagged (see above) + in case a demo matching the released files has to be provided + later. + + +2000-12-07 Bernd Kreimeier + + * TEST: compile and link - succeeds. + + * code/ui/ui_main.c: UI_StopServerRefresh now uaws. + New unused variables. + + * code/unix/unix_main.c: added Sys_LowPhysicalMemory() stub. + TODO: write Linux equivalent to GlobalMemoryStatus. + + * code/qcommon/common.c: Com_Memset/Com_Memcpy. Neither assembly + nor C versions included if not on Win32 i386. + TODO: using/porting assembly? + + * code/qcommon/files.c: unused variable. + TODO: fs_scrambledProductId unused if 0 for now. + Note: -DFS_MISSING for id's pak cleanup, not used. + + * TEST: compile and link - fails. + + * code/macosx/Client/Makefile.postamble: empty ORIG. + * code/macosx/Client/Makefile.preamble: ORIG. $(BOTLIB_OBJS) added. + + * code/server/sv_client.c: ORIG. Com_Memset. + * code/renderer/tr_shader.c: ORIG. Com_Memset, CIN_Shader. + * code/qcommon/vm_x86.c: ORIG. Com_Memcpy. + * code/qcommon/unzip.c: REJECT. Com_Memcpy, $SOS$. + * code/qcommon/qcommon.h: ORIG. PROTOCOL 47, plus Sys_LowPhysicalMemory. + * code/qcommon/md4.c: Com_Memset,Com_Memcpy (ORIG). + * code/qcommon/files.c (Sys_ConcatenateFileList): REJECT. + Our additons threw it off, plus $SOS$. + * code/qcommon/common.c: they fixed same unused variables (REJECT). + + * code/ui/ui_shared.c: additions (ORIG). + * code/ui/ui_gameinfo.c: COM_Compress added (ORIG). + * code/ui/ui_atoms.c: print statements removed (ORIG). + * code/ui/ui_main.c (UI_DoServerRefresh): REJECT on comment edit... + + * code/game/g_cmds.c (Cmd_VoiceTaunt_f): logic changed heavily. ORIG. + * code/game/q_shared.h: Q3_VERSION "Q3 Team Arena Demo 1.27b" + plus Com_Memset, Com_Memcpy, CIN_shader, COM_Compress. + * code/game/g_main.c: Cvar change only + * code/game/ai_dmq3.c: $SOS$. + + * code/client/snd_mix.c: Com_Memset + * code/client/client.h: additions (ORIG). + * code/client/snd_mem.c: see below. + * code/client/snd_dma.c: $SOS$ (CVS keyword). + + * code/client/cl_cin.c: they removed unused (REJECT). + * code/cgame/cg_servercmds.c: ORIG. compress, noTaunt etc. + * code/cgame/cg_main.c: ORIG. Conditonal branch, COM_Compress. + * code/cgame/cg_consolecmds.c: ORIG. Cvar values changed. + * code/cgame/cg_draw.c (CG_DrawTeamBackground): ORIG. + no reject but *.orig file created. I just mark spots were + code changed after verifying the patch succeeded. + + * code/cgame/cg_event.c: fixed reject (REJECT). + * code/botlib/: all *.rej here due to SOS/CVS $Keyword$. + TODO: preserve SOS comments/rev history somehow. + + * Patch: patching up to demo source. + ln -s sos001122-demo work + diff -urbB sos001119/ work > work.diff + ln -s cvs-1.26/ work + patch -p0 < work.diff > work.patch + find cvs1.26/ -name '*.rej' -print + + * CVS: going to check in this snapshot and tag it as + cvs tag id1-26w-loki001207 + to prepare for upgrading to RC4. I have already made + many more changes than I wanted to w/o getting any + closer to pinpointing the problem, I might as well + patch up to id's more current sources. + + * code/botlib/be_interface.c: initialize by memset. Turns + out that this fails in Export_BotLibSetup on BotSetupWeaponAI + loading "weapons.c" (from the pak, presumably) with an unknown + structure field number. Mismatch of datafiles vs. source again. + + TODO: id replaced memsets in later source. + TODO: have memsets on all exports and imports. + + * SOS: RC4 source should be tagged "1.27g RC4" (raduffy). + Unfortunately the Linux client doesn't care a bit. Show + History does work if from/to date differ by at least a + day, and it shows the tag on code/ (only that subtree), + but recursive get aborts halfway. + Manual: http://www.sourcegear.com/SOS/Doc/ + +2000-12-06 Bernd Kreimeier + + * TEST: accepting missing shaders now. No bots, but I can + actually enter the game and play (more than can be said for + classic Q3 right now). + TODO: Error: BotStartFrame: bot library used before being setup + + * code/renderer/tr_shader.c: took out assertion for now... + * TEST: now missiopack/cgame loads + TODO: tr_shader.c:2275: R_FindShaderByName: failed + TODO: searches ui/assets/3_cursor2.TGA, has ui/assets/3_cursor3.tga + + * code/unix/Makefile (MPCGOBJ): ui_shared.o (duh). + DONE: /cgamei386.so: undefined symbol: PC_Float_Parse + + * code/botlib/be_ai_goal.c: initialize campspots etc. This + might or might not fix this one (didn't get back to gdb due + to mouse-only navigation). + DONE: 0x80d1d5b in BotFreeInfoEntities () at be_ai_goal.c:447 + + * TEST: this time with missionpack/cgame loading... noy + TODO: TA menu blocked after end of intro movie + TODO: console in_mouse 1 doesn't grap pointer even on vid_start? + + * code/cgame/cg_newdraw.c: -Werror. + * code/unix/Makefile (MPCGOBJ): cg_newdraw.o was missing (duh). + DONE: missionpack/cgamei386.so: undefined symbol: CG_OwnerDrawVisible" + + * code/ui/ui_shared.c:1309 assign after bail on NULL. + DONE: segfault in Item_SetFocus (item=0x0, x=0, y=0) + + * TEST: new set of DLL's (this time hopefully correct). + All baseq3/ DLL's load, as does the missionpack/ UI DLL. + The menus now work in both (TA seems mouse-only on everything + but "Quit"). Segfault on delayed TA "Quit" (stack fubared): + #5 0x809fc28 in VM_Call (vm=0x88408a0, callnum=3) at ..//qcommon/vm.c:617 + #6 0x805aafc in CL_KeyEvent (key=9, down=qtrue, time=128644) cl_keys.c:1194 + TODO: TA menu's w/o mouse? + TODO: Win32 goes submenus but does not unfold + TODO: Linux does not go submenus + + * code/ui/ui_main.c: see below. + TODO: LCC gets fits - operands of = have illegal types + TODO: 'pointer to const unsigned char' and 'pointer to const char' + * code/ui/ui_shared.c: see below. + * code/ui/ui_gameinfo.c: see below. + * code/ui/ui_atoms.c: see below. + * code/game/g_bot.c: more cruft. + * code/cgame/cg_draw.c: loads of functions modified for + MISSIONPACK that aren't used at all for MISSIONPACK anymore. + Development relics. + + * code/cgame/cg_consolecmds.c: -Werror. + Note: due to Makefile error never ever compiled... + + * code/unix/Makefile: fixed various dependency errors + for game and ui library. + TODO: create a new Makefile with patsubst and rules. + TODO: why C_ONLY in the i386 dedicated server? + + * code/unix/unix_main.c: use dlerror() excessively. + Littered more unused DLL related functions with assert(0). + TODO: clean up Sys_Load/UnloadDll (a real mess) + TODO: remove Zoid code cruft (unused per-DLL functions) + + * code/game/bg_misc.c: changed G_Printf for Com_Printf. + This was undefined in baseq3/uii386.so preventing loading. + + * TEST: +set sv_pure 0 +set vm_game 0 +set vm_cgame 0 +set vm_ui 0 + Note: so far I used only the game DLL.. duh. + UI DLL fails to load: missing G_Printf. + + * code/unix/Makefile: -DMALLOC_CHECK in addition to + the -DZONE_DEBUG I have used since switching to calloc. + Using MALLOC_CHECK=1 for now, might use 2 if something + comes up. + + * code/renderer/tr_init.c (GL_SetDefaultState): it does get + called, but does not show up in the log. + + * TEST: tried executing a script - get bounced. + TODO: is there any way to jump into a map? + TODO: cl_cinematics 0 (supress all fullscreen RoQ) + Next: used r_logfile 200 in Win32 (RC4) and Linux. + There is a buckload of setup code seemingly not done + at all in Linux? Either that, or logging is enabled + with a delay in Linux. + + * code/unix/linux_glimp.c: fixed autorepeat (H2/Fakk2 way). + +2000-12-05 Bernd Kreimeier + + * code/renderer/tr_mesh.c: added assert there. + * TEST: menus and in-game drawing are just as they were with + the initial SOS001119 port. In addition: + R_AddMD3Surfaces: no such frame 0 to -2147483477 + for 'models/players/xaero/upper.md3' + R_AddMD3Surfaces: no such frame -2147483477 to 171 + R_AddMD3Surfaces: no such frame 171 to -2147483498 + ad nauseam (used as my player model). + Triggered: haveing a trRefEntity_t *) 0x41dbbd00 with + frame = -2147483477. Might be a red herring (PRINT_DEVELOPER), + ignore for now. + + * code/ui/ui_main.c: missing return. + * code/ui/ui_shared.c: excess byte in initializer (which gcc + did not caught, but LCC did). Also LCC complains about + missing returns, but gcc doesn't (neither says unreacheable + code though). If necessary (MsVC?) guard with Q3_VM. + + * code/q3_ui/ui_ingame.c: see below. + * code/q3_ui/ui_atoms.c: voidfunc_f. LCC warns about conversion + from `pointer to void' to `pointer to void function(void)' + being compiler dependent. Casting NULL. Guess what, doesn't fix + it either. + TODO: do not use these cursed scripts to generate VM code, + we do not have proper rules for LCC/q3asm, thus the files never + get updated. + + * code/unix/Makefile: for paranoia's sake recreated the 1.17 + compile for the UI DLL (where only q_shared/math were actually + compiled as DO_SHLIB_CC. + Later: switched to different gcc. + + * STATIC: remaining problems are vmMain (same entry point for all + DLL's), could use cgMain, uiMain and gMain here for HARD_LINKED. + Note: I don't think id has used this in ages. + Plus all the collisions in *_syscalls.c, which simply can't be + fixed cheaply. None is the superset of 2 others, neither seems + w/o overlap to others. Full stop. + + * code/botlib/be_aas_move.c: see below. + * code/game/ai_dmq3.c: VEC_UP/DOWN, MOVEDIR_UP/DOWN now static. + See also game/g_utils.c for existing static duplicates. + + * code/game/q_shared.h: #define stricmp strcasecmp + * code/unix/Makefile: no mo' -Dstricmp=strcasecmp, see q_shared.h + Also: no mo' -I/usr/include/glide, no FX + TODO: are we building against system GL headers? ../Mesa/? + + * code/q3_ui/ui_atoms.c: comment on duplication + * code/cgame/cg_drawtools.c: use UI/CGAME_HARD_LINKED on UI duplicates + TODO: does this UI_ code in cg_drawtools/ui_atoms belong into ui_shared? + + * code/unix/Makefile: use -DQ3_STATIC + * code/game/q_shared.h (*_HARD_LINKED): trigger on Q3_STATIC + Later: collision between UI and CGAME is still there. This fixed + the Com_Error, Com_Printf issues though + + * code/unix/Makefile ($(B)/q3static/ai_vcmd.o): this file was + missing, hence undefined symbol. + ($(B)/baseq3/game/ai_vcmd.o): same here. + ($(B)/missionpack/game/ai_vcmd.o): same here. + + * STATIC: cg_syscalls.c, g_syscalls.c and ui_syscalls.c alias. + Multiply defined symbols: + Com_Error, Com_Printf + VEC_UP, VEC_DOWN + MOVEDIR_UP, MOVEDIR_DOWN + vmMain + dllEntry + PASSFLOAT + trap_Error + trap_Milliseconds + trap_Argc + trap_Argv + trap_FS_FOpenFile + trap_FS_Read + trap_FS_Write + trap_FS_FCloseFile + trap_FS_GetFileList + trap_R_RegisterModel + trap_R_RegisterSkin + trap_R_RegisterFont + trap_R_RegisterShaderNoMip + trap_R_ClearScene + trap_R_AddRefEntityToScene + trap_R_AddPolyToScene + trap_R_AddLightToScene + trap_R_RenderScene + trap_R_SetColor + trap_R_DrawStretchPic + trap_R_ModelBounds + trap_UpdateScree + trap_S_StartLocalSound + trap_S_RegisterSound + trap_Key_IsDown + trap_Key_GetCatcher + trap_Key_SetCatcher + trap_GetGlconfig + trap_PC_AddGlobalDefine + trap_PC_LoadSource + trap_PC_FreeSource + trap_PC_FreeSource + trap_PC_ReadToken + trap_PC_SourceFileAndLine + trap_S_StopBackgroundTrack + trap_S_StartBackgroundTrack + trap_RealTime + trap_CIN_PlayCinematic + trap_CIN_StopCinematic + trap_CIN_RunCinematic + trap_CIN_DrawCinematic + trap_CIN_SetExtents + trap_MemoryRemaining + trap_SendConsoleCommand + trap_Cvar_Register + trap_Cvar_Update + trap_Cvar_Set + trap_Cvar_VariableValue + trap_Cvar_VariableStringBuffer + trap_RealTime + trap_SnapVector // used in game/bg_*.c, needs conditional + More aliasing between ui_atoms.c and cg_drawtools.c: + UI_DrawBannerString + UI_ProportionalStringWidth + UI_ProportionalSizeScale + Undefined symbol: ai_team.o: In function `FindHumanTeamLeader': + ai_team.c:1899: undefined reference to `BotVoiceChat_Defend' + Note: + + * code/game/g_main.c: unused. + * code/game/g_arenas.c: unused. + * code/game/ai_team.c: init. + * code/game/ai_dmnet.c: /* in comment (odd). + Note: why do these come up now but not earlier? + TODO: the make dependencies might target wrong files. + + * code/unix/Makefile (TARGETS): added q3static. + Note: this is baseq3/ + + * TEST: +set r_logfile 100. It seems that the addition of + code (add an assertion etc.) changes the behavio of the binary. + The intro cinematics code seems to suffer first - didn't play, + then played, then (another assert added) doesn't play. Watch + out for (missionpack): + UI_CIN_PlayCinematic + SCR_PlayCinematic( mpintro.roq ) + trFMV::play(), playing mpintro.roq + Also fails to exit cleanly: break gives + #0 0x401919ee in __select () + #1 0x400bbcb8 in __DTOR_END__ () + #2 0x4004baa1 in _XSend () + #3 0x452b009f in GLXRenderFlush () + #4 0x804ce0c in _XRead () + #5 0x40680813 in ?? () + Stack is corrupted. + Note: ~/.q3a/gl.log + TODO: write per-frame files (see Heretic2) + TODO: add Heretic2 QGL (more detail) + + * code/unix/linux_qgl.c (QGL_EnableLogging): fixed countdown + (i.e. propagated changes from win32/, see Fakk2). + + * code/unix/linux_glimp.c: fixed QGL_EnableLogging argument + to avoid cast error (always qfalse). + + * code/unix/Makefile (DEBUG_CFLAGS): use ZONE_DEBUG. + + * code/qcommon/common.c: replaced malloc with calloc calls. + + * code/q3_ui/ui_local.h: have to use ui/ui_public.h + * code/cgame/cg_servercmds.c: requires ../ui/menudef.h + + * code/cgame/cg_consolecmds.c: ui/ui_shared.h is unique. + * code/q3_ui/ui_public.h: make sure this won't be compiled. + * code/client/client.h: we have to include ui/ui_public.h. + Note: id is obviously maintaing only the ui/ headers, so the + headers in q3_ui/ are deprecated. + + * code/renderer/tr_shader.c: added assertions (see Ryan's Fakk2 + problems with missing shaders). + + * code/game/g_cmds.c: below. + * code/game/ai_vcmd.c: below. + * code/game/ai_team.c: below. + * code/game/ai_dmnet.c: below. + * code/game/ai_dmq3.c: below. + * code/game/ai_chat.c: below. + * code/game/ai_cmd.c: ../../ui/menudef.h (new Q3TA script directory). + + * code/cgame/cg_newdraw.c: make sure it won't compile w/o MISSIONPACK. + + * code/cgame/cg_servercmds.c: below. + * code/cgame/cg_event.c: below. + * code/cgame/cg_consolecmds.c: below. + * code/client/keys.h: below. + * code/client/client.h: below. + * code/q3_ui/ui_local.h: include from ../q3_ui/ not ../ui/. + Note: id seems to intentionally use the header from the new ui/. + + * Makefile: checked -I$(UIDIR), there is no such. That means all + files include directly, which means all (including Q3) are using + the new ui/ headers. + +2000-12-04 Bernd Kreimeier + + * RC4: released as 362101115 Dec 4 11:40 TA_Q3A_RC4.zip + + * TEST: the corrupted menu problem is back :-(. Looks like I am in + for a static link next. + + * code/unix/Makefile (clean2): fixed (not all new OBJ covered). + * code/q3_ui/ui_teamorders.c: -Werror. + * code/q3_ui/ui_team.c: -Werror. + * code/q3_ui/ui_qmenu.c (Bitmap_Draw): -Werror. + * code/q3_ui/ui_mods.c (UI_Mods_LoadModsFromFile): unused. -Werror. + * code/q3_ui/ui_controls2.c: -Werror. + * code/q3_ui/ui_atoms.c: -Werror + * code/null/null_client.c: -Werror. + * code/unix/linux_joystick.c: -Werror. + * code/unix/linux_glimp.c: -Werror. + * code/unix/linux_qgl.c: -Werror. + * code/unix/unix_shared.c: -Werror. + * code/unix/unix_net.c: -Werror. + * code/unix/linux_local.h: added missing prototypes. + * code/unix/unix_main.c: -Werror. Includes linux_local.h + * code/jpeg-6/jdmainct.c: see below. + * code/jpeg-6/jcmainct.c: variables called "main" (*moan*) + * code/jpeg-6/jcdctmgr.c (forward_DCT): -Werror. + * code/botlib/l_script.c (PS_ReadLiteral): -Werror + * code/botlib/l_precomp.c (PC_AddBuiltinDefines): -Werror. + * code/botlib/be_interface.c: -Werror. + * code/botlib/be_aas_reach.c: -Werror + * code/botlib/be_aas_cluster.c: -Werror + * code/game/be_aas.h: -Werror. + Note: MrElusive accumulates a lot of code history in nested comments, + which gcc doesn't like at all. #if 0'ed to avoid. + * code/qcommon/vm_interpreted.c: -Werror. + * code/qcommon/unzip.c: -Werror. + * code/cgame/cg_servercmds.c: -Werror. + * code/cgame/cg_main.c: -Werror. + * code/cgame/cg_drawtools.c: -Werror. + * code/game/bg_misc.c: -Werror. + * code/game/be_ai_move.h (bot_avoidspot_s): added. + * code/botlib/be_ai_move.c: removed typedef struct bot_avoidspot_s + * code/client/snd_mix.c: -Werror. + * code/qcommon/md4.c: -Werror. + * code/qcommon/common.c: -Werror. + * code/client/cl_keys.c: -Werror. + * code/client/cl_cin.c: -Werror, init local variables. + * code/unix/Makefile: -Werror. need -O for -Wall for uninitialized + Note: the above is the list of files that got touched during a pass + with -g -O -Werror -Wall flags (in the hope of finding uninitialized + memory and ambiguous statements). Most of the above are simply + unused variables (or even code). + + TEST: RC3 data files, but DLL's. + TODO: TA gets stuck in initial sound, doesn't play cinematics (sometimes) + TODO: Q3 intro movie looses sound after Sarge gets teleported + TODO: Q3 ingame renders world, weapon, muzzleflash, hud, can shoot, + TODO: but no movement, hud background is fubared. + + * code/cgame/cg_main.c: cg_singlePlayerActive + + * code/q3_ui/ui_login.c: doesn't seem to be used? + * code/game/g_rankings.c (G_RankRunFrame): doesn't seem to be used. + * code/q3_ui/ui.sh: disabled this. + * code/q3_ui/q3_ui.sh: changed include path to ../q3_ui/ (duh). + + * code/game/game.sh: changed include path to ../q3_ui/ which + is not in the Win32 batch file. + * code/cgame/cg_rankings.c: this does not seem to be included. + * code/cgame/cgame_ta.sh: added -DCGAME. Also added cg_syscalls.c + to build (also missing in Win32). + + * code/cgame/cgame.sh: added -DCGAME (see cgame.bat). Also + changed include path to ../q3_ui/ which is not in the Win32 + batch file. Also added cg_syscalls.c to build (missing in + Win32). + +2000-12-01 Bernd Kreimeier + + * RC3: released as of sos001201 / Q3 1.27f + + * code/unix/Makefile: more fixes with clean build. The + changes made fix the menu rendering for Q3 but not TA. + Ingame graphics still broken. + + * code/game/game_ta.sh: created. Use game_ta.q3asm here. + * code/game/game.sh: no -DMISSIONPACK + * code/game/game_ta.q3asm: CR/LF, /. + + * code/cgame/cgame_ta.sh: created. Use cgame_ta.q3asm here. + * code/cgame/cgame.sh: no -DMISSIONPACK. No cg_newdraw, ui_shared. + * code/cgame/cgame.q3asm: No cg_newdraw, ui_shared. + CR/LF, /, cg_newDraw, and the output path/name. + + * code/q3_ui/q3_ui.q3asm: output to ui not q3_ui... + + * code/cgame/cg_event.c: cg_singlePlayerActive used here. + TODO: guard by MISSIONPACK + * code/cgame/cg_local.h: named q3print_t enum. Cvar + cg_singlePlayerActive for both Q3 and TA. + + + * code/unix/Makefile: cleanly separate B/baseq3/ and + B/missionpack/ subtrees during build. While new and old + UI are in separate directories, the cgame/ and game/ + are shared, with conditional -DMISSIONPACK compile + and different files includeds (cd_draw, cg_newdraw). + That means twice the number of targets (3 DLL's, 3 QVM's, + times two), and different build rules. + TODO: carefully check Win32 build for (other) conditionals + TODO: carefully check Win32 build for link lists + + * CVS: ui/, code/ui, botfiles/ and subdirectories are added. + The code/macosx/ directory turned out to be a real pain that + had to be edited manually, throwing out CVS/ directories in + the tree that had been created by SOS as they are in id's + repository: + code/macosx/Client/CVS + code/macosx/Client/PBUserInfo/CVS + code/macosx/Client/Quake3.nib/CVS + code/macosx/Common/CVS + code/macosx/DedicatedServer/CVS + code/macosx/DedicatedServer/PBUserInfo/CVS + Now tagged + cvs tag id1-26y-loki001119 + TODO: there are several new files not yet linked? + + * ChangeLog: merged the Changelog from the bk00119 working + branch (initial Q3TA port) based on sos001119 snapshot. Also + merged the source tree with cvs-1.17. + In the ChangeLog below *** MISSIONPACK *** indicates work + that was done on the branch (code-sos/ prefix in files). + The cvs update of this will be tagged with + cvs tag id1-26y-loki001119 + Use this tag to hunt for possible Linux fixes that got lost + (i.e. got dropped by id since id000516 and were thus not in + sos001119, but did not show in diff id000516 cvs1-17). + New directories in CVS: botfiles/, ui/. + Missing from SOS/Missionpack: SDK directories. + common, lcc, libs, q3asm, q3data, q3map, q3radiant. + + + * ssreport.txt: below. + Note: watch for files called "ssreport.txt", that's id ChangeLog. + * ui/ui_syscalls.asm: below. + * q3_ui/ui_syscalls.asm: below. + * game/g_syscalls.asm: below. + * cgame/cg_syscalls.asm: below. + * bspc/linux-i386.mak: below. + * bspc/lcc.mak: below. + * botlib/linux-i386.mak: below. + * botlib/lcc.mak: below. + * A3D/a3d_console_variables.txt: CR/LF issue (minimize diffs). + + * CVS: the checked bk001119 work copy of the sos001119 initial + checkout (completed with everything in the SOS "Missionpack" + tree, i.e. botfiles/ and botfiles.* added), copied over the + cvs-1.17 checkout. + Note: in these cases, BEWARE ui -> q3_ui/ links, and different + ChangeLogs. Also "make clean" helps. + + * unix/unix_net.c: below. + * unix/unix_main.c: below. + * unix/matha.s: below. + * unix/linux_qgl.c: below. + * unix/linux_glimp.c: see also linux_joystick.c. + * server/sv_client.c: below. + * renderer/tr_surface.c: below. + * renderer/qgl.h: below. + * qcommon/qcommon.h: below. + * qcommon/files.c: below. + * qcommon/common.c: below. + * q3_ui/ui_demo2.c: below. + * mac/mac_net.c: below. + * mac/mac_glimp2.c: below. + * game/surfaceflags.h: below. + * game/bg_lib.c: checked against id00516/cvs-1.17a diff. + * bspc/bspc.c: TH_AASToTetrahedrons call removed since id000516. + Note: our final compare of id000516 against cvs-1.17a, making sure + that all these differences are in bk001119 (initial Q3TA port). + If id branched the Q3TA base off before id000516 we might be screwed. + Note: I do not diff against bk000520, which had some minor changes + against id000516 (check VectorArrayNormalize, OTConfiguration), which + seem consistent with me taking a pre-id000516 source snapshot for that + working branch. + +2000-11-30 Bernd Kreimeier + + * TEST: compiled using the symbolic link ui/ -> q3_ui/. + Had to undo one CVS change, regarding + code/cgame/cg_syscalls.asm + code/game/g_syscalls.asm + code/q3_ui/ui_syscalls.asm + These files are neither generated by Win32 cgame.bat + nor cgame.sh (etc.), thus seemingly maintained by hand. + cvs tag pr1-17-loki001130b + should be used if somebody needs this 1.17 snapshot + (which, remember, is post-release, with additional fixes). + Later: + cvs tag pr1-17-loki001130c + includes the full ChangeLog (duh). + + * CVS: up until cvs-1.17-001130, code/ui/ contained the + Q3 code for the UI QVM/DLL. In Q3TA, this code has been + moved to code/q3_ui/, while at the same time the new + (scripting driven) UI code for Q3TA was maintained in + code/ui/. To preserve the history of code/ui/, it has been + renamed to q3/ui/ in the CVSROOT. + Note: this will BREAK all cvs-1.17 and before checkouts. + To compile earlier versions, move or link q3_ui/ to ui/. + The code has been tagged + cvs tag pr1-17-loki001130 + after the change. + DONE: remove code/*/vm/*.asm from CVSROOT + Note: this includes code/*/*.asm files (from *_syscalls.c). + These were originally tracked in CVS, but if we need + comparison of q3asm output or QVM files we can rely + on the Win32 and Linux SDK now. These files have been + physically removed from CVS now, followed by + cvs tag pr1-17-loki001130a + +2000-11-30 Bernd Kreimeier *** MISSIONPACK *** + + * RC2: new ZIP file (another 360M for convenience). + + * SOS: new CVS module, Quake3_sos. This will be used to track + the unchanged SOS checkouts from id. As their repository + is read-only, and there is no estimate on when changes might + be backpropagated there, I will track their changes in a + separate module, and update our local Quake3 module + accordingly. This is effectively "tracking 3rd party" + w/o import and half-automated, forced mergers - in other + words, we now branch starting with our post-1.17 changes, + for the benefit of moving at all. + Baseline is a slightly changed PR-1.17 id000516 source dump + (essentially ui/ moved to q3_ui for continuity, and CR/LF etc.). + cvs import Quake3_sos id000516 pr1-17-win32 + cvs import Quake3_sos sos001119 pr1-26-win32 + cvs import Quake3_sos sos001120 pr1-26-win32-001120 + cvs import Quake3_sos sos001121 pr1-26-win32-001121 + cvs import Quake3_sos sos001122 pr1-26-win32-001122 + cvs import Quake3_sos sos001122-demo pr1-26-win32-demo + This is about the 1.26w Team Arena Win32 demo release, give or + take a couple of lines. Has Q3_VERSION "Q3 Team Arena Demo 1.27b". + cvs import Quake3_sos sos001123 pr1-26-win32-001123 + cvs import Quake3_sos sos001126 pr1-26-win32-001126 + Now track id versions (see code/game/q_shared.h:Q3_VERSION) + cvs import Quake3_sos sos001128 pr1-27c-win32-001128 + With 1.27d they switched from Demo to full version (RC1). + cvs import Quake3_sos sos001129 pr1-27d-win32-001129 + cvs import Quake3_sos sos001130a pr1-27d-win32-001130a + Now switched to 1.27e. This import is done from the SOS + working directory. + cvs import Quake3_sos sos001130b pr1-27e-win32-001130b + Note: SoS created rwx attributes which are luckily fixed + automagically during import. It is also seemingly incapable + to compare files, and leave files that have not changed the + hell alone. I can't do cvs update due to the $..$ tags in + the original files (which CVS can't be told to ignore), + so I have to do import (creating a load of vendor tagged + branches), but at least cvsweb and cvs get the revisions + right. + + * code-sos/unix/Makefile: added linux_joystick + * code-sos/unix/linux_local.h: match mac/ and win32/, for prototypes. + + * code-sos/unix/linux_joystick.c: new file, code from linux_glimp.c + Note: decided to separate this, as (a) we might edit/extend + a lot, (b), it's not in the id tree, (c) it's not GL, (d) + there might be even more oddball devices. Anything that + cuts down on diffs. + + * code-sos/unix/linux_glimp.c (Q_stristr): const return (cvs1.17). + Also (XLateKey): added more keyboard mappings (ASCII on + upper row digits) (cvs1.17). Added in the minimal joystick + hooks (cvars, function calls). Fixed joystick cvar naming + to match win32 (kept joystick_threshold). + TODO: joystick stubs for dedicated? + + * CVS: I have to move up to 1.27d (data, Win32 networking). + With exception of linux_glimp.c (mostly joystick code), + all cvs1.17 changes should now be in the work snapshot + based on the first sos001119 we got from id. There are + also some additional changes in there already, thus I'll + move the (buggy) 1.26 snapshot into CVS before adding even + more differences. + + +2000-11-29 Bernd Kreimeier *** MISSIONPACK *** + + * RC1: TeamArena_Q3A_RC1.zip. Source has moved from + Q3VERSION "Q3 Team Arena Demo 1.27c" to "Q3 1.27d" now. + + * code-sos/qcommon/common.c: added Com_InitPushEvent(). Also + increased MAX_PUSHED_EVENTS to 256. + Note: this is another case of buffer memory not zero'ed. + Com_EventLoop, fixed evTime to evType in debug print. + + * TEST: baseq3/ + +set sv_pure 0 +set vm_game 0 +set in_mouse 0 +set developer 2 + TODO: Team Arena in menu leads to RE_Shutdown(1) and locks + TODO: can't play game + TODO: shaders can't load *.tga, *.jpg files are there + TODO: DO_CC linking for DLL's, DO_SHLIB_CC only for export? + TODO: ERROR: Bad player movement angle + TODO: Warning: cvar "..." given initial values: "..." and "..." + TODO: TA demo ERROR: CL_ParseServerMessage: Illegible server message + TODO: WARNING: Com_PushEvent overflow + + * code-sos/qcommon/files.c: add NULL filter for our Sys_ListFiles calls. + * unix/unix_shared.c (Sys_ListFiles): signature has changed, + additional Sys_ListFiles argument now. + + * code-sos/unix/unix_net.c (Sys_GetPacket): see below (readcount=0). + * code-sos/unix/unix_main.c: see below (Mike's and my changes to DLL + loading, my event buffer clear fixes). + * code-sos/unix/linux_qgl.c (QGL_Init): see below (__FX__ guards). + TODO: abstract WGL/GLX and end unfortunate QGL duplication. + TODO: spice up QGL with Linux H2 full version. + * code-sos/q3_ui/ui_demo2.c: fix on demo names - no Q_strupr(demoname). + Note: in CVS this fix is in ui/ui_demo2.c. CVS is screwed by + id choosing the old name for new directory... + TODO: manual intervention on "ui goes q3_ui" in CVSROOT? + * renderer/qgl.h: see below (__FX__ guards). + * qcommon/files.c: migrated in the 1.17cvs changes against the + id000516 code dump, i.e. the (not marked - boo hiss) mkv changes. + Note: all the above is based on a diff of the last id code dump + pre-1.17 against our CVS, with those fixes now migrated into the + sos1.26 snapshot. + TODO: move in joystick code. + TODO: replace XAutoRepeatOn/Off with filter (focus). + TODO: DGA 2.0 and such. + + * code-sos/game/q_shared.c: valid compare for NULL strings + * code-sos/unix/unix_main.c: QRTLD, and now using RTLD_NOW. + Note: it is a bad idea to load game DLL's that are missing symbols. + + * code-sos/ui/ui_main.c: see below. + * code-sos/game/g_main.c: see below. + * code-sos/q3_ui/ui_main.c: see below. + * code-sos/cgame/cg_main.c: made cvarTable and cvarTableSize static. This resolved + a segfault related to traversing the UI table during Init. + Note: there is a segfault related to this variable being out of bounds. + Different struct size in global variables possible aliasing between the + DLL's. + + * code-sos/unix/unix_main.c (Sys_Error): assert(0), no exit in debug. + * code-sos/game/q_shared.c: now aborts on NULL destination. Also DPrintf's + on bogus excess copies. + TODO: make all those string functions safe, at least assert. + * code-sos/server/sv_init.c: comment in SV_Init + // init the botlib here because we need the pre-compiler in the UI + Called in qcommon/common.c:Com_Init, were CL_Init is called afterwards... + * code-sos/server/sv_bot.c: the botlib_import is filled here. + * code-sos/unix/unix_main.c (Sys_GetBotLibAPI): RTLD_NOW. Which is for naught, + as this code is not used and has never been used. assert(0) + + * code-sos/botlib/be_interface.c: botimport supposed to be set here. + * code-sos/botlib/l_memory.c: segfault with q3_ui/ DLL. + #1 0x80e23ec in GetMemory (size=35) at ..//botlib/l_memory.c:331 + 331 ptr = botimport.GetMemory(size + sizeof(unsigned long int)); + as botimport is completely NULL'ed. + + * code-sos/q3_ui/q3_ui.sh: created from ui/ui.sh 1.17 + + * code-sos/q3_ui/q3_ui.q3asm: unfubared (CR/LF, / path). + + * code-sos/unix/Makefile: added q3_ui/ make targets (basically + ui/ targets from CVS 1.17 Makefile for starters). + + * code-sos/q3_ui/: this is the old UI code, which does not use + ../ui/menus.txt (see ui/ui_main.c). In other words, + the code in ui/ now has to be compiled or qvm'ed + for missionpack/, but to create the necessary DLL or + QVM modules for baseq3/ we need to use q3_ui/. + + +2000-11-27 Bernd Kreimeier *** MISSIONPACK *** + + * code-sos/game/bg_lib.c: ld problem with a custom "tan(..)" + TODO: loooking forward to SIGFPE on this code base. + + * code-sos/ui/ui_util.c: this file is empty. + + * code-sos/ui/ui.sh: new files: + ui_shared.c + ui_util.c + Replaced by the /ui/*.menu files: + q3lcc: can't find `../ui_cdkey.c' + q3lcc: can't find `../ui_ingame.c' + etc. + + * code-sos/cgame/cgame.q3asm: added cg_newdraw entry. + Also added ui_shared entry. + * cgame/cgame.sh: added cg_newdraw.c entry. + Also added ../ui/ui_shared.c entry. + + * code-sos/cgame/cg_newdraw.c: renamed (was cg_newDraw.c mixed case). + Note: the infidels have taken over. + + * cgame/cgame.sh: added -DMISSIONPACK. + Note: w/o, q3lcc complains + ../cg_event.c:204: undeclared identifier `cg_singlePlayerActive' + ../cg_event.c:204: left operand of . has incompatible type `int' + which indicates that this source does not compile w/o MISSIONPACK + anymore. The baseq3/pak4.pk3 file in the Q3TA snapshot archives + are dated + 284464 11-10-00 14:02 vm/cgame.qvm + 463940 11-14-00 14:47 vm/qagame.qvm + 271596 11-14-00 14:48 vm/ui.qvm + the code dump is from 11-19. + Note: Make does not abort on q3lcc complains + + * code-sos/game/game.sh: also added ai_vcmd.c entry. + + * code-sos/ui/ui.q3asm: fubared (below). In addition, this is + the only one to have a + -o "/tmp/quake3/missionpack/vm/ui" + line in it. Given that the other 2 QVM modules are + also dependend on -DMISSIONPACK, this seems a real mess. + For now using the same path as the other 3. + * code-sos/cgame/cgame.q3asm: below. + * code-sos/game/game.q3asm: fubared. Fixed CR/LF and \ in paths + again (read by q3asm called by game.sh called by make). + * unix/Makefile: updated fpor DLL/QVM. + Note: also shell scripts to use q3lcc not lcc. + + +2000-11-27 Bernd Kreimeier + + * code/unix/Makefile: now expects a run/ directory + relative (between this, the Loki standards, and the + utility code in the same repository, it's ever so + slightly less dorky). + TODO: fix broken copyfiles target etc.pp. + + * code/game/bg_lib.c: turns out the changes I + undid 001120 were affecting original Zoid + Linux port related defines, which break VM + compile. Mike fixed those (which I unfixed + when referring to the latest id code that does + not contain these patches). However, they + duplicate ANSI libc symbols, so the guards might + be wrong. The symbols are missing when compiling + for VM, so I now use the existing lcc -DQ3_VM + flag: + //#if !defined ( _MSC_VER ) && ! defined ( __linux__ ) + #if defined ( Q3_VM ) + This will break DLL compile on non-ANSI platforms, + which will have to be added to the conditional then. + + * code/ui/ui.sh: below. + * code/game/game.sh: below. + * code/cgame/cgame.sh: Linux SDK installs q3lcc to + avoid collisions with regular lcc pre-installs. The + scripts fail with "lcc not found", but do not abort + the Makefile. + Note: now that VM code gets actually built, there + are errors: + g_main:648 ERROR: symbol vsprintf undefined + bg_pmove:1221 ERROR: symbol abs undefined + q_math:4309 ERROR: symbol fabs undefined + q_shared:2801 ERROR: symbol tolower undefined + q_shared:2862 ERROR: symbol toupper undefined + ai_dmq3:208 ERROR: symbol atoi undefined + ai_cmd:4951 ERROR: symbol sscanf undefined + + +2000-11-20 Bernd Kreimeier + + * TEST: test compile of pr-1.17+cvs fixes segfaults due + to new baseq3/pak4.pk3 + Note: to self ... 1.17 is not compatible with new files. + Checking into CVS next. + + * code/: changes applied by us that are not in id's code base + affect q_shared.c (NULL in Q_stricmp), files.c (FIXME fs_cdpath, + Sys_ConcatenateFileList, ui_demo2.c (demo no tolower on linux). + In unix/ linux_glimp.c (joystick code), qgl.h, linux_qgl.c (__FX__), + unix_main.c (dlopen bug and event buffers), unix_net.c (readcount), + matha.s (assembly warning). + + * code/server/sv_client.c (SV_WriteDownloadToClient): + No effective change on FS_SV_FOpenFileRead call, they reworked + autodownload some more seemingly. + + * code/renderer/tr_surface.c: VectorArrayNormalize + + * code/qcommon/qcommon.h: see below. + * code/qcommon/files.c: Com_ReadConfigs removed. + * code/qcommon/common.c: removed Com_ReadConfigs, + textual replacement of body in Com_Init. + + * code/mac/mac_net.c: not applied (undone by id) + OTConfiguration *config <> OTConfigurationRef config + + * code/mac/mac_glimp2.c: r_colorbits->integer > 16 + * code/game/surfaceflags.h (CONTENTS_BOTCLIP): added. + + * code/game/q_shared.h: not applied (undone by id) + #if defined(ppc) || defined(__ppc) || defined(__ppc__) + #define idppc 1 + #else + #define idppc 0 + #endif + + * code/game/q_math.c: added another CPP line to guard + BoxOnPlaneSide, removed WIN32 guard. + TODO: this could be broken code guarded in all current + compiles... + + * code/game/bg_lib.c: left Q#_VM guard for typedef cmp_t + Added !defined( __linux__ ) for tolower and atoi. + Note: the changes above relate to the very last code update + from id prior to the 6 month blackout, which were not in + CVS when Michael made his updates. Needed to establish the + baseline for the new patch. Source dump 1.17.00520, against + SOS 1.26w-001119 version. + +2000-11-20 Bernd Kreimeier *** MISSIONPACK *** + + * TEST: running against the data up to TeamArena_Q3A_001109.zip + Hunk_Clear: reset the hunk ok + Program received signal SIGBUS, Bus error. + "q3dm2", killBots==qtrue + #0 CM_ClearMap () at ..//qcommon/cm_load.c:644 + #1 0x80884a7 in SV_Map_f () at ..//server/sv_ccmds.c:159 + #2 0x8072579 in Cmd_ExecuteString (text=0xbffff4b0 "spmap q3dm2") at ..//qcommon/cmd.c:591 + #3 0x8071dfe in Cbuf_Execute () at ..//qcommon/cmd.c:190 + #4 0x80763f7 in Com_Frame () at ..//qcommon/common.c:2547 + #5 0x8130d6b in main (argc=13, argv=0xbffff984) at ..//unix/unix_main.c:953 + #6 0x40100cb3 in __libc_start_main (main=0x8130bc4
+ Not reproducible (screen stayed black). + + * TEST: +set developer 1, same for Win32 and Linux: + Can't find gfx/misc/flare.tga + Can't find gfx/misc/sun.tga + Can't find gfx/misc/console02.tga + Can't find vm/ui.map + Can't find textures/sfx/logo512.tga + Can't find gfx/colors/black.tga + Can't find models/mapobjects/banner/banner5_2.md3 + Can't find models/mapobjects/banner/banner5_1.md3 + Can't find textures/sfx/firegorre2.tga + Can't find textures/sfx/bolts.tga + Can't find menu/art/unknownmap.tga + + * Q3TA: after nearly 6 months, a code update from id. SOS access + even. Got it to compile, link and start, but it's currently broken + (menu doesn't render in full, can't get into game etc.). Need + a baseline 1.17 to diff against. Last code dump was May 16, with + bspc code updated May 19. Checking working directory of bk000520 + against CVS next (Mike's fixes never made it into id's codebase + or a post 1.17 release, neither did my fixes as released in the + point release version 1.17). + +2000-11-19 Bernd Kreimeier *** MISSIONPACK *** + + * TEST: Win32 install as tested with 1.26w. quake3.x86 (Q3A game) + Warning: cvar "r_uifullscreen" given initial values: "1" and "0" + Warning: cvar "r_inGameVideo" given initial values: "1" and "0" + ^3WARNING: sound/feedback/hit.wav is a 8 bit wav file + (on windows, sound/weapons/weapon_hover.wav is missing...) + Menu only partially displayed in TA and baseq3 play, menu itself + seems to work. Freetype? + WARNING: Com_PushEvent overflow + + * code-sos/game/game.sh: not in SOS, moved in from CVS snapshot. + + * code-sos/qcommon/common.c: conditional DEDICATED to get rid off + CL_ShutdownCGame/CL_ShutdownUI/CIN_CloseAllVideos. + Same for UI_usesUniqueCDKey: dedicated server does not + write CD key file. + TODO: check whether there is an unneeded "read CD key" + for dedicated server. + + * code-sos/null/null_client.c (CL_ShutdownAll): added dummy. + + * code-sos/unix/Makefile: server/sv_net_chan.o for dedicated server. + + * code-sos/null/null_snddma.c: fixed S_RegisterSound signature. + + * code-sos/client/snd_mix.c: snd_p, snd_linear_count, snd_out + can't be static, as used by unix/snd_mixa.s. + + * code-sos/unix/Makefile: added to the executable target: + renderer/tr_font.c + client/cl_net_chan.c + server/sv_net_chan.c + Also added a lot of jc*.c files to build, to fix unresolved + symbol errors. + TODO: is there unused jpeg-6/jd*.o code linked in now? + + * code-sos/ft2/smooth.c: includes ftgrays.c, ftsmooth.c + + * code-sos/ft2/truetype.c: ttdriver.c, ttpload.c, ttgload.c, ttobjs.c. + Also (see ftoption.h) TT_CONFIG_OPTION_BYTECODE_INTERPRETER ttinterp.c + + * code-sos/ft2/sfnt.c: includes ttload.c, ttcmap.c, sfobjs.c, + sfdriver.c. lso (see ftoption.h) + TT_CONFIG_OPTION_EMBEDDED_BITMAPS ttsbit.c + TT_CONFIG_OPTION_POSTSCRIPT_NAMES ttpost.c + + * code-sos/ft2/ftbase.c: includes ftcalc.c, ftobjs.c, ftstream.c, + ftlist.c, ftoutln.c, ftextend.c, ftnames.c. + + * code-sos/ft2/autohint.c: includes ahangles.c, ahglyph.c, ahglobal.c, + ahhint.c, ahmodule.c. + + * code-sos/unix/Makefile: added ft2/ to client objects, took out + ftraster.c/ftrend1.c (see below), added -DFT_FLAT_COMPILE. + * ft2/ftsmooth.c: -DFT_FLAT_COMPILE required. + * ft2/raster1.c: -DFT_FLAT_COMPILE required. + Note: this includes ftraster.c/ftrend1.c. + + * code-sos/qcommon/vm_x86.c: _ftol is missing, ftolPtr only defined + for Win32, but used in generic code. Workaround for now. + TODO: find good Linux ftol, or use old solution. + + * SoS checkout. chown -R a+w * recode ibmpc:lat1 */*.h */*.c + +2000-06-30 Michael Vance + + * misc: Spoke with Leonardo about qvm mess. + + * ui/ui.sh: Created to build much like the ui.bat script. + + * ui/ui.q3asm: Use linux style paths. + + * game/game.sh: Created to build much like the game.bat script. + + * game/game.q3asm: Use linux style paths. + + * cgame/cgame.sh: Created to build much like the cgame.bat script. + + * cgame/cgame.q3asm: Use linux systel paths. + + * unix/Makefile: Use the new .sh scripts to build the QVM files. + + * lcc/etc/linux.c: Build .asm files instead of .s files. + + * misc: QVMs now load properly, with minor glitches that should + hopefully be solvable. The new build scripts conflict with the + .asm files already in CVS, as the generated byte code is slightly + different in some cases. + +2000-06-29 Michael Vance + + * lcc/makefile: Tweaked to automatically include the system + compiler's header location. Added an install directory. + + * lcc/custom.mk: Added a build directory. + + * lcc/etc/linux.c: Numerous small tweaks to make compiling the VM + code a much simpler task. + + * q3asm/Makefile: Created. + + * q3asm/q3asm.c: Fixed uninitialized variable in + HashString(). Fixed off by one in argument parsing. + + * misc: Had Brian remove the Xmd.h include from glx.h so that we can + build Quake3 on XFree86 4.0 systems. + + * wine: Attempted to build with lcc.exe and q3asm.exe using wine, + also did not work. This is in contrast to MikeP's .qvms, which + seem to work. + +2000-06-28 Michael Vance + + * common/files.c: Fixed Mods menu behaviour. + + * unix/linux_qgl.c: Guarded references to fxMesa. + + * renderer/qgl.h: Guarded references to fxMesa. + + * ui/ui_demo2.c: Don't convert filename to uppercase. + +2000-05-07 Bernd Kreimeier + + * common/cmdlib.c: windowism, not guarded. Added WIN32 around "ATOM a". + + * q3map/Makefile: Linux Makefile. + + * q3map/Makefile.irix: "makefile" in original code, Irix-only Makefile. + Just fixed some redundant TAB that GNU make despises about as much as I + despise GNU Make, and changed to a relative path. + +2000-05-01 Bernd Kreimeier + + * q3radiant/: updated with Q3Radiant198b3-src.zip. + Tagged (globally) as q3radiant-198b3. + Kept the old files + 3DFXCamWnd.h + 3DFXCamWnd.cpp + MainFrm2.cpp + New files + Shaders.h + misc/ (contributed special TGA resources, don't relly belong) + Removed: + pName + Changed filenames to previous case: + UNNAMED.MAP -> unnamed.map + RES/BMP0002.BMP -> RES/bmp00002.bmp + Changed: + changelog.txt -> ChangeLog + +2000-04-28 Bernd Kreimeier + + * CVS: bk000425 modified sources. This replaces the unix/ directory + which is not yet in id's SourceSafe. Two check-ins, due to minor + changes in an attempt to nail the Voodoo3 related crashes (driver + problems, not a Q3 issue). Undid some of the QFL changes for PI + and the log bug fix - put back in (TODO). Also includes: + * Quake3/code/botlib/be_aas_sample.c: single file update from Robert. + + * CVS: id000423 code dumps (two of them). Applying Loki patches. + Tagged for the final version (all patches). + + * CVS: id000422 code dump. This did not include the 1.16n fixes + used for Linux, and was the first dump for the 1.17 security fix + release. + Note: forgot to check in the ft2/ headers themselves, but they + are not used in the current codebase anyway. Are added in next + dump. Also there is use of CVS/CVS-like $Keyword$ patterns in + some files, and between their revisions and ours we fuck this up. + Also, id ZIP files create write protected sources, have to do + chmod -R a+w Quake3/ to work and overwrite files. + + * CVS: bk000315 modified source. This version was the 1.16n release. + Note: the changes applied here are not in the subsequent code dumps + of id. If you want to compile the Linux version as released you + have to use bk-tagged versions until the patches are merged in by + Robert Duffy. + + * CVS: id000314 engine code dump, same procedure as below, tag. + Note: this version added vm/ sudirectories with assembly files + for cgame, game, ui. CVS tag id000314. + + * CVS: id000304 engine code dump. Now there is a problem, as CVS + was used in the Mac sources. Do + find . -name 'CVS' -exec rm -r {} \; + before cvs update, then tagged: + cvs -d /loki/cvsroot/ tag id000304 Quake3/ + + * CVS: checked in a source snapshot of the id00303 engine code + and the id0003029 tools code. The tool sources are not fully in + sync, and we have only partial source from earlier engine revisions. + The engine source marks where Loki took over from Dave Kirsch. + This snapshot (with all temporary and bogus files) is imported + and tagged using: + cvs -d /loki/cvsroot import Quake3 id000303 initial + + Modules: + code: the Q3 engine code, including a jpeg-6/ copy + common: code shared by tools + libs: code shared by tools, inlcuding a jpeg6/ copy + q3asm: VM bytecode assembly + q3data: misc. Q3 data conversions + q3map: BSP builder + q3radiant: Win32 editor, as is + lcc: C compiler for q3asm + + The sources have not been cleaned up, and binary files have not been + removed. The Q3Radiant code base might exhibit mixed case asmbiguities + in the future, and future source dumps might come from SourceForge + instead. + +2000-04-25 Bernd Kreimeier + + * q3code.id000425/unix/Makefile: relative path, relocatable. + Note: first code merge with id, finally :-). + +2000-04-24 Bernd Kreimeier + + * q3code.bk000422/unix/matha.s: in C(BoxOnPlaneSide) + the following line triggers assembler warning: + "missing prefix `*' in absolute indirect address, maybe misassembled!" + jmp Ljmptab(,%eax,4) + + + * q3code.bk000422/unix/Makefile (MOUNT_DIR): rember to change. + TODO: fix this bloody Makefile to be relocatable, damnit. + + * q3code.bk000422/cgame/cg_event.c: applied JCash fix again + (see EV_EVENT_BITS below). Send e-mail to verify. + + * q3code.bk000422/renderer/tr_image.c: "../jpeg-6/jpeglib.h" again. + + * q3code.bk000422/: created from the id dump of today, lacking + all but one of my changes (sigh). Swapped unix/ competely, takes + care of 90%. Submitted all changes again to Robert... + +2000-04-19 Bernd Kreimeier + + * q3code.bk000315/unix/linux_glimp.c (GLimp_EndFrame): + QGL_EnableLogging( r_logFile->value ) doesn't work? + + * q3code.bk000315/unix/linux_qgl.c: GLimp_LogNewFrame() is + obsolete. QGL_EnableLogging was out of sync with Win32 and + did not support the new framecounter decrement logic. + +2000-04-03 Bernd Kreimeier + + * q3code.bk000315/server/sv_snapshot.c: svs.nextSnapshotEntities + is a signed integer unconditionally incremented, which gets + negative and causes a segfaulting indexing an array. Added reset + to counter. Might fail if snapshot numbers are supposed to + monotonically increase. + +2000-04-02 Bernd Kreimeier + + * q3code.bk000315/client/cl_parse.c (CL_ParseServerMessage): + assert(0) on Illegible message (remember to +set in_mouse 0). + TODO: have to add a dump message function, it's unreadable. + + * botlib/be_ai_goal.c (InitLevelItemHeap): loop counter -2 + left -2 with uninitialized next, and -1 disconnected. Removed + redundant memset. There is an item alloc leak I suspect, as + max_levelitems 1024 merely delayed the overflow error. + +2000-04-01 Bernd Kreimeier + + * botlib/be_ai_goal.c (InitLevelItemHeap): still segfaults. + Not memsetting the entire item heap. As items are cleared + on return, that leaves only memory corruption? + Later: upped max_levelitems from 256 to 1024 + Later: client dies on connect: + Error: CL_ParseServerMessage: Illegible server message 255 + + +2000-03-31 Bernd Kreimeier + + * botlib/be_ai_goal.c: initializing global vars. + Segfault in AllocLevelItem () + at /home/bk/Games/Quake3/q3code/botlib/be_ai_goal.c:364 + I suspect that the initial freelevelitems setting is at + the end of the list and eventually exposed. + + * cgame/cg_event.c: according to Johmn Cash: + itemNum = (es->event & ~EV_EVENT_BITS) - EV_USE_ITEM0 + Quote: "This causes itemNum to be invalid about half the time, + preventing any client side effect tied to the item from occurring." + +2000-03-06 Bernd Kreimeier + + * qcommon/common.c: set pushEvent buffer and indices + to zero in Com_Init(). + + * q3code/qcommon/qcommon.h: made SE_NONE (and for paranoia + also NA_BOT) explicitely set to zero. + +2000-02-27 Bernd Kreimeier + + * unix/Makefile: added dmalloc in an attempt to get on + the Z_Free bug. Futile. Despite stripping dmalloc debug + token down to essentials, I get a (seemingly bogus or + unrelated): + debug-malloc library: dumping program, fatal error + Error: possibly bad .c filename pointer (err 24) + + +2000-02-26 Bernd Kreimeier + + * qcommon/common.c: various debug builts to isolate the + Z_Free bug. It reproducibly happens on some machines + with SE_PACKET, but the packets themselves look + thoroughly corrupted. + +2000-02-21 Bernd Kreimeier + + * qcommon/common.c (Com_EventLoop): possible problem + here, pointer does not get cleared. + + * unix/linux_glimp.c (InitSig): no signal handler. + * common/common.c: dump in Com_Error for debug. + +2000-02-17 Bernd Kreimeier + + * q3code: new dump from Zoid. Repeat tr_image.c fix. + + * unix/Makefile: added client/snd_adpcm.c (linkage errors). + Later: added entire JPDIR and rules, for tr_image.c. + Later: had to fix fules for game/ai_*.c files. + Later: removed ui/ui_quit.o (n/a) + Later: took out -mpentiumpro -march=pentiumpro + + * renderer/tr_image.c: windowism in #include path (see below). + #include "..\jpeg-6\jpeglib.h" + +1999-12-27 Bernd Kreimeier + + * Alpha: tried a dedicated server compile. Segfaults in + ../qcommon/files.c:1682, a paksort function doing pointer + fiddling. + + * Makefile.alpha: created. + Note: want to take the SDL/Setup autoconf ASAP. + + * unix/unix_main.c: fixed __axp__ to __alpha__, guarded + _FPU_SETCW. + + * qcommon/vm_alpha.c: dummy, created. + * qcommon/vm_null.c: dummy, created. + +1999-12-04 Bernd Kreimeier + + * renderer/tr_image.c: windowism in #include path. + #include "..\jpeg-6\jpeglib.h" + + * Revision 1.11: from Zoid by e-mail. + Note: threw away my playground copy, starting with the + ZIP file. Zoid's using CVS now, but we can't remote + access it. Thus did the + "find . -name 'CVS' -exec rm -rf {} \;" + and then track it as 3rd party source by + + + * ChangeLog: created. Now starting to track Q3A source. + +--------- q3code log --------------------------------------------- diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5bb591d --- /dev/null +++ b/Makefile @@ -0,0 +1,2473 @@ +# +# ioq3 Makefile +# +# GNU Make required +# + +COMPILE_PLATFORM=$(shell uname|sed -e s/_.*//|tr '[:upper:]' '[:lower:]'|sed -e 's/\//_/g') + +COMPILE_ARCH=$(shell uname -m | sed -e s/i.86/i386/) + +ifeq ($(COMPILE_PLATFORM),sunos) + # Solaris uname and GNU uname differ + COMPILE_ARCH=$(shell uname -p | sed -e s/i.86/i386/) +endif +ifeq ($(COMPILE_PLATFORM),darwin) + # Apple does some things a little differently... + COMPILE_ARCH=$(shell uname -p | sed -e s/i.86/i386/) +endif + +ifeq ($(COMPILE_PLATFORM),mingw32) + ifeq ($(COMPILE_ARCH),i386) + COMPILE_ARCH=x86 + endif + ifeq ($(COMPILE_ARCH),x86_64) + COMPILE_ARCH=x64 + endif +endif + +ifndef BUILD_STANDALONE + BUILD_STANDALONE = +endif +ifndef BUILD_CLIENT + BUILD_CLIENT = +endif +ifndef BUILD_CLIENT_SMP + BUILD_CLIENT_SMP = +endif +ifndef BUILD_SERVER + BUILD_SERVER = +endif +ifndef BUILD_GAME_SO + BUILD_GAME_SO = +endif +ifndef BUILD_GAME_QVM + BUILD_GAME_QVM = +endif +ifndef BUILD_MISSIONPACK + BUILD_MISSIONPACK= +endif + +ifneq ($(PLATFORM),darwin) + BUILD_CLIENT_SMP = 0 +endif + +############################################################################# +# +# If you require a different configuration from the defaults below, create a +# new file named "Makefile.local" in the same directory as this file and define +# your parameters there. This allows you to change configuration without +# causing problems with keeping up to date with the repository. +# +############################################################################# +-include Makefile.local + +ifndef PLATFORM +PLATFORM=$(COMPILE_PLATFORM) +endif +export PLATFORM + +ifeq ($(COMPILE_ARCH),powerpc) + COMPILE_ARCH=ppc +endif +ifeq ($(COMPILE_ARCH),powerpc64) + COMPILE_ARCH=ppc64 +endif + +ifndef ARCH +ARCH=$(COMPILE_ARCH) +endif +export ARCH + +ifneq ($(PLATFORM),$(COMPILE_PLATFORM)) + CROSS_COMPILING=1 +else + CROSS_COMPILING=0 + + ifneq ($(ARCH),$(COMPILE_ARCH)) + CROSS_COMPILING=1 + endif +endif +export CROSS_COMPILING + +ifndef COPYDIR +COPYDIR="/usr/local/games/quake3" +endif + +ifndef COPYBINDIR +COPYBINDIR=$(COPYDIR) +endif + +ifndef MOUNT_DIR +MOUNT_DIR=code +endif + +ifndef BUILD_DIR +BUILD_DIR=build +endif + +ifndef TEMPDIR +TEMPDIR=/tmp +endif + +ifndef GENERATE_DEPENDENCIES +GENERATE_DEPENDENCIES=1 +endif + +ifndef USE_OPENAL +USE_OPENAL=1 +endif + +ifndef USE_OPENAL_DLOPEN +USE_OPENAL_DLOPEN=1 +endif + +ifndef USE_CURL +USE_CURL=1 +endif + +ifndef USE_CURL_DLOPEN + ifeq ($(PLATFORM),mingw32) + USE_CURL_DLOPEN=0 + else + USE_CURL_DLOPEN=1 + endif +endif + +ifndef USE_CODEC_VORBIS +USE_CODEC_VORBIS=0 +endif + +ifndef USE_MUMBLE +USE_MUMBLE=1 +endif + +ifndef USE_VOIP +USE_VOIP=1 +endif + +ifndef USE_INTERNAL_SPEEX +USE_INTERNAL_SPEEX=1 +endif + +ifndef USE_INTERNAL_ZLIB +USE_INTERNAL_ZLIB=1 +endif + +ifndef USE_INTERNAL_JPEG +USE_INTERNAL_JPEG=1 +endif + +ifndef USE_LOCAL_HEADERS +USE_LOCAL_HEADERS=1 +endif + +ifndef USE_RENDERER_DLOPEN +USE_RENDERER_DLOPEN=1 +endif + +ifndef DEBUG_CFLAGS +DEBUG_CFLAGS=-g -O0 +endif + +ifndef USE_OLD_VM64 +USE_OLD_VM64=0 +endif + +############################################################################# + +BD=$(BUILD_DIR)/debug-$(PLATFORM)-$(ARCH) +BR=$(BUILD_DIR)/release-$(PLATFORM)-$(ARCH) +CDIR=$(MOUNT_DIR)/client +SDIR=$(MOUNT_DIR)/server +RDIR=$(MOUNT_DIR)/renderer +CMDIR=$(MOUNT_DIR)/qcommon +SDLDIR=$(MOUNT_DIR)/sdl +ASMDIR=$(MOUNT_DIR)/asm +SYSDIR=$(MOUNT_DIR)/sys +GDIR=$(MOUNT_DIR)/game +CGDIR=$(MOUNT_DIR)/cgame +BLIBDIR=$(MOUNT_DIR)/botlib +NDIR=$(MOUNT_DIR)/null +UIDIR=$(MOUNT_DIR)/ui +Q3UIDIR=$(MOUNT_DIR)/q3_ui +JPDIR=$(MOUNT_DIR)/jpeg-8c +SPEEXDIR=$(MOUNT_DIR)/libspeex +ZDIR=$(MOUNT_DIR)/zlib +Q3ASMDIR=$(MOUNT_DIR)/tools/asm +LBURGDIR=$(MOUNT_DIR)/tools/lcc/lburg +Q3CPPDIR=$(MOUNT_DIR)/tools/lcc/cpp +Q3LCCETCDIR=$(MOUNT_DIR)/tools/lcc/etc +Q3LCCSRCDIR=$(MOUNT_DIR)/tools/lcc/src +LOKISETUPDIR=misc/setup +NSISDIR=misc/nsis +SDLHDIR=$(MOUNT_DIR)/SDL12 +LIBSDIR=$(MOUNT_DIR)/libs + +bin_path=$(shell which $(1) 2> /dev/null) + +# We won't need this if we only build the server +ifneq ($(BUILD_CLIENT),0) + # set PKG_CONFIG_PATH to influence this, e.g. + # PKG_CONFIG_PATH=/opt/cross/i386-mingw32msvc/lib/pkgconfig + ifneq ($(call bin_path, pkg-config),) + CURL_CFLAGS=$(shell pkg-config --silence-errors --cflags libcurl) + CURL_LIBS=$(shell pkg-config --silence-errors --libs libcurl) + OPENAL_CFLAGS=$(shell pkg-config --silence-errors --cflags openal) + OPENAL_LIBS=$(shell pkg-config --silence-errors --libs openal) + SDL_CFLAGS=$(shell pkg-config --silence-errors --cflags sdl|sed 's/-Dmain=SDL_main//') + SDL_LIBS=$(shell pkg-config --silence-errors --libs sdl) + endif + # Use sdl-config if all else fails + ifeq ($(SDL_CFLAGS),) + ifneq ($(call bin_path, sdl-config),) + SDL_CFLAGS=$(shell sdl-config --cflags) + SDL_LIBS=$(shell sdl-config --libs) + endif + endif +endif + +# version info +VERSION=1.36 + +USE_SVN= +ifeq ($(wildcard .svn),.svn) + SVN_REV=$(shell LANG=C svnversion .) + ifneq ($(SVN_REV),) + VERSION:=$(VERSION)_SVN$(SVN_REV) + USE_SVN=1 + endif +else +ifeq ($(wildcard .git/svn/.metadata),.git/svn/.metadata) + SVN_REV=$(shell LANG=C git svn info | awk '$$1 == "Revision:" {print $$2; exit 0}') + ifneq ($(SVN_REV),) + VERSION:=$(VERSION)_SVN$(SVN_REV) + endif +endif +endif + + +############################################################################# +# SETUP AND BUILD -- LINUX +############################################################################# + +## Defaults +LIB=lib + +INSTALL=install +MKDIR=mkdir + +ifneq (,$(findstring "$(PLATFORM)", "linux" "gnu_kfreebsd" "kfreebsd-gnu")) + + ifeq ($(ARCH),axp) + ARCH=alpha + else + ifeq ($(ARCH),x86_64) + LIB=lib64 + else + ifeq ($(ARCH),ppc64) + LIB=lib64 + else + ifeq ($(ARCH),s390x) + LIB=lib64 + endif + endif + endif + endif + + BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \ + -pipe -DUSE_ICON + CLIENT_CFLAGS += $(SDL_CFLAGS) + + OPTIMIZEVM = -O3 -funroll-loops -fomit-frame-pointer + OPTIMIZE = $(OPTIMIZEVM) -ffast-math + + ifeq ($(ARCH),x86_64) + OPTIMIZEVM = -O3 -fomit-frame-pointer -funroll-loops \ + -falign-loops=2 -falign-jumps=2 -falign-functions=2 \ + -fstrength-reduce + OPTIMIZE = $(OPTIMIZEVM) -ffast-math + HAVE_VM_COMPILED = true + else + ifeq ($(ARCH),i386) + OPTIMIZEVM = -O3 -march=i586 -fomit-frame-pointer \ + -funroll-loops -falign-loops=2 -falign-jumps=2 \ + -falign-functions=2 -fstrength-reduce + OPTIMIZE = $(OPTIMIZEVM) -ffast-math + HAVE_VM_COMPILED=true + else + ifeq ($(ARCH),ppc) + BASE_CFLAGS += -maltivec + HAVE_VM_COMPILED=true + endif + ifeq ($(ARCH),ppc64) + BASE_CFLAGS += -maltivec + HAVE_VM_COMPILED=true + endif + ifeq ($(ARCH),sparc) + OPTIMIZE += -mtune=ultrasparc3 -mv8plus + OPTIMIZEVM += -mtune=ultrasparc3 -mv8plus + HAVE_VM_COMPILED=true + endif + ifeq ($(ARCH),alpha) + # According to http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=410555 + # -ffast-math will cause the client to die with SIGFPE on Alpha + OPTIMIZE = $(OPTIMIZEVM) + endif + endif + endif + + SHLIBEXT=so + SHLIBCFLAGS=-fPIC -fvisibility=hidden + SHLIBLDFLAGS=-shared $(LDFLAGS) + + THREAD_LIBS=-lpthread + LIBS=-ldl -lm + + CLIENT_LIBS=$(SDL_LIBS) + RENDERER_LIBS = $(SDL_LIBS) -lGL + + ifeq ($(USE_OPENAL),1) + ifneq ($(USE_OPENAL_DLOPEN),1) + CLIENT_LIBS += -lopenal + endif + endif + + ifeq ($(USE_CURL),1) + ifneq ($(USE_CURL_DLOPEN),1) + CLIENT_LIBS += -lcurl + endif + endif + + ifeq ($(USE_CODEC_VORBIS),1) + CLIENT_LIBS += -lvorbisfile -lvorbis -logg + endif + + ifeq ($(USE_MUMBLE),1) + CLIENT_LIBS += -lrt + endif + + ifeq ($(USE_LOCAL_HEADERS),1) + CLIENT_CFLAGS += -I$(SDLHDIR)/include + endif + + ifeq ($(ARCH),i386) + # linux32 make ... + BASE_CFLAGS += -m32 + else + ifeq ($(ARCH),ppc64) + BASE_CFLAGS += -m64 + endif + endif +else # ifeq Linux + +############################################################################# +# SETUP AND BUILD -- MAC OS X +############################################################################# + +ifeq ($(PLATFORM),darwin) + HAVE_VM_COMPILED=true + LIBS = -framework Cocoa + CLIENT_LIBS= + RENDERER_LIBS= + OPTIMIZEVM= + + BASE_CFLAGS = -Wall -Wimplicit -Wstrict-prototypes + + ifeq ($(ARCH),ppc) + BASE_CFLAGS += -faltivec + OPTIMIZEVM += -O3 + endif + ifeq ($(ARCH),ppc64) + BASE_CFLAGS += -faltivec + endif + ifeq ($(ARCH),i386) + OPTIMIZEVM += -march=prescott -mfpmath=sse + # x86 vm will crash without -mstackrealign since MMX instructions will be + # used no matter what and they corrupt the frame pointer in VM calls + BASE_CFLAGS += -m32 -mstackrealign + endif + ifeq ($(ARCH),x86_64) + OPTIMIZEVM += -mfpmath=sse + endif + + BASE_CFLAGS += -fno-strict-aliasing -DMACOS_X -fno-common -pipe + + ifeq ($(USE_OPENAL),1) + ifneq ($(USE_OPENAL_DLOPEN),1) + CLIENT_LIBS += -framework OpenAL + endif + endif + + ifeq ($(USE_CURL),1) + ifneq ($(USE_CURL_DLOPEN),1) + CLIENT_LIBS += -lcurl + endif + endif + + ifeq ($(USE_CODEC_VORBIS),1) + CLIENT_LIBS += -lvorbisfile -lvorbis -logg + endif + + BASE_CFLAGS += -D_THREAD_SAFE=1 + + ifeq ($(USE_LOCAL_HEADERS),1) + BASE_CFLAGS += -I$(SDLHDIR)/include + endif + + # We copy sdlmain before ranlib'ing it so that subversion doesn't think + # the file has been modified by each build. + LIBSDLMAIN=$(B)/libSDLmain.a + LIBSDLMAINSRC=$(LIBSDIR)/macosx/libSDLmain.a + CLIENT_LIBS += -framework IOKit \ + $(LIBSDIR)/macosx/libSDL-1.2.0.dylib + RENDERER_LIBS += -framework OpenGL $(LIBSDIR)/macosx/libSDL-1.2.0.dylib + + OPTIMIZEVM += -falign-loops=16 + OPTIMIZE = $(OPTIMIZEVM) -ffast-math + + SHLIBEXT=dylib + SHLIBCFLAGS=-fPIC -fno-common + SHLIBLDFLAGS=-dynamiclib $(LDFLAGS) + + NOTSHLIBCFLAGS=-mdynamic-no-pic + + TOOLS_CFLAGS += -DMACOS_X + +else # ifeq darwin + + +############################################################################# +# SETUP AND BUILD -- MINGW32 +############################################################################# + +ifeq ($(PLATFORM),mingw32) + + # Some MinGW installations define CC to cc, but don't actually provide cc, + # so explicitly use gcc instead (which is the only option anyway) + ifeq ($(call bin_path, $(CC)),) + CC=gcc + endif + + ifndef WINDRES + WINDRES=windres + endif + + BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \ + -DUSE_ICON + + # In the absence of wspiapi.h, require Windows XP or later + ifeq ($(shell test -e $(CMDIR)/wspiapi.h; echo $$?),1) + BASE_CFLAGS += -DWINVER=0x501 + endif + + ifeq ($(USE_OPENAL),1) + CLIENT_CFLAGS += $(OPENAL_CFLAGS) + ifneq ($(USE_OPENAL_DLOPEN),1) + CLIENT_LDFLAGS += $(OPENAL_LDFLAGS) + endif + endif + + ifeq ($(ARCH),x64) + OPTIMIZEVM = -O3 -fno-omit-frame-pointer \ + -falign-loops=2 -funroll-loops -falign-jumps=2 -falign-functions=2 \ + -fstrength-reduce + OPTIMIZE = $(OPTIMIZEVM) --fast-math + HAVE_VM_COMPILED = true + endif + ifeq ($(ARCH),x86) + OPTIMIZEVM = -O3 -march=i586 -fno-omit-frame-pointer \ + -falign-loops=2 -funroll-loops -falign-jumps=2 -falign-functions=2 \ + -fstrength-reduce + OPTIMIZE = $(OPTIMIZEVM) -ffast-math + HAVE_VM_COMPILED = true + endif + + SHLIBEXT=dll + SHLIBCFLAGS= + SHLIBLDFLAGS=-shared $(LDFLAGS) + + BINEXT=.exe + + LIBS= -lws2_32 -lwinmm -lpsapi + CLIENT_LDFLAGS += -mwindows + CLIENT_LIBS = -lgdi32 -lole32 + RENDERER_LIBS = -lgdi32 -lole32 -lopengl32 + + ifeq ($(USE_CURL),1) + CLIENT_CFLAGS += $(CURL_CFLAGS) + ifneq ($(USE_CURL_DLOPEN),1) + ifeq ($(USE_LOCAL_HEADERS),1) + CLIENT_CFLAGS += -DCURL_STATICLIB + ifeq ($(ARCH),x64) + CLIENT_LIBS += $(LIBSDIR)/win64/libcurl.a + else + CLIENT_LIBS += $(LIBSDIR)/win32/libcurl.a + endif + else + CLIENT_LIBS += $(CURL_LIBS) + endif + endif + endif + + ifeq ($(USE_CODEC_VORBIS),1) + CLIENT_LIBS += -lvorbisfile -lvorbis -logg + endif + + ifeq ($(ARCH),x86) + # build 32bit + BASE_CFLAGS += -m32 + else + BASE_CFLAGS += -m64 + endif + + # libmingw32 must be linked before libSDLmain + CLIENT_LIBS += -lmingw32 + RENDERER_LIBS += -lmingw32 + + ifeq ($(USE_LOCAL_HEADERS),1) + CLIENT_CFLAGS += -I$(SDLHDIR)/include + ifeq ($(ARCH), x86) + CLIENT_LIBS += $(LIBSDIR)/win32/libSDLmain.a \ + $(LIBSDIR)/win32/libSDL.dll.a + RENDERER_LIBS += $(LIBSDIR)/win32/libSDLmain.a \ + $(LIBSDIR)/win32/libSDL.dll.a + else + CLIENT_LIBS += $(LIBSDIR)/win64/libSDLmain.a \ + $(LIBSDIR)/win64/libSDL64.dll.a + RENDERER_LIBS += $(LIBSDIR)/win64/libSDLmain.a \ + $(LIBSDIR)/win64/libSDL64.dll.a + endif + else + CLIENT_CFLAGS += $(SDL_CFLAGS) + CLIENT_LIBS += $(SDL_LIBS) + RENDERER_LIBS += $(SDL_LIBS) + endif + + BUILD_CLIENT_SMP = 0 + +else # ifeq mingw32 + +############################################################################# +# SETUP AND BUILD -- FREEBSD +############################################################################# + +ifeq ($(PLATFORM),freebsd) + + # flags + BASE_CFLAGS = $(shell env MACHINE_ARCH=$(ARCH) make -f /dev/null -VCFLAGS) \ + -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \ + -DUSE_ICON -DMAP_ANONYMOUS=MAP_ANON + CLIENT_CFLAGS += $(SDL_CFLAGS) + HAVE_VM_COMPILED = true + + OPTIMIZEVM = -O3 -funroll-loops -fomit-frame-pointer + OPTIMIZE = $(OPTIMIZEVM) -ffast-math + + SHLIBEXT=so + SHLIBCFLAGS=-fPIC + SHLIBLDFLAGS=-shared $(LDFLAGS) + + THREAD_LIBS=-lpthread + # don't need -ldl (FreeBSD) + LIBS=-lm + + CLIENT_LIBS = + + CLIENT_LIBS += $(SDL_LIBS) + RENDERER_LIBS = $(SDL_LIBS) -lGL + + # optional features/libraries + ifeq ($(USE_OPENAL),1) + ifeq ($(USE_OPENAL_DLOPEN),1) + CLIENT_LIBS += $(THREAD_LIBS) -lopenal + endif + endif + + ifeq ($(USE_CURL),1) + ifeq ($(USE_CURL_DLOPEN),1) + CLIENT_LIBS += -lcurl + endif + endif + + ifeq ($(USE_CODEC_VORBIS),1) + CLIENT_LIBS += -lvorbisfile -lvorbis -logg + endif + + # cross-compiling tweaks + ifeq ($(ARCH),i386) + ifeq ($(CROSS_COMPILING),1) + BASE_CFLAGS += -m32 + endif + endif + ifeq ($(ARCH),amd64) + ifeq ($(CROSS_COMPILING),1) + BASE_CFLAGS += -m64 + endif + endif + +else # ifeq freebsd + +############################################################################# +# SETUP AND BUILD -- OPENBSD +############################################################################# + +ifeq ($(PLATFORM),openbsd) + + ARCH=$(shell uname -m) + + BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \ + -DUSE_ICON -DMAP_ANONYMOUS=MAP_ANON + CLIENT_CFLAGS += $(SDL_CFLAGS) + + ifeq ($(USE_CURL),1) + CLIENT_CFLAGS += $(CURL_CFLAGS) + USE_CURL_DLOPEN=0 + endif + + SHLIBEXT=so + SHLIBNAME=.$(SHLIBEXT) + SHLIBCFLAGS=-fPIC + SHLIBLDFLAGS=-shared $(LDFLAGS) + + THREAD_LIBS=-pthread + LIBS=-lm + + CLIENT_LIBS = + + CLIENT_LIBS += $(SDL_LIBS) + RENDERER_LIBS = $(SDL_LIBS) -lGL + + ifeq ($(USE_OPENAL),1) + ifneq ($(USE_OPENAL_DLOPEN),1) + CLIENT_LIBS += $(THREAD_LIBS) -lossaudio -lopenal + endif + endif + + ifeq ($(USE_CODEC_VORBIS),1) + CLIENT_LIBS += -lvorbisfile -lvorbis -logg + endif + + ifeq ($(USE_CURL),1) + ifneq ($(USE_CURL_DLOPEN),1) + CLIENT_LIBS += -lcurl + endif + endif + +else # ifeq openbsd + +############################################################################# +# SETUP AND BUILD -- NETBSD +############################################################################# + +ifeq ($(PLATFORM),netbsd) + + ifeq ($(shell uname -m),i386) + ARCH=i386 + endif + + LIBS=-lm + SHLIBEXT=so + SHLIBCFLAGS=-fPIC + SHLIBLDFLAGS=-shared $(LDFLAGS) + THREAD_LIBS=-lpthread + + BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes + + ifeq ($(ARCH),i386) + HAVE_VM_COMPILED=true + endif + + BUILD_CLIENT = 0 + +else # ifeq netbsd + +############################################################################# +# SETUP AND BUILD -- IRIX +############################################################################# + +ifeq ($(PLATFORM),irix64) + + ARCH=mips + + CC = c99 + MKDIR = mkdir -p + + BASE_CFLAGS=-Dstricmp=strcasecmp -Xcpluscomm -woff 1185 \ + -I. -I$(ROOT)/usr/include + CLIENT_CFLAGS += $(SDL_CFLAGS) + OPTIMIZE = -O3 + + SHLIBEXT=so + SHLIBCFLAGS= + SHLIBLDFLAGS=-shared + + LIBS=-ldl -lm -lgen + # FIXME: The X libraries probably aren't necessary? + CLIENT_LIBS=-L/usr/X11/$(LIB) $(SDL_LIBS) \ + -lX11 -lXext -lm + RENDERER_LIBS = $(SDL_LIBS) -lGL + +else # ifeq IRIX + +############################################################################# +# SETUP AND BUILD -- SunOS +############################################################################# + +ifeq ($(PLATFORM),sunos) + + CC=gcc + INSTALL=ginstall + MKDIR=gmkdir + COPYDIR="/usr/local/share/games/quake3" + + ifneq (,$(findstring i86pc,$(shell uname -m))) + ARCH=i386 + else #default to sparc + ARCH=sparc + endif + + ifneq ($(ARCH),i386) + ifneq ($(ARCH),sparc) + $(error arch $(ARCH) is currently not supported) + endif + endif + + BASE_CFLAGS = -Wall -fno-strict-aliasing -Wimplicit -Wstrict-prototypes \ + -pipe -DUSE_ICON + CLIENT_CFLAGS += $(SDL_CFLAGS) + + OPTIMIZEVM = -O3 -funroll-loops + + ifeq ($(ARCH),sparc) + OPTIMIZEVM += -O3 \ + -fstrength-reduce -falign-functions=2 \ + -mtune=ultrasparc3 -mv8plus -mno-faster-structs + HAVE_VM_COMPILED=true + else + ifeq ($(ARCH),i386) + OPTIMIZEVM += -march=i586 -fomit-frame-pointer \ + -falign-loops=2 -falign-jumps=2 \ + -falign-functions=2 -fstrength-reduce + HAVE_VM_COMPILED=true + BASE_CFLAGS += -m32 + CLIENT_CFLAGS += -I/usr/X11/include/NVIDIA + CLIENT_LDFLAGS += -L/usr/X11/lib/NVIDIA -R/usr/X11/lib/NVIDIA + endif + endif + + OPTIMIZE = $(OPTIMIZEVM) -ffast-math + + SHLIBEXT=so + SHLIBCFLAGS=-fPIC + SHLIBLDFLAGS=-shared $(LDFLAGS) + + THREAD_LIBS=-lpthread + LIBS=-lsocket -lnsl -ldl -lm + + BOTCFLAGS=-O0 + + CLIENT_LIBS +=$(SDL_LIBS) -lX11 -lXext -liconv -lm + RENDERER_LIBS = $(SDL_LIBS) -lGL + +else # ifeq sunos + +############################################################################# +# SETUP AND BUILD -- GENERIC +############################################################################# + BASE_CFLAGS= + OPTIMIZE = -O3 + + SHLIBEXT=so + SHLIBCFLAGS=-fPIC + SHLIBLDFLAGS=-shared + +endif #Linux +endif #darwin +endif #mingw32 +endif #FreeBSD +endif #OpenBSD +endif #NetBSD +endif #IRIX +endif #SunOS + +ifneq ($(HAVE_VM_COMPILED),true) + BASE_CFLAGS += -DNO_VM_COMPILED + BUILD_GAME_QVM=0 +endif + +TARGETS = + +ifndef FULLBINEXT + FULLBINEXT=.$(ARCH)$(BINEXT) +endif + +ifndef SHLIBNAME + SHLIBNAME=$(ARCH).$(SHLIBEXT) +endif + +ifneq ($(BUILD_SERVER),0) + TARGETS += $(B)/ioq3ded$(FULLBINEXT) +endif + +ifneq ($(BUILD_CLIENT),0) + ifneq ($(USE_RENDERER_DLOPEN),0) + TARGETS += $(B)/ioquake3$(FULLBINEXT) $(B)/renderer_opengl1_$(SHLIBNAME) + ifneq ($(BUILD_CLIENT_SMP),0) + TARGETS += $(B)/renderer_opengl1_smp_$(SHLIBNAME) + endif + else + TARGETS += $(B)/ioquake3$(FULLBINEXT) + ifneq ($(BUILD_CLIENT_SMP),0) + TARGETS += $(B)/ioquake3-smp$(FULLBINEXT) + endif + endif +endif + +ifneq ($(BUILD_GAME_SO),0) + TARGETS += \ + $(B)/baseq3/cgame$(SHLIBNAME) \ + $(B)/baseq3/qagame$(SHLIBNAME) \ + $(B)/baseq3/ui$(SHLIBNAME) + ifneq ($(BUILD_MISSIONPACK),0) + TARGETS += \ + $(B)/missionpack/cgame$(SHLIBNAME) \ + $(B)/missionpack/qagame$(SHLIBNAME) \ + $(B)/missionpack/ui$(SHLIBNAME) + endif +endif + +ifneq ($(BUILD_GAME_QVM),0) + ifneq ($(CROSS_COMPILING),1) + TARGETS += \ + $(B)/baseq3/vm/cgame.qvm \ + $(B)/baseq3/vm/qagame.qvm \ + $(B)/baseq3/vm/ui.qvm + ifneq ($(BUILD_MISSIONPACK),0) + TARGETS += \ + $(B)/missionpack/vm/qagame.qvm \ + $(B)/missionpack/vm/cgame.qvm \ + $(B)/missionpack/vm/ui.qvm + endif + endif +endif + +ifeq ($(USE_OPENAL),1) + CLIENT_CFLAGS += -DUSE_OPENAL + ifeq ($(USE_OPENAL_DLOPEN),1) + CLIENT_CFLAGS += -DUSE_OPENAL_DLOPEN + endif +endif + +ifeq ($(USE_CURL),1) + CLIENT_CFLAGS += -DUSE_CURL + ifeq ($(USE_CURL_DLOPEN),1) + CLIENT_CFLAGS += -DUSE_CURL_DLOPEN + endif +endif + +ifeq ($(USE_CODEC_VORBIS),1) + CLIENT_CFLAGS += -DUSE_CODEC_VORBIS +endif + +ifeq ($(USE_RENDERER_DLOPEN),1) + CLIENT_CFLAGS += -DUSE_RENDERER_DLOPEN +endif + +ifeq ($(USE_MUMBLE),1) + CLIENT_CFLAGS += -DUSE_MUMBLE +endif + +ifeq ($(USE_VOIP),1) + CLIENT_CFLAGS += -DUSE_VOIP + SERVER_CFLAGS += -DUSE_VOIP + ifeq ($(USE_INTERNAL_SPEEX),1) + CLIENT_CFLAGS += -DFLOATING_POINT -DUSE_ALLOCA -I$(SPEEXDIR)/include + else + CLIENT_LIBS += -lspeex -lspeexdsp + endif +endif + +ifeq ($(USE_INTERNAL_ZLIB),1) + BASE_CFLAGS += -DNO_GZIP + BASE_CFLAGS += -I$(ZDIR) +else + LIBS += -lz +endif + +ifeq ($(USE_INTERNAL_JPEG),1) + BASE_CFLAGS += -DUSE_INTERNAL_JPEG + BASE_CFLAGS += -I$(JPDIR) +else + RENDERER_LIBS += -ljpeg +endif + +ifdef DEFAULT_BASEDIR + BASE_CFLAGS += -DDEFAULT_BASEDIR=\\\"$(DEFAULT_BASEDIR)\\\" +endif + +ifeq ($(USE_LOCAL_HEADERS),1) + BASE_CFLAGS += -DUSE_LOCAL_HEADERS +endif + +ifeq ($(BUILD_STANDALONE),1) + BASE_CFLAGS += -DSTANDALONE +endif + +ifeq ($(GENERATE_DEPENDENCIES),1) + DEPEND_CFLAGS = -MMD +else + DEPEND_CFLAGS = +endif + +ifeq ($(NO_STRIP),1) + STRIP_FLAG = +else + STRIP_FLAG = -s +endif + +BASE_CFLAGS += -DPRODUCT_VERSION=\\\"$(VERSION)\\\" +BASE_CFLAGS += -Wformat=2 -Wno-format-zero-length -Wformat-security -Wno-format-nonliteral +BASE_CFLAGS += -Wstrict-aliasing=2 -Wmissing-format-attribute +BASE_CFLAGS += -Wdisabled-optimization +BASE_CFLAGS += -Werror-implicit-function-declaration + +ifeq ($(V),1) +echo_cmd=@: +Q= +else +echo_cmd=@echo +Q=@ +endif + +define DO_CC +$(echo_cmd) "CC $<" +$(Q)$(CC) $(NOTSHLIBCFLAGS) $(CFLAGS) $(CLIENT_CFLAGS) $(OPTIMIZE) -o $@ -c $< +endef + +define DO_REF_CC +$(echo_cmd) "REF_CC $<" +$(Q)$(CC) $(SHLIBCFLAGS) $(CFLAGS) $(CLIENT_CFLAGS) $(OPTIMIZE) -o $@ -c $< +endef + +define DO_SMP_CC +$(echo_cmd) "SMP_CC $<" +$(Q)$(CC) $(SHLIBCFLAGS) $(CFLAGS) $(CLIENT_CFLAGS) $(OPTIMIZE) -DSMP -o $@ -c $< +endef + +define DO_BOT_CC +$(echo_cmd) "BOT_CC $<" +$(Q)$(CC) $(NOTSHLIBCFLAGS) $(CFLAGS) $(BOTCFLAGS) $(OPTIMIZE) -DBOTLIB -o $@ -c $< +endef + +ifeq ($(GENERATE_DEPENDENCIES),1) + DO_QVM_DEP=cat $(@:%.o=%.d) | sed -e 's/\.o/\.asm/g' >> $(@:%.o=%.d) +endif + +define DO_SHLIB_CC +$(echo_cmd) "SHLIB_CC $<" +$(Q)$(CC) $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $< +$(Q)$(DO_QVM_DEP) +endef + +define DO_GAME_CC +$(echo_cmd) "GAME_CC $<" +$(Q)$(CC) -DQAGAME $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $< +$(Q)$(DO_QVM_DEP) +endef + +define DO_CGAME_CC +$(echo_cmd) "CGAME_CC $<" +$(Q)$(CC) -DCGAME $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $< +$(Q)$(DO_QVM_DEP) +endef + +define DO_UI_CC +$(echo_cmd) "UI_CC $<" +$(Q)$(CC) -DUI $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $< +$(Q)$(DO_QVM_DEP) +endef + +define DO_SHLIB_CC_MISSIONPACK +$(echo_cmd) "SHLIB_CC_MISSIONPACK $<" +$(Q)$(CC) -DMISSIONPACK $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $< +$(Q)$(DO_QVM_DEP) +endef + +define DO_GAME_CC_MISSIONPACK +$(echo_cmd) "GAME_CC_MISSIONPACK $<" +$(Q)$(CC) -DMISSIONPACK -DQAGAME $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $< +$(Q)$(DO_QVM_DEP) +endef + +define DO_CGAME_CC_MISSIONPACK +$(echo_cmd) "CGAME_CC_MISSIONPACK $<" +$(Q)$(CC) -DMISSIONPACK -DCGAME $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $< +$(Q)$(DO_QVM_DEP) +endef + +define DO_UI_CC_MISSIONPACK +$(echo_cmd) "UI_CC_MISSIONPACK $<" +$(Q)$(CC) -DMISSIONPACK -DUI $(SHLIBCFLAGS) $(CFLAGS) $(OPTIMIZEVM) -o $@ -c $< +$(Q)$(DO_QVM_DEP) +endef + +define DO_AS +$(echo_cmd) "AS $<" +$(Q)$(CC) $(CFLAGS) $(OPTIMIZE) -x assembler-with-cpp -o $@ -c $< +endef + +define DO_DED_CC +$(echo_cmd) "DED_CC $<" +$(Q)$(CC) $(NOTSHLIBCFLAGS) -DDEDICATED $(CFLAGS) $(SERVER_CFLAGS) $(OPTIMIZE) -o $@ -c $< +endef + +define DO_WINDRES +$(echo_cmd) "WINDRES $<" +$(Q)$(WINDRES) -i $< -o $@ +endef + + +############################################################################# +# MAIN TARGETS +############################################################################# + +default: release +all: debug release + +debug: + @$(MAKE) targets B=$(BD) CFLAGS="$(CFLAGS) $(BASE_CFLAGS) $(DEPEND_CFLAGS)" \ + OPTIMIZE="$(DEBUG_CFLAGS)" OPTIMIZEVM="$(DEBUG_CFLAGS)" \ + CLIENT_CFLAGS="$(CLIENT_CFLAGS)" SERVER_CFLAGS="$(SERVER_CFLAGS)" V=$(V) + +release: + @$(MAKE) targets B=$(BR) CFLAGS="$(CFLAGS) $(BASE_CFLAGS) $(DEPEND_CFLAGS)" \ + OPTIMIZE="-DNDEBUG $(OPTIMIZE)" OPTIMIZEVM="-DNDEBUG $(OPTIMIZEVM)" \ + CLIENT_CFLAGS="$(CLIENT_CFLAGS)" SERVER_CFLAGS="$(SERVER_CFLAGS)" V=$(V) + +# Create the build directories, check libraries and print out +# an informational message, then start building +targets: makedirs + @echo "" + @echo "Building ioquake3 in $(B):" + @echo " PLATFORM: $(PLATFORM)" + @echo " ARCH: $(ARCH)" + @echo " VERSION: $(VERSION)" + @echo " COMPILE_PLATFORM: $(COMPILE_PLATFORM)" + @echo " COMPILE_ARCH: $(COMPILE_ARCH)" + @echo " CC: $(CC)" + @echo "" + @echo " CFLAGS:" + -@for i in $(CFLAGS); \ + do \ + echo " $$i"; \ + done + -@for i in $(OPTIMIZE); \ + do \ + echo " $$i"; \ + done + @echo "" + @echo " CLIENT_CFLAGS:" + -@for i in $(CLIENT_CFLAGS); \ + do \ + echo " $$i"; \ + done + @echo "" + @echo " SERVER_CFLAGS:" + -@for i in $(SERVER_CFLAGS); \ + do \ + echo " $$i"; \ + done + @echo "" + @echo " LDFLAGS:" + -@for i in $(LDFLAGS); \ + do \ + echo " $$i"; \ + done + @echo "" + @echo " LIBS:" + -@for i in $(LIBS); \ + do \ + echo " $$i"; \ + done + @echo "" + @echo " CLIENT_LIBS:" + -@for i in $(CLIENT_LIBS); \ + do \ + echo " $$i"; \ + done + @echo "" + @echo " Output:" + -@for i in $(TARGETS); \ + do \ + echo " $$i"; \ + done + @echo "" +ifneq ($(TARGETS),) + @$(MAKE) $(TARGETS) V=$(V) +endif + +makedirs: + @if [ ! -d $(BUILD_DIR) ];then $(MKDIR) $(BUILD_DIR);fi + @if [ ! -d $(B) ];then $(MKDIR) $(B);fi + @if [ ! -d $(B)/client ];then $(MKDIR) $(B)/client;fi + @if [ ! -d $(B)/renderer ];then $(MKDIR) $(B)/renderer;fi + @if [ ! -d $(B)/renderersmp ];then $(MKDIR) $(B)/renderersmp;fi + @if [ ! -d $(B)/ded ];then $(MKDIR) $(B)/ded;fi + @if [ ! -d $(B)/baseq3 ];then $(MKDIR) $(B)/baseq3;fi + @if [ ! -d $(B)/baseq3/cgame ];then $(MKDIR) $(B)/baseq3/cgame;fi + @if [ ! -d $(B)/baseq3/game ];then $(MKDIR) $(B)/baseq3/game;fi + @if [ ! -d $(B)/baseq3/ui ];then $(MKDIR) $(B)/baseq3/ui;fi + @if [ ! -d $(B)/baseq3/qcommon ];then $(MKDIR) $(B)/baseq3/qcommon;fi + @if [ ! -d $(B)/baseq3/vm ];then $(MKDIR) $(B)/baseq3/vm;fi + @if [ ! -d $(B)/missionpack ];then $(MKDIR) $(B)/missionpack;fi + @if [ ! -d $(B)/missionpack/cgame ];then $(MKDIR) $(B)/missionpack/cgame;fi + @if [ ! -d $(B)/missionpack/game ];then $(MKDIR) $(B)/missionpack/game;fi + @if [ ! -d $(B)/missionpack/ui ];then $(MKDIR) $(B)/missionpack/ui;fi + @if [ ! -d $(B)/missionpack/qcommon ];then $(MKDIR) $(B)/missionpack/qcommon;fi + @if [ ! -d $(B)/missionpack/vm ];then $(MKDIR) $(B)/missionpack/vm;fi + @if [ ! -d $(B)/tools ];then $(MKDIR) $(B)/tools;fi + @if [ ! -d $(B)/tools/asm ];then $(MKDIR) $(B)/tools/asm;fi + @if [ ! -d $(B)/tools/etc ];then $(MKDIR) $(B)/tools/etc;fi + @if [ ! -d $(B)/tools/rcc ];then $(MKDIR) $(B)/tools/rcc;fi + @if [ ! -d $(B)/tools/cpp ];then $(MKDIR) $(B)/tools/cpp;fi + @if [ ! -d $(B)/tools/lburg ];then $(MKDIR) $(B)/tools/lburg;fi + +############################################################################# +# QVM BUILD TOOLS +############################################################################# + +TOOLS_OPTIMIZE = -g -Wall -fno-strict-aliasing +TOOLS_CFLAGS += $(TOOLS_OPTIMIZE) \ + -DTEMPDIR=\"$(TEMPDIR)\" -DSYSTEM=\"\" \ + -I$(Q3LCCSRCDIR) \ + -I$(LBURGDIR) +TOOLS_LIBS = +TOOLS_LDFLAGS = + +ifeq ($(GENERATE_DEPENDENCIES),1) + TOOLS_CFLAGS += -MMD +endif + +define DO_TOOLS_CC +$(echo_cmd) "TOOLS_CC $<" +$(Q)$(CC) $(TOOLS_CFLAGS) -o $@ -c $< +endef + +define DO_TOOLS_CC_DAGCHECK +$(echo_cmd) "TOOLS_CC_DAGCHECK $<" +$(Q)$(CC) $(TOOLS_CFLAGS) -Wno-unused -o $@ -c $< +endef + +LBURG = $(B)/tools/lburg/lburg$(BINEXT) +DAGCHECK_C = $(B)/tools/rcc/dagcheck.c +Q3RCC = $(B)/tools/q3rcc$(BINEXT) +Q3CPP = $(B)/tools/q3cpp$(BINEXT) +Q3LCC = $(B)/tools/q3lcc$(BINEXT) +Q3ASM = $(B)/tools/q3asm$(BINEXT) + +LBURGOBJ= \ + $(B)/tools/lburg/lburg.o \ + $(B)/tools/lburg/gram.o + +$(B)/tools/lburg/%.o: $(LBURGDIR)/%.c + $(DO_TOOLS_CC) + +$(LBURG): $(LBURGOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS) + +Q3RCCOBJ = \ + $(B)/tools/rcc/alloc.o \ + $(B)/tools/rcc/bind.o \ + $(B)/tools/rcc/bytecode.o \ + $(B)/tools/rcc/dag.o \ + $(B)/tools/rcc/dagcheck.o \ + $(B)/tools/rcc/decl.o \ + $(B)/tools/rcc/enode.o \ + $(B)/tools/rcc/error.o \ + $(B)/tools/rcc/event.o \ + $(B)/tools/rcc/expr.o \ + $(B)/tools/rcc/gen.o \ + $(B)/tools/rcc/init.o \ + $(B)/tools/rcc/inits.o \ + $(B)/tools/rcc/input.o \ + $(B)/tools/rcc/lex.o \ + $(B)/tools/rcc/list.o \ + $(B)/tools/rcc/main.o \ + $(B)/tools/rcc/null.o \ + $(B)/tools/rcc/output.o \ + $(B)/tools/rcc/prof.o \ + $(B)/tools/rcc/profio.o \ + $(B)/tools/rcc/simp.o \ + $(B)/tools/rcc/stmt.o \ + $(B)/tools/rcc/string.o \ + $(B)/tools/rcc/sym.o \ + $(B)/tools/rcc/symbolic.o \ + $(B)/tools/rcc/trace.o \ + $(B)/tools/rcc/tree.o \ + $(B)/tools/rcc/types.o + +$(DAGCHECK_C): $(LBURG) $(Q3LCCSRCDIR)/dagcheck.md + $(echo_cmd) "LBURG $(Q3LCCSRCDIR)/dagcheck.md" + $(Q)$(LBURG) $(Q3LCCSRCDIR)/dagcheck.md $@ + +$(B)/tools/rcc/dagcheck.o: $(DAGCHECK_C) + $(DO_TOOLS_CC_DAGCHECK) + +$(B)/tools/rcc/%.o: $(Q3LCCSRCDIR)/%.c + $(DO_TOOLS_CC) + +$(Q3RCC): $(Q3RCCOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS) + +Q3CPPOBJ = \ + $(B)/tools/cpp/cpp.o \ + $(B)/tools/cpp/lex.o \ + $(B)/tools/cpp/nlist.o \ + $(B)/tools/cpp/tokens.o \ + $(B)/tools/cpp/macro.o \ + $(B)/tools/cpp/eval.o \ + $(B)/tools/cpp/include.o \ + $(B)/tools/cpp/hideset.o \ + $(B)/tools/cpp/getopt.o \ + $(B)/tools/cpp/unix.o + +$(B)/tools/cpp/%.o: $(Q3CPPDIR)/%.c + $(DO_TOOLS_CC) + +$(Q3CPP): $(Q3CPPOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS) + +Q3LCCOBJ = \ + $(B)/tools/etc/lcc.o \ + $(B)/tools/etc/bytecode.o + +$(B)/tools/etc/%.o: $(Q3LCCETCDIR)/%.c + $(DO_TOOLS_CC) + +$(Q3LCC): $(Q3LCCOBJ) $(Q3RCC) $(Q3CPP) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $(Q3LCCOBJ) $(TOOLS_LIBS) + +define DO_Q3LCC +$(echo_cmd) "Q3LCC $<" +$(Q)$(Q3LCC) -o $@ $< +endef + +define DO_CGAME_Q3LCC +$(echo_cmd) "CGAME_Q3LCC $<" +$(Q)$(Q3LCC) -DCGAME -o $@ $< +endef + +define DO_GAME_Q3LCC +$(echo_cmd) "GAME_Q3LCC $<" +$(Q)$(Q3LCC) -DQAGAME -o $@ $< +endef + +define DO_UI_Q3LCC +$(echo_cmd) "UI_Q3LCC $<" +$(Q)$(Q3LCC) -DUI -o $@ $< +endef + +define DO_Q3LCC_MISSIONPACK +$(echo_cmd) "Q3LCC_MISSIONPACK $<" +$(Q)$(Q3LCC) -DMISSIONPACK -o $@ $< +endef + +define DO_CGAME_Q3LCC_MISSIONPACK +$(echo_cmd) "CGAME_Q3LCC_MISSIONPACK $<" +$(Q)$(Q3LCC) -DMISSIONPACK -DCGAME -o $@ $< +endef + +define DO_GAME_Q3LCC_MISSIONPACK +$(echo_cmd) "GAME_Q3LCC_MISSIONPACK $<" +$(Q)$(Q3LCC) -DMISSIONPACK -DQAGAME -o $@ $< +endef + +define DO_UI_Q3LCC_MISSIONPACK +$(echo_cmd) "UI_Q3LCC_MISSIONPACK $<" +$(Q)$(Q3LCC) -DMISSIONPACK -DUI -o $@ $< +endef + + +Q3ASMOBJ = \ + $(B)/tools/asm/q3asm.o \ + $(B)/tools/asm/cmdlib.o + +$(B)/tools/asm/%.o: $(Q3ASMDIR)/%.c + $(DO_TOOLS_CC) + +$(Q3ASM): $(Q3ASMOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(TOOLS_CFLAGS) $(TOOLS_LDFLAGS) -o $@ $^ $(TOOLS_LIBS) + + +############################################################################# +# CLIENT/SERVER +############################################################################# + +Q3OBJ = \ + $(B)/client/cl_cgame.o \ + $(B)/client/cl_cin.o \ + $(B)/client/cl_console.o \ + $(B)/client/cl_input.o \ + $(B)/client/cl_keys.o \ + $(B)/client/cl_main.o \ + $(B)/client/cl_net_chan.o \ + $(B)/client/cl_parse.o \ + $(B)/client/cl_scrn.o \ + $(B)/client/cl_ui.o \ + $(B)/client/cl_avi.o \ + \ + $(B)/client/cm_load.o \ + $(B)/client/cm_patch.o \ + $(B)/client/cm_polylib.o \ + $(B)/client/cm_test.o \ + $(B)/client/cm_trace.o \ + \ + $(B)/client/cmd.o \ + $(B)/client/common.o \ + $(B)/client/cvar.o \ + $(B)/client/files.o \ + $(B)/client/md4.o \ + $(B)/client/md5.o \ + $(B)/client/msg.o \ + $(B)/client/net_chan.o \ + $(B)/client/net_ip.o \ + $(B)/client/huffman.o \ + \ + $(B)/client/snd_adpcm.o \ + $(B)/client/snd_dma.o \ + $(B)/client/snd_mem.o \ + $(B)/client/snd_mix.o \ + $(B)/client/snd_wavelet.o \ + \ + $(B)/client/snd_main.o \ + $(B)/client/snd_codec.o \ + $(B)/client/snd_codec_wav.o \ + $(B)/client/snd_codec_ogg.o \ + \ + $(B)/client/qal.o \ + $(B)/client/snd_openal.o \ + \ + $(B)/client/cl_curl.o \ + \ + $(B)/client/sv_bot.o \ + $(B)/client/sv_ccmds.o \ + $(B)/client/sv_client.o \ + $(B)/client/sv_game.o \ + $(B)/client/sv_init.o \ + $(B)/client/sv_main.o \ + $(B)/client/sv_net_chan.o \ + $(B)/client/sv_snapshot.o \ + $(B)/client/sv_world.o \ + \ + $(B)/client/q_math.o \ + $(B)/client/q_shared.o \ + \ + $(B)/client/unzip.o \ + $(B)/client/ioapi.o \ + $(B)/client/puff.o \ + $(B)/client/vm.o \ + $(B)/client/vm_interpreted.o \ + \ + $(B)/client/be_aas_bspq3.o \ + $(B)/client/be_aas_cluster.o \ + $(B)/client/be_aas_debug.o \ + $(B)/client/be_aas_entity.o \ + $(B)/client/be_aas_file.o \ + $(B)/client/be_aas_main.o \ + $(B)/client/be_aas_move.o \ + $(B)/client/be_aas_optimize.o \ + $(B)/client/be_aas_reach.o \ + $(B)/client/be_aas_route.o \ + $(B)/client/be_aas_routealt.o \ + $(B)/client/be_aas_sample.o \ + $(B)/client/be_ai_char.o \ + $(B)/client/be_ai_chat.o \ + $(B)/client/be_ai_gen.o \ + $(B)/client/be_ai_goal.o \ + $(B)/client/be_ai_move.o \ + $(B)/client/be_ai_weap.o \ + $(B)/client/be_ai_weight.o \ + $(B)/client/be_ea.o \ + $(B)/client/be_interface.o \ + $(B)/client/l_crc.o \ + $(B)/client/l_libvar.o \ + $(B)/client/l_log.o \ + $(B)/client/l_memory.o \ + $(B)/client/l_precomp.o \ + $(B)/client/l_script.o \ + $(B)/client/l_struct.o \ + \ + $(B)/client/sdl_input.o \ + $(B)/client/sdl_snd.o \ + \ + $(B)/client/con_passive.o \ + $(B)/client/con_log.o \ + $(B)/client/sys_main.o + +Q3ROBJ = \ + $(B)/renderer/tr_animation.o \ + $(B)/renderer/tr_backend.o \ + $(B)/renderer/tr_bsp.o \ + $(B)/renderer/tr_cmds.o \ + $(B)/renderer/tr_curve.o \ + $(B)/renderer/tr_flares.o \ + $(B)/renderer/tr_font.o \ + $(B)/renderer/tr_image.o \ + $(B)/renderer/tr_image_png.o \ + $(B)/renderer/tr_image_jpg.o \ + $(B)/renderer/tr_image_bmp.o \ + $(B)/renderer/tr_image_tga.o \ + $(B)/renderer/tr_image_pcx.o \ + $(B)/renderer/tr_init.o \ + $(B)/renderer/tr_light.o \ + $(B)/renderer/tr_main.o \ + $(B)/renderer/tr_marks.o \ + $(B)/renderer/tr_mesh.o \ + $(B)/renderer/tr_model.o \ + $(B)/renderer/tr_model_iqm.o \ + $(B)/renderer/tr_noise.o \ + $(B)/renderer/tr_scene.o \ + $(B)/renderer/tr_shade.o \ + $(B)/renderer/tr_shade_calc.o \ + $(B)/renderer/tr_shader.o \ + $(B)/renderer/tr_shadows.o \ + $(B)/renderer/tr_sky.o \ + $(B)/renderer/tr_surface.o \ + $(B)/renderer/tr_world.o \ + \ + $(B)/renderer/sdl_gamma.o + +ifneq ($(USE_RENDERER_DLOPEN), 0) + Q3ROBJ += \ + $(B)/renderer/q_shared.o \ + $(B)/renderer/puff.o \ + $(B)/renderer/q_math.o \ + $(B)/renderer/tr_subs.o +endif + +ifneq ($(USE_INTERNAL_JPEG),0) + Q3ROBJ += \ + $(B)/renderer/jaricom.o \ + $(B)/renderer/jcapimin.o \ + $(B)/renderer/jcapistd.o \ + $(B)/renderer/jcarith.o \ + $(B)/renderer/jccoefct.o \ + $(B)/renderer/jccolor.o \ + $(B)/renderer/jcdctmgr.o \ + $(B)/renderer/jchuff.o \ + $(B)/renderer/jcinit.o \ + $(B)/renderer/jcmainct.o \ + $(B)/renderer/jcmarker.o \ + $(B)/renderer/jcmaster.o \ + $(B)/renderer/jcomapi.o \ + $(B)/renderer/jcparam.o \ + $(B)/renderer/jcprepct.o \ + $(B)/renderer/jcsample.o \ + $(B)/renderer/jctrans.o \ + $(B)/renderer/jdapimin.o \ + $(B)/renderer/jdapistd.o \ + $(B)/renderer/jdarith.o \ + $(B)/renderer/jdatadst.o \ + $(B)/renderer/jdatasrc.o \ + $(B)/renderer/jdcoefct.o \ + $(B)/renderer/jdcolor.o \ + $(B)/renderer/jddctmgr.o \ + $(B)/renderer/jdhuff.o \ + $(B)/renderer/jdinput.o \ + $(B)/renderer/jdmainct.o \ + $(B)/renderer/jdmarker.o \ + $(B)/renderer/jdmaster.o \ + $(B)/renderer/jdmerge.o \ + $(B)/renderer/jdpostct.o \ + $(B)/renderer/jdsample.o \ + $(B)/renderer/jdtrans.o \ + $(B)/renderer/jerror.o \ + $(B)/renderer/jfdctflt.o \ + $(B)/renderer/jfdctfst.o \ + $(B)/renderer/jfdctint.o \ + $(B)/renderer/jidctflt.o \ + $(B)/renderer/jidctfst.o \ + $(B)/renderer/jidctint.o \ + $(B)/renderer/jmemmgr.o \ + $(B)/renderer/jmemnobs.o \ + $(B)/renderer/jquant1.o \ + $(B)/renderer/jquant2.o \ + $(B)/renderer/jutils.o +endif + +ifeq ($(ARCH),i386) + Q3OBJ += \ + $(B)/client/snd_mixa.o \ + $(B)/client/matha.o \ + $(B)/client/snapvector.o \ + $(B)/client/ftola.o +endif +ifeq ($(ARCH),x86) + Q3OBJ += \ + $(B)/client/snd_mixa.o \ + $(B)/client/matha.o \ + $(B)/client/snapvector.o \ + $(B)/client/ftola.o +endif +ifeq ($(ARCH),x86_64) + Q3OBJ += \ + $(B)/client/snapvector.o \ + $(B)/client/ftola.o +endif +ifeq ($(ARCH),amd64) + Q3OBJ += \ + $(B)/client/snapvector.o \ + $(B)/client/ftola.o +endif +ifeq ($(ARCH),x64) + Q3OBJ += \ + $(B)/client/snapvector.o \ + $(B)/client/ftola.o +endif + +ifeq ($(USE_VOIP),1) +ifeq ($(USE_INTERNAL_SPEEX),1) +Q3OBJ += \ + $(B)/client/bits.o \ + $(B)/client/buffer.o \ + $(B)/client/cb_search.o \ + $(B)/client/exc_10_16_table.o \ + $(B)/client/exc_10_32_table.o \ + $(B)/client/exc_20_32_table.o \ + $(B)/client/exc_5_256_table.o \ + $(B)/client/exc_5_64_table.o \ + $(B)/client/exc_8_128_table.o \ + $(B)/client/fftwrap.o \ + $(B)/client/filterbank.o \ + $(B)/client/filters.o \ + $(B)/client/gain_table.o \ + $(B)/client/gain_table_lbr.o \ + $(B)/client/hexc_10_32_table.o \ + $(B)/client/hexc_table.o \ + $(B)/client/high_lsp_tables.o \ + $(B)/client/jitter.o \ + $(B)/client/kiss_fft.o \ + $(B)/client/kiss_fftr.o \ + $(B)/client/lpc.o \ + $(B)/client/lsp.o \ + $(B)/client/lsp_tables_nb.o \ + $(B)/client/ltp.o \ + $(B)/client/mdf.o \ + $(B)/client/modes.o \ + $(B)/client/modes_wb.o \ + $(B)/client/nb_celp.o \ + $(B)/client/preprocess.o \ + $(B)/client/quant_lsp.o \ + $(B)/client/resample.o \ + $(B)/client/sb_celp.o \ + $(B)/client/smallft.o \ + $(B)/client/speex.o \ + $(B)/client/speex_callbacks.o \ + $(B)/client/speex_header.o \ + $(B)/client/stereo.o \ + $(B)/client/vbr.o \ + $(B)/client/vq.o \ + $(B)/client/window.o +endif +endif + +ifeq ($(USE_INTERNAL_ZLIB),1) +Q3OBJ += \ + $(B)/client/adler32.o \ + $(B)/client/crc32.o \ + $(B)/client/inffast.o \ + $(B)/client/inflate.o \ + $(B)/client/inftrees.o \ + $(B)/client/zutil.o +endif + +ifeq ($(HAVE_VM_COMPILED),true) + ifeq ($(ARCH),i386) + Q3OBJ += \ + $(B)/client/vm_x86.o + endif + ifeq ($(ARCH),x86) + Q3OBJ += \ + $(B)/client/vm_x86.o + endif + ifeq ($(ARCH),x86_64) + ifeq ($(USE_OLD_VM64),1) + Q3OBJ += \ + $(B)/client/vm_x86_64.o \ + $(B)/client/vm_x86_64_assembler.o + else + Q3OBJ += \ + $(B)/client/vm_x86.o + endif + endif + ifeq ($(ARCH),amd64) + ifeq ($(USE_OLD_VM64),1) + Q3OBJ += \ + $(B)/client/vm_x86_64.o \ + $(B)/client/vm_x86_64_assembler.o + else + Q3OBJ += \ + $(B)/client/vm_x86.o + endif + endif + ifeq ($(ARCH),x64) + ifeq ($(USE_OLD_VM64),1) + Q3OBJ += \ + $(B)/client/vm_x86_64.o \ + $(B)/client/vm_x86_64_assembler.o + else + Q3OBJ += \ + $(B)/client/vm_x86.o + endif + endif + ifeq ($(ARCH),ppc) + Q3OBJ += $(B)/client/vm_powerpc.o $(B)/client/vm_powerpc_asm.o + endif + ifeq ($(ARCH),ppc64) + Q3OBJ += $(B)/client/vm_powerpc.o $(B)/client/vm_powerpc_asm.o + endif + ifeq ($(ARCH),sparc) + Q3OBJ += $(B)/client/vm_sparc.o + endif +endif + +ifeq ($(PLATFORM),mingw32) + Q3OBJ += \ + $(B)/client/win_resource.o \ + $(B)/client/sys_win32.o +else + Q3OBJ += \ + $(B)/client/sys_unix.o +endif + +ifeq ($(PLATFORM),darwin) + Q3OBJ += \ + $(B)/client/sys_osx.o +endif + +ifeq ($(USE_MUMBLE),1) + Q3OBJ += \ + $(B)/client/libmumblelink.o +endif + +Q3POBJ += \ + $(B)/renderer/sdl_glimp.o + +Q3POBJ_SMP += \ + $(B)/renderersmp/sdl_glimp.o + +ifneq ($(USE_RENDERER_DLOPEN),0) +$(B)/ioquake3$(FULLBINEXT): $(Q3OBJ) $(LIBSDLMAIN) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CLIENT_CFLAGS) $(CFLAGS) $(CLIENT_LDFLAGS) $(LDFLAGS) \ + -o $@ $(Q3OBJ) \ + $(LIBSDLMAIN) $(CLIENT_LIBS) $(LIBS) + +$(B)/renderer_opengl1_$(SHLIBNAME): $(Q3ROBJ) $(Q3POBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3ROBJ) $(Q3POBJ) \ + $(THREAD_LIBS) $(LIBSDLMAIN) $(RENDERER_LIBS) $(LIBS) + +$(B)/renderer_opengl1_smp_$(SHLIBNAME): $(Q3ROBJ) $(Q3POBJ_SMP) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3ROBJ) $(Q3POBJ_SMP) \ + $(THREAD_LIBS) $(LIBSDLMAIN) $(RENDERER_LIBS) $(LIBS) +else +$(B)/ioquake3$(FULLBINEXT): $(Q3OBJ) $(Q3ROBJ) $(Q3POBJ) $(LIBSDLMAIN) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CLIENT_CFLAGS) $(CFLAGS) $(CLIENT_LDFLAGS) $(LDFLAGS) \ + -o $@ $(Q3OBJ) $(Q3ROBJ) $(Q3POBJ) \ + $(LIBSDLMAIN) $(CLIENT_LIBS) $(RENDERER_LIBS) $(LIBS) + +$(B)/ioquake3-smp$(FULLBINEXT): $(Q3OBJ) $(Q3ROBJ) $(Q3POBJ_SMP) $(LIBSDLMAIN) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CLIENT_CFLAGS) $(CFLAGS) $(CLIENT_LDFLAGS) $(LDFLAGS) $(THREAD_LDFLAGS) \ + -o $@ $(Q3OBJ) $(Q3ROBJ) $(Q3POBJ_SMP) \ + $(THREAD_LIBS) $(LIBSDLMAIN) $(CLIENT_LIBS) $(RENDERER_LIBS) $(LIBS) +endif + +ifneq ($(strip $(LIBSDLMAIN)),) +ifneq ($(strip $(LIBSDLMAINSRC)),) +$(LIBSDLMAIN) : $(LIBSDLMAINSRC) + cp $< $@ + ranlib $@ +endif +endif + + + +############################################################################# +# DEDICATED SERVER +############################################################################# + +Q3DOBJ = \ + $(B)/ded/sv_bot.o \ + $(B)/ded/sv_client.o \ + $(B)/ded/sv_ccmds.o \ + $(B)/ded/sv_game.o \ + $(B)/ded/sv_init.o \ + $(B)/ded/sv_main.o \ + $(B)/ded/sv_net_chan.o \ + $(B)/ded/sv_snapshot.o \ + $(B)/ded/sv_world.o \ + \ + $(B)/ded/cm_load.o \ + $(B)/ded/cm_patch.o \ + $(B)/ded/cm_polylib.o \ + $(B)/ded/cm_test.o \ + $(B)/ded/cm_trace.o \ + $(B)/ded/cmd.o \ + $(B)/ded/common.o \ + $(B)/ded/cvar.o \ + $(B)/ded/files.o \ + $(B)/ded/md4.o \ + $(B)/ded/msg.o \ + $(B)/ded/net_chan.o \ + $(B)/ded/net_ip.o \ + $(B)/ded/huffman.o \ + \ + $(B)/ded/q_math.o \ + $(B)/ded/q_shared.o \ + \ + $(B)/ded/unzip.o \ + $(B)/ded/ioapi.o \ + $(B)/ded/vm.o \ + $(B)/ded/vm_interpreted.o \ + \ + $(B)/ded/be_aas_bspq3.o \ + $(B)/ded/be_aas_cluster.o \ + $(B)/ded/be_aas_debug.o \ + $(B)/ded/be_aas_entity.o \ + $(B)/ded/be_aas_file.o \ + $(B)/ded/be_aas_main.o \ + $(B)/ded/be_aas_move.o \ + $(B)/ded/be_aas_optimize.o \ + $(B)/ded/be_aas_reach.o \ + $(B)/ded/be_aas_route.o \ + $(B)/ded/be_aas_routealt.o \ + $(B)/ded/be_aas_sample.o \ + $(B)/ded/be_ai_char.o \ + $(B)/ded/be_ai_chat.o \ + $(B)/ded/be_ai_gen.o \ + $(B)/ded/be_ai_goal.o \ + $(B)/ded/be_ai_move.o \ + $(B)/ded/be_ai_weap.o \ + $(B)/ded/be_ai_weight.o \ + $(B)/ded/be_ea.o \ + $(B)/ded/be_interface.o \ + $(B)/ded/l_crc.o \ + $(B)/ded/l_libvar.o \ + $(B)/ded/l_log.o \ + $(B)/ded/l_memory.o \ + $(B)/ded/l_precomp.o \ + $(B)/ded/l_script.o \ + $(B)/ded/l_struct.o \ + \ + $(B)/ded/null_client.o \ + $(B)/ded/null_input.o \ + $(B)/ded/null_snddma.o \ + \ + $(B)/ded/con_log.o \ + $(B)/ded/sys_main.o + +ifeq ($(ARCH),i386) + Q3DOBJ += \ + $(B)/ded/matha.o \ + $(B)/ded/snapvector.o \ + $(B)/ded/ftola.o +endif +ifeq ($(ARCH),x86) + Q3DOBJ += \ + $(B)/ded/matha.o \ + $(B)/ded/snapvector.o \ + $(B)/ded/ftola.o +endif +ifeq ($(ARCH),x86_64) + Q3DOBJ += \ + $(B)/ded/snapvector.o \ + $(B)/ded/ftola.o +endif +ifeq ($(ARCH),amd64) + Q3DOBJ += \ + $(B)/ded/snapvector.o \ + $(B)/ded/ftola.o +endif +ifeq ($(ARCH),x64) + Q3DOBJ += \ + $(B)/ded/snapvector.o \ + $(B)/ded/ftola.o +endif + +ifeq ($(USE_INTERNAL_ZLIB),1) +Q3DOBJ += \ + $(B)/ded/adler32.o \ + $(B)/ded/crc32.o \ + $(B)/ded/inffast.o \ + $(B)/ded/inflate.o \ + $(B)/ded/inftrees.o \ + $(B)/ded/zutil.o +endif + +ifeq ($(HAVE_VM_COMPILED),true) + ifeq ($(ARCH),i386) + Q3DOBJ += \ + $(B)/ded/vm_x86.o + endif + ifeq ($(ARCH),x86) + Q3DOBJ += \ + $(B)/ded/vm_x86.o + endif + ifeq ($(ARCH),x86_64) + ifeq ($(USE_OLD_VM64),1) + Q3DOBJ += \ + $(B)/ded/vm_x86_64.o \ + $(B)/ded/vm_x86_64_assembler.o + else + Q3DOBJ += \ + $(B)/ded/vm_x86.o + endif + endif + ifeq ($(ARCH),amd64) + ifeq ($(USE_OLD_VM64),1) + Q3DOBJ += \ + $(B)/ded/vm_x86_64.o \ + $(B)/ded/vm_x86_64_assembler.o + else + Q3DOBJ += \ + $(B)/ded/vm_x86.o + endif + endif + ifeq ($(ARCH),x64) + ifeq ($(USE_OLD_VM64),1) + Q3DOBJ += \ + $(B)/ded/vm_x86_64.o \ + $(B)/ded/vm_x86_64_assembler.o + else + Q3DOBJ += \ + $(B)/ded/vm_x86.o + endif + endif + ifeq ($(ARCH),ppc) + Q3DOBJ += $(B)/ded/vm_powerpc.o $(B)/ded/vm_powerpc_asm.o + endif + ifeq ($(ARCH),ppc64) + Q3DOBJ += $(B)/ded/vm_powerpc.o $(B)/ded/vm_powerpc_asm.o + endif + ifeq ($(ARCH),sparc) + Q3DOBJ += $(B)/ded/vm_sparc.o + endif +endif + +ifeq ($(PLATFORM),mingw32) + Q3DOBJ += \ + $(B)/ded/win_resource.o \ + $(B)/ded/sys_win32.o \ + $(B)/ded/con_win32.o +else + Q3DOBJ += \ + $(B)/ded/sys_unix.o \ + $(B)/ded/con_tty.o +endif + +ifeq ($(PLATFORM),darwin) + Q3DOBJ += \ + $(B)/ded/sys_osx.o +endif + +$(B)/ioq3ded$(FULLBINEXT): $(Q3DOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(Q3DOBJ) $(LIBS) + + + +############################################################################# +## BASEQ3 CGAME +############################################################################# + +Q3CGOBJ_ = \ + $(B)/baseq3/cgame/cg_main.o \ + $(B)/baseq3/cgame/bg_misc.o \ + $(B)/baseq3/cgame/bg_pmove.o \ + $(B)/baseq3/cgame/bg_slidemove.o \ + $(B)/baseq3/cgame/bg_lib.o \ + $(B)/baseq3/cgame/cg_consolecmds.o \ + $(B)/baseq3/cgame/cg_draw.o \ + $(B)/baseq3/cgame/cg_drawtools.o \ + $(B)/baseq3/cgame/cg_effects.o \ + $(B)/baseq3/cgame/cg_ents.o \ + $(B)/baseq3/cgame/cg_event.o \ + $(B)/baseq3/cgame/cg_info.o \ + $(B)/baseq3/cgame/cg_localents.o \ + $(B)/baseq3/cgame/cg_marks.o \ + $(B)/baseq3/cgame/cg_particles.o \ + $(B)/baseq3/cgame/cg_players.o \ + $(B)/baseq3/cgame/cg_playerstate.o \ + $(B)/baseq3/cgame/cg_predict.o \ + $(B)/baseq3/cgame/cg_scoreboard.o \ + $(B)/baseq3/cgame/cg_servercmds.o \ + $(B)/baseq3/cgame/cg_snapshot.o \ + $(B)/baseq3/cgame/cg_view.o \ + $(B)/baseq3/cgame/cg_weapons.o \ + \ + $(B)/baseq3/qcommon/q_math.o \ + $(B)/baseq3/qcommon/q_shared.o + +Q3CGOBJ = $(Q3CGOBJ_) $(B)/baseq3/cgame/cg_syscalls.o +Q3CGVMOBJ = $(Q3CGOBJ_:%.o=%.asm) + +$(B)/baseq3/cgame$(SHLIBNAME): $(Q3CGOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3CGOBJ) + +$(B)/baseq3/vm/cgame.qvm: $(Q3CGVMOBJ) $(CGDIR)/cg_syscalls.asm $(Q3ASM) + $(echo_cmd) "Q3ASM $@" + $(Q)$(Q3ASM) -o $@ $(Q3CGVMOBJ) $(CGDIR)/cg_syscalls.asm + +############################################################################# +## MISSIONPACK CGAME +############################################################################# + +MPCGOBJ_ = \ + $(B)/missionpack/cgame/cg_main.o \ + $(B)/missionpack/cgame/bg_misc.o \ + $(B)/missionpack/cgame/bg_pmove.o \ + $(B)/missionpack/cgame/bg_slidemove.o \ + $(B)/missionpack/cgame/bg_lib.o \ + $(B)/missionpack/cgame/cg_consolecmds.o \ + $(B)/missionpack/cgame/cg_newdraw.o \ + $(B)/missionpack/cgame/cg_draw.o \ + $(B)/missionpack/cgame/cg_drawtools.o \ + $(B)/missionpack/cgame/cg_effects.o \ + $(B)/missionpack/cgame/cg_ents.o \ + $(B)/missionpack/cgame/cg_event.o \ + $(B)/missionpack/cgame/cg_info.o \ + $(B)/missionpack/cgame/cg_localents.o \ + $(B)/missionpack/cgame/cg_marks.o \ + $(B)/missionpack/cgame/cg_particles.o \ + $(B)/missionpack/cgame/cg_players.o \ + $(B)/missionpack/cgame/cg_playerstate.o \ + $(B)/missionpack/cgame/cg_predict.o \ + $(B)/missionpack/cgame/cg_scoreboard.o \ + $(B)/missionpack/cgame/cg_servercmds.o \ + $(B)/missionpack/cgame/cg_snapshot.o \ + $(B)/missionpack/cgame/cg_view.o \ + $(B)/missionpack/cgame/cg_weapons.o \ + $(B)/missionpack/ui/ui_shared.o \ + \ + $(B)/missionpack/qcommon/q_math.o \ + $(B)/missionpack/qcommon/q_shared.o + +MPCGOBJ = $(MPCGOBJ_) $(B)/missionpack/cgame/cg_syscalls.o +MPCGVMOBJ = $(MPCGOBJ_:%.o=%.asm) + +$(B)/missionpack/cgame$(SHLIBNAME): $(MPCGOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(MPCGOBJ) + +$(B)/missionpack/vm/cgame.qvm: $(MPCGVMOBJ) $(CGDIR)/cg_syscalls.asm $(Q3ASM) + $(echo_cmd) "Q3ASM $@" + $(Q)$(Q3ASM) -o $@ $(MPCGVMOBJ) $(CGDIR)/cg_syscalls.asm + + + +############################################################################# +## BASEQ3 GAME +############################################################################# + +Q3GOBJ_ = \ + $(B)/baseq3/game/g_main.o \ + $(B)/baseq3/game/ai_chat.o \ + $(B)/baseq3/game/ai_cmd.o \ + $(B)/baseq3/game/ai_dmnet.o \ + $(B)/baseq3/game/ai_dmq3.o \ + $(B)/baseq3/game/ai_main.o \ + $(B)/baseq3/game/ai_team.o \ + $(B)/baseq3/game/ai_vcmd.o \ + $(B)/baseq3/game/bg_misc.o \ + $(B)/baseq3/game/bg_pmove.o \ + $(B)/baseq3/game/bg_slidemove.o \ + $(B)/baseq3/game/bg_lib.o \ + $(B)/baseq3/game/g_active.o \ + $(B)/baseq3/game/g_arenas.o \ + $(B)/baseq3/game/g_bot.o \ + $(B)/baseq3/game/g_client.o \ + $(B)/baseq3/game/g_cmds.o \ + $(B)/baseq3/game/g_combat.o \ + $(B)/baseq3/game/g_items.o \ + $(B)/baseq3/game/g_mem.o \ + $(B)/baseq3/game/g_misc.o \ + $(B)/baseq3/game/g_missile.o \ + $(B)/baseq3/game/g_mover.o \ + $(B)/baseq3/game/g_session.o \ + $(B)/baseq3/game/g_spawn.o \ + $(B)/baseq3/game/g_svcmds.o \ + $(B)/baseq3/game/g_target.o \ + $(B)/baseq3/game/g_team.o \ + $(B)/baseq3/game/g_trigger.o \ + $(B)/baseq3/game/g_utils.o \ + $(B)/baseq3/game/g_weapon.o \ + \ + $(B)/baseq3/qcommon/q_math.o \ + $(B)/baseq3/qcommon/q_shared.o + +Q3GOBJ = $(Q3GOBJ_) $(B)/baseq3/game/g_syscalls.o +Q3GVMOBJ = $(Q3GOBJ_:%.o=%.asm) + +$(B)/baseq3/qagame$(SHLIBNAME): $(Q3GOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3GOBJ) + +$(B)/baseq3/vm/qagame.qvm: $(Q3GVMOBJ) $(GDIR)/g_syscalls.asm $(Q3ASM) + $(echo_cmd) "Q3ASM $@" + $(Q)$(Q3ASM) -o $@ $(Q3GVMOBJ) $(GDIR)/g_syscalls.asm + +############################################################################# +## MISSIONPACK GAME +############################################################################# + +MPGOBJ_ = \ + $(B)/missionpack/game/g_main.o \ + $(B)/missionpack/game/ai_chat.o \ + $(B)/missionpack/game/ai_cmd.o \ + $(B)/missionpack/game/ai_dmnet.o \ + $(B)/missionpack/game/ai_dmq3.o \ + $(B)/missionpack/game/ai_main.o \ + $(B)/missionpack/game/ai_team.o \ + $(B)/missionpack/game/ai_vcmd.o \ + $(B)/missionpack/game/bg_misc.o \ + $(B)/missionpack/game/bg_pmove.o \ + $(B)/missionpack/game/bg_slidemove.o \ + $(B)/missionpack/game/bg_lib.o \ + $(B)/missionpack/game/g_active.o \ + $(B)/missionpack/game/g_arenas.o \ + $(B)/missionpack/game/g_bot.o \ + $(B)/missionpack/game/g_client.o \ + $(B)/missionpack/game/g_cmds.o \ + $(B)/missionpack/game/g_combat.o \ + $(B)/missionpack/game/g_items.o \ + $(B)/missionpack/game/g_mem.o \ + $(B)/missionpack/game/g_misc.o \ + $(B)/missionpack/game/g_missile.o \ + $(B)/missionpack/game/g_mover.o \ + $(B)/missionpack/game/g_session.o \ + $(B)/missionpack/game/g_spawn.o \ + $(B)/missionpack/game/g_svcmds.o \ + $(B)/missionpack/game/g_target.o \ + $(B)/missionpack/game/g_team.o \ + $(B)/missionpack/game/g_trigger.o \ + $(B)/missionpack/game/g_utils.o \ + $(B)/missionpack/game/g_weapon.o \ + \ + $(B)/missionpack/qcommon/q_math.o \ + $(B)/missionpack/qcommon/q_shared.o + +MPGOBJ = $(MPGOBJ_) $(B)/missionpack/game/g_syscalls.o +MPGVMOBJ = $(MPGOBJ_:%.o=%.asm) + +$(B)/missionpack/qagame$(SHLIBNAME): $(MPGOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(MPGOBJ) + +$(B)/missionpack/vm/qagame.qvm: $(MPGVMOBJ) $(GDIR)/g_syscalls.asm $(Q3ASM) + $(echo_cmd) "Q3ASM $@" + $(Q)$(Q3ASM) -o $@ $(MPGVMOBJ) $(GDIR)/g_syscalls.asm + + + +############################################################################# +## BASEQ3 UI +############################################################################# + +Q3UIOBJ_ = \ + $(B)/baseq3/ui/ui_main.o \ + $(B)/baseq3/ui/bg_misc.o \ + $(B)/baseq3/ui/bg_lib.o \ + $(B)/baseq3/ui/ui_addbots.o \ + $(B)/baseq3/ui/ui_atoms.o \ + $(B)/baseq3/ui/ui_cdkey.o \ + $(B)/baseq3/ui/ui_cinematics.o \ + $(B)/baseq3/ui/ui_confirm.o \ + $(B)/baseq3/ui/ui_connect.o \ + $(B)/baseq3/ui/ui_controls2.o \ + $(B)/baseq3/ui/ui_credits.o \ + $(B)/baseq3/ui/ui_demo2.o \ + $(B)/baseq3/ui/ui_display.o \ + $(B)/baseq3/ui/ui_gameinfo.o \ + $(B)/baseq3/ui/ui_ingame.o \ + $(B)/baseq3/ui/ui_loadconfig.o \ + $(B)/baseq3/ui/ui_menu.o \ + $(B)/baseq3/ui/ui_mfield.o \ + $(B)/baseq3/ui/ui_mods.o \ + $(B)/baseq3/ui/ui_network.o \ + $(B)/baseq3/ui/ui_options.o \ + $(B)/baseq3/ui/ui_playermodel.o \ + $(B)/baseq3/ui/ui_players.o \ + $(B)/baseq3/ui/ui_playersettings.o \ + $(B)/baseq3/ui/ui_preferences.o \ + $(B)/baseq3/ui/ui_qmenu.o \ + $(B)/baseq3/ui/ui_removebots.o \ + $(B)/baseq3/ui/ui_saveconfig.o \ + $(B)/baseq3/ui/ui_serverinfo.o \ + $(B)/baseq3/ui/ui_servers2.o \ + $(B)/baseq3/ui/ui_setup.o \ + $(B)/baseq3/ui/ui_sound.o \ + $(B)/baseq3/ui/ui_sparena.o \ + $(B)/baseq3/ui/ui_specifyserver.o \ + $(B)/baseq3/ui/ui_splevel.o \ + $(B)/baseq3/ui/ui_sppostgame.o \ + $(B)/baseq3/ui/ui_spskill.o \ + $(B)/baseq3/ui/ui_startserver.o \ + $(B)/baseq3/ui/ui_team.o \ + $(B)/baseq3/ui/ui_teamorders.o \ + $(B)/baseq3/ui/ui_video.o \ + \ + $(B)/baseq3/qcommon/q_math.o \ + $(B)/baseq3/qcommon/q_shared.o + +Q3UIOBJ = $(Q3UIOBJ_) $(B)/missionpack/ui/ui_syscalls.o +Q3UIVMOBJ = $(Q3UIOBJ_:%.o=%.asm) + +$(B)/baseq3/ui$(SHLIBNAME): $(Q3UIOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(Q3UIOBJ) + +$(B)/baseq3/vm/ui.qvm: $(Q3UIVMOBJ) $(UIDIR)/ui_syscalls.asm $(Q3ASM) + $(echo_cmd) "Q3ASM $@" + $(Q)$(Q3ASM) -o $@ $(Q3UIVMOBJ) $(UIDIR)/ui_syscalls.asm + +############################################################################# +## MISSIONPACK UI +############################################################################# + +MPUIOBJ_ = \ + $(B)/missionpack/ui/ui_main.o \ + $(B)/missionpack/ui/ui_atoms.o \ + $(B)/missionpack/ui/ui_gameinfo.o \ + $(B)/missionpack/ui/ui_players.o \ + $(B)/missionpack/ui/ui_shared.o \ + \ + $(B)/missionpack/ui/bg_misc.o \ + $(B)/missionpack/ui/bg_lib.o \ + \ + $(B)/missionpack/qcommon/q_math.o \ + $(B)/missionpack/qcommon/q_shared.o + +MPUIOBJ = $(MPUIOBJ_) $(B)/missionpack/ui/ui_syscalls.o +MPUIVMOBJ = $(MPUIOBJ_:%.o=%.asm) + +$(B)/missionpack/ui$(SHLIBNAME): $(MPUIOBJ) + $(echo_cmd) "LD $@" + $(Q)$(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(MPUIOBJ) + +$(B)/missionpack/vm/ui.qvm: $(MPUIVMOBJ) $(UIDIR)/ui_syscalls.asm $(Q3ASM) + $(echo_cmd) "Q3ASM $@" + $(Q)$(Q3ASM) -o $@ $(MPUIVMOBJ) $(UIDIR)/ui_syscalls.asm + + + +############################################################################# +## CLIENT/SERVER RULES +############################################################################# + +$(B)/client/%.o: $(ASMDIR)/%.s + $(DO_AS) + +# k8 so inline assembler knows about SSE +$(B)/client/%.o: $(ASMDIR)/%.c + $(DO_CC) -march=k8 + +$(B)/client/%.o: $(CDIR)/%.c + $(DO_CC) + +$(B)/client/%.o: $(SDIR)/%.c + $(DO_CC) + +$(B)/client/%.o: $(CMDIR)/%.c + $(DO_CC) + +$(B)/client/%.o: $(BLIBDIR)/%.c + $(DO_BOT_CC) + +$(B)/client/%.o: $(SPEEXDIR)/%.c + $(DO_CC) + +$(B)/client/%.o: $(ZDIR)/%.c + $(DO_CC) + +$(B)/client/%.o: $(SDLDIR)/%.c + $(DO_CC) + +$(B)/renderersmp/%.o: $(SDLDIR)/%.c + $(DO_SMP_CC) + +$(B)/client/%.o: $(SYSDIR)/%.c + $(DO_CC) + +$(B)/client/%.o: $(SYSDIR)/%.m + $(DO_CC) + +$(B)/client/%.o: $(SYSDIR)/%.rc + $(DO_WINDRES) + + +$(B)/renderer/%.o: $(CMDIR)/%.c + $(DO_REF_CC) + +$(B)/renderer/%.o: $(SDLDIR)/%.c + $(DO_REF_CC) + +$(B)/renderer/%.o: $(JPDIR)/%.c + $(DO_REF_CC) + +$(B)/renderer/%.o: $(RDIR)/%.c + $(DO_REF_CC) + + +$(B)/ded/%.o: $(ASMDIR)/%.s + $(DO_AS) + +# k8 so inline assembler knows about SSE +$(B)/ded/%.o: $(ASMDIR)/%.c + $(DO_CC) -march=k8 + +$(B)/ded/%.o: $(SDIR)/%.c + $(DO_DED_CC) + +$(B)/ded/%.o: $(CMDIR)/%.c + $(DO_DED_CC) + +$(B)/ded/%.o: $(ZDIR)/%.c + $(DO_DED_CC) + +$(B)/ded/%.o: $(BLIBDIR)/%.c + $(DO_BOT_CC) + +$(B)/ded/%.o: $(SYSDIR)/%.c + $(DO_DED_CC) + +$(B)/ded/%.o: $(SYSDIR)/%.m + $(DO_DED_CC) + +$(B)/ded/%.o: $(SYSDIR)/%.rc + $(DO_WINDRES) + +$(B)/ded/%.o: $(NDIR)/%.c + $(DO_DED_CC) + +# Extra dependencies to ensure the SVN version is incorporated +ifeq ($(USE_SVN),1) + $(B)/client/cl_console.o : .svn/entries + $(B)/client/common.o : .svn/entries + $(B)/ded/common.o : .svn/entries +endif + + +############################################################################# +## GAME MODULE RULES +############################################################################# + +$(B)/baseq3/cgame/bg_%.o: $(GDIR)/bg_%.c + $(DO_CGAME_CC) + +$(B)/baseq3/cgame/%.o: $(CGDIR)/%.c + $(DO_CGAME_CC) + +$(B)/baseq3/cgame/bg_%.asm: $(GDIR)/bg_%.c $(Q3LCC) + $(DO_CGAME_Q3LCC) + +$(B)/baseq3/cgame/%.asm: $(CGDIR)/%.c $(Q3LCC) + $(DO_CGAME_Q3LCC) + +$(B)/missionpack/cgame/bg_%.o: $(GDIR)/bg_%.c + $(DO_CGAME_CC_MISSIONPACK) + +$(B)/missionpack/cgame/%.o: $(CGDIR)/%.c + $(DO_CGAME_CC_MISSIONPACK) + +$(B)/missionpack/cgame/bg_%.asm: $(GDIR)/bg_%.c $(Q3LCC) + $(DO_CGAME_Q3LCC_MISSIONPACK) + +$(B)/missionpack/cgame/%.asm: $(CGDIR)/%.c $(Q3LCC) + $(DO_CGAME_Q3LCC_MISSIONPACK) + + +$(B)/baseq3/game/%.o: $(GDIR)/%.c + $(DO_GAME_CC) + +$(B)/baseq3/game/%.asm: $(GDIR)/%.c $(Q3LCC) + $(DO_GAME_Q3LCC) + +$(B)/missionpack/game/%.o: $(GDIR)/%.c + $(DO_GAME_CC_MISSIONPACK) + +$(B)/missionpack/game/%.asm: $(GDIR)/%.c $(Q3LCC) + $(DO_GAME_Q3LCC_MISSIONPACK) + + +$(B)/baseq3/ui/bg_%.o: $(GDIR)/bg_%.c + $(DO_UI_CC) + +$(B)/baseq3/ui/%.o: $(Q3UIDIR)/%.c + $(DO_UI_CC) + +$(B)/baseq3/ui/bg_%.asm: $(GDIR)/bg_%.c $(Q3LCC) + $(DO_UI_Q3LCC) + +$(B)/baseq3/ui/%.asm: $(Q3UIDIR)/%.c $(Q3LCC) + $(DO_UI_Q3LCC) + +$(B)/missionpack/ui/bg_%.o: $(GDIR)/bg_%.c + $(DO_UI_CC_MISSIONPACK) + +$(B)/missionpack/ui/%.o: $(UIDIR)/%.c + $(DO_UI_CC_MISSIONPACK) + +$(B)/missionpack/ui/bg_%.asm: $(GDIR)/bg_%.c $(Q3LCC) + $(DO_UI_Q3LCC_MISSIONPACK) + +$(B)/missionpack/ui/%.asm: $(UIDIR)/%.c $(Q3LCC) + $(DO_UI_Q3LCC_MISSIONPACK) + + +$(B)/baseq3/qcommon/%.o: $(CMDIR)/%.c + $(DO_SHLIB_CC) + +$(B)/baseq3/qcommon/%.asm: $(CMDIR)/%.c $(Q3LCC) + $(DO_Q3LCC) + +$(B)/missionpack/qcommon/%.o: $(CMDIR)/%.c + $(DO_SHLIB_CC_MISSIONPACK) + +$(B)/missionpack/qcommon/%.asm: $(CMDIR)/%.c $(Q3LCC) + $(DO_Q3LCC_MISSIONPACK) + + +############################################################################# +# MISC +############################################################################# + +OBJ = $(Q3OBJ) $(Q3POBJ) $(Q3POBJ_SMP) $(Q3ROBJ) $(Q3DOBJ) \ + $(MPGOBJ) $(Q3GOBJ) $(Q3CGOBJ) $(MPCGOBJ) $(Q3UIOBJ) $(MPUIOBJ) \ + $(MPGVMOBJ) $(Q3GVMOBJ) $(Q3CGVMOBJ) $(MPCGVMOBJ) $(Q3UIVMOBJ) $(MPUIVMOBJ) +TOOLSOBJ = $(LBURGOBJ) $(Q3CPPOBJ) $(Q3RCCOBJ) $(Q3LCCOBJ) $(Q3ASMOBJ) + + +copyfiles: release + @if [ ! -d $(COPYDIR)/baseq3 ]; then echo "You need to set COPYDIR to where your Quake3 data is!"; fi +ifneq ($(BUILD_GAME_SO),0) + -$(MKDIR) -p -m 0755 $(COPYDIR)/baseq3 + ifneq ($(BUILD_MISSIONPACK),0) + -$(MKDIR) -p -m 0755 $(COPYDIR)/missionpack + endif +endif + +ifneq ($(BUILD_CLIENT),0) + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/ioquake3$(FULLBINEXT) $(COPYBINDIR)/ioquake3$(FULLBINEXT) + ifneq ($(USE_RENDERER_DLOPEN),0) + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/renderer_opengl1_$(SHLIBNAME) $(COPYBINDIR)/renderer_opengl1_$(SHLIBNAME) + endif +endif + +# Don't copy the SMP until it's working together with SDL. +ifneq ($(BUILD_CLIENT_SMP),0) + ifneq ($(USE_RENDERER_DLOPEN),0) + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/renderer_opengl1_smp_$(SHLIBNAME) $(COPYBINDIR)/renderer_opengl1_smp_$(SHLIBNAME) + else + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/ioquake3-smp$(FULLBINEXT) $(COPYBINDIR)/ioquake3-smp$(FULLBINEXT) + endif +endif + +ifneq ($(BUILD_SERVER),0) + @if [ -f $(BR)/ioq3ded$(FULLBINEXT) ]; then \ + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/ioq3ded$(FULLBINEXT) $(COPYBINDIR)/ioq3ded$(FULLBINEXT); \ + fi +endif + +ifneq ($(BUILD_GAME_SO),0) + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/baseq3/cgame$(SHLIBNAME) \ + $(COPYDIR)/baseq3/. + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/baseq3/qagame$(SHLIBNAME) \ + $(COPYDIR)/baseq3/. + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/baseq3/ui$(SHLIBNAME) \ + $(COPYDIR)/baseq3/. + ifneq ($(BUILD_MISSIONPACK),0) + -$(MKDIR) -p -m 0755 $(COPYDIR)/missionpack + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/missionpack/cgame$(SHLIBNAME) \ + $(COPYDIR)/missionpack/. + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/missionpack/qagame$(SHLIBNAME) \ + $(COPYDIR)/missionpack/. + $(INSTALL) $(STRIP_FLAG) -m 0755 $(BR)/missionpack/ui$(SHLIBNAME) \ + $(COPYDIR)/missionpack/. + endif +endif + +clean: clean-debug clean-release +ifeq ($(PLATFORM),mingw32) + @$(MAKE) -C $(NSISDIR) clean +else + @$(MAKE) -C $(LOKISETUPDIR) clean +endif + +clean-debug: + @$(MAKE) clean2 B=$(BD) + +clean-release: + @$(MAKE) clean2 B=$(BR) + +clean2: + @echo "CLEAN $(B)" + @rm -f $(OBJ) + @rm -f $(OBJ_D_FILES) + @rm -f $(TARGETS) + +toolsclean: toolsclean-debug toolsclean-release + +toolsclean-debug: + @$(MAKE) toolsclean2 B=$(BD) + +toolsclean-release: + @$(MAKE) toolsclean2 B=$(BR) + +toolsclean2: + @echo "TOOLS_CLEAN $(B)" + @rm -f $(TOOLSOBJ) + @rm -f $(TOOLSOBJ_D_FILES) + @rm -f $(LBURG) $(DAGCHECK_C) $(Q3RCC) $(Q3CPP) $(Q3LCC) $(Q3ASM) + +distclean: clean toolsclean + @rm -rf $(BUILD_DIR) + +installer: release +ifeq ($(PLATFORM),mingw32) + @$(MAKE) VERSION=$(VERSION) -C $(NSISDIR) V=$(V) +else + @$(MAKE) VERSION=$(VERSION) -C $(LOKISETUPDIR) V=$(V) +endif + +dist: + rm -rf ioquake3-$(VERSION) + svn export . ioquake3-$(VERSION) + tar --owner=root --group=root --force-local -cjf ioquake3-$(VERSION).tar.bz2 ioquake3-$(VERSION) + rm -rf ioquake3-$(VERSION) + +############################################################################# +# DEPENDENCIES +############################################################################# + +ifneq ($(B),) + OBJ_D_FILES=$(filter %.d,$(OBJ:%.o=%.d)) + TOOLSOBJ_D_FILES=$(filter %.d,$(TOOLSOBJ:%.o=%.d)) + -include $(OBJ_D_FILES) $(TOOLSOBJ_D_FILES) +endif + +.PHONY: all clean clean2 clean-debug clean-release copyfiles \ + debug default dist distclean installer makedirs \ + release targets \ + toolsclean toolsclean2 toolsclean-debug toolsclean-release \ + $(OBJ_D_FILES) $(TOOLSOBJ_D_FILES) diff --git a/NOTTODO b/NOTTODO new file mode 100644 index 0000000..0db1547 --- /dev/null +++ b/NOTTODO @@ -0,0 +1 @@ +http://wiki.ioquake3.org/NotToDo diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/README_IOQ3 b/README_IOQ3 new file mode 100644 index 0000000..ded561f --- /dev/null +++ b/README_IOQ3 @@ -0,0 +1,712 @@ + ,---------------------------------------. + | _ _ ____ | + | (_)___ __ _ _ _ __ _| |_____|__ / | + | | / _ \/ _` | || / _` | / / -_)|_ \ | + | |_\___/\__, |\_,_\__,_|_\_\___|___/ | + | |_| | + | | + `---------- http://ioquake3.org --------' + +The intent of this project is to provide a baseline Quake 3 which may be used +for further development and baseq3 fun. +Some of the major features currently implemented are: + + * SDL backend + * OpenAL sound API support (multiple speaker support and better sound + quality) + * Full x86_64 support on Linux + * VoIP support, both in-game and external support through Mumble. + * MinGW compilation support on Windows and cross compilation support on Linux + * AVI video capture of demos + * Much improved console autocompletion + * Persistent console history + * Colorized terminal output + * Optional Ogg Vorbis support + * Much improved QVM tools + * Support for various esoteric operating systems + * cl_guid support + * HTTP/FTP download redirection (using cURL) + * Multiuser support on Windows systems (user specific game data + is stored in "%APPDATA%\Quake3") + * PNG support + * Many, many bug fixes + +The map editor and associated compiling tools are not included. We suggest you +use a modern copy from http://www.qeradiant.com/. + +The original id software readme that accompanied the Q3 source release has been +renamed to id-readme.txt so as to prevent confusion. Please refer to the +web-site for updated status. + + +--------------------------------------------- Compilation and installation ----- + +For *nix + 1. Change to the directory containing this readme. + 2. Run 'make'. + +For Windows, + 1. Please refer to the excellent instructions here: + http://wiki.ioquake3.org/Building_ioquake3 + +For Mac OS X, building a Universal Binary + 1. Install MacOSX SDK packages from XCode. For maximum compatibility, + install MacOSX10.4u.sdk and MacOSX10.3.9.sdk, and MacOSX10.2.8.sdk. + 2. Change to the directory containing this README file. + 3. Run './make-macosx-ub.sh' + 4. Copy the resulting ioquake3.app in /build/release-darwin-ub to your + /Applications/ioquake3 folder. + +Installation, for *nix + 1. Set the COPYDIR variable in the shell to be where you installed Quake 3 + to. By default it will be /usr/local/games/quake3 if you haven't set it. + This is the path as used by the original Linux Q3 installer and subsequent + point releases. + 2. Run 'make copyfiles'. + +It is also possible to cross compile for Windows under *nix using MinGW. A +script is available to build a cross compilation environment from +http://www.libsdl.org/extras/win32/cross/build-cross.sh. The gcc/binutils +version numbers that the script downloads may need to be altered. +Alternatively, your distribution may have mingw32 packages available. On +debian/Ubuntu, these are mingw32, mingw32-runtime and mingw32-binutils. Cross +compiling is simply a case of using './cross-make-mingw.sh' in place of 'make', +though you may find you need to change the value of the variables in this +script to match your environment. + +The following variables may be set, either on the command line or in +Makefile.local: + + CFLAGS - use this for custom CFLAGS + V - set to show cc command line when building + DEFAULT_BASEDIR - extra path to search for baseq3 and such + BUILD_SERVER - build the 'ioq3ded' server binary + BUILD_CLIENT - build the 'ioquake3' client binary + BUILD_CLIENT_SMP - build the 'ioquake3-smp' client binary + BUILD_GAME_SO - build the game shared libraries + BUILD_GAME_QVM - build the game qvms + BUILD_STANDALONE - build binaries suited for stand-alone games + USE_OPENAL - use OpenAL where available + USE_OPENAL_DLOPEN - link with OpenAL at runtime + USE_CURL - use libcurl for http/ftp download support + USE_CURL_DLOPEN - link with libcurl at runtime + USE_CODEC_VORBIS - enable Ogg Vorbis support + USE_MUMBLE - enable Mumble support + USE_VOIP - enable built-in VoIP support + USE_INTERNAL_SPEEX - build internal speex library instead of dynamically + linking against system libspeex + USE_OLD_VM64 - use Ludwig Nussel's old JIT compiler implementation + for x86_64 + USE_INTERNAL_ZLIB - build and link against internal zlib + USE_INTERNAL_JPEG - build and link against internal JPEG library + USE_LOCAL_HEADERS - use headers local to ioq3 instead of system ones + DEBUG_CFLAGS - C compiler flags to use for building debug version + COPYDIR - the target installation directory + TEMPDIR - specify user defined directory for temp files + +The defaults for these variables differ depending on the target platform. + + +------------------------------------------------------------------ Console ----- + +New cvars + cl_autoRecordDemo - record a new demo on each map change + cl_aviFrameRate - the framerate to use when capturing video + cl_aviMotionJpeg - use the mjpeg codec when capturing video + cl_guidServerUniq - makes cl_guid unique for each server + cl_cURLLib - filename of cURL library to load + cl_consoleKeys - space delimited list of key names or + characters that toggle the console + cl_mouseAccelStyle - Set to 1 for QuakeLive mouse acceleration + behaviour, 0 for standard q3 + cl_mouseAccelOffset - Tuning the acceleration curve, see below + + in_joystickUseAnalog - Do not translate joystick axis events + to keyboard commands + + j_forward - Joystick analogue to m_forward, + for forward movement speed/direction. + j_side - Joystick analogue to m_side, + for side movement speed/direction. + j_pitch - Joystick analogue to m_pitch, + for pitch rotation speed/direction. + j_yaw - Joystick analogue to m_yaw, + for yaw rotation speed/direction. + j_forward_axis - Selects which joystick axis + controls forward/back. + j_side_axis - Selects which joystick axis + controls left/right. + j_pitch_axis - Selects which joystick axis + controls pitch. + j_yaw_axis - Selects which joystick axis + controls yaw. + + s_useOpenAL - use the OpenAL sound backend if available + s_alPrecache - cache OpenAL sounds before use + s_alGain - the value of AL_GAIN for each source + s_alSources - the total number of sources (memory) to + allocate + s_alDopplerFactor - the value passed to alDopplerFactor + s_alDopplerSpeed - the value passed to alDopplerVelocity + s_alMinDistance - the value of AL_REFERENCE_DISTANCE for + each source + s_alMaxDistance - the maximum distance before sounds start + to become inaudible. + s_alRolloff - the value of AL_ROLLOFF_FACTOR for each + source + s_alGraceDistance - after having passed MaxDistance, length + until sounds are completely inaudible + s_alDriver - which OpenAL library to use + s_alDevice - which OpenAL device to use + s_alAvailableDevices - list of available OpenAL devices + s_alInputDevice - which OpenAL input device to use + s_alAvailableInputDevices - list of available OpenAL input devices + s_sdlBits - SDL bit resolution + s_sdlSpeed - SDL sample rate + s_sdlChannels - SDL number of channels + s_sdlDevSamps - SDL DMA buffer size override + s_sdlMixSamps - SDL mix buffer size override + s_backend - read only, indicates the current sound + backend + s_muteWhenMinimized - mute sound when minimized + s_muteWhenUnfocused - mute sound when window is unfocused + sv_dlRate - bandwidth allotted to PK3 file downloads + via UDP, in kbyte/s + + com_ansiColor - enable use of ANSI escape codes in the tty + com_altivec - enable use of altivec on PowerPC systems + com_standalone (read only) - If set to 1, quake3 is running in + standalone mode + com_basegame - Use a different base than baseq3. If no + original Quake3 or TeamArena pak files + are found, this will enable running in + standalone mode + com_homepath - Specify name that is to be appended to the + home path + com_legacyprotocol - Specify protocol version number for + legacy Quake3 1.32c protocol, see + "Network protocols" section below + (startup only) + com_maxfpsUnfocused - Maximum frames per second when unfocused + com_maxfpsMinimized - Maximum frames per second when minimized + com_busyWait - Will use a busy loop to wait for rendering + next frame when set to non-zero value + com_pipefile - Specify filename to create a named pipe + through which other processes can control + the server while it is running. + Nonfunctional on Windows. + com_gamename - Gamename sent to master server in + getservers[Ext] query and infoResponse + "gamename" infostring value. Also used + for filtering local network games. + com_protocol - Specify protocol version number for + current ioquake3 protocol, see + "Network protocols" section below + (startup only) + + in_joystickNo - select which joystick to use + in_availableJoysticks - list of available Joysticks + in_keyboardDebug - print keyboard debug info + + sv_dlURL - the base of the HTTP or FTP site that + holds custom pk3 files for your server + sv_banFile - Name of the file that is used for storing + the server bans + + net_ip6 - IPv6 address to bind to + net_port6 - port to bind to using the ipv6 address + net_enabled - enable networking, bitmask. Add up + number for option to enable it: + enable ipv4 networking: 1 + enable ipv6 networking: 2 + prioritise ipv6 over ipv4: 4 + disable multicast support: 8 + net_mcast6addr - multicast address to use for scanning for + ipv6 servers on the local network + net_mcastiface - outgoing interface to use for scan + + r_allowResize - make window resizable (SDL only) + r_ext_texture_filter_anisotropic - anisotropic texture filtering + r_zProj - distance of observer camera to projection + plane in quake3 standard units + r_greyscale - desaturate textures, useful for anaglyph, + supports values in the range of 0 to 1 + r_stereoEnabled - enable stereo rendering for techniques + like shutter glasses (untested) + r_anaglyphMode - Enable rendering of anaglyph images + red-cyan glasses: 1 + red-blue: 2 + red-green: 3 + To swap the colors for left and right eye + just add 3 to the value for the wanted + color combination. For red-blue and + red-green you probably want to enable + r_greyscale + r_stereoSeparation - Control eye separation. Resulting + separation is r_zProj divided by this + value in quake3 standard units. + See also + http://wiki.ioquake3.org/Stereo_Rendering + for more information + r_marksOnTriangleMeshes - Support impact marks on md3 models, MOD + developers should increase the mark + triangle limits in cg_marks.c if they + intend to use this. + r_sdlDriver - read only, indicates the SDL driver + backend being used + r_noborder - Remove window decoration from window + managers, like borders and titlebar. + r_screenshotJpegQuality - Controls quality of jpeg screenshots + captured using screenshotJPEG + r_aviMotionJpegQuality - Controls quality of video capture when + cl_aviMotionJpeg is enabled + +New commands + video [filename] - start video capture (use with demo command) + stopvideo - stop video capture + stopmusic - stop background music + minimize - Minimize the game and show desktop + + print - print out the contents of a cvar + unset - unset a user created cvar + + banaddr - ban an ip address range from joining a game on this + server, valid is either playernum or CIDR + notation address range. + exceptaddr - exempt an ip address range from a ban. + bandel - delete ban (either range or ban number) + exceptdel - delete exception (either range or exception number) + listbans - list all currently active bans and exceptions + rehashbans - reload the banlist from serverbans.dat + flushbans - delete all bans + + net_restart - restart network subsystem to change latched settings + game_restart - Switch to another mod + + which - print out the path on disk to a loaded item + + +--------------------------------------------------------- README for Users ----- + +Using shared libraries instead of qvm + To force Q3 to use shared libraries instead of qvms run it with the following + parameters: +set sv_pure 0 +set vm_cgame 0 +set vm_game 0 +set vm_ui 0 + +Using Demo Data Files + Copy demoq3/pak0.pk3 from the demo installer to your baseq3 directory. The + qvm files in this pak0.pk3 will not work, so you have to use the native + shared libraries or qvms from this project. To use the new qvms, they must be + put into a pk3 file. A pk3 file is just a zip file, so any compression tool + that can create such files will work. The shared libraries should already be + in the correct place. Use the instructions above to use them. + + Please bear in mind that you will not be able to play online using the demo + data, nor is it something that we like to spend much time maintaining or + supporting. + +Help! Ioquake3 won't give me an fps of X anymore when setting com_maxfps! + Ioquake3 now uses the select() system call to wait for the rendering of the + next frame when com_maxfps was hit. This will improve your CPU load + considerably in these cases. However, not all systems may support a + granularity for its timing functions that is required to perform this waiting + correctly. For instance, ioquake3 tells select() to wait 2 milliseconds, but + really it can only wait for a multiple of 5ms, i.e. 5, 10, 15, 20... ms. + In this case you can always revert back to the old behaviour by setting the + cvar com_busyWait to 1. + +Using HTTP/FTP Download Support (Server) + You can enable redirected downloads on your server even if it's not + an ioquake3 server. You simply need to use the 'sets' command to put + the sv_dlURL cvar into your SERVERINFO string and ensure sv_allowDownloads + is set to 1 + + sv_dlURL is the base of the URL that contains your custom .pk3 files + the client will append both fs_game and the filename to the end of + this value. For example, if you have sv_dlURL set to + "http://ioquake3.org", fs_game is "baseq3", and the client is + missing "test.pk3", it will attempt to download from the URL + "http://ioquake3.org/baseq3/test.pk3" + + sv_allowDownload's value is now a bitmask made up of the following + flags: + 1 - ENABLE + 4 - do not use UDP downloads + 8 - do not ask the client to disconnect when using HTTP/FTP + + Server operators who are concerned about potential "leeching" from their + HTTP servers from other ioquake3 servers can make use of the HTTP_REFERER + that ioquake3 sets which is "ioQ3://{SERVER_IP}:{SERVER_PORT}". For, + example, Apache's mod_rewrite can restrict access based on HTTP_REFERER. + + On a sidenote, downloading via UDP has been improved and yields higher data + rates now. You can configure the maximum bandwidth for UDP downloads via the + cvar sv_dlRate. Due to system-specific limits the download rate is capped + at about 1 Mbyte/s per client, so curl downloading may still be faster. + +Using HTTP/FTP Download Support (Client) + Simply setting cl_allowDownload to 1 will enable HTTP/FTP downloads + assuming ioquake3 was compiled with USE_CURL=1 (the default). + like sv_allowDownload, cl_allowDownload also uses a bitmask value + supporting the following flags: + 1 - ENABLE + 2 - do not use HTTP/FTP downloads + 4 - do not use UDP downloads + + When ioquake3 is built with USE_CURL_DLOPEN=1 (default on some platforms), + it will use the value of the cvar cl_cURLLib as the filename of the cURL + library to dynamically load. + +Multiuser Support on Windows systems + On Windows, all user specific files such as autogenerated configuration, + demos, videos, screenshots, and autodownloaded pk3s are now saved in a + directory specific to the user who is running ioquake3. + + On NT-based such as Windows XP, this is usually a directory named: + "C:\Documents and Settings\%USERNAME%\Application Data\Quake3\" + + Windows 95, Windows 98, and Windows ME will use a directory like: + "C:\Windows\Application Data\Quake3" + in single-user mode, or: + "C:\Windows\Profiles\%USERNAME%\Application Data\Quake3" + if multiple logins have been enabled. + + In order to access this directory more easily, the installer may create a + Shortcut which has its target set to: + "%APPDATA%\Quake3\" + This Shortcut would work for all users on the system regardless of the + locale settings. Unfortunately, this environment variable is only + present on Windows NT based systems. + + You can revert to the old single-user behaviour by setting the fs_homepath + cvar to the directory where ioquake3 is installed. For example: + ioquake3.exe +set fs_homepath "c:\ioquake3" + Note that this cvar MUST be set as a command line parameter. + +SDL Keyboard Differences + ioquake3 clients have different keyboard behaviour compared to the original + Quake3 clients. + + * "Caps Lock" and "Num Lock" can not be used as normal binds since they + do not send a KEYUP event until the key is pressed again. + + * SDL > 1.2.9 does not support disabling dead key recognition. In order to + send dead key characters (e.g. ~, ', `, and ^), you must key a Space (or + sometimes the same character again) after the character to send it on + many international keyboard layouts. + + * The SDL client supports many more keys than the original Quake3 client. + For example the keys: "Windows", "SysReq", "ScrollLock", and "Break". + For non-US keyboards, all of the so called "World" keys are now supported + as well as F13, F14, F15, and the country-specific mode/meta keys. + + On many international layouts the default console toggle keys are also dead + keys, meaning that dropping the console potentially results in + unintentionally initiating the keying of a dead key. Furthermore SDL 1.2's + dead key support is broken by design and Q3 doesn't support non-ASCII text + entry, so the chances are you won't get the correct character anyway. + + If you use such a keyboard layout, you can set the cvar cl_consoleKeys. This + is a space delimited list of key names that will toggle the console. The key + names are the usual Q3 names e.g. "~", "`", "c", "BACKSPACE", "PAUSE", + "WINDOWS" etc. It's also possible to use ASCII characters, by hexadecimal + number. Some example values for cl_consoleKeys: + + "~ ` 0x7e 0x60" Toggle on ~ or ` (the default) + "WINDOWS" Toggle on the Windows key + "c" Toggle on the c key + "0x43" Toggle on the C character (Shift-c) + "PAUSE F1 PGUP" Toggle on the Pause, F1 or Page Up keys + + Note that when you elect a set of console keys or characters, they cannot + then be used for binding, nor will they generate characters when entering + text. Also, in addition to the nominated console keys, Shift-ESC is hard + coded to always toggle the console. + +QuakeLive mouse acceleration (patch and this text written by TTimo from id) + I've been using an experimental mouse acceleration code for a while, and + decided to make it available to everyone. Don't be too worried if you don't + understand the explanations below, this is mostly intended for advanced + players: + To enable it, set cl_mouseAccelStyle 1 (0 is the default/legacy behavior) + + New style is controlled with 3 cvars: + + sensitivity + cl_mouseAccel + cl_mouseAccelOffset + + The old code (cl_mouseAccelStyle 0) can be difficult to calibrate because if + you have a base sensitivity setup, as soon as you set a non zero acceleration + your base sensitivity at low speeds will change as well. The other problem + with style 0 is that you are stuck on a square (power of two) acceleration + curve. + + The new code tries to solve both problems: + + Once you setup your sensitivity to feel comfortable and accurate enough for + low mouse deltas with no acceleration (cl_mouseAccel 0), you can start + increasing cl_mouseAccel and tweaking cl_mouseAccelOffset to get the + amplification you want for high deltas with little effect on low mouse deltas. + + cl_mouseAccel is a power value. Should be >= 1, 2 will be the same power curve + as style 0. The higher the value, the faster the amplification grows with the + mouse delta. + + cl_mouseAccelOffset sets how much base mouse delta will be doubled by + acceleration. The closer to zero you bring it, the more acceleration will + happen at low speeds. This is also very useful if you are changing to a new + mouse with higher dpi, if you go from 500 to 1000 dpi, you can divide your + cl_mouseAccelOffset by two to keep the same overall 'feel' (you will likely + gain in precision when you do that, but that is not related to mouse + acceleration). + + Mouse acceleration is tricky to configure, and when you do you'll have to + re-learn your aiming. But you will find that it's very much forth it in the + long run. + + If you try the new acceleration code and start using it, I'd be very + interested by your feedback. + + +---------------------------------------------------- README for Developers ----- + +64bit mods + If you wish to compile external mods as shared libraries on a 64bit platform, + and the mod source is derived from the id Q3 SDK, you will need to modify the + interface code a little. Open the files ending in _syscalls.c and change + every instance of int to intptr_t in the declaration of the syscall function + pointer and the dllEntry function. Also find the vmMain function for each + module (usually in cg_main.c g_main.c etc.) and similarly replace the return + value in the prototype with intptr_t (arg0, arg1, ...stay int). + + Add the following code snippet to q_shared.h: + + #ifdef Q3_VM + typedef int intptr_t; + #else + #include + #endif + + Note if you simply wish to run mods on a 64bit platform you do not need to + recompile anything since by default Q3 uses a virtual machine system. + +Creating mods compatible with Q3 1.32b + If you're using this package to create mods for the last official release of + Q3, it is necessary to pass the commandline option '-vq3' to your invocation + of q3asm. This is because by default q3asm outputs an updated qvm format that + is necessary to fix a bug involving the optimizing pass of the x86 vm JIT + compiler. + +Creating standalone games + Have you finished the daunting task of removing all dependencies on the Q3 + game data? You probably now want to give your users the opportunity to play + the game without owning a copy of Q3, which consequently means removing cd-key + and authentication server checks. In addition to being a straightforward Q3 + client, ioquake3 also purports to be a reliable and stable code base on which + to base your game project. + + However, before you start compiling your own version of ioquake3, you have to + ask yourself: Have we changed or will we need to change anything of importance + in the engine? + + If your answer to this question is "no", it probably makes no sense to build + your own binaries. Instead, you can just use the pre-built binaries on the + website. Just make sure the game is called with: + + +set com_basegame + + in any links/scripts you install for your users to start the game. The + binary must not detect any original quake3 game pak files. If this + condition is met, the game will set com_standalone to 1 and is then running + in stand alone mode. + + If you want the engine to use a different directory in your homepath than + e.g. "Quake3" on Windows or ".q3a" on Linux, then set a new name at startup + by adding + + +set com_homepath + + to the command line. You can also control which game name to use when talking + to the master server: + + +set com_gamename + + So clients requesting a server list will only receive servers that have a + matching game name. + + Example line: + + +set com_basegame basefoo +set com_homepath .foo + +set com_gamename foo + + + If you really changed parts that would make vanilla ioquake3 incompatible with + your mod, we have included another way to conveniently build a stand-alone + binary. Just run make with the option BUILD_STANDALONE=1. Don't forget to edit + the PRODUCT_NAME and subsequent #defines in qcommon/q_shared.h with + information appropriate for your project. + + While a lot of work has been put into ioquake3 that you can benefit from free + of charge, it does not mean that you have no obligations to fulfill. Please be + aware that as soon as you start distributing your game with an engine based on + our sources we expect you to fully comply with the requirements as stated in + the GPL. That includes making sources and modifications you made to the + ioquake3 engine as well as the game-code used to compile the .qvm files for + the game logic freely available to everyone. Furthermore, note that the "QIIIA + Game Source License" prohibits distribution of mods that are intended to + operate on a version of Q3 not sanctioned by id software: + + "with this Agreement, ID grants to you the non-exclusive and limited right + to distribute copies of the Software ... for operation only with the full + version of the software game QUAKE III ARENA" + + This means that if you're creating a standalone game, you cannot use said + license on any portion of the product. As the only other license this code has + been released under is the GPL, this is the only option. + + This does NOT mean that you cannot market this game commercially. The GPL does + not prohibit commercial exploitation and all assets (e.g. textures, sounds, + maps) created by yourself are your property and can be sold like every other + game you find in stores. + +Network protocols + There are now two cvars that give you some degree of freedom over the reported + protocol versions between clients and servers: "com_protocol" and + "com_legacyprotocol". + The reason for this is that some standalone games increased the protocol + number even though nothing really changed in their protocol and the ioquake3 + engine is still fully compatible. + + In order to harden the network protocol against UDP spoofing attacks a new + network protocol was introduced that defends against such attacks. + Unfortunately, this protocol will be incompatible to the original quake3 1.32c + which is the latest official release from id. + Luckily, ioquake3 has backwards compatibility, on the client as well as on the + server. This means ioquake3 players can play on old servers just as ioquake3 + servers are able to service old clients. + + The cvar "com_protocol" denotes the protocol version for the new hardened + protocol, whereas the "com_legacyprotocol" cvar denotes the protocol version + for the legacy protocol. + If the value for "com_protocol" and "com_legacyprotocol" is identical, then + the legacy protocol is always used. If "com_legacyprotocol" is set to 0, then + support for the legacy protocol is disabled. + + Mods that use a standalone engine obviously do not require dual protocol + support, and it is turned off if the engine is compiled with STANDALONE per + default. If you desire backwards compatibility to older versions of your + game you can still enable it in q_shared.h by defining + LEGACY_PROTOCOL. + +cl_guid Support + cl_guid is a cvar which is part of the client's USERINFO string. Its value + is a 32 character string made up of [a-f] and [0-9] characters. This + value is pseudo-unique for every player. Id's Quake 3 Arena client also + sets cl_guid, but only if Punkbuster is enabled on the client. + + If cl_guidServerUniq is non-zero (the default), then this value is also + pseudo-unique for each server a client connects to (based on IP:PORT of + the server). + + The purpose of cl_guid is to add an identifier for each player on + a server. This value can be reset by the client at any time so it's not + useful for blocking access. However, it can have at least two uses in + your mod's game code: + 1) improve logging to allow statistical tools to index players by more + than just name + 2) granting some weak admin rights to players without requiring passwords + +PNG support + ioquake3 supports the use of PNG (Portable Network Graphic) images as + textures. It should be noted that the use of such images in a map will + result in missing placeholder textures where the map is used with the id + Quake 3 client or earlier versions of ioquake3. + + Recent versions of GtkRadiant and q3map2 support PNG images without + modification. However GtkRadiant is not aware that PNG textures are supported + by ioquake3. To change this behaviour open the file 'q3.game' in the 'games' + directory of the GtkRadiant base directory with an editor and change the + line: + + texturetypes="tga jpg" + + to + + texturetypes="tga jpg png" + + Restart GtkRadiant and PNG textures are now available. + +Building with MinGW for pre Windows XP + IPv6 support requires a header named "wspiapi.h" to abstract away from + differences in earlier versions of Windows' IPv6 stack. There is no MinGW + equivalent of this header and the Microsoft version is obviously not + redistributable, so in its absence we're forced to require Windows XP. + However if this header is acquired separately and placed in the qcommon/ + directory, this restriction is lifted. + + +------------------------------------------------------------- Contributing ----- + +Please send all patches to bugzilla (https://bugzilla.icculus.org), or join the +mailing list (quake3-subscribe@icculus.org) and submit your patch there. The +best case scenario is that you submit your patch to bugzilla, and then post the +URL to the mailing list. + +The focus for ioq3 is to develop a stable base suitable for further development +and provide players with the same Quake 3 experience they've had for years. As +such ioq3 does not have any significant graphical enhancements and none are +planned at this time. However, improved graphics and sound patches will be +accepted as long as they are entirely optional, do not require new media and +are off by default. + + +--------------------------------------------- Building Official Installers ----- + +We need help getting automated installers on all the platforms that ioquake3 +supports. We don't necessarily care about all the installers being identical, +but we have some general guidelines: + + * Please include the id patch pk3s in your installer, which are available + from http://ioquake3.org/patch-data/ subject to agreement to the id + EULA. Your installer shall also ask the user to agree to this EULA (which + is in the /web/include directory for your convenience) and subsequently + refuse to continue the installation of the patch pk3s and pak0.pk3 if they + do not. + + * Please don't require pak0.pk3, since not everyone using the engine + plans on playing Quake 3 Arena on it. It's fine to (optionally) assist the + user in copying the file or tell them how. + + * It is fine to just install the binaries without requiring id EULA agreement, + providing pak0.pk3 and the patch pk3s are not referred to or included in the + installer. + + * Please include at least an SDL so/dylib/dll on every platform. + + * Please include an OpenAL so/dylib/dll, since every platform should be using + it by now. + + * Please contact the mailing list when you've made your installer. + + * Please be prepared to alter your installer on the whim of the maintainers. + + * Your installer will be mirrored to an "official" directory, thus making it + a done deal. + +------------------------------------------------------------------ Credits ----- + +Maintainers + Ludwig Nussel + Thilo Schulz + Tim Angus + Tony J. White + Zachary J. Slater + +Significant contributions from + Ryan C. Gordon + Andreas Kohn + Joerg Dietrich + Stuart Dalton + Vincent S. Cojot + optical + Aaron Gyes diff --git a/TODO b/TODO new file mode 100644 index 0000000..5a3b545 --- /dev/null +++ b/TODO @@ -0,0 +1 @@ +http://wiki.ioquake3.org/Ioquake3_Road_Map diff --git a/build/game_debug/CL.read.1.tlog b/build/game_debug/CL.read.1.tlog new file mode 100644 index 0000000..5cae5b1 Binary files /dev/null and b/build/game_debug/CL.read.1.tlog differ diff --git a/build/game_debug/CL.write.1.tlog b/build/game_debug/CL.write.1.tlog new file mode 100644 index 0000000..33fb20d Binary files /dev/null and b/build/game_debug/CL.write.1.tlog differ diff --git a/build/game_debug/bg_misc.sbr b/build/game_debug/bg_misc.sbr new file mode 100644 index 0000000..ef17134 Binary files /dev/null and b/build/game_debug/bg_misc.sbr differ diff --git a/build/game_debug/bg_saga.obj b/build/game_debug/bg_saga.obj new file mode 100644 index 0000000..f9db0dc Binary files /dev/null and b/build/game_debug/bg_saga.obj differ diff --git a/build/game_debug/bg_saga.sbr b/build/game_debug/bg_saga.sbr new file mode 100644 index 0000000..26f00e8 Binary files /dev/null and b/build/game_debug/bg_saga.sbr differ diff --git a/build/game_debug/bg_vehicleLoad.obj b/build/game_debug/bg_vehicleLoad.obj new file mode 100644 index 0000000..c74bc0e Binary files /dev/null and b/build/game_debug/bg_vehicleLoad.obj differ diff --git a/build/game_debug/bg_vehicleLoad.sbr b/build/game_debug/bg_vehicleLoad.sbr new file mode 100644 index 0000000..4835aa5 Binary files /dev/null and b/build/game_debug/bg_vehicleLoad.sbr differ diff --git a/build/game_debug/bg_weapons.obj b/build/game_debug/bg_weapons.obj new file mode 100644 index 0000000..e3bbb5b Binary files /dev/null and b/build/game_debug/bg_weapons.obj differ diff --git a/build/game_debug/bg_weapons.sbr b/build/game_debug/bg_weapons.sbr new file mode 100644 index 0000000..bebf306 Binary files /dev/null and b/build/game_debug/bg_weapons.sbr differ diff --git a/build/game_debug/cl.command.1.tlog b/build/game_debug/cl.command.1.tlog new file mode 100644 index 0000000..221fdb1 Binary files /dev/null and b/build/game_debug/cl.command.1.tlog differ diff --git a/build/game_debug/game.lastbuildstate b/build/game_debug/game.lastbuildstate new file mode 100644 index 0000000..8597d23 --- /dev/null +++ b/build/game_debug/game.lastbuildstate @@ -0,0 +1,2 @@ +#v4.0:v100 +Debug|Win32|C:\Users\dan\Desktop\jkaq3\misc\msvc10\| diff --git a/build/game_debug/game.log b/build/game_debug/game.log new file mode 100644 index 0000000..5a1e120 --- /dev/null +++ b/build/game_debug/game.log @@ -0,0 +1,210 @@ +Build started 8/26/2011 11:28:52 PM. + 1>Project "C:\Users\dan\Desktop\jkaq3\misc\msvc10\game.vcxproj" on node 2 (build target(s)). + 1>InitializeBuildStatus: + Creating "..\..\build\game_debug\game.unsuccessfulbuild" because "AlwaysCreate" was specified. + ClCompile: + C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin\CL.exe /c /ZI /nologo /W3 /WX- /Od /Oy- /D WIN32 /D _DEBUG /D _WINDOWS /D BUILDING_REF_GL /D DEBUG /D GLOBALRANK /D _JK2MP /D QAGAME /D _CRT_SECURE_NO_DEPRECATE /D _WINDLL /Gm- /EHsc /MTd /GS /fp:precise /Zc:wchar_t /Zc:forScope /Fo"..\..\build\game_debug\\" /Fd"..\..\build\game_debug\vc100.pdb" /FR"..\..\build\game_debug\\" /Gd /TC /analyze- /errorReport:prompt ..\..\..\JEDI_Academy_SDK\codemp\game\tri_coll_test.c ..\..\code\game\ai_main.c ..\..\code\game\ai_util.c ..\..\code\game\ai_wpnav.c ..\..\code\game\AnimalNPC.c ..\..\code\game\bg_g2_utils.c ..\..\code\game\bg_misc.c ..\..\code\game\bg_panimate.c ..\..\code\game\bg_pmove.c ..\..\code\game\bg_saber.c ..\..\code\game\bg_saberLoad.c ..\..\code\game\bg_saga.c ..\..\code\game\bg_slidemove.c ..\..\code\game\bg_vehicleLoad.c ..\..\code\game\bg_weapons.c ..\..\code\game\FighterNPC.c ..\..\code\game\g_active.c ..\..\code\game\g_arenas.c ..\..\code\game\g_bot.c ..\..\code\game\g_client.c ..\..\code\game\g_cmds.c ..\..\code\game\g_combat.c ..\..\code\game\g_exphysics.c ..\..\code\game\g_ICARUScb.c ..\..\code\game\g_items.c ..\..\code\game\g_log.c ..\..\code\game\g_main.c ..\..\code\game\g_mem.c ..\..\code\game\g_misc.c ..\..\code\game\g_missile.c ..\..\code\game\g_mover.c ..\..\code\game\g_nav.c ..\..\code\game\g_navnew.c ..\..\code\game\g_object.c ..\..\code\game\g_saga.c ..\..\code\game\g_session.c ..\..\code\game\g_spawn.c ..\..\code\game\g_strap.c ..\..\code\game\g_svcmds.c ..\..\code\game\g_syscalls.c ..\..\code\game\g_target.c ..\..\code\game\g_team.c ..\..\code\game\g_timer.c ..\..\code\game\g_trigger.c ..\..\code\game\g_turret.c ..\..\code\game\g_turret_G2.c ..\..\code\game\g_utils.c ..\..\code\game\g_vehicles.c ..\..\code\game\g_vehicleTurret.c ..\..\code\game\g_weapon.c ..\..\code\game\NPC.c ..\..\code\game\NPC_AI_Atst.c ..\..\code\game\NPC_AI_Default.c ..\..\code\game\NPC_AI_Droid.c ..\..\code\game\NPC_AI_GalakMech.c ..\..\code\game\NPC_AI_Grenadier.c ..\..\code\game\NPC_AI_Howler.c ..\..\code\game\NPC_AI_ImperialProbe.c ..\..\code\game\NPC_AI_Interrogator.c ..\..\code\game\NPC_AI_Jedi.c ..\..\code\game\NPC_AI_Mark1.c ..\..\code\game\NPC_AI_Mark2.c ..\..\code\game\NPC_AI_MineMonster.c ..\..\code\game\NPC_AI_Rancor.c ..\..\code\game\NPC_AI_Remote.c ..\..\code\game\NPC_AI_Seeker.c ..\..\code\game\NPC_AI_Sentry.c ..\..\code\game\NPC_AI_Sniper.c ..\..\code\game\NPC_AI_Stormtrooper.c ..\..\code\game\NPC_AI_Utils.c ..\..\code\game\NPC_AI_Wampa.c ..\..\code\game\NPC_behavior.c ..\..\code\game\NPC_combat.c ..\..\code\game\NPC_goal.c ..\..\code\game\NPC_misc.c ..\..\code\game\NPC_move.c ..\..\code\game\NPC_reactions.c ..\..\code\game\NPC_senses.c ..\..\code\game\NPC_sounds.c ..\..\code\game\NPC_spawn.c ..\..\code\game\NPC_stats.c ..\..\code\game\NPC_utils.c ..\..\code\game\SpeederNPC.c ..\..\code\game\WalkerNPC.c ..\..\code\game\w_force.c ..\..\code\game\w_saber.c + tri_coll_test.c + 1>c1 : fatal error C1083: Cannot open source file: '..\..\..\JEDI_Academy_SDK\codemp\game\tri_coll_test.c': No such file or directory + ai_main.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + ai_util.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + ai_wpnav.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + AnimalNPC.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + bg_g2_utils.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + bg_misc.c + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(300): error C2061: syntax error : identifier 'material_t' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(360): error C2059: syntax error : '}' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(681): error C2061: syntax error : identifier 'npcteam_t' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(682): error C2061: syntax error : identifier 'enemyTeam' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(682): error C2059: syntax error : ';' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(690): error C2061: syntax error : identifier 'NPC_class' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(690): error C2059: syntax error : ';' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(740): error C2059: syntax error : '}' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1715): error C2143: syntax error : missing ')' before '*' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1715): error C2143: syntax error : missing '{' before '*' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1715): error C2059: syntax error : ')' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1716): error C2143: syntax error : missing ')' before '*' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1716): error C2143: syntax error : missing '{' before '*' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1716): error C2059: syntax error : ')' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1973): error C2143: syntax error : missing ')' before '*' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1973): error C2081: 'wpobject_t' : name in formal parameter list illegal + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1973): error C2143: syntax error : missing '{' before '*' + 1>c:\users\dan\desktop\jkaq3\code\game\g_local.h(1973): error C2059: syntax error : ')' + 1>c:\users\dan\desktop\jkaq3\code\game\bg_misc.c(413): error C2037: left of 's' specifies undefined struct/union 'gentity_s' + 1>c:\users\dan\desktop\jkaq3\code\game\bg_misc.c(413): warning C4047: 'function' : 'int' differs in levels of indirection from 'char *' + 1>c:\users\dan\desktop\jkaq3\code\game\bg_misc.c(413): warning C4024: 'Q3_SetParm' : different types for formal and actual parameter 2 + 1>c:\users\dan\desktop\jkaq3\code\game\bg_misc.c(413): error C2198: 'Q3_SetParm' : too few arguments for call + bg_panimate.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + bg_pmove.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + bg_saber.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + bg_saberLoad.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + bg_saga.c + bg_slidemove.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + bg_vehicleLoad.c + bg_weapons.c + FighterNPC.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_active.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_arenas.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_bot.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_client.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + Generating Code... + Compiling... + g_cmds.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_combat.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_exphysics.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_ICARUScb.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_items.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_log.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_main.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_mem.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_misc.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_missile.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_mover.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_nav.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_navnew.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_object.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_saga.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_session.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_spawn.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_strap.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_svcmds.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_syscalls.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + Generating Code... + Compiling... + g_target.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_team.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_timer.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_trigger.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_turret.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_turret_G2.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_utils.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_vehicles.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_vehicleTurret.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + g_weapon.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Atst.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Default.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Droid.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_GalakMech.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Grenadier.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Howler.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_ImperialProbe.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Interrogator.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Jedi.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + Generating Code... + Compiling... + NPC_AI_Mark1.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Mark2.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_MineMonster.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Rancor.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Remote.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Seeker.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Sentry.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Sniper.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Stormtrooper.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Utils.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_AI_Wampa.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_behavior.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_combat.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_goal.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_misc.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_move.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_reactions.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_senses.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_sounds.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_spawn.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + Generating Code... + Compiling... + NPC_stats.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + NPC_utils.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + SpeederNPC.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + WalkerNPC.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + w_force.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + w_saber.c + 1>c:\users\dan\desktop\jkaq3\code\game\q_shared.h(14): fatal error C1083: Cannot open include file: '../qcommon/disablewarnings.h': No such file or directory + Generating Code... + 1>Done Building Project "C:\Users\dan\Desktop\jkaq3\misc\msvc10\game.vcxproj" (build target(s)) -- FAILED. + +Build FAILED. + +Time Elapsed 00:00:01.63 diff --git a/build/game_debug/game.unsuccessfulbuild b/build/game_debug/game.unsuccessfulbuild new file mode 100644 index 0000000..e69de29 diff --git a/build/game_debug/vc100.idb b/build/game_debug/vc100.idb new file mode 100644 index 0000000..5e24ea5 Binary files /dev/null and b/build/game_debug/vc100.idb differ diff --git a/build/game_debug/vc100.pdb b/build/game_debug/vc100.pdb new file mode 100644 index 0000000..8ff4e88 Binary files /dev/null and b/build/game_debug/vc100.pdb differ diff --git a/build/quake3_debug/ftola.lst b/build/quake3_debug/ftola.lst new file mode 100644 index 0000000..7bfb14e --- /dev/null +++ b/build/quake3_debug/ftola.lst @@ -0,0 +1,135 @@ +Microsoft (R) Macro Assembler Version 10.00.30319.01 08/27/11 00:49:08 +C:\Users\dan\Desktop\jkaq3\code\asm\ftola.asm Page 1 - 1 + + + ; =========================================================================== + ; Copyright (C) 2011 Thilo Schulz + ; + ; This file is part of Quake III Arena source code. + ; + ; Quake III Arena source code is free software; you can redistribute it + ; and/or modify it under the terms of the GNU General Public License as + ; published by the Free Software Foundation; either version 2 of the License, + ; or (at your option) any later version. + ; + ; Quake III Arena source code is distributed in the hope that it will be + ; useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ; GNU General Public License for more details. + ; + ; You should have received a copy of the GNU General Public License + ; along with Quake III Arena source code; if not, write to the Free Software + ; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + ; =========================================================================== + + ; MASM ftol conversion functions using SSE or FPU + ; assume __cdecl calling convention is being used for x86, __fastcall for x64 + + IFNDEF idx64 + .model flat, c + ENDIF + + ; .data + + ; ifndef idx64 + ; fpucw WORD 0F7Fh + ; endif + + 00000000 .code + + IFDEF idx64 + ELSE + ; qftol using FPU + + qftolx87m macro src + ; not necessary, fpucw is set with _controlfp at startup ; sub esp, 2 ; fnstcw word ptr [esp] ; fldcw fpucw fld dword ptr src + fistp dword ptr src + ; fldcw [esp] mov eax, src + ; add esp, 2 ret endm + + qftolx87 PROC + ; need this line when storing FPU control word on stack + ; qftolx87m [esp + 6] + qftolx87m [esp + 4] + qftolx87 ENDP + + qvmftolx87 PROC + 00000000 qftolx87m [edi + ebx * 4] + qvmftolx87 ENDP + + ; qftol using SSE + 00000000 D9 44 24 04 1 fld dword ptr [esp + 4] + 00000004 DB 5C 24 04 1 fistp dword ptr [esp + 4] + 00000008 8B 44 24 04 1 mov eax, [esp + 4] + 0000000C C3 1 ret + 0000000D qftolsse PROC + movss xmm0, dword ptr [esp + 4] + 0000000D cvttss2si eax, xmm0 + ret + 0000000D D9 04 9F 1 fld dword ptr [edi + ebx * 4] + 00000010 DB 1C 9F 1 fistp dword ptr [edi + ebx * 4] + 00000013 8B 04 9F 1 mov eax, [edi + ebx * 4] + 00000016 C3 1 ret + 00000017 qftolsse ENDP + + qvmftolsse PROC + 00000017 movss xmm0, dword ptr [edi + ebx * 4] + 00000017 F3/ 0F 10 44 24 cvttss2si eax, xmm0 + 04 + 0000001D F3/ 0F 2C C0 ret + 00000021 C3 qvmftolsse ENDP + 00000022 ENDIF + + 00000022 end + 00000022 F3/ 0F 10 04 9F + 00000027 F3/ 0F 2C C0 + 0000002B C3 + 0000002C + Microsoft (R) Macro Assembler Version 10.00.30319.01 08/27/11 00:49:08 +C:\Users\dan\Desktop\jkaq3\code\asm\ftola.asm Symbols 2 - 1 + + + + +Macros: + + N a m e Type + +qftolx87m . . . . . . . . . . . Proc + + +Segments and Groups: + + N a m e Size Length Align Combine Class + +FLAT . . . . . . . . . . . . . . GROUP +_DATA . . . . . . . . . . . . . 32 Bit 00000000 Para Public 'DATA' +_TEXT . . . . . . . . . . . . . 32 Bit 0000002C Para Public 'CODE' + + +Procedures, parameters, and locals: + + N a m e Type Value Attr + +qftolsse . . . . . . . . . . . . P Near 00000017 _TEXT Length= 0000000B Public C +qftolx87 . . . . . . . . . . . . P Near 00000000 _TEXT Length= 0000000D Public C +qvmftolsse . . . . . . . . . . . P Near 00000022 _TEXT Length= 0000000A Public C +qvmftolx87 . . . . . . . . . . . P Near 0000000D _TEXT Length= 0000000A Public C + + +Symbols: + + N a m e Type Value Attr + +@CodeSize . . . . . . . . . . . Number 00000000h +@DataSize . . . . . . . . . . . Number 00000000h +@Interface . . . . . . . . . . . Number 00000001h +@Model . . . . . . . . . . . . . Number 00000007h +@code . . . . . . . . . . . . . Text _TEXT +@data . . . . . . . . . . . . . Text FLAT +@fardata? . . . . . . . . . . . Text FLAT +@fardata . . . . . . . . . . . . Text FLAT +@stack . . . . . . . . . . . . . Text FLAT + + 0 Warnings + 0 Errors diff --git a/build/quake3_debug/ioquake3.x86.Build.CppClean.log b/build/quake3_debug/ioquake3.x86.Build.CppClean.log new file mode 100644 index 0000000..c617eee --- /dev/null +++ b/build/quake3_debug/ioquake3.x86.Build.CppClean.log @@ -0,0 +1,467 @@ +..\..\build\quake3_debug\ioquake3.x86.bsc +..\..\build\quake3_debug\ioquake3.x86.ilk +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\ADLER32.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\ADLER32.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_BSPQ3.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_BSPQ3.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_CLUSTER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_CLUSTER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_DEBUG.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_DEBUG.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_ENTITY.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_ENTITY.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_FILE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_FILE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_MAIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_MAIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_MOVE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_MOVE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_OPTIMIZE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_OPTIMIZE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_REACH.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_REACH.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_ROUTE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_ROUTE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_ROUTEALT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_ROUTEALT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_SAMPLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AAS_SAMPLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_CHAR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_CHAR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_CHAT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_CHAT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_GEN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_GEN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_GOAL.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_GOAL.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_MOVE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_MOVE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_WEAP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_WEAP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_WEIGHT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_AI_WEIGHT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_EA.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_EA.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_INTERFACE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BE_INTERFACE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BITS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BITS.SBR +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\BscMake.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\bscmake.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\bscmake.write.1.tlog +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BUFFER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\BUFFER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CB_SEARCH.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CB_SEARCH.SBR +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\cl.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\CL.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\CL.write.1.tlog +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_AVI.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_AVI.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_CGAME.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_CGAME.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_CIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_CIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_CONSOLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_CONSOLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_CURL.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_CURL.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_INPUT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_INPUT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_KEYS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_KEYS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_MAIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_MAIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_NET_CHAN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_NET_CHAN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_PARSE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_PARSE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_SCRN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_SCRN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_UI.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CL_UI.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_LOAD.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_LOAD.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_PATCH.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_PATCH.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_POLYLIB.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_POLYLIB.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_TEST.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_TEST.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_TRACE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CM_TRACE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CMD.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CMD.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\COMMON.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\COMMON.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CON_LOG.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CON_LOG.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CON_PASSIVE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CON_PASSIVE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CRC32.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CRC32.SBR +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\custombuild.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\custombuild.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\custombuild.write.1.tlog +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CVAR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\CVAR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_10_16_TABLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_10_16_TABLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_10_32_TABLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_10_32_TABLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_20_32_TABLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_20_32_TABLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_5_256_TABLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_5_256_TABLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_5_64_TABLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_5_64_TABLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_8_128_TABLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\EXC_8_128_TABLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\FFTWRAP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\FFTWRAP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\FILES.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\FILES.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\FILTERBANK.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\FILTERBANK.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\FILTERS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\FILTERS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\FTOLA.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\GAIN_TABLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\GAIN_TABLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\GAIN_TABLE_LBR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\GAIN_TABLE_LBR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\HEXC_10_32_TABLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\HEXC_10_32_TABLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\HEXC_TABLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\HEXC_TABLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\HIGH_LSP_TABLES.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\HIGH_LSP_TABLES.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\HUFFMAN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\HUFFMAN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\INFFAST.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\INFFAST.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\INFLATE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\INFLATE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\INFTREES.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\INFTREES.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\IOAPI.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\IOAPI.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\IOQUAKE3.X86.BSC +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\IOQUAKE3.X86.EXE +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\IOQUAKE3.X86.EXE.EMBED.MANIFEST +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\IOQUAKE3.X86.EXE.EMBED.MANIFEST.RES +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\IOQUAKE3.X86.EXE.INTERMEDIATE.MANIFEST +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\ioquake3.x86.exp +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\IOQUAKE3.X86.ILK +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\ioquake3.x86.lib +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\ioquake3.x86_manifest.rc +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JARICOM.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JARICOM.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCAPIMIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCAPIMIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCAPISTD.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCAPISTD.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCARITH.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCARITH.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCCOEFCT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCCOEFCT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCCOLOR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCCOLOR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCDCTMGR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCDCTMGR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCHUFF.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCHUFF.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCINIT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCINIT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCMAINCT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCMAINCT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCMARKER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCMARKER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCMASTER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCMASTER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCOMAPI.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCOMAPI.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCPARAM.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCPARAM.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCPREPCT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCPREPCT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCSAMPLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCSAMPLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCTRANS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JCTRANS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDAPIMIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDAPIMIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDAPISTD.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDAPISTD.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDARITH.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDARITH.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDATADST.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDATADST.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDATASRC.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDATASRC.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDCOEFCT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDCOEFCT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDCOLOR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDCOLOR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDDCTMGR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDDCTMGR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDHUFF.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDHUFF.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDINPUT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDINPUT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDMAINCT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDMAINCT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDMARKER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDMARKER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDMASTER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDMASTER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDMERGE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDMERGE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDPOSTCT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDPOSTCT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDSAMPLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDSAMPLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDTRANS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JDTRANS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JERROR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JERROR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JFDCTFLT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JFDCTFLT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JFDCTFST.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JFDCTFST.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JFDCTINT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JFDCTINT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JIDCTFLT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JIDCTFLT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JIDCTFST.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JIDCTFST.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JIDCTINT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JIDCTINT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JITTER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JITTER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JMEMMGR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JMEMMGR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JMEMNOBS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JMEMNOBS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JQUANT1.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JQUANT1.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JQUANT2.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JQUANT2.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JUTILS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\JUTILS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\KISS_FFT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\KISS_FFT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\KISS_FFTR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\KISS_FFTR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_CRC.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_CRC.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_LIBVAR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_LIBVAR.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_LOG.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_LOG.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_MEMORY.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_MEMORY.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_PRECOMP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_PRECOMP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_SCRIPT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_SCRIPT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_STRUCT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\L_STRUCT.SBR +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\link.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\link.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\link.write.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\link-cvtres.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\link-cvtres.write.1.tlog +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\LPC.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\LPC.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\LSP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\LSP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\LSP_TABLES_NB.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\LSP_TABLES_NB.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\LTP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\LTP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MD4.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MD4.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MD5.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MD5.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MDF.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MDF.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MODES.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MODES.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MODES_WB.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MODES_WB.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MSG.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\MSG.SBR +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\mt.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\mt.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\mt.write.1.tlog +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\NB_CELP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\NB_CELP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\NET_CHAN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\NET_CHAN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\NET_IP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\NET_IP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\PREPROCESS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\PREPROCESS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\PUFF.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\PUFF.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\Q_MATH.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\Q_MATH.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\Q_SHARED.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\Q_SHARED.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\QAL.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\QAL.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\QUAKE3.MAP +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\QUAKE3.PDB +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\quake3.write.1.tlog +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\QUANT_LSP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\QUANT_LSP.SBR +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\rc.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\rc.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\rc.write.1.tlog +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\RESAMPLE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\RESAMPLE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SB_CELP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SB_CELP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SDL_GAMMA.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SDL_GAMMA.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SDL_GLIMP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SDL_GLIMP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SDL_INPUT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SDL_INPUT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SDL_SND.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SDL_SND.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SMALLFT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SMALLFT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SNAPVECTOR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_ADPCM.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_ADPCM.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_CODEC.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_CODEC.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_CODEC_MP3.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_CODEC_MP3.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_CODEC_OGG.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_CODEC_OGG.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_CODEC_WAV.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_CODEC_WAV.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_DMA.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_DMA.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_MAIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_MAIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_MEM.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_MEM.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_MIX.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_MIX.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_OPENAL.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_OPENAL.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_WAVELET.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SND_WAVELET.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SPEEX.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SPEEX.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SPEEX_CALLBACKS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SPEEX_CALLBACKS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SPEEX_HEADER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SPEEX_HEADER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\STEREO.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\STEREO.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_BOT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_BOT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_CCMDS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_CCMDS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_CLIENT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_CLIENT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_GAME.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_GAME.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_INIT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_INIT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_MAIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_MAIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_NET_CHAN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_NET_CHAN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_SNAPSHOT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_SNAPSHOT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_WORLD.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SV_WORLD.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SYS_MAIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SYS_MAIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SYS_WIN32.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\SYS_WIN32.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_ANIMATION.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_ANIMATION.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_BACKEND.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_BACKEND.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_BSP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_BSP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_CMDS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_CMDS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_CURVE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_CURVE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_FLARES.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_FLARES.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_FONT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_FONT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_BMP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_BMP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_JPG.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_JPG.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_PCX.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_PCX.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_PNG.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_PNG.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_TGA.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_IMAGE_TGA.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_INIT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_INIT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_LIGHT.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_LIGHT.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MAIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MAIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MARKS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MARKS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MESH.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MESH.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MODEL.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MODEL.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MODEL_IQM.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_MODEL_IQM.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_NOISE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_NOISE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SCENE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SCENE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SHADE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SHADE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SHADE_CALC.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SHADE_CALC.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SHADER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SHADER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SHADOWS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SHADOWS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SKY.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SKY.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SURFACE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_SURFACE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_WORLD.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\TR_WORLD.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\UNZIP.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\UNZIP.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VBR.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VBR.SBR +C:\Users\dan\Desktop\jkaq3\build\quake3_debug\vc100.idb +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VC100.PDB +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VM.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VM.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VM_INTERPRETED.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VM_INTERPRETED.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VM_X86.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VM_X86.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VQ.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\VQ.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\WINDOW.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\WINDOW.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\ZUTIL.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\QUAKE3_DEBUG\ZUTIL.SBR +C:\Users\dan\Desktop\jkaq3\misc\msvc10\..\..\build\quake3_debug\ioquake3.x86.exe +C:\Users\dan\Desktop\jkaq3\misc\msvc10\..\..\build\quake3_debug\ioquake3.x86.exe.intermediate.manifest +C:\USERS\DAN\DESKTOP\JKAQ3\MISC\WINQUAKE.RES diff --git a/build/quake3_debug/quake3.log b/build/quake3_debug/quake3.log new file mode 100644 index 0000000..257d4d1 --- /dev/null +++ b/build/quake3_debug/quake3.log @@ -0,0 +1,9 @@ +Build started 8/27/2011 1:05:26 AM. + 1>Project "C:\Users\dan\Desktop\jkaq3\misc\msvc10\quake3.vcxproj" on node 3 (clean target(s)). + 1>_PrepareForClean: + Deleting file "..\..\build\quake3_debug\quake3.lastbuildstate". + 1>Done Building Project "C:\Users\dan\Desktop\jkaq3\misc\msvc10\quake3.vcxproj" (clean target(s)). + +Build succeeded. + +Time Elapsed 00:00:00.26 diff --git a/build/quake3_debug/snapvector.lst b/build/quake3_debug/snapvector.lst new file mode 100644 index 0000000..e190fc7 --- /dev/null +++ b/build/quake3_debug/snapvector.lst @@ -0,0 +1,156 @@ +Microsoft (R) Macro Assembler Version 10.00.30319.01 08/27/11 00:49:08 +C:\Users\dan\Desktop\jkaq3\code\asm\snapvector.asm Page 1 - 1 + + + ; =========================================================================== + ; Copyright (C) 2011 Thilo Schulz + ; + ; This file is part of Quake III Arena source code. + ; + ; Quake III Arena source code is free software; you can redistribute it + ; and/or modify it under the terms of the GNU General Public License as + ; published by the Free Software Foundation; either version 2 of the License, + ; or (at your option) any later version. + ; + ; Quake III Arena source code is distributed in the hope that it will be + ; useful, but WITHOUT ANY WARRANTY; without even the implied warranty of + ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + ; GNU General Public License for more details. + ; + ; You should have received a copy of the GNU General Public License + ; along with Quake III Arena source code; if not, write to the Free Software + ; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + ; =========================================================================== + + ; MASM version of snapvector conversion function using SSE or FPU + ; assume __cdecl calling convention is being used for x86, __fastcall for x64 + ; + ; function prototype: + ; void qsnapvector(vec3_t vec) + + IFNDEF idx64 + .model flat, c + ENDIF + + 00000000 .data + + ALIGN 16 + 00000000 FFFFFFFF ssemask DWORD 0FFFFFFFFh, 0FFFFFFFFh, 0FFFFFFFFh, 00000000h + FFFFFFFF + FFFFFFFF + 00000000 + 00000010 00001F80 ssecw DWORD 00001F80h + + IFNDEF idx64 + 00000014 037F fpucw WORD 037Fh + ENDIF + + 00000000 .code + + IFDEF idx64 + ELSE + + 00000000 qsnapvectorsse PROC + 00000000 83 EC 08 sub esp, 8 + 00000003 0F AE 1C 24 stmxcsr [esp] ; save SSE control word + 00000007 0F AE 15 ldmxcsr ssecw ; set to round nearest + 00000010 R + + 0000000E 57 push edi + 0000000F 8B 7C 24 10 mov edi, dword ptr 16[esp] ; maskmovdqu uses edi as implicit memory operand + 00000013 0F 28 0D movaps xmm1, ssemask ; initialize the mask register for maskmovdqu + 00000000 R + 0000001A 0F 10 07 movups xmm0, [edi] ; here is stored our vector. Read 4 values in one go + 0000001D 66| 0F 5B C0 cvtps2dq xmm0, xmm0 ; convert 4 single fp to int + 00000021 0F 5B C0 cvtdq2ps xmm0, xmm0 ; convert 4 int to single fp + 00000024 66| 0F F7 C1 maskmovdqu xmm0, xmm1 ; write 3 values back to memory + 00000028 5F pop edi + + 00000029 0F AE 14 24 ldmxcsr [esp] ; restore sse control word to old value + 0000002D 83 C4 08 add esp, 8 + 00000030 C3 ret + 00000031 qsnapvectorsse ENDP + + qroundx87 macro src + fld dword ptr src + fistp dword ptr src + fild dword ptr src + fstp dword ptr src + endm + + 00000031 qsnapvectorx87 PROC + 00000031 8B 44 24 04 mov eax, dword ptr 4[esp] + 00000035 83 EC 02 sub esp, 2 + 00000038 D9 3C 24 fnstcw word ptr [esp] + 0000003B D9 2D 00000014 R fldcw fpucw + qroundx87 [eax] + 00000041 D9 00 1 fld dword ptr [eax] + 00000043 DB 18 1 fistp dword ptr [eax] + 00000045 DB 00 1 fild dword ptr [eax] + 00000047 D9 18 1 fstp dword ptr [eax] + qroundx87 4[eax] + 00000049 D9 40 04 1 fld dword ptr 4[eax] + 0000004C DB 58 04 1 fistp dword ptr 4[eax] + 0000004F DB 40 04 1 fild dword ptr 4[eax] + 00000052 D9 58 04 1 fstp dword ptr 4[eax] + qroundx87 8[eax] + 00000055 D9 40 08 1 fld dword ptr 8[eax] + 00000058 DB 58 08 1 fistp dword ptr 8[eax] + 0000005B DB 40 08 1 fild dword ptr 8[eax] + 0000005E D9 58 08 1 fstp dword ptr 8[eax] + 00000061 D9 2C 24 fldcw [esp] + 00000064 83 C4 02 add esp, 2 + 00000067 qsnapvectorx87 ENDP + + ENDIF + + end + Microsoft (R) Macro Assembler Version 10.00.30319.01 08/27/11 00:49:08 +C:\Users\dan\Desktop\jkaq3\code\asm\snapvector.asm Symbols 2 - 1 + + + + +Macros: + + N a m e Type + +qroundx87 . . . . . . . . . . . Proc + + +Segments and Groups: + + N a m e Size Length Align Combine Class + +FLAT . . . . . . . . . . . . . . GROUP +_DATA . . . . . . . . . . . . . 32 Bit 00000016 Para Public 'DATA' +_TEXT . . . . . . . . . . . . . 32 Bit 00000067 Para Public 'CODE' + + +Procedures, parameters, and locals: + + N a m e Type Value Attr + +qsnapvectorsse . . . . . . . . . P Near 00000000 _TEXT Length= 00000031 Public C +qsnapvectorx87 . . . . . . . . . P Near 00000031 _TEXT Length= 00000036 Public C + + +Symbols: + + N a m e Type Value Attr + +@CodeSize . . . . . . . . . . . Number 00000000h +@DataSize . . . . . . . . . . . Number 00000000h +@Interface . . . . . . . . . . . Number 00000001h +@Model . . . . . . . . . . . . . Number 00000007h +@code . . . . . . . . . . . . . Text _TEXT +@data . . . . . . . . . . . . . Text FLAT +@fardata? . . . . . . . . . . . Text FLAT +@fardata . . . . . . . . . . . . Text FLAT +@stack . . . . . . . . . . . . . Text FLAT +fpucw . . . . . . . . . . . . . Word 00000014 _DATA +ssecw . . . . . . . . . . . . . DWord 00000010 _DATA +ssemask . . . . . . . . . . . . DWord 00000000 _DATA + + 0 Warnings + 0 Errors diff --git a/build/ui_debug/ui.log b/build/ui_debug/ui.log new file mode 100644 index 0000000..7bb71d0 --- /dev/null +++ b/build/ui_debug/ui.log @@ -0,0 +1,9 @@ +Build started 8/27/2011 1:05:26 AM. + 1>Project "C:\Users\dan\Desktop\jkaq3\misc\msvc10\ui.vcxproj" on node 2 (clean target(s)). + 1>_PrepareForClean: + Deleting file "..\..\build\ui_debug\ui.lastbuildstate". + 1>Done Building Project "C:\Users\dan\Desktop\jkaq3\misc\msvc10\ui.vcxproj" (clean target(s)). + +Build succeeded. + +Time Elapsed 00:00:00.08 diff --git a/build/ui_debug/ui_newx86.Build.CppClean.log b/build/ui_debug/ui_newx86.Build.CppClean.log new file mode 100644 index 0000000..afdbaa9 --- /dev/null +++ b/build/ui_debug/ui_newx86.Build.CppClean.log @@ -0,0 +1,66 @@ +..\..\build\ui_debug\ui_newx86.bsc +..\..\build\ui_debug\ui_newx86.ilk +..\..\build\ui_debug\ui_newx86.pdb +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\BG_MISC.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\BG_MISC.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\BG_SAGA.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\BG_SAGA.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\BG_VEHICLELOAD.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\BG_VEHICLELOAD.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\BG_WEAPONS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\BG_WEAPONS.SBR +C:\Users\dan\Desktop\jkaq3\build\ui_debug\BscMake.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\bscmake.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\bscmake.write.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\cl.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\CL.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\CL.write.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\link.5468.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\link.5468.write.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\link.5468-cvtres.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\link.5468-cvtres.write.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\link.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\link.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\link.write.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\link-cvtres.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\link-cvtres.write.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\mt.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\mt.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\mt.write.1.tlog +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\Q_MATH.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\Q_MATH.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\Q_SHARED.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\Q_SHARED.SBR +C:\Users\dan\Desktop\jkaq3\build\ui_debug\rc.command.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\rc.read.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\rc.write.1.tlog +C:\Users\dan\Desktop\jkaq3\build\ui_debug\ui.write.1.tlog +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_ATOMS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_ATOMS.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_FORCE.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_FORCE.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_GAMEINFO.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_GAMEINFO.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_MAIN.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_MAIN.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_NEWX86.BSC +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_NEWX86.DLL +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_NEWX86.DLL.EMBED.MANIFEST +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_NEWX86.DLL.EMBED.MANIFEST.RES +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_NEWX86.DLL.INTERMEDIATE.MANIFEST +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_NEWX86.ILK +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_NEWX86.MAP +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_NEWX86.PDB +C:\Users\dan\Desktop\jkaq3\build\ui_debug\ui_newx86_manifest.rc +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_SABER.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_SABER.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_SHARED.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_SHARED.SBR +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_SYSCALLS.OBJ +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\UI_SYSCALLS.SBR +C:\Users\dan\Desktop\jkaq3\build\ui_debug\uix86_new.exp +C:\Users\dan\Desktop\jkaq3\build\ui_debug\uix86_new.lib +C:\Users\dan\Desktop\jkaq3\build\ui_debug\vc100.idb +C:\USERS\DAN\DESKTOP\JKAQ3\BUILD\UI_DEBUG\VC100.PDB +C:\Users\dan\Desktop\jkaq3\misc\msvc10\..\..\build\ui_debug\ui_newx86.dll +C:\Users\dan\Desktop\jkaq3\misc\msvc10\..\..\build\ui_debug\ui_newx86.dll.intermediate.manifest diff --git a/code/AL/VERSION b/code/AL/VERSION new file mode 100644 index 0000000..1af4e3f --- /dev/null +++ b/code/AL/VERSION @@ -0,0 +1,16 @@ +This file identifies the version of the AL headers in this directory. If you +change or update the AL headers in any way, please make sure you also update +this file. + +SVN revision >= 402 +------------------- +Headers are from OpenAL CVS 6th August 2005: +$ cvs -d:pserver:guest@opensource.creative.com:/usr/local/cvs-repository +login +(use password "guest") +$ cvs -d:pserver:guest@opensource.creative.com:/usr/local/cvs-repository +co -D "6 Aug 2005" openal + +SVN revision >= 374 +------------------- +Standard OpenAL 1.0 headers diff --git a/code/AL/al.h b/code/AL/al.h new file mode 100644 index 0000000..fd9a537 --- /dev/null +++ b/code/AL/al.h @@ -0,0 +1,506 @@ +#ifndef __al_h_ +#define __al_h_ + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ +#include "altypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* WIN32, not Xbox */ +#ifdef _WIN32 +#ifndef _XBOX +#ifdef _OPENAL32LIB +#define ALAPI __declspec(dllexport) +#else +#define ALAPI __declspec(dllimport) +#endif +#define ALAPIENTRY __cdecl +#define AL_CALLBACK +#endif +#endif + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export on +#endif +#endif + +#ifndef ALAPI +#define ALAPI +#endif + +#ifndef ALAPIENTRY +#define ALAPIENTRY +#endif + +#ifndef CALLBACK +#define AL_CALLBACK +#endif + +#define OPENAL + +#ifndef AL_NO_PROTOTYPES + +/* + * Renderer State management + */ +ALAPI void ALAPIENTRY alEnable( ALenum capability ); + +ALAPI void ALAPIENTRY alDisable( ALenum capability ); + +ALAPI ALboolean ALAPIENTRY alIsEnabled( ALenum capability ); + + +/* + * State retrieval + */ +ALAPI const ALchar* ALAPIENTRY alGetString( ALenum param ); + +ALAPI void ALAPIENTRY alGetBooleanv( ALenum param, ALboolean* data ); + +ALAPI void ALAPIENTRY alGetIntegerv( ALenum param, ALint* data ); + +ALAPI void ALAPIENTRY alGetFloatv( ALenum param, ALfloat* data ); + +ALAPI void ALAPIENTRY alGetDoublev( ALenum param, ALdouble* data ); + +ALAPI ALboolean ALAPIENTRY alGetBoolean( ALenum param ); + +ALAPI ALint ALAPIENTRY alGetInteger( ALenum param ); + +ALAPI ALfloat ALAPIENTRY alGetFloat( ALenum param ); + +ALAPI ALdouble ALAPIENTRY alGetDouble( ALenum param ); + + +/* + * Error support. + * Obtain the most recent error generated in the AL state machine. + */ +ALAPI ALenum ALAPIENTRY alGetError( ALvoid ); + + +/* + * Extension support. + * Query for the presence of an extension, and obtain any appropriate + * function pointers and enum values. + */ +ALAPI ALboolean ALAPIENTRY alIsExtensionPresent( const ALchar* extname ); + +ALAPI void* ALAPIENTRY alGetProcAddress( const ALchar* fname ); + +ALAPI ALenum ALAPIENTRY alGetEnumValue( const ALchar* ename ); + + +/* + * LISTENER + * Listener represents the location and orientation of the + * 'user' in 3D-space. + * + * Properties include: - + * + * Gain AL_GAIN ALfloat + * Position AL_POSITION ALfloat[3] + * Velocity AL_VELOCITY ALfloat[3] + * Orientation AL_ORIENTATION ALfloat[6] (Forward then Up vectors) +*/ + +/* + * Set Listener parameters + */ +ALAPI void ALAPIENTRY alListenerf( ALenum param, ALfloat value ); + +ALAPI void ALAPIENTRY alListener3f( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); + +ALAPI void ALAPIENTRY alListenerfv( ALenum param, const ALfloat* values ); + +ALAPI void ALAPIENTRY alListeneri( ALenum param, ALint value ); + +ALAPI void ALAPIENTRY alListener3i( ALenum param, ALint value1, ALint value2, ALint value3 ); + +ALAPI void ALAPIENTRY alListeneriv( ALenum param, const ALint* values ); + +/* + * Get Listener parameters + */ +ALAPI void ALAPIENTRY alGetListenerf( ALenum param, ALfloat* value ); + +ALAPI void ALAPIENTRY alGetListener3f( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 ); + +ALAPI void ALAPIENTRY alGetListenerfv( ALenum param, ALfloat* values ); + +ALAPI void ALAPIENTRY alGetListeneri( ALenum param, ALint* value ); + +ALAPI void ALAPIENTRY alGetListener3i( ALenum param, ALint *value1, ALint *value2, ALint *value3 ); + +ALAPI void ALAPIENTRY alGetListeneriv( ALenum param, ALint* values ); + + +/** + * SOURCE + * Sources represent individual sound objects in 3D-space. + * Sources take the PCM data provided in the specified Buffer, + * apply Source-specific modifications, and then + * submit them to be mixed according to spatial arrangement etc. + * + * Properties include: - + * + * Gain AL_GAIN ALfloat + * Min Gain AL_MIN_GAIN ALfloat + * Max Gain AL_MAX_GAIN ALfloat + * Position AL_POSITION ALfloat[3] + * Velocity AL_VELOCITY ALfloat[3] + * Direction AL_DIRECTION ALfloat[3] + * Head Relative Mode AL_SOURCE_RELATIVE ALint (AL_TRUE or AL_FALSE) + * Reference Distance AL_REFERENCE_DISTANCE ALfloat + * Max Distance AL_MAX_DISTANCE ALfloat + * RollOff Factor AL_ROLLOFF_FACTOR ALfloat + * Inner Angle AL_CONE_INNER_ANGLE ALint or ALfloat + * Outer Angle AL_CONE_OUTER_ANGLE ALint or ALfloat + * Cone Outer Gain AL_CONE_OUTER_GAIN ALint or ALfloat + * Pitch AL_PITCH ALfloat + * Looping AL_LOOPING ALint (AL_TRUE or AL_FALSE) + * MS Offset AL_MSEC_OFFSET ALint or ALfloat + * Byte Offset AL_BYTE_OFFSET ALint or ALfloat + * Sample Offset AL_SAMPLE_OFFSET ALint or ALfloat + * Attached Buffer AL_BUFFER ALint + * State (Query only) AL_SOURCE_STATE ALint + * Buffers Queued (Query only) AL_BUFFERS_QUEUED ALint + * Buffers Processed (Query only) AL_BUFFERS_PROCESSED ALint + */ + +/* Create Source objects */ +ALAPI void ALAPIENTRY alGenSources( ALsizei n, ALuint* sources ); + +/* Delete Source objects */ +ALAPI void ALAPIENTRY alDeleteSources( ALsizei n, const ALuint* sources ); + +/* Verify a handle is a valid Source */ +ALAPI ALboolean ALAPIENTRY alIsSource( ALuint sid ); + +/* + * Set Source parameters + */ +ALAPI void ALAPIENTRY alSourcef( ALuint sid, ALenum param, ALfloat value ); + +ALAPI void ALAPIENTRY alSource3f( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); + +ALAPI void ALAPIENTRY alSourcefv( ALuint sid, ALenum param, const ALfloat* values ); + +ALAPI void ALAPIENTRY alSourcei( ALuint sid, ALenum param, ALint value ); + +ALAPI void ALAPIENTRY alSource3i( ALuint sid, ALenum param, ALint value1, ALint value2, ALint value3 ); + +ALAPI void ALAPIENTRY alSourceiv( ALuint sid, ALenum param, const ALint* values ); + +/* + * Get Source parameters + */ +ALAPI void ALAPIENTRY alGetSourcef( ALuint sid, ALenum param, ALfloat* value ); + +ALAPI void ALAPIENTRY alGetSource3f( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); + +ALAPI void ALAPIENTRY alGetSourcefv( ALuint sid, ALenum param, ALfloat* values ); + +ALAPI void ALAPIENTRY alGetSourcei( ALuint sid, ALenum param, ALint* value ); + +ALAPI void ALAPIENTRY alGetSource3i( ALuint sid, ALenum param, ALint* value1, ALint* value2, ALint* value3); + +ALAPI void ALAPIENTRY alGetSourceiv( ALuint sid, ALenum param, ALint* values ); + + +/* + * Source vector based playback calls + */ + +/* Play, replay, or resume (if paused) a list of Sources */ +ALAPI void ALAPIENTRY alSourcePlayv( ALsizei ns, const ALuint *sids ); + +/* Stop a list of Sources */ +ALAPI void ALAPIENTRY alSourceStopv( ALsizei ns, const ALuint *sids ); + +/* Rewind a list of Sources */ +ALAPI void ALAPIENTRY alSourceRewindv( ALsizei ns, const ALuint *sids ); + +/* Pause a list of Sources */ +ALAPI void ALAPIENTRY alSourcePausev( ALsizei ns, const ALuint *sids ); + +/* + * Source based playback calls + */ + +/* Play, replay, or resume a Source */ +ALAPI void ALAPIENTRY alSourcePlay( ALuint sid ); + +/* Stop a Source */ +ALAPI void ALAPIENTRY alSourceStop( ALuint sid ); + +/* Rewind a Source (set playback postiton to beginning) */ +ALAPI void ALAPIENTRY alSourceRewind( ALuint sid ); + +/* Pause a Source */ +ALAPI void ALAPIENTRY alSourcePause( ALuint sid ); + +/* + * Source Queuing + */ +ALAPI void ALAPIENTRY alSourceQueueBuffers( ALuint sid, ALsizei numEntries, const ALuint *bids ); + +ALAPI void ALAPIENTRY alSourceUnqueueBuffers( ALuint sid, ALsizei numEntries, ALuint *bids ); + + +/** + * BUFFER + * Buffer objects are storage space for sample data. + * Buffers are referred to by Sources. One Buffer can be used + * by multiple Sources. + * + * Properties include: - + * + * Frequency (Query only) AL_FREQUENCY ALint + * Size (Query only) AL_SIZE ALint + * Bits (Query only) AL_BITS ALint + * Channels (Query only) AL_CHANNELS ALint + */ + +/* Create Buffer objects */ +ALAPI void ALAPIENTRY alGenBuffers( ALsizei n, ALuint* buffers ); + +/* Delete Buffer objects */ +ALAPI void ALAPIENTRY alDeleteBuffers( ALsizei n, const ALuint* buffers ); + +/* Verify a handle is a valid Buffer */ +ALAPI ALboolean ALAPIENTRY alIsBuffer( ALuint bid ); + +/* Specify the data to be copied into a buffer */ +ALAPI void ALAPIENTRY alBufferData( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq ); + +/* + * Set Buffer parameters + */ +ALAPI void ALAPIENTRY alBufferf( ALuint bid, ALenum param, ALfloat value ); + +ALAPI void ALAPIENTRY alBuffer3f( ALuint bid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); + +ALAPI void ALAPIENTRY alBufferfv( ALuint bid, ALenum param, const ALfloat* values ); + +ALAPI void ALAPIENTRY alBufferi( ALuint bid, ALenum param, ALint value ); + +ALAPI void ALAPIENTRY alBuffer3i( ALuint bid, ALenum param, ALint value1, ALint value2, ALint value3 ); + +ALAPI void ALAPIENTRY alBufferiv( ALuint bid, ALenum param, const ALint* values ); + +/* + * Get Buffer parameters + */ +ALAPI void ALAPIENTRY alGetBufferf( ALuint bid, ALenum param, ALfloat* value ); + +ALAPI void ALAPIENTRY alGetBuffer3f( ALuint bid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); + +ALAPI void ALAPIENTRY alGetBufferfv( ALuint bid, ALenum param, ALfloat* values ); + +ALAPI void ALAPIENTRY alGetBufferi( ALuint bid, ALenum param, ALint* value ); + +ALAPI void ALAPIENTRY alGetBuffer3i( ALuint bid, ALenum param, ALint* value1, ALint* value2, ALint* value3); + +ALAPI void ALAPIENTRY alGetBufferiv( ALuint bid, ALenum param, ALint* values ); + + +/* + * Global Parameters + */ +ALAPI void ALAPIENTRY alDopplerFactor( ALfloat value ); + +ALAPI void ALAPIENTRY alDopplerVelocity( ALfloat value ); + +ALAPI void ALAPIENTRY alSpeedOfSound( ALfloat value ); + +ALAPI void ALAPIENTRY alDistanceModel( ALenum distanceModel ); + +#else /* AL_NO_PROTOTYPES */ + +/* +void (ALAPIENTRY *alEnable)( ALenum capability ); +void (ALAPIENTRY *alDisable)( ALenum capability ); +ALboolean (ALAPIENTRY *alIsEnabled)( ALenum capability ); +const ALchar* (ALAPIENTRY *alGetString)( ALenum param ); +void (ALAPIENTRY *alGetBooleanv)( ALenum param, ALboolean* data ); +void (ALAPIENTRY *alGetIntegerv)( ALenum param, ALint* data ); +void (ALAPIENTRY *alGetFloatv)( ALenum param, ALfloat* data ); +void (ALAPIENTRY *alGetDoublev)( ALenum param, ALdouble* data ); +ALboolean (ALAPIENTRY *alGetBoolean)( ALenum param ); +ALint (ALAPIENTRY *alGetInteger)( ALenum param ); +ALfloat (ALAPIENTRY *alGetFloat)( ALenum param ); +ALdouble (ALAPIENTRY *alGetDouble)( ALenum param ); +ALenum (ALAPIENTRY *alGetError)( ALvoid ); +ALboolean (ALAPIENTRY *alIsExtensionPresent)(const ALchar* extname ); +void* (ALAPIENTRY *alGetProcAddress)( const ALchar* fname ); +ALenum (ALAPIENTRY *alGetEnumValue)( const ALchar* ename ); +void (ALAPIENTRY *alListenerf)( ALenum param, ALfloat value ); +void (ALAPIENTRY *alListener3f)( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +void (ALAPIENTRY *alListenerfv)( ALenum param, const ALfloat* values ); +void (ALAPIENTRY *alListeneri)( ALenum param, ALint value ); +void (ALAPIENTRY *alListener3i)( ALenum param, ALint value1, ALint value2, ALint value3 ); +void (ALAPIENTRY *alListeneriv)( ALenum param, const ALint* values ); +void (ALAPIENTRY *alGetListenerf)( ALenum param, ALfloat* value ); +void (ALAPIENTRY *alGetListener3f)( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 ); +void (ALAPIENTRY *alGetListenerfv)( ALenum param, ALfloat* values ); +void (ALAPIENTRY *alGetListeneri)( ALenum param, ALint* value ); +void (ALAPIENTRY *alGetListener3i)( ALenum param, ALint *value1, ALint *value2, ALint *value3 ); +void (ALAPIENTRY *alGetListeneriv)( ALenum param, ALint* values ); +void (ALAPIENTRY *alGenSources)( ALsizei n, ALuint* sources ); +void (ALAPIENTRY *alDeleteSources)( ALsizei n, const ALuint* sources ); +ALboolean (ALAPIENTRY *alIsSource)( ALuint sid ); +void (ALAPIENTRY *alSourcef)( ALuint sid, ALenum param, ALfloat value); +void (ALAPIENTRY *alSource3f)( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +void (ALAPIENTRY *alSourcefv)( ALuint sid, ALenum param, const ALfloat* values ); +void (ALAPIENTRY *alSourcei)( ALuint sid, ALenum param, ALint value); +void (ALAPIENTRY *alSource3i)( ALuint sid, ALenum param, ALint value1, ALint value2, ALint value3 ); +void (ALAPIENTRY *alSourceiv)( ALuint sid, ALenum param, const ALint* values ); +void (ALAPIENTRY *alGetSourcef)( ALuint sid, ALenum param, ALfloat* value ); +void (ALAPIENTRY *alGetSource3f)( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +void (ALAPIENTRY *alGetSourcefv)( ALuint sid, ALenum param, ALfloat* values ); +void (ALAPIENTRY *alGetSourcei)( ALuint sid, ALenum param, ALint* value ); +void (ALAPIENTRY *alGetSource3i)( ALuint sid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +void (ALAPIENTRY *alGetSourceiv)( ALuint sid, ALenum param, ALint* values ); +void (ALAPIENTRY *alSourcePlayv)( ALsizei ns, const ALuint *sids ); +void (ALAPIENTRY *alSourceStopv)( ALsizei ns, const ALuint *sids ); +void (ALAPIENTRY *alSourceRewindv)( ALsizei ns, const ALuint *sids ); +void (ALAPIENTRY *alSourcePausev)( ALsizei ns, const ALuint *sids ); +void (ALAPIENTRY *alSourcePlay)( ALuint sid ); +void (ALAPIENTRY *alSourceStop)( ALuint sid ); +void (ALAPIENTRY *alSourceRewind)( ALuint sid ); +void (ALAPIENTRY *alSourcePause)( ALuint sid ); +void (ALAPIENTRY *alSourceQueueBuffers)( ALuint sid, ALsizei numEntries, const ALuint *bids ); +void (ALAPIENTRY *alSourceUnqueueBuffers)( ALuint sid, ALsizei numEntries, ALuint *bids ); +void (ALAPIENTRY *alGenBuffers)( ALsizei n, ALuint* buffers ); +void (ALAPIENTRY *alDeleteBuffers)( ALsizei n, const ALuint* buffers ); +ALboolean (ALAPIENTRY *alIsBuffer)( ALuint bid ); +void (ALAPIENTRY *alBufferData)( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq ); +void (ALAPIENTRY *alBufferf)( ALuint bid, ALenum param, ALfloat value); +void (ALAPIENTRY *alBuffer3f)( ALuint bid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +void (ALAPIENTRY *alBufferfv)( ALuint bid, ALenum param, const ALfloat* values ); +void (ALAPIENTRY *alBufferi)( ALuint bid, ALenum param, ALint value); +void (ALAPIENTRY *alBuffer3i)( ALuint bid, ALenum param, ALint value1, ALint value2, ALint value3 ); +void (ALAPIENTRY *alBufferiv)( ALuint bid, ALenum param, const ALint* values ); +void (ALAPIENTRY *alGetBufferf)( ALuint bid, ALenum param, ALfloat* value ); +void (ALAPIENTRY *alGetBuffer3f)( ALuint bid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +void (ALAPIENTRY *alGetBufferfv)( ALuint bid, ALenum param, ALfloat* values ); +void (ALAPIENTRY *alGetBufferi)( ALuint bid, ALenum param, ALint* value ); +void (ALAPIENTRY *alGetBuffer3i)( ALuint bid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +void (ALAPIENTRY *alGetBufferiv)( ALuint bid, ALenum param, ALint* values ); +void (ALAPIENTRY *alDopplerFactor)( ALfloat value ); +void (ALAPIENTRY *alDopplerVelocity)( ALfloat value ); +void (ALAPIENTRY *alSpeedOfSound)( ALfloat value ); +void (ALAPIENTRY *alDistanceModel)( ALenum distanceModel ); +*/ +/* Type Definitions */ + +typedef void (ALAPIENTRY *LPALENABLE)( ALenum capability ); +typedef void (ALAPIENTRY *LPALDISABLE)( ALenum capability ); +typedef ALboolean (ALAPIENTRY *LPALISENABLED)( ALenum capability ); +typedef const ALchar* (ALAPIENTRY *LPALGETSTRING)( ALenum param ); +typedef void (ALAPIENTRY *LPALGETBOOLEANV)( ALenum param, ALboolean* data ); +typedef void (ALAPIENTRY *LPALGETINTEGERV)( ALenum param, ALint* data ); +typedef void (ALAPIENTRY *LPALGETFLOATV)( ALenum param, ALfloat* data ); +typedef void (ALAPIENTRY *LPALGETDOUBLEV)( ALenum param, ALdouble* data ); +typedef ALboolean (ALAPIENTRY *LPALGETBOOLEAN)( ALenum param ); +typedef ALint (ALAPIENTRY *LPALGETINTEGER)( ALenum param ); +typedef ALfloat (ALAPIENTRY *LPALGETFLOAT)( ALenum param ); +typedef ALdouble (ALAPIENTRY *LPALGETDOUBLE)( ALenum param ); +typedef ALenum (ALAPIENTRY *LPALGETERROR)( ALvoid ); +typedef ALboolean (ALAPIENTRY *LPALISEXTENSIONPRESENT)(const ALchar* extname ); +typedef void* (ALAPIENTRY *LPALGETPROCADDRESS)( const ALchar* fname ); +typedef ALenum (ALAPIENTRY *LPALGETENUMVALUE)( const ALchar* ename ); +typedef void (ALAPIENTRY *LPALLISTENERF)( ALenum param, ALfloat value ); +typedef void (ALAPIENTRY *LPALLISTENER3F)( ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (ALAPIENTRY *LPALLISTENERFV)( ALenum param, const ALfloat* values ); +typedef void (ALAPIENTRY *LPALLISTENERI)( ALenum param, ALint value ); +typedef void (ALAPIENTRY *LPALLISTENER3I)( ALenum param, ALint value1, ALint value2, ALint value3 ); +typedef void (ALAPIENTRY *LPALLISTENERIV)( ALenum param, const ALint* values ); +typedef void (ALAPIENTRY *LPALGETLISTENERF)( ALenum param, ALfloat* value ); +typedef void (ALAPIENTRY *LPALGETLISTENER3F)( ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3 ); +typedef void (ALAPIENTRY *LPALGETLISTENERFV)( ALenum param, ALfloat* values ); +typedef void (ALAPIENTRY *LPALGETLISTENERI)( ALenum param, ALint* value ); +typedef void (ALAPIENTRY *LPALGETLISTENER3I)( ALenum param, ALint *value1, ALint *value2, ALint *value3 ); +typedef void (ALAPIENTRY *LPALGETLISTENERIV)( ALenum param, ALint* values ); +typedef void (ALAPIENTRY *LPALGENSOURCES)( ALsizei n, ALuint* sources ); +typedef void (ALAPIENTRY *LPALDELETESOURCES)( ALsizei n, const ALuint* sources ); +typedef ALboolean (ALAPIENTRY *LPALISSOURCE)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEF)( ALuint sid, ALenum param, ALfloat value); +typedef void (ALAPIENTRY *LPALSOURCE3F)( ALuint sid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (ALAPIENTRY *LPALSOURCEFV)( ALuint sid, ALenum param, const ALfloat* values ); +typedef void (ALAPIENTRY *LPALSOURCEI)( ALuint sid, ALenum param, ALint value); +typedef void (ALAPIENTRY *LPALSOURCE3I)( ALuint sid, ALenum param, ALint value1, ALint value2, ALint value3 ); +typedef void (ALAPIENTRY *LPALSOURCEIV)( ALuint sid, ALenum param, const ALint* values ); +typedef void (ALAPIENTRY *LPALGETSOURCEF)( ALuint sid, ALenum param, ALfloat* value ); +typedef void (ALAPIENTRY *LPALGETSOURCE3F)( ALuint sid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +typedef void (ALAPIENTRY *LPALGETSOURCEFV)( ALuint sid, ALenum param, ALfloat* values ); +typedef void (ALAPIENTRY *LPALGETSOURCEI)( ALuint sid, ALenum param, ALint* value ); +typedef void (ALAPIENTRY *LPALGETSOURCE3I)( ALuint sid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +typedef void (ALAPIENTRY *LPALGETSOURCEIV)( ALuint sid, ALenum param, ALint* values ); +typedef void (ALAPIENTRY *LPALSOURCEPLAYV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCESTOPV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCEREWINDV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCEPAUSEV)( ALsizei ns, const ALuint *sids ); +typedef void (ALAPIENTRY *LPALSOURCEPLAY)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCESTOP)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEREWIND)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEPAUSE)( ALuint sid ); +typedef void (ALAPIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, const ALuint *bids ); +typedef void (ALAPIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint sid, ALsizei numEntries, ALuint *bids ); +typedef void (ALAPIENTRY *LPALGENBUFFERS)( ALsizei n, ALuint* buffers ); +typedef void (ALAPIENTRY *LPALDELETEBUFFERS)( ALsizei n, const ALuint* buffers ); +typedef ALboolean (ALAPIENTRY *LPALISBUFFER)( ALuint bid ); +typedef void (ALAPIENTRY *LPALBUFFERDATA)( ALuint bid, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq ); +typedef void (ALAPIENTRY *LPALBUFFERF)( ALuint bid, ALenum param, ALfloat value); +typedef void (ALAPIENTRY *LPALBUFFER3F)( ALuint bid, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3 ); +typedef void (ALAPIENTRY *LPALBUFFERFV)( ALuint bid, ALenum param, const ALfloat* values ); +typedef void (ALAPIENTRY *LPALBUFFERI)( ALuint bid, ALenum param, ALint value); +typedef void (ALAPIENTRY *LPALBUFFER3I)( ALuint bid, ALenum param, ALint value1, ALint value2, ALint value3 ); +typedef void (ALAPIENTRY *LPALBUFFERIV)( ALuint bid, ALenum param, const ALint* values ); +typedef void (ALAPIENTRY *LPALGETBUFFERF)( ALuint bid, ALenum param, ALfloat* value ); +typedef void (ALAPIENTRY *LPALGETBUFFER3F)( ALuint bid, ALenum param, ALfloat* value1, ALfloat* value2, ALfloat* value3); +typedef void (ALAPIENTRY *LPALGETBUFFERFV)( ALuint bid, ALenum param, ALfloat* values ); +typedef void (ALAPIENTRY *LPALGETBUFFERI)( ALuint bid, ALenum param, ALint* value ); +typedef void (ALAPIENTRY *LPALGETBUFFER3I)( ALuint bid, ALenum param, ALint* value1, ALint* value2, ALint* value3); +typedef void (ALAPIENTRY *LPALGETBUFFERIV)( ALuint bid, ALenum param, ALint* values ); +typedef void (ALAPIENTRY *LPALDOPPLERFACTOR)( ALfloat value ); +typedef void (ALAPIENTRY *LPALDOPPLERVELOCITY)( ALfloat value ); +typedef void (ALAPIENTRY *LPALSPEEDOFSOUND)( ALfloat value ); +typedef void (ALAPIENTRY *LPALDISTANCEMODEL)( ALenum distanceModel ); + +#endif /* AL_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export off +#endif /* TARGET_OS_MAC */ +#endif /* TARGET_OS_MAC */ + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* __al_h_ */ diff --git a/code/AL/alc.h b/code/AL/alc.h new file mode 100644 index 0000000..f3a41bc --- /dev/null +++ b/code/AL/alc.h @@ -0,0 +1,166 @@ +#ifndef ALC_CONTEXT_H_ +#define ALC_CONTEXT_H_ + +#include "altypes.h" +#include "alctypes.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ALC_VERSION_0_1 1 + +#ifdef _WIN32 + typedef struct ALCdevice_struct ALCdevice; + typedef struct ALCcontext_struct ALCcontext; + #ifndef _XBOX + #ifdef _OPENAL32LIB + #define ALCAPI __declspec(dllexport) + #else + #define ALCAPI __declspec(dllimport) + #endif + #define ALCAPIENTRY __cdecl + #endif +#endif + +#ifdef TARGET_OS_MAC + #if TARGET_OS_MAC + #pragma export on + #endif +#endif + +#ifndef ALCAPI + #define ALCAPI +#endif + +#ifndef ALCAPIENTRY + #define ALCAPIENTRY +#endif + + +#ifndef ALC_NO_PROTOTYPES + +/* + * Context Management + */ +ALCAPI ALCcontext * ALCAPIENTRY alcCreateContext( ALCdevice *device, const ALCint* attrlist ); + +ALCAPI ALCboolean ALCAPIENTRY alcMakeContextCurrent( ALCcontext *context ); + +ALCAPI void ALCAPIENTRY alcProcessContext( ALCcontext *context ); + +ALCAPI void ALCAPIENTRY alcSuspendContext( ALCcontext *context ); + +ALCAPI void ALCAPIENTRY alcDestroyContext( ALCcontext *context ); + +ALCAPI ALCcontext * ALCAPIENTRY alcGetCurrentContext( ALCvoid ); + +ALCAPI ALCdevice* ALCAPIENTRY alcGetContextsDevice( ALCcontext *context ); + + +/* + * Device Management + */ +ALCAPI ALCdevice * ALCAPIENTRY alcOpenDevice( const ALchar *devicename ); + +ALCAPI ALCboolean ALCAPIENTRY alcCloseDevice( ALCdevice *device ); + + +/* + * Error support. + * Obtain the most recent Context error + */ +ALCAPI ALCenum ALCAPIENTRY alcGetError( ALCdevice *device ); + + +/* + * Extension support. + * Query for the presence of an extension, and obtain any appropriate + * function pointers and enum values. + */ +ALCAPI ALCboolean ALCAPIENTRY alcIsExtensionPresent( ALCdevice *device, const ALCchar *extname ); + +ALCAPI void * ALCAPIENTRY alcGetProcAddress( ALCdevice *device, const ALCchar *funcname ); + +ALCAPI ALCenum ALCAPIENTRY alcGetEnumValue( ALCdevice *device, const ALCchar *enumname ); + + +/* + * Query functions + */ +ALCAPI const ALCchar * ALCAPIENTRY alcGetString( ALCdevice *device, ALCenum param ); + +ALCAPI void ALCAPIENTRY alcGetIntegerv( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *data ); + + +/* + * Capture functions + */ +ALCAPI ALCdevice* ALCAPIENTRY alcCaptureOpenDevice( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); + +ALCAPI ALCboolean ALCAPIENTRY alcCaptureCloseDevice( ALCdevice *device ); + +ALCAPI void ALCAPIENTRY alcCaptureStart( ALCdevice *device ); + +ALCAPI void ALCAPIENTRY alcCaptureStop( ALCdevice *device ); + +ALCAPI void ALCAPIENTRY alcCaptureSamples( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); + +#else /* ALC_NO_PROTOTYPES */ +/* +ALCAPI ALCcontext * (ALCAPIENTRY *alcCreateContext)( ALCdevice *device, const ALCint* attrlist ); +ALCAPI ALCboolean (ALCAPIENTRY *alcMakeContextCurrent)( ALCcontext *context ); +ALCAPI void (ALCAPIENTRY *alcProcessContext)( ALCcontext *context ); +ALCAPI void (ALCAPIENTRY *alcSuspendContext)( ALCcontext *context ); +ALCAPI void (ALCAPIENTRY *alcDestroyContext)( ALCcontext *context ); +ALCAPI ALCcontext * (ALCAPIENTRY *alcGetCurrentContext)( ALCvoid ); +ALCAPI ALCdevice * (ALCAPIENTRY *alcGetContextsDevice)( ALCcontext *context ); +ALCAPI ALCdevice * (ALCAPIENTRY *alcOpenDevice)( const ALCchar *devicename ); +ALCAPI ALCboolean (ALCAPIENTRY *alcCloseDevice)( ALCdevice *device ); +ALCAPI ALCenum (ALCAPIENTRY *alcGetError)( ALCdevice *device ); +ALCAPI ALCboolean (ALCAPIENTRY *alcIsExtensionPresent)( ALCdevice *device, const ALCchar *extname ); +ALCAPI void * (ALCAPIENTRY *alcGetProcAddress)( ALCdevice *device, const ALCchar *funcname ); +ALCAPI ALCenum (ALCAPIENTRY *alcGetEnumValue)( ALCdevice *device, const ALCchar *enumname ); +ALCAPI const ALCchar* (ALCAPIENTRY *alcGetString)( ALCdevice *device, ALCenum param ); +ALCAPI void (ALCAPIENTRY *alcGetIntegerv)( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *dest ); +ALCAPI ALCdevice * (ALCAPIENTRY *alcCaptureOpenDevice)( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); +ALCAPI ALCboolean (ALCAPIENTRY *alcCaptureCloseDevice)( ALCdevice *device ); +ALCAPI void (ALCAPIENTRY *alcCaptureStart)( ALCdevice *device ); +ALCAPI void (ALCAPIENTRY *alcCaptureStop)( ALCdevice *device ); +ALCAPI void (ALCAPIENTRY *alcCaptureSamples)( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); +*/ +/* Type definitions */ +typedef ALCcontext * (ALCAPIENTRY *LPALCCREATECONTEXT) (ALCdevice *device, const ALCint *attrlist); +typedef ALCboolean (ALCAPIENTRY *LPALCMAKECONTEXTCURRENT)( ALCcontext *context ); +typedef void (ALCAPIENTRY *LPALCPROCESSCONTEXT)( ALCcontext *context ); +typedef void (ALCAPIENTRY *LPALCSUSPENDCONTEXT)( ALCcontext *context ); +typedef void (ALCAPIENTRY *LPALCDESTROYCONTEXT)( ALCcontext *context ); +typedef ALCcontext * (ALCAPIENTRY *LPALCGETCURRENTCONTEXT)( ALCvoid ); +typedef ALCdevice * (ALCAPIENTRY *LPALCGETCONTEXTSDEVICE)( ALCcontext *context ); +typedef ALCdevice * (ALCAPIENTRY *LPALCOPENDEVICE)( const ALCchar *devicename ); +typedef ALCboolean (ALCAPIENTRY *LPALCCLOSEDEVICE)( ALCdevice *device ); +typedef ALCenum (ALCAPIENTRY *LPALCGETERROR)( ALCdevice *device ); +typedef ALCboolean (ALCAPIENTRY *LPALCISEXTENSIONPRESENT)( ALCdevice *device, const ALCchar *extname ); +typedef void * (ALCAPIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname ); +typedef ALCenum (ALCAPIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname ); +typedef const ALCchar* (ALCAPIENTRY *LPALCGETSTRING)( ALCdevice *device, ALCenum param ); +typedef void (ALCAPIENTRY *LPALCGETINTEGERV)( ALCdevice *device, ALCenum param, ALCsizei size, ALCint *dest ); +typedef ALCdevice * (ALCAPIENTRY *LPALCCAPTUREOPENDEVICE)( const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize ); +typedef ALCboolean (ALCAPIENTRY *LPALCCAPTURECLOSEDEVICE)( ALCdevice *device ); +typedef void (ALCAPIENTRY *LPALCCAPTURESTART)( ALCdevice *device ); +typedef void (ALCAPIENTRY *LPALCCAPTURESTOP)( ALCdevice *device ); +typedef void (ALCAPIENTRY *LPALCCAPTURESAMPLES)( ALCdevice *device, ALCvoid *buffer, ALCsizei samples ); + +#endif /* ALC_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export off +#endif /* TARGET_OS_MAC */ +#endif /* TARGET_OS_MAC */ + +#ifdef __cplusplus +} +#endif + +#endif /* ALC_CONTEXT_H_ */ diff --git a/code/AL/alctypes.h b/code/AL/alctypes.h new file mode 100644 index 0000000..a7c7858 --- /dev/null +++ b/code/AL/alctypes.h @@ -0,0 +1,143 @@ +#ifndef _ALCTYPES_H_ +#define _ALCTYPES_H_ + +#if !defined(_WIN32) +struct _AL_device; +typedef struct _AL_device ALCdevice; + +typedef void ALCcontext; +#endif /* _WIN32 */ + +typedef int ALCenum; + +/** ALC boolean type. */ +typedef char ALCboolean; + +/** ALC 8bit signed byte. */ +typedef char ALCbyte; + +/** ALC 8bit unsigned byte. */ +typedef unsigned char ALCubyte; + +/** OpenAL 8bit char */ +typedef char ALCchar; + +/** ALC 16bit signed short integer type. */ +typedef short ALCshort; + +/** ALC 16bit unsigned short integer type. */ +typedef unsigned short ALCushort; + +/** ALC 32bit unsigned integer type. */ +typedef unsigned ALCuint; + +/** ALC 32bit signed integer type. */ +typedef int ALCint; + +/** ALC 32bit floating point type. */ +typedef float ALCfloat; + +/** ALC 64bit double point type. */ +typedef double ALCdouble; + +/** ALC 32bit type. */ +typedef int ALCsizei; + +/** ALC void type */ +typedef void ALCvoid; + +/* Enumerant values begin at column 50. No tabs. */ + +/* bad value */ +#define ALC_INVALID 0 + +/* Boolean False. */ +#define ALC_FALSE 0 + +/* Boolean True. */ +#define ALC_TRUE 1 + +/** + * followed by Hz + */ +#define ALC_FREQUENCY 0x1007 + +/** + * followed by Hz + */ +#define ALC_REFRESH 0x1008 + +/** + * followed by AL_TRUE, AL_FALSE + */ +#define ALC_SYNC 0x1009 + +/** + * followed by Num of requested Mono (3D) Sources + */ +#define ALC_MONO_SOURCES 0x1010 + +/** + * followed by Num of requested Stereo Sources + */ +#define ALC_STEREO_SOURCES 0x1011 + +/** + * errors + */ + +/** + * No error + */ +#define ALC_NO_ERROR ALC_FALSE + +/** + * No device + */ +#define ALC_INVALID_DEVICE 0xA001 + +/** + * invalid context ID + */ +#define ALC_INVALID_CONTEXT 0xA002 + +/** + * bad enum + */ +#define ALC_INVALID_ENUM 0xA003 + +/** + * bad value + */ +#define ALC_INVALID_VALUE 0xA004 + +/** + * Out of memory. + */ +#define ALC_OUT_OF_MEMORY 0xA005 + + + +/** + * The Specifier string for default device + */ +#define ALC_DEFAULT_DEVICE_SPECIFIER 0x1004 +#define ALC_DEVICE_SPECIFIER 0x1005 +#define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#define ALC_EXTENSIONS 0x1006 + +#define ALC_MAJOR_VERSION 0x1000 +#define ALC_MINOR_VERSION 0x1001 + +#define ALC_ATTRIBUTES_SIZE 0x1002 +#define ALC_ALL_ATTRIBUTES 0x1003 + +/** + * Capture extension + */ +#define ALC_CAPTURE_DEVICE_SPECIFIER 0x310 +#define ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER 0x311 +#define ALC_CAPTURE_SAMPLES 0x312 + + +#endif /* _ALCTYPES_H */ diff --git a/code/AL/altypes.h b/code/AL/altypes.h new file mode 100644 index 0000000..3ec959f --- /dev/null +++ b/code/AL/altypes.h @@ -0,0 +1,352 @@ +#ifndef _AL_TYPES_H_ +#define _AL_TYPES_H_ + +/* define platform type */ +#if !defined(MACINTOSH_AL) && !defined(LINUX_AL) && !defined(WINDOWS_AL) + #ifdef __APPLE__ + #define MACINTOSH_AL + #else + #ifdef _WIN32 + #define WINDOWS_AL + #else + #define LINUX_AL + #endif + #endif +#endif + +/** OpenAL bool type. */ +typedef char ALboolean; + +/** OpenAL 8bit signed byte. */ +typedef char ALbyte; + +/** OpenAL 8bit unsigned byte. */ +typedef unsigned char ALubyte; + +/** OpenAL 8bit char */ +typedef char ALchar; + +/** OpenAL 16bit signed short integer type. */ +typedef short ALshort; + +/** OpenAL 16bit unsigned short integer type. */ +typedef unsigned short ALushort; + +/** OpenAL 32bit unsigned integer type. */ +typedef unsigned int ALuint; + +/** OpenAL 32bit signed integer type. */ +typedef int ALint; + +/** OpenAL 32bit floating point type. */ +typedef float ALfloat; + +/** OpenAL 64bit double point type. */ +typedef double ALdouble; + +/** OpenAL 32bit type. */ +typedef int ALsizei; + +/** OpenAL void type (for params, not returns). */ +typedef void ALvoid; + +/** OpenAL enumerations. */ +typedef int ALenum; + +/** OpenAL bitfields. */ +typedef unsigned int ALbitfield; + +/** OpenAL clamped float. */ +typedef ALfloat ALclampf; + +/** Openal clamped double. */ +typedef ALdouble ALclampd; + +/* Enumerant values begin at column 50. No tabs. */ + +/* bad value */ +#define AL_INVALID -1 + +#define AL_NONE 0 + +/* Boolean False. */ +#define AL_FALSE 0 + +/** Boolean True. */ +#define AL_TRUE 1 + +/** Indicate Source has relative coordinates. */ +#define AL_SOURCE_RELATIVE 0x202 + + + +/** + * Directional source, inner cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_INNER_ANGLE 0x1001 + +/** + * Directional source, outer cone angle, in degrees. + * Range: [0-360] + * Default: 360 + */ +#define AL_CONE_OUTER_ANGLE 0x1002 + +/** + * Specify the pitch to be applied, either at source, + * or on mixer results, at listener. + * Range: [0.5-2.0] + * Default: 1.0 + */ +#define AL_PITCH 0x1003 + +/** + * Specify the current location in three dimensional space. + * OpenAL, like OpenGL, uses a right handed coordinate system, + * where in a frontal default view X (thumb) points right, + * Y points up (index finger), and Z points towards the + * viewer/camera (middle finger). + * To switch from a left handed coordinate system, flip the + * sign on the Z coordinate. + * Listener position is always in the world coordinate system. + */ +#define AL_POSITION 0x1004 + +/** Specify the current direction. */ +#define AL_DIRECTION 0x1005 + +/** Specify the current velocity in three dimensional space. */ +#define AL_VELOCITY 0x1006 + +/** + * Indicate whether source is looping. + * Type: ALboolean? + * Range: [AL_TRUE, AL_FALSE] + * Default: FALSE. + */ +#define AL_LOOPING 0x1007 + +/** + * Indicate the buffer to provide sound samples. + * Type: ALuint. + * Range: any valid Buffer id. + */ +#define AL_BUFFER 0x1009 + +/** + * Indicate the gain (volume amplification) applied. + * Type: ALfloat. + * Range: ]0.0- ] + * A value of 1.0 means un-attenuated/unchanged. + * Each division by 2 equals an attenuation of -6dB. + * Each multiplicaton with 2 equals an amplification of +6dB. + * A value of 0.0 is meaningless with respect to a logarithmic + * scale; it is interpreted as zero volume - the channel + * is effectively disabled. + */ +#define AL_GAIN 0x100A + +/* + * Indicate minimum source attenuation + * Type: ALfloat + * Range: [0.0 - 1.0] + * + * Logarthmic + */ +#define AL_MIN_GAIN 0x100D + +/** + * Indicate maximum source attenuation + * Type: ALfloat + * Range: [0.0 - 1.0] + * + * Logarthmic + */ +#define AL_MAX_GAIN 0x100E + +/** + * Indicate listener orientation. + * + * at/up + */ +#define AL_ORIENTATION 0x100F + +/** + * Specify the channel mask. (Creative) + * Type: ALuint + * Range: [0 - 255] + */ +#define AL_CHANNEL_MASK 0x3000 + + +/** + * Source state information. + */ +#define AL_SOURCE_STATE 0x1010 +#define AL_INITIAL 0x1011 +#define AL_PLAYING 0x1012 +#define AL_PAUSED 0x1013 +#define AL_STOPPED 0x1014 + +/** + * Buffer Queue params + */ +#define AL_BUFFERS_QUEUED 0x1015 +#define AL_BUFFERS_PROCESSED 0x1016 + +/** + * Source buffer position information + */ +#define AL_SEC_OFFSET 0x1024 +#define AL_SAMPLE_OFFSET 0x1025 +#define AL_BYTE_OFFSET 0x1026 + +/* + * Source type (Static, Streaming or undetermined) + * Source is Static if a Buffer has been attached using AL_BUFFER + * Source is Streaming if one or more Buffers have been attached using alSourceQueueBuffers + * Source is undetermined when it has the NULL buffer attached + */ +#define AL_SOURCE_TYPE 0x1027 +#define AL_STATIC 0x1028 +#define AL_STREAMING 0x1029 +#define AL_UNDETERMINED 0x1030 + +/** Sound samples: format specifier. */ +#define AL_FORMAT_MONO8 0x1100 +#define AL_FORMAT_MONO16 0x1101 +#define AL_FORMAT_STEREO8 0x1102 +#define AL_FORMAT_STEREO16 0x1103 + +/** + * source specific reference distance + * Type: ALfloat + * Range: 0.0 - +inf + * + * At 0.0, no distance attenuation occurs. Default is + * 1.0. + */ +#define AL_REFERENCE_DISTANCE 0x1020 + +/** + * source specific rolloff factor + * Type: ALfloat + * Range: 0.0 - +inf + * + */ +#define AL_ROLLOFF_FACTOR 0x1021 + +/** + * Directional source, outer cone gain. + * + * Default: 0.0 + * Range: [0.0 - 1.0] + * Logarithmic + */ +#define AL_CONE_OUTER_GAIN 0x1022 + +/** + * Indicate distance above which sources are not + * attenuated using the inverse clamped distance model. + * + * Default: +inf + * Type: ALfloat + * Range: 0.0 - +inf + */ +#define AL_MAX_DISTANCE 0x1023 + +/** + * Sound samples: frequency, in units of Hertz [Hz]. + * This is the number of samples per second. Half of the + * sample frequency marks the maximum significant + * frequency component. + */ +#define AL_FREQUENCY 0x2001 +#define AL_BITS 0x2002 +#define AL_CHANNELS 0x2003 +#define AL_SIZE 0x2004 +#define AL_DATA 0x2005 + +/** + * Buffer state. + * + * Not supported for public use (yet). + */ +#define AL_UNUSED 0x2010 +#define AL_PENDING 0x2011 +#define AL_PROCESSED 0x2012 + + +/** Errors: No Error. */ +#define AL_NO_ERROR AL_FALSE + +/** + * Invalid Name paramater passed to AL call. + */ +#define AL_INVALID_NAME 0xA001 + +/** + * Invalid parameter passed to AL call. + */ +#define AL_ILLEGAL_ENUM 0xA002 +#define AL_INVALID_ENUM 0xA002 + +/** + * Invalid enum parameter value. + */ +#define AL_INVALID_VALUE 0xA003 + +/** + * Illegal call. + */ +#define AL_ILLEGAL_COMMAND 0xA004 +#define AL_INVALID_OPERATION 0xA004 + + +/** + * No mojo. + */ +#define AL_OUT_OF_MEMORY 0xA005 + + +/** Context strings: Vendor Name. */ +#define AL_VENDOR 0xB001 +#define AL_VERSION 0xB002 +#define AL_RENDERER 0xB003 +#define AL_EXTENSIONS 0xB004 + +/** Global tweakage. */ + +/** + * Doppler scale. Default 1.0 + */ +#define AL_DOPPLER_FACTOR 0xC000 + +/** + * Tweaks speed of propagation. + */ +#define AL_DOPPLER_VELOCITY 0xC001 + +/** + * Speed of Sound in units per second + */ +#define AL_SPEED_OF_SOUND 0xC003 + +/** + * Distance models + * + * used in conjunction with DistanceModel + * + * implicit: NONE, which disances distance attenuation. + */ +#define AL_DISTANCE_MODEL 0xD000 +#define AL_INVERSE_DISTANCE 0xD001 +#define AL_INVERSE_DISTANCE_CLAMPED 0xD002 +#define AL_LINEAR_DISTANCE 0xD003 +#define AL_LINEAR_DISTANCE_CLAMPED 0xD004 +#define AL_EXPONENT_DISTANCE 0xD005 +#define AL_EXPONENT_DISTANCE_CLAMPED 0xD006 + +#endif diff --git a/code/AL/alut.h b/code/AL/alut.h new file mode 100644 index 0000000..e29ae58 --- /dev/null +++ b/code/AL/alut.h @@ -0,0 +1,90 @@ +#ifndef _ALUT_H_ +#define _ALUT_H_ + +/* define platform type */ +#if !defined(MACINTOSH_AL) && !defined(LINUX_AL) && !defined(WINDOWS_AL) + #ifdef __APPLE__ + #define MACINTOSH_AL + #else + #ifdef _WIN32 + #define WINDOWS_AL + #else + #define LINUX_AL + #endif + #endif +#endif + +#include "altypes.h" + +#ifdef _WIN32 +#define ALUTAPI +#define ALUTAPIENTRY __cdecl +#define AL_CALLBACK +#else /* _WIN32 */ + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export on +#endif /* TARGET_OS_MAC */ +#endif /* TARGET_OS_MAC */ + +#ifndef ALUTAPI +#define ALUTAPI +#endif + +#ifndef ALUTAPIENTRY +#define ALUTAPIENTRY +#endif + +#ifndef AL_CALLBACK +#define AL_CALLBACK +#endif + +#endif /* _WIN32 */ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef ALUT_NO_PROTOTYPES + +ALUTAPI void ALUTAPIENTRY alutInit(int *argc, char *argv[]); +ALUTAPI void ALUTAPIENTRY alutExit(ALvoid); + +#ifndef MACINTOSH_AL +/* Windows and Linux versions have a loop parameter, Macintosh doesn't */ +ALUTAPI void ALUTAPIENTRY alutLoadWAVFile(ALbyte *file, ALenum *format, ALvoid **data, ALsizei *size, ALsizei *freq, ALboolean *loop); +ALUTAPI void ALUTAPIENTRY alutLoadWAVMemory(ALbyte *memory, ALenum *format, ALvoid **data, ALsizei *size, ALsizei *freq, ALboolean *loop); +#else +ALUTAPI void ALUTAPIENTRY alutLoadWAVFile(ALbyte *file, ALenum *format, ALvoid **data, ALsizei *size, ALsizei *freq); +ALUTAPI void ALUTAPIENTRY alutLoadWAVMemory(ALbyte *memory, ALenum *format, ALvoid **data, ALsizei *size, ALsizei *freq); +#endif + +ALUTAPI void ALUTAPIENTRY alutUnloadWAV(ALenum format, ALvoid *data, ALsizei size, ALsizei freq); + +#else /* ALUT_NO_PROTOTYPES */ + + void (ALUTAPIENTRY *alutInit)( int *argc, char *argv[] ); + void (ALUTAPIENTRY *alutExit)( ALvoid ); +#ifndef MACINTOSH_AL + void (ALUTAPIENTRY *alutLoadWAVFile)( ALbyte *file,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq,ALboolean *loop ); + void (ALUTAPIENTRY *alutLoadWAVMemory)( ALbyte *memory,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq,ALboolean *loop ); +#else + void (ALUTAPIENTRY *alutLoadWAVFile( ALbyte *file,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq ); + void (ALUTAPIENTRY *alutLoadWAVMemory)( ALbyte *memory,ALenum *format,ALvoid **data,ALsizei *size,ALsizei *freq ); +#endif + void (ALUTAPIENTRY *alutUnloadWAV)( ALenum format,ALvoid *data,ALsizei size,ALsizei freq ); + +#endif /* ALUT_NO_PROTOTYPES */ + +#ifdef TARGET_OS_MAC +#if TARGET_OS_MAC +#pragma export off +#endif /* TARGET_OS_MAC */ +#endif /* TARGET_OS_MAC */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/code/SDL12/include/SDL.h b/code/SDL12/include/SDL.h new file mode 100644 index 0000000..119ed7f --- /dev/null +++ b/code/SDL12/include/SDL.h @@ -0,0 +1,101 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL.h + * Main include header for the SDL library + */ + +#ifndef _SDL_H +#define _SDL_H + +#include "SDL_main.h" +#include "SDL_stdinc.h" +#include "SDL_audio.h" +#include "SDL_cdrom.h" +#include "SDL_cpuinfo.h" +#include "SDL_endian.h" +#include "SDL_error.h" +#include "SDL_events.h" +#include "SDL_loadso.h" +#include "SDL_mutex.h" +#include "SDL_rwops.h" +#include "SDL_thread.h" +#include "SDL_timer.h" +#include "SDL_video.h" +#include "SDL_version.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @file SDL.h + * @note As of version 0.5, SDL is loaded dynamically into the application + */ + +/** @name SDL_INIT Flags + * These are the flags which may be passed to SDL_Init() -- you should + * specify the subsystems which you will be using in your application. + */ +/*@{*/ +#define SDL_INIT_TIMER 0x00000001 +#define SDL_INIT_AUDIO 0x00000010 +#define SDL_INIT_VIDEO 0x00000020 +#define SDL_INIT_CDROM 0x00000100 +#define SDL_INIT_JOYSTICK 0x00000200 +#define SDL_INIT_NOPARACHUTE 0x00100000 /**< Don't catch fatal signals */ +#define SDL_INIT_EVENTTHREAD 0x01000000 /**< Not supported on all OS's */ +#define SDL_INIT_EVERYTHING 0x0000FFFF +/*@}*/ + +/** This function loads the SDL dynamically linked library and initializes + * the subsystems specified by 'flags' (and those satisfying dependencies) + * Unless the SDL_INIT_NOPARACHUTE flag is set, it will install cleanup + * signal handlers for some commonly ignored fatal signals (like SIGSEGV) + */ +extern DECLSPEC int SDLCALL SDL_Init(Uint32 flags); + +/** This function initializes specific SDL subsystems */ +extern DECLSPEC int SDLCALL SDL_InitSubSystem(Uint32 flags); + +/** This function cleans up specific SDL subsystems */ +extern DECLSPEC void SDLCALL SDL_QuitSubSystem(Uint32 flags); + +/** This function returns mask of the specified subsystems which have + * been initialized. + * If 'flags' is 0, it returns a mask of all initialized subsystems. + */ +extern DECLSPEC Uint32 SDLCALL SDL_WasInit(Uint32 flags); + +/** This function cleans up all initialized subsystems and unloads the + * dynamically linked library. You should call it upon all exit conditions. + */ +extern DECLSPEC void SDLCALL SDL_Quit(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_H */ diff --git a/code/SDL12/include/SDL_active.h b/code/SDL12/include/SDL_active.h new file mode 100644 index 0000000..0ae92f2 --- /dev/null +++ b/code/SDL12/include/SDL_active.h @@ -0,0 +1,63 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_active.h + * Include file for SDL application focus event handling + */ + +#ifndef _SDL_active_h +#define _SDL_active_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @name The available application states */ +/*@{*/ +#define SDL_APPMOUSEFOCUS 0x01 /**< The app has mouse coverage */ +#define SDL_APPINPUTFOCUS 0x02 /**< The app has input focus */ +#define SDL_APPACTIVE 0x04 /**< The application is active */ +/*@}*/ + +/* Function prototypes */ +/** + * This function returns the current state of the application, which is a + * bitwise combination of SDL_APPMOUSEFOCUS, SDL_APPINPUTFOCUS, and + * SDL_APPACTIVE. If SDL_APPACTIVE is set, then the user is able to + * see your application, otherwise it has been iconified or disabled. + */ +extern DECLSPEC Uint8 SDLCALL SDL_GetAppState(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_active_h */ diff --git a/code/SDL12/include/SDL_audio.h b/code/SDL12/include/SDL_audio.h new file mode 100644 index 0000000..3a8e7fa --- /dev/null +++ b/code/SDL12/include/SDL_audio.h @@ -0,0 +1,284 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_audio.h + * Access to the raw audio mixing buffer for the SDL library + */ + +#ifndef _SDL_audio_h +#define _SDL_audio_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_endian.h" +#include "SDL_mutex.h" +#include "SDL_thread.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * When filling in the desired audio spec structure, + * - 'desired->freq' should be the desired audio frequency in samples-per-second. + * - 'desired->format' should be the desired audio format. + * - 'desired->samples' is the desired size of the audio buffer, in samples. + * This number should be a power of two, and may be adjusted by the audio + * driver to a value more suitable for the hardware. Good values seem to + * range between 512 and 8096 inclusive, depending on the application and + * CPU speed. Smaller values yield faster response time, but can lead + * to underflow if the application is doing heavy processing and cannot + * fill the audio buffer in time. A stereo sample consists of both right + * and left channels in LR ordering. + * Note that the number of samples is directly related to time by the + * following formula: ms = (samples*1000)/freq + * - 'desired->size' is the size in bytes of the audio buffer, and is + * calculated by SDL_OpenAudio(). + * - 'desired->silence' is the value used to set the buffer to silence, + * and is calculated by SDL_OpenAudio(). + * - 'desired->callback' should be set to a function that will be called + * when the audio device is ready for more data. It is passed a pointer + * to the audio buffer, and the length in bytes of the audio buffer. + * This function usually runs in a separate thread, and so you should + * protect data structures that it accesses by calling SDL_LockAudio() + * and SDL_UnlockAudio() in your code. + * - 'desired->userdata' is passed as the first parameter to your callback + * function. + * + * @note The calculated values in this structure are calculated by SDL_OpenAudio() + * + */ +typedef struct SDL_AudioSpec { + int freq; /**< DSP frequency -- samples per second */ + Uint16 format; /**< Audio data format */ + Uint8 channels; /**< Number of channels: 1 mono, 2 stereo */ + Uint8 silence; /**< Audio buffer silence value (calculated) */ + Uint16 samples; /**< Audio buffer size in samples (power of 2) */ + Uint16 padding; /**< Necessary for some compile environments */ + Uint32 size; /**< Audio buffer size in bytes (calculated) */ + /** + * This function is called when the audio device needs more data. + * + * @param[out] stream A pointer to the audio data buffer + * @param[in] len The length of the audio buffer in bytes. + * + * Once the callback returns, the buffer will no longer be valid. + * Stereo samples are stored in a LRLRLR ordering. + */ + void (SDLCALL *callback)(void *userdata, Uint8 *stream, int len); + void *userdata; +} SDL_AudioSpec; + +/** + * @name Audio format flags + * defaults to LSB byte order + */ +/*@{*/ +#define AUDIO_U8 0x0008 /**< Unsigned 8-bit samples */ +#define AUDIO_S8 0x8008 /**< Signed 8-bit samples */ +#define AUDIO_U16LSB 0x0010 /**< Unsigned 16-bit samples */ +#define AUDIO_S16LSB 0x8010 /**< Signed 16-bit samples */ +#define AUDIO_U16MSB 0x1010 /**< As above, but big-endian byte order */ +#define AUDIO_S16MSB 0x9010 /**< As above, but big-endian byte order */ +#define AUDIO_U16 AUDIO_U16LSB +#define AUDIO_S16 AUDIO_S16LSB + +/** + * @name Native audio byte ordering + */ +/*@{*/ +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define AUDIO_U16SYS AUDIO_U16LSB +#define AUDIO_S16SYS AUDIO_S16LSB +#else +#define AUDIO_U16SYS AUDIO_U16MSB +#define AUDIO_S16SYS AUDIO_S16MSB +#endif +/*@}*/ + +/*@}*/ + + +/** A structure to hold a set of audio conversion filters and buffers */ +typedef struct SDL_AudioCVT { + int needed; /**< Set to 1 if conversion possible */ + Uint16 src_format; /**< Source audio format */ + Uint16 dst_format; /**< Target audio format */ + double rate_incr; /**< Rate conversion increment */ + Uint8 *buf; /**< Buffer to hold entire audio data */ + int len; /**< Length of original audio buffer */ + int len_cvt; /**< Length of converted audio buffer */ + int len_mult; /**< buffer must be len*len_mult big */ + double len_ratio; /**< Given len, final size is len*len_ratio */ + void (SDLCALL *filters[10])(struct SDL_AudioCVT *cvt, Uint16 format); + int filter_index; /**< Current audio conversion function */ +} SDL_AudioCVT; + + +/* Function prototypes */ + +/** + * @name Audio Init and Quit + * These functions are used internally, and should not be used unless you + * have a specific need to specify the audio driver you want to use. + * You should normally use SDL_Init() or SDL_InitSubSystem(). + */ +/*@{*/ +extern DECLSPEC int SDLCALL SDL_AudioInit(const char *driver_name); +extern DECLSPEC void SDLCALL SDL_AudioQuit(void); +/*@}*/ + +/** + * This function fills the given character buffer with the name of the + * current audio driver, and returns a pointer to it if the audio driver has + * been initialized. It returns NULL if no driver has been initialized. + */ +extern DECLSPEC char * SDLCALL SDL_AudioDriverName(char *namebuf, int maxlen); + +/** + * This function opens the audio device with the desired parameters, and + * returns 0 if successful, placing the actual hardware parameters in the + * structure pointed to by 'obtained'. If 'obtained' is NULL, the audio + * data passed to the callback function will be guaranteed to be in the + * requested format, and will be automatically converted to the hardware + * audio format if necessary. This function returns -1 if it failed + * to open the audio device, or couldn't set up the audio thread. + * + * The audio device starts out playing silence when it's opened, and should + * be enabled for playing by calling SDL_PauseAudio(0) when you are ready + * for your audio callback function to be called. Since the audio driver + * may modify the requested size of the audio buffer, you should allocate + * any local mixing buffers after you open the audio device. + * + * @sa SDL_AudioSpec + */ +extern DECLSPEC int SDLCALL SDL_OpenAudio(SDL_AudioSpec *desired, SDL_AudioSpec *obtained); + +typedef enum { + SDL_AUDIO_STOPPED = 0, + SDL_AUDIO_PLAYING, + SDL_AUDIO_PAUSED +} SDL_audiostatus; + +/** Get the current audio state */ +extern DECLSPEC SDL_audiostatus SDLCALL SDL_GetAudioStatus(void); + +/** + * This function pauses and unpauses the audio callback processing. + * It should be called with a parameter of 0 after opening the audio + * device to start playing sound. This is so you can safely initialize + * data for your callback function after opening the audio device. + * Silence will be written to the audio device during the pause. + */ +extern DECLSPEC void SDLCALL SDL_PauseAudio(int pause_on); + +/** + * This function loads a WAVE from the data source, automatically freeing + * that source if 'freesrc' is non-zero. For example, to load a WAVE file, + * you could do: + * @code SDL_LoadWAV_RW(SDL_RWFromFile("sample.wav", "rb"), 1, ...); @endcode + * + * If this function succeeds, it returns the given SDL_AudioSpec, + * filled with the audio data format of the wave data, and sets + * 'audio_buf' to a malloc()'d buffer containing the audio data, + * and sets 'audio_len' to the length of that audio buffer, in bytes. + * You need to free the audio buffer with SDL_FreeWAV() when you are + * done with it. + * + * This function returns NULL and sets the SDL error message if the + * wave file cannot be opened, uses an unknown data format, or is + * corrupt. Currently raw and MS-ADPCM WAVE files are supported. + */ +extern DECLSPEC SDL_AudioSpec * SDLCALL SDL_LoadWAV_RW(SDL_RWops *src, int freesrc, SDL_AudioSpec *spec, Uint8 **audio_buf, Uint32 *audio_len); + +/** Compatibility convenience function -- loads a WAV from a file */ +#define SDL_LoadWAV(file, spec, audio_buf, audio_len) \ + SDL_LoadWAV_RW(SDL_RWFromFile(file, "rb"),1, spec,audio_buf,audio_len) + +/** + * This function frees data previously allocated with SDL_LoadWAV_RW() + */ +extern DECLSPEC void SDLCALL SDL_FreeWAV(Uint8 *audio_buf); + +/** + * This function takes a source format and rate and a destination format + * and rate, and initializes the 'cvt' structure with information needed + * by SDL_ConvertAudio() to convert a buffer of audio data from one format + * to the other. + * + * @return This function returns 0, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_BuildAudioCVT(SDL_AudioCVT *cvt, + Uint16 src_format, Uint8 src_channels, int src_rate, + Uint16 dst_format, Uint8 dst_channels, int dst_rate); + +/** + * Once you have initialized the 'cvt' structure using SDL_BuildAudioCVT(), + * created an audio buffer cvt->buf, and filled it with cvt->len bytes of + * audio data in the source format, this function will convert it in-place + * to the desired format. + * The data conversion may expand the size of the audio data, so the buffer + * cvt->buf should be allocated after the cvt structure is initialized by + * SDL_BuildAudioCVT(), and should be cvt->len*cvt->len_mult bytes long. + */ +extern DECLSPEC int SDLCALL SDL_ConvertAudio(SDL_AudioCVT *cvt); + + +#define SDL_MIX_MAXVOLUME 128 +/** + * This takes two audio buffers of the playing audio format and mixes + * them, performing addition, volume adjustment, and overflow clipping. + * The volume ranges from 0 - 128, and should be set to SDL_MIX_MAXVOLUME + * for full audio volume. Note this does not change hardware volume. + * This is provided for convenience -- you can mix your own audio data. + */ +extern DECLSPEC void SDLCALL SDL_MixAudio(Uint8 *dst, const Uint8 *src, Uint32 len, int volume); + +/** + * @name Audio Locks + * The lock manipulated by these functions protects the callback function. + * During a LockAudio/UnlockAudio pair, you can be guaranteed that the + * callback function is not running. Do not call these from the callback + * function or you will cause deadlock. + */ +/*@{*/ +extern DECLSPEC void SDLCALL SDL_LockAudio(void); +extern DECLSPEC void SDLCALL SDL_UnlockAudio(void); +/*@}*/ + +/** + * This function shuts down audio processing and closes the audio device. + */ +extern DECLSPEC void SDLCALL SDL_CloseAudio(void); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_audio_h */ diff --git a/code/SDL12/include/SDL_byteorder.h b/code/SDL12/include/SDL_byteorder.h new file mode 100644 index 0000000..9b93cd6 --- /dev/null +++ b/code/SDL12/include/SDL_byteorder.h @@ -0,0 +1,29 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_byteorder.h + * @deprecated Use SDL_endian.h instead + */ + +/* DEPRECATED */ +#include "SDL_endian.h" diff --git a/code/SDL12/include/SDL_cdrom.h b/code/SDL12/include/SDL_cdrom.h new file mode 100644 index 0000000..fff5cfa --- /dev/null +++ b/code/SDL12/include/SDL_cdrom.h @@ -0,0 +1,202 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_cdrom.h + * This is the CD-audio control API for Simple DirectMedia Layer + */ + +#ifndef _SDL_cdrom_h +#define _SDL_cdrom_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file SDL_cdrom.h + * In order to use these functions, SDL_Init() must have been called + * with the SDL_INIT_CDROM flag. This causes SDL to scan the system + * for CD-ROM drives, and load appropriate drivers. + */ + +/** The maximum number of CD-ROM tracks on a disk */ +#define SDL_MAX_TRACKS 99 + +/** @name Track Types + * The types of CD-ROM track possible + */ +/*@{*/ +#define SDL_AUDIO_TRACK 0x00 +#define SDL_DATA_TRACK 0x04 +/*@}*/ + +/** The possible states which a CD-ROM drive can be in. */ +typedef enum { + CD_TRAYEMPTY, + CD_STOPPED, + CD_PLAYING, + CD_PAUSED, + CD_ERROR = -1 +} CDstatus; + +/** Given a status, returns true if there's a disk in the drive */ +#define CD_INDRIVE(status) ((int)(status) > 0) + +typedef struct SDL_CDtrack { + Uint8 id; /**< Track number */ + Uint8 type; /**< Data or audio track */ + Uint16 unused; + Uint32 length; /**< Length, in frames, of this track */ + Uint32 offset; /**< Offset, in frames, from start of disk */ +} SDL_CDtrack; + +/** This structure is only current as of the last call to SDL_CDStatus() */ +typedef struct SDL_CD { + int id; /**< Private drive identifier */ + CDstatus status; /**< Current drive status */ + + /** The rest of this structure is only valid if there's a CD in drive */ + /*@{*/ + int numtracks; /**< Number of tracks on disk */ + int cur_track; /**< Current track position */ + int cur_frame; /**< Current frame offset within current track */ + SDL_CDtrack track[SDL_MAX_TRACKS+1]; + /*@}*/ +} SDL_CD; + +/** @name Frames / MSF Conversion Functions + * Conversion functions from frames to Minute/Second/Frames and vice versa + */ +/*@{*/ +#define CD_FPS 75 +#define FRAMES_TO_MSF(f, M,S,F) { \ + int value = f; \ + *(F) = value%CD_FPS; \ + value /= CD_FPS; \ + *(S) = value%60; \ + value /= 60; \ + *(M) = value; \ +} +#define MSF_TO_FRAMES(M, S, F) ((M)*60*CD_FPS+(S)*CD_FPS+(F)) +/*@}*/ + +/* CD-audio API functions: */ + +/** + * Returns the number of CD-ROM drives on the system, or -1 if + * SDL_Init() has not been called with the SDL_INIT_CDROM flag. + */ +extern DECLSPEC int SDLCALL SDL_CDNumDrives(void); + +/** + * Returns a human-readable, system-dependent identifier for the CD-ROM. + * Example: + * - "/dev/cdrom" + * - "E:" + * - "/dev/disk/ide/1/master" + */ +extern DECLSPEC const char * SDLCALL SDL_CDName(int drive); + +/** + * Opens a CD-ROM drive for access. It returns a drive handle on success, + * or NULL if the drive was invalid or busy. This newly opened CD-ROM + * becomes the default CD used when other CD functions are passed a NULL + * CD-ROM handle. + * Drives are numbered starting with 0. Drive 0 is the system default CD-ROM. + */ +extern DECLSPEC SDL_CD * SDLCALL SDL_CDOpen(int drive); + +/** + * This function returns the current status of the given drive. + * If the drive has a CD in it, the table of contents of the CD and current + * play position of the CD will be stored in the SDL_CD structure. + */ +extern DECLSPEC CDstatus SDLCALL SDL_CDStatus(SDL_CD *cdrom); + +/** + * Play the given CD starting at 'start_track' and 'start_frame' for 'ntracks' + * tracks and 'nframes' frames. If both 'ntrack' and 'nframe' are 0, play + * until the end of the CD. This function will skip data tracks. + * This function should only be called after calling SDL_CDStatus() to + * get track information about the CD. + * For example: + * @code + * // Play entire CD: + * if ( CD_INDRIVE(SDL_CDStatus(cdrom)) ) + * SDL_CDPlayTracks(cdrom, 0, 0, 0, 0); + * // Play last track: + * if ( CD_INDRIVE(SDL_CDStatus(cdrom)) ) { + * SDL_CDPlayTracks(cdrom, cdrom->numtracks-1, 0, 0, 0); + * } + * // Play first and second track and 10 seconds of third track: + * if ( CD_INDRIVE(SDL_CDStatus(cdrom)) ) + * SDL_CDPlayTracks(cdrom, 0, 0, 2, 10); + * @endcode + * + * @return This function returns 0, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_CDPlayTracks(SDL_CD *cdrom, + int start_track, int start_frame, int ntracks, int nframes); + +/** + * Play the given CD starting at 'start' frame for 'length' frames. + * @return It returns 0, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_CDPlay(SDL_CD *cdrom, int start, int length); + +/** Pause play + * @return returns 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_CDPause(SDL_CD *cdrom); + +/** Resume play + * @return returns 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_CDResume(SDL_CD *cdrom); + +/** Stop play + * @return returns 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_CDStop(SDL_CD *cdrom); + +/** Eject CD-ROM + * @return returns 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_CDEject(SDL_CD *cdrom); + +/** Closes the handle for the CD-ROM drive */ +extern DECLSPEC void SDLCALL SDL_CDClose(SDL_CD *cdrom); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_video_h */ diff --git a/code/SDL12/include/SDL_config.h b/code/SDL12/include/SDL_config.h new file mode 100644 index 0000000..a508101 --- /dev/null +++ b/code/SDL12/include/SDL_config.h @@ -0,0 +1,45 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_h +#define _SDL_config_h + +#include "SDL_platform.h" + +/* Add any platform that doesn't build using the configure system */ +#if defined(__DREAMCAST__) +#include "SDL_config_dreamcast.h" +#elif defined(__MACOS__) +#include "SDL_config_macos.h" +#elif defined(__MACOSX__) +#include "SDL_config_macosx.h" +#elif defined(__SYMBIAN32__) +#include "SDL_config_symbian.h" /* must be before win32! */ +#elif defined(__WIN32__) +#include "SDL_config_win32.h" +#elif defined(__OS2__) +#include "SDL_config_os2.h" +#else +#include "SDL_config_minimal.h" +#endif /* platform config */ + +#endif /* _SDL_config_h */ diff --git a/code/SDL12/include/SDL_config_amiga.h b/code/SDL12/include/SDL_config_amiga.h new file mode 100644 index 0000000..23e0861 --- /dev/null +++ b/code/SDL12/include/SDL_config_amiga.h @@ -0,0 +1,80 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2006 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_amiga_h +#define _SDL_config_amiga_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +#define SDL_HAS_64BIT_TYPE 1 + +/* Useful headers */ +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_AHI 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#define SDL_CDROM_DUMMY 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_AMIGA 1 + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_DUMMY 1 + +/* Enable various threading systems */ +#define SDL_THREAD_AMIGA 1 + +/* Enable various timer systems */ +#define SDL_TIMER_AMIGA 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_CYBERGRAPHICS 1 +#define SDL_VIDEO_DRIVER_DUMMY 1 + +/* Enable OpenGL support */ +#define SDL_VIDEO_OPENGL 1 + +#endif /* _SDL_config_amiga_h */ diff --git a/code/SDL12/include/SDL_config_dreamcast.h b/code/SDL12/include/SDL_config_dreamcast.h new file mode 100644 index 0000000..07c2f08 --- /dev/null +++ b/code/SDL12/include/SDL_config_dreamcast.h @@ -0,0 +1,106 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_dreamcast_h +#define _SDL_config_dreamcast_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; +typedef unsigned long uintptr_t; +#define SDL_HAS_64BIT_TYPE 1 + +/* Useful headers */ +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRDUP 1 +#define HAVE_INDEX 1 +#define HAVE_RINDEX 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRICMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_DC 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#define SDL_CDROM_DC 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_DC 1 + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_DUMMY 1 + +/* Enable various threading systems */ +#define SDL_THREAD_DC 1 + +/* Enable various timer systems */ +#define SDL_TIMER_DC 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_DC 1 +#define SDL_VIDEO_DRIVER_DUMMY 1 + +#endif /* _SDL_config_dreamcast_h */ diff --git a/code/SDL12/include/SDL_config_macos.h b/code/SDL12/include/SDL_config_macos.h new file mode 100644 index 0000000..4ba5c22 --- /dev/null +++ b/code/SDL12/include/SDL_config_macos.h @@ -0,0 +1,112 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_macos_h +#define _SDL_config_macos_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +#include + +typedef SInt8 int8_t; +typedef UInt8 uint8_t; +typedef SInt16 int16_t; +typedef UInt16 uint16_t; +typedef SInt32 int32_t; +typedef UInt32 uint32_t; +typedef SInt64 int64_t; +typedef UInt64 uint64_t; +typedef unsigned long uintptr_t; + +#define SDL_HAS_64BIT_TYPE 1 + +/* Useful headers */ +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_ABS 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_ITOA 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_SSCANF 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_SNDMGR 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#if TARGET_API_MAC_CARBON +#define SDL_CDROM_DUMMY 1 +#else +#define SDL_CDROM_MACOS 1 +#endif + +/* Enable various input drivers */ +#if TARGET_API_MAC_CARBON +#define SDL_JOYSTICK_DUMMY 1 +#else +#define SDL_JOYSTICK_MACOS 1 +#endif + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_MACOS 1 + +/* Enable various threading systems */ +#define SDL_THREADS_DISABLED 1 + +/* Enable various timer systems */ +#define SDL_TIMER_MACOS 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_DRAWSPROCKET 1 +#define SDL_VIDEO_DRIVER_TOOLBOX 1 + +/* Enable OpenGL support */ +#define SDL_VIDEO_OPENGL 1 + +#endif /* _SDL_config_macos_h */ diff --git a/code/SDL12/include/SDL_config_macosx.h b/code/SDL12/include/SDL_config_macosx.h new file mode 100644 index 0000000..295b872 --- /dev/null +++ b/code/SDL12/include/SDL_config_macosx.h @@ -0,0 +1,150 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_macosx_h +#define _SDL_config_macosx_h + +#include "SDL_platform.h" + +/* This gets us MAC_OS_X_VERSION_MIN_REQUIRED... */ +#include + +/* This is a set of defines to configure the SDL features */ + +#define SDL_HAS_64BIT_TYPE 1 + +/* Useful headers */ +/* If we specified an SDK or have a post-PowerPC chip, then alloca.h exists. */ +#if ( (MAC_OS_X_VERSION_MIN_REQUIRED >= 1030) || (!defined(__POWERPC__)) ) +#define HAVE_ALLOCA_H 1 +#endif +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_SIGACTION 1 +#define HAVE_SETJMP 1 +#define HAVE_NANOSLEEP 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_COREAUDIO 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#define SDL_CDROM_MACOSX 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_IOKIT 1 + +/* Enable various shared object loading systems */ +#ifdef __ppc__ +/* For Mac OS X 10.2 compatibility */ +#define SDL_LOADSO_DLCOMPAT 1 +#else +#define SDL_LOADSO_DLOPEN 1 +#endif + +/* Enable various threading systems */ +#define SDL_THREAD_PTHREAD 1 +#define SDL_THREAD_PTHREAD_RECURSIVE_MUTEX 1 + +/* Enable various timer systems */ +#define SDL_TIMER_UNIX 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +#if ((defined TARGET_API_MAC_CARBON) && (TARGET_API_MAC_CARBON)) +#define SDL_VIDEO_DRIVER_TOOLBOX 1 +#else +#define SDL_VIDEO_DRIVER_QUARTZ 1 +#endif +#define SDL_VIDEO_DRIVER_DGA 1 +#define SDL_VIDEO_DRIVER_X11 1 +#define SDL_VIDEO_DRIVER_X11_DGAMOUSE 1 +#define SDL_VIDEO_DRIVER_X11_DYNAMIC "/usr/X11R6/lib/libX11.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XEXT "/usr/X11R6/lib/libXext.6.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRANDR "/usr/X11R6/lib/libXrandr.2.dylib" +#define SDL_VIDEO_DRIVER_X11_DYNAMIC_XRENDER "/usr/X11R6/lib/libXrender.1.dylib" +#define SDL_VIDEO_DRIVER_X11_VIDMODE 1 +#define SDL_VIDEO_DRIVER_X11_XINERAMA 1 +#define SDL_VIDEO_DRIVER_X11_XME 1 +#define SDL_VIDEO_DRIVER_X11_XRANDR 1 +#define SDL_VIDEO_DRIVER_X11_XV 1 + +/* Enable OpenGL support */ +#define SDL_VIDEO_OPENGL 1 +#define SDL_VIDEO_OPENGL_GLX 1 + +/* Disable screensaver */ +#define SDL_VIDEO_DISABLE_SCREENSAVER 1 + +/* Enable assembly routines */ +#define SDL_ASSEMBLY_ROUTINES 1 +#ifdef __ppc__ +#define SDL_ALTIVEC_BLITTERS 1 +#endif + +#endif /* _SDL_config_macosx_h */ diff --git a/code/SDL12/include/SDL_config_minimal.h b/code/SDL12/include/SDL_config_minimal.h new file mode 100644 index 0000000..002c56e --- /dev/null +++ b/code/SDL12/include/SDL_config_minimal.h @@ -0,0 +1,62 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_minimal_h +#define _SDL_config_minimal_h + +#include "SDL_platform.h" + +/* This is the minimal configuration that can be used to build SDL */ + +#include + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef unsigned int size_t; +typedef unsigned long uintptr_t; + +/* Enable the dummy audio driver (src/audio/dummy/\*.c) */ +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable the stub cdrom driver (src/cdrom/dummy/\*.c) */ +#define SDL_CDROM_DISABLED 1 + +/* Enable the stub joystick driver (src/joystick/dummy/\*.c) */ +#define SDL_JOYSTICK_DISABLED 1 + +/* Enable the stub shared object loader (src/loadso/dummy/\*.c) */ +#define SDL_LOADSO_DISABLED 1 + +/* Enable the stub thread support (src/thread/generic/\*.c) */ +#define SDL_THREADS_DISABLED 1 + +/* Enable the stub timer support (src/timer/dummy/\*.c) */ +#define SDL_TIMERS_DISABLED 1 + +/* Enable the dummy video driver (src/video/dummy/\*.c) */ +#define SDL_VIDEO_DRIVER_DUMMY 1 + +#endif /* _SDL_config_minimal_h */ diff --git a/code/SDL12/include/SDL_config_nds.h b/code/SDL12/include/SDL_config_nds.h new file mode 100644 index 0000000..4ac60a5 --- /dev/null +++ b/code/SDL12/include/SDL_config_nds.h @@ -0,0 +1,115 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_nds_h +#define _SDL_config_nds_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +/* General platform specific identifiers */ +#include "SDL_platform.h" + +/* C datatypes */ +#define SDL_HAS_64BIT_TYPE 1 + +/* Endianness */ +#define SDL_BYTEORDER 1234 + +/* Useful headers */ +#define HAVE_ALLOCA_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_STRING_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_ICONV_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOULL 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_SETJMP 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_NDS 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable the stub cdrom driver (src/cdrom/dummy/\*.c) */ +#define SDL_CDROM_DISABLED 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_NDS 1 + +/* Enable the stub shared object loader (src/loadso/dummy/\*.c) */ +#define SDL_LOADSO_DISABLED 1 + +/* Enable the stub thread support (src/thread/generic/\*.c) */ +#define SDL_THREADS_DISABLED 1 + +/* Enable various timer systems */ +#define SDL_TIMER_NDS 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_NDS 1 +#define SDL_VIDEO_DRIVER_DUMMY 1 + +#endif /* _SDL_config_nds_h */ diff --git a/code/SDL12/include/SDL_config_os2.h b/code/SDL12/include/SDL_config_os2.h new file mode 100644 index 0000000..bb40df0 --- /dev/null +++ b/code/SDL12/include/SDL_config_os2.h @@ -0,0 +1,141 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_os2_h +#define _SDL_config_os2_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef unsigned int size_t; +typedef unsigned long uintptr_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; + +#define SDL_HAS_64BIT_TYPE 1 + +/* Use Watcom's LIBC */ +#define HAVE_LIBC 1 + +/* Useful headers */ +#define HAVE_SYS_TYPES_H 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STDARG_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_MEMORY_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#define HAVE_SIGNAL_H 1 + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_GETENV 1 +#define HAVE_PUTENV 1 +#define HAVE_UNSETENV 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_BCOPY 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE_STRLCPY 1 +#define HAVE_STRLCAT 1 +#define HAVE_STRDUP 1 +#define HAVE__STRREV 1 +#define HAVE__STRUPR 1 +#define HAVE__STRLWR 1 +#define HAVE_INDEX 1 +#define HAVE_RINDEX 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_ITOA 1 +#define HAVE__LTOA 1 +#define HAVE__UITOA 1 +#define HAVE__ULTOA 1 +#define HAVE_STRTOL 1 +#define HAVE__I64TOA 1 +#define HAVE__UI64TOA 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE_STRICMP 1 +#define HAVE_STRCASECMP 1 +#define HAVE_SSCANF 1 +#define HAVE_SNPRINTF 1 +#define HAVE_VSNPRINTF 1 +#define HAVE_SETJMP 1 +#define HAVE_CLOCK_GETTIME 1 + +/* Enable various audio drivers */ +#define SDL_AUDIO_DRIVER_DART 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#define SDL_CDROM_OS2 1 + +/* Enable various input drivers */ +#define SDL_JOYSTICK_OS2 1 + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_OS2 1 + +/* Enable various threading systems */ +#define SDL_THREAD_OS2 1 + +/* Enable various timer systems */ +#define SDL_TIMER_OS2 1 + +/* Enable various video drivers */ +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_OS2FS 1 + +/* Enable OpenGL support */ +/* Nothing here yet for OS/2... :( */ + +/* Enable assembly routines where available */ +#define SDL_ASSEMBLY_ROUTINES 1 + +#endif /* _SDL_config_os2_h */ diff --git a/code/SDL12/include/SDL_config_symbian.h b/code/SDL12/include/SDL_config_symbian.h new file mode 100644 index 0000000..53527b2 --- /dev/null +++ b/code/SDL12/include/SDL_config_symbian.h @@ -0,0 +1,146 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/* + +Symbian version Markus Mertama + +*/ + + +#ifndef _SDL_CONFIG_SYMBIAN_H +#define _SDL_CONFIG_SYMBIAN_H + +#include "SDL_platform.h" + +/* This is the minimal configuration that can be used to build SDL */ + + +#include +#include + + +#ifdef __GCCE__ +#define SYMBIAN32_GCCE +#endif + +#ifndef _SIZE_T_DEFINED +typedef unsigned int size_t; +#endif + +#ifndef _INTPTR_T_DECLARED +typedef unsigned int uintptr_t; +#endif + +#ifndef _INT8_T_DECLARED +typedef signed char int8_t; +#endif + +#ifndef _UINT8_T_DECLARED +typedef unsigned char uint8_t; +#endif + +#ifndef _INT16_T_DECLARED +typedef signed short int16_t; +#endif + +#ifndef _UINT16_T_DECLARED +typedef unsigned short uint16_t; +#endif + +#ifndef _INT32_T_DECLARED +typedef signed int int32_t; +#endif + +#ifndef _UINT32_T_DECLARED +typedef unsigned int uint32_t; +#endif + +#ifndef _INT64_T_DECLARED +typedef signed long long int64_t; +#endif + +#ifndef _UINT64_T_DECLARED +typedef unsigned long long uint64_t; +#endif + +#define SDL_AUDIO_DRIVER_EPOCAUDIO 1 + + +/* Enable the stub cdrom driver (src/cdrom/dummy/\*.c) */ +#define SDL_CDROM_DISABLED 1 + +/* Enable the stub joystick driver (src/joystick/dummy/\*.c) */ +#define SDL_JOYSTICK_DISABLED 1 + +/* Enable the stub shared object loader (src/loadso/dummy/\*.c) */ +#define SDL_LOADSO_DISABLED 1 + +#define SDL_THREAD_SYMBIAN 1 + +#define SDL_VIDEO_DRIVER_EPOC 1 + +#define SDL_VIDEO_OPENGL 0 + +#define SDL_HAS_64BIT_TYPE 1 + +#define HAVE_LIBC 1 +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 + +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +/*#define HAVE_ALLOCA 1*/ +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE__STRUPR 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_ITOA 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +/*#define HAVE__STRICMP 1*/ +#define HAVE__STRNICMP 1 +#define HAVE_SSCANF 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 + + + +#endif /* _SDL_CONFIG_SYMBIAN_H */ diff --git a/code/SDL12/include/SDL_config_win32.h b/code/SDL12/include/SDL_config_win32.h new file mode 100644 index 0000000..6d019a8 --- /dev/null +++ b/code/SDL12/include/SDL_config_win32.h @@ -0,0 +1,183 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_config_win32_h +#define _SDL_config_win32_h + +#include "SDL_platform.h" + +/* This is a set of defines to configure the SDL features */ + +#if defined(__GNUC__) || defined(__DMC__) +#define HAVE_STDINT_H 1 +#elif defined(_MSC_VER) +typedef signed __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef signed __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef signed __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef signed __int64 int64_t; +typedef unsigned __int64 uint64_t; +#ifndef _UINTPTR_T_DEFINED +#ifdef _WIN64 +typedef unsigned __int64 uintptr_t; +#else +typedef unsigned int uintptr_t; +#endif +#define _UINTPTR_T_DEFINED +#endif +/* Older Visual C++ headers don't have the Win64-compatible typedefs... */ +#if ((_MSC_VER <= 1200) && (!defined(DWORD_PTR))) +#define DWORD_PTR DWORD +#endif +#if ((_MSC_VER <= 1200) && (!defined(LONG_PTR))) +#define LONG_PTR LONG +#endif +#else /* !__GNUC__ && !_MSC_VER */ +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef signed short int16_t; +typedef unsigned short uint16_t; +typedef signed int int32_t; +typedef unsigned int uint32_t; +typedef signed long long int64_t; +typedef unsigned long long uint64_t; +#ifndef _SIZE_T_DEFINED_ +#define _SIZE_T_DEFINED_ +typedef unsigned int size_t; +#endif +typedef unsigned int uintptr_t; +#endif /* __GNUC__ || _MSC_VER */ +#define SDL_HAS_64BIT_TYPE 1 + +/* Enabled for SDL 1.2 (binary compatibility) */ +#define HAVE_LIBC 1 +#ifdef HAVE_LIBC +/* Useful headers */ +#define HAVE_STDIO_H 1 +#define STDC_HEADERS 1 +#define HAVE_STRING_H 1 +#define HAVE_CTYPE_H 1 +#define HAVE_MATH_H 1 +#ifndef _WIN32_WCE +#define HAVE_SIGNAL_H 1 +#endif + +/* C library functions */ +#define HAVE_MALLOC 1 +#define HAVE_CALLOC 1 +#define HAVE_REALLOC 1 +#define HAVE_FREE 1 +#define HAVE_ALLOCA 1 +#define HAVE_QSORT 1 +#define HAVE_ABS 1 +#define HAVE_MEMSET 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMCMP 1 +#define HAVE_STRLEN 1 +#define HAVE__STRREV 1 +#define HAVE__STRUPR 1 +#define HAVE__STRLWR 1 +#define HAVE_STRCHR 1 +#define HAVE_STRRCHR 1 +#define HAVE_STRSTR 1 +#define HAVE_ITOA 1 +#define HAVE__LTOA 1 +#define HAVE__ULTOA 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRTOLL 1 +#define HAVE_STRTOD 1 +#define HAVE_ATOI 1 +#define HAVE_ATOF 1 +#define HAVE_STRCMP 1 +#define HAVE_STRNCMP 1 +#define HAVE__STRICMP 1 +#define HAVE__STRNICMP 1 +#define HAVE_SSCANF 1 +#else +#define HAVE_STDARG_H 1 +#define HAVE_STDDEF_H 1 +#endif + +/* Enable various audio drivers */ +#ifndef _WIN32_WCE +#define SDL_AUDIO_DRIVER_DSOUND 1 +#endif +#define SDL_AUDIO_DRIVER_WAVEOUT 1 +#define SDL_AUDIO_DRIVER_DISK 1 +#define SDL_AUDIO_DRIVER_DUMMY 1 + +/* Enable various cdrom drivers */ +#ifdef _WIN32_WCE +#define SDL_CDROM_DISABLED 1 +#else +#define SDL_CDROM_WIN32 1 +#endif + +/* Enable various input drivers */ +#ifdef _WIN32_WCE +#define SDL_JOYSTICK_DISABLED 1 +#else +#define SDL_JOYSTICK_WINMM 1 +#endif + +/* Enable various shared object loading systems */ +#define SDL_LOADSO_WIN32 1 + +/* Enable various threading systems */ +#define SDL_THREAD_WIN32 1 + +/* Enable various timer systems */ +#ifdef _WIN32_WCE +#define SDL_TIMER_WINCE 1 +#else +#define SDL_TIMER_WIN32 1 +#endif + +/* Enable various video drivers */ +#ifdef _WIN32_WCE +#define SDL_VIDEO_DRIVER_GAPI 1 +#endif +#ifndef _WIN32_WCE +#define SDL_VIDEO_DRIVER_DDRAW 1 +#endif +#define SDL_VIDEO_DRIVER_DUMMY 1 +#define SDL_VIDEO_DRIVER_WINDIB 1 + +/* Enable OpenGL support */ +#ifndef _WIN32_WCE +#define SDL_VIDEO_OPENGL 1 +#define SDL_VIDEO_OPENGL_WGL 1 +#endif + +/* Disable screensaver */ +#define SDL_VIDEO_DISABLE_SCREENSAVER 1 + +/* Enable assembly routines (Win64 doesn't have inline asm) */ +#ifndef _WIN64 +#define SDL_ASSEMBLY_ROUTINES 1 +#endif + +#endif /* _SDL_config_win32_h */ diff --git a/code/SDL12/include/SDL_copying.h b/code/SDL12/include/SDL_copying.h new file mode 100644 index 0000000..1bd6b84 --- /dev/null +++ b/code/SDL12/include/SDL_copying.h @@ -0,0 +1,22 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + diff --git a/code/SDL12/include/SDL_cpuinfo.h b/code/SDL12/include/SDL_cpuinfo.h new file mode 100644 index 0000000..f4be8e0 --- /dev/null +++ b/code/SDL12/include/SDL_cpuinfo.h @@ -0,0 +1,69 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_cpuinfo.h + * CPU feature detection for SDL + */ + +#ifndef _SDL_cpuinfo_h +#define _SDL_cpuinfo_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** This function returns true if the CPU has the RDTSC instruction */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasRDTSC(void); + +/** This function returns true if the CPU has MMX features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasMMX(void); + +/** This function returns true if the CPU has MMX Ext. features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasMMXExt(void); + +/** This function returns true if the CPU has 3DNow features */ +extern DECLSPEC SDL_bool SDLCALL SDL_Has3DNow(void); + +/** This function returns true if the CPU has 3DNow! Ext. features */ +extern DECLSPEC SDL_bool SDLCALL SDL_Has3DNowExt(void); + +/** This function returns true if the CPU has SSE features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE(void); + +/** This function returns true if the CPU has SSE2 features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasSSE2(void); + +/** This function returns true if the CPU has AltiVec features */ +extern DECLSPEC SDL_bool SDLCALL SDL_HasAltiVec(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_cpuinfo_h */ diff --git a/code/SDL12/include/SDL_endian.h b/code/SDL12/include/SDL_endian.h new file mode 100644 index 0000000..f7a2e2f --- /dev/null +++ b/code/SDL12/include/SDL_endian.h @@ -0,0 +1,209 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_endian.h + * Functions for reading and writing endian-specific values + */ + +#ifndef _SDL_endian_h +#define _SDL_endian_h + +#include "SDL_stdinc.h" + +/** @name SDL_ENDIANs + * The two types of endianness + */ +/*@{*/ +#define SDL_LIL_ENDIAN 1234 +#define SDL_BIG_ENDIAN 4321 +/*@}*/ + +#ifndef SDL_BYTEORDER /* Not defined in SDL_config.h? */ +#if defined(__hppa__) || \ + defined(__m68k__) || defined(mc68000) || defined(_M_M68K) || \ + (defined(__MIPS__) && defined(__MISPEB__)) || \ + defined(__ppc__) || defined(__POWERPC__) || defined(_M_PPC) || \ + defined(__sparc__) +#define SDL_BYTEORDER SDL_BIG_ENDIAN +#else +#define SDL_BYTEORDER SDL_LIL_ENDIAN +#endif +#endif /* !SDL_BYTEORDER */ + + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name SDL_Swap Functions + * Use inline functions for compilers that support them, and static + * functions for those that do not. Because these functions become + * static for compilers that do not support inline functions, this + * header should only be included in files that actually use them. + */ +/*@{*/ +#if defined(__GNUC__) && defined(__i386__) && \ + !(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */) +static __inline__ Uint16 SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0" : "=q" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && defined(__x86_64__) +static __inline__ Uint16 SDL_Swap16(Uint16 x) +{ + __asm__("xchgb %b0,%h0" : "=Q" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +static __inline__ Uint16 SDL_Swap16(Uint16 x) +{ + Uint16 result; + + __asm__("rlwimi %0,%2,8,16,23" : "=&r" (result) : "0" (x >> 8), "r" (x)); + return result; +} +#elif defined(__GNUC__) && (defined(__M68000__) || defined(__M68020__)) +static __inline__ Uint16 SDL_Swap16(Uint16 x) +{ + __asm__("rorw #8,%0" : "=d" (x) : "0" (x) : "cc"); + return x; +} +#else +static __inline__ Uint16 SDL_Swap16(Uint16 x) { + return((x<<8)|(x>>8)); +} +#endif + +#if defined(__GNUC__) && defined(__i386__) && \ + !(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */) +static __inline__ Uint32 SDL_Swap32(Uint32 x) +{ + __asm__("bswap %0" : "=r" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && defined(__x86_64__) +static __inline__ Uint32 SDL_Swap32(Uint32 x) +{ + __asm__("bswapl %0" : "=r" (x) : "0" (x)); + return x; +} +#elif defined(__GNUC__) && (defined(__powerpc__) || defined(__ppc__)) +static __inline__ Uint32 SDL_Swap32(Uint32 x) +{ + Uint32 result; + + __asm__("rlwimi %0,%2,24,16,23" : "=&r" (result) : "0" (x>>24), "r" (x)); + __asm__("rlwimi %0,%2,8,8,15" : "=&r" (result) : "0" (result), "r" (x)); + __asm__("rlwimi %0,%2,24,0,7" : "=&r" (result) : "0" (result), "r" (x)); + return result; +} +#elif defined(__GNUC__) && (defined(__M68000__) || defined(__M68020__)) +static __inline__ Uint32 SDL_Swap32(Uint32 x) +{ + __asm__("rorw #8,%0\n\tswap %0\n\trorw #8,%0" : "=d" (x) : "0" (x) : "cc"); + return x; +} +#else +static __inline__ Uint32 SDL_Swap32(Uint32 x) { + return((x<<24)|((x<<8)&0x00FF0000)|((x>>8)&0x0000FF00)|(x>>24)); +} +#endif + +#ifdef SDL_HAS_64BIT_TYPE +#if defined(__GNUC__) && defined(__i386__) && \ + !(__GNUC__ == 2 && __GNUC_MINOR__ <= 95 /* broken gcc version */) +static __inline__ Uint64 SDL_Swap64(Uint64 x) +{ + union { + struct { Uint32 a,b; } s; + Uint64 u; + } v; + v.u = x; + __asm__("bswapl %0 ; bswapl %1 ; xchgl %0,%1" + : "=r" (v.s.a), "=r" (v.s.b) + : "0" (v.s.a), "1" (v.s.b)); + return v.u; +} +#elif defined(__GNUC__) && defined(__x86_64__) +static __inline__ Uint64 SDL_Swap64(Uint64 x) +{ + __asm__("bswapq %0" : "=r" (x) : "0" (x)); + return x; +} +#else +static __inline__ Uint64 SDL_Swap64(Uint64 x) +{ + Uint32 hi, lo; + + /* Separate into high and low 32-bit values and swap them */ + lo = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x >>= 32; + hi = SDL_static_cast(Uint32, x & 0xFFFFFFFF); + x = SDL_Swap32(lo); + x <<= 32; + x |= SDL_Swap32(hi); + return(x); +} +#endif +#else +/* This is mainly to keep compilers from complaining in SDL code. + * If there is no real 64-bit datatype, then compilers will complain about + * the fake 64-bit datatype that SDL provides when it compiles user code. + */ +#define SDL_Swap64(X) (X) +#endif /* SDL_HAS_64BIT_TYPE */ +/*@}*/ + +/** + * @name SDL_SwapLE and SDL_SwapBE Functions + * Byteswap item from the specified endianness to the native endianness + */ +/*@{*/ +#if SDL_BYTEORDER == SDL_LIL_ENDIAN +#define SDL_SwapLE16(X) (X) +#define SDL_SwapLE32(X) (X) +#define SDL_SwapLE64(X) (X) +#define SDL_SwapBE16(X) SDL_Swap16(X) +#define SDL_SwapBE32(X) SDL_Swap32(X) +#define SDL_SwapBE64(X) SDL_Swap64(X) +#else +#define SDL_SwapLE16(X) SDL_Swap16(X) +#define SDL_SwapLE32(X) SDL_Swap32(X) +#define SDL_SwapLE64(X) SDL_Swap64(X) +#define SDL_SwapBE16(X) (X) +#define SDL_SwapBE32(X) (X) +#define SDL_SwapBE64(X) (X) +#endif +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_endian_h */ diff --git a/code/SDL12/include/SDL_error.h b/code/SDL12/include/SDL_error.h new file mode 100644 index 0000000..b103703 --- /dev/null +++ b/code/SDL12/include/SDL_error.h @@ -0,0 +1,72 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_error.h + * Simple error message routines for SDL + */ + +#ifndef _SDL_error_h +#define _SDL_error_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @name Public functions + */ +/*@{*/ +extern DECLSPEC void SDLCALL SDL_SetError(const char *fmt, ...); +extern DECLSPEC char * SDLCALL SDL_GetError(void); +extern DECLSPEC void SDLCALL SDL_ClearError(void); +/*@}*/ + +/** + * @name Private functions + * @internal Private error message function - used internally + */ +/*@{*/ +#define SDL_OutOfMemory() SDL_Error(SDL_ENOMEM) +#define SDL_Unsupported() SDL_Error(SDL_UNSUPPORTED) +typedef enum { + SDL_ENOMEM, + SDL_EFREAD, + SDL_EFWRITE, + SDL_EFSEEK, + SDL_UNSUPPORTED, + SDL_LASTERROR +} SDL_errorcode; +extern DECLSPEC void SDLCALL SDL_Error(SDL_errorcode code); +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_error_h */ diff --git a/code/SDL12/include/SDL_events.h b/code/SDL12/include/SDL_events.h new file mode 100644 index 0000000..c94a30c --- /dev/null +++ b/code/SDL12/include/SDL_events.h @@ -0,0 +1,356 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file SDL_events.h + * Include file for SDL event handling + */ + +#ifndef _SDL_events_h +#define _SDL_events_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_active.h" +#include "SDL_keyboard.h" +#include "SDL_mouse.h" +#include "SDL_joystick.h" +#include "SDL_quit.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @name General keyboard/mouse state definitions */ +/*@{*/ +#define SDL_RELEASED 0 +#define SDL_PRESSED 1 +/*@}*/ + +/** Event enumerations */ +typedef enum { + SDL_NOEVENT = 0, /**< Unused (do not remove) */ + SDL_ACTIVEEVENT, /**< Application loses/gains visibility */ + SDL_KEYDOWN, /**< Keys pressed */ + SDL_KEYUP, /**< Keys released */ + SDL_MOUSEMOTION, /**< Mouse moved */ + SDL_MOUSEBUTTONDOWN, /**< Mouse button pressed */ + SDL_MOUSEBUTTONUP, /**< Mouse button released */ + SDL_JOYAXISMOTION, /**< Joystick axis motion */ + SDL_JOYBALLMOTION, /**< Joystick trackball motion */ + SDL_JOYHATMOTION, /**< Joystick hat position change */ + SDL_JOYBUTTONDOWN, /**< Joystick button pressed */ + SDL_JOYBUTTONUP, /**< Joystick button released */ + SDL_QUIT, /**< User-requested quit */ + SDL_SYSWMEVENT, /**< System specific event */ + SDL_EVENT_RESERVEDA, /**< Reserved for future use.. */ + SDL_EVENT_RESERVEDB, /**< Reserved for future use.. */ + SDL_VIDEORESIZE, /**< User resized video mode */ + SDL_VIDEOEXPOSE, /**< Screen needs to be redrawn */ + SDL_EVENT_RESERVED2, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED3, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED4, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED5, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED6, /**< Reserved for future use.. */ + SDL_EVENT_RESERVED7, /**< Reserved for future use.. */ + /** Events SDL_USEREVENT through SDL_MAXEVENTS-1 are for your use */ + SDL_USEREVENT = 24, + /** This last event is only for bounding internal arrays + * It is the number of bits in the event mask datatype -- Uint32 + */ + SDL_NUMEVENTS = 32 +} SDL_EventType; + +/** @name Predefined event masks */ +/*@{*/ +#define SDL_EVENTMASK(X) (1<<(X)) +typedef enum { + SDL_ACTIVEEVENTMASK = SDL_EVENTMASK(SDL_ACTIVEEVENT), + SDL_KEYDOWNMASK = SDL_EVENTMASK(SDL_KEYDOWN), + SDL_KEYUPMASK = SDL_EVENTMASK(SDL_KEYUP), + SDL_KEYEVENTMASK = SDL_EVENTMASK(SDL_KEYDOWN)| + SDL_EVENTMASK(SDL_KEYUP), + SDL_MOUSEMOTIONMASK = SDL_EVENTMASK(SDL_MOUSEMOTION), + SDL_MOUSEBUTTONDOWNMASK = SDL_EVENTMASK(SDL_MOUSEBUTTONDOWN), + SDL_MOUSEBUTTONUPMASK = SDL_EVENTMASK(SDL_MOUSEBUTTONUP), + SDL_MOUSEEVENTMASK = SDL_EVENTMASK(SDL_MOUSEMOTION)| + SDL_EVENTMASK(SDL_MOUSEBUTTONDOWN)| + SDL_EVENTMASK(SDL_MOUSEBUTTONUP), + SDL_JOYAXISMOTIONMASK = SDL_EVENTMASK(SDL_JOYAXISMOTION), + SDL_JOYBALLMOTIONMASK = SDL_EVENTMASK(SDL_JOYBALLMOTION), + SDL_JOYHATMOTIONMASK = SDL_EVENTMASK(SDL_JOYHATMOTION), + SDL_JOYBUTTONDOWNMASK = SDL_EVENTMASK(SDL_JOYBUTTONDOWN), + SDL_JOYBUTTONUPMASK = SDL_EVENTMASK(SDL_JOYBUTTONUP), + SDL_JOYEVENTMASK = SDL_EVENTMASK(SDL_JOYAXISMOTION)| + SDL_EVENTMASK(SDL_JOYBALLMOTION)| + SDL_EVENTMASK(SDL_JOYHATMOTION)| + SDL_EVENTMASK(SDL_JOYBUTTONDOWN)| + SDL_EVENTMASK(SDL_JOYBUTTONUP), + SDL_VIDEORESIZEMASK = SDL_EVENTMASK(SDL_VIDEORESIZE), + SDL_VIDEOEXPOSEMASK = SDL_EVENTMASK(SDL_VIDEOEXPOSE), + SDL_QUITMASK = SDL_EVENTMASK(SDL_QUIT), + SDL_SYSWMEVENTMASK = SDL_EVENTMASK(SDL_SYSWMEVENT) +} SDL_EventMask ; +#define SDL_ALLEVENTS 0xFFFFFFFF +/*@}*/ + +/** Application visibility event structure */ +typedef struct SDL_ActiveEvent { + Uint8 type; /**< SDL_ACTIVEEVENT */ + Uint8 gain; /**< Whether given states were gained or lost (1/0) */ + Uint8 state; /**< A mask of the focus states */ +} SDL_ActiveEvent; + +/** Keyboard event structure */ +typedef struct SDL_KeyboardEvent { + Uint8 type; /**< SDL_KEYDOWN or SDL_KEYUP */ + Uint8 which; /**< The keyboard device index */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ + SDL_keysym keysym; +} SDL_KeyboardEvent; + +/** Mouse motion event structure */ +typedef struct SDL_MouseMotionEvent { + Uint8 type; /**< SDL_MOUSEMOTION */ + Uint8 which; /**< The mouse device index */ + Uint8 state; /**< The current button state */ + Uint16 x, y; /**< The X/Y coordinates of the mouse */ + Sint16 xrel; /**< The relative motion in the X direction */ + Sint16 yrel; /**< The relative motion in the Y direction */ +} SDL_MouseMotionEvent; + +/** Mouse button event structure */ +typedef struct SDL_MouseButtonEvent { + Uint8 type; /**< SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP */ + Uint8 which; /**< The mouse device index */ + Uint8 button; /**< The mouse button index */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ + Uint16 x, y; /**< The X/Y coordinates of the mouse at press time */ +} SDL_MouseButtonEvent; + +/** Joystick axis motion event structure */ +typedef struct SDL_JoyAxisEvent { + Uint8 type; /**< SDL_JOYAXISMOTION */ + Uint8 which; /**< The joystick device index */ + Uint8 axis; /**< The joystick axis index */ + Sint16 value; /**< The axis value (range: -32768 to 32767) */ +} SDL_JoyAxisEvent; + +/** Joystick trackball motion event structure */ +typedef struct SDL_JoyBallEvent { + Uint8 type; /**< SDL_JOYBALLMOTION */ + Uint8 which; /**< The joystick device index */ + Uint8 ball; /**< The joystick trackball index */ + Sint16 xrel; /**< The relative motion in the X direction */ + Sint16 yrel; /**< The relative motion in the Y direction */ +} SDL_JoyBallEvent; + +/** Joystick hat position change event structure */ +typedef struct SDL_JoyHatEvent { + Uint8 type; /**< SDL_JOYHATMOTION */ + Uint8 which; /**< The joystick device index */ + Uint8 hat; /**< The joystick hat index */ + Uint8 value; /**< The hat position value: + * SDL_HAT_LEFTUP SDL_HAT_UP SDL_HAT_RIGHTUP + * SDL_HAT_LEFT SDL_HAT_CENTERED SDL_HAT_RIGHT + * SDL_HAT_LEFTDOWN SDL_HAT_DOWN SDL_HAT_RIGHTDOWN + * Note that zero means the POV is centered. + */ +} SDL_JoyHatEvent; + +/** Joystick button event structure */ +typedef struct SDL_JoyButtonEvent { + Uint8 type; /**< SDL_JOYBUTTONDOWN or SDL_JOYBUTTONUP */ + Uint8 which; /**< The joystick device index */ + Uint8 button; /**< The joystick button index */ + Uint8 state; /**< SDL_PRESSED or SDL_RELEASED */ +} SDL_JoyButtonEvent; + +/** The "window resized" event + * When you get this event, you are responsible for setting a new video + * mode with the new width and height. + */ +typedef struct SDL_ResizeEvent { + Uint8 type; /**< SDL_VIDEORESIZE */ + int w; /**< New width */ + int h; /**< New height */ +} SDL_ResizeEvent; + +/** The "screen redraw" event */ +typedef struct SDL_ExposeEvent { + Uint8 type; /**< SDL_VIDEOEXPOSE */ +} SDL_ExposeEvent; + +/** The "quit requested" event */ +typedef struct SDL_QuitEvent { + Uint8 type; /**< SDL_QUIT */ +} SDL_QuitEvent; + +/** A user-defined event type */ +typedef struct SDL_UserEvent { + Uint8 type; /**< SDL_USEREVENT through SDL_NUMEVENTS-1 */ + int code; /**< User defined event code */ + void *data1; /**< User defined data pointer */ + void *data2; /**< User defined data pointer */ +} SDL_UserEvent; + +/** If you want to use this event, you should include SDL_syswm.h */ +struct SDL_SysWMmsg; +typedef struct SDL_SysWMmsg SDL_SysWMmsg; +typedef struct SDL_SysWMEvent { + Uint8 type; + SDL_SysWMmsg *msg; +} SDL_SysWMEvent; + +/** General event structure */ +typedef union SDL_Event { + Uint8 type; + SDL_ActiveEvent active; + SDL_KeyboardEvent key; + SDL_MouseMotionEvent motion; + SDL_MouseButtonEvent button; + SDL_JoyAxisEvent jaxis; + SDL_JoyBallEvent jball; + SDL_JoyHatEvent jhat; + SDL_JoyButtonEvent jbutton; + SDL_ResizeEvent resize; + SDL_ExposeEvent expose; + SDL_QuitEvent quit; + SDL_UserEvent user; + SDL_SysWMEvent syswm; +} SDL_Event; + + +/* Function prototypes */ + +/** Pumps the event loop, gathering events from the input devices. + * This function updates the event queue and internal input device state. + * This should only be run in the thread that sets the video mode. + */ +extern DECLSPEC void SDLCALL SDL_PumpEvents(void); + +typedef enum { + SDL_ADDEVENT, + SDL_PEEKEVENT, + SDL_GETEVENT +} SDL_eventaction; + +/** + * Checks the event queue for messages and optionally returns them. + * + * If 'action' is SDL_ADDEVENT, up to 'numevents' events will be added to + * the back of the event queue. + * If 'action' is SDL_PEEKEVENT, up to 'numevents' events at the front + * of the event queue, matching 'mask', will be returned and will not + * be removed from the queue. + * If 'action' is SDL_GETEVENT, up to 'numevents' events at the front + * of the event queue, matching 'mask', will be returned and will be + * removed from the queue. + * + * @return + * This function returns the number of events actually stored, or -1 + * if there was an error. + * + * This function is thread-safe. + */ +extern DECLSPEC int SDLCALL SDL_PeepEvents(SDL_Event *events, int numevents, + SDL_eventaction action, Uint32 mask); + +/** Polls for currently pending events, and returns 1 if there are any pending + * events, or 0 if there are none available. If 'event' is not NULL, the next + * event is removed from the queue and stored in that area. + */ +extern DECLSPEC int SDLCALL SDL_PollEvent(SDL_Event *event); + +/** Waits indefinitely for the next available event, returning 1, or 0 if there + * was an error while waiting for events. If 'event' is not NULL, the next + * event is removed from the queue and stored in that area. + */ +extern DECLSPEC int SDLCALL SDL_WaitEvent(SDL_Event *event); + +/** Add an event to the event queue. + * This function returns 0 on success, or -1 if the event queue was full + * or there was some other error. + */ +extern DECLSPEC int SDLCALL SDL_PushEvent(SDL_Event *event); + +/** @name Event Filtering */ +/*@{*/ +typedef int (SDLCALL *SDL_EventFilter)(const SDL_Event *event); +/** + * This function sets up a filter to process all events before they + * change internal state and are posted to the internal event queue. + * + * The filter is protypted as: + * @code typedef int (SDLCALL *SDL_EventFilter)(const SDL_Event *event); @endcode + * + * If the filter returns 1, then the event will be added to the internal queue. + * If it returns 0, then the event will be dropped from the queue, but the + * internal state will still be updated. This allows selective filtering of + * dynamically arriving events. + * + * @warning Be very careful of what you do in the event filter function, as + * it may run in a different thread! + * + * There is one caveat when dealing with the SDL_QUITEVENT event type. The + * event filter is only called when the window manager desires to close the + * application window. If the event filter returns 1, then the window will + * be closed, otherwise the window will remain open if possible. + * If the quit event is generated by an interrupt signal, it will bypass the + * internal queue and be delivered to the application at the next event poll. + */ +extern DECLSPEC void SDLCALL SDL_SetEventFilter(SDL_EventFilter filter); + +/** + * Return the current event filter - can be used to "chain" filters. + * If there is no event filter set, this function returns NULL. + */ +extern DECLSPEC SDL_EventFilter SDLCALL SDL_GetEventFilter(void); +/*@}*/ + +/** @name Event State */ +/*@{*/ +#define SDL_QUERY -1 +#define SDL_IGNORE 0 +#define SDL_DISABLE 0 +#define SDL_ENABLE 1 +/*@}*/ + +/** +* This function allows you to set the state of processing certain events. +* If 'state' is set to SDL_IGNORE, that event will be automatically dropped +* from the event queue and will not event be filtered. +* If 'state' is set to SDL_ENABLE, that event will be processed normally. +* If 'state' is set to SDL_QUERY, SDL_EventState() will return the +* current processing state of the specified event. +*/ +extern DECLSPEC Uint8 SDLCALL SDL_EventState(Uint8 type, int state); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_events_h */ diff --git a/code/SDL12/include/SDL_getenv.h b/code/SDL12/include/SDL_getenv.h new file mode 100644 index 0000000..253ad88 --- /dev/null +++ b/code/SDL12/include/SDL_getenv.h @@ -0,0 +1,28 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_getenv.h + * @deprecated Use SDL_stdinc.h instead + */ + +/* DEPRECATED */ +#include "SDL_stdinc.h" diff --git a/code/SDL12/include/SDL_joystick.h b/code/SDL12/include/SDL_joystick.h new file mode 100644 index 0000000..d5135c3 --- /dev/null +++ b/code/SDL12/include/SDL_joystick.h @@ -0,0 +1,187 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_joystick.h + * Include file for SDL joystick event handling + */ + +#ifndef _SDL_joystick_h +#define _SDL_joystick_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @file SDL_joystick.h + * @note In order to use these functions, SDL_Init() must have been called + * with the SDL_INIT_JOYSTICK flag. This causes SDL to scan the system + * for joysticks, and load appropriate drivers. + */ + +/** The joystick structure used to identify an SDL joystick */ +struct _SDL_Joystick; +typedef struct _SDL_Joystick SDL_Joystick; + +/* Function prototypes */ +/** + * Count the number of joysticks attached to the system + */ +extern DECLSPEC int SDLCALL SDL_NumJoysticks(void); + +/** + * Get the implementation dependent name of a joystick. + * + * This can be called before any joysticks are opened. + * If no name can be found, this function returns NULL. + */ +extern DECLSPEC const char * SDLCALL SDL_JoystickName(int device_index); + +/** + * Open a joystick for use. + * + * @param[in] device_index + * The index passed as an argument refers to + * the N'th joystick on the system. This index is the value which will + * identify this joystick in future joystick events. + * + * @return This function returns a joystick identifier, or NULL if an error occurred. + */ +extern DECLSPEC SDL_Joystick * SDLCALL SDL_JoystickOpen(int device_index); + +/** + * Returns 1 if the joystick has been opened, or 0 if it has not. + */ +extern DECLSPEC int SDLCALL SDL_JoystickOpened(int device_index); + +/** + * Get the device index of an opened joystick. + */ +extern DECLSPEC int SDLCALL SDL_JoystickIndex(SDL_Joystick *joystick); + +/** + * Get the number of general axis controls on a joystick + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumAxes(SDL_Joystick *joystick); + +/** + * Get the number of trackballs on a joystick + * + * Joystick trackballs have only relative motion events associated + * with them and their state cannot be polled. + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumBalls(SDL_Joystick *joystick); + +/** + * Get the number of POV hats on a joystick + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumHats(SDL_Joystick *joystick); + +/** + * Get the number of buttons on a joystick + */ +extern DECLSPEC int SDLCALL SDL_JoystickNumButtons(SDL_Joystick *joystick); + +/** + * Update the current state of the open joysticks. + * + * This is called automatically by the event loop if any joystick + * events are enabled. + */ +extern DECLSPEC void SDLCALL SDL_JoystickUpdate(void); + +/** + * Enable/disable joystick event polling. + * + * If joystick events are disabled, you must call SDL_JoystickUpdate() + * yourself and check the state of the joystick when you want joystick + * information. + * + * @param[in] state The state can be one of SDL_QUERY, SDL_ENABLE or SDL_IGNORE. + */ +extern DECLSPEC int SDLCALL SDL_JoystickEventState(int state); + +/** + * Get the current state of an axis control on a joystick + * + * @param[in] axis The axis indices start at index 0. + * + * @return The state is a value ranging from -32768 to 32767. + */ +extern DECLSPEC Sint16 SDLCALL SDL_JoystickGetAxis(SDL_Joystick *joystick, int axis); + +/** + * @name Hat Positions + * The return value of SDL_JoystickGetHat() is one of the following positions: + */ +/*@{*/ +#define SDL_HAT_CENTERED 0x00 +#define SDL_HAT_UP 0x01 +#define SDL_HAT_RIGHT 0x02 +#define SDL_HAT_DOWN 0x04 +#define SDL_HAT_LEFT 0x08 +#define SDL_HAT_RIGHTUP (SDL_HAT_RIGHT|SDL_HAT_UP) +#define SDL_HAT_RIGHTDOWN (SDL_HAT_RIGHT|SDL_HAT_DOWN) +#define SDL_HAT_LEFTUP (SDL_HAT_LEFT|SDL_HAT_UP) +#define SDL_HAT_LEFTDOWN (SDL_HAT_LEFT|SDL_HAT_DOWN) +/*@}*/ + +/** + * Get the current state of a POV hat on a joystick + * + * @param[in] hat The hat indices start at index 0. + */ +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetHat(SDL_Joystick *joystick, int hat); + +/** + * Get the ball axis change since the last poll + * + * @param[in] ball The ball indices start at index 0. + * + * @return This returns 0, or -1 if you passed it invalid parameters. + */ +extern DECLSPEC int SDLCALL SDL_JoystickGetBall(SDL_Joystick *joystick, int ball, int *dx, int *dy); + +/** + * Get the current state of a button on a joystick + * + * @param[in] button The button indices start at index 0. + */ +extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick *joystick, int button); + +/** + * Close a joystick previously opened with SDL_JoystickOpen() + */ +extern DECLSPEC void SDLCALL SDL_JoystickClose(SDL_Joystick *joystick); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_joystick_h */ diff --git a/code/SDL12/include/SDL_keyboard.h b/code/SDL12/include/SDL_keyboard.h new file mode 100644 index 0000000..7b59d24 --- /dev/null +++ b/code/SDL12/include/SDL_keyboard.h @@ -0,0 +1,135 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_keyboard.h + * Include file for SDL keyboard event handling + */ + +#ifndef _SDL_keyboard_h +#define _SDL_keyboard_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_keysym.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** Keysym structure + * + * - The scancode is hardware dependent, and should not be used by general + * applications. If no hardware scancode is available, it will be 0. + * + * - The 'unicode' translated character is only available when character + * translation is enabled by the SDL_EnableUNICODE() API. If non-zero, + * this is a UNICODE character corresponding to the keypress. If the + * high 9 bits of the character are 0, then this maps to the equivalent + * ASCII character: + * @code + * char ch; + * if ( (keysym.unicode & 0xFF80) == 0 ) { + * ch = keysym.unicode & 0x7F; + * } else { + * An international character.. + * } + * @endcode + */ +typedef struct SDL_keysym { + Uint8 scancode; /**< hardware specific scancode */ + SDLKey sym; /**< SDL virtual keysym */ + SDLMod mod; /**< current key modifiers */ + Uint16 unicode; /**< translated character */ +} SDL_keysym; + +/** This is the mask which refers to all hotkey bindings */ +#define SDL_ALL_HOTKEYS 0xFFFFFFFF + +/* Function prototypes */ +/** + * Enable/Disable UNICODE translation of keyboard input. + * + * This translation has some overhead, so translation defaults off. + * + * @param[in] enable + * If 'enable' is 1, translation is enabled. + * If 'enable' is 0, translation is disabled. + * If 'enable' is -1, the translation state is not changed. + * + * @return It returns the previous state of keyboard translation. + */ +extern DECLSPEC int SDLCALL SDL_EnableUNICODE(int enable); + +#define SDL_DEFAULT_REPEAT_DELAY 500 +#define SDL_DEFAULT_REPEAT_INTERVAL 30 +/** + * Enable/Disable keyboard repeat. Keyboard repeat defaults to off. + * + * @param[in] delay + * 'delay' is the initial delay in ms between the time when a key is + * pressed, and keyboard repeat begins. + * + * @param[in] interval + * 'interval' is the time in ms between keyboard repeat events. + * + * If 'delay' is set to 0, keyboard repeat is disabled. + */ +extern DECLSPEC int SDLCALL SDL_EnableKeyRepeat(int delay, int interval); +extern DECLSPEC void SDLCALL SDL_GetKeyRepeat(int *delay, int *interval); + +/** + * Get a snapshot of the current state of the keyboard. + * Returns an array of keystates, indexed by the SDLK_* syms. + * Usage: + * @code + * Uint8 *keystate = SDL_GetKeyState(NULL); + * if ( keystate[SDLK_RETURN] ) //... \ is pressed. + * @endcode + */ +extern DECLSPEC Uint8 * SDLCALL SDL_GetKeyState(int *numkeys); + +/** + * Get the current key modifier state + */ +extern DECLSPEC SDLMod SDLCALL SDL_GetModState(void); + +/** + * Set the current key modifier state. + * This does not change the keyboard state, only the key modifier flags. + */ +extern DECLSPEC void SDLCALL SDL_SetModState(SDLMod modstate); + +/** + * Get the name of an SDL virtual keysym + */ +extern DECLSPEC char * SDLCALL SDL_GetKeyName(SDLKey key); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_keyboard_h */ diff --git a/code/SDL12/include/SDL_keysym.h b/code/SDL12/include/SDL_keysym.h new file mode 100644 index 0000000..9010128 --- /dev/null +++ b/code/SDL12/include/SDL_keysym.h @@ -0,0 +1,326 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_keysym_h +#define _SDL_keysym_h + +/** What we really want is a mapping of every raw key on the keyboard. + * To support international keyboards, we use the range 0xA1 - 0xFF + * as international virtual keycodes. We'll follow in the footsteps of X11... + * @brief The names of the keys + */ +typedef enum { + /** @name ASCII mapped keysyms + * The keyboard syms have been cleverly chosen to map to ASCII + */ + /*@{*/ + SDLK_UNKNOWN = 0, + SDLK_FIRST = 0, + SDLK_BACKSPACE = 8, + SDLK_TAB = 9, + SDLK_CLEAR = 12, + SDLK_RETURN = 13, + SDLK_PAUSE = 19, + SDLK_ESCAPE = 27, + SDLK_SPACE = 32, + SDLK_EXCLAIM = 33, + SDLK_QUOTEDBL = 34, + SDLK_HASH = 35, + SDLK_DOLLAR = 36, + SDLK_AMPERSAND = 38, + SDLK_QUOTE = 39, + SDLK_LEFTPAREN = 40, + SDLK_RIGHTPAREN = 41, + SDLK_ASTERISK = 42, + SDLK_PLUS = 43, + SDLK_COMMA = 44, + SDLK_MINUS = 45, + SDLK_PERIOD = 46, + SDLK_SLASH = 47, + SDLK_0 = 48, + SDLK_1 = 49, + SDLK_2 = 50, + SDLK_3 = 51, + SDLK_4 = 52, + SDLK_5 = 53, + SDLK_6 = 54, + SDLK_7 = 55, + SDLK_8 = 56, + SDLK_9 = 57, + SDLK_COLON = 58, + SDLK_SEMICOLON = 59, + SDLK_LESS = 60, + SDLK_EQUALS = 61, + SDLK_GREATER = 62, + SDLK_QUESTION = 63, + SDLK_AT = 64, + /* + Skip uppercase letters + */ + SDLK_LEFTBRACKET = 91, + SDLK_BACKSLASH = 92, + SDLK_RIGHTBRACKET = 93, + SDLK_CARET = 94, + SDLK_UNDERSCORE = 95, + SDLK_BACKQUOTE = 96, + SDLK_a = 97, + SDLK_b = 98, + SDLK_c = 99, + SDLK_d = 100, + SDLK_e = 101, + SDLK_f = 102, + SDLK_g = 103, + SDLK_h = 104, + SDLK_i = 105, + SDLK_j = 106, + SDLK_k = 107, + SDLK_l = 108, + SDLK_m = 109, + SDLK_n = 110, + SDLK_o = 111, + SDLK_p = 112, + SDLK_q = 113, + SDLK_r = 114, + SDLK_s = 115, + SDLK_t = 116, + SDLK_u = 117, + SDLK_v = 118, + SDLK_w = 119, + SDLK_x = 120, + SDLK_y = 121, + SDLK_z = 122, + SDLK_DELETE = 127, + /* End of ASCII mapped keysyms */ + /*@}*/ + + /** @name International keyboard syms */ + /*@{*/ + SDLK_WORLD_0 = 160, /* 0xA0 */ + SDLK_WORLD_1 = 161, + SDLK_WORLD_2 = 162, + SDLK_WORLD_3 = 163, + SDLK_WORLD_4 = 164, + SDLK_WORLD_5 = 165, + SDLK_WORLD_6 = 166, + SDLK_WORLD_7 = 167, + SDLK_WORLD_8 = 168, + SDLK_WORLD_9 = 169, + SDLK_WORLD_10 = 170, + SDLK_WORLD_11 = 171, + SDLK_WORLD_12 = 172, + SDLK_WORLD_13 = 173, + SDLK_WORLD_14 = 174, + SDLK_WORLD_15 = 175, + SDLK_WORLD_16 = 176, + SDLK_WORLD_17 = 177, + SDLK_WORLD_18 = 178, + SDLK_WORLD_19 = 179, + SDLK_WORLD_20 = 180, + SDLK_WORLD_21 = 181, + SDLK_WORLD_22 = 182, + SDLK_WORLD_23 = 183, + SDLK_WORLD_24 = 184, + SDLK_WORLD_25 = 185, + SDLK_WORLD_26 = 186, + SDLK_WORLD_27 = 187, + SDLK_WORLD_28 = 188, + SDLK_WORLD_29 = 189, + SDLK_WORLD_30 = 190, + SDLK_WORLD_31 = 191, + SDLK_WORLD_32 = 192, + SDLK_WORLD_33 = 193, + SDLK_WORLD_34 = 194, + SDLK_WORLD_35 = 195, + SDLK_WORLD_36 = 196, + SDLK_WORLD_37 = 197, + SDLK_WORLD_38 = 198, + SDLK_WORLD_39 = 199, + SDLK_WORLD_40 = 200, + SDLK_WORLD_41 = 201, + SDLK_WORLD_42 = 202, + SDLK_WORLD_43 = 203, + SDLK_WORLD_44 = 204, + SDLK_WORLD_45 = 205, + SDLK_WORLD_46 = 206, + SDLK_WORLD_47 = 207, + SDLK_WORLD_48 = 208, + SDLK_WORLD_49 = 209, + SDLK_WORLD_50 = 210, + SDLK_WORLD_51 = 211, + SDLK_WORLD_52 = 212, + SDLK_WORLD_53 = 213, + SDLK_WORLD_54 = 214, + SDLK_WORLD_55 = 215, + SDLK_WORLD_56 = 216, + SDLK_WORLD_57 = 217, + SDLK_WORLD_58 = 218, + SDLK_WORLD_59 = 219, + SDLK_WORLD_60 = 220, + SDLK_WORLD_61 = 221, + SDLK_WORLD_62 = 222, + SDLK_WORLD_63 = 223, + SDLK_WORLD_64 = 224, + SDLK_WORLD_65 = 225, + SDLK_WORLD_66 = 226, + SDLK_WORLD_67 = 227, + SDLK_WORLD_68 = 228, + SDLK_WORLD_69 = 229, + SDLK_WORLD_70 = 230, + SDLK_WORLD_71 = 231, + SDLK_WORLD_72 = 232, + SDLK_WORLD_73 = 233, + SDLK_WORLD_74 = 234, + SDLK_WORLD_75 = 235, + SDLK_WORLD_76 = 236, + SDLK_WORLD_77 = 237, + SDLK_WORLD_78 = 238, + SDLK_WORLD_79 = 239, + SDLK_WORLD_80 = 240, + SDLK_WORLD_81 = 241, + SDLK_WORLD_82 = 242, + SDLK_WORLD_83 = 243, + SDLK_WORLD_84 = 244, + SDLK_WORLD_85 = 245, + SDLK_WORLD_86 = 246, + SDLK_WORLD_87 = 247, + SDLK_WORLD_88 = 248, + SDLK_WORLD_89 = 249, + SDLK_WORLD_90 = 250, + SDLK_WORLD_91 = 251, + SDLK_WORLD_92 = 252, + SDLK_WORLD_93 = 253, + SDLK_WORLD_94 = 254, + SDLK_WORLD_95 = 255, /* 0xFF */ + /*@}*/ + + /** @name Numeric keypad */ + /*@{*/ + SDLK_KP0 = 256, + SDLK_KP1 = 257, + SDLK_KP2 = 258, + SDLK_KP3 = 259, + SDLK_KP4 = 260, + SDLK_KP5 = 261, + SDLK_KP6 = 262, + SDLK_KP7 = 263, + SDLK_KP8 = 264, + SDLK_KP9 = 265, + SDLK_KP_PERIOD = 266, + SDLK_KP_DIVIDE = 267, + SDLK_KP_MULTIPLY = 268, + SDLK_KP_MINUS = 269, + SDLK_KP_PLUS = 270, + SDLK_KP_ENTER = 271, + SDLK_KP_EQUALS = 272, + /*@}*/ + + /** @name Arrows + Home/End pad */ + /*@{*/ + SDLK_UP = 273, + SDLK_DOWN = 274, + SDLK_RIGHT = 275, + SDLK_LEFT = 276, + SDLK_INSERT = 277, + SDLK_HOME = 278, + SDLK_END = 279, + SDLK_PAGEUP = 280, + SDLK_PAGEDOWN = 281, + /*@}*/ + + /** @name Function keys */ + /*@{*/ + SDLK_F1 = 282, + SDLK_F2 = 283, + SDLK_F3 = 284, + SDLK_F4 = 285, + SDLK_F5 = 286, + SDLK_F6 = 287, + SDLK_F7 = 288, + SDLK_F8 = 289, + SDLK_F9 = 290, + SDLK_F10 = 291, + SDLK_F11 = 292, + SDLK_F12 = 293, + SDLK_F13 = 294, + SDLK_F14 = 295, + SDLK_F15 = 296, + /*@}*/ + + /** @name Key state modifier keys */ + /*@{*/ + SDLK_NUMLOCK = 300, + SDLK_CAPSLOCK = 301, + SDLK_SCROLLOCK = 302, + SDLK_RSHIFT = 303, + SDLK_LSHIFT = 304, + SDLK_RCTRL = 305, + SDLK_LCTRL = 306, + SDLK_RALT = 307, + SDLK_LALT = 308, + SDLK_RMETA = 309, + SDLK_LMETA = 310, + SDLK_LSUPER = 311, /**< Left "Windows" key */ + SDLK_RSUPER = 312, /**< Right "Windows" key */ + SDLK_MODE = 313, /**< "Alt Gr" key */ + SDLK_COMPOSE = 314, /**< Multi-key compose key */ + /*@}*/ + + /** @name Miscellaneous function keys */ + /*@{*/ + SDLK_HELP = 315, + SDLK_PRINT = 316, + SDLK_SYSREQ = 317, + SDLK_BREAK = 318, + SDLK_MENU = 319, + SDLK_POWER = 320, /**< Power Macintosh power key */ + SDLK_EURO = 321, /**< Some european keyboards */ + SDLK_UNDO = 322, /**< Atari keyboard has Undo */ + /*@}*/ + + /* Add any other keys here */ + + SDLK_LAST +} SDLKey; + +/** Enumeration of valid key mods (possibly OR'd together) */ +typedef enum { + KMOD_NONE = 0x0000, + KMOD_LSHIFT= 0x0001, + KMOD_RSHIFT= 0x0002, + KMOD_LCTRL = 0x0040, + KMOD_RCTRL = 0x0080, + KMOD_LALT = 0x0100, + KMOD_RALT = 0x0200, + KMOD_LMETA = 0x0400, + KMOD_RMETA = 0x0800, + KMOD_NUM = 0x1000, + KMOD_CAPS = 0x2000, + KMOD_MODE = 0x4000, + KMOD_RESERVED = 0x8000 +} SDLMod; + +#define KMOD_CTRL (KMOD_LCTRL|KMOD_RCTRL) +#define KMOD_SHIFT (KMOD_LSHIFT|KMOD_RSHIFT) +#define KMOD_ALT (KMOD_LALT|KMOD_RALT) +#define KMOD_META (KMOD_LMETA|KMOD_RMETA) + +#endif /* _SDL_keysym_h */ diff --git a/code/SDL12/include/SDL_loadso.h b/code/SDL12/include/SDL_loadso.h new file mode 100644 index 0000000..45a17f9 --- /dev/null +++ b/code/SDL12/include/SDL_loadso.h @@ -0,0 +1,78 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_loadso.h + * System dependent library loading routines + */ + +/** @file SDL_loadso.h + * Some things to keep in mind: + * - These functions only work on C function names. Other languages may + * have name mangling and intrinsic language support that varies from + * compiler to compiler. + * - Make sure you declare your function pointers with the same calling + * convention as the actual library function. Your code will crash + * mysteriously if you do not do this. + * - Avoid namespace collisions. If you load a symbol from the library, + * it is not defined whether or not it goes into the global symbol + * namespace for the application. If it does and it conflicts with + * symbols in your code or other shared libraries, you will not get + * the results you expect. :) + */ + + +#ifndef _SDL_loadso_h +#define _SDL_loadso_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * This function dynamically loads a shared object and returns a pointer + * to the object handle (or NULL if there was an error). + * The 'sofile' parameter is a system dependent name of the object file. + */ +extern DECLSPEC void * SDLCALL SDL_LoadObject(const char *sofile); + +/** + * Given an object handle, this function looks up the address of the + * named function in the shared object and returns it. This address + * is no longer valid after calling SDL_UnloadObject(). + */ +extern DECLSPEC void * SDLCALL SDL_LoadFunction(void *handle, const char *name); + +/** Unload a shared object from memory */ +extern DECLSPEC void SDLCALL SDL_UnloadObject(void *handle); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_loadso_h */ diff --git a/code/SDL12/include/SDL_main.h b/code/SDL12/include/SDL_main.h new file mode 100644 index 0000000..b7f6b2c --- /dev/null +++ b/code/SDL12/include/SDL_main.h @@ -0,0 +1,106 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_main_h +#define _SDL_main_h + +#include "SDL_stdinc.h" + +/** @file SDL_main.h + * Redefine main() on Win32 and MacOS so that it is called by winmain.c + */ + +#if defined(__WIN32__) || \ + (defined(__MWERKS__) && !defined(__BEOS__)) || \ + defined(__MACOS__) || defined(__MACOSX__) || \ + defined(__SYMBIAN32__) || defined(QWS) + +#ifdef __cplusplus +#define C_LINKAGE "C" +#else +#define C_LINKAGE +#endif /* __cplusplus */ + +/** The application's main() function must be called with C linkage, + * and should be declared like this: + * @code + * #ifdef __cplusplus + * extern "C" + * #endif + * int main(int argc, char *argv[]) + * { + * } + * @endcode + */ +#define main SDL_main + +/** The prototype for the application's main() function */ +extern C_LINKAGE int SDL_main(int argc, char *argv[]); + + +/** @name From the SDL library code -- needed for registering the app on Win32 */ +/*@{*/ +#ifdef __WIN32__ + +#include "begin_code.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** This should be called from your WinMain() function, if any */ +extern DECLSPEC void SDLCALL SDL_SetModuleHandle(void *hInst); +/** This can also be called, but is no longer necessary */ +extern DECLSPEC int SDLCALL SDL_RegisterApp(char *name, Uint32 style, void *hInst); +/** This can also be called, but is no longer necessary (SDL_Quit calls it) */ +extern DECLSPEC void SDLCALL SDL_UnregisterApp(void); +#ifdef __cplusplus +} +#endif +#include "close_code.h" +#endif +/*@}*/ + +/** @name From the SDL library code -- needed for registering QuickDraw on MacOS */ +/*@{*/ +#if defined(__MACOS__) + +#include "begin_code.h" +#ifdef __cplusplus +extern "C" { +#endif + +/** Forward declaration so we don't need to include QuickDraw.h */ +struct QDGlobals; + +/** This should be called from your main() function, if any */ +extern DECLSPEC void SDLCALL SDL_InitQuickDraw(struct QDGlobals *the_qd); + +#ifdef __cplusplus +} +#endif +#include "close_code.h" +#endif +/*@}*/ + +#endif /* Need to redefine main()? */ + +#endif /* _SDL_main_h */ diff --git a/code/SDL12/include/SDL_mouse.h b/code/SDL12/include/SDL_mouse.h new file mode 100644 index 0000000..a573f04 --- /dev/null +++ b/code/SDL12/include/SDL_mouse.h @@ -0,0 +1,143 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_mouse.h + * Include file for SDL mouse event handling + */ + +#ifndef _SDL_mouse_h +#define _SDL_mouse_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_video.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct WMcursor WMcursor; /**< Implementation dependent */ +typedef struct SDL_Cursor { + SDL_Rect area; /**< The area of the mouse cursor */ + Sint16 hot_x, hot_y; /**< The "tip" of the cursor */ + Uint8 *data; /**< B/W cursor data */ + Uint8 *mask; /**< B/W cursor mask */ + Uint8 *save[2]; /**< Place to save cursor area */ + WMcursor *wm_cursor; /**< Window-manager cursor */ +} SDL_Cursor; + +/* Function prototypes */ +/** + * Retrieve the current state of the mouse. + * The current button state is returned as a button bitmask, which can + * be tested using the SDL_BUTTON(X) macros, and x and y are set to the + * current mouse cursor position. You can pass NULL for either x or y. + */ +extern DECLSPEC Uint8 SDLCALL SDL_GetMouseState(int *x, int *y); + +/** + * Retrieve the current state of the mouse. + * The current button state is returned as a button bitmask, which can + * be tested using the SDL_BUTTON(X) macros, and x and y are set to the + * mouse deltas since the last call to SDL_GetRelativeMouseState(). + */ +extern DECLSPEC Uint8 SDLCALL SDL_GetRelativeMouseState(int *x, int *y); + +/** + * Set the position of the mouse cursor (generates a mouse motion event) + */ +extern DECLSPEC void SDLCALL SDL_WarpMouse(Uint16 x, Uint16 y); + +/** + * Create a cursor using the specified data and mask (in MSB format). + * The cursor width must be a multiple of 8 bits. + * + * The cursor is created in black and white according to the following: + * data mask resulting pixel on screen + * 0 1 White + * 1 1 Black + * 0 0 Transparent + * 1 0 Inverted color if possible, black if not. + * + * Cursors created with this function must be freed with SDL_FreeCursor(). + */ +extern DECLSPEC SDL_Cursor * SDLCALL SDL_CreateCursor + (Uint8 *data, Uint8 *mask, int w, int h, int hot_x, int hot_y); + +/** + * Set the currently active cursor to the specified one. + * If the cursor is currently visible, the change will be immediately + * represented on the display. + */ +extern DECLSPEC void SDLCALL SDL_SetCursor(SDL_Cursor *cursor); + +/** + * Returns the currently active cursor. + */ +extern DECLSPEC SDL_Cursor * SDLCALL SDL_GetCursor(void); + +/** + * Deallocates a cursor created with SDL_CreateCursor(). + */ +extern DECLSPEC void SDLCALL SDL_FreeCursor(SDL_Cursor *cursor); + +/** + * Toggle whether or not the cursor is shown on the screen. + * The cursor start off displayed, but can be turned off. + * SDL_ShowCursor() returns 1 if the cursor was being displayed + * before the call, or 0 if it was not. You can query the current + * state by passing a 'toggle' value of -1. + */ +extern DECLSPEC int SDLCALL SDL_ShowCursor(int toggle); + +/*@{*/ +/** Used as a mask when testing buttons in buttonstate + * Button 1: Left mouse button + * Button 2: Middle mouse button + * Button 3: Right mouse button + * Button 4: Mouse wheel up (may also be a real button) + * Button 5: Mouse wheel down (may also be a real button) + */ +#define SDL_BUTTON(X) (1 << ((X)-1)) +#define SDL_BUTTON_LEFT 1 +#define SDL_BUTTON_MIDDLE 2 +#define SDL_BUTTON_RIGHT 3 +#define SDL_BUTTON_WHEELUP 4 +#define SDL_BUTTON_WHEELDOWN 5 +#define SDL_BUTTON_X1 6 +#define SDL_BUTTON_X2 7 +#define SDL_BUTTON_LMASK SDL_BUTTON(SDL_BUTTON_LEFT) +#define SDL_BUTTON_MMASK SDL_BUTTON(SDL_BUTTON_MIDDLE) +#define SDL_BUTTON_RMASK SDL_BUTTON(SDL_BUTTON_RIGHT) +#define SDL_BUTTON_X1MASK SDL_BUTTON(SDL_BUTTON_X1) +#define SDL_BUTTON_X2MASK SDL_BUTTON(SDL_BUTTON_X2) +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_mouse_h */ diff --git a/code/SDL12/include/SDL_mutex.h b/code/SDL12/include/SDL_mutex.h new file mode 100644 index 0000000..920971d --- /dev/null +++ b/code/SDL12/include/SDL_mutex.h @@ -0,0 +1,177 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_mutex_h +#define _SDL_mutex_h + +/** @file SDL_mutex.h + * Functions to provide thread synchronization primitives + * + * @note These are independent of the other SDL routines. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** Synchronization functions which can time out return this value + * if they time out. + */ +#define SDL_MUTEX_TIMEDOUT 1 + +/** This is the timeout value which corresponds to never time out */ +#define SDL_MUTEX_MAXWAIT (~(Uint32)0) + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name Mutex functions */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** The SDL mutex structure, defined in SDL_mutex.c */ +struct SDL_mutex; +typedef struct SDL_mutex SDL_mutex; + +/** Create a mutex, initialized unlocked */ +extern DECLSPEC SDL_mutex * SDLCALL SDL_CreateMutex(void); + +#define SDL_LockMutex(m) SDL_mutexP(m) +/** Lock the mutex + * @return 0, or -1 on error + */ +extern DECLSPEC int SDLCALL SDL_mutexP(SDL_mutex *mutex); + +#define SDL_UnlockMutex(m) SDL_mutexV(m) +/** Unlock the mutex + * @return 0, or -1 on error + * + * It is an error to unlock a mutex that has not been locked by + * the current thread, and doing so results in undefined behavior. + */ +extern DECLSPEC int SDLCALL SDL_mutexV(SDL_mutex *mutex); + +/** Destroy a mutex */ +extern DECLSPEC void SDLCALL SDL_DestroyMutex(SDL_mutex *mutex); + +/*@}*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name Semaphore functions */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** The SDL semaphore structure, defined in SDL_sem.c */ +struct SDL_semaphore; +typedef struct SDL_semaphore SDL_sem; + +/** Create a semaphore, initialized with value, returns NULL on failure. */ +extern DECLSPEC SDL_sem * SDLCALL SDL_CreateSemaphore(Uint32 initial_value); + +/** Destroy a semaphore */ +extern DECLSPEC void SDLCALL SDL_DestroySemaphore(SDL_sem *sem); + +/** + * This function suspends the calling thread until the semaphore pointed + * to by sem has a positive count. It then atomically decreases the semaphore + * count. + */ +extern DECLSPEC int SDLCALL SDL_SemWait(SDL_sem *sem); + +/** Non-blocking variant of SDL_SemWait(). + * @return 0 if the wait succeeds, + * SDL_MUTEX_TIMEDOUT if the wait would block, and -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_SemTryWait(SDL_sem *sem); + +/** Variant of SDL_SemWait() with a timeout in milliseconds, returns 0 if + * the wait succeeds, SDL_MUTEX_TIMEDOUT if the wait does not succeed in + * the allotted time, and -1 on error. + * + * On some platforms this function is implemented by looping with a delay + * of 1 ms, and so should be avoided if possible. + */ +extern DECLSPEC int SDLCALL SDL_SemWaitTimeout(SDL_sem *sem, Uint32 ms); + +/** Atomically increases the semaphore's count (not blocking). + * @return 0, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_SemPost(SDL_sem *sem); + +/** Returns the current count of the semaphore */ +extern DECLSPEC Uint32 SDLCALL SDL_SemValue(SDL_sem *sem); + +/*@}*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name Condition_variable_functions */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*@{*/ +/** The SDL condition variable structure, defined in SDL_cond.c */ +struct SDL_cond; +typedef struct SDL_cond SDL_cond; +/*@}*/ + +/** Create a condition variable */ +extern DECLSPEC SDL_cond * SDLCALL SDL_CreateCond(void); + +/** Destroy a condition variable */ +extern DECLSPEC void SDLCALL SDL_DestroyCond(SDL_cond *cond); + +/** Restart one of the threads that are waiting on the condition variable, + * @return 0 or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_CondSignal(SDL_cond *cond); + +/** Restart all threads that are waiting on the condition variable, + * @return 0 or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_CondBroadcast(SDL_cond *cond); + +/** Wait on the condition variable, unlocking the provided mutex. + * The mutex must be locked before entering this function! + * The mutex is re-locked once the condition variable is signaled. + * @return 0 when it is signaled, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_CondWait(SDL_cond *cond, SDL_mutex *mut); + +/** Waits for at most 'ms' milliseconds, and returns 0 if the condition + * variable is signaled, SDL_MUTEX_TIMEDOUT if the condition is not + * signaled in the allotted time, and -1 on error. + * On some platforms this function is implemented by looping with a delay + * of 1 ms, and so should be avoided if possible. + */ +extern DECLSPEC int SDLCALL SDL_CondWaitTimeout(SDL_cond *cond, SDL_mutex *mutex, Uint32 ms); + +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_mutex_h */ + diff --git a/code/SDL12/include/SDL_name.h b/code/SDL12/include/SDL_name.h new file mode 100644 index 0000000..511619a --- /dev/null +++ b/code/SDL12/include/SDL_name.h @@ -0,0 +1,11 @@ + +#ifndef _SDLname_h_ +#define _SDLname_h_ + +#if defined(__STDC__) || defined(__cplusplus) +#define NeedFunctionPrototypes 1 +#endif + +#define SDL_NAME(X) SDL_##X + +#endif /* _SDLname_h_ */ diff --git a/code/SDL12/include/SDL_opengl.h b/code/SDL12/include/SDL_opengl.h new file mode 100644 index 0000000..c479a3a --- /dev/null +++ b/code/SDL12/include/SDL_opengl.h @@ -0,0 +1,6556 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_opengl.h + * This is a simple file to encapsulate the OpenGL API headers + */ + +#include "SDL_config.h" + +#ifdef __WIN32__ +#define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX +#define NOMINMAX /* Don't defined min() and max() */ +#endif +#include +#endif +#ifndef NO_SDL_GLEXT +#define __glext_h_ /* Don't let gl.h include glext.h */ +#endif +#if defined(__MACOSX__) +#include /* Header File For The OpenGL Library */ +#include /* Header File For The GLU Library */ +#elif defined(__MACOS__) +#include /* Header File For The OpenGL Library */ +#include /* Header File For The GLU Library */ +#else +#include /* Header File For The OpenGL Library */ +#include /* Header File For The GLU Library */ +#endif +#ifndef NO_SDL_GLEXT +#undef __glext_h_ +#endif + +/** @name GLext.h + * This file taken from "GLext.h" from the Jeff Molofee OpenGL tutorials. + * It is included here because glext.h is not available on some systems. + * If you don't want this version included, simply define "NO_SDL_GLEXT" + */ +/*@{*/ +#ifndef NO_SDL_GLEXT +#if !defined(__glext_h_) && !defined(GL_GLEXT_LEGACY) +#define __glext_h_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** License Applicability. Except to the extent portions of this file are +** made subject to an alternative license as permitted in the SGI Free +** Software License B, Version 1.1 (the "License"), the contents of this +** file are subject only to the provisions of the License. You may not use +** this file except in compliance with the License. You may obtain a copy +** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600 +** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at: +** +** http://oss.sgi.com/projects/FreeB +** +** Note that, as provided in the License, the Software is distributed on an +** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS +** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND +** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A +** PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +** +** Original Code. The Original Code is: OpenGL Sample Implementation, +** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics, +** Inc. The Original Code is Copyright (c) 1991-2004 Silicon Graphics, Inc. +** Copyright in any portions created by third parties is as indicated +** elsewhere herein. All Rights Reserved. +** +** Additional Notice Provisions: This software was created using the +** OpenGL(R) version 1.2.1 Sample Implementation published by SGI, but has +** not been independently verified as being compliant with the OpenGL(R) +** version 1.2.1 Specification. +*/ + +#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) +#define WIN32_LEAN_AND_MEAN 1 +#include +#endif + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY * +#endif +#ifndef GLAPI +#define GLAPI extern +#endif + +/*************************************************************/ + +/* Header file version number, required by OpenGL ABI for Linux */ +/* glext.h last updated 2005/06/20 */ +/* Current version at http://oss.sgi.com/projects/ogl-sample/registry/ */ +#define GL_GLEXT_VERSION 29 + +#ifndef GL_VERSION_1_2 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#define GL_RESCALE_NORMAL 0x803A +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_MAX_ELEMENTS_VERTICES 0x80E8 +#define GL_MAX_ELEMENTS_INDICES 0x80E9 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_TEXTURE_MIN_LOD 0x813A +#define GL_TEXTURE_MAX_LOD 0x813B +#define GL_TEXTURE_BASE_LEVEL 0x813C +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8 +#define GL_SINGLE_COLOR 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR 0x81FA +#define GL_SMOOTH_POINT_SIZE_RANGE 0x0B12 +#define GL_SMOOTH_POINT_SIZE_GRANULARITY 0x0B13 +#define GL_SMOOTH_LINE_WIDTH_RANGE 0x0B22 +#define GL_SMOOTH_LINE_WIDTH_GRANULARITY 0x0B23 +#define GL_ALIASED_POINT_SIZE_RANGE 0x846D +#define GL_ALIASED_LINE_WIDTH_RANGE 0x846E +#endif + +#ifndef GL_ARB_imaging +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 +#define GL_BLEND_COLOR 0x8005 +#define GL_FUNC_ADD 0x8006 +#define GL_MIN 0x8007 +#define GL_MAX 0x8008 +#define GL_BLEND_EQUATION 0x8009 +#define GL_FUNC_SUBTRACT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT 0x800B +#define GL_CONVOLUTION_1D 0x8010 +#define GL_CONVOLUTION_2D 0x8011 +#define GL_SEPARABLE_2D 0x8012 +#define GL_CONVOLUTION_BORDER_MODE 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS 0x8015 +#define GL_REDUCE 0x8016 +#define GL_CONVOLUTION_FORMAT 0x8017 +#define GL_CONVOLUTION_WIDTH 0x8018 +#define GL_CONVOLUTION_HEIGHT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS 0x8023 +#define GL_HISTOGRAM 0x8024 +#define GL_PROXY_HISTOGRAM 0x8025 +#define GL_HISTOGRAM_WIDTH 0x8026 +#define GL_HISTOGRAM_FORMAT 0x8027 +#define GL_HISTOGRAM_RED_SIZE 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE 0x802C +#define GL_HISTOGRAM_SINK 0x802D +#define GL_MINMAX 0x802E +#define GL_MINMAX_FORMAT 0x802F +#define GL_MINMAX_SINK 0x8030 +#define GL_TABLE_TOO_LARGE 0x8031 +#define GL_COLOR_MATRIX 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS 0x80BB +#define GL_COLOR_TABLE 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE 0x80D2 +#define GL_PROXY_COLOR_TABLE 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE 0x80D5 +#define GL_COLOR_TABLE_SCALE 0x80D6 +#define GL_COLOR_TABLE_BIAS 0x80D7 +#define GL_COLOR_TABLE_FORMAT 0x80D8 +#define GL_COLOR_TABLE_WIDTH 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE 0x80DF +#define GL_CONSTANT_BORDER 0x8151 +#define GL_REPLICATE_BORDER 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR 0x8154 +#endif + +#ifndef GL_VERSION_1_3 +#define GL_TEXTURE0 0x84C0 +#define GL_TEXTURE1 0x84C1 +#define GL_TEXTURE2 0x84C2 +#define GL_TEXTURE3 0x84C3 +#define GL_TEXTURE4 0x84C4 +#define GL_TEXTURE5 0x84C5 +#define GL_TEXTURE6 0x84C6 +#define GL_TEXTURE7 0x84C7 +#define GL_TEXTURE8 0x84C8 +#define GL_TEXTURE9 0x84C9 +#define GL_TEXTURE10 0x84CA +#define GL_TEXTURE11 0x84CB +#define GL_TEXTURE12 0x84CC +#define GL_TEXTURE13 0x84CD +#define GL_TEXTURE14 0x84CE +#define GL_TEXTURE15 0x84CF +#define GL_TEXTURE16 0x84D0 +#define GL_TEXTURE17 0x84D1 +#define GL_TEXTURE18 0x84D2 +#define GL_TEXTURE19 0x84D3 +#define GL_TEXTURE20 0x84D4 +#define GL_TEXTURE21 0x84D5 +#define GL_TEXTURE22 0x84D6 +#define GL_TEXTURE23 0x84D7 +#define GL_TEXTURE24 0x84D8 +#define GL_TEXTURE25 0x84D9 +#define GL_TEXTURE26 0x84DA +#define GL_TEXTURE27 0x84DB +#define GL_TEXTURE28 0x84DC +#define GL_TEXTURE29 0x84DD +#define GL_TEXTURE30 0x84DE +#define GL_TEXTURE31 0x84DF +#define GL_ACTIVE_TEXTURE 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE 0x84E1 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_TRANSPOSE_MODELVIEW_MATRIX 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX 0x84E6 +#define GL_MULTISAMPLE 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE 0x809F +#define GL_SAMPLE_COVERAGE 0x80A0 +#define GL_SAMPLE_BUFFERS 0x80A8 +#define GL_SAMPLES 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT 0x80AB +#define GL_MULTISAMPLE_BIT 0x20000000 +#define GL_NORMAL_MAP 0x8511 +#define GL_REFLECTION_MAP 0x8512 +#define GL_TEXTURE_CUBE_MAP 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE 0x851C +#define GL_COMPRESSED_ALPHA 0x84E9 +#define GL_COMPRESSED_LUMINANCE 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA 0x84EB +#define GL_COMPRESSED_INTENSITY 0x84EC +#define GL_COMPRESSED_RGB 0x84ED +#define GL_COMPRESSED_RGBA 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE 0x86A0 +#define GL_TEXTURE_COMPRESSED 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS 0x86A3 +#define GL_CLAMP_TO_BORDER 0x812D +#define GL_COMBINE 0x8570 +#define GL_COMBINE_RGB 0x8571 +#define GL_COMBINE_ALPHA 0x8572 +#define GL_SOURCE0_RGB 0x8580 +#define GL_SOURCE1_RGB 0x8581 +#define GL_SOURCE2_RGB 0x8582 +#define GL_SOURCE0_ALPHA 0x8588 +#define GL_SOURCE1_ALPHA 0x8589 +#define GL_SOURCE2_ALPHA 0x858A +#define GL_OPERAND0_RGB 0x8590 +#define GL_OPERAND1_RGB 0x8591 +#define GL_OPERAND2_RGB 0x8592 +#define GL_OPERAND0_ALPHA 0x8598 +#define GL_OPERAND1_ALPHA 0x8599 +#define GL_OPERAND2_ALPHA 0x859A +#define GL_RGB_SCALE 0x8573 +#define GL_ADD_SIGNED 0x8574 +#define GL_INTERPOLATE 0x8575 +#define GL_SUBTRACT 0x84E7 +#define GL_CONSTANT 0x8576 +#define GL_PRIMARY_COLOR 0x8577 +#define GL_PREVIOUS 0x8578 +#define GL_DOT3_RGB 0x86AE +#define GL_DOT3_RGBA 0x86AF +#endif + +#ifndef GL_VERSION_1_4 +#define GL_BLEND_DST_RGB 0x80C8 +#define GL_BLEND_SRC_RGB 0x80C9 +#define GL_BLEND_DST_ALPHA 0x80CA +#define GL_BLEND_SRC_ALPHA 0x80CB +#define GL_POINT_SIZE_MIN 0x8126 +#define GL_POINT_SIZE_MAX 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION 0x8129 +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_FOG_COORDINATE_SOURCE 0x8450 +#define GL_FOG_COORDINATE 0x8451 +#define GL_FRAGMENT_DEPTH 0x8452 +#define GL_CURRENT_FOG_COORDINATE 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER 0x8456 +#define GL_FOG_COORDINATE_ARRAY 0x8457 +#define GL_COLOR_SUM 0x8458 +#define GL_CURRENT_SECONDARY_COLOR 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER 0x845D +#define GL_SECONDARY_COLOR_ARRAY 0x845E +#define GL_MAX_TEXTURE_LOD_BIAS 0x84FD +#define GL_TEXTURE_FILTER_CONTROL 0x8500 +#define GL_TEXTURE_LOD_BIAS 0x8501 +#define GL_INCR_WRAP 0x8507 +#define GL_DECR_WRAP 0x8508 +#define GL_TEXTURE_DEPTH_SIZE 0x884A +#define GL_DEPTH_TEXTURE_MODE 0x884B +#define GL_TEXTURE_COMPARE_MODE 0x884C +#define GL_TEXTURE_COMPARE_FUNC 0x884D +#define GL_COMPARE_R_TO_TEXTURE 0x884E +#endif + +#ifndef GL_VERSION_1_5 +#define GL_BUFFER_SIZE 0x8764 +#define GL_BUFFER_USAGE 0x8765 +#define GL_QUERY_COUNTER_BITS 0x8864 +#define GL_CURRENT_QUERY 0x8865 +#define GL_QUERY_RESULT 0x8866 +#define GL_QUERY_RESULT_AVAILABLE 0x8867 +#define GL_ARRAY_BUFFER 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER 0x8893 +#define GL_ARRAY_BUFFER_BINDING 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING 0x889F +#define GL_READ_ONLY 0x88B8 +#define GL_WRITE_ONLY 0x88B9 +#define GL_READ_WRITE 0x88BA +#define GL_BUFFER_ACCESS 0x88BB +#define GL_BUFFER_MAPPED 0x88BC +#define GL_BUFFER_MAP_POINTER 0x88BD +#define GL_STREAM_DRAW 0x88E0 +#define GL_STREAM_READ 0x88E1 +#define GL_STREAM_COPY 0x88E2 +#define GL_STATIC_DRAW 0x88E4 +#define GL_STATIC_READ 0x88E5 +#define GL_STATIC_COPY 0x88E6 +#define GL_DYNAMIC_DRAW 0x88E8 +#define GL_DYNAMIC_READ 0x88E9 +#define GL_DYNAMIC_COPY 0x88EA +#define GL_SAMPLES_PASSED 0x8914 +#define GL_FOG_COORD_SRC GL_FOG_COORDINATE_SOURCE +#define GL_FOG_COORD GL_FOG_COORDINATE +#define GL_CURRENT_FOG_COORD GL_CURRENT_FOG_COORDINATE +#define GL_FOG_COORD_ARRAY_TYPE GL_FOG_COORDINATE_ARRAY_TYPE +#define GL_FOG_COORD_ARRAY_STRIDE GL_FOG_COORDINATE_ARRAY_STRIDE +#define GL_FOG_COORD_ARRAY_POINTER GL_FOG_COORDINATE_ARRAY_POINTER +#define GL_FOG_COORD_ARRAY GL_FOG_COORDINATE_ARRAY +#define GL_FOG_COORD_ARRAY_BUFFER_BINDING GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING +#define GL_SRC0_RGB GL_SOURCE0_RGB +#define GL_SRC1_RGB GL_SOURCE1_RGB +#define GL_SRC2_RGB GL_SOURCE2_RGB +#define GL_SRC0_ALPHA GL_SOURCE0_ALPHA +#define GL_SRC1_ALPHA GL_SOURCE1_ALPHA +#define GL_SRC2_ALPHA GL_SOURCE2_ALPHA +#endif + +#ifndef GL_VERSION_2_0 +#define GL_BLEND_EQUATION_RGB GL_BLEND_EQUATION +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB 0x8626 +#define GL_VERTEX_PROGRAM_POINT_SIZE 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER 0x8645 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 +#define GL_MAX_DRAW_BUFFERS 0x8824 +#define GL_DRAW_BUFFER0 0x8825 +#define GL_DRAW_BUFFER1 0x8826 +#define GL_DRAW_BUFFER2 0x8827 +#define GL_DRAW_BUFFER3 0x8828 +#define GL_DRAW_BUFFER4 0x8829 +#define GL_DRAW_BUFFER5 0x882A +#define GL_DRAW_BUFFER6 0x882B +#define GL_DRAW_BUFFER7 0x882C +#define GL_DRAW_BUFFER8 0x882D +#define GL_DRAW_BUFFER9 0x882E +#define GL_DRAW_BUFFER10 0x882F +#define GL_DRAW_BUFFER11 0x8830 +#define GL_DRAW_BUFFER12 0x8831 +#define GL_DRAW_BUFFER13 0x8832 +#define GL_DRAW_BUFFER14 0x8833 +#define GL_DRAW_BUFFER15 0x8834 +#define GL_BLEND_EQUATION_ALPHA 0x883D +#define GL_POINT_SPRITE 0x8861 +#define GL_COORD_REPLACE 0x8862 +#define GL_MAX_VERTEX_ATTRIBS 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED 0x886A +#define GL_MAX_TEXTURE_COORDS 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS 0x8B49 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS 0x8B4A +#define GL_MAX_VARYING_FLOATS 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS 0x8B4D +#define GL_SHADER_TYPE 0x8B4F +#define GL_FLOAT_VEC2 0x8B50 +#define GL_FLOAT_VEC3 0x8B51 +#define GL_FLOAT_VEC4 0x8B52 +#define GL_INT_VEC2 0x8B53 +#define GL_INT_VEC3 0x8B54 +#define GL_INT_VEC4 0x8B55 +#define GL_BOOL 0x8B56 +#define GL_BOOL_VEC2 0x8B57 +#define GL_BOOL_VEC3 0x8B58 +#define GL_BOOL_VEC4 0x8B59 +#define GL_FLOAT_MAT2 0x8B5A +#define GL_FLOAT_MAT3 0x8B5B +#define GL_FLOAT_MAT4 0x8B5C +#define GL_SAMPLER_1D 0x8B5D +#define GL_SAMPLER_2D 0x8B5E +#define GL_SAMPLER_3D 0x8B5F +#define GL_SAMPLER_CUBE 0x8B60 +#define GL_SAMPLER_1D_SHADOW 0x8B61 +#define GL_SAMPLER_2D_SHADOW 0x8B62 +#define GL_DELETE_STATUS 0x8B80 +#define GL_COMPILE_STATUS 0x8B81 +#define GL_LINK_STATUS 0x8B82 +#define GL_VALIDATE_STATUS 0x8B83 +#define GL_INFO_LOG_LENGTH 0x8B84 +#define GL_ATTACHED_SHADERS 0x8B85 +#define GL_ACTIVE_UNIFORMS 0x8B86 +#define GL_ACTIVE_UNIFORM_MAX_LENGTH 0x8B87 +#define GL_SHADER_SOURCE_LENGTH 0x8B88 +#define GL_ACTIVE_ATTRIBUTES 0x8B89 +#define GL_ACTIVE_ATTRIBUTE_MAX_LENGTH 0x8B8A +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT 0x8B8B +#define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#define GL_CURRENT_PROGRAM 0x8B8D +#define GL_POINT_SPRITE_COORD_ORIGIN 0x8CA0 +#define GL_LOWER_LEFT 0x8CA1 +#define GL_UPPER_LEFT 0x8CA2 +#define GL_STENCIL_BACK_REF 0x8CA3 +#define GL_STENCIL_BACK_VALUE_MASK 0x8CA4 +#define GL_STENCIL_BACK_WRITEMASK 0x8CA5 +#endif + +#ifndef GL_ARB_multitexture +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE4_ARB 0x84C4 +#define GL_TEXTURE5_ARB 0x84C5 +#define GL_TEXTURE6_ARB 0x84C6 +#define GL_TEXTURE7_ARB 0x84C7 +#define GL_TEXTURE8_ARB 0x84C8 +#define GL_TEXTURE9_ARB 0x84C9 +#define GL_TEXTURE10_ARB 0x84CA +#define GL_TEXTURE11_ARB 0x84CB +#define GL_TEXTURE12_ARB 0x84CC +#define GL_TEXTURE13_ARB 0x84CD +#define GL_TEXTURE14_ARB 0x84CE +#define GL_TEXTURE15_ARB 0x84CF +#define GL_TEXTURE16_ARB 0x84D0 +#define GL_TEXTURE17_ARB 0x84D1 +#define GL_TEXTURE18_ARB 0x84D2 +#define GL_TEXTURE19_ARB 0x84D3 +#define GL_TEXTURE20_ARB 0x84D4 +#define GL_TEXTURE21_ARB 0x84D5 +#define GL_TEXTURE22_ARB 0x84D6 +#define GL_TEXTURE23_ARB 0x84D7 +#define GL_TEXTURE24_ARB 0x84D8 +#define GL_TEXTURE25_ARB 0x84D9 +#define GL_TEXTURE26_ARB 0x84DA +#define GL_TEXTURE27_ARB 0x84DB +#define GL_TEXTURE28_ARB 0x84DC +#define GL_TEXTURE29_ARB 0x84DD +#define GL_TEXTURE30_ARB 0x84DE +#define GL_TEXTURE31_ARB 0x84DF +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_TRANSPOSE_MODELVIEW_MATRIX_ARB 0x84E3 +#define GL_TRANSPOSE_PROJECTION_MATRIX_ARB 0x84E4 +#define GL_TRANSPOSE_TEXTURE_MATRIX_ARB 0x84E5 +#define GL_TRANSPOSE_COLOR_MATRIX_ARB 0x84E6 +#endif + +#ifndef GL_ARB_multisample +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 +#endif + +#ifndef GL_ARB_texture_env_add +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C +#endif + +#ifndef GL_ARB_texture_compression +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_CLAMP_TO_BORDER_ARB 0x812D +#endif + +#ifndef GL_ARB_point_parameters +#define GL_POINT_SIZE_MIN_ARB 0x8126 +#define GL_POINT_SIZE_MAX_ARB 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_ARB 0x8128 +#define GL_POINT_DISTANCE_ATTENUATION_ARB 0x8129 +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_MAX_VERTEX_UNITS_ARB 0x86A4 +#define GL_ACTIVE_VERTEX_UNITS_ARB 0x86A5 +#define GL_WEIGHT_SUM_UNITY_ARB 0x86A6 +#define GL_VERTEX_BLEND_ARB 0x86A7 +#define GL_CURRENT_WEIGHT_ARB 0x86A8 +#define GL_WEIGHT_ARRAY_TYPE_ARB 0x86A9 +#define GL_WEIGHT_ARRAY_STRIDE_ARB 0x86AA +#define GL_WEIGHT_ARRAY_SIZE_ARB 0x86AB +#define GL_WEIGHT_ARRAY_POINTER_ARB 0x86AC +#define GL_WEIGHT_ARRAY_ARB 0x86AD +#define GL_MODELVIEW0_ARB 0x1700 +#define GL_MODELVIEW1_ARB 0x850A +#define GL_MODELVIEW2_ARB 0x8722 +#define GL_MODELVIEW3_ARB 0x8723 +#define GL_MODELVIEW4_ARB 0x8724 +#define GL_MODELVIEW5_ARB 0x8725 +#define GL_MODELVIEW6_ARB 0x8726 +#define GL_MODELVIEW7_ARB 0x8727 +#define GL_MODELVIEW8_ARB 0x8728 +#define GL_MODELVIEW9_ARB 0x8729 +#define GL_MODELVIEW10_ARB 0x872A +#define GL_MODELVIEW11_ARB 0x872B +#define GL_MODELVIEW12_ARB 0x872C +#define GL_MODELVIEW13_ARB 0x872D +#define GL_MODELVIEW14_ARB 0x872E +#define GL_MODELVIEW15_ARB 0x872F +#define GL_MODELVIEW16_ARB 0x8730 +#define GL_MODELVIEW17_ARB 0x8731 +#define GL_MODELVIEW18_ARB 0x8732 +#define GL_MODELVIEW19_ARB 0x8733 +#define GL_MODELVIEW20_ARB 0x8734 +#define GL_MODELVIEW21_ARB 0x8735 +#define GL_MODELVIEW22_ARB 0x8736 +#define GL_MODELVIEW23_ARB 0x8737 +#define GL_MODELVIEW24_ARB 0x8738 +#define GL_MODELVIEW25_ARB 0x8739 +#define GL_MODELVIEW26_ARB 0x873A +#define GL_MODELVIEW27_ARB 0x873B +#define GL_MODELVIEW28_ARB 0x873C +#define GL_MODELVIEW29_ARB 0x873D +#define GL_MODELVIEW30_ARB 0x873E +#define GL_MODELVIEW31_ARB 0x873F +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_MATRIX_PALETTE_ARB 0x8840 +#define GL_MAX_MATRIX_PALETTE_STACK_DEPTH_ARB 0x8841 +#define GL_MAX_PALETTE_MATRICES_ARB 0x8842 +#define GL_CURRENT_PALETTE_MATRIX_ARB 0x8843 +#define GL_MATRIX_INDEX_ARRAY_ARB 0x8844 +#define GL_CURRENT_MATRIX_INDEX_ARB 0x8845 +#define GL_MATRIX_INDEX_ARRAY_SIZE_ARB 0x8846 +#define GL_MATRIX_INDEX_ARRAY_TYPE_ARB 0x8847 +#define GL_MATRIX_INDEX_ARRAY_STRIDE_ARB 0x8848 +#define GL_MATRIX_INDEX_ARRAY_POINTER_ARB 0x8849 +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_ARB 0x8370 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_DEPTH_COMPONENT16_ARB 0x81A5 +#define GL_DEPTH_COMPONENT24_ARB 0x81A6 +#define GL_DEPTH_COMPONENT32_ARB 0x81A7 +#define GL_TEXTURE_DEPTH_SIZE_ARB 0x884A +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#endif + +#ifndef GL_ARB_shadow +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF +#endif + +#ifndef GL_ARB_window_pos +#endif + +#ifndef GL_ARB_vertex_program +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#endif + +#ifndef GL_ARB_fragment_program +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 +#endif + +#ifndef GL_ARB_shader_objects +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_SHADING_LANGUAGE_VERSION_ARB 0x8B8C +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#endif + +#ifndef GL_ARB_point_sprite +#define GL_POINT_SPRITE_ARB 0x8861 +#define GL_COORD_REPLACE_ARB 0x8862 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#endif + +#ifndef GL_ARB_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ARB 0x8824 +#define GL_DRAW_BUFFER0_ARB 0x8825 +#define GL_DRAW_BUFFER1_ARB 0x8826 +#define GL_DRAW_BUFFER2_ARB 0x8827 +#define GL_DRAW_BUFFER3_ARB 0x8828 +#define GL_DRAW_BUFFER4_ARB 0x8829 +#define GL_DRAW_BUFFER5_ARB 0x882A +#define GL_DRAW_BUFFER6_ARB 0x882B +#define GL_DRAW_BUFFER7_ARB 0x882C +#define GL_DRAW_BUFFER8_ARB 0x882D +#define GL_DRAW_BUFFER9_ARB 0x882E +#define GL_DRAW_BUFFER10_ARB 0x882F +#define GL_DRAW_BUFFER11_ARB 0x8830 +#define GL_DRAW_BUFFER12_ARB 0x8831 +#define GL_DRAW_BUFFER13_ARB 0x8832 +#define GL_DRAW_BUFFER14_ARB 0x8833 +#define GL_DRAW_BUFFER15_ARB 0x8834 +#endif + +#ifndef GL_ARB_texture_rectangle +#define GL_TEXTURE_RECTANGLE_ARB 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_ARB 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_ARB 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB 0x84F8 +#endif + +#ifndef GL_ARB_color_buffer_float +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_HALF_FLOAT_ARB 0x140B +#endif + +#ifndef GL_ARB_texture_float +#define GL_TEXTURE_RED_TYPE_ARB 0x8C10 +#define GL_TEXTURE_GREEN_TYPE_ARB 0x8C11 +#define GL_TEXTURE_BLUE_TYPE_ARB 0x8C12 +#define GL_TEXTURE_ALPHA_TYPE_ARB 0x8C13 +#define GL_TEXTURE_LUMINANCE_TYPE_ARB 0x8C14 +#define GL_TEXTURE_INTENSITY_TYPE_ARB 0x8C15 +#define GL_TEXTURE_DEPTH_TYPE_ARB 0x8C16 +#define GL_UNSIGNED_NORMALIZED_ARB 0x8C17 +#define GL_RGBA32F_ARB 0x8814 +#define GL_RGB32F_ARB 0x8815 +#define GL_ALPHA32F_ARB 0x8816 +#define GL_INTENSITY32F_ARB 0x8817 +#define GL_LUMINANCE32F_ARB 0x8818 +#define GL_LUMINANCE_ALPHA32F_ARB 0x8819 +#define GL_RGBA16F_ARB 0x881A +#define GL_RGB16F_ARB 0x881B +#define GL_ALPHA16F_ARB 0x881C +#define GL_INTENSITY16F_ARB 0x881D +#define GL_LUMINANCE16F_ARB 0x881E +#define GL_LUMINANCE_ALPHA16F_ARB 0x881F +#endif + +#ifndef GL_ARB_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_ARB 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_ARB 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_ARB 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_ARB 0x88EF +#endif + +#ifndef GL_EXT_abgr +#define GL_ABGR_EXT 0x8000 +#endif + +#ifndef GL_EXT_blend_color +#define GL_CONSTANT_COLOR_EXT 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR_EXT 0x8002 +#define GL_CONSTANT_ALPHA_EXT 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA_EXT 0x8004 +#define GL_BLEND_COLOR_EXT 0x8005 +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_POLYGON_OFFSET_EXT 0x8037 +#define GL_POLYGON_OFFSET_FACTOR_EXT 0x8038 +#define GL_POLYGON_OFFSET_BIAS_EXT 0x8039 +#endif + +#ifndef GL_EXT_texture +#define GL_ALPHA4_EXT 0x803B +#define GL_ALPHA8_EXT 0x803C +#define GL_ALPHA12_EXT 0x803D +#define GL_ALPHA16_EXT 0x803E +#define GL_LUMINANCE4_EXT 0x803F +#define GL_LUMINANCE8_EXT 0x8040 +#define GL_LUMINANCE12_EXT 0x8041 +#define GL_LUMINANCE16_EXT 0x8042 +#define GL_LUMINANCE4_ALPHA4_EXT 0x8043 +#define GL_LUMINANCE6_ALPHA2_EXT 0x8044 +#define GL_LUMINANCE8_ALPHA8_EXT 0x8045 +#define GL_LUMINANCE12_ALPHA4_EXT 0x8046 +#define GL_LUMINANCE12_ALPHA12_EXT 0x8047 +#define GL_LUMINANCE16_ALPHA16_EXT 0x8048 +#define GL_INTENSITY_EXT 0x8049 +#define GL_INTENSITY4_EXT 0x804A +#define GL_INTENSITY8_EXT 0x804B +#define GL_INTENSITY12_EXT 0x804C +#define GL_INTENSITY16_EXT 0x804D +#define GL_RGB2_EXT 0x804E +#define GL_RGB4_EXT 0x804F +#define GL_RGB5_EXT 0x8050 +#define GL_RGB8_EXT 0x8051 +#define GL_RGB10_EXT 0x8052 +#define GL_RGB12_EXT 0x8053 +#define GL_RGB16_EXT 0x8054 +#define GL_RGBA2_EXT 0x8055 +#define GL_RGBA4_EXT 0x8056 +#define GL_RGB5_A1_EXT 0x8057 +#define GL_RGBA8_EXT 0x8058 +#define GL_RGB10_A2_EXT 0x8059 +#define GL_RGBA12_EXT 0x805A +#define GL_RGBA16_EXT 0x805B +#define GL_TEXTURE_RED_SIZE_EXT 0x805C +#define GL_TEXTURE_GREEN_SIZE_EXT 0x805D +#define GL_TEXTURE_BLUE_SIZE_EXT 0x805E +#define GL_TEXTURE_ALPHA_SIZE_EXT 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE_EXT 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE_EXT 0x8061 +#define GL_REPLACE_EXT 0x8062 +#define GL_PROXY_TEXTURE_1D_EXT 0x8063 +#define GL_PROXY_TEXTURE_2D_EXT 0x8064 +#define GL_TEXTURE_TOO_LARGE_EXT 0x8065 +#endif + +#ifndef GL_EXT_texture3D +#define GL_PACK_SKIP_IMAGES_EXT 0x806B +#define GL_PACK_IMAGE_HEIGHT_EXT 0x806C +#define GL_UNPACK_SKIP_IMAGES_EXT 0x806D +#define GL_UNPACK_IMAGE_HEIGHT_EXT 0x806E +#define GL_TEXTURE_3D_EXT 0x806F +#define GL_PROXY_TEXTURE_3D_EXT 0x8070 +#define GL_TEXTURE_DEPTH_EXT 0x8071 +#define GL_TEXTURE_WRAP_R_EXT 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE_EXT 0x8073 +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_FILTER4_SGIS 0x8146 +#define GL_TEXTURE_FILTER4_SIZE_SGIS 0x8147 +#endif + +#ifndef GL_EXT_subtexture +#endif + +#ifndef GL_EXT_copy_texture +#endif + +#ifndef GL_EXT_histogram +#define GL_HISTOGRAM_EXT 0x8024 +#define GL_PROXY_HISTOGRAM_EXT 0x8025 +#define GL_HISTOGRAM_WIDTH_EXT 0x8026 +#define GL_HISTOGRAM_FORMAT_EXT 0x8027 +#define GL_HISTOGRAM_RED_SIZE_EXT 0x8028 +#define GL_HISTOGRAM_GREEN_SIZE_EXT 0x8029 +#define GL_HISTOGRAM_BLUE_SIZE_EXT 0x802A +#define GL_HISTOGRAM_ALPHA_SIZE_EXT 0x802B +#define GL_HISTOGRAM_LUMINANCE_SIZE_EXT 0x802C +#define GL_HISTOGRAM_SINK_EXT 0x802D +#define GL_MINMAX_EXT 0x802E +#define GL_MINMAX_FORMAT_EXT 0x802F +#define GL_MINMAX_SINK_EXT 0x8030 +#define GL_TABLE_TOO_LARGE_EXT 0x8031 +#endif + +#ifndef GL_EXT_convolution +#define GL_CONVOLUTION_1D_EXT 0x8010 +#define GL_CONVOLUTION_2D_EXT 0x8011 +#define GL_SEPARABLE_2D_EXT 0x8012 +#define GL_CONVOLUTION_BORDER_MODE_EXT 0x8013 +#define GL_CONVOLUTION_FILTER_SCALE_EXT 0x8014 +#define GL_CONVOLUTION_FILTER_BIAS_EXT 0x8015 +#define GL_REDUCE_EXT 0x8016 +#define GL_CONVOLUTION_FORMAT_EXT 0x8017 +#define GL_CONVOLUTION_WIDTH_EXT 0x8018 +#define GL_CONVOLUTION_HEIGHT_EXT 0x8019 +#define GL_MAX_CONVOLUTION_WIDTH_EXT 0x801A +#define GL_MAX_CONVOLUTION_HEIGHT_EXT 0x801B +#define GL_POST_CONVOLUTION_RED_SCALE_EXT 0x801C +#define GL_POST_CONVOLUTION_GREEN_SCALE_EXT 0x801D +#define GL_POST_CONVOLUTION_BLUE_SCALE_EXT 0x801E +#define GL_POST_CONVOLUTION_ALPHA_SCALE_EXT 0x801F +#define GL_POST_CONVOLUTION_RED_BIAS_EXT 0x8020 +#define GL_POST_CONVOLUTION_GREEN_BIAS_EXT 0x8021 +#define GL_POST_CONVOLUTION_BLUE_BIAS_EXT 0x8022 +#define GL_POST_CONVOLUTION_ALPHA_BIAS_EXT 0x8023 +#endif + +#ifndef GL_SGI_color_matrix +#define GL_COLOR_MATRIX_SGI 0x80B1 +#define GL_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B2 +#define GL_MAX_COLOR_MATRIX_STACK_DEPTH_SGI 0x80B3 +#define GL_POST_COLOR_MATRIX_RED_SCALE_SGI 0x80B4 +#define GL_POST_COLOR_MATRIX_GREEN_SCALE_SGI 0x80B5 +#define GL_POST_COLOR_MATRIX_BLUE_SCALE_SGI 0x80B6 +#define GL_POST_COLOR_MATRIX_ALPHA_SCALE_SGI 0x80B7 +#define GL_POST_COLOR_MATRIX_RED_BIAS_SGI 0x80B8 +#define GL_POST_COLOR_MATRIX_GREEN_BIAS_SGI 0x80B9 +#define GL_POST_COLOR_MATRIX_BLUE_BIAS_SGI 0x80BA +#define GL_POST_COLOR_MATRIX_ALPHA_BIAS_SGI 0x80BB +#endif + +#ifndef GL_SGI_color_table +#define GL_COLOR_TABLE_SGI 0x80D0 +#define GL_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D1 +#define GL_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D2 +#define GL_PROXY_COLOR_TABLE_SGI 0x80D3 +#define GL_PROXY_POST_CONVOLUTION_COLOR_TABLE_SGI 0x80D4 +#define GL_PROXY_POST_COLOR_MATRIX_COLOR_TABLE_SGI 0x80D5 +#define GL_COLOR_TABLE_SCALE_SGI 0x80D6 +#define GL_COLOR_TABLE_BIAS_SGI 0x80D7 +#define GL_COLOR_TABLE_FORMAT_SGI 0x80D8 +#define GL_COLOR_TABLE_WIDTH_SGI 0x80D9 +#define GL_COLOR_TABLE_RED_SIZE_SGI 0x80DA +#define GL_COLOR_TABLE_GREEN_SIZE_SGI 0x80DB +#define GL_COLOR_TABLE_BLUE_SIZE_SGI 0x80DC +#define GL_COLOR_TABLE_ALPHA_SIZE_SGI 0x80DD +#define GL_COLOR_TABLE_LUMINANCE_SIZE_SGI 0x80DE +#define GL_COLOR_TABLE_INTENSITY_SIZE_SGI 0x80DF +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_PIXEL_TEXTURE_SGIS 0x8353 +#define GL_PIXEL_FRAGMENT_RGB_SOURCE_SGIS 0x8354 +#define GL_PIXEL_FRAGMENT_ALPHA_SOURCE_SGIS 0x8355 +#define GL_PIXEL_GROUP_COLOR_SGIS 0x8356 +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_PIXEL_TEX_GEN_SGIX 0x8139 +#define GL_PIXEL_TEX_GEN_MODE_SGIX 0x832B +#endif + +#ifndef GL_SGIS_texture4D +#define GL_PACK_SKIP_VOLUMES_SGIS 0x8130 +#define GL_PACK_IMAGE_DEPTH_SGIS 0x8131 +#define GL_UNPACK_SKIP_VOLUMES_SGIS 0x8132 +#define GL_UNPACK_IMAGE_DEPTH_SGIS 0x8133 +#define GL_TEXTURE_4D_SGIS 0x8134 +#define GL_PROXY_TEXTURE_4D_SGIS 0x8135 +#define GL_TEXTURE_4DSIZE_SGIS 0x8136 +#define GL_TEXTURE_WRAP_Q_SGIS 0x8137 +#define GL_MAX_4D_TEXTURE_SIZE_SGIS 0x8138 +#define GL_TEXTURE_4D_BINDING_SGIS 0x814F +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_TEXTURE_COLOR_TABLE_SGI 0x80BC +#define GL_PROXY_TEXTURE_COLOR_TABLE_SGI 0x80BD +#endif + +#ifndef GL_EXT_cmyka +#define GL_CMYK_EXT 0x800C +#define GL_CMYKA_EXT 0x800D +#define GL_PACK_CMYK_HINT_EXT 0x800E +#define GL_UNPACK_CMYK_HINT_EXT 0x800F +#endif + +#ifndef GL_EXT_texture_object +#define GL_TEXTURE_PRIORITY_EXT 0x8066 +#define GL_TEXTURE_RESIDENT_EXT 0x8067 +#define GL_TEXTURE_1D_BINDING_EXT 0x8068 +#define GL_TEXTURE_2D_BINDING_EXT 0x8069 +#define GL_TEXTURE_3D_BINDING_EXT 0x806A +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_DETAIL_TEXTURE_2D_SGIS 0x8095 +#define GL_DETAIL_TEXTURE_2D_BINDING_SGIS 0x8096 +#define GL_LINEAR_DETAIL_SGIS 0x8097 +#define GL_LINEAR_DETAIL_ALPHA_SGIS 0x8098 +#define GL_LINEAR_DETAIL_COLOR_SGIS 0x8099 +#define GL_DETAIL_TEXTURE_LEVEL_SGIS 0x809A +#define GL_DETAIL_TEXTURE_MODE_SGIS 0x809B +#define GL_DETAIL_TEXTURE_FUNC_POINTS_SGIS 0x809C +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_LINEAR_SHARPEN_SGIS 0x80AD +#define GL_LINEAR_SHARPEN_ALPHA_SGIS 0x80AE +#define GL_LINEAR_SHARPEN_COLOR_SGIS 0x80AF +#define GL_SHARPEN_TEXTURE_FUNC_POINTS_SGIS 0x80B0 +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_UNSIGNED_BYTE_3_3_2_EXT 0x8032 +#define GL_UNSIGNED_SHORT_4_4_4_4_EXT 0x8033 +#define GL_UNSIGNED_SHORT_5_5_5_1_EXT 0x8034 +#define GL_UNSIGNED_INT_8_8_8_8_EXT 0x8035 +#define GL_UNSIGNED_INT_10_10_10_2_EXT 0x8036 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_TEXTURE_MIN_LOD_SGIS 0x813A +#define GL_TEXTURE_MAX_LOD_SGIS 0x813B +#define GL_TEXTURE_BASE_LEVEL_SGIS 0x813C +#define GL_TEXTURE_MAX_LEVEL_SGIS 0x813D +#endif + +#ifndef GL_SGIS_multisample +#define GL_MULTISAMPLE_SGIS 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_SGIS 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_SGIS 0x809F +#define GL_SAMPLE_MASK_SGIS 0x80A0 +#define GL_1PASS_SGIS 0x80A1 +#define GL_2PASS_0_SGIS 0x80A2 +#define GL_2PASS_1_SGIS 0x80A3 +#define GL_4PASS_0_SGIS 0x80A4 +#define GL_4PASS_1_SGIS 0x80A5 +#define GL_4PASS_2_SGIS 0x80A6 +#define GL_4PASS_3_SGIS 0x80A7 +#define GL_SAMPLE_BUFFERS_SGIS 0x80A8 +#define GL_SAMPLES_SGIS 0x80A9 +#define GL_SAMPLE_MASK_VALUE_SGIS 0x80AA +#define GL_SAMPLE_MASK_INVERT_SGIS 0x80AB +#define GL_SAMPLE_PATTERN_SGIS 0x80AC +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_RESCALE_NORMAL_EXT 0x803A +#endif + +#ifndef GL_EXT_vertex_array +#define GL_VERTEX_ARRAY_EXT 0x8074 +#define GL_NORMAL_ARRAY_EXT 0x8075 +#define GL_COLOR_ARRAY_EXT 0x8076 +#define GL_INDEX_ARRAY_EXT 0x8077 +#define GL_TEXTURE_COORD_ARRAY_EXT 0x8078 +#define GL_EDGE_FLAG_ARRAY_EXT 0x8079 +#define GL_VERTEX_ARRAY_SIZE_EXT 0x807A +#define GL_VERTEX_ARRAY_TYPE_EXT 0x807B +#define GL_VERTEX_ARRAY_STRIDE_EXT 0x807C +#define GL_VERTEX_ARRAY_COUNT_EXT 0x807D +#define GL_NORMAL_ARRAY_TYPE_EXT 0x807E +#define GL_NORMAL_ARRAY_STRIDE_EXT 0x807F +#define GL_NORMAL_ARRAY_COUNT_EXT 0x8080 +#define GL_COLOR_ARRAY_SIZE_EXT 0x8081 +#define GL_COLOR_ARRAY_TYPE_EXT 0x8082 +#define GL_COLOR_ARRAY_STRIDE_EXT 0x8083 +#define GL_COLOR_ARRAY_COUNT_EXT 0x8084 +#define GL_INDEX_ARRAY_TYPE_EXT 0x8085 +#define GL_INDEX_ARRAY_STRIDE_EXT 0x8086 +#define GL_INDEX_ARRAY_COUNT_EXT 0x8087 +#define GL_TEXTURE_COORD_ARRAY_SIZE_EXT 0x8088 +#define GL_TEXTURE_COORD_ARRAY_TYPE_EXT 0x8089 +#define GL_TEXTURE_COORD_ARRAY_STRIDE_EXT 0x808A +#define GL_TEXTURE_COORD_ARRAY_COUNT_EXT 0x808B +#define GL_EDGE_FLAG_ARRAY_STRIDE_EXT 0x808C +#define GL_EDGE_FLAG_ARRAY_COUNT_EXT 0x808D +#define GL_VERTEX_ARRAY_POINTER_EXT 0x808E +#define GL_NORMAL_ARRAY_POINTER_EXT 0x808F +#define GL_COLOR_ARRAY_POINTER_EXT 0x8090 +#define GL_INDEX_ARRAY_POINTER_EXT 0x8091 +#define GL_TEXTURE_COORD_ARRAY_POINTER_EXT 0x8092 +#define GL_EDGE_FLAG_ARRAY_POINTER_EXT 0x8093 +#endif + +#ifndef GL_EXT_misc_attribute +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_LINEAR_CLIPMAP_LINEAR_SGIX 0x8170 +#define GL_TEXTURE_CLIPMAP_CENTER_SGIX 0x8171 +#define GL_TEXTURE_CLIPMAP_FRAME_SGIX 0x8172 +#define GL_TEXTURE_CLIPMAP_OFFSET_SGIX 0x8173 +#define GL_TEXTURE_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8174 +#define GL_TEXTURE_CLIPMAP_LOD_OFFSET_SGIX 0x8175 +#define GL_TEXTURE_CLIPMAP_DEPTH_SGIX 0x8176 +#define GL_MAX_CLIPMAP_DEPTH_SGIX 0x8177 +#define GL_MAX_CLIPMAP_VIRTUAL_DEPTH_SGIX 0x8178 +#define GL_NEAREST_CLIPMAP_NEAREST_SGIX 0x844D +#define GL_NEAREST_CLIPMAP_LINEAR_SGIX 0x844E +#define GL_LINEAR_CLIPMAP_NEAREST_SGIX 0x844F +#endif + +#ifndef GL_SGIX_shadow +#define GL_TEXTURE_COMPARE_SGIX 0x819A +#define GL_TEXTURE_COMPARE_OPERATOR_SGIX 0x819B +#define GL_TEXTURE_LEQUAL_R_SGIX 0x819C +#define GL_TEXTURE_GEQUAL_R_SGIX 0x819D +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_CLAMP_TO_EDGE_SGIS 0x812F +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_CLAMP_TO_BORDER_SGIS 0x812D +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#endif + +#ifndef GL_EXT_blend_logic_op +#endif + +#ifndef GL_SGIX_interlace +#define GL_INTERLACE_SGIX 0x8094 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_PIXEL_TILE_BEST_ALIGNMENT_SGIX 0x813E +#define GL_PIXEL_TILE_CACHE_INCREMENT_SGIX 0x813F +#define GL_PIXEL_TILE_WIDTH_SGIX 0x8140 +#define GL_PIXEL_TILE_HEIGHT_SGIX 0x8141 +#define GL_PIXEL_TILE_GRID_WIDTH_SGIX 0x8142 +#define GL_PIXEL_TILE_GRID_HEIGHT_SGIX 0x8143 +#define GL_PIXEL_TILE_GRID_DEPTH_SGIX 0x8144 +#define GL_PIXEL_TILE_CACHE_SIZE_SGIX 0x8145 +#endif + +#ifndef GL_SGIS_texture_select +#define GL_DUAL_ALPHA4_SGIS 0x8110 +#define GL_DUAL_ALPHA8_SGIS 0x8111 +#define GL_DUAL_ALPHA12_SGIS 0x8112 +#define GL_DUAL_ALPHA16_SGIS 0x8113 +#define GL_DUAL_LUMINANCE4_SGIS 0x8114 +#define GL_DUAL_LUMINANCE8_SGIS 0x8115 +#define GL_DUAL_LUMINANCE12_SGIS 0x8116 +#define GL_DUAL_LUMINANCE16_SGIS 0x8117 +#define GL_DUAL_INTENSITY4_SGIS 0x8118 +#define GL_DUAL_INTENSITY8_SGIS 0x8119 +#define GL_DUAL_INTENSITY12_SGIS 0x811A +#define GL_DUAL_INTENSITY16_SGIS 0x811B +#define GL_DUAL_LUMINANCE_ALPHA4_SGIS 0x811C +#define GL_DUAL_LUMINANCE_ALPHA8_SGIS 0x811D +#define GL_QUAD_ALPHA4_SGIS 0x811E +#define GL_QUAD_ALPHA8_SGIS 0x811F +#define GL_QUAD_LUMINANCE4_SGIS 0x8120 +#define GL_QUAD_LUMINANCE8_SGIS 0x8121 +#define GL_QUAD_INTENSITY4_SGIS 0x8122 +#define GL_QUAD_INTENSITY8_SGIS 0x8123 +#define GL_DUAL_TEXTURE_SELECT_SGIS 0x8124 +#define GL_QUAD_TEXTURE_SELECT_SGIS 0x8125 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SPRITE_SGIX 0x8148 +#define GL_SPRITE_MODE_SGIX 0x8149 +#define GL_SPRITE_AXIS_SGIX 0x814A +#define GL_SPRITE_TRANSLATION_SGIX 0x814B +#define GL_SPRITE_AXIAL_SGIX 0x814C +#define GL_SPRITE_OBJECT_ALIGNED_SGIX 0x814D +#define GL_SPRITE_EYE_ALIGNED_SGIX 0x814E +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_TEXTURE_MULTI_BUFFER_HINT_SGIX 0x812E +#endif + +#ifndef GL_EXT_point_parameters +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_POINT_SIZE_MIN_SGIS 0x8126 +#define GL_POINT_SIZE_MAX_SGIS 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_SGIS 0x8128 +#define GL_DISTANCE_ATTENUATION_SGIS 0x8129 +#endif + +#ifndef GL_SGIX_instruments +#define GL_INSTRUMENT_BUFFER_POINTER_SGIX 0x8180 +#define GL_INSTRUMENT_MEASUREMENTS_SGIX 0x8181 +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_POST_TEXTURE_FILTER_BIAS_SGIX 0x8179 +#define GL_POST_TEXTURE_FILTER_SCALE_SGIX 0x817A +#define GL_POST_TEXTURE_FILTER_BIAS_RANGE_SGIX 0x817B +#define GL_POST_TEXTURE_FILTER_SCALE_RANGE_SGIX 0x817C +#endif + +#ifndef GL_SGIX_framezoom +#define GL_FRAMEZOOM_SGIX 0x818B +#define GL_FRAMEZOOM_FACTOR_SGIX 0x818C +#define GL_MAX_FRAMEZOOM_FACTOR_SGIX 0x818D +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#endif + +#ifndef GL_FfdMaskSGIX +#define GL_TEXTURE_DEFORMATION_BIT_SGIX 0x00000001 +#define GL_GEOMETRY_DEFORMATION_BIT_SGIX 0x00000002 +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_GEOMETRY_DEFORMATION_SGIX 0x8194 +#define GL_TEXTURE_DEFORMATION_SGIX 0x8195 +#define GL_DEFORMATIONS_MASK_SGIX 0x8196 +#define GL_MAX_DEFORMATION_ORDER_SGIX 0x8197 +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_REFERENCE_PLANE_SGIX 0x817D +#define GL_REFERENCE_PLANE_EQUATION_SGIX 0x817E +#endif + +#ifndef GL_SGIX_flush_raster +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_DEPTH_COMPONENT16_SGIX 0x81A5 +#define GL_DEPTH_COMPONENT24_SGIX 0x81A6 +#define GL_DEPTH_COMPONENT32_SGIX 0x81A7 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_FOG_FUNC_SGIS 0x812A +#define GL_FOG_FUNC_POINTS_SGIS 0x812B +#define GL_MAX_FOG_FUNC_POINTS_SGIS 0x812C +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_FOG_OFFSET_SGIX 0x8198 +#define GL_FOG_OFFSET_VALUE_SGIX 0x8199 +#endif + +#ifndef GL_HP_image_transform +#define GL_IMAGE_SCALE_X_HP 0x8155 +#define GL_IMAGE_SCALE_Y_HP 0x8156 +#define GL_IMAGE_TRANSLATE_X_HP 0x8157 +#define GL_IMAGE_TRANSLATE_Y_HP 0x8158 +#define GL_IMAGE_ROTATE_ANGLE_HP 0x8159 +#define GL_IMAGE_ROTATE_ORIGIN_X_HP 0x815A +#define GL_IMAGE_ROTATE_ORIGIN_Y_HP 0x815B +#define GL_IMAGE_MAG_FILTER_HP 0x815C +#define GL_IMAGE_MIN_FILTER_HP 0x815D +#define GL_IMAGE_CUBIC_WEIGHT_HP 0x815E +#define GL_CUBIC_HP 0x815F +#define GL_AVERAGE_HP 0x8160 +#define GL_IMAGE_TRANSFORM_2D_HP 0x8161 +#define GL_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8162 +#define GL_PROXY_POST_IMAGE_TRANSFORM_COLOR_TABLE_HP 0x8163 +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_IGNORE_BORDER_HP 0x8150 +#define GL_CONSTANT_BORDER_HP 0x8151 +#define GL_REPLICATE_BORDER_HP 0x8153 +#define GL_CONVOLUTION_BORDER_COLOR_HP 0x8154 +#endif + +#ifndef GL_INGR_palette_buffer +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_TEXTURE_ENV_BIAS_SGIX 0x80BE +#endif + +#ifndef GL_EXT_color_subtable +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_VERTEX_DATA_HINT_PGI 0x1A22A +#define GL_VERTEX_CONSISTENT_HINT_PGI 0x1A22B +#define GL_MATERIAL_SIDE_HINT_PGI 0x1A22C +#define GL_MAX_VERTEX_HINT_PGI 0x1A22D +#define GL_COLOR3_BIT_PGI 0x00010000 +#define GL_COLOR4_BIT_PGI 0x00020000 +#define GL_EDGEFLAG_BIT_PGI 0x00040000 +#define GL_INDEX_BIT_PGI 0x00080000 +#define GL_MAT_AMBIENT_BIT_PGI 0x00100000 +#define GL_MAT_AMBIENT_AND_DIFFUSE_BIT_PGI 0x00200000 +#define GL_MAT_DIFFUSE_BIT_PGI 0x00400000 +#define GL_MAT_EMISSION_BIT_PGI 0x00800000 +#define GL_MAT_COLOR_INDEXES_BIT_PGI 0x01000000 +#define GL_MAT_SHININESS_BIT_PGI 0x02000000 +#define GL_MAT_SPECULAR_BIT_PGI 0x04000000 +#define GL_NORMAL_BIT_PGI 0x08000000 +#define GL_TEXCOORD1_BIT_PGI 0x10000000 +#define GL_TEXCOORD2_BIT_PGI 0x20000000 +#define GL_TEXCOORD3_BIT_PGI 0x40000000 +#define GL_TEXCOORD4_BIT_PGI 0x80000000 +#define GL_VERTEX23_BIT_PGI 0x00000004 +#define GL_VERTEX4_BIT_PGI 0x00000008 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PREFER_DOUBLEBUFFER_HINT_PGI 0x1A1F8 +#define GL_CONSERVE_MEMORY_HINT_PGI 0x1A1FD +#define GL_RECLAIM_MEMORY_HINT_PGI 0x1A1FE +#define GL_NATIVE_GRAPHICS_HANDLE_PGI 0x1A202 +#define GL_NATIVE_GRAPHICS_BEGIN_HINT_PGI 0x1A203 +#define GL_NATIVE_GRAPHICS_END_HINT_PGI 0x1A204 +#define GL_ALWAYS_FAST_HINT_PGI 0x1A20C +#define GL_ALWAYS_SOFT_HINT_PGI 0x1A20D +#define GL_ALLOW_DRAW_OBJ_HINT_PGI 0x1A20E +#define GL_ALLOW_DRAW_WIN_HINT_PGI 0x1A20F +#define GL_ALLOW_DRAW_FRG_HINT_PGI 0x1A210 +#define GL_ALLOW_DRAW_MEM_HINT_PGI 0x1A211 +#define GL_STRICT_DEPTHFUNC_HINT_PGI 0x1A216 +#define GL_STRICT_LIGHTING_HINT_PGI 0x1A217 +#define GL_STRICT_SCISSOR_HINT_PGI 0x1A218 +#define GL_FULL_STIPPLE_HINT_PGI 0x1A219 +#define GL_CLIP_NEAR_HINT_PGI 0x1A220 +#define GL_CLIP_FAR_HINT_PGI 0x1A221 +#define GL_WIDE_LINE_HINT_PGI 0x1A222 +#define GL_BACK_NORMALS_HINT_PGI 0x1A223 +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_COLOR_INDEX1_EXT 0x80E2 +#define GL_COLOR_INDEX2_EXT 0x80E3 +#define GL_COLOR_INDEX4_EXT 0x80E4 +#define GL_COLOR_INDEX8_EXT 0x80E5 +#define GL_COLOR_INDEX12_EXT 0x80E6 +#define GL_COLOR_INDEX16_EXT 0x80E7 +#define GL_TEXTURE_INDEX_SIZE_EXT 0x80ED +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_CLIP_VOLUME_CLIPPING_HINT_EXT 0x80F0 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_LIST_PRIORITY_SGIX 0x8182 +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_IR_INSTRUMENT1_SGIX 0x817F +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_CALLIGRAPHIC_FRAGMENT_SGIX 0x8183 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_TEXTURE_LOD_BIAS_S_SGIX 0x818E +#define GL_TEXTURE_LOD_BIAS_T_SGIX 0x818F +#define GL_TEXTURE_LOD_BIAS_R_SGIX 0x8190 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SHADOW_AMBIENT_SGIX 0x80BF +#endif + +#ifndef GL_EXT_index_texture +#endif + +#ifndef GL_EXT_index_material +#define GL_INDEX_MATERIAL_EXT 0x81B8 +#define GL_INDEX_MATERIAL_PARAMETER_EXT 0x81B9 +#define GL_INDEX_MATERIAL_FACE_EXT 0x81BA +#endif + +#ifndef GL_EXT_index_func +#define GL_INDEX_TEST_EXT 0x81B5 +#define GL_INDEX_TEST_FUNC_EXT 0x81B6 +#define GL_INDEX_TEST_REF_EXT 0x81B7 +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_IUI_V2F_EXT 0x81AD +#define GL_IUI_V3F_EXT 0x81AE +#define GL_IUI_N3F_V2F_EXT 0x81AF +#define GL_IUI_N3F_V3F_EXT 0x81B0 +#define GL_T2F_IUI_V2F_EXT 0x81B1 +#define GL_T2F_IUI_V3F_EXT 0x81B2 +#define GL_T2F_IUI_N3F_V2F_EXT 0x81B3 +#define GL_T2F_IUI_N3F_V3F_EXT 0x81B4 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_ARRAY_ELEMENT_LOCK_FIRST_EXT 0x81A8 +#define GL_ARRAY_ELEMENT_LOCK_COUNT_EXT 0x81A9 +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_CULL_VERTEX_EXT 0x81AA +#define GL_CULL_VERTEX_EYE_POSITION_EXT 0x81AB +#define GL_CULL_VERTEX_OBJECT_POSITION_EXT 0x81AC +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_YCRCB_422_SGIX 0x81BB +#define GL_YCRCB_444_SGIX 0x81BC +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_FRAGMENT_LIGHTING_SGIX 0x8400 +#define GL_FRAGMENT_COLOR_MATERIAL_SGIX 0x8401 +#define GL_FRAGMENT_COLOR_MATERIAL_FACE_SGIX 0x8402 +#define GL_FRAGMENT_COLOR_MATERIAL_PARAMETER_SGIX 0x8403 +#define GL_MAX_FRAGMENT_LIGHTS_SGIX 0x8404 +#define GL_MAX_ACTIVE_LIGHTS_SGIX 0x8405 +#define GL_CURRENT_RASTER_NORMAL_SGIX 0x8406 +#define GL_LIGHT_ENV_MODE_SGIX 0x8407 +#define GL_FRAGMENT_LIGHT_MODEL_LOCAL_VIEWER_SGIX 0x8408 +#define GL_FRAGMENT_LIGHT_MODEL_TWO_SIDE_SGIX 0x8409 +#define GL_FRAGMENT_LIGHT_MODEL_AMBIENT_SGIX 0x840A +#define GL_FRAGMENT_LIGHT_MODEL_NORMAL_INTERPOLATION_SGIX 0x840B +#define GL_FRAGMENT_LIGHT0_SGIX 0x840C +#define GL_FRAGMENT_LIGHT1_SGIX 0x840D +#define GL_FRAGMENT_LIGHT2_SGIX 0x840E +#define GL_FRAGMENT_LIGHT3_SGIX 0x840F +#define GL_FRAGMENT_LIGHT4_SGIX 0x8410 +#define GL_FRAGMENT_LIGHT5_SGIX 0x8411 +#define GL_FRAGMENT_LIGHT6_SGIX 0x8412 +#define GL_FRAGMENT_LIGHT7_SGIX 0x8413 +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_RASTER_POSITION_UNCLIPPED_IBM 0x19262 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_TEXTURE_LIGHTING_MODE_HP 0x8167 +#define GL_TEXTURE_POST_SPECULAR_HP 0x8168 +#define GL_TEXTURE_PRE_SPECULAR_HP 0x8169 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_MAX_ELEMENTS_VERTICES_EXT 0x80E8 +#define GL_MAX_ELEMENTS_INDICES_EXT 0x80E9 +#endif + +#ifndef GL_WIN_phong_shading +#define GL_PHONG_WIN 0x80EA +#define GL_PHONG_HINT_WIN 0x80EB +#endif + +#ifndef GL_WIN_specular_fog +#define GL_FOG_SPECULAR_TEXTURE_WIN 0x80EC +#endif + +#ifndef GL_EXT_light_texture +#define GL_FRAGMENT_MATERIAL_EXT 0x8349 +#define GL_FRAGMENT_NORMAL_EXT 0x834A +#define GL_FRAGMENT_COLOR_EXT 0x834C +#define GL_ATTENUATION_EXT 0x834D +#define GL_SHADOW_ATTENUATION_EXT 0x834E +#define GL_TEXTURE_APPLICATION_MODE_EXT 0x834F +#define GL_TEXTURE_LIGHT_EXT 0x8350 +#define GL_TEXTURE_MATERIAL_FACE_EXT 0x8351 +#define GL_TEXTURE_MATERIAL_PARAMETER_EXT 0x8352 +/* reuse GL_FRAGMENT_DEPTH_EXT */ +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_ALPHA_MIN_SGIX 0x8320 +#define GL_ALPHA_MAX_SGIX 0x8321 +#endif + +#ifndef GL_SGIX_impact_pixel_texture +#define GL_PIXEL_TEX_GEN_Q_CEILING_SGIX 0x8184 +#define GL_PIXEL_TEX_GEN_Q_ROUND_SGIX 0x8185 +#define GL_PIXEL_TEX_GEN_Q_FLOOR_SGIX 0x8186 +#define GL_PIXEL_TEX_GEN_ALPHA_REPLACE_SGIX 0x8187 +#define GL_PIXEL_TEX_GEN_ALPHA_NO_REPLACE_SGIX 0x8188 +#define GL_PIXEL_TEX_GEN_ALPHA_LS_SGIX 0x8189 +#define GL_PIXEL_TEX_GEN_ALPHA_MS_SGIX 0x818A +#endif + +#ifndef GL_EXT_bgra +#define GL_BGR_EXT 0x80E0 +#define GL_BGRA_EXT 0x80E1 +#endif + +#ifndef GL_SGIX_async +#define GL_ASYNC_MARKER_SGIX 0x8329 +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_ASYNC_TEX_IMAGE_SGIX 0x835C +#define GL_ASYNC_DRAW_PIXELS_SGIX 0x835D +#define GL_ASYNC_READ_PIXELS_SGIX 0x835E +#define GL_MAX_ASYNC_TEX_IMAGE_SGIX 0x835F +#define GL_MAX_ASYNC_DRAW_PIXELS_SGIX 0x8360 +#define GL_MAX_ASYNC_READ_PIXELS_SGIX 0x8361 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_ASYNC_HISTOGRAM_SGIX 0x832C +#define GL_MAX_ASYNC_HISTOGRAM_SGIX 0x832D +#endif + +#ifndef GL_INTEL_texture_scissor +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_PARALLEL_ARRAYS_INTEL 0x83F4 +#define GL_VERTEX_ARRAY_PARALLEL_POINTERS_INTEL 0x83F5 +#define GL_NORMAL_ARRAY_PARALLEL_POINTERS_INTEL 0x83F6 +#define GL_COLOR_ARRAY_PARALLEL_POINTERS_INTEL 0x83F7 +#define GL_TEXTURE_COORD_ARRAY_PARALLEL_POINTERS_INTEL 0x83F8 +#endif + +#ifndef GL_HP_occlusion_test +#define GL_OCCLUSION_TEST_HP 0x8165 +#define GL_OCCLUSION_TEST_RESULT_HP 0x8166 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_PIXEL_TRANSFORM_2D_EXT 0x8330 +#define GL_PIXEL_MAG_FILTER_EXT 0x8331 +#define GL_PIXEL_MIN_FILTER_EXT 0x8332 +#define GL_PIXEL_CUBIC_WEIGHT_EXT 0x8333 +#define GL_CUBIC_EXT 0x8334 +#define GL_AVERAGE_EXT 0x8335 +#define GL_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8336 +#define GL_MAX_PIXEL_TRANSFORM_2D_STACK_DEPTH_EXT 0x8337 +#define GL_PIXEL_TRANSFORM_2D_MATRIX_EXT 0x8338 +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_SHARED_TEXTURE_PALETTE_EXT 0x81FB +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_LIGHT_MODEL_COLOR_CONTROL_EXT 0x81F8 +#define GL_SINGLE_COLOR_EXT 0x81F9 +#define GL_SEPARATE_SPECULAR_COLOR_EXT 0x81FA +#endif + +#ifndef GL_EXT_secondary_color +#define GL_COLOR_SUM_EXT 0x8458 +#define GL_CURRENT_SECONDARY_COLOR_EXT 0x8459 +#define GL_SECONDARY_COLOR_ARRAY_SIZE_EXT 0x845A +#define GL_SECONDARY_COLOR_ARRAY_TYPE_EXT 0x845B +#define GL_SECONDARY_COLOR_ARRAY_STRIDE_EXT 0x845C +#define GL_SECONDARY_COLOR_ARRAY_POINTER_EXT 0x845D +#define GL_SECONDARY_COLOR_ARRAY_EXT 0x845E +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_PERTURB_EXT 0x85AE +#define GL_TEXTURE_NORMAL_EXT 0x85AF +#endif + +#ifndef GL_EXT_multi_draw_arrays +#endif + +#ifndef GL_EXT_fog_coord +#define GL_FOG_COORDINATE_SOURCE_EXT 0x8450 +#define GL_FOG_COORDINATE_EXT 0x8451 +#define GL_FRAGMENT_DEPTH_EXT 0x8452 +#define GL_CURRENT_FOG_COORDINATE_EXT 0x8453 +#define GL_FOG_COORDINATE_ARRAY_TYPE_EXT 0x8454 +#define GL_FOG_COORDINATE_ARRAY_STRIDE_EXT 0x8455 +#define GL_FOG_COORDINATE_ARRAY_POINTER_EXT 0x8456 +#define GL_FOG_COORDINATE_ARRAY_EXT 0x8457 +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_SCREEN_COORDINATES_REND 0x8490 +#define GL_INVERTED_SCREEN_W_REND 0x8491 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_TANGENT_ARRAY_EXT 0x8439 +#define GL_BINORMAL_ARRAY_EXT 0x843A +#define GL_CURRENT_TANGENT_EXT 0x843B +#define GL_CURRENT_BINORMAL_EXT 0x843C +#define GL_TANGENT_ARRAY_TYPE_EXT 0x843E +#define GL_TANGENT_ARRAY_STRIDE_EXT 0x843F +#define GL_BINORMAL_ARRAY_TYPE_EXT 0x8440 +#define GL_BINORMAL_ARRAY_STRIDE_EXT 0x8441 +#define GL_TANGENT_ARRAY_POINTER_EXT 0x8442 +#define GL_BINORMAL_ARRAY_POINTER_EXT 0x8443 +#define GL_MAP1_TANGENT_EXT 0x8444 +#define GL_MAP2_TANGENT_EXT 0x8445 +#define GL_MAP1_BINORMAL_EXT 0x8446 +#define GL_MAP2_BINORMAL_EXT 0x8447 +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_COMBINE_EXT 0x8570 +#define GL_COMBINE_RGB_EXT 0x8571 +#define GL_COMBINE_ALPHA_EXT 0x8572 +#define GL_RGB_SCALE_EXT 0x8573 +#define GL_ADD_SIGNED_EXT 0x8574 +#define GL_INTERPOLATE_EXT 0x8575 +#define GL_CONSTANT_EXT 0x8576 +#define GL_PRIMARY_COLOR_EXT 0x8577 +#define GL_PREVIOUS_EXT 0x8578 +#define GL_SOURCE0_RGB_EXT 0x8580 +#define GL_SOURCE1_RGB_EXT 0x8581 +#define GL_SOURCE2_RGB_EXT 0x8582 +#define GL_SOURCE0_ALPHA_EXT 0x8588 +#define GL_SOURCE1_ALPHA_EXT 0x8589 +#define GL_SOURCE2_ALPHA_EXT 0x858A +#define GL_OPERAND0_RGB_EXT 0x8590 +#define GL_OPERAND1_RGB_EXT 0x8591 +#define GL_OPERAND2_RGB_EXT 0x8592 +#define GL_OPERAND0_ALPHA_EXT 0x8598 +#define GL_OPERAND1_ALPHA_EXT 0x8599 +#define GL_OPERAND2_ALPHA_EXT 0x859A +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_LIGHT_MODEL_SPECULAR_VECTOR_APPLE 0x85B0 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_TRANSFORM_HINT_APPLE 0x85B1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_FOG_SCALE_SGIX 0x81FC +#define GL_FOG_SCALE_VALUE_SGIX 0x81FD +#endif + +#ifndef GL_SUNX_constant_data +#define GL_UNPACK_CONSTANT_DATA_SUNX 0x81D5 +#define GL_TEXTURE_CONSTANT_DATA_SUNX 0x81D6 +#endif + +#ifndef GL_SUN_global_alpha +#define GL_GLOBAL_ALPHA_SUN 0x81D9 +#define GL_GLOBAL_ALPHA_FACTOR_SUN 0x81DA +#endif + +#ifndef GL_SUN_triangle_list +#define GL_RESTART_SUN 0x0001 +#define GL_REPLACE_MIDDLE_SUN 0x0002 +#define GL_REPLACE_OLDEST_SUN 0x0003 +#define GL_TRIANGLE_LIST_SUN 0x81D7 +#define GL_REPLACEMENT_CODE_SUN 0x81D8 +#define GL_REPLACEMENT_CODE_ARRAY_SUN 0x85C0 +#define GL_REPLACEMENT_CODE_ARRAY_TYPE_SUN 0x85C1 +#define GL_REPLACEMENT_CODE_ARRAY_STRIDE_SUN 0x85C2 +#define GL_REPLACEMENT_CODE_ARRAY_POINTER_SUN 0x85C3 +#define GL_R1UI_V3F_SUN 0x85C4 +#define GL_R1UI_C4UB_V3F_SUN 0x85C5 +#define GL_R1UI_C3F_V3F_SUN 0x85C6 +#define GL_R1UI_N3F_V3F_SUN 0x85C7 +#define GL_R1UI_C4F_N3F_V3F_SUN 0x85C8 +#define GL_R1UI_T2F_V3F_SUN 0x85C9 +#define GL_R1UI_T2F_N3F_V3F_SUN 0x85CA +#define GL_R1UI_T2F_C4F_N3F_V3F_SUN 0x85CB +#endif + +#ifndef GL_SUN_vertex +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_BLEND_DST_RGB_EXT 0x80C8 +#define GL_BLEND_SRC_RGB_EXT 0x80C9 +#define GL_BLEND_DST_ALPHA_EXT 0x80CA +#define GL_BLEND_SRC_ALPHA_EXT 0x80CB +#endif + +#ifndef GL_INGR_color_clamp +#define GL_RED_MIN_CLAMP_INGR 0x8560 +#define GL_GREEN_MIN_CLAMP_INGR 0x8561 +#define GL_BLUE_MIN_CLAMP_INGR 0x8562 +#define GL_ALPHA_MIN_CLAMP_INGR 0x8563 +#define GL_RED_MAX_CLAMP_INGR 0x8564 +#define GL_GREEN_MAX_CLAMP_INGR 0x8565 +#define GL_BLUE_MAX_CLAMP_INGR 0x8566 +#define GL_ALPHA_MAX_CLAMP_INGR 0x8567 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INTERLACE_READ_INGR 0x8568 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_INCR_WRAP_EXT 0x8507 +#define GL_DECR_WRAP_EXT 0x8508 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_422_EXT 0x80CC +#define GL_422_REV_EXT 0x80CD +#define GL_422_AVERAGE_EXT 0x80CE +#define GL_422_REV_AVERAGE_EXT 0x80CF +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NORMAL_MAP_NV 0x8511 +#define GL_REFLECTION_MAP_NV 0x8512 +#endif + +#ifndef GL_EXT_texture_cube_map +#define GL_NORMAL_MAP_EXT 0x8511 +#define GL_REFLECTION_MAP_EXT 0x8512 +#define GL_TEXTURE_CUBE_MAP_EXT 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_WRAP_BORDER_SUN 0x81D4 +#endif + +#ifndef GL_EXT_texture_env_add +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_MODELVIEW0_STACK_DEPTH_EXT GL_MODELVIEW_STACK_DEPTH +#define GL_MODELVIEW1_STACK_DEPTH_EXT 0x8502 +#define GL_MODELVIEW0_MATRIX_EXT GL_MODELVIEW_MATRIX +#define GL_MODELVIEW1_MATRIX_EXT 0x8506 +#define GL_VERTEX_WEIGHTING_EXT 0x8509 +#define GL_MODELVIEW0_EXT GL_MODELVIEW +#define GL_MODELVIEW1_EXT 0x850A +#define GL_CURRENT_VERTEX_WEIGHT_EXT 0x850B +#define GL_VERTEX_WEIGHT_ARRAY_EXT 0x850C +#define GL_VERTEX_WEIGHT_ARRAY_SIZE_EXT 0x850D +#define GL_VERTEX_WEIGHT_ARRAY_TYPE_EXT 0x850E +#define GL_VERTEX_WEIGHT_ARRAY_STRIDE_EXT 0x850F +#define GL_VERTEX_WEIGHT_ARRAY_POINTER_EXT 0x8510 +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_MAX_SHININESS_NV 0x8504 +#define GL_MAX_SPOT_EXPONENT_NV 0x8505 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_NV 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_NV 0x851E +#define GL_VERTEX_ARRAY_RANGE_VALID_NV 0x851F +#define GL_MAX_VERTEX_ARRAY_RANGE_ELEMENT_NV 0x8520 +#define GL_VERTEX_ARRAY_RANGE_POINTER_NV 0x8521 +#endif + +#ifndef GL_NV_register_combiners +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +/* reuse GL_TEXTURE0_ARB */ +/* reuse GL_TEXTURE1_ARB */ +/* reuse GL_ZERO */ +/* reuse GL_NONE */ +/* reuse GL_FOG */ +#endif + +#ifndef GL_NV_fog_distance +#define GL_FOG_DISTANCE_MODE_NV 0x855A +#define GL_EYE_RADIAL_NV 0x855B +#define GL_EYE_PLANE_ABSOLUTE_NV 0x855C +/* reuse GL_EYE_PLANE */ +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_EMBOSS_LIGHT_NV 0x855D +#define GL_EMBOSS_CONSTANT_NV 0x855E +#define GL_EMBOSS_MAP_NV 0x855F +#endif + +#ifndef GL_NV_blend_square +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_COMBINE4_NV 0x8503 +#define GL_SOURCE3_RGB_NV 0x8583 +#define GL_SOURCE3_ALPHA_NV 0x858B +#define GL_OPERAND3_RGB_NV 0x8593 +#define GL_OPERAND3_ALPHA_NV 0x859B +#endif + +#ifndef GL_MESA_resize_buffers +#endif + +#ifndef GL_MESA_window_pos +#endif + +#ifndef GL_EXT_texture_compression_s3tc +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_CULL_VERTEX_IBM 103050 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_VERTEX_ARRAY_LIST_IBM 103070 +#define GL_NORMAL_ARRAY_LIST_IBM 103071 +#define GL_COLOR_ARRAY_LIST_IBM 103072 +#define GL_INDEX_ARRAY_LIST_IBM 103073 +#define GL_TEXTURE_COORD_ARRAY_LIST_IBM 103074 +#define GL_EDGE_FLAG_ARRAY_LIST_IBM 103075 +#define GL_FOG_COORDINATE_ARRAY_LIST_IBM 103076 +#define GL_SECONDARY_COLOR_ARRAY_LIST_IBM 103077 +#define GL_VERTEX_ARRAY_LIST_STRIDE_IBM 103080 +#define GL_NORMAL_ARRAY_LIST_STRIDE_IBM 103081 +#define GL_COLOR_ARRAY_LIST_STRIDE_IBM 103082 +#define GL_INDEX_ARRAY_LIST_STRIDE_IBM 103083 +#define GL_TEXTURE_COORD_ARRAY_LIST_STRIDE_IBM 103084 +#define GL_EDGE_FLAG_ARRAY_LIST_STRIDE_IBM 103085 +#define GL_FOG_COORDINATE_ARRAY_LIST_STRIDE_IBM 103086 +#define GL_SECONDARY_COLOR_ARRAY_LIST_STRIDE_IBM 103087 +#endif + +#ifndef GL_SGIX_subsample +#define GL_PACK_SUBSAMPLE_RATE_SGIX 0x85A0 +#define GL_UNPACK_SUBSAMPLE_RATE_SGIX 0x85A1 +#define GL_PIXEL_SUBSAMPLE_4444_SGIX 0x85A2 +#define GL_PIXEL_SUBSAMPLE_2424_SGIX 0x85A3 +#define GL_PIXEL_SUBSAMPLE_4242_SGIX 0x85A4 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_YCRCB_SGIX 0x8318 +#define GL_YCRCBA_SGIX 0x8319 +#endif + +#ifndef GL_SGI_depth_pass_instrument +#define GL_DEPTH_PASS_INSTRUMENT_SGIX 0x8310 +#define GL_DEPTH_PASS_INSTRUMENT_COUNTERS_SGIX 0x8311 +#define GL_DEPTH_PASS_INSTRUMENT_MAX_SGIX 0x8312 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_COMPRESSED_RGB_FXT1_3DFX 0x86B0 +#define GL_COMPRESSED_RGBA_FXT1_3DFX 0x86B1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_MULTISAMPLE_3DFX 0x86B2 +#define GL_SAMPLE_BUFFERS_3DFX 0x86B3 +#define GL_SAMPLES_3DFX 0x86B4 +#define GL_MULTISAMPLE_BIT_3DFX 0x20000000 +#endif + +#ifndef GL_3DFX_tbuffer +#endif + +#ifndef GL_EXT_multisample +#define GL_MULTISAMPLE_EXT 0x809D +#define GL_SAMPLE_ALPHA_TO_MASK_EXT 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_EXT 0x809F +#define GL_SAMPLE_MASK_EXT 0x80A0 +#define GL_1PASS_EXT 0x80A1 +#define GL_2PASS_0_EXT 0x80A2 +#define GL_2PASS_1_EXT 0x80A3 +#define GL_4PASS_0_EXT 0x80A4 +#define GL_4PASS_1_EXT 0x80A5 +#define GL_4PASS_2_EXT 0x80A6 +#define GL_4PASS_3_EXT 0x80A7 +#define GL_SAMPLE_BUFFERS_EXT 0x80A8 +#define GL_SAMPLES_EXT 0x80A9 +#define GL_SAMPLE_MASK_VALUE_EXT 0x80AA +#define GL_SAMPLE_MASK_INVERT_EXT 0x80AB +#define GL_SAMPLE_PATTERN_EXT 0x80AC +#define GL_MULTISAMPLE_BIT_EXT 0x20000000 +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_VERTEX_PRECLIP_SGIX 0x83EE +#define GL_VERTEX_PRECLIP_HINT_SGIX 0x83EF +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_CONVOLUTION_HINT_SGIX 0x8316 +#endif + +#ifndef GL_SGIX_resample +#define GL_PACK_RESAMPLE_SGIX 0x842C +#define GL_UNPACK_RESAMPLE_SGIX 0x842D +#define GL_RESAMPLE_REPLICATE_SGIX 0x842E +#define GL_RESAMPLE_ZERO_FILL_SGIX 0x842F +#define GL_RESAMPLE_DECIMATE_SGIX 0x8430 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_EYE_DISTANCE_TO_POINT_SGIS 0x81F0 +#define GL_OBJECT_DISTANCE_TO_POINT_SGIS 0x81F1 +#define GL_EYE_DISTANCE_TO_LINE_SGIS 0x81F2 +#define GL_OBJECT_DISTANCE_TO_LINE_SGIS 0x81F3 +#define GL_EYE_POINT_SGIS 0x81F4 +#define GL_OBJECT_POINT_SGIS 0x81F5 +#define GL_EYE_LINE_SGIS 0x81F6 +#define GL_OBJECT_LINE_SGIS 0x81F7 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_TEXTURE_COLOR_WRITEMASK_SGIS 0x81EF +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_DOT3_RGB_EXT 0x8740 +#define GL_DOT3_RGBA_EXT 0x8741 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_MIRROR_CLAMP_ATI 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_ATI 0x8743 +#endif + +#ifndef GL_NV_fence +#define GL_ALL_COMPLETED_NV 0x84F2 +#define GL_FENCE_STATUS_NV 0x84F3 +#define GL_FENCE_CONDITION_NV 0x84F4 +#endif + +#ifndef GL_IBM_texture_mirrored_repeat +#define GL_MIRRORED_REPEAT_IBM 0x8370 +#endif + +#ifndef GL_NV_evaluators +#define GL_EVAL_2D_NV 0x86C0 +#define GL_EVAL_TRIANGULAR_2D_NV 0x86C1 +#define GL_MAP_TESSELLATION_NV 0x86C2 +#define GL_MAP_ATTRIB_U_ORDER_NV 0x86C3 +#define GL_MAP_ATTRIB_V_ORDER_NV 0x86C4 +#define GL_EVAL_FRACTIONAL_TESSELLATION_NV 0x86C5 +#define GL_EVAL_VERTEX_ATTRIB0_NV 0x86C6 +#define GL_EVAL_VERTEX_ATTRIB1_NV 0x86C7 +#define GL_EVAL_VERTEX_ATTRIB2_NV 0x86C8 +#define GL_EVAL_VERTEX_ATTRIB3_NV 0x86C9 +#define GL_EVAL_VERTEX_ATTRIB4_NV 0x86CA +#define GL_EVAL_VERTEX_ATTRIB5_NV 0x86CB +#define GL_EVAL_VERTEX_ATTRIB6_NV 0x86CC +#define GL_EVAL_VERTEX_ATTRIB7_NV 0x86CD +#define GL_EVAL_VERTEX_ATTRIB8_NV 0x86CE +#define GL_EVAL_VERTEX_ATTRIB9_NV 0x86CF +#define GL_EVAL_VERTEX_ATTRIB10_NV 0x86D0 +#define GL_EVAL_VERTEX_ATTRIB11_NV 0x86D1 +#define GL_EVAL_VERTEX_ATTRIB12_NV 0x86D2 +#define GL_EVAL_VERTEX_ATTRIB13_NV 0x86D3 +#define GL_EVAL_VERTEX_ATTRIB14_NV 0x86D4 +#define GL_EVAL_VERTEX_ATTRIB15_NV 0x86D5 +#define GL_MAX_MAP_TESSELLATION_NV 0x86D6 +#define GL_MAX_RATIONAL_EVAL_ORDER_NV 0x86D7 +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_DEPTH_STENCIL_NV 0x84F9 +#define GL_UNSIGNED_INT_24_8_NV 0x84FA +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_PER_STAGE_CONSTANTS_NV 0x8535 +#endif + +#ifndef GL_NV_texture_compression_vtc +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#endif + +#ifndef GL_NV_texture_shader +#define GL_OFFSET_TEXTURE_RECTANGLE_NV 0x864C +#define GL_OFFSET_TEXTURE_RECTANGLE_SCALE_NV 0x864D +#define GL_DOT_PRODUCT_TEXTURE_RECTANGLE_NV 0x864E +#define GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV 0x86D9 +#define GL_UNSIGNED_INT_S8_S8_8_8_NV 0x86DA +#define GL_UNSIGNED_INT_8_8_S8_S8_REV_NV 0x86DB +#define GL_DSDT_MAG_INTENSITY_NV 0x86DC +#define GL_SHADER_CONSISTENT_NV 0x86DD +#define GL_TEXTURE_SHADER_NV 0x86DE +#define GL_SHADER_OPERATION_NV 0x86DF +#define GL_CULL_MODES_NV 0x86E0 +#define GL_OFFSET_TEXTURE_MATRIX_NV 0x86E1 +#define GL_OFFSET_TEXTURE_SCALE_NV 0x86E2 +#define GL_OFFSET_TEXTURE_BIAS_NV 0x86E3 +#define GL_OFFSET_TEXTURE_2D_MATRIX_NV GL_OFFSET_TEXTURE_MATRIX_NV +#define GL_OFFSET_TEXTURE_2D_SCALE_NV GL_OFFSET_TEXTURE_SCALE_NV +#define GL_OFFSET_TEXTURE_2D_BIAS_NV GL_OFFSET_TEXTURE_BIAS_NV +#define GL_PREVIOUS_TEXTURE_INPUT_NV 0x86E4 +#define GL_CONST_EYE_NV 0x86E5 +#define GL_PASS_THROUGH_NV 0x86E6 +#define GL_CULL_FRAGMENT_NV 0x86E7 +#define GL_OFFSET_TEXTURE_2D_NV 0x86E8 +#define GL_DEPENDENT_AR_TEXTURE_2D_NV 0x86E9 +#define GL_DEPENDENT_GB_TEXTURE_2D_NV 0x86EA +#define GL_DOT_PRODUCT_NV 0x86EC +#define GL_DOT_PRODUCT_DEPTH_REPLACE_NV 0x86ED +#define GL_DOT_PRODUCT_TEXTURE_2D_NV 0x86EE +#define GL_DOT_PRODUCT_TEXTURE_CUBE_MAP_NV 0x86F0 +#define GL_DOT_PRODUCT_DIFFUSE_CUBE_MAP_NV 0x86F1 +#define GL_DOT_PRODUCT_REFLECT_CUBE_MAP_NV 0x86F2 +#define GL_DOT_PRODUCT_CONST_EYE_REFLECT_CUBE_MAP_NV 0x86F3 +#define GL_HILO_NV 0x86F4 +#define GL_DSDT_NV 0x86F5 +#define GL_DSDT_MAG_NV 0x86F6 +#define GL_DSDT_MAG_VIB_NV 0x86F7 +#define GL_HILO16_NV 0x86F8 +#define GL_SIGNED_HILO_NV 0x86F9 +#define GL_SIGNED_HILO16_NV 0x86FA +#define GL_SIGNED_RGBA_NV 0x86FB +#define GL_SIGNED_RGBA8_NV 0x86FC +#define GL_SIGNED_RGB_NV 0x86FE +#define GL_SIGNED_RGB8_NV 0x86FF +#define GL_SIGNED_LUMINANCE_NV 0x8701 +#define GL_SIGNED_LUMINANCE8_NV 0x8702 +#define GL_SIGNED_LUMINANCE_ALPHA_NV 0x8703 +#define GL_SIGNED_LUMINANCE8_ALPHA8_NV 0x8704 +#define GL_SIGNED_ALPHA_NV 0x8705 +#define GL_SIGNED_ALPHA8_NV 0x8706 +#define GL_SIGNED_INTENSITY_NV 0x8707 +#define GL_SIGNED_INTENSITY8_NV 0x8708 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT8_MAG8_NV 0x870A +#define GL_DSDT8_MAG8_INTENSITY8_NV 0x870B +#define GL_SIGNED_RGB_UNSIGNED_ALPHA_NV 0x870C +#define GL_SIGNED_RGB8_UNSIGNED_ALPHA8_NV 0x870D +#define GL_HI_SCALE_NV 0x870E +#define GL_LO_SCALE_NV 0x870F +#define GL_DS_SCALE_NV 0x8710 +#define GL_DT_SCALE_NV 0x8711 +#define GL_MAGNITUDE_SCALE_NV 0x8712 +#define GL_VIBRANCE_SCALE_NV 0x8713 +#define GL_HI_BIAS_NV 0x8714 +#define GL_LO_BIAS_NV 0x8715 +#define GL_DS_BIAS_NV 0x8716 +#define GL_DT_BIAS_NV 0x8717 +#define GL_MAGNITUDE_BIAS_NV 0x8718 +#define GL_VIBRANCE_BIAS_NV 0x8719 +#define GL_TEXTURE_BORDER_VALUES_NV 0x871A +#define GL_TEXTURE_HI_SIZE_NV 0x871B +#define GL_TEXTURE_LO_SIZE_NV 0x871C +#define GL_TEXTURE_DS_SIZE_NV 0x871D +#define GL_TEXTURE_DT_SIZE_NV 0x871E +#define GL_TEXTURE_MAG_SIZE_NV 0x871F +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_DOT_PRODUCT_TEXTURE_3D_NV 0x86EF +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_VERTEX_ARRAY_RANGE_WITHOUT_FLUSH_NV 0x8533 +#endif + +#ifndef GL_NV_vertex_program +#define GL_VERTEX_PROGRAM_NV 0x8620 +#define GL_VERTEX_STATE_PROGRAM_NV 0x8621 +#define GL_ATTRIB_ARRAY_SIZE_NV 0x8623 +#define GL_ATTRIB_ARRAY_STRIDE_NV 0x8624 +#define GL_ATTRIB_ARRAY_TYPE_NV 0x8625 +#define GL_CURRENT_ATTRIB_NV 0x8626 +#define GL_PROGRAM_LENGTH_NV 0x8627 +#define GL_PROGRAM_STRING_NV 0x8628 +#define GL_MODELVIEW_PROJECTION_NV 0x8629 +#define GL_IDENTITY_NV 0x862A +#define GL_INVERSE_NV 0x862B +#define GL_TRANSPOSE_NV 0x862C +#define GL_INVERSE_TRANSPOSE_NV 0x862D +#define GL_MAX_TRACK_MATRIX_STACK_DEPTH_NV 0x862E +#define GL_MAX_TRACK_MATRICES_NV 0x862F +#define GL_MATRIX0_NV 0x8630 +#define GL_MATRIX1_NV 0x8631 +#define GL_MATRIX2_NV 0x8632 +#define GL_MATRIX3_NV 0x8633 +#define GL_MATRIX4_NV 0x8634 +#define GL_MATRIX5_NV 0x8635 +#define GL_MATRIX6_NV 0x8636 +#define GL_MATRIX7_NV 0x8637 +#define GL_CURRENT_MATRIX_STACK_DEPTH_NV 0x8640 +#define GL_CURRENT_MATRIX_NV 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_NV 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_NV 0x8643 +#define GL_PROGRAM_PARAMETER_NV 0x8644 +#define GL_ATTRIB_ARRAY_POINTER_NV 0x8645 +#define GL_PROGRAM_TARGET_NV 0x8646 +#define GL_PROGRAM_RESIDENT_NV 0x8647 +#define GL_TRACK_MATRIX_NV 0x8648 +#define GL_TRACK_MATRIX_TRANSFORM_NV 0x8649 +#define GL_VERTEX_PROGRAM_BINDING_NV 0x864A +#define GL_PROGRAM_ERROR_POSITION_NV 0x864B +#define GL_VERTEX_ATTRIB_ARRAY0_NV 0x8650 +#define GL_VERTEX_ATTRIB_ARRAY1_NV 0x8651 +#define GL_VERTEX_ATTRIB_ARRAY2_NV 0x8652 +#define GL_VERTEX_ATTRIB_ARRAY3_NV 0x8653 +#define GL_VERTEX_ATTRIB_ARRAY4_NV 0x8654 +#define GL_VERTEX_ATTRIB_ARRAY5_NV 0x8655 +#define GL_VERTEX_ATTRIB_ARRAY6_NV 0x8656 +#define GL_VERTEX_ATTRIB_ARRAY7_NV 0x8657 +#define GL_VERTEX_ATTRIB_ARRAY8_NV 0x8658 +#define GL_VERTEX_ATTRIB_ARRAY9_NV 0x8659 +#define GL_VERTEX_ATTRIB_ARRAY10_NV 0x865A +#define GL_VERTEX_ATTRIB_ARRAY11_NV 0x865B +#define GL_VERTEX_ATTRIB_ARRAY12_NV 0x865C +#define GL_VERTEX_ATTRIB_ARRAY13_NV 0x865D +#define GL_VERTEX_ATTRIB_ARRAY14_NV 0x865E +#define GL_VERTEX_ATTRIB_ARRAY15_NV 0x865F +#define GL_MAP1_VERTEX_ATTRIB0_4_NV 0x8660 +#define GL_MAP1_VERTEX_ATTRIB1_4_NV 0x8661 +#define GL_MAP1_VERTEX_ATTRIB2_4_NV 0x8662 +#define GL_MAP1_VERTEX_ATTRIB3_4_NV 0x8663 +#define GL_MAP1_VERTEX_ATTRIB4_4_NV 0x8664 +#define GL_MAP1_VERTEX_ATTRIB5_4_NV 0x8665 +#define GL_MAP1_VERTEX_ATTRIB6_4_NV 0x8666 +#define GL_MAP1_VERTEX_ATTRIB7_4_NV 0x8667 +#define GL_MAP1_VERTEX_ATTRIB8_4_NV 0x8668 +#define GL_MAP1_VERTEX_ATTRIB9_4_NV 0x8669 +#define GL_MAP1_VERTEX_ATTRIB10_4_NV 0x866A +#define GL_MAP1_VERTEX_ATTRIB11_4_NV 0x866B +#define GL_MAP1_VERTEX_ATTRIB12_4_NV 0x866C +#define GL_MAP1_VERTEX_ATTRIB13_4_NV 0x866D +#define GL_MAP1_VERTEX_ATTRIB14_4_NV 0x866E +#define GL_MAP1_VERTEX_ATTRIB15_4_NV 0x866F +#define GL_MAP2_VERTEX_ATTRIB0_4_NV 0x8670 +#define GL_MAP2_VERTEX_ATTRIB1_4_NV 0x8671 +#define GL_MAP2_VERTEX_ATTRIB2_4_NV 0x8672 +#define GL_MAP2_VERTEX_ATTRIB3_4_NV 0x8673 +#define GL_MAP2_VERTEX_ATTRIB4_4_NV 0x8674 +#define GL_MAP2_VERTEX_ATTRIB5_4_NV 0x8675 +#define GL_MAP2_VERTEX_ATTRIB6_4_NV 0x8676 +#define GL_MAP2_VERTEX_ATTRIB7_4_NV 0x8677 +#define GL_MAP2_VERTEX_ATTRIB8_4_NV 0x8678 +#define GL_MAP2_VERTEX_ATTRIB9_4_NV 0x8679 +#define GL_MAP2_VERTEX_ATTRIB10_4_NV 0x867A +#define GL_MAP2_VERTEX_ATTRIB11_4_NV 0x867B +#define GL_MAP2_VERTEX_ATTRIB12_4_NV 0x867C +#define GL_MAP2_VERTEX_ATTRIB13_4_NV 0x867D +#define GL_MAP2_VERTEX_ATTRIB14_4_NV 0x867E +#define GL_MAP2_VERTEX_ATTRIB15_4_NV 0x867F +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_TEXTURE_MAX_CLAMP_S_SGIX 0x8369 +#define GL_TEXTURE_MAX_CLAMP_T_SGIX 0x836A +#define GL_TEXTURE_MAX_CLAMP_R_SGIX 0x836B +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SCALEBIAS_HINT_SGIX 0x8322 +#endif + +#ifndef GL_OML_interlace +#define GL_INTERLACE_OML 0x8980 +#define GL_INTERLACE_READ_OML 0x8981 +#endif + +#ifndef GL_OML_subsample +#define GL_FORMAT_SUBSAMPLE_24_24_OML 0x8982 +#define GL_FORMAT_SUBSAMPLE_244_244_OML 0x8983 +#endif + +#ifndef GL_OML_resample +#define GL_PACK_RESAMPLE_OML 0x8984 +#define GL_UNPACK_RESAMPLE_OML 0x8985 +#define GL_RESAMPLE_REPLICATE_OML 0x8986 +#define GL_RESAMPLE_ZERO_FILL_OML 0x8987 +#define GL_RESAMPLE_AVERAGE_OML 0x8988 +#define GL_RESAMPLE_DECIMATE_OML 0x8989 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_DEPTH_STENCIL_TO_RGBA_NV 0x886E +#define GL_DEPTH_STENCIL_TO_BGRA_NV 0x886F +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_BUMP_ROT_MATRIX_ATI 0x8775 +#define GL_BUMP_ROT_MATRIX_SIZE_ATI 0x8776 +#define GL_BUMP_NUM_TEX_UNITS_ATI 0x8777 +#define GL_BUMP_TEX_UNITS_ATI 0x8778 +#define GL_DUDV_ATI 0x8779 +#define GL_DU8DV8_ATI 0x877A +#define GL_BUMP_ENVMAP_ATI 0x877B +#define GL_BUMP_TARGET_ATI 0x877C +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#define GL_REG_0_ATI 0x8921 +#define GL_REG_1_ATI 0x8922 +#define GL_REG_2_ATI 0x8923 +#define GL_REG_3_ATI 0x8924 +#define GL_REG_4_ATI 0x8925 +#define GL_REG_5_ATI 0x8926 +#define GL_REG_6_ATI 0x8927 +#define GL_REG_7_ATI 0x8928 +#define GL_REG_8_ATI 0x8929 +#define GL_REG_9_ATI 0x892A +#define GL_REG_10_ATI 0x892B +#define GL_REG_11_ATI 0x892C +#define GL_REG_12_ATI 0x892D +#define GL_REG_13_ATI 0x892E +#define GL_REG_14_ATI 0x892F +#define GL_REG_15_ATI 0x8930 +#define GL_REG_16_ATI 0x8931 +#define GL_REG_17_ATI 0x8932 +#define GL_REG_18_ATI 0x8933 +#define GL_REG_19_ATI 0x8934 +#define GL_REG_20_ATI 0x8935 +#define GL_REG_21_ATI 0x8936 +#define GL_REG_22_ATI 0x8937 +#define GL_REG_23_ATI 0x8938 +#define GL_REG_24_ATI 0x8939 +#define GL_REG_25_ATI 0x893A +#define GL_REG_26_ATI 0x893B +#define GL_REG_27_ATI 0x893C +#define GL_REG_28_ATI 0x893D +#define GL_REG_29_ATI 0x893E +#define GL_REG_30_ATI 0x893F +#define GL_REG_31_ATI 0x8940 +#define GL_CON_0_ATI 0x8941 +#define GL_CON_1_ATI 0x8942 +#define GL_CON_2_ATI 0x8943 +#define GL_CON_3_ATI 0x8944 +#define GL_CON_4_ATI 0x8945 +#define GL_CON_5_ATI 0x8946 +#define GL_CON_6_ATI 0x8947 +#define GL_CON_7_ATI 0x8948 +#define GL_CON_8_ATI 0x8949 +#define GL_CON_9_ATI 0x894A +#define GL_CON_10_ATI 0x894B +#define GL_CON_11_ATI 0x894C +#define GL_CON_12_ATI 0x894D +#define GL_CON_13_ATI 0x894E +#define GL_CON_14_ATI 0x894F +#define GL_CON_15_ATI 0x8950 +#define GL_CON_16_ATI 0x8951 +#define GL_CON_17_ATI 0x8952 +#define GL_CON_18_ATI 0x8953 +#define GL_CON_19_ATI 0x8954 +#define GL_CON_20_ATI 0x8955 +#define GL_CON_21_ATI 0x8956 +#define GL_CON_22_ATI 0x8957 +#define GL_CON_23_ATI 0x8958 +#define GL_CON_24_ATI 0x8959 +#define GL_CON_25_ATI 0x895A +#define GL_CON_26_ATI 0x895B +#define GL_CON_27_ATI 0x895C +#define GL_CON_28_ATI 0x895D +#define GL_CON_29_ATI 0x895E +#define GL_CON_30_ATI 0x895F +#define GL_CON_31_ATI 0x8960 +#define GL_MOV_ATI 0x8961 +#define GL_ADD_ATI 0x8963 +#define GL_MUL_ATI 0x8964 +#define GL_SUB_ATI 0x8965 +#define GL_DOT3_ATI 0x8966 +#define GL_DOT4_ATI 0x8967 +#define GL_MAD_ATI 0x8968 +#define GL_LERP_ATI 0x8969 +#define GL_CND_ATI 0x896A +#define GL_CND0_ATI 0x896B +#define GL_DOT2_ADD_ATI 0x896C +#define GL_SECONDARY_INTERPOLATOR_ATI 0x896D +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#define GL_NUM_FRAGMENT_CONSTANTS_ATI 0x896F +#define GL_NUM_PASSES_ATI 0x8970 +#define GL_NUM_INSTRUCTIONS_PER_PASS_ATI 0x8971 +#define GL_NUM_INSTRUCTIONS_TOTAL_ATI 0x8972 +#define GL_NUM_INPUT_INTERPOLATOR_COMPONENTS_ATI 0x8973 +#define GL_NUM_LOOPBACK_COMPONENTS_ATI 0x8974 +#define GL_COLOR_ALPHA_PAIRING_ATI 0x8975 +#define GL_SWIZZLE_STR_ATI 0x8976 +#define GL_SWIZZLE_STQ_ATI 0x8977 +#define GL_SWIZZLE_STR_DR_ATI 0x8978 +#define GL_SWIZZLE_STQ_DQ_ATI 0x8979 +#define GL_SWIZZLE_STRQ_ATI 0x897A +#define GL_SWIZZLE_STRQ_DQ_ATI 0x897B +#define GL_RED_BIT_ATI 0x00000001 +#define GL_GREEN_BIT_ATI 0x00000002 +#define GL_BLUE_BIT_ATI 0x00000004 +#define GL_2X_BIT_ATI 0x00000001 +#define GL_4X_BIT_ATI 0x00000002 +#define GL_8X_BIT_ATI 0x00000004 +#define GL_HALF_BIT_ATI 0x00000008 +#define GL_QUARTER_BIT_ATI 0x00000010 +#define GL_EIGHTH_BIT_ATI 0x00000020 +#define GL_SATURATE_BIT_ATI 0x00000040 +#define GL_COMP_BIT_ATI 0x00000002 +#define GL_NEGATE_BIT_ATI 0x00000004 +#define GL_BIAS_BIT_ATI 0x00000008 +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_PN_TRIANGLES_ATI 0x87F0 +#define GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F1 +#define GL_PN_TRIANGLES_POINT_MODE_ATI 0x87F2 +#define GL_PN_TRIANGLES_NORMAL_MODE_ATI 0x87F3 +#define GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI 0x87F4 +#define GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI 0x87F5 +#define GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI 0x87F6 +#define GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI 0x87F7 +#define GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI 0x87F8 +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_STATIC_ATI 0x8760 +#define GL_DYNAMIC_ATI 0x8761 +#define GL_PRESERVE_ATI 0x8762 +#define GL_DISCARD_ATI 0x8763 +#define GL_OBJECT_BUFFER_SIZE_ATI 0x8764 +#define GL_OBJECT_BUFFER_USAGE_ATI 0x8765 +#define GL_ARRAY_OBJECT_BUFFER_ATI 0x8766 +#define GL_ARRAY_OBJECT_OFFSET_ATI 0x8767 +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_VERTEX_SHADER_EXT 0x8780 +#define GL_VERTEX_SHADER_BINDING_EXT 0x8781 +#define GL_OP_INDEX_EXT 0x8782 +#define GL_OP_NEGATE_EXT 0x8783 +#define GL_OP_DOT3_EXT 0x8784 +#define GL_OP_DOT4_EXT 0x8785 +#define GL_OP_MUL_EXT 0x8786 +#define GL_OP_ADD_EXT 0x8787 +#define GL_OP_MADD_EXT 0x8788 +#define GL_OP_FRAC_EXT 0x8789 +#define GL_OP_MAX_EXT 0x878A +#define GL_OP_MIN_EXT 0x878B +#define GL_OP_SET_GE_EXT 0x878C +#define GL_OP_SET_LT_EXT 0x878D +#define GL_OP_CLAMP_EXT 0x878E +#define GL_OP_FLOOR_EXT 0x878F +#define GL_OP_ROUND_EXT 0x8790 +#define GL_OP_EXP_BASE_2_EXT 0x8791 +#define GL_OP_LOG_BASE_2_EXT 0x8792 +#define GL_OP_POWER_EXT 0x8793 +#define GL_OP_RECIP_EXT 0x8794 +#define GL_OP_RECIP_SQRT_EXT 0x8795 +#define GL_OP_SUB_EXT 0x8796 +#define GL_OP_CROSS_PRODUCT_EXT 0x8797 +#define GL_OP_MULTIPLY_MATRIX_EXT 0x8798 +#define GL_OP_MOV_EXT 0x8799 +#define GL_OUTPUT_VERTEX_EXT 0x879A +#define GL_OUTPUT_COLOR0_EXT 0x879B +#define GL_OUTPUT_COLOR1_EXT 0x879C +#define GL_OUTPUT_TEXTURE_COORD0_EXT 0x879D +#define GL_OUTPUT_TEXTURE_COORD1_EXT 0x879E +#define GL_OUTPUT_TEXTURE_COORD2_EXT 0x879F +#define GL_OUTPUT_TEXTURE_COORD3_EXT 0x87A0 +#define GL_OUTPUT_TEXTURE_COORD4_EXT 0x87A1 +#define GL_OUTPUT_TEXTURE_COORD5_EXT 0x87A2 +#define GL_OUTPUT_TEXTURE_COORD6_EXT 0x87A3 +#define GL_OUTPUT_TEXTURE_COORD7_EXT 0x87A4 +#define GL_OUTPUT_TEXTURE_COORD8_EXT 0x87A5 +#define GL_OUTPUT_TEXTURE_COORD9_EXT 0x87A6 +#define GL_OUTPUT_TEXTURE_COORD10_EXT 0x87A7 +#define GL_OUTPUT_TEXTURE_COORD11_EXT 0x87A8 +#define GL_OUTPUT_TEXTURE_COORD12_EXT 0x87A9 +#define GL_OUTPUT_TEXTURE_COORD13_EXT 0x87AA +#define GL_OUTPUT_TEXTURE_COORD14_EXT 0x87AB +#define GL_OUTPUT_TEXTURE_COORD15_EXT 0x87AC +#define GL_OUTPUT_TEXTURE_COORD16_EXT 0x87AD +#define GL_OUTPUT_TEXTURE_COORD17_EXT 0x87AE +#define GL_OUTPUT_TEXTURE_COORD18_EXT 0x87AF +#define GL_OUTPUT_TEXTURE_COORD19_EXT 0x87B0 +#define GL_OUTPUT_TEXTURE_COORD20_EXT 0x87B1 +#define GL_OUTPUT_TEXTURE_COORD21_EXT 0x87B2 +#define GL_OUTPUT_TEXTURE_COORD22_EXT 0x87B3 +#define GL_OUTPUT_TEXTURE_COORD23_EXT 0x87B4 +#define GL_OUTPUT_TEXTURE_COORD24_EXT 0x87B5 +#define GL_OUTPUT_TEXTURE_COORD25_EXT 0x87B6 +#define GL_OUTPUT_TEXTURE_COORD26_EXT 0x87B7 +#define GL_OUTPUT_TEXTURE_COORD27_EXT 0x87B8 +#define GL_OUTPUT_TEXTURE_COORD28_EXT 0x87B9 +#define GL_OUTPUT_TEXTURE_COORD29_EXT 0x87BA +#define GL_OUTPUT_TEXTURE_COORD30_EXT 0x87BB +#define GL_OUTPUT_TEXTURE_COORD31_EXT 0x87BC +#define GL_OUTPUT_FOG_EXT 0x87BD +#define GL_SCALAR_EXT 0x87BE +#define GL_VECTOR_EXT 0x87BF +#define GL_MATRIX_EXT 0x87C0 +#define GL_VARIANT_EXT 0x87C1 +#define GL_INVARIANT_EXT 0x87C2 +#define GL_LOCAL_CONSTANT_EXT 0x87C3 +#define GL_LOCAL_EXT 0x87C4 +#define GL_MAX_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87C5 +#define GL_MAX_VERTEX_SHADER_VARIANTS_EXT 0x87C6 +#define GL_MAX_VERTEX_SHADER_INVARIANTS_EXT 0x87C7 +#define GL_MAX_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87C8 +#define GL_MAX_VERTEX_SHADER_LOCALS_EXT 0x87C9 +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CA +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_VARIANTS_EXT 0x87CB +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87CC +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_INVARIANTS_EXT 0x87CD +#define GL_MAX_OPTIMIZED_VERTEX_SHADER_LOCALS_EXT 0x87CE +#define GL_VERTEX_SHADER_INSTRUCTIONS_EXT 0x87CF +#define GL_VERTEX_SHADER_VARIANTS_EXT 0x87D0 +#define GL_VERTEX_SHADER_INVARIANTS_EXT 0x87D1 +#define GL_VERTEX_SHADER_LOCAL_CONSTANTS_EXT 0x87D2 +#define GL_VERTEX_SHADER_LOCALS_EXT 0x87D3 +#define GL_VERTEX_SHADER_OPTIMIZED_EXT 0x87D4 +#define GL_X_EXT 0x87D5 +#define GL_Y_EXT 0x87D6 +#define GL_Z_EXT 0x87D7 +#define GL_W_EXT 0x87D8 +#define GL_NEGATIVE_X_EXT 0x87D9 +#define GL_NEGATIVE_Y_EXT 0x87DA +#define GL_NEGATIVE_Z_EXT 0x87DB +#define GL_NEGATIVE_W_EXT 0x87DC +#define GL_ZERO_EXT 0x87DD +#define GL_ONE_EXT 0x87DE +#define GL_NEGATIVE_ONE_EXT 0x87DF +#define GL_NORMALIZED_RANGE_EXT 0x87E0 +#define GL_FULL_RANGE_EXT 0x87E1 +#define GL_CURRENT_VERTEX_EXT 0x87E2 +#define GL_MVP_MATRIX_EXT 0x87E3 +#define GL_VARIANT_VALUE_EXT 0x87E4 +#define GL_VARIANT_DATATYPE_EXT 0x87E5 +#define GL_VARIANT_ARRAY_STRIDE_EXT 0x87E6 +#define GL_VARIANT_ARRAY_TYPE_EXT 0x87E7 +#define GL_VARIANT_ARRAY_EXT 0x87E8 +#define GL_VARIANT_ARRAY_POINTER_EXT 0x87E9 +#define GL_INVARIANT_VALUE_EXT 0x87EA +#define GL_INVARIANT_DATATYPE_EXT 0x87EB +#define GL_LOCAL_CONSTANT_VALUE_EXT 0x87EC +#define GL_LOCAL_CONSTANT_DATATYPE_EXT 0x87ED +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_MAX_VERTEX_STREAMS_ATI 0x876B +#define GL_VERTEX_STREAM0_ATI 0x876C +#define GL_VERTEX_STREAM1_ATI 0x876D +#define GL_VERTEX_STREAM2_ATI 0x876E +#define GL_VERTEX_STREAM3_ATI 0x876F +#define GL_VERTEX_STREAM4_ATI 0x8770 +#define GL_VERTEX_STREAM5_ATI 0x8771 +#define GL_VERTEX_STREAM6_ATI 0x8772 +#define GL_VERTEX_STREAM7_ATI 0x8773 +#define GL_VERTEX_SOURCE_ATI 0x8774 +#endif + +#ifndef GL_ATI_element_array +#define GL_ELEMENT_ARRAY_ATI 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_ATI 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_ATI 0x876A +#endif + +#ifndef GL_SUN_mesh_array +#define GL_QUAD_MESH_SUN 0x8614 +#define GL_TRIANGLE_MESH_SUN 0x8615 +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SLICE_ACCUM_SUN 0x85CC +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_MULTISAMPLE_FILTER_HINT_NV 0x8534 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_DEPTH_CLAMP_NV 0x864F +#endif + +#ifndef GL_NV_occlusion_query +#define GL_PIXEL_COUNTER_BITS_NV 0x8864 +#define GL_CURRENT_OCCLUSION_QUERY_ID_NV 0x8865 +#define GL_PIXEL_COUNT_NV 0x8866 +#define GL_PIXEL_COUNT_AVAILABLE_NV 0x8867 +#endif + +#ifndef GL_NV_point_sprite +#define GL_POINT_SPRITE_NV 0x8861 +#define GL_COORD_REPLACE_NV 0x8862 +#define GL_POINT_SPRITE_R_MODE_NV 0x8863 +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_NV 0x8850 +#define GL_OFFSET_PROJECTIVE_TEXTURE_2D_SCALE_NV 0x8851 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8852 +#define GL_OFFSET_PROJECTIVE_TEXTURE_RECTANGLE_SCALE_NV 0x8853 +#define GL_OFFSET_HILO_TEXTURE_2D_NV 0x8854 +#define GL_OFFSET_HILO_TEXTURE_RECTANGLE_NV 0x8855 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_2D_NV 0x8856 +#define GL_OFFSET_HILO_PROJECTIVE_TEXTURE_RECTANGLE_NV 0x8857 +#define GL_DEPENDENT_HILO_TEXTURE_2D_NV 0x8858 +#define GL_DEPENDENT_RGB_TEXTURE_3D_NV 0x8859 +#define GL_DEPENDENT_RGB_TEXTURE_CUBE_MAP_NV 0x885A +#define GL_DOT_PRODUCT_PASS_THROUGH_NV 0x885B +#define GL_DOT_PRODUCT_TEXTURE_1D_NV 0x885C +#define GL_DOT_PRODUCT_AFFINE_DEPTH_REPLACE_NV 0x885D +#define GL_HILO8_NV 0x885E +#define GL_SIGNED_HILO8_NV 0x885F +#define GL_FORCE_BLUE_TO_ONE_NV 0x8860 +#endif + +#ifndef GL_NV_vertex_program1_1 +#endif + +#ifndef GL_EXT_shadow_funcs +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif + +#ifndef GL_APPLE_element_array +#define GL_ELEMENT_ARRAY_APPLE 0x8768 +#define GL_ELEMENT_ARRAY_TYPE_APPLE 0x8769 +#define GL_ELEMENT_ARRAY_POINTER_APPLE 0x876A +#endif + +#ifndef GL_APPLE_fence +#define GL_DRAW_PIXELS_APPLE 0x8A0A +#define GL_FENCE_APPLE 0x8A0B +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_VERTEX_ARRAY_BINDING_APPLE 0x85B5 +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_VERTEX_ARRAY_RANGE_APPLE 0x851D +#define GL_VERTEX_ARRAY_RANGE_LENGTH_APPLE 0x851E +#define GL_VERTEX_ARRAY_STORAGE_HINT_APPLE 0x851F +#define GL_VERTEX_ARRAY_RANGE_POINTER_APPLE 0x8521 +#define GL_STORAGE_CACHED_APPLE 0x85BE +#define GL_STORAGE_SHARED_APPLE 0x85BF +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_YCBCR_422_APPLE 0x85B9 +#define GL_UNSIGNED_SHORT_8_8_APPLE 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_APPLE 0x85BB +#endif + +#ifndef GL_S3_s3tc +#define GL_RGB_S3TC 0x83A0 +#define GL_RGB4_S3TC 0x83A1 +#define GL_RGBA_S3TC 0x83A2 +#define GL_RGBA4_S3TC 0x83A3 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_MAX_DRAW_BUFFERS_ATI 0x8824 +#define GL_DRAW_BUFFER0_ATI 0x8825 +#define GL_DRAW_BUFFER1_ATI 0x8826 +#define GL_DRAW_BUFFER2_ATI 0x8827 +#define GL_DRAW_BUFFER3_ATI 0x8828 +#define GL_DRAW_BUFFER4_ATI 0x8829 +#define GL_DRAW_BUFFER5_ATI 0x882A +#define GL_DRAW_BUFFER6_ATI 0x882B +#define GL_DRAW_BUFFER7_ATI 0x882C +#define GL_DRAW_BUFFER8_ATI 0x882D +#define GL_DRAW_BUFFER9_ATI 0x882E +#define GL_DRAW_BUFFER10_ATI 0x882F +#define GL_DRAW_BUFFER11_ATI 0x8830 +#define GL_DRAW_BUFFER12_ATI 0x8831 +#define GL_DRAW_BUFFER13_ATI 0x8832 +#define GL_DRAW_BUFFER14_ATI 0x8833 +#define GL_DRAW_BUFFER15_ATI 0x8834 +#endif + +#ifndef GL_ATI_pixel_format_float +#define GL_TYPE_RGBA_FLOAT_ATI 0x8820 +#define GL_COLOR_CLEAR_UNCLAMPED_VALUE_ATI 0x8835 +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_MODULATE_ADD_ATI 0x8744 +#define GL_MODULATE_SIGNED_ADD_ATI 0x8745 +#define GL_MODULATE_SUBTRACT_ATI 0x8746 +#endif + +#ifndef GL_ATI_texture_float +#define GL_RGBA_FLOAT32_ATI 0x8814 +#define GL_RGB_FLOAT32_ATI 0x8815 +#define GL_ALPHA_FLOAT32_ATI 0x8816 +#define GL_INTENSITY_FLOAT32_ATI 0x8817 +#define GL_LUMINANCE_FLOAT32_ATI 0x8818 +#define GL_LUMINANCE_ALPHA_FLOAT32_ATI 0x8819 +#define GL_RGBA_FLOAT16_ATI 0x881A +#define GL_RGB_FLOAT16_ATI 0x881B +#define GL_ALPHA_FLOAT16_ATI 0x881C +#define GL_INTENSITY_FLOAT16_ATI 0x881D +#define GL_LUMINANCE_FLOAT16_ATI 0x881E +#define GL_LUMINANCE_ALPHA_FLOAT16_ATI 0x881F +#endif + +#ifndef GL_NV_float_buffer +#define GL_FLOAT_R_NV 0x8880 +#define GL_FLOAT_RG_NV 0x8881 +#define GL_FLOAT_RGB_NV 0x8882 +#define GL_FLOAT_RGBA_NV 0x8883 +#define GL_FLOAT_R16_NV 0x8884 +#define GL_FLOAT_R32_NV 0x8885 +#define GL_FLOAT_RG16_NV 0x8886 +#define GL_FLOAT_RG32_NV 0x8887 +#define GL_FLOAT_RGB16_NV 0x8888 +#define GL_FLOAT_RGB32_NV 0x8889 +#define GL_FLOAT_RGBA16_NV 0x888A +#define GL_FLOAT_RGBA32_NV 0x888B +#define GL_TEXTURE_FLOAT_COMPONENTS_NV 0x888C +#define GL_FLOAT_CLEAR_COLOR_VALUE_NV 0x888D +#define GL_FLOAT_RGBA_MODE_NV 0x888E +#endif + +#ifndef GL_NV_fragment_program +#define GL_MAX_FRAGMENT_PROGRAM_LOCAL_PARAMETERS_NV 0x8868 +#define GL_FRAGMENT_PROGRAM_NV 0x8870 +#define GL_MAX_TEXTURE_COORDS_NV 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_NV 0x8872 +#define GL_FRAGMENT_PROGRAM_BINDING_NV 0x8873 +#define GL_PROGRAM_ERROR_STRING_NV 0x8874 +#endif + +#ifndef GL_NV_half_float +#define GL_HALF_FLOAT_NV 0x140B +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_WRITE_PIXEL_DATA_RANGE_NV 0x8878 +#define GL_READ_PIXEL_DATA_RANGE_NV 0x8879 +#define GL_WRITE_PIXEL_DATA_RANGE_LENGTH_NV 0x887A +#define GL_READ_PIXEL_DATA_RANGE_LENGTH_NV 0x887B +#define GL_WRITE_PIXEL_DATA_RANGE_POINTER_NV 0x887C +#define GL_READ_PIXEL_DATA_RANGE_POINTER_NV 0x887D +#endif + +#ifndef GL_NV_primitive_restart +#define GL_PRIMITIVE_RESTART_NV 0x8558 +#define GL_PRIMITIVE_RESTART_INDEX_NV 0x8559 +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_TEXTURE_UNSIGNED_REMAP_MODE_NV 0x888F +#endif + +#ifndef GL_NV_vertex_program2 +#endif + +#ifndef GL_ATI_map_object_buffer +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_STENCIL_BACK_FUNC_ATI 0x8800 +#define GL_STENCIL_BACK_FAIL_ATI 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL_ATI 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS_ATI 0x8803 +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#endif + +#ifndef GL_OES_read_format +#define GL_IMPLEMENTATION_COLOR_READ_TYPE_OES 0x8B9A +#define GL_IMPLEMENTATION_COLOR_READ_FORMAT_OES 0x8B9B +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_DEPTH_BOUNDS_TEST_EXT 0x8890 +#define GL_DEPTH_BOUNDS_EXT 0x8891 +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_MIRROR_CLAMP_EXT 0x8742 +#define GL_MIRROR_CLAMP_TO_EDGE_EXT 0x8743 +#define GL_MIRROR_CLAMP_TO_BORDER_EXT 0x8912 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_BLEND_EQUATION_RGB_EXT GL_BLEND_EQUATION +#define GL_BLEND_EQUATION_ALPHA_EXT 0x883D +#endif + +#ifndef GL_MESA_pack_invert +#define GL_PACK_INVERT_MESA 0x8758 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_UNSIGNED_SHORT_8_8_MESA 0x85BA +#define GL_UNSIGNED_SHORT_8_8_REV_MESA 0x85BB +#define GL_YCBCR_MESA 0x8757 +#endif + +#ifndef GL_EXT_pixel_buffer_object +#define GL_PIXEL_PACK_BUFFER_EXT 0x88EB +#define GL_PIXEL_UNPACK_BUFFER_EXT 0x88EC +#define GL_PIXEL_PACK_BUFFER_BINDING_EXT 0x88ED +#define GL_PIXEL_UNPACK_BUFFER_BINDING_EXT 0x88EF +#endif + +#ifndef GL_NV_fragment_program_option +#endif + +#ifndef GL_NV_fragment_program2 +#define GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV 0x88F4 +#define GL_MAX_PROGRAM_CALL_DEPTH_NV 0x88F5 +#define GL_MAX_PROGRAM_IF_DEPTH_NV 0x88F6 +#define GL_MAX_PROGRAM_LOOP_DEPTH_NV 0x88F7 +#define GL_MAX_PROGRAM_LOOP_COUNT_NV 0x88F8 +#endif + +#ifndef GL_NV_vertex_program2_option +/* reuse GL_MAX_PROGRAM_EXEC_INSTRUCTIONS_NV */ +/* reuse GL_MAX_PROGRAM_CALL_DEPTH_NV */ +#endif + +#ifndef GL_NV_vertex_program3 +/* reuse GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB */ +#endif + +#ifndef GL_EXT_framebuffer_object +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT 0x8CD8 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +#endif + +#ifndef GL_GREMEDY_string_marker +#endif + + +/*************************************************************/ + +#include +#ifndef GL_VERSION_2_0 +/* GL type for program/shader text */ +typedef char GLchar; /* native character */ +#endif + +#ifndef GL_VERSION_1_5 +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptr; +typedef ptrdiff_t GLsizeiptr; +#endif + +#ifndef GL_ARB_vertex_buffer_object +/* GL types for handling large vertex buffer objects */ +typedef ptrdiff_t GLintptrARB; +typedef ptrdiff_t GLsizeiptrARB; +#endif + +#ifndef GL_ARB_shader_objects +/* GL types for handling shader object handles and program/shader text */ +typedef char GLcharARB; /* native character */ +typedef unsigned int GLhandleARB; /* shader object handle */ +#endif + +/* GL types for "half" precision (s10e5) float data in host memory */ +#ifndef GL_ARB_half_float_pixel +typedef unsigned short GLhalfARB; +#endif + +#ifndef GL_NV_half_float +typedef unsigned short GLhalfNV; +#endif + +#ifndef GL_VERSION_1_2 +#define GL_VERSION_1_2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColor (GLclampf, GLclampf, GLclampf, GLclampf); +GLAPI void APIENTRY glBlendEquation (GLenum); +GLAPI void APIENTRY glDrawRangeElements (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTable (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTableParameterfv (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glColorTableParameteriv (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyColorTable (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glGetColorTable (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetColorTableParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glColorSubTable (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyColorSubTable (GLenum, GLsizei, GLint, GLint, GLsizei); +GLAPI void APIENTRY glConvolutionFilter1D (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionParameterf (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glConvolutionParameterfv (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glConvolutionParameteri (GLenum, GLenum, GLint); +GLAPI void APIENTRY glConvolutionParameteriv (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyConvolutionFilter1D (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyConvolutionFilter2D (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glGetConvolutionFilter (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetConvolutionParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetConvolutionParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetSeparableFilter (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +GLAPI void APIENTRY glSeparableFilter2D (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +GLAPI void APIENTRY glGetHistogram (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetHistogramParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetHistogramParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMinmax (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetMinmaxParameterfv (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMinmaxParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glHistogram (GLenum, GLsizei, GLenum, GLboolean); +GLAPI void APIENTRY glMinmax (GLenum, GLenum, GLboolean); +GLAPI void APIENTRY glResetHistogram (GLenum); +GLAPI void APIENTRY glResetMinmax (GLenum); +GLAPI void APIENTRY glTexImage3D (GLenum, GLint, GLint, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLORPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +typedef void (APIENTRYP PFNGLBLENDEQUATIONPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +typedef void (APIENTRYP PFNGLCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLEPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTERPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXPROC) (GLenum target); +typedef void (APIENTRYP PFNGLTEXIMAGE3DPROC) (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_VERSION_1_3 +#define GL_VERSION_1_3 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTexture (GLenum); +GLAPI void APIENTRY glClientActiveTexture (GLenum); +GLAPI void APIENTRY glMultiTexCoord1d (GLenum, GLdouble); +GLAPI void APIENTRY glMultiTexCoord1dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord1f (GLenum, GLfloat); +GLAPI void APIENTRY glMultiTexCoord1fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord1i (GLenum, GLint); +GLAPI void APIENTRY glMultiTexCoord1iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord1s (GLenum, GLshort); +GLAPI void APIENTRY glMultiTexCoord1sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord2d (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord2dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord2f (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord2fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord2i (GLenum, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord2iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord2s (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord2sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord3d (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord3dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord3f (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord3fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord3i (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord3iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord3s (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord3sv (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord4d (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord4dv (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord4f (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord4fv (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord4i (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord4iv (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord4s (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord4sv (GLenum, const GLshort *); +GLAPI void APIENTRY glLoadTransposeMatrixf (const GLfloat *); +GLAPI void APIENTRY glLoadTransposeMatrixd (const GLdouble *); +GLAPI void APIENTRY glMultTransposeMatrixf (const GLfloat *); +GLAPI void APIENTRY glMultTransposeMatrixd (const GLdouble *); +GLAPI void APIENTRY glSampleCoverage (GLclampf, GLboolean); +GLAPI void APIENTRY glCompressedTexImage3D (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage2D (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage1D (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage3D (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage2D (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage1D (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glGetCompressedTexImage (GLenum, GLint, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_VERSION_1_4 +#define GL_VERSION_1_4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparate (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glFogCoordf (GLfloat); +GLAPI void APIENTRY glFogCoordfv (const GLfloat *); +GLAPI void APIENTRY glFogCoordd (GLdouble); +GLAPI void APIENTRY glFogCoorddv (const GLdouble *); +GLAPI void APIENTRY glFogCoordPointer (GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glMultiDrawArrays (GLenum, GLint *, GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawElements (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +GLAPI void APIENTRY glPointParameterf (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfv (GLenum, const GLfloat *); +GLAPI void APIENTRY glPointParameteri (GLenum, GLint); +GLAPI void APIENTRY glPointParameteriv (GLenum, const GLint *); +GLAPI void APIENTRY glSecondaryColor3b (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glSecondaryColor3bv (const GLbyte *); +GLAPI void APIENTRY glSecondaryColor3d (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glSecondaryColor3dv (const GLdouble *); +GLAPI void APIENTRY glSecondaryColor3f (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glSecondaryColor3fv (const GLfloat *); +GLAPI void APIENTRY glSecondaryColor3i (GLint, GLint, GLint); +GLAPI void APIENTRY glSecondaryColor3iv (const GLint *); +GLAPI void APIENTRY glSecondaryColor3s (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glSecondaryColor3sv (const GLshort *); +GLAPI void APIENTRY glSecondaryColor3ub (GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glSecondaryColor3ubv (const GLubyte *); +GLAPI void APIENTRY glSecondaryColor3ui (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSecondaryColor3uiv (const GLuint *); +GLAPI void APIENTRY glSecondaryColor3us (GLushort, GLushort, GLushort); +GLAPI void APIENTRY glSecondaryColor3usv (const GLushort *); +GLAPI void APIENTRY glSecondaryColorPointer (GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glWindowPos2d (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dv (const GLdouble *); +GLAPI void APIENTRY glWindowPos2f (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fv (const GLfloat *); +GLAPI void APIENTRY glWindowPos2i (GLint, GLint); +GLAPI void APIENTRY glWindowPos2iv (const GLint *); +GLAPI void APIENTRY glWindowPos2s (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2sv (const GLshort *); +GLAPI void APIENTRY glWindowPos3d (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dv (const GLdouble *); +GLAPI void APIENTRY glWindowPos3f (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fv (const GLfloat *); +GLAPI void APIENTRY glWindowPos3i (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3iv (const GLint *); +GLAPI void APIENTRY glWindowPos3s (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3sv (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +typedef void (APIENTRYP PFNGLFOGCOORDFPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLWINDOWPOS2DPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVPROC) (const GLshort *v); +#endif + +#ifndef GL_VERSION_1_5 +#define GL_VERSION_1_5 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueries (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteQueries (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsQuery (GLuint); +GLAPI void APIENTRY glBeginQuery (GLenum, GLuint); +GLAPI void APIENTRY glEndQuery (GLenum); +GLAPI void APIENTRY glGetQueryiv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectuiv (GLuint, GLenum, GLuint *); +GLAPI void APIENTRY glBindBuffer (GLenum, GLuint); +GLAPI void APIENTRY glDeleteBuffers (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenBuffers (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsBuffer (GLuint); +GLAPI void APIENTRY glBufferData (GLenum, GLsizeiptr, const GLvoid *, GLenum); +GLAPI void APIENTRY glBufferSubData (GLenum, GLintptr, GLsizeiptr, const GLvoid *); +GLAPI void APIENTRY glGetBufferSubData (GLenum, GLintptr, GLsizeiptr, GLvoid *); +GLAPI GLvoid* APIENTRY glMapBuffer (GLenum, GLenum); +GLAPI GLboolean APIENTRY glUnmapBuffer (GLenum); +GLAPI void APIENTRY glGetBufferParameteriv (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetBufferPointerv (GLenum, GLenum, GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVPROC) (GLuint id, GLenum pname, GLuint *params); +typedef void (APIENTRYP PFNGLBINDBUFFERPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_VERSION_2_0 +#define GL_VERSION_2_0 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparate (GLenum, GLenum); +GLAPI void APIENTRY glDrawBuffers (GLsizei, const GLenum *); +GLAPI void APIENTRY glStencilOpSeparate (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glStencilFuncSeparate (GLenum, GLenum, GLint, GLuint); +GLAPI void APIENTRY glStencilMaskSeparate (GLenum, GLuint); +GLAPI void APIENTRY glAttachShader (GLuint, GLuint); +GLAPI void APIENTRY glBindAttribLocation (GLuint, GLuint, const GLchar *); +GLAPI void APIENTRY glCompileShader (GLuint); +GLAPI GLuint APIENTRY glCreateProgram (void); +GLAPI GLuint APIENTRY glCreateShader (GLenum); +GLAPI void APIENTRY glDeleteProgram (GLuint); +GLAPI void APIENTRY glDeleteShader (GLuint); +GLAPI void APIENTRY glDetachShader (GLuint, GLuint); +GLAPI void APIENTRY glDisableVertexAttribArray (GLuint); +GLAPI void APIENTRY glEnableVertexAttribArray (GLuint); +GLAPI void APIENTRY glGetActiveAttrib (GLuint, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLchar *); +GLAPI void APIENTRY glGetActiveUniform (GLuint, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLchar *); +GLAPI void APIENTRY glGetAttachedShaders (GLuint, GLsizei, GLsizei *, GLuint *); +GLAPI GLint APIENTRY glGetAttribLocation (GLuint, const GLchar *); +GLAPI void APIENTRY glGetProgramiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramInfoLog (GLuint, GLsizei, GLsizei *, GLchar *); +GLAPI void APIENTRY glGetShaderiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetShaderInfoLog (GLuint, GLsizei, GLsizei *, GLchar *); +GLAPI void APIENTRY glGetShaderSource (GLuint, GLsizei, GLsizei *, GLchar *); +GLAPI GLint APIENTRY glGetUniformLocation (GLuint, const GLchar *); +GLAPI void APIENTRY glGetUniformfv (GLuint, GLint, GLfloat *); +GLAPI void APIENTRY glGetUniformiv (GLuint, GLint, GLint *); +GLAPI void APIENTRY glGetVertexAttribdv (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfv (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribiv (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointerv (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgram (GLuint); +GLAPI GLboolean APIENTRY glIsShader (GLuint); +GLAPI void APIENTRY glLinkProgram (GLuint); +GLAPI void APIENTRY glShaderSource (GLuint, GLsizei, const GLchar* *, const GLint *); +GLAPI void APIENTRY glUseProgram (GLuint); +GLAPI void APIENTRY glUniform1f (GLint, GLfloat); +GLAPI void APIENTRY glUniform2f (GLint, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform3f (GLint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform4f (GLint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform1i (GLint, GLint); +GLAPI void APIENTRY glUniform2i (GLint, GLint, GLint); +GLAPI void APIENTRY glUniform3i (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform4i (GLint, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform1fv (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform2fv (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform3fv (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform4fv (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform1iv (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform2iv (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform3iv (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform4iv (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniformMatrix2fv (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix3fv (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix4fv (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glValidateProgram (GLuint); +GLAPI void APIENTRY glVertexAttrib1d (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dv (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1f (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fv (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1s (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1sv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2d (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dv (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2f (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fv (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2s (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2sv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3d (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dv (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3f (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fv (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3s (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3sv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4Nbv (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4Niv (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4Nsv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4Nub (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4Nubv (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4Nuiv (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4Nusv (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttrib4bv (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4d (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dv (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4f (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fv (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4iv (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4s (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4sv (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubv (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4uiv (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4usv (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttribPointer (GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEPROC) (GLenum modeRGB, GLenum modeAlpha); +typedef void (APIENTRYP PFNGLDRAWBUFFERSPROC) (GLsizei n, const GLenum *bufs); +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +typedef void (APIENTRYP PFNGLSTENCILMASKSEPARATEPROC) (GLenum face, GLuint mask); +typedef void (APIENTRYP PFNGLATTACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONPROC) (GLuint program, GLuint index, const GLchar *name); +typedef void (APIENTRYP PFNGLCOMPILESHADERPROC) (GLuint shader); +typedef GLuint (APIENTRYP PFNGLCREATEPROGRAMPROC) (void); +typedef GLuint (APIENTRYP PFNGLCREATESHADERPROC) (GLenum type); +typedef void (APIENTRYP PFNGLDELETEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLDELETESHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLDETACHSHADERPROC) (GLuint program, GLuint shader); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYPROC) (GLuint index); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMPROC) (GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name); +typedef void (APIENTRYP PFNGLGETATTACHEDSHADERSPROC) (GLuint program, GLsizei maxCount, GLsizei *count, GLuint *obj); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETPROGRAMIVPROC) (GLuint program, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMINFOLOGPROC) (GLuint program, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERIVPROC) (GLuint shader, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERINFOLOGPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *infoLog); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEPROC) (GLuint shader, GLsizei bufSize, GLsizei *length, GLchar *source); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONPROC) (GLuint program, const GLchar *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVPROC) (GLuint program, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVPROC) (GLuint program, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMPROC) (GLuint program); +typedef GLboolean (APIENTRYP PFNGLISSHADERPROC) (GLuint shader); +typedef void (APIENTRYP PFNGLLINKPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLSHADERSOURCEPROC) (GLuint shader, GLsizei count, const GLchar* *string, const GLint *length); +typedef void (APIENTRYP PFNGLUSEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLUNIFORM1FPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMPROC) (GLuint program); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_ARB_multitexture +#define GL_ARB_multitexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveTextureARB (GLenum); +GLAPI void APIENTRY glClientActiveTextureARB (GLenum); +GLAPI void APIENTRY glMultiTexCoord1dARB (GLenum, GLdouble); +GLAPI void APIENTRY glMultiTexCoord1dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord1fARB (GLenum, GLfloat); +GLAPI void APIENTRY glMultiTexCoord1fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord1iARB (GLenum, GLint); +GLAPI void APIENTRY glMultiTexCoord1ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord1sARB (GLenum, GLshort); +GLAPI void APIENTRY glMultiTexCoord1svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord2dARB (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord2dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord2fARB (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord2fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord2iARB (GLenum, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord2ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord2sARB (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord2svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord3dARB (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord3dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord3fARB (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord3fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord3iARB (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord3ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord3sARB (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord3svARB (GLenum, const GLshort *); +GLAPI void APIENTRY glMultiTexCoord4dARB (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glMultiTexCoord4dvARB (GLenum, const GLdouble *); +GLAPI void APIENTRY glMultiTexCoord4fARB (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glMultiTexCoord4fvARB (GLenum, const GLfloat *); +GLAPI void APIENTRY glMultiTexCoord4iARB (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glMultiTexCoord4ivARB (GLenum, const GLint *); +GLAPI void APIENTRY glMultiTexCoord4sARB (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glMultiTexCoord4svARB (GLenum, const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLCLIENTACTIVETEXTUREARBPROC) (GLenum texture); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DARBPROC) (GLenum target, GLdouble s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FARBPROC) (GLenum target, GLfloat s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IARBPROC) (GLenum target, GLint s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SARBPROC) (GLenum target, GLshort s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DARBPROC) (GLenum target, GLdouble s, GLdouble t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FARBPROC) (GLenum target, GLfloat s, GLfloat t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IARBPROC) (GLenum target, GLint s, GLint t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SARBPROC) (GLenum target, GLshort s, GLshort t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IARBPROC) (GLenum target, GLint s, GLint t, GLint r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3SVARBPROC) (GLenum target, const GLshort *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DARBPROC) (GLenum target, GLdouble s, GLdouble t, GLdouble r, GLdouble q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4DVARBPROC) (GLenum target, const GLdouble *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FARBPROC) (GLenum target, GLfloat s, GLfloat t, GLfloat r, GLfloat q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4FVARBPROC) (GLenum target, const GLfloat *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IARBPROC) (GLenum target, GLint s, GLint t, GLint r, GLint q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4IVARBPROC) (GLenum target, const GLint *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SARBPROC) (GLenum target, GLshort s, GLshort t, GLshort r, GLshort q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4SVARBPROC) (GLenum target, const GLshort *v); +#endif + +#ifndef GL_ARB_transpose_matrix +#define GL_ARB_transpose_matrix 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLoadTransposeMatrixfARB (const GLfloat *); +GLAPI void APIENTRY glLoadTransposeMatrixdARB (const GLdouble *); +GLAPI void APIENTRY glMultTransposeMatrixfARB (const GLfloat *); +GLAPI void APIENTRY glMultTransposeMatrixdARB (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLLOADTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXFARBPROC) (const GLfloat *m); +typedef void (APIENTRYP PFNGLMULTTRANSPOSEMATRIXDARBPROC) (const GLdouble *m); +#endif + +#ifndef GL_ARB_multisample +#define GL_ARB_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleCoverageARB (GLclampf, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLECOVERAGEARBPROC) (GLclampf value, GLboolean invert); +#endif + +#ifndef GL_ARB_texture_env_add +#define GL_ARB_texture_env_add 1 +#endif + +#ifndef GL_ARB_texture_cube_map +#define GL_ARB_texture_cube_map 1 +#endif + +#ifndef GL_ARB_texture_compression +#define GL_ARB_texture_compression 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCompressedTexImage3DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage2DARB (GLenum, GLint, GLenum, GLsizei, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexImage1DARB (GLenum, GLint, GLenum, GLsizei, GLint, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage3DARB (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage2DARB (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glCompressedTexSubImage1DARB (GLenum, GLint, GLint, GLsizei, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glGetCompressedTexImageARB (GLenum, GLint, GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE3DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE2DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXIMAGE1DARBPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE3DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE2DARBPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOMPRESSEDTEXSUBIMAGE1DARBPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOMPRESSEDTEXIMAGEARBPROC) (GLenum target, GLint level, GLvoid *img); +#endif + +#ifndef GL_ARB_texture_border_clamp +#define GL_ARB_texture_border_clamp 1 +#endif + +#ifndef GL_ARB_point_parameters +#define GL_ARB_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfARB (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvARB (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFARBPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVARBPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_ARB_vertex_blend +#define GL_ARB_vertex_blend 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWeightbvARB (GLint, const GLbyte *); +GLAPI void APIENTRY glWeightsvARB (GLint, const GLshort *); +GLAPI void APIENTRY glWeightivARB (GLint, const GLint *); +GLAPI void APIENTRY glWeightfvARB (GLint, const GLfloat *); +GLAPI void APIENTRY glWeightdvARB (GLint, const GLdouble *); +GLAPI void APIENTRY glWeightubvARB (GLint, const GLubyte *); +GLAPI void APIENTRY glWeightusvARB (GLint, const GLushort *); +GLAPI void APIENTRY glWeightuivARB (GLint, const GLuint *); +GLAPI void APIENTRY glWeightPointerARB (GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexBlendARB (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWEIGHTBVARBPROC) (GLint size, const GLbyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTSVARBPROC) (GLint size, const GLshort *weights); +typedef void (APIENTRYP PFNGLWEIGHTIVARBPROC) (GLint size, const GLint *weights); +typedef void (APIENTRYP PFNGLWEIGHTFVARBPROC) (GLint size, const GLfloat *weights); +typedef void (APIENTRYP PFNGLWEIGHTDVARBPROC) (GLint size, const GLdouble *weights); +typedef void (APIENTRYP PFNGLWEIGHTUBVARBPROC) (GLint size, const GLubyte *weights); +typedef void (APIENTRYP PFNGLWEIGHTUSVARBPROC) (GLint size, const GLushort *weights); +typedef void (APIENTRYP PFNGLWEIGHTUIVARBPROC) (GLint size, const GLuint *weights); +typedef void (APIENTRYP PFNGLWEIGHTPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXBLENDARBPROC) (GLint count); +#endif + +#ifndef GL_ARB_matrix_palette +#define GL_ARB_matrix_palette 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCurrentPaletteMatrixARB (GLint); +GLAPI void APIENTRY glMatrixIndexubvARB (GLint, const GLubyte *); +GLAPI void APIENTRY glMatrixIndexusvARB (GLint, const GLushort *); +GLAPI void APIENTRY glMatrixIndexuivARB (GLint, const GLuint *); +GLAPI void APIENTRY glMatrixIndexPointerARB (GLint, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCURRENTPALETTEMATRIXARBPROC) (GLint index); +typedef void (APIENTRYP PFNGLMATRIXINDEXUBVARBPROC) (GLint size, const GLubyte *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUSVARBPROC) (GLint size, const GLushort *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXUIVARBPROC) (GLint size, const GLuint *indices); +typedef void (APIENTRYP PFNGLMATRIXINDEXPOINTERARBPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_ARB_texture_env_combine +#define GL_ARB_texture_env_combine 1 +#endif + +#ifndef GL_ARB_texture_env_crossbar +#define GL_ARB_texture_env_crossbar 1 +#endif + +#ifndef GL_ARB_texture_env_dot3 +#define GL_ARB_texture_env_dot3 1 +#endif + +#ifndef GL_ARB_texture_mirrored_repeat +#define GL_ARB_texture_mirrored_repeat 1 +#endif + +#ifndef GL_ARB_depth_texture +#define GL_ARB_depth_texture 1 +#endif + +#ifndef GL_ARB_shadow +#define GL_ARB_shadow 1 +#endif + +#ifndef GL_ARB_shadow_ambient +#define GL_ARB_shadow_ambient 1 +#endif + +#ifndef GL_ARB_window_pos +#define GL_ARB_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dARB (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dvARB (const GLdouble *); +GLAPI void APIENTRY glWindowPos2fARB (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fvARB (const GLfloat *); +GLAPI void APIENTRY glWindowPos2iARB (GLint, GLint); +GLAPI void APIENTRY glWindowPos2ivARB (const GLint *); +GLAPI void APIENTRY glWindowPos2sARB (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2svARB (const GLshort *); +GLAPI void APIENTRY glWindowPos3dARB (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dvARB (const GLdouble *); +GLAPI void APIENTRY glWindowPos3fARB (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fvARB (const GLfloat *); +GLAPI void APIENTRY glWindowPos3iARB (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3ivARB (const GLint *); +GLAPI void APIENTRY glWindowPos3sARB (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3svARB (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DARBPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FARBPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IARBPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SARBPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVARBPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DARBPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVARBPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FARBPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVARBPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IARBPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVARBPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SARBPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVARBPROC) (const GLshort *v); +#endif + +#ifndef GL_ARB_vertex_program +#define GL_ARB_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttrib1dARB (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1fARB (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1sARB (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2dARB (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2fARB (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2sARB (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3dARB (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3fARB (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3sARB (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4NbvARB (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4NivARB (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4NsvARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4NubARB (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4NubvARB (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4NuivARB (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4NusvARB (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttrib4bvARB (GLuint, const GLbyte *); +GLAPI void APIENTRY glVertexAttrib4dARB (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dvARB (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4fARB (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fvARB (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4ivARB (GLuint, const GLint *); +GLAPI void APIENTRY glVertexAttrib4sARB (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4svARB (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubvARB (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttrib4uivARB (GLuint, const GLuint *); +GLAPI void APIENTRY glVertexAttrib4usvARB (GLuint, const GLushort *); +GLAPI void APIENTRY glVertexAttribPointerARB (GLuint, GLint, GLenum, GLboolean, GLsizei, const GLvoid *); +GLAPI void APIENTRY glEnableVertexAttribArrayARB (GLuint); +GLAPI void APIENTRY glDisableVertexAttribArrayARB (GLuint); +GLAPI void APIENTRY glProgramStringARB (GLenum, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glBindProgramARB (GLenum, GLuint); +GLAPI void APIENTRY glDeleteProgramsARB (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenProgramsARB (GLsizei, GLuint *); +GLAPI void APIENTRY glProgramEnvParameter4dARB (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramEnvParameter4dvARB (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramEnvParameter4fARB (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramEnvParameter4fvARB (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glProgramLocalParameter4dARB (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramLocalParameter4dvARB (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramLocalParameter4fARB (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramLocalParameter4fvARB (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glGetProgramEnvParameterdvARB (GLenum, GLuint, GLdouble *); +GLAPI void APIENTRY glGetProgramEnvParameterfvARB (GLenum, GLuint, GLfloat *); +GLAPI void APIENTRY glGetProgramLocalParameterdvARB (GLenum, GLuint, GLdouble *); +GLAPI void APIENTRY glGetProgramLocalParameterfvARB (GLenum, GLuint, GLfloat *); +GLAPI void APIENTRY glGetProgramivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramStringARB (GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetVertexAttribdvARB (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfvARB (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribivARB (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointervARB (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgramARB (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DARBPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FARBPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SARBPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DARBPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FARBPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SARBPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NBVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NIVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NSVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBARBPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4NUSVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4BVARBPROC) (GLuint index, const GLbyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DARBPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVARBPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FARBPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVARBPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4IVARBPROC) (GLuint index, const GLint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SARBPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVARBPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVARBPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UIVARBPROC) (GLuint index, const GLuint *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4USVARBPROC) (GLuint index, const GLushort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERARBPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLENABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLDISABLEVERTEXATTRIBARRAYARBPROC) (GLuint index); +typedef void (APIENTRYP PFNGLPROGRAMSTRINGARBPROC) (GLenum target, GLenum format, GLsizei len, const GLvoid *string); +typedef void (APIENTRYP PFNGLBINDPROGRAMARBPROC) (GLenum target, GLuint program); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSARBPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLGENPROGRAMSARBPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMENVPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DARBPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4DVARBPROC) (GLenum target, GLuint index, const GLdouble *params); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FARBPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMLOCALPARAMETER4FVARBPROC) (GLenum target, GLuint index, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMENVPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERDVARBPROC) (GLenum target, GLuint index, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMLOCALPARAMETERFVARBPROC) (GLenum target, GLuint index, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGARBPROC) (GLenum target, GLenum pname, GLvoid *string); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVARBPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVARBPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVARBPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVARBPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMARBPROC) (GLuint program); +#endif + +#ifndef GL_ARB_fragment_program +#define GL_ARB_fragment_program 1 +/* All ARB_fragment_program entry points are shared with ARB_vertex_program. */ +#endif + +#ifndef GL_ARB_vertex_buffer_object +#define GL_ARB_vertex_buffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindBufferARB (GLenum, GLuint); +GLAPI void APIENTRY glDeleteBuffersARB (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenBuffersARB (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsBufferARB (GLuint); +GLAPI void APIENTRY glBufferDataARB (GLenum, GLsizeiptrARB, const GLvoid *, GLenum); +GLAPI void APIENTRY glBufferSubDataARB (GLenum, GLintptrARB, GLsizeiptrARB, const GLvoid *); +GLAPI void APIENTRY glGetBufferSubDataARB (GLenum, GLintptrARB, GLsizeiptrARB, GLvoid *); +GLAPI GLvoid* APIENTRY glMapBufferARB (GLenum, GLenum); +GLAPI GLboolean APIENTRY glUnmapBufferARB (GLenum); +GLAPI void APIENTRY glGetBufferParameterivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetBufferPointervARB (GLenum, GLenum, GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDBUFFERARBPROC) (GLenum target, GLuint buffer); +typedef void (APIENTRYP PFNGLDELETEBUFFERSARBPROC) (GLsizei n, const GLuint *buffers); +typedef void (APIENTRYP PFNGLGENBUFFERSARBPROC) (GLsizei n, GLuint *buffers); +typedef GLboolean (APIENTRYP PFNGLISBUFFERARBPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLBUFFERDATAARBPROC) (GLenum target, GLsizeiptrARB size, const GLvoid *data, GLenum usage); +typedef void (APIENTRYP PFNGLBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const GLvoid *data); +typedef void (APIENTRYP PFNGLGETBUFFERSUBDATAARBPROC) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, GLvoid *data); +typedef GLvoid* (APIENTRYP PFNGLMAPBUFFERARBPROC) (GLenum target, GLenum access); +typedef GLboolean (APIENTRYP PFNGLUNMAPBUFFERARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETBUFFERPARAMETERIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETBUFFERPOINTERVARBPROC) (GLenum target, GLenum pname, GLvoid* *params); +#endif + +#ifndef GL_ARB_occlusion_query +#define GL_ARB_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenQueriesARB (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteQueriesARB (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsQueryARB (GLuint); +GLAPI void APIENTRY glBeginQueryARB (GLenum, GLuint); +GLAPI void APIENTRY glEndQueryARB (GLenum); +GLAPI void APIENTRY glGetQueryivARB (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectivARB (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetQueryObjectuivARB (GLuint, GLenum, GLuint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENQUERIESARBPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEQUERIESARBPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISQUERYARBPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINQUERYARBPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLENDQUERYARBPROC) (GLenum target); +typedef void (APIENTRYP PFNGLGETQUERYIVARBPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTIVARBPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETQUERYOBJECTUIVARBPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_ARB_shader_objects +#define GL_ARB_shader_objects 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteObjectARB (GLhandleARB); +GLAPI GLhandleARB APIENTRY glGetHandleARB (GLenum); +GLAPI void APIENTRY glDetachObjectARB (GLhandleARB, GLhandleARB); +GLAPI GLhandleARB APIENTRY glCreateShaderObjectARB (GLenum); +GLAPI void APIENTRY glShaderSourceARB (GLhandleARB, GLsizei, const GLcharARB* *, const GLint *); +GLAPI void APIENTRY glCompileShaderARB (GLhandleARB); +GLAPI GLhandleARB APIENTRY glCreateProgramObjectARB (void); +GLAPI void APIENTRY glAttachObjectARB (GLhandleARB, GLhandleARB); +GLAPI void APIENTRY glLinkProgramARB (GLhandleARB); +GLAPI void APIENTRY glUseProgramObjectARB (GLhandleARB); +GLAPI void APIENTRY glValidateProgramARB (GLhandleARB); +GLAPI void APIENTRY glUniform1fARB (GLint, GLfloat); +GLAPI void APIENTRY glUniform2fARB (GLint, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform3fARB (GLint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform4fARB (GLint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glUniform1iARB (GLint, GLint); +GLAPI void APIENTRY glUniform2iARB (GLint, GLint, GLint); +GLAPI void APIENTRY glUniform3iARB (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform4iARB (GLint, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glUniform1fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform2fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform3fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform4fvARB (GLint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glUniform1ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform2ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform3ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniform4ivARB (GLint, GLsizei, const GLint *); +GLAPI void APIENTRY glUniformMatrix2fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix3fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glUniformMatrix4fvARB (GLint, GLsizei, GLboolean, const GLfloat *); +GLAPI void APIENTRY glGetObjectParameterfvARB (GLhandleARB, GLenum, GLfloat *); +GLAPI void APIENTRY glGetObjectParameterivARB (GLhandleARB, GLenum, GLint *); +GLAPI void APIENTRY glGetInfoLogARB (GLhandleARB, GLsizei, GLsizei *, GLcharARB *); +GLAPI void APIENTRY glGetAttachedObjectsARB (GLhandleARB, GLsizei, GLsizei *, GLhandleARB *); +GLAPI GLint APIENTRY glGetUniformLocationARB (GLhandleARB, const GLcharARB *); +GLAPI void APIENTRY glGetActiveUniformARB (GLhandleARB, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLcharARB *); +GLAPI void APIENTRY glGetUniformfvARB (GLhandleARB, GLint, GLfloat *); +GLAPI void APIENTRY glGetUniformivARB (GLhandleARB, GLint, GLint *); +GLAPI void APIENTRY glGetShaderSourceARB (GLhandleARB, GLsizei, GLsizei *, GLcharARB *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEOBJECTARBPROC) (GLhandleARB obj); +typedef GLhandleARB (APIENTRYP PFNGLGETHANDLEARBPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLDETACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB attachedObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATESHADEROBJECTARBPROC) (GLenum shaderType); +typedef void (APIENTRYP PFNGLSHADERSOURCEARBPROC) (GLhandleARB shaderObj, GLsizei count, const GLcharARB* *string, const GLint *length); +typedef void (APIENTRYP PFNGLCOMPILESHADERARBPROC) (GLhandleARB shaderObj); +typedef GLhandleARB (APIENTRYP PFNGLCREATEPROGRAMOBJECTARBPROC) (void); +typedef void (APIENTRYP PFNGLATTACHOBJECTARBPROC) (GLhandleARB containerObj, GLhandleARB obj); +typedef void (APIENTRYP PFNGLLINKPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUSEPROGRAMOBJECTARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLVALIDATEPROGRAMARBPROC) (GLhandleARB programObj); +typedef void (APIENTRYP PFNGLUNIFORM1FARBPROC) (GLint location, GLfloat v0); +typedef void (APIENTRYP PFNGLUNIFORM2FARBPROC) (GLint location, GLfloat v0, GLfloat v1); +typedef void (APIENTRYP PFNGLUNIFORM3FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +typedef void (APIENTRYP PFNGLUNIFORM4FARBPROC) (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +typedef void (APIENTRYP PFNGLUNIFORM1IARBPROC) (GLint location, GLint v0); +typedef void (APIENTRYP PFNGLUNIFORM2IARBPROC) (GLint location, GLint v0, GLint v1); +typedef void (APIENTRYP PFNGLUNIFORM3IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2); +typedef void (APIENTRYP PFNGLUNIFORM4IARBPROC) (GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +typedef void (APIENTRYP PFNGLUNIFORM1FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM2FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM3FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM4FVARBPROC) (GLint location, GLsizei count, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORM1IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM2IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM3IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORM4IVARBPROC) (GLint location, GLsizei count, const GLint *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX2FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX3FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLUNIFORMMATRIX4FVARBPROC) (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERFVARBPROC) (GLhandleARB obj, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTPARAMETERIVARBPROC) (GLhandleARB obj, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETINFOLOGARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +typedef void (APIENTRYP PFNGLGETATTACHEDOBJECTSARBPROC) (GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +typedef GLint (APIENTRYP PFNGLGETUNIFORMLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEUNIFORMARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef void (APIENTRYP PFNGLGETUNIFORMFVARBPROC) (GLhandleARB programObj, GLint location, GLfloat *params); +typedef void (APIENTRYP PFNGLGETUNIFORMIVARBPROC) (GLhandleARB programObj, GLint location, GLint *params); +typedef void (APIENTRYP PFNGLGETSHADERSOURCEARBPROC) (GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +#endif + +#ifndef GL_ARB_vertex_shader +#define GL_ARB_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindAttribLocationARB (GLhandleARB, GLuint, const GLcharARB *); +GLAPI void APIENTRY glGetActiveAttribARB (GLhandleARB, GLuint, GLsizei, GLsizei *, GLint *, GLenum *, GLcharARB *); +GLAPI GLint APIENTRY glGetAttribLocationARB (GLhandleARB, const GLcharARB *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDATTRIBLOCATIONARBPROC) (GLhandleARB programObj, GLuint index, const GLcharARB *name); +typedef void (APIENTRYP PFNGLGETACTIVEATTRIBARBPROC) (GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +typedef GLint (APIENTRYP PFNGLGETATTRIBLOCATIONARBPROC) (GLhandleARB programObj, const GLcharARB *name); +#endif + +#ifndef GL_ARB_fragment_shader +#define GL_ARB_fragment_shader 1 +#endif + +#ifndef GL_ARB_shading_language_100 +#define GL_ARB_shading_language_100 1 +#endif + +#ifndef GL_ARB_texture_non_power_of_two +#define GL_ARB_texture_non_power_of_two 1 +#endif + +#ifndef GL_ARB_point_sprite +#define GL_ARB_point_sprite 1 +#endif + +#ifndef GL_ARB_fragment_program_shadow +#define GL_ARB_fragment_program_shadow 1 +#endif + +#ifndef GL_ARB_draw_buffers +#define GL_ARB_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersARB (GLsizei, const GLenum *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSARBPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ARB_texture_rectangle +#define GL_ARB_texture_rectangle 1 +#endif + +#ifndef GL_ARB_color_buffer_float +#define GL_ARB_color_buffer_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glClampColorARB (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCLAMPCOLORARBPROC) (GLenum target, GLenum clamp); +#endif + +#ifndef GL_ARB_half_float_pixel +#define GL_ARB_half_float_pixel 1 +#endif + +#ifndef GL_ARB_texture_float +#define GL_ARB_texture_float 1 +#endif + +#ifndef GL_ARB_pixel_buffer_object +#define GL_ARB_pixel_buffer_object 1 +#endif + +#ifndef GL_EXT_abgr +#define GL_EXT_abgr 1 +#endif + +#ifndef GL_EXT_blend_color +#define GL_EXT_blend_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendColorEXT (GLclampf, GLclampf, GLclampf, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDCOLOREXTPROC) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +#endif + +#ifndef GL_EXT_polygon_offset +#define GL_EXT_polygon_offset 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPolygonOffsetEXT (GLfloat, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOLYGONOFFSETEXTPROC) (GLfloat factor, GLfloat bias); +#endif + +#ifndef GL_EXT_texture +#define GL_EXT_texture 1 +#endif + +#ifndef GL_EXT_texture3D +#define GL_EXT_texture3D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage3DEXT (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE3DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGIS_texture_filter4 +#define GL_SGIS_texture_filter4 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetTexFilterFuncSGIS (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glTexFilterFuncSGIS (GLenum, GLenum, GLsizei, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLfloat *weights); +typedef void (APIENTRYP PFNGLTEXFILTERFUNCSGISPROC) (GLenum target, GLenum filter, GLsizei n, const GLfloat *weights); +#endif + +#ifndef GL_EXT_subtexture +#define GL_EXT_subtexture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexSubImage1DEXT (GLenum, GLint, GLint, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_EXT_copy_texture +#define GL_EXT_copy_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCopyTexImage1DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLint); +GLAPI void APIENTRY glCopyTexImage2DEXT (GLenum, GLint, GLenum, GLint, GLint, GLsizei, GLsizei, GLint); +GLAPI void APIENTRY glCopyTexSubImage1DEXT (GLenum, GLint, GLint, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyTexSubImage2DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glCopyTexSubImage3DEXT (GLenum, GLint, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE1DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXIMAGE2DEXTPROC) (GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE1DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE2DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLCOPYTEXSUBIMAGE3DEXTPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height); +#endif + +#ifndef GL_EXT_histogram +#define GL_EXT_histogram 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetHistogramEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetHistogramParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetHistogramParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMinmaxEXT (GLenum, GLboolean, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetMinmaxParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMinmaxParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glHistogramEXT (GLenum, GLsizei, GLenum, GLboolean); +GLAPI void APIENTRY glMinmaxEXT (GLenum, GLenum, GLboolean); +GLAPI void APIENTRY glResetHistogramEXT (GLenum); +GLAPI void APIENTRY glResetMinmaxEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETHISTOGRAMEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETHISTOGRAMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMINMAXEXTPROC) (GLenum target, GLboolean reset, GLenum format, GLenum type, GLvoid *values); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMINMAXPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLHISTOGRAMEXTPROC) (GLenum target, GLsizei width, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLMINMAXEXTPROC) (GLenum target, GLenum internalformat, GLboolean sink); +typedef void (APIENTRYP PFNGLRESETHISTOGRAMEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLRESETMINMAXEXTPROC) (GLenum target); +#endif + +#ifndef GL_EXT_convolution +#define GL_EXT_convolution 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glConvolutionFilter1DEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glConvolutionParameterfEXT (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glConvolutionParameterfvEXT (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glConvolutionParameteriEXT (GLenum, GLenum, GLint); +GLAPI void APIENTRY glConvolutionParameterivEXT (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyConvolutionFilter1DEXT (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glCopyConvolutionFilter2DEXT (GLenum, GLenum, GLint, GLint, GLsizei, GLsizei); +GLAPI void APIENTRY glGetConvolutionFilterEXT (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetConvolutionParameterfvEXT (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetConvolutionParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetSeparableFilterEXT (GLenum, GLenum, GLenum, GLvoid *, GLvoid *, GLvoid *); +GLAPI void APIENTRY glSeparableFilter2DEXT (GLenum, GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *image); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint params); +typedef void (APIENTRYP PFNGLCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER1DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLCOPYCONVOLUTIONFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *image); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCONVOLUTIONPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETSEPARABLEFILTEREXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *row, GLvoid *column, GLvoid *span); +typedef void (APIENTRYP PFNGLSEPARABLEFILTER2DEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *row, const GLvoid *column); +#endif + +#ifndef GL_EXT_color_matrix +#define GL_EXT_color_matrix 1 +#endif + +#ifndef GL_SGI_color_table +#define GL_SGI_color_table 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableSGI (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glColorTableParameterfvSGI (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glColorTableParameterivSGI (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glCopyColorTableSGI (GLenum, GLenum, GLint, GLint, GLsizei); +GLAPI void APIENTRY glGetColorTableSGI (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterfvSGI (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetColorTableParameterivSGI (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOPYCOLORTABLESGIPROC) (GLenum target, GLenum internalformat, GLint x, GLint y, GLsizei width); +typedef void (APIENTRYP PFNGLGETCOLORTABLESGIPROC) (GLenum target, GLenum format, GLenum type, GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVSGIPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVSGIPROC) (GLenum target, GLenum pname, GLint *params); +#endif + +#ifndef GL_SGIX_pixel_texture +#define GL_SGIX_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenSGIX (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENSGIXPROC) (GLenum mode); +#endif + +#ifndef GL_SGIS_pixel_texture +#define GL_SGIS_pixel_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTexGenParameteriSGIS (GLenum, GLint); +GLAPI void APIENTRY glPixelTexGenParameterivSGIS (GLenum, const GLint *); +GLAPI void APIENTRY glPixelTexGenParameterfSGIS (GLenum, GLfloat); +GLAPI void APIENTRY glPixelTexGenParameterfvSGIS (GLenum, const GLfloat *); +GLAPI void APIENTRY glGetPixelTexGenParameterivSGIS (GLenum, GLint *); +GLAPI void APIENTRY glGetPixelTexGenParameterfvSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERISGISPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERIVSGISPROC) (GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPIXELTEXGENPARAMETERFVSGISPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIS_texture4D +#define GL_SGIS_texture4D 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexImage4DSGIS (GLenum, GLint, GLenum, GLsizei, GLsizei, GLsizei, GLsizei, GLint, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glTexSubImage4DSGIS (GLenum, GLint, GLint, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXIMAGE4DSGISPROC) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +typedef void (APIENTRYP PFNGLTEXSUBIMAGE4DSGISPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint woffset, GLsizei width, GLsizei height, GLsizei depth, GLsizei size4d, GLenum format, GLenum type, const GLvoid *pixels); +#endif + +#ifndef GL_SGI_texture_color_table +#define GL_SGI_texture_color_table 1 +#endif + +#ifndef GL_EXT_cmyka +#define GL_EXT_cmyka 1 +#endif + +#ifndef GL_EXT_texture_object +#define GL_EXT_texture_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreTexturesResidentEXT (GLsizei, const GLuint *, GLboolean *); +GLAPI void APIENTRY glBindTextureEXT (GLenum, GLuint); +GLAPI void APIENTRY glDeleteTexturesEXT (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenTexturesEXT (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsTextureEXT (GLuint); +GLAPI void APIENTRY glPrioritizeTexturesEXT (GLsizei, const GLuint *, const GLclampf *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLARETEXTURESRESIDENTEXTPROC) (GLsizei n, const GLuint *textures, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDTEXTUREEXTPROC) (GLenum target, GLuint texture); +typedef void (APIENTRYP PFNGLDELETETEXTURESEXTPROC) (GLsizei n, const GLuint *textures); +typedef void (APIENTRYP PFNGLGENTEXTURESEXTPROC) (GLsizei n, GLuint *textures); +typedef GLboolean (APIENTRYP PFNGLISTEXTUREEXTPROC) (GLuint texture); +typedef void (APIENTRYP PFNGLPRIORITIZETEXTURESEXTPROC) (GLsizei n, const GLuint *textures, const GLclampf *priorities); +#endif + +#ifndef GL_SGIS_detail_texture +#define GL_SGIS_detail_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDetailTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetDetailTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDETAILTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETDETAILTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_SGIS_sharpen_texture +#define GL_SGIS_sharpen_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSharpenTexFuncSGIS (GLenum, GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetSharpenTexFuncSGIS (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSHARPENTEXFUNCSGISPROC) (GLenum target, GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETSHARPENTEXFUNCSGISPROC) (GLenum target, GLfloat *points); +#endif + +#ifndef GL_EXT_packed_pixels +#define GL_EXT_packed_pixels 1 +#endif + +#ifndef GL_SGIS_texture_lod +#define GL_SGIS_texture_lod 1 +#endif + +#ifndef GL_SGIS_multisample +#define GL_SGIS_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskSGIS (GLclampf, GLboolean); +GLAPI void APIENTRY glSamplePatternSGIS (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKSGISPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNSGISPROC) (GLenum pattern); +#endif + +#ifndef GL_EXT_rescale_normal +#define GL_EXT_rescale_normal 1 +#endif + +#ifndef GL_EXT_vertex_array +#define GL_EXT_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glArrayElementEXT (GLint); +GLAPI void APIENTRY glColorPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glDrawArraysEXT (GLenum, GLint, GLsizei); +GLAPI void APIENTRY glEdgeFlagPointerEXT (GLsizei, GLsizei, const GLboolean *); +GLAPI void APIENTRY glGetPointervEXT (GLenum, GLvoid* *); +GLAPI void APIENTRY glIndexPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glNormalPointerEXT (GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glTexCoordPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexPointerEXT (GLint, GLenum, GLsizei, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLARRAYELEMENTEXTPROC) (GLint i); +typedef void (APIENTRYP PFNGLCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWARRAYSEXTPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTEREXTPROC) (GLsizei stride, GLsizei count, const GLboolean *pointer); +typedef void (APIENTRYP PFNGLGETPOINTERVEXTPROC) (GLenum pname, GLvoid* *params); +typedef void (APIENTRYP PFNGLINDEXPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, GLsizei count, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_misc_attribute +#define GL_EXT_misc_attribute 1 +#endif + +#ifndef GL_SGIS_generate_mipmap +#define GL_SGIS_generate_mipmap 1 +#endif + +#ifndef GL_SGIX_clipmap +#define GL_SGIX_clipmap 1 +#endif + +#ifndef GL_SGIX_shadow +#define GL_SGIX_shadow 1 +#endif + +#ifndef GL_SGIS_texture_edge_clamp +#define GL_SGIS_texture_edge_clamp 1 +#endif + +#ifndef GL_SGIS_texture_border_clamp +#define GL_SGIS_texture_border_clamp 1 +#endif + +#ifndef GL_EXT_blend_minmax +#define GL_EXT_blend_minmax 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_blend_subtract +#define GL_EXT_blend_subtract 1 +#endif + +#ifndef GL_EXT_blend_logic_op +#define GL_EXT_blend_logic_op 1 +#endif + +#ifndef GL_SGIX_interlace +#define GL_SGIX_interlace 1 +#endif + +#ifndef GL_SGIX_pixel_tiles +#define GL_SGIX_pixel_tiles 1 +#endif + +#ifndef GL_SGIX_texture_select +#define GL_SGIX_texture_select 1 +#endif + +#ifndef GL_SGIX_sprite +#define GL_SGIX_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSpriteParameterfSGIX (GLenum, GLfloat); +GLAPI void APIENTRY glSpriteParameterfvSGIX (GLenum, const GLfloat *); +GLAPI void APIENTRY glSpriteParameteriSGIX (GLenum, GLint); +GLAPI void APIENTRY glSpriteParameterivSGIX (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLSPRITEPARAMETERIVSGIXPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_texture_multi_buffer +#define GL_SGIX_texture_multi_buffer 1 +#endif + +#ifndef GL_EXT_point_parameters +#define GL_EXT_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfEXT (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvEXT (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFEXTPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVEXTPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIS_point_parameters +#define GL_SGIS_point_parameters 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameterfSGIS (GLenum, GLfloat); +GLAPI void APIENTRY glPointParameterfvSGIS (GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERFSGISPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERFVSGISPROC) (GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_SGIX_instruments +#define GL_SGIX_instruments 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLint APIENTRY glGetInstrumentsSGIX (void); +GLAPI void APIENTRY glInstrumentsBufferSGIX (GLsizei, GLint *); +GLAPI GLint APIENTRY glPollInstrumentsSGIX (GLint *); +GLAPI void APIENTRY glReadInstrumentsSGIX (GLint); +GLAPI void APIENTRY glStartInstrumentsSGIX (void); +GLAPI void APIENTRY glStopInstrumentsSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLint (APIENTRYP PFNGLGETINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLINSTRUMENTSBUFFERSGIXPROC) (GLsizei size, GLint *buffer); +typedef GLint (APIENTRYP PFNGLPOLLINSTRUMENTSSGIXPROC) (GLint *marker_p); +typedef void (APIENTRYP PFNGLREADINSTRUMENTSSGIXPROC) (GLint marker); +typedef void (APIENTRYP PFNGLSTARTINSTRUMENTSSGIXPROC) (void); +typedef void (APIENTRYP PFNGLSTOPINSTRUMENTSSGIXPROC) (GLint marker); +#endif + +#ifndef GL_SGIX_texture_scale_bias +#define GL_SGIX_texture_scale_bias 1 +#endif + +#ifndef GL_SGIX_framezoom +#define GL_SGIX_framezoom 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFrameZoomSGIX (GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAMEZOOMSGIXPROC) (GLint factor); +#endif + +#ifndef GL_SGIX_tag_sample_buffer +#define GL_SGIX_tag_sample_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTagSampleBufferSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTAGSAMPLEBUFFERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_polynomial_ffd +#define GL_SGIX_polynomial_ffd 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeformationMap3dSGIX (GLenum, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, GLdouble, GLdouble, GLint, GLint, const GLdouble *); +GLAPI void APIENTRY glDeformationMap3fSGIX (GLenum, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, GLfloat, GLfloat, GLint, GLint, const GLfloat *); +GLAPI void APIENTRY glDeformSGIX (GLbitfield); +GLAPI void APIENTRY glLoadIdentityDeformationMapSGIX (GLbitfield); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3DSGIXPROC) (GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, GLdouble w1, GLdouble w2, GLint wstride, GLint worder, const GLdouble *points); +typedef void (APIENTRYP PFNGLDEFORMATIONMAP3FSGIXPROC) (GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, GLfloat w1, GLfloat w2, GLint wstride, GLint worder, const GLfloat *points); +typedef void (APIENTRYP PFNGLDEFORMSGIXPROC) (GLbitfield mask); +typedef void (APIENTRYP PFNGLLOADIDENTITYDEFORMATIONMAPSGIXPROC) (GLbitfield mask); +#endif + +#ifndef GL_SGIX_reference_plane +#define GL_SGIX_reference_plane 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReferencePlaneSGIX (const GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREFERENCEPLANESGIXPROC) (const GLdouble *equation); +#endif + +#ifndef GL_SGIX_flush_raster +#define GL_SGIX_flush_raster 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushRasterSGIX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHRASTERSGIXPROC) (void); +#endif + +#ifndef GL_SGIX_depth_texture +#define GL_SGIX_depth_texture 1 +#endif + +#ifndef GL_SGIS_fog_function +#define GL_SGIS_fog_function 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogFuncSGIS (GLsizei, const GLfloat *); +GLAPI void APIENTRY glGetFogFuncSGIS (GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGFUNCSGISPROC) (GLsizei n, const GLfloat *points); +typedef void (APIENTRYP PFNGLGETFOGFUNCSGISPROC) (GLfloat *points); +#endif + +#ifndef GL_SGIX_fog_offset +#define GL_SGIX_fog_offset 1 +#endif + +#ifndef GL_HP_image_transform +#define GL_HP_image_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glImageTransformParameteriHP (GLenum, GLenum, GLint); +GLAPI void APIENTRY glImageTransformParameterfHP (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glImageTransformParameterivHP (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glImageTransformParameterfvHP (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetImageTransformParameterivHP (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetImageTransformParameterfvHP (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIHPPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFHPPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERIVHPPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETIMAGETRANSFORMPARAMETERFVHPPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_HP_convolution_border_modes +#define GL_HP_convolution_border_modes 1 +#endif + +#ifndef GL_SGIX_texture_add_env +#define GL_SGIX_texture_add_env 1 +#endif + +#ifndef GL_EXT_color_subtable +#define GL_EXT_color_subtable 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorSubTableEXT (GLenum, GLsizei, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glCopyColorSubTableEXT (GLenum, GLsizei, GLint, GLint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLsizei count, GLenum format, GLenum type, const GLvoid *data); +typedef void (APIENTRYP PFNGLCOPYCOLORSUBTABLEEXTPROC) (GLenum target, GLsizei start, GLint x, GLint y, GLsizei width); +#endif + +#ifndef GL_PGI_vertex_hints +#define GL_PGI_vertex_hints 1 +#endif + +#ifndef GL_PGI_misc_hints +#define GL_PGI_misc_hints 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glHintPGI (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLHINTPGIPROC) (GLenum target, GLint mode); +#endif + +#ifndef GL_EXT_paletted_texture +#define GL_EXT_paletted_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorTableEXT (GLenum, GLenum, GLsizei, GLenum, GLenum, const GLvoid *); +GLAPI void APIENTRY glGetColorTableEXT (GLenum, GLenum, GLenum, GLvoid *); +GLAPI void APIENTRY glGetColorTableParameterivEXT (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetColorTableParameterfvEXT (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORTABLEEXTPROC) (GLenum target, GLenum internalFormat, GLsizei width, GLenum format, GLenum type, const GLvoid *table); +typedef void (APIENTRYP PFNGLGETCOLORTABLEEXTPROC) (GLenum target, GLenum format, GLenum type, GLvoid *data); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOLORTABLEPARAMETERFVEXTPROC) (GLenum target, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_EXT_clip_volume_hint +#define GL_EXT_clip_volume_hint 1 +#endif + +#ifndef GL_SGIX_list_priority +#define GL_SGIX_list_priority 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetListParameterfvSGIX (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetListParameterivSGIX (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glListParameterfSGIX (GLuint, GLenum, GLfloat); +GLAPI void APIENTRY glListParameterfvSGIX (GLuint, GLenum, const GLfloat *); +GLAPI void APIENTRY glListParameteriSGIX (GLuint, GLenum, GLint); +GLAPI void APIENTRY glListParameterivSGIX (GLuint, GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGETLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERFSGIXPROC) (GLuint list, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLLISTPARAMETERFVSGIXPROC) (GLuint list, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLLISTPARAMETERISGIXPROC) (GLuint list, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLLISTPARAMETERIVSGIXPROC) (GLuint list, GLenum pname, const GLint *params); +#endif + +#ifndef GL_SGIX_ir_instrument1 +#define GL_SGIX_ir_instrument1 1 +#endif + +#ifndef GL_SGIX_calligraphic_fragment +#define GL_SGIX_calligraphic_fragment 1 +#endif + +#ifndef GL_SGIX_texture_lod_bias +#define GL_SGIX_texture_lod_bias 1 +#endif + +#ifndef GL_SGIX_shadow_ambient +#define GL_SGIX_shadow_ambient 1 +#endif + +#ifndef GL_EXT_index_texture +#define GL_EXT_index_texture 1 +#endif + +#ifndef GL_EXT_index_material +#define GL_EXT_index_material 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_EXT_index_func +#define GL_EXT_index_func 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIndexFuncEXT (GLenum, GLclampf); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLINDEXFUNCEXTPROC) (GLenum func, GLclampf ref); +#endif + +#ifndef GL_EXT_index_array_formats +#define GL_EXT_index_array_formats 1 +#endif + +#ifndef GL_EXT_compiled_vertex_array +#define GL_EXT_compiled_vertex_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glLockArraysEXT (GLint, GLsizei); +GLAPI void APIENTRY glUnlockArraysEXT (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLLOCKARRAYSEXTPROC) (GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLUNLOCKARRAYSEXTPROC) (void); +#endif + +#ifndef GL_EXT_cull_vertex +#define GL_EXT_cull_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCullParameterdvEXT (GLenum, GLdouble *); +GLAPI void APIENTRY glCullParameterfvEXT (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCULLPARAMETERDVEXTPROC) (GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLCULLPARAMETERFVEXTPROC) (GLenum pname, GLfloat *params); +#endif + +#ifndef GL_SGIX_ycrcb +#define GL_SGIX_ycrcb 1 +#endif + +#ifndef GL_SGIX_fragment_lighting +#define GL_SGIX_fragment_lighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFragmentColorMaterialSGIX (GLenum, GLenum); +GLAPI void APIENTRY glFragmentLightfSGIX (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glFragmentLightfvSGIX (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentLightiSGIX (GLenum, GLenum, GLint); +GLAPI void APIENTRY glFragmentLightivSGIX (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glFragmentLightModelfSGIX (GLenum, GLfloat); +GLAPI void APIENTRY glFragmentLightModelfvSGIX (GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentLightModeliSGIX (GLenum, GLint); +GLAPI void APIENTRY glFragmentLightModelivSGIX (GLenum, const GLint *); +GLAPI void APIENTRY glFragmentMaterialfSGIX (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glFragmentMaterialfvSGIX (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glFragmentMaterialiSGIX (GLenum, GLenum, GLint); +GLAPI void APIENTRY glFragmentMaterialivSGIX (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glGetFragmentLightfvSGIX (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFragmentLightivSGIX (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetFragmentMaterialfvSGIX (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFragmentMaterialivSGIX (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glLightEnviSGIX (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFRAGMENTCOLORMATERIALSGIXPROC) (GLenum face, GLenum mode); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFSGIXPROC) (GLenum light, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTISGIXPROC) (GLenum light, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFSGIXPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELFVSGIXPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELISGIXPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTLIGHTMODELIVSGIXPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFSGIXPROC) (GLenum face, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALISGIXPROC) (GLenum face, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTFVSGIXPROC) (GLenum light, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTLIGHTIVSGIXPROC) (GLenum light, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALFVSGIXPROC) (GLenum face, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFRAGMENTMATERIALIVSGIXPROC) (GLenum face, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLLIGHTENVISGIXPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_IBM_rasterpos_clip +#define GL_IBM_rasterpos_clip 1 +#endif + +#ifndef GL_HP_texture_lighting +#define GL_HP_texture_lighting 1 +#endif + +#ifndef GL_EXT_draw_range_elements +#define GL_EXT_draw_range_elements 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawRangeElementsEXT (GLenum, GLuint, GLuint, GLsizei, GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTSEXTPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices); +#endif + +#ifndef GL_WIN_phong_shading +#define GL_WIN_phong_shading 1 +#endif + +#ifndef GL_WIN_specular_fog +#define GL_WIN_specular_fog 1 +#endif + +#ifndef GL_EXT_light_texture +#define GL_EXT_light_texture 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glApplyTextureEXT (GLenum); +GLAPI void APIENTRY glTextureLightEXT (GLenum); +GLAPI void APIENTRY glTextureMaterialEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLAPPLYTEXTUREEXTPROC) (GLenum mode); +typedef void (APIENTRYP PFNGLTEXTURELIGHTEXTPROC) (GLenum pname); +typedef void (APIENTRYP PFNGLTEXTUREMATERIALEXTPROC) (GLenum face, GLenum mode); +#endif + +#ifndef GL_SGIX_blend_alpha_minmax +#define GL_SGIX_blend_alpha_minmax 1 +#endif + +#ifndef GL_EXT_bgra +#define GL_EXT_bgra 1 +#endif + +#ifndef GL_SGIX_async +#define GL_SGIX_async 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glAsyncMarkerSGIX (GLuint); +GLAPI GLint APIENTRY glFinishAsyncSGIX (GLuint *); +GLAPI GLint APIENTRY glPollAsyncSGIX (GLuint *); +GLAPI GLuint APIENTRY glGenAsyncMarkersSGIX (GLsizei); +GLAPI void APIENTRY glDeleteAsyncMarkersSGIX (GLuint, GLsizei); +GLAPI GLboolean APIENTRY glIsAsyncMarkerSGIX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLASYNCMARKERSGIXPROC) (GLuint marker); +typedef GLint (APIENTRYP PFNGLFINISHASYNCSGIXPROC) (GLuint *markerp); +typedef GLint (APIENTRYP PFNGLPOLLASYNCSGIXPROC) (GLuint *markerp); +typedef GLuint (APIENTRYP PFNGLGENASYNCMARKERSSGIXPROC) (GLsizei range); +typedef void (APIENTRYP PFNGLDELETEASYNCMARKERSSGIXPROC) (GLuint marker, GLsizei range); +typedef GLboolean (APIENTRYP PFNGLISASYNCMARKERSGIXPROC) (GLuint marker); +#endif + +#ifndef GL_SGIX_async_pixel +#define GL_SGIX_async_pixel 1 +#endif + +#ifndef GL_SGIX_async_histogram +#define GL_SGIX_async_histogram 1 +#endif + +#ifndef GL_INTEL_parallel_arrays +#define GL_INTEL_parallel_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexPointervINTEL (GLint, GLenum, const GLvoid* *); +GLAPI void APIENTRY glNormalPointervINTEL (GLenum, const GLvoid* *); +GLAPI void APIENTRY glColorPointervINTEL (GLint, GLenum, const GLvoid* *); +GLAPI void APIENTRY glTexCoordPointervINTEL (GLint, GLenum, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLNORMALPOINTERVINTELPROC) (GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLCOLORPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERVINTELPROC) (GLint size, GLenum type, const GLvoid* *pointer); +#endif + +#ifndef GL_HP_occlusion_test +#define GL_HP_occlusion_test 1 +#endif + +#ifndef GL_EXT_pixel_transform +#define GL_EXT_pixel_transform 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelTransformParameteriEXT (GLenum, GLenum, GLint); +GLAPI void APIENTRY glPixelTransformParameterfEXT (GLenum, GLenum, GLfloat); +GLAPI void APIENTRY glPixelTransformParameterivEXT (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glPixelTransformParameterfvEXT (GLenum, GLenum, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIEXTPROC) (GLenum target, GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFEXTPROC) (GLenum target, GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERIVEXTPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLPIXELTRANSFORMPARAMETERFVEXTPROC) (GLenum target, GLenum pname, const GLfloat *params); +#endif + +#ifndef GL_EXT_pixel_transform_color_table +#define GL_EXT_pixel_transform_color_table 1 +#endif + +#ifndef GL_EXT_shared_texture_palette +#define GL_EXT_shared_texture_palette 1 +#endif + +#ifndef GL_EXT_separate_specular_color +#define GL_EXT_separate_specular_color 1 +#endif + +#ifndef GL_EXT_secondary_color +#define GL_EXT_secondary_color 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSecondaryColor3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glSecondaryColor3bvEXT (const GLbyte *); +GLAPI void APIENTRY glSecondaryColor3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glSecondaryColor3dvEXT (const GLdouble *); +GLAPI void APIENTRY glSecondaryColor3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glSecondaryColor3fvEXT (const GLfloat *); +GLAPI void APIENTRY glSecondaryColor3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glSecondaryColor3ivEXT (const GLint *); +GLAPI void APIENTRY glSecondaryColor3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glSecondaryColor3svEXT (const GLshort *); +GLAPI void APIENTRY glSecondaryColor3ubEXT (GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glSecondaryColor3ubvEXT (const GLubyte *); +GLAPI void APIENTRY glSecondaryColor3uiEXT (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSecondaryColor3uivEXT (const GLuint *); +GLAPI void APIENTRY glSecondaryColor3usEXT (GLushort, GLushort, GLushort); +GLAPI void APIENTRY glSecondaryColor3usvEXT (const GLushort *); +GLAPI void APIENTRY glSecondaryColorPointerEXT (GLint, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BEXTPROC) (GLbyte red, GLbyte green, GLbyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DEXTPROC) (GLdouble red, GLdouble green, GLdouble blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FEXTPROC) (GLfloat red, GLfloat green, GLfloat blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IEXTPROC) (GLint red, GLint green, GLint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SEXTPROC) (GLshort red, GLshort green, GLshort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBEXTPROC) (GLubyte red, GLubyte green, GLubyte blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UBVEXTPROC) (const GLubyte *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIEXTPROC) (GLuint red, GLuint green, GLuint blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3UIVEXTPROC) (const GLuint *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USEXTPROC) (GLushort red, GLushort green, GLushort blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3USVEXTPROC) (const GLushort *v); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTEREXTPROC) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_perturb_normal +#define GL_EXT_texture_perturb_normal 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureNormalEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURENORMALEXTPROC) (GLenum mode); +#endif + +#ifndef GL_EXT_multi_draw_arrays +#define GL_EXT_multi_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiDrawArraysEXT (GLenum, GLint *, GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawElementsEXT (GLenum, const GLsizei *, GLenum, const GLvoid* *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIDRAWARRAYSEXTPROC) (GLenum mode, GLint *first, GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTSEXTPROC) (GLenum mode, const GLsizei *count, GLenum type, const GLvoid* *indices, GLsizei primcount); +#endif + +#ifndef GL_EXT_fog_coord +#define GL_EXT_fog_coord 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFogCoordfEXT (GLfloat); +GLAPI void APIENTRY glFogCoordfvEXT (const GLfloat *); +GLAPI void APIENTRY glFogCoorddEXT (GLdouble); +GLAPI void APIENTRY glFogCoorddvEXT (const GLdouble *); +GLAPI void APIENTRY glFogCoordPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFOGCOORDFEXTPROC) (GLfloat coord); +typedef void (APIENTRYP PFNGLFOGCOORDFVEXTPROC) (const GLfloat *coord); +typedef void (APIENTRYP PFNGLFOGCOORDDEXTPROC) (GLdouble coord); +typedef void (APIENTRYP PFNGLFOGCOORDDVEXTPROC) (const GLdouble *coord); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_REND_screen_coordinates +#define GL_REND_screen_coordinates 1 +#endif + +#ifndef GL_EXT_coordinate_frame +#define GL_EXT_coordinate_frame 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTangent3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glTangent3bvEXT (const GLbyte *); +GLAPI void APIENTRY glTangent3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glTangent3dvEXT (const GLdouble *); +GLAPI void APIENTRY glTangent3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTangent3fvEXT (const GLfloat *); +GLAPI void APIENTRY glTangent3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glTangent3ivEXT (const GLint *); +GLAPI void APIENTRY glTangent3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glTangent3svEXT (const GLshort *); +GLAPI void APIENTRY glBinormal3bEXT (GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glBinormal3bvEXT (const GLbyte *); +GLAPI void APIENTRY glBinormal3dEXT (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glBinormal3dvEXT (const GLdouble *); +GLAPI void APIENTRY glBinormal3fEXT (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glBinormal3fvEXT (const GLfloat *); +GLAPI void APIENTRY glBinormal3iEXT (GLint, GLint, GLint); +GLAPI void APIENTRY glBinormal3ivEXT (const GLint *); +GLAPI void APIENTRY glBinormal3sEXT (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glBinormal3svEXT (const GLshort *); +GLAPI void APIENTRY glTangentPointerEXT (GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glBinormalPointerEXT (GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTANGENT3BEXTPROC) (GLbyte tx, GLbyte ty, GLbyte tz); +typedef void (APIENTRYP PFNGLTANGENT3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLTANGENT3DEXTPROC) (GLdouble tx, GLdouble ty, GLdouble tz); +typedef void (APIENTRYP PFNGLTANGENT3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLTANGENT3FEXTPROC) (GLfloat tx, GLfloat ty, GLfloat tz); +typedef void (APIENTRYP PFNGLTANGENT3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLTANGENT3IEXTPROC) (GLint tx, GLint ty, GLint tz); +typedef void (APIENTRYP PFNGLTANGENT3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLTANGENT3SEXTPROC) (GLshort tx, GLshort ty, GLshort tz); +typedef void (APIENTRYP PFNGLTANGENT3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLBINORMAL3BEXTPROC) (GLbyte bx, GLbyte by, GLbyte bz); +typedef void (APIENTRYP PFNGLBINORMAL3BVEXTPROC) (const GLbyte *v); +typedef void (APIENTRYP PFNGLBINORMAL3DEXTPROC) (GLdouble bx, GLdouble by, GLdouble bz); +typedef void (APIENTRYP PFNGLBINORMAL3DVEXTPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLBINORMAL3FEXTPROC) (GLfloat bx, GLfloat by, GLfloat bz); +typedef void (APIENTRYP PFNGLBINORMAL3FVEXTPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLBINORMAL3IEXTPROC) (GLint bx, GLint by, GLint bz); +typedef void (APIENTRYP PFNGLBINORMAL3IVEXTPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLBINORMAL3SEXTPROC) (GLshort bx, GLshort by, GLshort bz); +typedef void (APIENTRYP PFNGLBINORMAL3SVEXTPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLTANGENTPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLBINORMALPOINTEREXTPROC) (GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_EXT_texture_env_combine +#define GL_EXT_texture_env_combine 1 +#endif + +#ifndef GL_APPLE_specular_vector +#define GL_APPLE_specular_vector 1 +#endif + +#ifndef GL_APPLE_transform_hint +#define GL_APPLE_transform_hint 1 +#endif + +#ifndef GL_SGIX_fog_scale +#define GL_SGIX_fog_scale 1 +#endif + +#ifndef GL_SUNX_constant_data +#define GL_SUNX_constant_data 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFinishTextureSUNX (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFINISHTEXTURESUNXPROC) (void); +#endif + +#ifndef GL_SUN_global_alpha +#define GL_SUN_global_alpha 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGlobalAlphaFactorbSUN (GLbyte); +GLAPI void APIENTRY glGlobalAlphaFactorsSUN (GLshort); +GLAPI void APIENTRY glGlobalAlphaFactoriSUN (GLint); +GLAPI void APIENTRY glGlobalAlphaFactorfSUN (GLfloat); +GLAPI void APIENTRY glGlobalAlphaFactordSUN (GLdouble); +GLAPI void APIENTRY glGlobalAlphaFactorubSUN (GLubyte); +GLAPI void APIENTRY glGlobalAlphaFactorusSUN (GLushort); +GLAPI void APIENTRY glGlobalAlphaFactoruiSUN (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORBSUNPROC) (GLbyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORSSUNPROC) (GLshort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORISUNPROC) (GLint factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORFSUNPROC) (GLfloat factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORDSUNPROC) (GLdouble factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUBSUNPROC) (GLubyte factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUSSUNPROC) (GLushort factor); +typedef void (APIENTRYP PFNGLGLOBALALPHAFACTORUISUNPROC) (GLuint factor); +#endif + +#ifndef GL_SUN_triangle_list +#define GL_SUN_triangle_list 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glReplacementCodeuiSUN (GLuint); +GLAPI void APIENTRY glReplacementCodeusSUN (GLushort); +GLAPI void APIENTRY glReplacementCodeubSUN (GLubyte); +GLAPI void APIENTRY glReplacementCodeuivSUN (const GLuint *); +GLAPI void APIENTRY glReplacementCodeusvSUN (const GLushort *); +GLAPI void APIENTRY glReplacementCodeubvSUN (const GLubyte *); +GLAPI void APIENTRY glReplacementCodePointerSUN (GLenum, GLsizei, const GLvoid* *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUISUNPROC) (GLuint code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSSUNPROC) (GLushort code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBSUNPROC) (GLubyte code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVSUNPROC) (const GLuint *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUSVSUNPROC) (const GLushort *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUBVSUNPROC) (const GLubyte *code); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEPOINTERSUNPROC) (GLenum type, GLsizei stride, const GLvoid* *pointer); +#endif + +#ifndef GL_SUN_vertex +#define GL_SUN_vertex 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColor4ubVertex2fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4ubVertex2fvSUN (const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glColor4ubVertex3fSUN (GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4ubVertex3fvSUN (const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor3fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fVertex3fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord4fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord4fVertex4fvSUN (const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fSUN (GLfloat, GLfloat, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor4ubVertex3fvSUN (const GLfloat *, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord2fColor4fNormal3fVertex3fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fSUN (GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glTexCoord4fColor4fNormal3fVertex4fvSUN (const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiVertex3fvSUN (const GLuint *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fSUN (GLuint, GLubyte, GLubyte, GLubyte, GLubyte, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor4ubVertex3fvSUN (const GLuint *, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiColor4fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN (GLuint, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN (const GLuint *, const GLfloat *, const GLfloat *, const GLfloat *, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX2FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FSUNPROC) (GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4UBVERTEX3FVSUNPROC) (const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FSUNPROC) (GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4UBVERTEX3FVSUNPROC) (const GLfloat *tc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLTEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FSUNPROC) (GLfloat s, GLfloat t, GLfloat p, GLfloat q, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLTEXCOORD4FCOLOR4FNORMAL3FVERTEX4FVSUNPROC) (const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FSUNPROC) (GLuint rc, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUIVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FSUNPROC) (GLuint rc, GLubyte r, GLubyte g, GLubyte b, GLubyte a, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4UBVERTEX3FVSUNPROC) (const GLuint *rc, const GLubyte *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUINORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUICOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *n, const GLfloat *v); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FSUNPROC) (GLuint rc, GLfloat s, GLfloat t, GLfloat r, GLfloat g, GLfloat b, GLfloat a, GLfloat nx, GLfloat ny, GLfloat nz, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLREPLACEMENTCODEUITEXCOORD2FCOLOR4FNORMAL3FVERTEX3FVSUNPROC) (const GLuint *rc, const GLfloat *tc, const GLfloat *c, const GLfloat *n, const GLfloat *v); +#endif + +#ifndef GL_EXT_blend_func_separate +#define GL_EXT_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateEXT (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEEXTPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_blend_func_separate +#define GL_INGR_blend_func_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendFuncSeparateINGR (GLenum, GLenum, GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDFUNCSEPARATEINGRPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); +#endif + +#ifndef GL_INGR_color_clamp +#define GL_INGR_color_clamp 1 +#endif + +#ifndef GL_INGR_interlace_read +#define GL_INGR_interlace_read 1 +#endif + +#ifndef GL_EXT_stencil_wrap +#define GL_EXT_stencil_wrap 1 +#endif + +#ifndef GL_EXT_422_pixels +#define GL_EXT_422_pixels 1 +#endif + +#ifndef GL_NV_texgen_reflection +#define GL_NV_texgen_reflection 1 +#endif + +#ifndef GL_SUN_convolution_border_modes +#define GL_SUN_convolution_border_modes 1 +#endif + +#ifndef GL_EXT_texture_env_add +#define GL_EXT_texture_env_add 1 +#endif + +#ifndef GL_EXT_texture_lod_bias +#define GL_EXT_texture_lod_bias 1 +#endif + +#ifndef GL_EXT_texture_filter_anisotropic +#define GL_EXT_texture_filter_anisotropic 1 +#endif + +#ifndef GL_EXT_vertex_weighting +#define GL_EXT_vertex_weighting 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexWeightfEXT (GLfloat); +GLAPI void APIENTRY glVertexWeightfvEXT (const GLfloat *); +GLAPI void APIENTRY glVertexWeightPointerEXT (GLsizei, GLenum, GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFEXTPROC) (GLfloat weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTFVEXTPROC) (const GLfloat *weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTPOINTEREXTPROC) (GLsizei size, GLenum type, GLsizei stride, const GLvoid *pointer); +#endif + +#ifndef GL_NV_light_max_exponent +#define GL_NV_light_max_exponent 1 +#endif + +#ifndef GL_NV_vertex_array_range +#define GL_NV_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glFlushVertexArrayRangeNV (void); +GLAPI void APIENTRY glVertexArrayRangeNV (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGENVPROC) (void); +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGENVPROC) (GLsizei length, const GLvoid *pointer); +#endif + +#ifndef GL_NV_register_combiners +#define GL_NV_register_combiners 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerParameterfvNV (GLenum, const GLfloat *); +GLAPI void APIENTRY glCombinerParameterfNV (GLenum, GLfloat); +GLAPI void APIENTRY glCombinerParameterivNV (GLenum, const GLint *); +GLAPI void APIENTRY glCombinerParameteriNV (GLenum, GLint); +GLAPI void APIENTRY glCombinerInputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glCombinerOutputNV (GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean); +GLAPI void APIENTRY glFinalCombinerInputNV (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glGetCombinerInputParameterfvNV (GLenum, GLenum, GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetCombinerInputParameterivNV (GLenum, GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetCombinerOutputParameterfvNV (GLenum, GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetCombinerOutputParameterivNV (GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetFinalCombinerInputParameterfvNV (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetFinalCombinerInputParameterivNV (GLenum, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFVNVPROC) (GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERFNVPROC) (GLenum pname, GLfloat param); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLCOMBINERPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLCOMBINERINPUTNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLCOMBINEROUTPUTNVPROC) (GLenum stage, GLenum portion, GLenum abOutput, GLenum cdOutput, GLenum sumOutput, GLenum scale, GLenum bias, GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum); +typedef void (APIENTRYP PFNGLFINALCOMBINERINPUTNVPROC) (GLenum variable, GLenum input, GLenum mapping, GLenum componentUsage); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERINPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum variable, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERFVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINEROUTPUTPARAMETERIVNVPROC) (GLenum stage, GLenum portion, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERFVNVPROC) (GLenum variable, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETFINALCOMBINERINPUTPARAMETERIVNVPROC) (GLenum variable, GLenum pname, GLint *params); +#endif + +#ifndef GL_NV_fog_distance +#define GL_NV_fog_distance 1 +#endif + +#ifndef GL_NV_texgen_emboss +#define GL_NV_texgen_emboss 1 +#endif + +#ifndef GL_NV_blend_square +#define GL_NV_blend_square 1 +#endif + +#ifndef GL_NV_texture_env_combine4 +#define GL_NV_texture_env_combine4 1 +#endif + +#ifndef GL_MESA_resize_buffers +#define GL_MESA_resize_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glResizeBuffersMESA (void); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLRESIZEBUFFERSMESAPROC) (void); +#endif + +#ifndef GL_MESA_window_pos +#define GL_MESA_window_pos 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glWindowPos2dMESA (GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos2dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos2fMESA (GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos2fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos2iMESA (GLint, GLint); +GLAPI void APIENTRY glWindowPos2ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos2sMESA (GLshort, GLshort); +GLAPI void APIENTRY glWindowPos2svMESA (const GLshort *); +GLAPI void APIENTRY glWindowPos3dMESA (GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos3dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos3fMESA (GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos3fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos3iMESA (GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos3ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos3sMESA (GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos3svMESA (const GLshort *); +GLAPI void APIENTRY glWindowPos4dMESA (GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glWindowPos4dvMESA (const GLdouble *); +GLAPI void APIENTRY glWindowPos4fMESA (GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glWindowPos4fvMESA (const GLfloat *); +GLAPI void APIENTRY glWindowPos4iMESA (GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glWindowPos4ivMESA (const GLint *); +GLAPI void APIENTRY glWindowPos4sMESA (GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glWindowPos4svMESA (const GLshort *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLWINDOWPOS2DMESAPROC) (GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLWINDOWPOS2DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2FMESAPROC) (GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLWINDOWPOS2FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2IMESAPROC) (GLint x, GLint y); +typedef void (APIENTRYP PFNGLWINDOWPOS2IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS2SMESAPROC) (GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLWINDOWPOS2SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3DMESAPROC) (GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLWINDOWPOS3DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3FMESAPROC) (GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLWINDOWPOS3FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3IMESAPROC) (GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLWINDOWPOS3IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS3SMESAPROC) (GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLWINDOWPOS3SVMESAPROC) (const GLshort *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4DMESAPROC) (GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLWINDOWPOS4DVMESAPROC) (const GLdouble *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4FMESAPROC) (GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLWINDOWPOS4FVMESAPROC) (const GLfloat *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4IMESAPROC) (GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLWINDOWPOS4IVMESAPROC) (const GLint *v); +typedef void (APIENTRYP PFNGLWINDOWPOS4SMESAPROC) (GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLWINDOWPOS4SVMESAPROC) (const GLshort *v); +#endif + +#ifndef GL_IBM_cull_vertex +#define GL_IBM_cull_vertex 1 +#endif + +#ifndef GL_IBM_multimode_draw_arrays +#define GL_IBM_multimode_draw_arrays 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMultiModeDrawArraysIBM (const GLenum *, const GLint *, const GLsizei *, GLsizei, GLint); +GLAPI void APIENTRY glMultiModeDrawElementsIBM (const GLenum *, const GLsizei *, GLenum, const GLvoid* const *, GLsizei, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMULTIMODEDRAWARRAYSIBMPROC) (const GLenum *mode, const GLint *first, const GLsizei *count, GLsizei primcount, GLint modestride); +typedef void (APIENTRYP PFNGLMULTIMODEDRAWELEMENTSIBMPROC) (const GLenum *mode, const GLsizei *count, GLenum type, const GLvoid* const *indices, GLsizei primcount, GLint modestride); +#endif + +#ifndef GL_IBM_vertex_array_lists +#define GL_IBM_vertex_array_lists 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glSecondaryColorPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glEdgeFlagPointerListIBM (GLint, const GLboolean* *, GLint); +GLAPI void APIENTRY glFogCoordPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glIndexPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glNormalPointerListIBM (GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glTexCoordPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +GLAPI void APIENTRY glVertexPointerListIBM (GLint, GLenum, GLint, const GLvoid* *, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLSECONDARYCOLORPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLEDGEFLAGPOINTERLISTIBMPROC) (GLint stride, const GLboolean* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLFOGCOORDPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLINDEXPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLNORMALPOINTERLISTIBMPROC) (GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLTEXCOORDPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +typedef void (APIENTRYP PFNGLVERTEXPOINTERLISTIBMPROC) (GLint size, GLenum type, GLint stride, const GLvoid* *pointer, GLint ptrstride); +#endif + +#ifndef GL_SGIX_subsample +#define GL_SGIX_subsample 1 +#endif + +#ifndef GL_SGIX_ycrcba +#define GL_SGIX_ycrcba 1 +#endif + +#ifndef GL_SGIX_ycrcb_subsample +#define GL_SGIX_ycrcb_subsample 1 +#endif + +#ifndef GL_SGIX_depth_pass_instrument +#define GL_SGIX_depth_pass_instrument 1 +#endif + +#ifndef GL_3DFX_texture_compression_FXT1 +#define GL_3DFX_texture_compression_FXT1 1 +#endif + +#ifndef GL_3DFX_multisample +#define GL_3DFX_multisample 1 +#endif + +#ifndef GL_3DFX_tbuffer +#define GL_3DFX_tbuffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTbufferMask3DFX (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTBUFFERMASK3DFXPROC) (GLuint mask); +#endif + +#ifndef GL_EXT_multisample +#define GL_EXT_multisample 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glSampleMaskEXT (GLclampf, GLboolean); +GLAPI void APIENTRY glSamplePatternEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSAMPLEMASKEXTPROC) (GLclampf value, GLboolean invert); +typedef void (APIENTRYP PFNGLSAMPLEPATTERNEXTPROC) (GLenum pattern); +#endif + +#ifndef GL_SGIX_vertex_preclip +#define GL_SGIX_vertex_preclip 1 +#endif + +#ifndef GL_SGIX_convolution_accuracy +#define GL_SGIX_convolution_accuracy 1 +#endif + +#ifndef GL_SGIX_resample +#define GL_SGIX_resample 1 +#endif + +#ifndef GL_SGIS_point_line_texgen +#define GL_SGIS_point_line_texgen 1 +#endif + +#ifndef GL_SGIS_texture_color_mask +#define GL_SGIS_texture_color_mask 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTextureColorMaskSGIS (GLboolean, GLboolean, GLboolean, GLboolean); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXTURECOLORMASKSGISPROC) (GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +#endif + +#ifndef GL_SGIX_igloo_interface +#define GL_SGIX_igloo_interface 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glIglooInterfaceSGIX (GLenum, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLIGLOOINTERFACESGIXPROC) (GLenum pname, const GLvoid *params); +#endif + +#ifndef GL_EXT_texture_env_dot3 +#define GL_EXT_texture_env_dot3 1 +#endif + +#ifndef GL_ATI_texture_mirror_once +#define GL_ATI_texture_mirror_once 1 +#endif + +#ifndef GL_NV_fence +#define GL_NV_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDeleteFencesNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenFencesNV (GLsizei, GLuint *); +GLAPI GLboolean APIENTRY glIsFenceNV (GLuint); +GLAPI GLboolean APIENTRY glTestFenceNV (GLuint); +GLAPI void APIENTRY glGetFenceivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glFinishFenceNV (GLuint); +GLAPI void APIENTRY glSetFenceNV (GLuint, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDELETEFENCESNVPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLGENFENCESNVPROC) (GLsizei n, GLuint *fences); +typedef GLboolean (APIENTRYP PFNGLISFENCENVPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLGETFENCEIVNVPROC) (GLuint fence, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFINISHFENCENVPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLSETFENCENVPROC) (GLuint fence, GLenum condition); +#endif + +#ifndef GL_NV_evaluators +#define GL_NV_evaluators 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glMapControlPointsNV (GLenum, GLuint, GLenum, GLsizei, GLsizei, GLint, GLint, GLboolean, const GLvoid *); +GLAPI void APIENTRY glMapParameterivNV (GLenum, GLenum, const GLint *); +GLAPI void APIENTRY glMapParameterfvNV (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetMapControlPointsNV (GLenum, GLuint, GLenum, GLsizei, GLsizei, GLboolean, GLvoid *); +GLAPI void APIENTRY glGetMapParameterivNV (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGetMapParameterfvNV (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetMapAttribParameterivNV (GLenum, GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetMapAttribParameterfvNV (GLenum, GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glEvalMapsNV (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLint uorder, GLint vorder, GLboolean packed, const GLvoid *points); +typedef void (APIENTRYP PFNGLMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, const GLint *params); +typedef void (APIENTRYP PFNGLMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPCONTROLPOINTSNVPROC) (GLenum target, GLuint index, GLenum type, GLsizei ustride, GLsizei vstride, GLboolean packed, GLvoid *points); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERIVNVPROC) (GLenum target, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPPARAMETERFVNVPROC) (GLenum target, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERIVNVPROC) (GLenum target, GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETMAPATTRIBPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLEVALMAPSNVPROC) (GLenum target, GLenum mode); +#endif + +#ifndef GL_NV_packed_depth_stencil +#define GL_NV_packed_depth_stencil 1 +#endif + +#ifndef GL_NV_register_combiners2 +#define GL_NV_register_combiners2 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glCombinerStageParameterfvNV (GLenum, GLenum, const GLfloat *); +GLAPI void APIENTRY glGetCombinerStageParameterfvNV (GLenum, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, const GLfloat *params); +typedef void (APIENTRYP PFNGLGETCOMBINERSTAGEPARAMETERFVNVPROC) (GLenum stage, GLenum pname, GLfloat *params); +#endif + +#ifndef GL_NV_texture_compression_vtc +#define GL_NV_texture_compression_vtc 1 +#endif + +#ifndef GL_NV_texture_rectangle +#define GL_NV_texture_rectangle 1 +#endif + +#ifndef GL_NV_texture_shader +#define GL_NV_texture_shader 1 +#endif + +#ifndef GL_NV_texture_shader2 +#define GL_NV_texture_shader2 1 +#endif + +#ifndef GL_NV_vertex_array_range2 +#define GL_NV_vertex_array_range2 1 +#endif + +#ifndef GL_NV_vertex_program +#define GL_NV_vertex_program 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glAreProgramsResidentNV (GLsizei, const GLuint *, GLboolean *); +GLAPI void APIENTRY glBindProgramNV (GLenum, GLuint); +GLAPI void APIENTRY glDeleteProgramsNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glExecuteProgramNV (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glGenProgramsNV (GLsizei, GLuint *); +GLAPI void APIENTRY glGetProgramParameterdvNV (GLenum, GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetProgramParameterfvNV (GLenum, GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetProgramivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetProgramStringNV (GLuint, GLenum, GLubyte *); +GLAPI void APIENTRY glGetTrackMatrixivNV (GLenum, GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribdvNV (GLuint, GLenum, GLdouble *); +GLAPI void APIENTRY glGetVertexAttribfvNV (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVertexAttribPointervNV (GLuint, GLenum, GLvoid* *); +GLAPI GLboolean APIENTRY glIsProgramNV (GLuint); +GLAPI void APIENTRY glLoadProgramNV (GLenum, GLuint, GLsizei, const GLubyte *); +GLAPI void APIENTRY glProgramParameter4dNV (GLenum, GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramParameter4dvNV (GLenum, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramParameter4fNV (GLenum, GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramParameter4fvNV (GLenum, GLuint, const GLfloat *); +GLAPI void APIENTRY glProgramParameters4dvNV (GLenum, GLuint, GLuint, const GLdouble *); +GLAPI void APIENTRY glProgramParameters4fvNV (GLenum, GLuint, GLuint, const GLfloat *); +GLAPI void APIENTRY glRequestResidentProgramsNV (GLsizei, const GLuint *); +GLAPI void APIENTRY glTrackMatrixNV (GLenum, GLuint, GLenum, GLenum); +GLAPI void APIENTRY glVertexAttribPointerNV (GLuint, GLint, GLenum, GLsizei, const GLvoid *); +GLAPI void APIENTRY glVertexAttrib1dNV (GLuint, GLdouble); +GLAPI void APIENTRY glVertexAttrib1dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib1fNV (GLuint, GLfloat); +GLAPI void APIENTRY glVertexAttrib1fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib1sNV (GLuint, GLshort); +GLAPI void APIENTRY glVertexAttrib1svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib2dNV (GLuint, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib2dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib2fNV (GLuint, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib2fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib2sNV (GLuint, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib2svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib3dNV (GLuint, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib3dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib3fNV (GLuint, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib3fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib3sNV (GLuint, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib3svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4dNV (GLuint, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexAttrib4dvNV (GLuint, const GLdouble *); +GLAPI void APIENTRY glVertexAttrib4fNV (GLuint, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexAttrib4fvNV (GLuint, const GLfloat *); +GLAPI void APIENTRY glVertexAttrib4sNV (GLuint, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexAttrib4svNV (GLuint, const GLshort *); +GLAPI void APIENTRY glVertexAttrib4ubNV (GLuint, GLubyte, GLubyte, GLubyte, GLubyte); +GLAPI void APIENTRY glVertexAttrib4ubvNV (GLuint, const GLubyte *); +GLAPI void APIENTRY glVertexAttribs1dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs1fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs1svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs2dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs2fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs2svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs3dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs3fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs3svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs4dvNV (GLuint, GLsizei, const GLdouble *); +GLAPI void APIENTRY glVertexAttribs4fvNV (GLuint, GLsizei, const GLfloat *); +GLAPI void APIENTRY glVertexAttribs4svNV (GLuint, GLsizei, const GLshort *); +GLAPI void APIENTRY glVertexAttribs4ubvNV (GLuint, GLsizei, const GLubyte *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLAREPROGRAMSRESIDENTNVPROC) (GLsizei n, const GLuint *programs, GLboolean *residences); +typedef void (APIENTRYP PFNGLBINDPROGRAMNVPROC) (GLenum target, GLuint id); +typedef void (APIENTRYP PFNGLDELETEPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLEXECUTEPROGRAMNVPROC) (GLenum target, GLuint id, const GLfloat *params); +typedef void (APIENTRYP PFNGLGENPROGRAMSNVPROC) (GLsizei n, GLuint *programs); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERDVNVPROC) (GLenum target, GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETPROGRAMPARAMETERFVNVPROC) (GLenum target, GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETPROGRAMSTRINGNVPROC) (GLuint id, GLenum pname, GLubyte *program); +typedef void (APIENTRYP PFNGLGETTRACKMATRIXIVNVPROC) (GLenum target, GLuint address, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBDVNVPROC) (GLuint index, GLenum pname, GLdouble *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBFVNVPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBIVNVPROC) (GLuint index, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBPOINTERVNVPROC) (GLuint index, GLenum pname, GLvoid* *pointer); +typedef GLboolean (APIENTRYP PFNGLISPROGRAMNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLLOADPROGRAMNVPROC) (GLenum target, GLuint id, GLsizei len, const GLubyte *program); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DNVPROC) (GLenum target, GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4DVNVPROC) (GLenum target, GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FNVPROC) (GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETER4FVNVPROC) (GLenum target, GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4DVNVPROC) (GLenum target, GLuint index, GLuint count, const GLdouble *v); +typedef void (APIENTRYP PFNGLPROGRAMPARAMETERS4FVNVPROC) (GLenum target, GLuint index, GLuint count, const GLfloat *v); +typedef void (APIENTRYP PFNGLREQUESTRESIDENTPROGRAMSNVPROC) (GLsizei n, const GLuint *programs); +typedef void (APIENTRYP PFNGLTRACKMATRIXNVPROC) (GLenum target, GLuint address, GLenum matrix, GLenum transform); +typedef void (APIENTRYP PFNGLVERTEXATTRIBPOINTERNVPROC) (GLuint index, GLint fsize, GLenum type, GLsizei stride, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DNVPROC) (GLuint index, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FNVPROC) (GLuint index, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SNVPROC) (GLuint index, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DNVPROC) (GLuint index, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FNVPROC) (GLuint index, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SNVPROC) (GLuint index, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DNVPROC) (GLuint index, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4DVNVPROC) (GLuint index, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FNVPROC) (GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4FVNVPROC) (GLuint index, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SNVPROC) (GLuint index, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4SVNVPROC) (GLuint index, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBNVPROC) (GLuint index, GLubyte x, GLubyte y, GLubyte z, GLubyte w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4UBVNVPROC) (GLuint index, const GLubyte *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4DVNVPROC) (GLuint index, GLsizei count, const GLdouble *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4FVNVPROC) (GLuint index, GLsizei count, const GLfloat *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4SVNVPROC) (GLuint index, GLsizei count, const GLshort *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4UBVNVPROC) (GLuint index, GLsizei count, const GLubyte *v); +#endif + +#ifndef GL_SGIX_texture_coordinate_clamp +#define GL_SGIX_texture_coordinate_clamp 1 +#endif + +#ifndef GL_SGIX_scalebias_hint +#define GL_SGIX_scalebias_hint 1 +#endif + +#ifndef GL_OML_interlace +#define GL_OML_interlace 1 +#endif + +#ifndef GL_OML_subsample +#define GL_OML_subsample 1 +#endif + +#ifndef GL_OML_resample +#define GL_OML_resample 1 +#endif + +#ifndef GL_NV_copy_depth_to_color +#define GL_NV_copy_depth_to_color 1 +#endif + +#ifndef GL_ATI_envmap_bumpmap +#define GL_ATI_envmap_bumpmap 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glTexBumpParameterivATI (GLenum, const GLint *); +GLAPI void APIENTRY glTexBumpParameterfvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glGetTexBumpParameterivATI (GLenum, GLint *); +GLAPI void APIENTRY glGetTexBumpParameterfvATI (GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERIVATIPROC) (GLenum pname, const GLint *param); +typedef void (APIENTRYP PFNGLTEXBUMPPARAMETERFVATIPROC) (GLenum pname, const GLfloat *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERIVATIPROC) (GLenum pname, GLint *param); +typedef void (APIENTRYP PFNGLGETTEXBUMPPARAMETERFVATIPROC) (GLenum pname, GLfloat *param); +#endif + +#ifndef GL_ATI_fragment_shader +#define GL_ATI_fragment_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glGenFragmentShadersATI (GLuint); +GLAPI void APIENTRY glBindFragmentShaderATI (GLuint); +GLAPI void APIENTRY glDeleteFragmentShaderATI (GLuint); +GLAPI void APIENTRY glBeginFragmentShaderATI (void); +GLAPI void APIENTRY glEndFragmentShaderATI (void); +GLAPI void APIENTRY glPassTexCoordATI (GLuint, GLuint, GLenum); +GLAPI void APIENTRY glSampleMapATI (GLuint, GLuint, GLenum); +GLAPI void APIENTRY glColorFragmentOp1ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glColorFragmentOp2ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glColorFragmentOp3ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp1ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp2ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glAlphaFragmentOp3ATI (GLenum, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSetFragmentShaderConstantATI (GLuint, const GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLGENFRAGMENTSHADERSATIPROC) (GLuint range); +typedef void (APIENTRYP PFNGLBINDFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDELETEFRAGMENTSHADERATIPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLENDFRAGMENTSHADERATIPROC) (void); +typedef void (APIENTRYP PFNGLPASSTEXCOORDATIPROC) (GLuint dst, GLuint coord, GLenum swizzle); +typedef void (APIENTRYP PFNGLSAMPLEMAPATIPROC) (GLuint dst, GLuint interp, GLenum swizzle); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLCOLORFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMask, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP1ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP2ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod); +typedef void (APIENTRYP PFNGLALPHAFRAGMENTOP3ATIPROC) (GLenum op, GLuint dst, GLuint dstMod, GLuint arg1, GLuint arg1Rep, GLuint arg1Mod, GLuint arg2, GLuint arg2Rep, GLuint arg2Mod, GLuint arg3, GLuint arg3Rep, GLuint arg3Mod); +typedef void (APIENTRYP PFNGLSETFRAGMENTSHADERCONSTANTATIPROC) (GLuint dst, const GLfloat *value); +#endif + +#ifndef GL_ATI_pn_triangles +#define GL_ATI_pn_triangles 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPNTrianglesiATI (GLenum, GLint); +GLAPI void APIENTRY glPNTrianglesfATI (GLenum, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPNTRIANGLESIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPNTRIANGLESFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_vertex_array_object +#define GL_ATI_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLuint APIENTRY glNewObjectBufferATI (GLsizei, const GLvoid *, GLenum); +GLAPI GLboolean APIENTRY glIsObjectBufferATI (GLuint); +GLAPI void APIENTRY glUpdateObjectBufferATI (GLuint, GLuint, GLsizei, const GLvoid *, GLenum); +GLAPI void APIENTRY glGetObjectBufferfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetObjectBufferivATI (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glFreeObjectBufferATI (GLuint); +GLAPI void APIENTRY glArrayObjectATI (GLenum, GLint, GLenum, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetArrayObjectfvATI (GLenum, GLenum, GLfloat *); +GLAPI void APIENTRY glGetArrayObjectivATI (GLenum, GLenum, GLint *); +GLAPI void APIENTRY glVariantArrayObjectATI (GLuint, GLenum, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetVariantArrayObjectfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVariantArrayObjectivATI (GLuint, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLuint (APIENTRYP PFNGLNEWOBJECTBUFFERATIPROC) (GLsizei size, const GLvoid *pointer, GLenum usage); +typedef GLboolean (APIENTRYP PFNGLISOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUPDATEOBJECTBUFFERATIPROC) (GLuint buffer, GLuint offset, GLsizei size, const GLvoid *pointer, GLenum preserve); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERFVATIPROC) (GLuint buffer, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETOBJECTBUFFERIVATIPROC) (GLuint buffer, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLFREEOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLARRAYOBJECTATIPROC) (GLenum array, GLint size, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTFVATIPROC) (GLenum array, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETARRAYOBJECTIVATIPROC) (GLenum array, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLVARIANTARRAYOBJECTATIPROC) (GLuint id, GLenum type, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTFVATIPROC) (GLuint id, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVARIANTARRAYOBJECTIVATIPROC) (GLuint id, GLenum pname, GLint *params); +#endif + +#ifndef GL_EXT_vertex_shader +#define GL_EXT_vertex_shader 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBeginVertexShaderEXT (void); +GLAPI void APIENTRY glEndVertexShaderEXT (void); +GLAPI void APIENTRY glBindVertexShaderEXT (GLuint); +GLAPI GLuint APIENTRY glGenVertexShadersEXT (GLuint); +GLAPI void APIENTRY glDeleteVertexShaderEXT (GLuint); +GLAPI void APIENTRY glShaderOp1EXT (GLenum, GLuint, GLuint); +GLAPI void APIENTRY glShaderOp2EXT (GLenum, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glShaderOp3EXT (GLenum, GLuint, GLuint, GLuint, GLuint); +GLAPI void APIENTRY glSwizzleEXT (GLuint, GLuint, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glWriteMaskEXT (GLuint, GLuint, GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glInsertComponentEXT (GLuint, GLuint, GLuint); +GLAPI void APIENTRY glExtractComponentEXT (GLuint, GLuint, GLuint); +GLAPI GLuint APIENTRY glGenSymbolsEXT (GLenum, GLenum, GLenum, GLuint); +GLAPI void APIENTRY glSetInvariantEXT (GLuint, GLenum, const GLvoid *); +GLAPI void APIENTRY glSetLocalConstantEXT (GLuint, GLenum, const GLvoid *); +GLAPI void APIENTRY glVariantbvEXT (GLuint, const GLbyte *); +GLAPI void APIENTRY glVariantsvEXT (GLuint, const GLshort *); +GLAPI void APIENTRY glVariantivEXT (GLuint, const GLint *); +GLAPI void APIENTRY glVariantfvEXT (GLuint, const GLfloat *); +GLAPI void APIENTRY glVariantdvEXT (GLuint, const GLdouble *); +GLAPI void APIENTRY glVariantubvEXT (GLuint, const GLubyte *); +GLAPI void APIENTRY glVariantusvEXT (GLuint, const GLushort *); +GLAPI void APIENTRY glVariantuivEXT (GLuint, const GLuint *); +GLAPI void APIENTRY glVariantPointerEXT (GLuint, GLenum, GLuint, const GLvoid *); +GLAPI void APIENTRY glEnableVariantClientStateEXT (GLuint); +GLAPI void APIENTRY glDisableVariantClientStateEXT (GLuint); +GLAPI GLuint APIENTRY glBindLightParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindMaterialParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindTexGenParameterEXT (GLenum, GLenum, GLenum); +GLAPI GLuint APIENTRY glBindTextureUnitParameterEXT (GLenum, GLenum); +GLAPI GLuint APIENTRY glBindParameterEXT (GLenum); +GLAPI GLboolean APIENTRY glIsVariantEnabledEXT (GLuint, GLenum); +GLAPI void APIENTRY glGetVariantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetVariantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetVariantFloatvEXT (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVariantPointervEXT (GLuint, GLenum, GLvoid* *); +GLAPI void APIENTRY glGetInvariantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetInvariantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetInvariantFloatvEXT (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetLocalConstantBooleanvEXT (GLuint, GLenum, GLboolean *); +GLAPI void APIENTRY glGetLocalConstantIntegervEXT (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetLocalConstantFloatvEXT (GLuint, GLenum, GLfloat *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBEGINVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLENDVERTEXSHADEREXTPROC) (void); +typedef void (APIENTRYP PFNGLBINDVERTEXSHADEREXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLGENVERTEXSHADERSEXTPROC) (GLuint range); +typedef void (APIENTRYP PFNGLDELETEVERTEXSHADEREXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLSHADEROP1EXTPROC) (GLenum op, GLuint res, GLuint arg1); +typedef void (APIENTRYP PFNGLSHADEROP2EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2); +typedef void (APIENTRYP PFNGLSHADEROP3EXTPROC) (GLenum op, GLuint res, GLuint arg1, GLuint arg2, GLuint arg3); +typedef void (APIENTRYP PFNGLSWIZZLEEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLWRITEMASKEXTPROC) (GLuint res, GLuint in, GLenum outX, GLenum outY, GLenum outZ, GLenum outW); +typedef void (APIENTRYP PFNGLINSERTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef void (APIENTRYP PFNGLEXTRACTCOMPONENTEXTPROC) (GLuint res, GLuint src, GLuint num); +typedef GLuint (APIENTRYP PFNGLGENSYMBOLSEXTPROC) (GLenum datatype, GLenum storagetype, GLenum range, GLuint components); +typedef void (APIENTRYP PFNGLSETINVARIANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLSETLOCALCONSTANTEXTPROC) (GLuint id, GLenum type, const GLvoid *addr); +typedef void (APIENTRYP PFNGLVARIANTBVEXTPROC) (GLuint id, const GLbyte *addr); +typedef void (APIENTRYP PFNGLVARIANTSVEXTPROC) (GLuint id, const GLshort *addr); +typedef void (APIENTRYP PFNGLVARIANTIVEXTPROC) (GLuint id, const GLint *addr); +typedef void (APIENTRYP PFNGLVARIANTFVEXTPROC) (GLuint id, const GLfloat *addr); +typedef void (APIENTRYP PFNGLVARIANTDVEXTPROC) (GLuint id, const GLdouble *addr); +typedef void (APIENTRYP PFNGLVARIANTUBVEXTPROC) (GLuint id, const GLubyte *addr); +typedef void (APIENTRYP PFNGLVARIANTUSVEXTPROC) (GLuint id, const GLushort *addr); +typedef void (APIENTRYP PFNGLVARIANTUIVEXTPROC) (GLuint id, const GLuint *addr); +typedef void (APIENTRYP PFNGLVARIANTPOINTEREXTPROC) (GLuint id, GLenum type, GLuint stride, const GLvoid *addr); +typedef void (APIENTRYP PFNGLENABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef void (APIENTRYP PFNGLDISABLEVARIANTCLIENTSTATEEXTPROC) (GLuint id); +typedef GLuint (APIENTRYP PFNGLBINDLIGHTPARAMETEREXTPROC) (GLenum light, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDMATERIALPARAMETEREXTPROC) (GLenum face, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXGENPARAMETEREXTPROC) (GLenum unit, GLenum coord, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDTEXTUREUNITPARAMETEREXTPROC) (GLenum unit, GLenum value); +typedef GLuint (APIENTRYP PFNGLBINDPARAMETEREXTPROC) (GLenum value); +typedef GLboolean (APIENTRYP PFNGLISVARIANTENABLEDEXTPROC) (GLuint id, GLenum cap); +typedef void (APIENTRYP PFNGLGETVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETVARIANTPOINTERVEXTPROC) (GLuint id, GLenum value, GLvoid* *data); +typedef void (APIENTRYP PFNGLGETINVARIANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETINVARIANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETINVARIANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTBOOLEANVEXTPROC) (GLuint id, GLenum value, GLboolean *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTINTEGERVEXTPROC) (GLuint id, GLenum value, GLint *data); +typedef void (APIENTRYP PFNGLGETLOCALCONSTANTFLOATVEXTPROC) (GLuint id, GLenum value, GLfloat *data); +#endif + +#ifndef GL_ATI_vertex_streams +#define GL_ATI_vertex_streams 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexStream1sATI (GLenum, GLshort); +GLAPI void APIENTRY glVertexStream1svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream1iATI (GLenum, GLint); +GLAPI void APIENTRY glVertexStream1ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream1fATI (GLenum, GLfloat); +GLAPI void APIENTRY glVertexStream1fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream1dATI (GLenum, GLdouble); +GLAPI void APIENTRY glVertexStream1dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream2sATI (GLenum, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream2svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream2iATI (GLenum, GLint, GLint); +GLAPI void APIENTRY glVertexStream2ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream2fATI (GLenum, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream2fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream2dATI (GLenum, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream2dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream3sATI (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream3svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream3iATI (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glVertexStream3ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream3fATI (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream3fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream3dATI (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream3dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glVertexStream4sATI (GLenum, GLshort, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glVertexStream4svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glVertexStream4iATI (GLenum, GLint, GLint, GLint, GLint); +GLAPI void APIENTRY glVertexStream4ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glVertexStream4fATI (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glVertexStream4fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glVertexStream4dATI (GLenum, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glVertexStream4dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glNormalStream3bATI (GLenum, GLbyte, GLbyte, GLbyte); +GLAPI void APIENTRY glNormalStream3bvATI (GLenum, const GLbyte *); +GLAPI void APIENTRY glNormalStream3sATI (GLenum, GLshort, GLshort, GLshort); +GLAPI void APIENTRY glNormalStream3svATI (GLenum, const GLshort *); +GLAPI void APIENTRY glNormalStream3iATI (GLenum, GLint, GLint, GLint); +GLAPI void APIENTRY glNormalStream3ivATI (GLenum, const GLint *); +GLAPI void APIENTRY glNormalStream3fATI (GLenum, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glNormalStream3fvATI (GLenum, const GLfloat *); +GLAPI void APIENTRY glNormalStream3dATI (GLenum, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glNormalStream3dvATI (GLenum, const GLdouble *); +GLAPI void APIENTRY glClientActiveVertexStreamATI (GLenum); +GLAPI void APIENTRY glVertexBlendEnviATI (GLenum, GLint); +GLAPI void APIENTRY glVertexBlendEnvfATI (GLenum, GLfloat); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SATIPROC) (GLenum stream, GLshort x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IATIPROC) (GLenum stream, GLint x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FATIPROC) (GLenum stream, GLfloat x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DATIPROC) (GLenum stream, GLdouble x); +typedef void (APIENTRYP PFNGLVERTEXSTREAM1DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SATIPROC) (GLenum stream, GLshort x, GLshort y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IATIPROC) (GLenum stream, GLint x, GLint y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FATIPROC) (GLenum stream, GLfloat x, GLfloat y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DATIPROC) (GLenum stream, GLdouble x, GLdouble y); +typedef void (APIENTRYP PFNGLVERTEXSTREAM2DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IATIPROC) (GLenum stream, GLint x, GLint y, GLint z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z); +typedef void (APIENTRYP PFNGLVERTEXSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SATIPROC) (GLenum stream, GLshort x, GLshort y, GLshort z, GLshort w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IATIPROC) (GLenum stream, GLint x, GLint y, GLint z, GLint w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FATIPROC) (GLenum stream, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DATIPROC) (GLenum stream, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLVERTEXSTREAM4DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BATIPROC) (GLenum stream, GLbyte nx, GLbyte ny, GLbyte nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3BVATIPROC) (GLenum stream, const GLbyte *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SATIPROC) (GLenum stream, GLshort nx, GLshort ny, GLshort nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3SVATIPROC) (GLenum stream, const GLshort *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IATIPROC) (GLenum stream, GLint nx, GLint ny, GLint nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3IVATIPROC) (GLenum stream, const GLint *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FATIPROC) (GLenum stream, GLfloat nx, GLfloat ny, GLfloat nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3FVATIPROC) (GLenum stream, const GLfloat *coords); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DATIPROC) (GLenum stream, GLdouble nx, GLdouble ny, GLdouble nz); +typedef void (APIENTRYP PFNGLNORMALSTREAM3DVATIPROC) (GLenum stream, const GLdouble *coords); +typedef void (APIENTRYP PFNGLCLIENTACTIVEVERTEXSTREAMATIPROC) (GLenum stream); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVIATIPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLVERTEXBLENDENVFATIPROC) (GLenum pname, GLfloat param); +#endif + +#ifndef GL_ATI_element_array +#define GL_ATI_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerATI (GLenum, const GLvoid *); +GLAPI void APIENTRY glDrawElementArrayATI (GLenum, GLsizei); +GLAPI void APIENTRY glDrawRangeElementArrayATI (GLenum, GLuint, GLuint, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERATIPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYATIPROC) (GLenum mode, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYATIPROC) (GLenum mode, GLuint start, GLuint end, GLsizei count); +#endif + +#ifndef GL_SUN_mesh_array +#define GL_SUN_mesh_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshArraysSUN (GLenum, GLint, GLsizei, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWMESHARRAYSSUNPROC) (GLenum mode, GLint first, GLsizei count, GLsizei width); +#endif + +#ifndef GL_SUN_slice_accum +#define GL_SUN_slice_accum 1 +#endif + +#ifndef GL_NV_multisample_filter_hint +#define GL_NV_multisample_filter_hint 1 +#endif + +#ifndef GL_NV_depth_clamp +#define GL_NV_depth_clamp 1 +#endif + +#ifndef GL_NV_occlusion_query +#define GL_NV_occlusion_query 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenOcclusionQueriesNV (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteOcclusionQueriesNV (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsOcclusionQueryNV (GLuint); +GLAPI void APIENTRY glBeginOcclusionQueryNV (GLuint); +GLAPI void APIENTRY glEndOcclusionQueryNV (void); +GLAPI void APIENTRY glGetOcclusionQueryivNV (GLuint, GLenum, GLint *); +GLAPI void APIENTRY glGetOcclusionQueryuivNV (GLuint, GLenum, GLuint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENOCCLUSIONQUERIESNVPROC) (GLsizei n, GLuint *ids); +typedef void (APIENTRYP PFNGLDELETEOCCLUSIONQUERIESNVPROC) (GLsizei n, const GLuint *ids); +typedef GLboolean (APIENTRYP PFNGLISOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLBEGINOCCLUSIONQUERYNVPROC) (GLuint id); +typedef void (APIENTRYP PFNGLENDOCCLUSIONQUERYNVPROC) (void); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYIVNVPROC) (GLuint id, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGETOCCLUSIONQUERYUIVNVPROC) (GLuint id, GLenum pname, GLuint *params); +#endif + +#ifndef GL_NV_point_sprite +#define GL_NV_point_sprite 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPointParameteriNV (GLenum, GLint); +GLAPI void APIENTRY glPointParameterivNV (GLenum, const GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPOINTPARAMETERINVPROC) (GLenum pname, GLint param); +typedef void (APIENTRYP PFNGLPOINTPARAMETERIVNVPROC) (GLenum pname, const GLint *params); +#endif + +#ifndef GL_NV_texture_shader3 +#define GL_NV_texture_shader3 1 +#endif + +#ifndef GL_NV_vertex_program1_1 +#define GL_NV_vertex_program1_1 1 +#endif + +#ifndef GL_EXT_shadow_funcs +#define GL_EXT_shadow_funcs 1 +#endif + +#ifndef GL_EXT_stencil_two_side +#define GL_EXT_stencil_two_side 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glActiveStencilFaceEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLACTIVESTENCILFACEEXTPROC) (GLenum face); +#endif + +#ifndef GL_ATI_text_fragment_shader +#define GL_ATI_text_fragment_shader 1 +#endif + +#ifndef GL_APPLE_client_storage +#define GL_APPLE_client_storage 1 +#endif + +#ifndef GL_APPLE_element_array +#define GL_APPLE_element_array 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glElementPointerAPPLE (GLenum, const GLvoid *); +GLAPI void APIENTRY glDrawElementArrayAPPLE (GLenum, GLint, GLsizei); +GLAPI void APIENTRY glDrawRangeElementArrayAPPLE (GLenum, GLuint, GLuint, GLint, GLsizei); +GLAPI void APIENTRY glMultiDrawElementArrayAPPLE (GLenum, const GLint *, const GLsizei *, GLsizei); +GLAPI void APIENTRY glMultiDrawRangeElementArrayAPPLE (GLenum, GLuint, GLuint, const GLint *, const GLsizei *, GLsizei); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLELEMENTPOINTERAPPLEPROC) (GLenum type, const GLvoid *pointer); +typedef void (APIENTRYP PFNGLDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, GLint first, GLsizei count); +typedef void (APIENTRYP PFNGLMULTIDRAWELEMENTARRAYAPPLEPROC) (GLenum mode, const GLint *first, const GLsizei *count, GLsizei primcount); +typedef void (APIENTRYP PFNGLMULTIDRAWRANGEELEMENTARRAYAPPLEPROC) (GLenum mode, GLuint start, GLuint end, const GLint *first, const GLsizei *count, GLsizei primcount); +#endif + +#ifndef GL_APPLE_fence +#define GL_APPLE_fence 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGenFencesAPPLE (GLsizei, GLuint *); +GLAPI void APIENTRY glDeleteFencesAPPLE (GLsizei, const GLuint *); +GLAPI void APIENTRY glSetFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glIsFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glTestFenceAPPLE (GLuint); +GLAPI void APIENTRY glFinishFenceAPPLE (GLuint); +GLAPI GLboolean APIENTRY glTestObjectAPPLE (GLenum, GLuint); +GLAPI void APIENTRY glFinishObjectAPPLE (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLGENFENCESAPPLEPROC) (GLsizei n, GLuint *fences); +typedef void (APIENTRYP PFNGLDELETEFENCESAPPLEPROC) (GLsizei n, const GLuint *fences); +typedef void (APIENTRYP PFNGLSETFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLISFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTFENCEAPPLEPROC) (GLuint fence); +typedef void (APIENTRYP PFNGLFINISHFENCEAPPLEPROC) (GLuint fence); +typedef GLboolean (APIENTRYP PFNGLTESTOBJECTAPPLEPROC) (GLenum object, GLuint name); +typedef void (APIENTRYP PFNGLFINISHOBJECTAPPLEPROC) (GLenum object, GLint name); +#endif + +#ifndef GL_APPLE_vertex_array_object +#define GL_APPLE_vertex_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBindVertexArrayAPPLE (GLuint); +GLAPI void APIENTRY glDeleteVertexArraysAPPLE (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenVertexArraysAPPLE (GLsizei, const GLuint *); +GLAPI GLboolean APIENTRY glIsVertexArrayAPPLE (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBINDVERTEXARRAYAPPLEPROC) (GLuint array); +typedef void (APIENTRYP PFNGLDELETEVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef void (APIENTRYP PFNGLGENVERTEXARRAYSAPPLEPROC) (GLsizei n, const GLuint *arrays); +typedef GLboolean (APIENTRYP PFNGLISVERTEXARRAYAPPLEPROC) (GLuint array); +#endif + +#ifndef GL_APPLE_vertex_array_range +#define GL_APPLE_vertex_array_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexArrayRangeAPPLE (GLsizei, GLvoid *); +GLAPI void APIENTRY glFlushVertexArrayRangeAPPLE (GLsizei, GLvoid *); +GLAPI void APIENTRY glVertexArrayParameteriAPPLE (GLenum, GLint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHVERTEXARRAYRANGEAPPLEPROC) (GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLVERTEXARRAYPARAMETERIAPPLEPROC) (GLenum pname, GLint param); +#endif + +#ifndef GL_APPLE_ycbcr_422 +#define GL_APPLE_ycbcr_422 1 +#endif + +#ifndef GL_S3_s3tc +#define GL_S3_s3tc 1 +#endif + +#ifndef GL_ATI_draw_buffers +#define GL_ATI_draw_buffers 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawBuffersATI (GLsizei, const GLenum *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDRAWBUFFERSATIPROC) (GLsizei n, const GLenum *bufs); +#endif + +#ifndef GL_ATI_pixel_format_float +#define GL_ATI_pixel_format_float 1 +/* This is really a WGL extension, but defines some associated GL enums. + * ATI does not export "GL_ATI_pixel_format_float" in the GL_EXTENSIONS string. + */ +#endif + +#ifndef GL_ATI_texture_env_combine3 +#define GL_ATI_texture_env_combine3 1 +#endif + +#ifndef GL_ATI_texture_float +#define GL_ATI_texture_float 1 +#endif + +#ifndef GL_NV_float_buffer +#define GL_NV_float_buffer 1 +#endif + +#ifndef GL_NV_fragment_program +#define GL_NV_fragment_program 1 +/* Some NV_fragment_program entry points are shared with ARB_vertex_program. */ +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glProgramNamedParameter4fNV (GLuint, GLsizei, const GLubyte *, GLfloat, GLfloat, GLfloat, GLfloat); +GLAPI void APIENTRY glProgramNamedParameter4dNV (GLuint, GLsizei, const GLubyte *, GLdouble, GLdouble, GLdouble, GLdouble); +GLAPI void APIENTRY glProgramNamedParameter4fvNV (GLuint, GLsizei, const GLubyte *, const GLfloat *); +GLAPI void APIENTRY glProgramNamedParameter4dvNV (GLuint, GLsizei, const GLubyte *, const GLdouble *); +GLAPI void APIENTRY glGetProgramNamedParameterfvNV (GLuint, GLsizei, const GLubyte *, GLfloat *); +GLAPI void APIENTRY glGetProgramNamedParameterdvNV (GLuint, GLsizei, const GLubyte *, GLdouble *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble x, GLdouble y, GLdouble z, GLdouble w); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4FVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLfloat *v); +typedef void (APIENTRYP PFNGLPROGRAMNAMEDPARAMETER4DVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, const GLdouble *v); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERFVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLfloat *params); +typedef void (APIENTRYP PFNGLGETPROGRAMNAMEDPARAMETERDVNVPROC) (GLuint id, GLsizei len, const GLubyte *name, GLdouble *params); +#endif + +#ifndef GL_NV_half_float +#define GL_NV_half_float 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertex2hNV (GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex2hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertex3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertex4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertex4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glNormal3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glNormal3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glColor3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glColor3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glColor4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glColor4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord1hNV (GLhalfNV); +GLAPI void APIENTRY glTexCoord1hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord2hNV (GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord2hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glTexCoord4hNV (GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glTexCoord4hvNV (const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord1hNV (GLenum, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord1hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord2hNV (GLenum, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord2hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord3hNV (GLenum, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord3hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glMultiTexCoord4hNV (GLenum, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glMultiTexCoord4hvNV (GLenum, const GLhalfNV *); +GLAPI void APIENTRY glFogCoordhNV (GLhalfNV); +GLAPI void APIENTRY glFogCoordhvNV (const GLhalfNV *); +GLAPI void APIENTRY glSecondaryColor3hNV (GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glSecondaryColor3hvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertexWeighthNV (GLhalfNV); +GLAPI void APIENTRY glVertexWeighthvNV (const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib1hNV (GLuint, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib1hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib2hNV (GLuint, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib2hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib3hNV (GLuint, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib3hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttrib4hNV (GLuint, GLhalfNV, GLhalfNV, GLhalfNV, GLhalfNV); +GLAPI void APIENTRY glVertexAttrib4hvNV (GLuint, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs1hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs2hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs3hvNV (GLuint, GLsizei, const GLhalfNV *); +GLAPI void APIENTRY glVertexAttribs4hvNV (GLuint, GLsizei, const GLhalfNV *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEX2HNVPROC) (GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEX2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX3HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEX3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEX4HNVPROC) (GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEX4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLNORMAL3HNVPROC) (GLhalfNV nx, GLhalfNV ny, GLhalfNV nz); +typedef void (APIENTRYP PFNGLNORMAL3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLCOLOR4HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue, GLhalfNV alpha); +typedef void (APIENTRYP PFNGLCOLOR4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD1HNVPROC) (GLhalfNV s); +typedef void (APIENTRYP PFNGLTEXCOORD1HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD2HNVPROC) (GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLTEXCOORD2HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD3HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLTEXCOORD3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLTEXCOORD4HNVPROC) (GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLTEXCOORD4HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HNVPROC) (GLenum target, GLhalfNV s); +typedef void (APIENTRYP PFNGLMULTITEXCOORD1HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t); +typedef void (APIENTRYP PFNGLMULTITEXCOORD2HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r); +typedef void (APIENTRYP PFNGLMULTITEXCOORD3HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HNVPROC) (GLenum target, GLhalfNV s, GLhalfNV t, GLhalfNV r, GLhalfNV q); +typedef void (APIENTRYP PFNGLMULTITEXCOORD4HVNVPROC) (GLenum target, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLFOGCOORDHNVPROC) (GLhalfNV fog); +typedef void (APIENTRYP PFNGLFOGCOORDHVNVPROC) (const GLhalfNV *fog); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HNVPROC) (GLhalfNV red, GLhalfNV green, GLhalfNV blue); +typedef void (APIENTRYP PFNGLSECONDARYCOLOR3HVNVPROC) (const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHNVPROC) (GLhalfNV weight); +typedef void (APIENTRYP PFNGLVERTEXWEIGHTHVNVPROC) (const GLhalfNV *weight); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HNVPROC) (GLuint index, GLhalfNV x); +typedef void (APIENTRYP PFNGLVERTEXATTRIB1HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y); +typedef void (APIENTRYP PFNGLVERTEXATTRIB2HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z); +typedef void (APIENTRYP PFNGLVERTEXATTRIB3HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HNVPROC) (GLuint index, GLhalfNV x, GLhalfNV y, GLhalfNV z, GLhalfNV w); +typedef void (APIENTRYP PFNGLVERTEXATTRIB4HVNVPROC) (GLuint index, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS1HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS2HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS3HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +typedef void (APIENTRYP PFNGLVERTEXATTRIBS4HVNVPROC) (GLuint index, GLsizei n, const GLhalfNV *v); +#endif + +#ifndef GL_NV_pixel_data_range +#define GL_NV_pixel_data_range 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPixelDataRangeNV (GLenum, GLsizei, GLvoid *); +GLAPI void APIENTRY glFlushPixelDataRangeNV (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPIXELDATARANGENVPROC) (GLenum target, GLsizei length, GLvoid *pointer); +typedef void (APIENTRYP PFNGLFLUSHPIXELDATARANGENVPROC) (GLenum target); +#endif + +#ifndef GL_NV_primitive_restart +#define GL_NV_primitive_restart 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glPrimitiveRestartNV (void); +GLAPI void APIENTRY glPrimitiveRestartIndexNV (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTNVPROC) (void); +typedef void (APIENTRYP PFNGLPRIMITIVERESTARTINDEXNVPROC) (GLuint index); +#endif + +#ifndef GL_NV_texture_expand_normal +#define GL_NV_texture_expand_normal 1 +#endif + +#ifndef GL_NV_vertex_program2 +#define GL_NV_vertex_program2 1 +#endif + +#ifndef GL_ATI_map_object_buffer +#define GL_ATI_map_object_buffer 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLvoid* APIENTRY glMapObjectBufferATI (GLuint); +GLAPI void APIENTRY glUnmapObjectBufferATI (GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLvoid* (APIENTRYP PFNGLMAPOBJECTBUFFERATIPROC) (GLuint buffer); +typedef void (APIENTRYP PFNGLUNMAPOBJECTBUFFERATIPROC) (GLuint buffer); +#endif + +#ifndef GL_ATI_separate_stencil +#define GL_ATI_separate_stencil 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStencilOpSeparateATI (GLenum, GLenum, GLenum, GLenum); +GLAPI void APIENTRY glStencilFuncSeparateATI (GLenum, GLenum, GLint, GLuint); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTENCILOPSEPARATEATIPROC) (GLenum face, GLenum sfail, GLenum dpfail, GLenum dppass); +typedef void (APIENTRYP PFNGLSTENCILFUNCSEPARATEATIPROC) (GLenum frontfunc, GLenum backfunc, GLint ref, GLuint mask); +#endif + +#ifndef GL_ATI_vertex_attrib_array_object +#define GL_ATI_vertex_attrib_array_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glVertexAttribArrayObjectATI (GLuint, GLint, GLenum, GLboolean, GLsizei, GLuint, GLuint); +GLAPI void APIENTRY glGetVertexAttribArrayObjectfvATI (GLuint, GLenum, GLfloat *); +GLAPI void APIENTRY glGetVertexAttribArrayObjectivATI (GLuint, GLenum, GLint *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLVERTEXATTRIBARRAYOBJECTATIPROC) (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLuint buffer, GLuint offset); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTFVATIPROC) (GLuint index, GLenum pname, GLfloat *params); +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBARRAYOBJECTIVATIPROC) (GLuint index, GLenum pname, GLint *params); +#endif + +#ifndef GL_OES_read_format +#define GL_OES_read_format 1 +#endif + +#ifndef GL_EXT_depth_bounds_test +#define GL_EXT_depth_bounds_test 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDepthBoundsEXT (GLclampd, GLclampd); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLDEPTHBOUNDSEXTPROC) (GLclampd zmin, GLclampd zmax); +#endif + +#ifndef GL_EXT_texture_mirror_clamp +#define GL_EXT_texture_mirror_clamp 1 +#endif + +#ifndef GL_EXT_blend_equation_separate +#define GL_EXT_blend_equation_separate 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glBlendEquationSeparateEXT (GLenum, GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLBLENDEQUATIONSEPARATEEXTPROC) (GLenum modeRGB, GLenum modeAlpha); +#endif + +#ifndef GL_MESA_pack_invert +#define GL_MESA_pack_invert 1 +#endif + +#ifndef GL_MESA_ycbcr_texture +#define GL_MESA_ycbcr_texture 1 +#endif + +#ifndef GL_EXT_pixel_buffer_object +#define GL_EXT_pixel_buffer_object 1 +#endif + +#ifndef GL_NV_fragment_program_option +#define GL_NV_fragment_program_option 1 +#endif + +#ifndef GL_NV_fragment_program2 +#define GL_NV_fragment_program2 1 +#endif + +#ifndef GL_NV_vertex_program2_option +#define GL_NV_vertex_program2_option 1 +#endif + +#ifndef GL_NV_vertex_program3 +#define GL_NV_vertex_program3 1 +#endif + +#ifndef GL_EXT_framebuffer_object +#define GL_EXT_framebuffer_object 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI GLboolean APIENTRY glIsRenderbufferEXT (GLuint); +GLAPI void APIENTRY glBindRenderbufferEXT (GLenum, GLuint); +GLAPI void APIENTRY glDeleteRenderbuffersEXT (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenRenderbuffersEXT (GLsizei, GLuint *); +GLAPI void APIENTRY glRenderbufferStorageEXT (GLenum, GLenum, GLsizei, GLsizei); +GLAPI void APIENTRY glGetRenderbufferParameterivEXT (GLenum, GLenum, GLint *); +GLAPI GLboolean APIENTRY glIsFramebufferEXT (GLuint); +GLAPI void APIENTRY glBindFramebufferEXT (GLenum, GLuint); +GLAPI void APIENTRY glDeleteFramebuffersEXT (GLsizei, const GLuint *); +GLAPI void APIENTRY glGenFramebuffersEXT (GLsizei, GLuint *); +GLAPI GLenum APIENTRY glCheckFramebufferStatusEXT (GLenum); +GLAPI void APIENTRY glFramebufferTexture1DEXT (GLenum, GLenum, GLenum, GLuint, GLint); +GLAPI void APIENTRY glFramebufferTexture2DEXT (GLenum, GLenum, GLenum, GLuint, GLint); +GLAPI void APIENTRY glFramebufferTexture3DEXT (GLenum, GLenum, GLenum, GLuint, GLint, GLint); +GLAPI void APIENTRY glFramebufferRenderbufferEXT (GLenum, GLenum, GLenum, GLuint); +GLAPI void APIENTRY glGetFramebufferAttachmentParameterivEXT (GLenum, GLenum, GLenum, GLint *); +GLAPI void APIENTRY glGenerateMipmapEXT (GLenum); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef GLboolean (APIENTRYP PFNGLISRENDERBUFFEREXTPROC) (GLuint renderbuffer); +typedef void (APIENTRYP PFNGLBINDRENDERBUFFEREXTPROC) (GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLDELETERENDERBUFFERSEXTPROC) (GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLGENRENDERBUFFERSEXTPROC) (GLsizei n, GLuint *renderbuffers); +typedef void (APIENTRYP PFNGLRENDERBUFFERSTORAGEEXTPROC) (GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +typedef void (APIENTRYP PFNGLGETRENDERBUFFERPARAMETERIVEXTPROC) (GLenum target, GLenum pname, GLint *params); +typedef GLboolean (APIENTRYP PFNGLISFRAMEBUFFEREXTPROC) (GLuint framebuffer); +typedef void (APIENTRYP PFNGLBINDFRAMEBUFFEREXTPROC) (GLenum target, GLuint framebuffer); +typedef void (APIENTRYP PFNGLDELETEFRAMEBUFFERSEXTPROC) (GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP PFNGLGENFRAMEBUFFERSEXTPROC) (GLsizei n, GLuint *framebuffers); +typedef GLenum (APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSEXTPROC) (GLenum target); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE1DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP PFNGLFRAMEBUFFERTEXTURE3DEXTPROC) (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); +typedef void (APIENTRYP PFNGLFRAMEBUFFERRENDERBUFFEREXTPROC) (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVEXTPROC) (GLenum target, GLenum attachment, GLenum pname, GLint *params); +typedef void (APIENTRYP PFNGLGENERATEMIPMAPEXTPROC) (GLenum target); +#endif + +#ifndef GL_GREMEDY_string_marker +#define GL_GREMEDY_string_marker 1 +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glStringMarkerGREMEDY (GLsizei, const GLvoid *); +#endif /* GL_GLEXT_PROTOTYPES */ +typedef void (APIENTRYP PFNGLSTRINGMARKERGREMEDYPROC) (GLsizei len, const GLvoid *string); +#endif + + +#ifdef __cplusplus +} +#endif + +#endif +#endif /* NO_SDL_GLEXT */ +/*@}*/ diff --git a/code/SDL12/include/SDL_platform.h b/code/SDL12/include/SDL_platform.h new file mode 100644 index 0000000..11d8673 --- /dev/null +++ b/code/SDL12/include/SDL_platform.h @@ -0,0 +1,110 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_platform.h + * Try to get a standard set of platform defines + */ + +#ifndef _SDL_platform_h +#define _SDL_platform_h + +#if defined(_AIX) +#undef __AIX__ +#define __AIX__ 1 +#endif +#if defined(__BEOS__) +#undef __BEOS__ +#define __BEOS__ 1 +#endif +#if defined(__HAIKU__) +#undef __HAIKU__ +#define __HAIKU__ 1 +#endif +#if defined(bsdi) || defined(__bsdi) || defined(__bsdi__) +#undef __BSDI__ +#define __BSDI__ 1 +#endif +#if defined(_arch_dreamcast) +#undef __DREAMCAST__ +#define __DREAMCAST__ 1 +#endif +#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) +#undef __FREEBSD__ +#define __FREEBSD__ 1 +#endif +#if defined(__HAIKU__) +#undef __HAIKU__ +#define __HAIKU__ 1 +#endif +#if defined(hpux) || defined(__hpux) || defined(__hpux__) +#undef __HPUX__ +#define __HPUX__ 1 +#endif +#if defined(sgi) || defined(__sgi) || defined(__sgi__) || defined(_SGI_SOURCE) +#undef __IRIX__ +#define __IRIX__ 1 +#endif +#if defined(linux) || defined(__linux) || defined(__linux__) +#undef __LINUX__ +#define __LINUX__ 1 +#endif +#if defined(__APPLE__) +#undef __MACOSX__ +#define __MACOSX__ 1 +#elif defined(macintosh) +#undef __MACOS__ +#define __MACOS__ 1 +#endif +#if defined(__NetBSD__) +#undef __NETBSD__ +#define __NETBSD__ 1 +#endif +#if defined(__OpenBSD__) +#undef __OPENBSD__ +#define __OPENBSD__ 1 +#endif +#if defined(__OS2__) +#undef __OS2__ +#define __OS2__ 1 +#endif +#if defined(osf) || defined(__osf) || defined(__osf__) || defined(_OSF_SOURCE) +#undef __OSF__ +#define __OSF__ 1 +#endif +#if defined(__QNXNTO__) +#undef __QNXNTO__ +#define __QNXNTO__ 1 +#endif +#if defined(riscos) || defined(__riscos) || defined(__riscos__) +#undef __RISCOS__ +#define __RISCOS__ 1 +#endif +#if defined(__SVR4) +#undef __SOLARIS__ +#define __SOLARIS__ 1 +#endif +#if defined(WIN32) || defined(_WIN32) +#undef __WIN32__ +#define __WIN32__ 1 +#endif + +#endif /* _SDL_platform_h */ diff --git a/code/SDL12/include/SDL_quit.h b/code/SDL12/include/SDL_quit.h new file mode 100644 index 0000000..6d82e7e --- /dev/null +++ b/code/SDL12/include/SDL_quit.h @@ -0,0 +1,55 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_quit.h + * Include file for SDL quit event handling + */ + +#ifndef _SDL_quit_h +#define _SDL_quit_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +/** @file SDL_quit.h + * An SDL_QUITEVENT is generated when the user tries to close the application + * window. If it is ignored or filtered out, the window will remain open. + * If it is not ignored or filtered, it is queued normally and the window + * is allowed to close. When the window is closed, screen updates will + * complete, but have no effect. + * + * SDL_Init() installs signal handlers for SIGINT (keyboard interrupt) + * and SIGTERM (system termination request), if handlers do not already + * exist, that generate SDL_QUITEVENT events as well. There is no way + * to determine the cause of an SDL_QUITEVENT, but setting a signal + * handler in your application will override the default generation of + * quit events for that signal. + */ + +/** @file SDL_quit.h + * There are no functions directly affecting the quit event + */ + +#define SDL_QuitRequested() \ + (SDL_PumpEvents(), SDL_PeepEvents(NULL,0,SDL_PEEKEVENT,SDL_QUITMASK)) + +#endif /* _SDL_quit_h */ diff --git a/code/SDL12/include/SDL_rwops.h b/code/SDL12/include/SDL_rwops.h new file mode 100644 index 0000000..a450119 --- /dev/null +++ b/code/SDL12/include/SDL_rwops.h @@ -0,0 +1,155 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_rwops.h + * This file provides a general interface for SDL to read and write + * data sources. It can easily be extended to files, memory, etc. + */ + +#ifndef _SDL_rwops_h +#define _SDL_rwops_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** This is the read/write operation structure -- very basic */ + +typedef struct SDL_RWops { + /** Seek to 'offset' relative to whence, one of stdio's whence values: + * SEEK_SET, SEEK_CUR, SEEK_END + * Returns the final offset in the data source. + */ + int (SDLCALL *seek)(struct SDL_RWops *context, int offset, int whence); + + /** Read up to 'maxnum' objects each of size 'size' from the data + * source to the area pointed at by 'ptr'. + * Returns the number of objects read, or -1 if the read failed. + */ + int (SDLCALL *read)(struct SDL_RWops *context, void *ptr, int size, int maxnum); + + /** Write exactly 'num' objects each of size 'objsize' from the area + * pointed at by 'ptr' to data source. + * Returns 'num', or -1 if the write failed. + */ + int (SDLCALL *write)(struct SDL_RWops *context, const void *ptr, int size, int num); + + /** Close and free an allocated SDL_FSops structure */ + int (SDLCALL *close)(struct SDL_RWops *context); + + Uint32 type; + union { +#if defined(__WIN32__) && !defined(__SYMBIAN32__) + struct { + int append; + void *h; + struct { + void *data; + int size; + int left; + } buffer; + } win32io; +#endif +#ifdef HAVE_STDIO_H + struct { + int autoclose; + FILE *fp; + } stdio; +#endif + struct { + Uint8 *base; + Uint8 *here; + Uint8 *stop; + } mem; + struct { + void *data1; + } unknown; + } hidden; + +} SDL_RWops; + + +/** @name Functions to create SDL_RWops structures from various data sources */ +/*@{*/ + +extern DECLSPEC SDL_RWops * SDLCALL SDL_RWFromFile(const char *file, const char *mode); + +#ifdef HAVE_STDIO_H +extern DECLSPEC SDL_RWops * SDLCALL SDL_RWFromFP(FILE *fp, int autoclose); +#endif + +extern DECLSPEC SDL_RWops * SDLCALL SDL_RWFromMem(void *mem, int size); +extern DECLSPEC SDL_RWops * SDLCALL SDL_RWFromConstMem(const void *mem, int size); + +extern DECLSPEC SDL_RWops * SDLCALL SDL_AllocRW(void); +extern DECLSPEC void SDLCALL SDL_FreeRW(SDL_RWops *area); + +/*@}*/ + +/** @name Seek Reference Points */ +/*@{*/ +#define RW_SEEK_SET 0 /**< Seek from the beginning of data */ +#define RW_SEEK_CUR 1 /**< Seek relative to current read point */ +#define RW_SEEK_END 2 /**< Seek relative to the end of data */ +/*@}*/ + +/** @name Macros to easily read and write from an SDL_RWops structure */ +/*@{*/ +#define SDL_RWseek(ctx, offset, whence) (ctx)->seek(ctx, offset, whence) +#define SDL_RWtell(ctx) (ctx)->seek(ctx, 0, RW_SEEK_CUR) +#define SDL_RWread(ctx, ptr, size, n) (ctx)->read(ctx, ptr, size, n) +#define SDL_RWwrite(ctx, ptr, size, n) (ctx)->write(ctx, ptr, size, n) +#define SDL_RWclose(ctx) (ctx)->close(ctx) +/*@}*/ + +/** @name Read an item of the specified endianness and return in native format */ +/*@{*/ +extern DECLSPEC Uint16 SDLCALL SDL_ReadLE16(SDL_RWops *src); +extern DECLSPEC Uint16 SDLCALL SDL_ReadBE16(SDL_RWops *src); +extern DECLSPEC Uint32 SDLCALL SDL_ReadLE32(SDL_RWops *src); +extern DECLSPEC Uint32 SDLCALL SDL_ReadBE32(SDL_RWops *src); +extern DECLSPEC Uint64 SDLCALL SDL_ReadLE64(SDL_RWops *src); +extern DECLSPEC Uint64 SDLCALL SDL_ReadBE64(SDL_RWops *src); +/*@}*/ + +/** @name Write an item of native format to the specified endianness */ +/*@{*/ +extern DECLSPEC int SDLCALL SDL_WriteLE16(SDL_RWops *dst, Uint16 value); +extern DECLSPEC int SDLCALL SDL_WriteBE16(SDL_RWops *dst, Uint16 value); +extern DECLSPEC int SDLCALL SDL_WriteLE32(SDL_RWops *dst, Uint32 value); +extern DECLSPEC int SDLCALL SDL_WriteBE32(SDL_RWops *dst, Uint32 value); +extern DECLSPEC int SDLCALL SDL_WriteLE64(SDL_RWops *dst, Uint64 value); +extern DECLSPEC int SDLCALL SDL_WriteBE64(SDL_RWops *dst, Uint64 value); +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_rwops_h */ diff --git a/code/SDL12/include/SDL_stdinc.h b/code/SDL12/include/SDL_stdinc.h new file mode 100644 index 0000000..e1f85fb --- /dev/null +++ b/code/SDL12/include/SDL_stdinc.h @@ -0,0 +1,620 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_stdinc.h + * This is a general header that includes C language support + */ + +#ifndef _SDL_stdinc_h +#define _SDL_stdinc_h + +#include "SDL_config.h" + + +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_STDIO_H +#include +#endif +#if defined(STDC_HEADERS) +# include +# include +# include +#else +# if defined(HAVE_STDLIB_H) +# include +# elif defined(HAVE_MALLOC_H) +# include +# endif +# if defined(HAVE_STDDEF_H) +# include +# endif +# if defined(HAVE_STDARG_H) +# include +# endif +#endif +#ifdef HAVE_STRING_H +# if !defined(STDC_HEADERS) && defined(HAVE_MEMORY_H) +# include +# endif +# include +#endif +#ifdef HAVE_STRINGS_H +# include +#endif +#if defined(HAVE_INTTYPES_H) +# include +#elif defined(HAVE_STDINT_H) +# include +#endif +#ifdef HAVE_CTYPE_H +# include +#endif +#if defined(HAVE_ICONV) && defined(HAVE_ICONV_H) +# include +#endif + +/** The number of elements in an array */ +#define SDL_arraysize(array) (sizeof(array)/sizeof(array[0])) +#define SDL_TABLESIZE(table) SDL_arraysize(table) + +/* Use proper C++ casts when compiled as C++ to be compatible with the option + -Wold-style-cast of GCC (and -Werror=old-style-cast in GCC 4.2 and above. */ +#ifdef __cplusplus +#define SDL_reinterpret_cast(type, expression) reinterpret_cast(expression) +#define SDL_static_cast(type, expression) static_cast(expression) +#else +#define SDL_reinterpret_cast(type, expression) ((type)(expression)) +#define SDL_static_cast(type, expression) ((type)(expression)) +#endif + +/** @name Basic data types */ +/*@{*/ +typedef enum { + SDL_FALSE = 0, + SDL_TRUE = 1 +} SDL_bool; + +typedef int8_t Sint8; +typedef uint8_t Uint8; +typedef int16_t Sint16; +typedef uint16_t Uint16; +typedef int32_t Sint32; +typedef uint32_t Uint32; + +#ifdef SDL_HAS_64BIT_TYPE +typedef int64_t Sint64; +#ifndef SYMBIAN32_GCCE +typedef uint64_t Uint64; +#endif +#else +/* This is really just a hack to prevent the compiler from complaining */ +typedef struct { + Uint32 hi; + Uint32 lo; +} Uint64, Sint64; +#endif + +/*@}*/ + +/** @name Make sure the types really have the right sizes */ +/*@{*/ +#define SDL_COMPILE_TIME_ASSERT(name, x) \ + typedef int SDL_dummy_ ## name[(x) * 2 - 1] + +SDL_COMPILE_TIME_ASSERT(uint8, sizeof(Uint8) == 1); +SDL_COMPILE_TIME_ASSERT(sint8, sizeof(Sint8) == 1); +SDL_COMPILE_TIME_ASSERT(uint16, sizeof(Uint16) == 2); +SDL_COMPILE_TIME_ASSERT(sint16, sizeof(Sint16) == 2); +SDL_COMPILE_TIME_ASSERT(uint32, sizeof(Uint32) == 4); +SDL_COMPILE_TIME_ASSERT(sint32, sizeof(Sint32) == 4); +SDL_COMPILE_TIME_ASSERT(uint64, sizeof(Uint64) == 8); +SDL_COMPILE_TIME_ASSERT(sint64, sizeof(Sint64) == 8); +/*@}*/ + +/** @name Enum Size Check + * Check to make sure enums are the size of ints, for structure packing. + * For both Watcom C/C++ and Borland C/C++ the compiler option that makes + * enums having the size of an int must be enabled. + * This is "-b" for Borland C/C++ and "-ei" for Watcom C/C++ (v11). + */ +/* Enable enums always int in CodeWarrior (for MPW use "-enum int") */ +#ifdef __MWERKS__ +#pragma enumsalwaysint on +#endif + +typedef enum { + DUMMY_ENUM_VALUE +} SDL_DUMMY_ENUM; + +#ifndef __NDS__ +SDL_COMPILE_TIME_ASSERT(enum, sizeof(SDL_DUMMY_ENUM) == sizeof(int)); +#endif +/*@}*/ + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef HAVE_MALLOC +#define SDL_malloc malloc +#else +extern DECLSPEC void * SDLCALL SDL_malloc(size_t size); +#endif + +#ifdef HAVE_CALLOC +#define SDL_calloc calloc +#else +extern DECLSPEC void * SDLCALL SDL_calloc(size_t nmemb, size_t size); +#endif + +#ifdef HAVE_REALLOC +#define SDL_realloc realloc +#else +extern DECLSPEC void * SDLCALL SDL_realloc(void *mem, size_t size); +#endif + +#ifdef HAVE_FREE +#define SDL_free free +#else +extern DECLSPEC void SDLCALL SDL_free(void *mem); +#endif + +#if defined(HAVE_ALLOCA) && !defined(alloca) +# if defined(HAVE_ALLOCA_H) +# include +# elif defined(__GNUC__) +# define alloca __builtin_alloca +# elif defined(_MSC_VER) +# include +# define alloca _alloca +# elif defined(__WATCOMC__) +# include +# elif defined(__BORLANDC__) +# include +# elif defined(__DMC__) +# include +# elif defined(__AIX__) + #pragma alloca +# elif defined(__MRC__) + void *alloca (unsigned); +# else + char *alloca (); +# endif +#endif +#ifdef HAVE_ALLOCA +#define SDL_stack_alloc(type, count) (type*)alloca(sizeof(type)*(count)) +#define SDL_stack_free(data) +#else +#define SDL_stack_alloc(type, count) (type*)SDL_malloc(sizeof(type)*(count)) +#define SDL_stack_free(data) SDL_free(data) +#endif + +#ifdef HAVE_GETENV +#define SDL_getenv getenv +#else +extern DECLSPEC char * SDLCALL SDL_getenv(const char *name); +#endif + +#ifdef HAVE_PUTENV +#define SDL_putenv putenv +#else +extern DECLSPEC int SDLCALL SDL_putenv(const char *variable); +#endif + +#ifdef HAVE_QSORT +#define SDL_qsort qsort +#else +extern DECLSPEC void SDLCALL SDL_qsort(void *base, size_t nmemb, size_t size, + int (*compare)(const void *, const void *)); +#endif + +#ifdef HAVE_ABS +#define SDL_abs abs +#else +#define SDL_abs(X) ((X) < 0 ? -(X) : (X)) +#endif + +#define SDL_min(x, y) (((x) < (y)) ? (x) : (y)) +#define SDL_max(x, y) (((x) > (y)) ? (x) : (y)) + +#ifdef HAVE_CTYPE_H +#define SDL_isdigit(X) isdigit(X) +#define SDL_isspace(X) isspace(X) +#define SDL_toupper(X) toupper(X) +#define SDL_tolower(X) tolower(X) +#else +#define SDL_isdigit(X) (((X) >= '0') && ((X) <= '9')) +#define SDL_isspace(X) (((X) == ' ') || ((X) == '\t') || ((X) == '\r') || ((X) == '\n')) +#define SDL_toupper(X) (((X) >= 'a') && ((X) <= 'z') ? ('A'+((X)-'a')) : (X)) +#define SDL_tolower(X) (((X) >= 'A') && ((X) <= 'Z') ? ('a'+((X)-'A')) : (X)) +#endif + +#ifdef HAVE_MEMSET +#define SDL_memset memset +#else +extern DECLSPEC void * SDLCALL SDL_memset(void *dst, int c, size_t len); +#endif + +#if defined(__GNUC__) && defined(i386) +#define SDL_memset4(dst, val, len) \ +do { \ + int u0, u1, u2; \ + __asm__ __volatile__ ( \ + "cld\n\t" \ + "rep ; stosl\n\t" \ + : "=&D" (u0), "=&a" (u1), "=&c" (u2) \ + : "0" (dst), "1" (val), "2" (SDL_static_cast(Uint32, len)) \ + : "memory" ); \ +} while(0) +#endif +#ifndef SDL_memset4 +#define SDL_memset4(dst, val, len) \ +do { \ + unsigned _count = (len); \ + unsigned _n = (_count + 3) / 4; \ + Uint32 *_p = SDL_static_cast(Uint32 *, dst); \ + Uint32 _val = (val); \ + if (len == 0) break; \ + switch (_count % 4) { \ + case 0: do { *_p++ = _val; \ + case 3: *_p++ = _val; \ + case 2: *_p++ = _val; \ + case 1: *_p++ = _val; \ + } while ( --_n ); \ + } \ +} while(0) +#endif + +/* We can count on memcpy existing on Mac OS X and being well-tuned. */ +#if defined(__MACH__) && defined(__APPLE__) +#define SDL_memcpy(dst, src, len) memcpy(dst, src, len) +#elif defined(__GNUC__) && defined(i386) +#define SDL_memcpy(dst, src, len) \ +do { \ + int u0, u1, u2; \ + __asm__ __volatile__ ( \ + "cld\n\t" \ + "rep ; movsl\n\t" \ + "testb $2,%b4\n\t" \ + "je 1f\n\t" \ + "movsw\n" \ + "1:\ttestb $1,%b4\n\t" \ + "je 2f\n\t" \ + "movsb\n" \ + "2:" \ + : "=&c" (u0), "=&D" (u1), "=&S" (u2) \ + : "0" (SDL_static_cast(unsigned, len)/4), "q" (len), "1" (dst),"2" (src) \ + : "memory" ); \ +} while(0) +#endif +#ifndef SDL_memcpy +#ifdef HAVE_MEMCPY +#define SDL_memcpy memcpy +#elif defined(HAVE_BCOPY) +#define SDL_memcpy(d, s, n) bcopy((s), (d), (n)) +#else +extern DECLSPEC void * SDLCALL SDL_memcpy(void *dst, const void *src, size_t len); +#endif +#endif + +/* We can count on memcpy existing on Mac OS X and being well-tuned. */ +#if defined(__MACH__) && defined(__APPLE__) +#define SDL_memcpy4(dst, src, len) memcpy(dst, src, (len)*4) +#elif defined(__GNUC__) && defined(i386) +#define SDL_memcpy4(dst, src, len) \ +do { \ + int ecx, edi, esi; \ + __asm__ __volatile__ ( \ + "cld\n\t" \ + "rep ; movsl" \ + : "=&c" (ecx), "=&D" (edi), "=&S" (esi) \ + : "0" (SDL_static_cast(unsigned, len)), "1" (dst), "2" (src) \ + : "memory" ); \ +} while(0) +#endif +#ifndef SDL_memcpy4 +#define SDL_memcpy4(dst, src, len) SDL_memcpy(dst, src, (len) << 2) +#endif + +#if defined(__GNUC__) && defined(i386) +#define SDL_revcpy(dst, src, len) \ +do { \ + int u0, u1, u2; \ + char *dstp = SDL_static_cast(char *, dst); \ + char *srcp = SDL_static_cast(char *, src); \ + int n = (len); \ + if ( n >= 4 ) { \ + __asm__ __volatile__ ( \ + "std\n\t" \ + "rep ; movsl\n\t" \ + "cld\n\t" \ + : "=&c" (u0), "=&D" (u1), "=&S" (u2) \ + : "0" (n >> 2), \ + "1" (dstp+(n-4)), "2" (srcp+(n-4)) \ + : "memory" ); \ + } \ + switch (n & 3) { \ + case 3: dstp[2] = srcp[2]; \ + case 2: dstp[1] = srcp[1]; \ + case 1: dstp[0] = srcp[0]; \ + break; \ + default: \ + break; \ + } \ +} while(0) +#endif +#ifndef SDL_revcpy +extern DECLSPEC void * SDLCALL SDL_revcpy(void *dst, const void *src, size_t len); +#endif + +#ifdef HAVE_MEMMOVE +#define SDL_memmove memmove +#elif defined(HAVE_BCOPY) +#define SDL_memmove(d, s, n) bcopy((s), (d), (n)) +#else +#define SDL_memmove(dst, src, len) \ +do { \ + if ( dst < src ) { \ + SDL_memcpy(dst, src, len); \ + } else { \ + SDL_revcpy(dst, src, len); \ + } \ +} while(0) +#endif + +#ifdef HAVE_MEMCMP +#define SDL_memcmp memcmp +#else +extern DECLSPEC int SDLCALL SDL_memcmp(const void *s1, const void *s2, size_t len); +#endif + +#ifdef HAVE_STRLEN +#define SDL_strlen strlen +#else +extern DECLSPEC size_t SDLCALL SDL_strlen(const char *string); +#endif + +#ifdef HAVE_STRLCPY +#define SDL_strlcpy strlcpy +#else +extern DECLSPEC size_t SDLCALL SDL_strlcpy(char *dst, const char *src, size_t maxlen); +#endif + +#ifdef HAVE_STRLCAT +#define SDL_strlcat strlcat +#else +extern DECLSPEC size_t SDLCALL SDL_strlcat(char *dst, const char *src, size_t maxlen); +#endif + +#ifdef HAVE_STRDUP +#define SDL_strdup strdup +#else +extern DECLSPEC char * SDLCALL SDL_strdup(const char *string); +#endif + +#ifdef HAVE__STRREV +#define SDL_strrev _strrev +#else +extern DECLSPEC char * SDLCALL SDL_strrev(char *string); +#endif + +#ifdef HAVE__STRUPR +#define SDL_strupr _strupr +#else +extern DECLSPEC char * SDLCALL SDL_strupr(char *string); +#endif + +#ifdef HAVE__STRLWR +#define SDL_strlwr _strlwr +#else +extern DECLSPEC char * SDLCALL SDL_strlwr(char *string); +#endif + +#ifdef HAVE_STRCHR +#define SDL_strchr strchr +#elif defined(HAVE_INDEX) +#define SDL_strchr index +#else +extern DECLSPEC char * SDLCALL SDL_strchr(const char *string, int c); +#endif + +#ifdef HAVE_STRRCHR +#define SDL_strrchr strrchr +#elif defined(HAVE_RINDEX) +#define SDL_strrchr rindex +#else +extern DECLSPEC char * SDLCALL SDL_strrchr(const char *string, int c); +#endif + +#ifdef HAVE_STRSTR +#define SDL_strstr strstr +#else +extern DECLSPEC char * SDLCALL SDL_strstr(const char *haystack, const char *needle); +#endif + +#ifdef HAVE_ITOA +#define SDL_itoa itoa +#else +#define SDL_itoa(value, string, radix) SDL_ltoa((long)value, string, radix) +#endif + +#ifdef HAVE__LTOA +#define SDL_ltoa _ltoa +#else +extern DECLSPEC char * SDLCALL SDL_ltoa(long value, char *string, int radix); +#endif + +#ifdef HAVE__UITOA +#define SDL_uitoa _uitoa +#else +#define SDL_uitoa(value, string, radix) SDL_ultoa((long)value, string, radix) +#endif + +#ifdef HAVE__ULTOA +#define SDL_ultoa _ultoa +#else +extern DECLSPEC char * SDLCALL SDL_ultoa(unsigned long value, char *string, int radix); +#endif + +#ifdef HAVE_STRTOL +#define SDL_strtol strtol +#else +extern DECLSPEC long SDLCALL SDL_strtol(const char *string, char **endp, int base); +#endif + +#ifdef HAVE_STRTOUL +#define SDL_strtoul strtoul +#else +extern DECLSPEC unsigned long SDLCALL SDL_strtoul(const char *string, char **endp, int base); +#endif + +#ifdef SDL_HAS_64BIT_TYPE + +#ifdef HAVE__I64TOA +#define SDL_lltoa _i64toa +#else +extern DECLSPEC char* SDLCALL SDL_lltoa(Sint64 value, char *string, int radix); +#endif + +#ifdef HAVE__UI64TOA +#define SDL_ulltoa _ui64toa +#else +extern DECLSPEC char* SDLCALL SDL_ulltoa(Uint64 value, char *string, int radix); +#endif + +#ifdef HAVE_STRTOLL +#define SDL_strtoll strtoll +#else +extern DECLSPEC Sint64 SDLCALL SDL_strtoll(const char *string, char **endp, int base); +#endif + +#ifdef HAVE_STRTOULL +#define SDL_strtoull strtoull +#else +extern DECLSPEC Uint64 SDLCALL SDL_strtoull(const char *string, char **endp, int base); +#endif + +#endif /* SDL_HAS_64BIT_TYPE */ + +#ifdef HAVE_STRTOD +#define SDL_strtod strtod +#else +extern DECLSPEC double SDLCALL SDL_strtod(const char *string, char **endp); +#endif + +#ifdef HAVE_ATOI +#define SDL_atoi atoi +#else +#define SDL_atoi(X) SDL_strtol(X, NULL, 0) +#endif + +#ifdef HAVE_ATOF +#define SDL_atof atof +#else +#define SDL_atof(X) SDL_strtod(X, NULL) +#endif + +#ifdef HAVE_STRCMP +#define SDL_strcmp strcmp +#else +extern DECLSPEC int SDLCALL SDL_strcmp(const char *str1, const char *str2); +#endif + +#ifdef HAVE_STRNCMP +#define SDL_strncmp strncmp +#else +extern DECLSPEC int SDLCALL SDL_strncmp(const char *str1, const char *str2, size_t maxlen); +#endif + +#ifdef HAVE_STRCASECMP +#define SDL_strcasecmp strcasecmp +#elif defined(HAVE__STRICMP) +#define SDL_strcasecmp _stricmp +#else +extern DECLSPEC int SDLCALL SDL_strcasecmp(const char *str1, const char *str2); +#endif + +#ifdef HAVE_STRNCASECMP +#define SDL_strncasecmp strncasecmp +#elif defined(HAVE__STRNICMP) +#define SDL_strncasecmp _strnicmp +#else +extern DECLSPEC int SDLCALL SDL_strncasecmp(const char *str1, const char *str2, size_t maxlen); +#endif + +#ifdef HAVE_SSCANF +#define SDL_sscanf sscanf +#else +extern DECLSPEC int SDLCALL SDL_sscanf(const char *text, const char *fmt, ...); +#endif + +#ifdef HAVE_SNPRINTF +#define SDL_snprintf snprintf +#else +extern DECLSPEC int SDLCALL SDL_snprintf(char *text, size_t maxlen, const char *fmt, ...); +#endif + +#ifdef HAVE_VSNPRINTF +#define SDL_vsnprintf vsnprintf +#else +extern DECLSPEC int SDLCALL SDL_vsnprintf(char *text, size_t maxlen, const char *fmt, va_list ap); +#endif + +/** @name SDL_ICONV Error Codes + * The SDL implementation of iconv() returns these error codes + */ +/*@{*/ +#define SDL_ICONV_ERROR (size_t)-1 +#define SDL_ICONV_E2BIG (size_t)-2 +#define SDL_ICONV_EILSEQ (size_t)-3 +#define SDL_ICONV_EINVAL (size_t)-4 +/*@}*/ + +#if defined(HAVE_ICONV) && defined(HAVE_ICONV_H) +#define SDL_iconv_t iconv_t +#define SDL_iconv_open iconv_open +#define SDL_iconv_close iconv_close +#else +typedef struct _SDL_iconv_t *SDL_iconv_t; +extern DECLSPEC SDL_iconv_t SDLCALL SDL_iconv_open(const char *tocode, const char *fromcode); +extern DECLSPEC int SDLCALL SDL_iconv_close(SDL_iconv_t cd); +#endif +extern DECLSPEC size_t SDLCALL SDL_iconv(SDL_iconv_t cd, const char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft); +/** This function converts a string between encodings in one pass, returning a + * string that must be freed with SDL_free() or NULL on error. + */ +extern DECLSPEC char * SDLCALL SDL_iconv_string(const char *tocode, const char *fromcode, const char *inbuf, size_t inbytesleft); +#define SDL_iconv_utf8_locale(S) SDL_iconv_string("", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs2(S) (Uint16 *)SDL_iconv_string("UCS-2", "UTF-8", S, SDL_strlen(S)+1) +#define SDL_iconv_utf8_ucs4(S) (Uint32 *)SDL_iconv_string("UCS-4", "UTF-8", S, SDL_strlen(S)+1) + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_stdinc_h */ diff --git a/code/SDL12/include/SDL_syswm.h b/code/SDL12/include/SDL_syswm.h new file mode 100644 index 0000000..716dddc --- /dev/null +++ b/code/SDL12/include/SDL_syswm.h @@ -0,0 +1,225 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_syswm.h + * Include file for SDL custom system window manager hooks + */ + +#ifndef _SDL_syswm_h +#define _SDL_syswm_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_version.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @file SDL_syswm.h + * Your application has access to a special type of event 'SDL_SYSWMEVENT', + * which contains window-manager specific information and arrives whenever + * an unhandled window event occurs. This event is ignored by default, but + * you can enable it with SDL_EventState() + */ +#ifdef SDL_PROTOTYPES_ONLY +struct SDL_SysWMinfo; +typedef struct SDL_SysWMinfo SDL_SysWMinfo; +#else + +/* This is the structure for custom window manager events */ +#if defined(SDL_VIDEO_DRIVER_X11) +#if defined(__APPLE__) && defined(__MACH__) +/* conflicts with Quickdraw.h */ +#define Cursor X11Cursor +#endif + +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) +/* matches the re-define above */ +#undef Cursor +#endif + +/** These are the various supported subsystems under UNIX */ +typedef enum { + SDL_SYSWM_X11 +} SDL_SYSWM_TYPE; + +/** The UNIX custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + SDL_SYSWM_TYPE subsystem; + union { + XEvent xevent; + } event; +}; + +/** The UNIX custom window manager information structure. + * When this structure is returned, it holds information about which + * low level system it is using, and will be one of SDL_SYSWM_TYPE. + */ +typedef struct SDL_SysWMinfo { + SDL_version version; + SDL_SYSWM_TYPE subsystem; + union { + struct { + Display *display; /**< The X11 display */ + Window window; /**< The X11 display window */ + /** These locking functions should be called around + * any X11 functions using the display variable, + * but not the gfxdisplay variable. + * They lock the event thread, so should not be + * called around event functions or from event filters. + */ + /*@{*/ + void (*lock_func)(void); + void (*unlock_func)(void); + /*@}*/ + + /** @name Introduced in SDL 1.0.2 */ + /*@{*/ + Window fswindow; /**< The X11 fullscreen window */ + Window wmwindow; /**< The X11 managed input window */ + /*@}*/ + + /** @name Introduced in SDL 1.2.12 */ + /*@{*/ + Display *gfxdisplay; /**< The X11 display to which rendering is done */ + /*@}*/ + } x11; + } info; +} SDL_SysWMinfo; + +#elif defined(SDL_VIDEO_DRIVER_NANOX) +#include + +/** The generic custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + int data; +}; + +/** The windows custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version ; + GR_WINDOW_ID window ; /* The display window */ +} SDL_SysWMinfo; + +#elif defined(SDL_VIDEO_DRIVER_WINDIB) || defined(SDL_VIDEO_DRIVER_DDRAW) || defined(SDL_VIDEO_DRIVER_GAPI) +#define WIN32_LEAN_AND_MEAN +#include + +/** The windows custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + HWND hwnd; /**< The window for the message */ + UINT msg; /**< The type of message */ + WPARAM wParam; /**< WORD message parameter */ + LPARAM lParam; /**< LONG message parameter */ +}; + +/** The windows custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version; + HWND window; /**< The Win32 display window */ + HGLRC hglrc; /**< The OpenGL context, if any */ +} SDL_SysWMinfo; + +#elif defined(SDL_VIDEO_DRIVER_RISCOS) + +/** RISC OS custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + int eventCode; /**< The window for the message */ + int pollBlock[64]; +}; + +/** The RISC OS custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version; + int wimpVersion; /**< Wimp version running under */ + int taskHandle; /**< The RISC OS task handle */ + int window; /**< The RISC OS display window */ +} SDL_SysWMinfo; + +#elif defined(SDL_VIDEO_DRIVER_PHOTON) +#include +#include + +/** The QNX custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + int data; +}; + +/** The QNX custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version; + int data; +} SDL_SysWMinfo; + +#else + +/** The generic custom event structure */ +struct SDL_SysWMmsg { + SDL_version version; + int data; +}; + +/** The generic custom window manager information structure */ +typedef struct SDL_SysWMinfo { + SDL_version version; + int data; +} SDL_SysWMinfo; + +#endif /* video driver type */ + +#endif /* SDL_PROTOTYPES_ONLY */ + +/* Function prototypes */ +/** + * This function gives you custom hooks into the window manager information. + * It fills the structure pointed to by 'info' with custom information and + * returns 1 if the function is implemented. If it's not implemented, or + * the version member of the 'info' structure is invalid, it returns 0. + * + * You typically use this function like this: + * @code + * SDL_SysWMInfo info; + * SDL_VERSION(&info.version); + * if ( SDL_GetWMInfo(&info) ) { ... } + * @endcode + */ +extern DECLSPEC int SDLCALL SDL_GetWMInfo(SDL_SysWMinfo *info); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_syswm_h */ diff --git a/code/SDL12/include/SDL_thread.h b/code/SDL12/include/SDL_thread.h new file mode 100644 index 0000000..1ca9a1b --- /dev/null +++ b/code/SDL12/include/SDL_thread.h @@ -0,0 +1,120 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_thread_h +#define _SDL_thread_h + +/** @file SDL_thread.h + * Header for the SDL thread management routines + * + * @note These are independent of the other SDL routines. + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +/* Thread synchronization primitives */ +#include "SDL_mutex.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** The SDL thread structure, defined in SDL_thread.c */ +struct SDL_Thread; +typedef struct SDL_Thread SDL_Thread; + +/** Create a thread */ +#if ((defined(__WIN32__) && !defined(HAVE_LIBC)) || defined(__OS2__)) && !defined(__SYMBIAN32__) +/** + * We compile SDL into a DLL on OS/2. This means, that it's the DLL which + * creates a new thread for the calling process with the SDL_CreateThread() + * API. There is a problem with this, that only the RTL of the SDL.DLL will + * be initialized for those threads, and not the RTL of the calling application! + * To solve this, we make a little hack here. + * We'll always use the caller's _beginthread() and _endthread() APIs to + * start a new thread. This way, if it's the SDL.DLL which uses this API, + * then the RTL of SDL.DLL will be used to create the new thread, and if it's + * the application, then the RTL of the application will be used. + * So, in short: + * Always use the _beginthread() and _endthread() of the calling runtime library! + */ +#define SDL_PASSED_BEGINTHREAD_ENDTHREAD +#ifndef _WIN32_WCE +#include /* This has _beginthread() and _endthread() defined! */ +#endif + +#ifdef __OS2__ +typedef int (*pfnSDL_CurrentBeginThread)(void (*func)(void *), void *, unsigned, void *arg); +typedef void (*pfnSDL_CurrentEndThread)(void); +#elif __GNUC__ +typedef unsigned long (__cdecl *pfnSDL_CurrentBeginThread) (void *, unsigned, + unsigned (__stdcall *func)(void *), void *arg, + unsigned, unsigned *threadID); +typedef void (__cdecl *pfnSDL_CurrentEndThread)(unsigned code); +#else +typedef uintptr_t (__cdecl *pfnSDL_CurrentBeginThread) (void *, unsigned, + unsigned (__stdcall *func)(void *), void *arg, + unsigned, unsigned *threadID); +typedef void (__cdecl *pfnSDL_CurrentEndThread)(unsigned code); +#endif + +extern DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(int (SDLCALL *fn)(void *), void *data, pfnSDL_CurrentBeginThread pfnBeginThread, pfnSDL_CurrentEndThread pfnEndThread); + +#ifdef __OS2__ +#define SDL_CreateThread(fn, data) SDL_CreateThread(fn, data, _beginthread, _endthread) +#elif defined(_WIN32_WCE) +#define SDL_CreateThread(fn, data) SDL_CreateThread(fn, data, NULL, NULL) +#else +#define SDL_CreateThread(fn, data) SDL_CreateThread(fn, data, _beginthreadex, _endthreadex) +#endif +#else +extern DECLSPEC SDL_Thread * SDLCALL SDL_CreateThread(int (SDLCALL *fn)(void *), void *data); +#endif + +/** Get the 32-bit thread identifier for the current thread */ +extern DECLSPEC Uint32 SDLCALL SDL_ThreadID(void); + +/** Get the 32-bit thread identifier for the specified thread, + * equivalent to SDL_ThreadID() if the specified thread is NULL. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetThreadID(SDL_Thread *thread); + +/** Wait for a thread to finish. + * The return code for the thread function is placed in the area + * pointed to by 'status', if 'status' is not NULL. + */ +extern DECLSPEC void SDLCALL SDL_WaitThread(SDL_Thread *thread, int *status); + +/** Forcefully kill a thread without worrying about its state */ +extern DECLSPEC void SDLCALL SDL_KillThread(SDL_Thread *thread); + + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_thread_h */ diff --git a/code/SDL12/include/SDL_timer.h b/code/SDL12/include/SDL_timer.h new file mode 100644 index 0000000..d7cd024 --- /dev/null +++ b/code/SDL12/include/SDL_timer.h @@ -0,0 +1,125 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +#ifndef _SDL_timer_h +#define _SDL_timer_h + +/** @file SDL_timer.h + * Header for the SDL time management routines + */ + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** This is the OS scheduler timeslice, in milliseconds */ +#define SDL_TIMESLICE 10 + +/** This is the maximum resolution of the SDL timer on all platforms */ +#define TIMER_RESOLUTION 10 /**< Experimentally determined */ + +/** + * Get the number of milliseconds since the SDL library initialization. + * Note that this value wraps if the program runs for more than ~49 days. + */ +extern DECLSPEC Uint32 SDLCALL SDL_GetTicks(void); + +/** Wait a specified number of milliseconds before returning */ +extern DECLSPEC void SDLCALL SDL_Delay(Uint32 ms); + +/** Function prototype for the timer callback function */ +typedef Uint32 (SDLCALL *SDL_TimerCallback)(Uint32 interval); + +/** + * Set a callback to run after the specified number of milliseconds has + * elapsed. The callback function is passed the current timer interval + * and returns the next timer interval. If the returned value is the + * same as the one passed in, the periodic alarm continues, otherwise a + * new alarm is scheduled. If the callback returns 0, the periodic alarm + * is cancelled. + * + * To cancel a currently running timer, call SDL_SetTimer(0, NULL); + * + * The timer callback function may run in a different thread than your + * main code, and so shouldn't call any functions from within itself. + * + * The maximum resolution of this timer is 10 ms, which means that if + * you request a 16 ms timer, your callback will run approximately 20 ms + * later on an unloaded system. If you wanted to set a flag signaling + * a frame update at 30 frames per second (every 33 ms), you might set a + * timer for 30 ms: + * @code SDL_SetTimer((33/10)*10, flag_update); @endcode + * + * If you use this function, you need to pass SDL_INIT_TIMER to SDL_Init(). + * + * Under UNIX, you should not use raise or use SIGALRM and this function + * in the same program, as it is implemented using setitimer(). You also + * should not use this function in multi-threaded applications as signals + * to multi-threaded apps have undefined behavior in some implementations. + * + * This function returns 0 if successful, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_SetTimer(Uint32 interval, SDL_TimerCallback callback); + +/** @name New timer API + * New timer API, supports multiple timers + * Written by Stephane Peter + */ +/*@{*/ + +/** + * Function prototype for the new timer callback function. + * The callback function is passed the current timer interval and returns + * the next timer interval. If the returned value is the same as the one + * passed in, the periodic alarm continues, otherwise a new alarm is + * scheduled. If the callback returns 0, the periodic alarm is cancelled. + */ +typedef Uint32 (SDLCALL *SDL_NewTimerCallback)(Uint32 interval, void *param); + +/** Definition of the timer ID type */ +typedef struct _SDL_TimerID *SDL_TimerID; + +/** Add a new timer to the pool of timers already running. + * Returns a timer ID, or NULL when an error occurs. + */ +extern DECLSPEC SDL_TimerID SDLCALL SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param); + +/** + * Remove one of the multiple timers knowing its ID. + * Returns a boolean value indicating success. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_RemoveTimer(SDL_TimerID t); + +/*@}*/ + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_timer_h */ diff --git a/code/SDL12/include/SDL_types.h b/code/SDL12/include/SDL_types.h new file mode 100644 index 0000000..cfa3523 --- /dev/null +++ b/code/SDL12/include/SDL_types.h @@ -0,0 +1,28 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_types.h + * @deprecated Use SDL_stdinc.h instead. + */ + +/* DEPRECATED */ +#include "SDL_stdinc.h" diff --git a/code/SDL12/include/SDL_version.h b/code/SDL12/include/SDL_version.h new file mode 100644 index 0000000..fa02c3f --- /dev/null +++ b/code/SDL12/include/SDL_version.h @@ -0,0 +1,91 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_version.h + * This header defines the current SDL version + */ + +#ifndef _SDL_version_h +#define _SDL_version_h + +#include "SDL_stdinc.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @name Version Number + * Printable format: "%d.%d.%d", MAJOR, MINOR, PATCHLEVEL + */ +/*@{*/ +#define SDL_MAJOR_VERSION 1 +#define SDL_MINOR_VERSION 2 +#define SDL_PATCHLEVEL 14 +/*@}*/ + +typedef struct SDL_version { + Uint8 major; + Uint8 minor; + Uint8 patch; +} SDL_version; + +/** + * This macro can be used to fill a version structure with the compile-time + * version of the SDL library. + */ +#define SDL_VERSION(X) \ +{ \ + (X)->major = SDL_MAJOR_VERSION; \ + (X)->minor = SDL_MINOR_VERSION; \ + (X)->patch = SDL_PATCHLEVEL; \ +} + +/** This macro turns the version numbers into a numeric value: + * (1,2,3) -> (1203) + * This assumes that there will never be more than 100 patchlevels + */ +#define SDL_VERSIONNUM(X, Y, Z) \ + ((X)*1000 + (Y)*100 + (Z)) + +/** This is the version number macro for the current SDL version */ +#define SDL_COMPILEDVERSION \ + SDL_VERSIONNUM(SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL) + +/** This macro will evaluate to true if compiled with SDL at least X.Y.Z */ +#define SDL_VERSION_ATLEAST(X, Y, Z) \ + (SDL_COMPILEDVERSION >= SDL_VERSIONNUM(X, Y, Z)) + +/** This function gets the version of the dynamically linked SDL library. + * it should NOT be used to fill a version structure, instead you should + * use the SDL_Version() macro. + */ +extern DECLSPEC const SDL_version * SDLCALL SDL_Linked_Version(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_version_h */ diff --git a/code/SDL12/include/SDL_video.h b/code/SDL12/include/SDL_video.h new file mode 100644 index 0000000..8f7f305 --- /dev/null +++ b/code/SDL12/include/SDL_video.h @@ -0,0 +1,951 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** @file SDL_video.h + * Header file for access to the SDL raw framebuffer window + */ + +#ifndef _SDL_video_h +#define _SDL_video_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" +#include "SDL_rwops.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +extern "C" { +#endif + +/** @name Transparency definitions + * These define alpha as the opacity of a surface + */ +/*@{*/ +#define SDL_ALPHA_OPAQUE 255 +#define SDL_ALPHA_TRANSPARENT 0 +/*@}*/ + +/** @name Useful data types */ +/*@{*/ +typedef struct SDL_Rect { + Sint16 x, y; + Uint16 w, h; +} SDL_Rect; + +typedef struct SDL_Color { + Uint8 r; + Uint8 g; + Uint8 b; + Uint8 unused; +} SDL_Color; +#define SDL_Colour SDL_Color + +typedef struct SDL_Palette { + int ncolors; + SDL_Color *colors; +} SDL_Palette; +/*@}*/ + +/** Everything in the pixel format structure is read-only */ +typedef struct SDL_PixelFormat { + SDL_Palette *palette; + Uint8 BitsPerPixel; + Uint8 BytesPerPixel; + Uint8 Rloss; + Uint8 Gloss; + Uint8 Bloss; + Uint8 Aloss; + Uint8 Rshift; + Uint8 Gshift; + Uint8 Bshift; + Uint8 Ashift; + Uint32 Rmask; + Uint32 Gmask; + Uint32 Bmask; + Uint32 Amask; + + /** RGB color key information */ + Uint32 colorkey; + /** Alpha value information (per-surface alpha) */ + Uint8 alpha; +} SDL_PixelFormat; + +/** This structure should be treated as read-only, except for 'pixels', + * which, if not NULL, contains the raw pixel data for the surface. + */ +typedef struct SDL_Surface { + Uint32 flags; /**< Read-only */ + SDL_PixelFormat *format; /**< Read-only */ + int w, h; /**< Read-only */ + Uint16 pitch; /**< Read-only */ + void *pixels; /**< Read-write */ + int offset; /**< Private */ + + /** Hardware-specific surface info */ + struct private_hwdata *hwdata; + + /** clipping information */ + SDL_Rect clip_rect; /**< Read-only */ + Uint32 unused1; /**< for binary compatibility */ + + /** Allow recursive locks */ + Uint32 locked; /**< Private */ + + /** info for fast blit mapping to other surfaces */ + struct SDL_BlitMap *map; /**< Private */ + + /** format version, bumped at every change to invalidate blit maps */ + unsigned int format_version; /**< Private */ + + /** Reference count -- used when freeing surface */ + int refcount; /**< Read-mostly */ +} SDL_Surface; + +/** @name SDL_Surface Flags + * These are the currently supported flags for the SDL_surface + */ +/*@{*/ + +/** Available for SDL_CreateRGBSurface() or SDL_SetVideoMode() */ +/*@{*/ +#define SDL_SWSURFACE 0x00000000 /**< Surface is in system memory */ +#define SDL_HWSURFACE 0x00000001 /**< Surface is in video memory */ +#define SDL_ASYNCBLIT 0x00000004 /**< Use asynchronous blits if possible */ +/*@}*/ + +/** Available for SDL_SetVideoMode() */ +/*@{*/ +#define SDL_ANYFORMAT 0x10000000 /**< Allow any video depth/pixel-format */ +#define SDL_HWPALETTE 0x20000000 /**< Surface has exclusive palette */ +#define SDL_DOUBLEBUF 0x40000000 /**< Set up double-buffered video mode */ +#define SDL_FULLSCREEN 0x80000000 /**< Surface is a full screen display */ +#define SDL_OPENGL 0x00000002 /**< Create an OpenGL rendering context */ +#define SDL_OPENGLBLIT 0x0000000A /**< Create an OpenGL rendering context and use it for blitting */ +#define SDL_RESIZABLE 0x00000010 /**< This video mode may be resized */ +#define SDL_NOFRAME 0x00000020 /**< No window caption or edge frame */ +/*@}*/ + +/** Used internally (read-only) */ +/*@{*/ +#define SDL_HWACCEL 0x00000100 /**< Blit uses hardware acceleration */ +#define SDL_SRCCOLORKEY 0x00001000 /**< Blit uses a source color key */ +#define SDL_RLEACCELOK 0x00002000 /**< Private flag */ +#define SDL_RLEACCEL 0x00004000 /**< Surface is RLE encoded */ +#define SDL_SRCALPHA 0x00010000 /**< Blit uses source alpha blending */ +#define SDL_PREALLOC 0x01000000 /**< Surface uses preallocated memory */ +/*@}*/ + +/*@}*/ + +/** Evaluates to true if the surface needs to be locked before access */ +#define SDL_MUSTLOCK(surface) \ + (surface->offset || \ + ((surface->flags & (SDL_HWSURFACE|SDL_ASYNCBLIT|SDL_RLEACCEL)) != 0)) + +/** typedef for private surface blitting functions */ +typedef int (*SDL_blit)(struct SDL_Surface *src, SDL_Rect *srcrect, + struct SDL_Surface *dst, SDL_Rect *dstrect); + + +/** Useful for determining the video hardware capabilities */ +typedef struct SDL_VideoInfo { + Uint32 hw_available :1; /**< Flag: Can you create hardware surfaces? */ + Uint32 wm_available :1; /**< Flag: Can you talk to a window manager? */ + Uint32 UnusedBits1 :6; + Uint32 UnusedBits2 :1; + Uint32 blit_hw :1; /**< Flag: Accelerated blits HW --> HW */ + Uint32 blit_hw_CC :1; /**< Flag: Accelerated blits with Colorkey */ + Uint32 blit_hw_A :1; /**< Flag: Accelerated blits with Alpha */ + Uint32 blit_sw :1; /**< Flag: Accelerated blits SW --> HW */ + Uint32 blit_sw_CC :1; /**< Flag: Accelerated blits with Colorkey */ + Uint32 blit_sw_A :1; /**< Flag: Accelerated blits with Alpha */ + Uint32 blit_fill :1; /**< Flag: Accelerated color fill */ + Uint32 UnusedBits3 :16; + Uint32 video_mem; /**< The total amount of video memory (in K) */ + SDL_PixelFormat *vfmt; /**< Value: The format of the video surface */ + int current_w; /**< Value: The current video mode width */ + int current_h; /**< Value: The current video mode height */ +} SDL_VideoInfo; + + +/** @name Overlay Formats + * The most common video overlay formats. + * For an explanation of these pixel formats, see: + * http://www.webartz.com/fourcc/indexyuv.htm + * + * For information on the relationship between color spaces, see: + * http://www.neuro.sfc.keio.ac.jp/~aly/polygon/info/color-space-faq.html + */ +/*@{*/ +#define SDL_YV12_OVERLAY 0x32315659 /**< Planar mode: Y + V + U (3 planes) */ +#define SDL_IYUV_OVERLAY 0x56555949 /**< Planar mode: Y + U + V (3 planes) */ +#define SDL_YUY2_OVERLAY 0x32595559 /**< Packed mode: Y0+U0+Y1+V0 (1 plane) */ +#define SDL_UYVY_OVERLAY 0x59565955 /**< Packed mode: U0+Y0+V0+Y1 (1 plane) */ +#define SDL_YVYU_OVERLAY 0x55595659 /**< Packed mode: Y0+V0+Y1+U0 (1 plane) */ +/*@}*/ + +/** The YUV hardware video overlay */ +typedef struct SDL_Overlay { + Uint32 format; /**< Read-only */ + int w, h; /**< Read-only */ + int planes; /**< Read-only */ + Uint16 *pitches; /**< Read-only */ + Uint8 **pixels; /**< Read-write */ + + /** @name Hardware-specific surface info */ + /*@{*/ + struct private_yuvhwfuncs *hwfuncs; + struct private_yuvhwdata *hwdata; + /*@{*/ + + /** @name Special flags */ + /*@{*/ + Uint32 hw_overlay :1; /**< Flag: This overlay hardware accelerated? */ + Uint32 UnusedBits :31; + /*@}*/ +} SDL_Overlay; + + +/** Public enumeration for setting the OpenGL window attributes. */ +typedef enum { + SDL_GL_RED_SIZE, + SDL_GL_GREEN_SIZE, + SDL_GL_BLUE_SIZE, + SDL_GL_ALPHA_SIZE, + SDL_GL_BUFFER_SIZE, + SDL_GL_DOUBLEBUFFER, + SDL_GL_DEPTH_SIZE, + SDL_GL_STENCIL_SIZE, + SDL_GL_ACCUM_RED_SIZE, + SDL_GL_ACCUM_GREEN_SIZE, + SDL_GL_ACCUM_BLUE_SIZE, + SDL_GL_ACCUM_ALPHA_SIZE, + SDL_GL_STEREO, + SDL_GL_MULTISAMPLEBUFFERS, + SDL_GL_MULTISAMPLESAMPLES, + SDL_GL_ACCELERATED_VISUAL, + SDL_GL_SWAP_CONTROL +} SDL_GLattr; + +/** @name flags for SDL_SetPalette() */ +/*@{*/ +#define SDL_LOGPAL 0x01 +#define SDL_PHYSPAL 0x02 +/*@}*/ + +/* Function prototypes */ + +/** + * @name Video Init and Quit + * These functions are used internally, and should not be used unless you + * have a specific need to specify the video driver you want to use. + * You should normally use SDL_Init() or SDL_InitSubSystem(). + */ +/*@{*/ +/** + * Initializes the video subsystem. Sets up a connection + * to the window manager, etc, and determines the current video mode and + * pixel format, but does not initialize a window or graphics mode. + * Note that event handling is activated by this routine. + * + * If you use both sound and video in your application, you need to call + * SDL_Init() before opening the sound device, otherwise under Win32 DirectX, + * you won't be able to set full-screen display modes. + */ +extern DECLSPEC int SDLCALL SDL_VideoInit(const char *driver_name, Uint32 flags); +extern DECLSPEC void SDLCALL SDL_VideoQuit(void); +/*@}*/ + +/** + * This function fills the given character buffer with the name of the + * video driver, and returns a pointer to it if the video driver has + * been initialized. It returns NULL if no driver has been initialized. + */ +extern DECLSPEC char * SDLCALL SDL_VideoDriverName(char *namebuf, int maxlen); + +/** + * This function returns a pointer to the current display surface. + * If SDL is doing format conversion on the display surface, this + * function returns the publicly visible surface, not the real video + * surface. + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_GetVideoSurface(void); + +/** + * This function returns a read-only pointer to information about the + * video hardware. If this is called before SDL_SetVideoMode(), the 'vfmt' + * member of the returned structure will contain the pixel format of the + * "best" video mode. + */ +extern DECLSPEC const SDL_VideoInfo * SDLCALL SDL_GetVideoInfo(void); + +/** + * Check to see if a particular video mode is supported. + * It returns 0 if the requested mode is not supported under any bit depth, + * or returns the bits-per-pixel of the closest available mode with the + * given width and height. If this bits-per-pixel is different from the + * one used when setting the video mode, SDL_SetVideoMode() will succeed, + * but will emulate the requested bits-per-pixel with a shadow surface. + * + * The arguments to SDL_VideoModeOK() are the same ones you would pass to + * SDL_SetVideoMode() + */ +extern DECLSPEC int SDLCALL SDL_VideoModeOK(int width, int height, int bpp, Uint32 flags); + +/** + * Return a pointer to an array of available screen dimensions for the + * given format and video flags, sorted largest to smallest. Returns + * NULL if there are no dimensions available for a particular format, + * or (SDL_Rect **)-1 if any dimension is okay for the given format. + * + * If 'format' is NULL, the mode list will be for the format given + * by SDL_GetVideoInfo()->vfmt + */ +extern DECLSPEC SDL_Rect ** SDLCALL SDL_ListModes(SDL_PixelFormat *format, Uint32 flags); + +/** + * Set up a video mode with the specified width, height and bits-per-pixel. + * + * If 'bpp' is 0, it is treated as the current display bits per pixel. + * + * If SDL_ANYFORMAT is set in 'flags', the SDL library will try to set the + * requested bits-per-pixel, but will return whatever video pixel format is + * available. The default is to emulate the requested pixel format if it + * is not natively available. + * + * If SDL_HWSURFACE is set in 'flags', the video surface will be placed in + * video memory, if possible, and you may have to call SDL_LockSurface() + * in order to access the raw framebuffer. Otherwise, the video surface + * will be created in system memory. + * + * If SDL_ASYNCBLIT is set in 'flags', SDL will try to perform rectangle + * updates asynchronously, but you must always lock before accessing pixels. + * SDL will wait for updates to complete before returning from the lock. + * + * If SDL_HWPALETTE is set in 'flags', the SDL library will guarantee + * that the colors set by SDL_SetColors() will be the colors you get. + * Otherwise, in 8-bit mode, SDL_SetColors() may not be able to set all + * of the colors exactly the way they are requested, and you should look + * at the video surface structure to determine the actual palette. + * If SDL cannot guarantee that the colors you request can be set, + * i.e. if the colormap is shared, then the video surface may be created + * under emulation in system memory, overriding the SDL_HWSURFACE flag. + * + * If SDL_FULLSCREEN is set in 'flags', the SDL library will try to set + * a fullscreen video mode. The default is to create a windowed mode + * if the current graphics system has a window manager. + * If the SDL library is able to set a fullscreen video mode, this flag + * will be set in the surface that is returned. + * + * If SDL_DOUBLEBUF is set in 'flags', the SDL library will try to set up + * two surfaces in video memory and swap between them when you call + * SDL_Flip(). This is usually slower than the normal single-buffering + * scheme, but prevents "tearing" artifacts caused by modifying video + * memory while the monitor is refreshing. It should only be used by + * applications that redraw the entire screen on every update. + * + * If SDL_RESIZABLE is set in 'flags', the SDL library will allow the + * window manager, if any, to resize the window at runtime. When this + * occurs, SDL will send a SDL_VIDEORESIZE event to you application, + * and you must respond to the event by re-calling SDL_SetVideoMode() + * with the requested size (or another size that suits the application). + * + * If SDL_NOFRAME is set in 'flags', the SDL library will create a window + * without any title bar or frame decoration. Fullscreen video modes have + * this flag set automatically. + * + * This function returns the video framebuffer surface, or NULL if it fails. + * + * If you rely on functionality provided by certain video flags, check the + * flags of the returned surface to make sure that functionality is available. + * SDL will fall back to reduced functionality if the exact flags you wanted + * are not available. + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_SetVideoMode + (int width, int height, int bpp, Uint32 flags); + +/** @name SDL_Update Functions + * These functions should not be called while 'screen' is locked. + */ +/*@{*/ +/** + * Makes sure the given list of rectangles is updated on the given screen. + */ +extern DECLSPEC void SDLCALL SDL_UpdateRects + (SDL_Surface *screen, int numrects, SDL_Rect *rects); +/** + * If 'x', 'y', 'w' and 'h' are all 0, SDL_UpdateRect will update the entire + * screen. + */ +extern DECLSPEC void SDLCALL SDL_UpdateRect + (SDL_Surface *screen, Sint32 x, Sint32 y, Uint32 w, Uint32 h); +/*@}*/ + +/** + * On hardware that supports double-buffering, this function sets up a flip + * and returns. The hardware will wait for vertical retrace, and then swap + * video buffers before the next video surface blit or lock will return. + * On hardware that doesn not support double-buffering, this is equivalent + * to calling SDL_UpdateRect(screen, 0, 0, 0, 0); + * The SDL_DOUBLEBUF flag must have been passed to SDL_SetVideoMode() when + * setting the video mode for this function to perform hardware flipping. + * This function returns 0 if successful, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_Flip(SDL_Surface *screen); + +/** + * Set the gamma correction for each of the color channels. + * The gamma values range (approximately) between 0.1 and 10.0 + * + * If this function isn't supported directly by the hardware, it will + * be emulated using gamma ramps, if available. If successful, this + * function returns 0, otherwise it returns -1. + */ +extern DECLSPEC int SDLCALL SDL_SetGamma(float red, float green, float blue); + +/** + * Set the gamma translation table for the red, green, and blue channels + * of the video hardware. Each table is an array of 256 16-bit quantities, + * representing a mapping between the input and output for that channel. + * The input is the index into the array, and the output is the 16-bit + * gamma value at that index, scaled to the output color precision. + * + * You may pass NULL for any of the channels to leave it unchanged. + * If the call succeeds, it will return 0. If the display driver or + * hardware does not support gamma translation, or otherwise fails, + * this function will return -1. + */ +extern DECLSPEC int SDLCALL SDL_SetGammaRamp(const Uint16 *red, const Uint16 *green, const Uint16 *blue); + +/** + * Retrieve the current values of the gamma translation tables. + * + * You must pass in valid pointers to arrays of 256 16-bit quantities. + * Any of the pointers may be NULL to ignore that channel. + * If the call succeeds, it will return 0. If the display driver or + * hardware does not support gamma translation, or otherwise fails, + * this function will return -1. + */ +extern DECLSPEC int SDLCALL SDL_GetGammaRamp(Uint16 *red, Uint16 *green, Uint16 *blue); + +/** + * Sets a portion of the colormap for the given 8-bit surface. If 'surface' + * is not a palettized surface, this function does nothing, returning 0. + * If all of the colors were set as passed to SDL_SetColors(), it will + * return 1. If not all the color entries were set exactly as given, + * it will return 0, and you should look at the surface palette to + * determine the actual color palette. + * + * When 'surface' is the surface associated with the current display, the + * display colormap will be updated with the requested colors. If + * SDL_HWPALETTE was set in SDL_SetVideoMode() flags, SDL_SetColors() + * will always return 1, and the palette is guaranteed to be set the way + * you desire, even if the window colormap has to be warped or run under + * emulation. + */ +extern DECLSPEC int SDLCALL SDL_SetColors(SDL_Surface *surface, + SDL_Color *colors, int firstcolor, int ncolors); + +/** + * Sets a portion of the colormap for a given 8-bit surface. + * 'flags' is one or both of: + * SDL_LOGPAL -- set logical palette, which controls how blits are mapped + * to/from the surface, + * SDL_PHYSPAL -- set physical palette, which controls how pixels look on + * the screen + * Only screens have physical palettes. Separate change of physical/logical + * palettes is only possible if the screen has SDL_HWPALETTE set. + * + * The return value is 1 if all colours could be set as requested, and 0 + * otherwise. + * + * SDL_SetColors() is equivalent to calling this function with + * flags = (SDL_LOGPAL|SDL_PHYSPAL). + */ +extern DECLSPEC int SDLCALL SDL_SetPalette(SDL_Surface *surface, int flags, + SDL_Color *colors, int firstcolor, + int ncolors); + +/** + * Maps an RGB triple to an opaque pixel value for a given pixel format + */ +extern DECLSPEC Uint32 SDLCALL SDL_MapRGB +(const SDL_PixelFormat * const format, + const Uint8 r, const Uint8 g, const Uint8 b); + +/** + * Maps an RGBA quadruple to a pixel value for a given pixel format + */ +extern DECLSPEC Uint32 SDLCALL SDL_MapRGBA +(const SDL_PixelFormat * const format, + const Uint8 r, const Uint8 g, const Uint8 b, const Uint8 a); + +/** + * Maps a pixel value into the RGB components for a given pixel format + */ +extern DECLSPEC void SDLCALL SDL_GetRGB(Uint32 pixel, + const SDL_PixelFormat * const fmt, + Uint8 *r, Uint8 *g, Uint8 *b); + +/** + * Maps a pixel value into the RGBA components for a given pixel format + */ +extern DECLSPEC void SDLCALL SDL_GetRGBA(Uint32 pixel, + const SDL_PixelFormat * const fmt, + Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); + +/** @sa SDL_CreateRGBSurface */ +#define SDL_AllocSurface SDL_CreateRGBSurface +/** + * Allocate and free an RGB surface (must be called after SDL_SetVideoMode) + * If the depth is 4 or 8 bits, an empty palette is allocated for the surface. + * If the depth is greater than 8 bits, the pixel format is set using the + * flags '[RGB]mask'. + * If the function runs out of memory, it will return NULL. + * + * The 'flags' tell what kind of surface to create. + * SDL_SWSURFACE means that the surface should be created in system memory. + * SDL_HWSURFACE means that the surface should be created in video memory, + * with the same format as the display surface. This is useful for surfaces + * that will not change much, to take advantage of hardware acceleration + * when being blitted to the display surface. + * SDL_ASYNCBLIT means that SDL will try to perform asynchronous blits with + * this surface, but you must always lock it before accessing the pixels. + * SDL will wait for current blits to finish before returning from the lock. + * SDL_SRCCOLORKEY indicates that the surface will be used for colorkey blits. + * If the hardware supports acceleration of colorkey blits between + * two surfaces in video memory, SDL will try to place the surface in + * video memory. If this isn't possible or if there is no hardware + * acceleration available, the surface will be placed in system memory. + * SDL_SRCALPHA means that the surface will be used for alpha blits and + * if the hardware supports hardware acceleration of alpha blits between + * two surfaces in video memory, to place the surface in video memory + * if possible, otherwise it will be placed in system memory. + * If the surface is created in video memory, blits will be _much_ faster, + * but the surface format must be identical to the video surface format, + * and the only way to access the pixels member of the surface is to use + * the SDL_LockSurface() and SDL_UnlockSurface() calls. + * If the requested surface actually resides in video memory, SDL_HWSURFACE + * will be set in the flags member of the returned surface. If for some + * reason the surface could not be placed in video memory, it will not have + * the SDL_HWSURFACE flag set, and will be created in system memory instead. + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_CreateRGBSurface + (Uint32 flags, int width, int height, int depth, + Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); +/** @sa SDL_CreateRGBSurface */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_CreateRGBSurfaceFrom(void *pixels, + int width, int height, int depth, int pitch, + Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); +extern DECLSPEC void SDLCALL SDL_FreeSurface(SDL_Surface *surface); + +/** + * SDL_LockSurface() sets up a surface for directly accessing the pixels. + * Between calls to SDL_LockSurface()/SDL_UnlockSurface(), you can write + * to and read from 'surface->pixels', using the pixel format stored in + * 'surface->format'. Once you are done accessing the surface, you should + * use SDL_UnlockSurface() to release it. + * + * Not all surfaces require locking. If SDL_MUSTLOCK(surface) evaluates + * to 0, then you can read and write to the surface at any time, and the + * pixel format of the surface will not change. In particular, if the + * SDL_HWSURFACE flag is not given when calling SDL_SetVideoMode(), you + * will not need to lock the display surface before accessing it. + * + * No operating system or library calls should be made between lock/unlock + * pairs, as critical system locks may be held during this time. + * + * SDL_LockSurface() returns 0, or -1 if the surface couldn't be locked. + */ +extern DECLSPEC int SDLCALL SDL_LockSurface(SDL_Surface *surface); +extern DECLSPEC void SDLCALL SDL_UnlockSurface(SDL_Surface *surface); + +/** + * Load a surface from a seekable SDL data source (memory or file.) + * If 'freesrc' is non-zero, the source will be closed after being read. + * Returns the new surface, or NULL if there was an error. + * The new surface should be freed with SDL_FreeSurface(). + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_LoadBMP_RW(SDL_RWops *src, int freesrc); + +/** Convenience macro -- load a surface from a file */ +#define SDL_LoadBMP(file) SDL_LoadBMP_RW(SDL_RWFromFile(file, "rb"), 1) + +/** + * Save a surface to a seekable SDL data source (memory or file.) + * If 'freedst' is non-zero, the source will be closed after being written. + * Returns 0 if successful or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_SaveBMP_RW + (SDL_Surface *surface, SDL_RWops *dst, int freedst); + +/** Convenience macro -- save a surface to a file */ +#define SDL_SaveBMP(surface, file) \ + SDL_SaveBMP_RW(surface, SDL_RWFromFile(file, "wb"), 1) + +/** + * Sets the color key (transparent pixel) in a blittable surface. + * If 'flag' is SDL_SRCCOLORKEY (optionally OR'd with SDL_RLEACCEL), + * 'key' will be the transparent pixel in the source image of a blit. + * SDL_RLEACCEL requests RLE acceleration for the surface if present, + * and removes RLE acceleration if absent. + * If 'flag' is 0, this function clears any current color key. + * This function returns 0, or -1 if there was an error. + */ +extern DECLSPEC int SDLCALL SDL_SetColorKey + (SDL_Surface *surface, Uint32 flag, Uint32 key); + +/** + * This function sets the alpha value for the entire surface, as opposed to + * using the alpha component of each pixel. This value measures the range + * of transparency of the surface, 0 being completely transparent to 255 + * being completely opaque. An 'alpha' value of 255 causes blits to be + * opaque, the source pixels copied to the destination (the default). Note + * that per-surface alpha can be combined with colorkey transparency. + * + * If 'flag' is 0, alpha blending is disabled for the surface. + * If 'flag' is SDL_SRCALPHA, alpha blending is enabled for the surface. + * OR:ing the flag with SDL_RLEACCEL requests RLE acceleration for the + * surface; if SDL_RLEACCEL is not specified, the RLE accel will be removed. + * + * The 'alpha' parameter is ignored for surfaces that have an alpha channel. + */ +extern DECLSPEC int SDLCALL SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha); + +/** + * Sets the clipping rectangle for the destination surface in a blit. + * + * If the clip rectangle is NULL, clipping will be disabled. + * If the clip rectangle doesn't intersect the surface, the function will + * return SDL_FALSE and blits will be completely clipped. Otherwise the + * function returns SDL_TRUE and blits to the surface will be clipped to + * the intersection of the surface area and the clipping rectangle. + * + * Note that blits are automatically clipped to the edges of the source + * and destination surfaces. + */ +extern DECLSPEC SDL_bool SDLCALL SDL_SetClipRect(SDL_Surface *surface, const SDL_Rect *rect); + +/** + * Gets the clipping rectangle for the destination surface in a blit. + * 'rect' must be a pointer to a valid rectangle which will be filled + * with the correct values. + */ +extern DECLSPEC void SDLCALL SDL_GetClipRect(SDL_Surface *surface, SDL_Rect *rect); + +/** + * Creates a new surface of the specified format, and then copies and maps + * the given surface to it so the blit of the converted surface will be as + * fast as possible. If this function fails, it returns NULL. + * + * The 'flags' parameter is passed to SDL_CreateRGBSurface() and has those + * semantics. You can also pass SDL_RLEACCEL in the flags parameter and + * SDL will try to RLE accelerate colorkey and alpha blits in the resulting + * surface. + * + * This function is used internally by SDL_DisplayFormat(). + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_ConvertSurface + (SDL_Surface *src, SDL_PixelFormat *fmt, Uint32 flags); + +/** + * This performs a fast blit from the source surface to the destination + * surface. It assumes that the source and destination rectangles are + * the same size. If either 'srcrect' or 'dstrect' are NULL, the entire + * surface (src or dst) is copied. The final blit rectangles are saved + * in 'srcrect' and 'dstrect' after all clipping is performed. + * If the blit is successful, it returns 0, otherwise it returns -1. + * + * The blit function should not be called on a locked surface. + * + * The blit semantics for surfaces with and without alpha and colorkey + * are defined as follows: + * + * RGBA->RGB: + * SDL_SRCALPHA set: + * alpha-blend (using alpha-channel). + * SDL_SRCCOLORKEY ignored. + * SDL_SRCALPHA not set: + * copy RGB. + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * RGB values of the source colour key, ignoring alpha in the + * comparison. + * + * RGB->RGBA: + * SDL_SRCALPHA set: + * alpha-blend (using the source per-surface alpha value); + * set destination alpha to opaque. + * SDL_SRCALPHA not set: + * copy RGB, set destination alpha to source per-surface alpha value. + * both: + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * source colour key. + * + * RGBA->RGBA: + * SDL_SRCALPHA set: + * alpha-blend (using the source alpha channel) the RGB values; + * leave destination alpha untouched. [Note: is this correct?] + * SDL_SRCCOLORKEY ignored. + * SDL_SRCALPHA not set: + * copy all of RGBA to the destination. + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * RGB values of the source colour key, ignoring alpha in the + * comparison. + * + * RGB->RGB: + * SDL_SRCALPHA set: + * alpha-blend (using the source per-surface alpha value). + * SDL_SRCALPHA not set: + * copy RGB. + * both: + * if SDL_SRCCOLORKEY set, only copy the pixels matching the + * source colour key. + * + * If either of the surfaces were in video memory, and the blit returns -2, + * the video memory was lost, so it should be reloaded with artwork and + * re-blitted: + * @code + * while ( SDL_BlitSurface(image, imgrect, screen, dstrect) == -2 ) { + * while ( SDL_LockSurface(image) < 0 ) + * Sleep(10); + * -- Write image pixels to image->pixels -- + * SDL_UnlockSurface(image); + * } + * @endcode + * + * This happens under DirectX 5.0 when the system switches away from your + * fullscreen application. The lock will also fail until you have access + * to the video memory again. + * + * You should call SDL_BlitSurface() unless you know exactly how SDL + * blitting works internally and how to use the other blit functions. + */ +#define SDL_BlitSurface SDL_UpperBlit + +/** This is the public blit function, SDL_BlitSurface(), and it performs + * rectangle validation and clipping before passing it to SDL_LowerBlit() + */ +extern DECLSPEC int SDLCALL SDL_UpperBlit + (SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect); +/** This is a semi-private blit function and it performs low-level surface + * blitting only. + */ +extern DECLSPEC int SDLCALL SDL_LowerBlit + (SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect); + +/** + * This function performs a fast fill of the given rectangle with 'color' + * The given rectangle is clipped to the destination surface clip area + * and the final fill rectangle is saved in the passed in pointer. + * If 'dstrect' is NULL, the whole surface will be filled with 'color' + * The color should be a pixel of the format used by the surface, and + * can be generated by the SDL_MapRGB() function. + * This function returns 0 on success, or -1 on error. + */ +extern DECLSPEC int SDLCALL SDL_FillRect + (SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color); + +/** + * This function takes a surface and copies it to a new surface of the + * pixel format and colors of the video framebuffer, suitable for fast + * blitting onto the display surface. It calls SDL_ConvertSurface() + * + * If you want to take advantage of hardware colorkey or alpha blit + * acceleration, you should set the colorkey and alpha value before + * calling this function. + * + * If the conversion fails or runs out of memory, it returns NULL + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_DisplayFormat(SDL_Surface *surface); + +/** + * This function takes a surface and copies it to a new surface of the + * pixel format and colors of the video framebuffer (if possible), + * suitable for fast alpha blitting onto the display surface. + * The new surface will always have an alpha channel. + * + * If you want to take advantage of hardware colorkey or alpha blit + * acceleration, you should set the colorkey and alpha value before + * calling this function. + * + * If the conversion fails or runs out of memory, it returns NULL + */ +extern DECLSPEC SDL_Surface * SDLCALL SDL_DisplayFormatAlpha(SDL_Surface *surface); + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name YUV video surface overlay functions */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** This function creates a video output overlay + * Calling the returned surface an overlay is something of a misnomer because + * the contents of the display surface underneath the area where the overlay + * is shown is undefined - it may be overwritten with the converted YUV data. + */ +extern DECLSPEC SDL_Overlay * SDLCALL SDL_CreateYUVOverlay(int width, int height, + Uint32 format, SDL_Surface *display); + +/** Lock an overlay for direct access, and unlock it when you are done */ +extern DECLSPEC int SDLCALL SDL_LockYUVOverlay(SDL_Overlay *overlay); +extern DECLSPEC void SDLCALL SDL_UnlockYUVOverlay(SDL_Overlay *overlay); + +/** Blit a video overlay to the display surface. + * The contents of the video surface underneath the blit destination are + * not defined. + * The width and height of the destination rectangle may be different from + * that of the overlay, but currently only 2x scaling is supported. + */ +extern DECLSPEC int SDLCALL SDL_DisplayYUVOverlay(SDL_Overlay *overlay, SDL_Rect *dstrect); + +/** Free a video overlay */ +extern DECLSPEC void SDLCALL SDL_FreeYUVOverlay(SDL_Overlay *overlay); + +/*@}*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name OpenGL support functions. */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** + * Dynamically load an OpenGL library, or the default one if path is NULL + * + * If you do this, you need to retrieve all of the GL functions used in + * your program from the dynamic library using SDL_GL_GetProcAddress(). + */ +extern DECLSPEC int SDLCALL SDL_GL_LoadLibrary(const char *path); + +/** + * Get the address of a GL function + */ +extern DECLSPEC void * SDLCALL SDL_GL_GetProcAddress(const char* proc); + +/** + * Set an attribute of the OpenGL subsystem before intialization. + */ +extern DECLSPEC int SDLCALL SDL_GL_SetAttribute(SDL_GLattr attr, int value); + +/** + * Get an attribute of the OpenGL subsystem from the windowing + * interface, such as glX. This is of course different from getting + * the values from SDL's internal OpenGL subsystem, which only + * stores the values you request before initialization. + * + * Developers should track the values they pass into SDL_GL_SetAttribute + * themselves if they want to retrieve these values. + */ +extern DECLSPEC int SDLCALL SDL_GL_GetAttribute(SDL_GLattr attr, int* value); + +/** + * Swap the OpenGL buffers, if double-buffering is supported. + */ +extern DECLSPEC void SDLCALL SDL_GL_SwapBuffers(void); + +/** @name OpenGL Internal Functions + * Internal functions that should not be called unless you have read + * and understood the source code for these functions. + */ +/*@{*/ +extern DECLSPEC void SDLCALL SDL_GL_UpdateRects(int numrects, SDL_Rect* rects); +extern DECLSPEC void SDLCALL SDL_GL_Lock(void); +extern DECLSPEC void SDLCALL SDL_GL_Unlock(void); +/*@}*/ + +/*@}*/ + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/** @name Window Manager Functions */ +/** These functions allow interaction with the window manager, if any. */ /*@{*/ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/** + * Sets the title and icon text of the display window (UTF-8 encoded) + */ +extern DECLSPEC void SDLCALL SDL_WM_SetCaption(const char *title, const char *icon); +/** + * Gets the title and icon text of the display window (UTF-8 encoded) + */ +extern DECLSPEC void SDLCALL SDL_WM_GetCaption(char **title, char **icon); + +/** + * Sets the icon for the display window. + * This function must be called before the first call to SDL_SetVideoMode(). + * It takes an icon surface, and a mask in MSB format. + * If 'mask' is NULL, the entire icon surface will be used as the icon. + */ +extern DECLSPEC void SDLCALL SDL_WM_SetIcon(SDL_Surface *icon, Uint8 *mask); + +/** + * This function iconifies the window, and returns 1 if it succeeded. + * If the function succeeds, it generates an SDL_APPACTIVE loss event. + * This function is a noop and returns 0 in non-windowed environments. + */ +extern DECLSPEC int SDLCALL SDL_WM_IconifyWindow(void); + +/** + * Toggle fullscreen mode without changing the contents of the screen. + * If the display surface does not require locking before accessing + * the pixel information, then the memory pointers will not change. + * + * If this function was able to toggle fullscreen mode (change from + * running in a window to fullscreen, or vice-versa), it will return 1. + * If it is not implemented, or fails, it returns 0. + * + * The next call to SDL_SetVideoMode() will set the mode fullscreen + * attribute based on the flags parameter - if SDL_FULLSCREEN is not + * set, then the display will be windowed by default where supported. + * + * This is currently only implemented in the X11 video driver. + */ +extern DECLSPEC int SDLCALL SDL_WM_ToggleFullScreen(SDL_Surface *surface); + +typedef enum { + SDL_GRAB_QUERY = -1, + SDL_GRAB_OFF = 0, + SDL_GRAB_ON = 1, + SDL_GRAB_FULLSCREEN /**< Used internally */ +} SDL_GrabMode; +/** + * This function allows you to set and query the input grab state of + * the application. It returns the new input grab state. + * + * Grabbing means that the mouse is confined to the application window, + * and nearly all keyboard input is passed directly to the application, + * and not interpreted by a window manager, if any. + */ +extern DECLSPEC SDL_GrabMode SDLCALL SDL_WM_GrabInput(SDL_GrabMode mode); + +/*@}*/ + +/** @internal Not in public API at the moment - do not use! */ +extern DECLSPEC int SDLCALL SDL_SoftStretch(SDL_Surface *src, SDL_Rect *srcrect, + SDL_Surface *dst, SDL_Rect *dstrect); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +} +#endif +#include "close_code.h" + +#endif /* _SDL_video_h */ diff --git a/code/SDL12/include/begin_code.h b/code/SDL12/include/begin_code.h new file mode 100644 index 0000000..2274809 --- /dev/null +++ b/code/SDL12/include/begin_code.h @@ -0,0 +1,191 @@ +/* + SDL - Simple DirectMedia Layer + Copyright (C) 1997-2009 Sam Lantinga + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + Sam Lantinga + slouken@libsdl.org +*/ + +/** + * @file begin_code.h + * This file sets things up for C dynamic library function definitions, + * static inlined functions, and structures aligned at 4-byte alignment. + * If you don't like ugly C preprocessor code, don't look at this file. :) + */ + +/** + * @file begin_code.h + * This shouldn't be nested -- included it around code only. + */ +#ifdef _begin_code_h +#error Nested inclusion of begin_code.h +#endif +#define _begin_code_h + +/** + * @def DECLSPEC + * Some compilers use a special export keyword + */ +#ifndef DECLSPEC +# if defined(__BEOS__) || defined(__HAIKU__) +# if defined(__GNUC__) +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC __declspec(export) +# endif +# elif defined(__WIN32__) +# ifdef __BORLANDC__ +# ifdef BUILD_SDL +# define DECLSPEC +# else +# define DECLSPEC __declspec(dllimport) +# endif +# else +# define DECLSPEC __declspec(dllexport) +# endif +# elif defined(__OS2__) +# ifdef __WATCOMC__ +# ifdef BUILD_SDL +# define DECLSPEC __declspec(dllexport) +# else +# define DECLSPEC +# endif +# elif defined (__GNUC__) && __GNUC__ < 4 +# /* Added support for GCC-EMX = 4 +# define DECLSPEC __attribute__ ((visibility("default"))) +# else +# define DECLSPEC +# endif +# endif +#endif + +/** + * @def SDLCALL + * By default SDL uses the C calling convention + */ +#ifndef SDLCALL +# if defined(__WIN32__) && !defined(__GNUC__) +# define SDLCALL __cdecl +# elif defined(__OS2__) +# if defined (__GNUC__) && __GNUC__ < 4 +# /* Added support for GCC-EMX +; +; This file is part of Quake III Arena source code. +; +; Quake III Arena source code is free software; you can redistribute it +; and/or modify it under the terms of the GNU General Public License as +; published by the Free Software Foundation; either version 2 of the License, +; or (at your option) any later version. +; +; Quake III Arena source code is distributed in the hope that it will be +; useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with Quake III Arena source code; if not, write to the Free Software +; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +; =========================================================================== + +; MASM ftol conversion functions using SSE or FPU +; assume __cdecl calling convention is being used for x86, __fastcall for x64 + +IFNDEF idx64 +.model flat, c +ENDIF + +; .data + +; ifndef idx64 +; fpucw WORD 0F7Fh +; endif + +.code + +IFDEF idx64 +; qftol using SSE + + qftolsse PROC + cvttss2si eax, xmm0 + ret + qftolsse ENDP + + qvmftolsse PROC + movss xmm0, dword ptr [rdi + rbx * 4] + cvttss2si eax, xmm0 + ret + qvmftolsse ENDP + +ELSE +; qftol using FPU + + qftolx87m macro src +; not necessary, fpucw is set with _controlfp at startup +; sub esp, 2 +; fnstcw word ptr [esp] +; fldcw fpucw + fld dword ptr src + fistp dword ptr src +; fldcw [esp] + mov eax, src +; add esp, 2 + ret + endm + + qftolx87 PROC +; need this line when storing FPU control word on stack +; qftolx87m [esp + 6] + qftolx87m [esp + 4] + qftolx87 ENDP + + qvmftolx87 PROC + qftolx87m [edi + ebx * 4] + qvmftolx87 ENDP + +; qftol using SSE + qftolsse PROC + movss xmm0, dword ptr [esp + 4] + cvttss2si eax, xmm0 + ret + qftolsse ENDP + + qvmftolsse PROC + movss xmm0, dword ptr [edi + ebx * 4] + cvttss2si eax, xmm0 + ret + qvmftolsse ENDP +ENDIF + +end diff --git a/code/asm/ftola.c b/code/asm/ftola.c new file mode 100644 index 0000000..ad19783 --- /dev/null +++ b/code/asm/ftola.c @@ -0,0 +1,88 @@ +/* +=========================================================================== +Copyright (C) 2011 Thilo Schulz + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + + You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qasm-inline.h" + +/* + * GNU inline asm ftol conversion functions using SSE or FPU + */ + +long qftolsse(float f) +{ + long retval; + + __asm__ volatile + ( + "cvttss2si %1, %0\n" + : "=r" (retval) + : "x" (f) + ); + + return retval; +} + +int qvmftolsse(void) +{ + int retval; + + __asm__ volatile + ( + "movss (" EDI ", " EBX ", 4), %%xmm0\n" + "cvttss2si %%xmm0, %0\n" + : "=r" (retval) + : + : "%xmm0" + ); + + return retval; +} + +long qftolx87(float f) +{ + long retval; + + __asm__ volatile + ( + "flds %1\n" + "fistpl %1\n" + "mov %1, %0\n" + : "=r" (retval) + : "m" (f) + ); + + return retval; +} + +int qvmftolx87(void) +{ + int retval; + + __asm__ volatile + ( + "flds (" EDI ", " EBX ", 4)\n" + "fistpl (" EDI ", " EBX ", 4)\n" + "mov (" EDI ", " EBX ", 4), %0\n" + : "=r" (retval) + ); + + return retval; +} diff --git a/code/asm/matha.s b/code/asm/matha.s new file mode 100644 index 0000000..88974d3 --- /dev/null +++ b/code/asm/matha.s @@ -0,0 +1,54 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// math.s +// x86 assembly-language math routines. + +#include "qasm.h" + + +#if id386 + + .text + +// TODO: rounding needed? +// stack parameter offset +#define val 4 + +.globl C(Invert24To16) +C(Invert24To16): + + movl val(%esp),%ecx + movl $0x100,%edx // 0x10000000000 as dividend + cmpl %edx,%ecx + jle LOutOfRange + + subl %eax,%eax + divl %ecx + + ret + +LOutOfRange: + movl $0xFFFFFFFF,%eax + ret + +#endif // id386 diff --git a/code/asm/qasm-inline.h b/code/asm/qasm-inline.h new file mode 100644 index 0000000..b609aec --- /dev/null +++ b/code/asm/qasm-inline.h @@ -0,0 +1,39 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#ifndef __ASM_INLINE_I386__ +#define __ASM_INLINE_I386__ + +#include "../qcommon/q_platform.h" + +#if idx64 + #define EAX "%%rax" + #define EBX "%%rbx" + #define ESP "%%rsp" + #define EDI "%%rdi" +#else + #define EAX "%%eax" + #define EBX "%%ebx" + #define ESP "%%esp" + #define EDI "%%edi" +#endif + +#endif diff --git a/code/asm/qasm.h b/code/asm/qasm.h new file mode 100644 index 0000000..777a323 --- /dev/null +++ b/code/asm/qasm.h @@ -0,0 +1,37 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#ifndef __ASM_I386__ +#define __ASM_I386__ + +#include "../qcommon/q_platform.h" + +#ifdef __ELF__ +.section .note.GNU-stack,"",@progbits +#endif + +#if defined(__ELF__) || defined(__WIN64__) +#define C(label) label +#else +#define C(label) _##label +#endif + +#endif diff --git a/code/asm/snapvector.asm b/code/asm/snapvector.asm new file mode 100644 index 0000000..eca40fe --- /dev/null +++ b/code/asm/snapvector.asm @@ -0,0 +1,107 @@ +; =========================================================================== +; Copyright (C) 2011 Thilo Schulz +; +; This file is part of Quake III Arena source code. +; +; Quake III Arena source code is free software; you can redistribute it +; and/or modify it under the terms of the GNU General Public License as +; published by the Free Software Foundation; either version 2 of the License, +; or (at your option) any later version. +; +; Quake III Arena source code is distributed in the hope that it will be +; useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with Quake III Arena source code; if not, write to the Free Software +; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +; =========================================================================== + +; MASM version of snapvector conversion function using SSE or FPU +; assume __cdecl calling convention is being used for x86, __fastcall for x64 +; +; function prototype: +; void qsnapvector(vec3_t vec) + +IFNDEF idx64 +.model flat, c +ENDIF + +.data + + ALIGN 16 + ssemask DWORD 0FFFFFFFFh, 0FFFFFFFFh, 0FFFFFFFFh, 00000000h + ssecw DWORD 00001F80h + +IFNDEF idx64 + fpucw WORD 037Fh +ENDIF + +.code + +IFDEF idx64 +; qsnapvector using SSE + + qsnapvectorsse PROC + sub rsp, 8 + stmxcsr [rsp] ; save SSE control word + ldmxcsr ssecw ; set to round nearest + + push rdi + mov rdi, rcx ; maskmovdqu uses rdi as implicit memory operand + movaps xmm1, ssemask ; initialize the mask register for maskmovdqu + movups xmm0, [rdi] ; here is stored our vector. Read 4 values in one go + cvtps2dq xmm0, xmm0 ; convert 4 single fp to int + cvtdq2ps xmm0, xmm0 ; convert 4 int to single fp + maskmovdqu xmm0, xmm1 ; write 3 values back to memory + pop rdi + + ldmxcsr [rsp] ; restore sse control word to old value + add rsp, 8 + ret + qsnapvectorsse ENDP + +ELSE + + qsnapvectorsse PROC + sub esp, 8 + stmxcsr [esp] ; save SSE control word + ldmxcsr ssecw ; set to round nearest + + push edi + mov edi, dword ptr 16[esp] ; maskmovdqu uses edi as implicit memory operand + movaps xmm1, ssemask ; initialize the mask register for maskmovdqu + movups xmm0, [edi] ; here is stored our vector. Read 4 values in one go + cvtps2dq xmm0, xmm0 ; convert 4 single fp to int + cvtdq2ps xmm0, xmm0 ; convert 4 int to single fp + maskmovdqu xmm0, xmm1 ; write 3 values back to memory + pop edi + + ldmxcsr [esp] ; restore sse control word to old value + add esp, 8 + ret + qsnapvectorsse ENDP + + qroundx87 macro src + fld dword ptr src + fistp dword ptr src + fild dword ptr src + fstp dword ptr src + endm + + qsnapvectorx87 PROC + mov eax, dword ptr 4[esp] + sub esp, 2 + fnstcw word ptr [esp] + fldcw fpucw + qroundx87 [eax] + qroundx87 4[eax] + qroundx87 8[eax] + fldcw [esp] + add esp, 2 + qsnapvectorx87 ENDP + +ENDIF + +end diff --git a/code/asm/snapvector.c b/code/asm/snapvector.c new file mode 100644 index 0000000..8e9b286 --- /dev/null +++ b/code/asm/snapvector.c @@ -0,0 +1,87 @@ +/* +=========================================================================== +Copyright (C) 2011 Thilo Schulz + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "qasm-inline.h" +#include "../qcommon/q_shared.h" + +/* + * GNU inline asm version of qsnapvector + * See MASM snapvector.asm for commentary + */ + +static unsigned char ssemask[16] __attribute__((aligned(16))) = +{ + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\x00\x00" +}; + +static const unsigned int ssecw __attribute__((aligned(16))) = 0x00001F80; +static const unsigned short fpucw = 0x037F; + +void qsnapvectorsse(vec3_t vec) +{ + uint32_t oldcw __attribute__((aligned(16))); + + __asm__ volatile + ( + "stmxcsr %3\n" + "ldmxcsr %1\n" + + "movaps (%0), %%xmm1\n" + "movups (%2), %%xmm0\n" + "cvtps2dq %%xmm0, %%xmm0\n" + "cvtdq2ps %%xmm0, %%xmm0\n" + // vec MUST reside in register rdi as maskmovdqu uses + // it as an implicit operand. The "D" constraint makes + // sure of that. + "maskmovdqu %%xmm1, %%xmm0\n" + + "ldmxcsr %3\n" + : + : "r" (ssemask), "m" (ssecw), "D" (vec), "m" (oldcw) + : "memory", "%xmm0", "%xmm1" + ); + +} + +#define QROUNDX87(src) \ + "flds " src "\n" \ + "fistp " src "\n" \ + "fild " src "\n" \ + "fstp " src "\n" + +void qsnapvectorx87(vec3_t vec) +{ + __asm__ volatile + ( + "sub $2, " ESP "\n" + "fnstcw (" ESP ")\n" + "fldcw %0\n" + QROUNDX87("(%1)") + QROUNDX87("4(%1)") + QROUNDX87("8(%1)") + "fldcw (" ESP ")\n" + "add $2, " ESP "\n" + : + : "m" (fpucw), "r" (vec) + : "memory" + ); +} diff --git a/code/asm/snd_mixa.s b/code/asm/snd_mixa.s new file mode 100644 index 0000000..7c3bf6b --- /dev/null +++ b/code/asm/snd_mixa.s @@ -0,0 +1,217 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +// snd_mixa.s +// x86 assembly-language sound code +// + +#include "qasm.h" + +#if id386 + + .text + +#if 0 +//---------------------------------------------------------------------- +// 8-bit sound-mixing code +//---------------------------------------------------------------------- + +#define ch 4+16 +#define sc 8+16 +#define count 12+16 + +.globl C(S_PaintChannelFrom8) +C(S_PaintChannelFrom8): + pushl %esi // preserve register variables + pushl %edi + pushl %ebx + pushl %ebp + +// int data; +// short *lscale, *rscale; +// unsigned char *sfx; +// int i; + + movl ch(%esp),%ebx + movl sc(%esp),%esi + +// if (ch->leftvol > 255) +// ch->leftvol = 255; +// if (ch->rightvol > 255) +// ch->rightvol = 255; + movl ch_leftvol(%ebx),%eax + movl ch_rightvol(%ebx),%edx + cmpl $255,%eax + jna LLeftSet + movl $255,%eax +LLeftSet: + cmpl $255,%edx + jna LRightSet + movl $255,%edx +LRightSet: + +// lscale = snd_scaletable[ch->leftvol >> 3]; +// rscale = snd_scaletable[ch->rightvol >> 3]; +// sfx = (signed char *)sc->data + ch->pos; +// ch->pos += count; + andl $0xF8,%eax + addl $20,%esi + movl (%esi),%esi + andl $0xF8,%edx + movl ch_pos(%ebx),%edi + movl count(%esp),%ecx + addl %edi,%esi + shll $7,%eax + addl %ecx,%edi + shll $7,%edx + movl %edi,ch_pos(%ebx) + addl $(C(snd_scaletable)),%eax + addl $(C(snd_scaletable)),%edx + subl %ebx,%ebx + movb -1(%esi,%ecx,1),%bl + + testl $1,%ecx + jz LMix8Loop + + movl (%eax,%ebx,4),%edi + movl (%edx,%ebx,4),%ebp + addl C(paintbuffer)+psp_left-psp_size(,%ecx,psp_size),%edi + addl C(paintbuffer)+psp_right-psp_size(,%ecx,psp_size),%ebp + movl %edi,C(paintbuffer)+psp_left-psp_size(,%ecx,psp_size) + movl %ebp,C(paintbuffer)+psp_right-psp_size(,%ecx,psp_size) + movb -2(%esi,%ecx,1),%bl + + decl %ecx + jz LDone + +// for (i=0 ; i>8; +// if (val > 0x7fff) +// snd_out[i] = 0x7fff; +// else if (val < (short)0x8000) +// snd_out[i] = (short)0x8000; +// else +// snd_out[i] = val; + movl -8(%ebx,%ecx,4),%eax + sarl $8,%eax + cmpl $0x7FFF,%eax + jg LClampHigh + cmpl $0xFFFF8000,%eax + jnl LClampDone + movl $0xFFFF8000,%eax + jmp LClampDone +LClampHigh: + movl $0x7FFF,%eax +LClampDone: + +// val = (snd_p[i+1]*snd_vol)>>8; +// if (val > 0x7fff) +// snd_out[i+1] = 0x7fff; +// else if (val < (short)0x8000) +// snd_out[i+1] = (short)0x8000; +// else +// snd_out[i+1] = val; + movl -4(%ebx,%ecx,4),%edx + sarl $8,%edx + cmpl $0x7FFF,%edx + jg LClampHigh2 + cmpl $0xFFFF8000,%edx + jnl LClampDone2 + movl $0xFFFF8000,%edx + jmp LClampDone2 +LClampHigh2: + movl $0x7FFF,%edx +LClampDone2: + shll $16,%edx + andl $0xFFFF,%eax + orl %eax,%edx + movl %edx,-4(%edi,%ecx,2) + +// } + subl $2,%ecx + jnz LWLBLoopTop + +// snd_p += snd_linear_count; + + popl %ebx + popl %edi + + ret + +#endif // id386 + diff --git a/code/asm/vm_x86_64.asm b/code/asm/vm_x86_64.asm new file mode 100644 index 0000000..030b698 --- /dev/null +++ b/code/asm/vm_x86_64.asm @@ -0,0 +1,76 @@ +; =========================================================================== +; Copyright (C) 2011 Thilo Schulz +; +; This file is part of Quake III Arena source code. +; +; Quake III Arena source code is free software; you can redistribute it +; and/or modify it under the terms of the GNU General Public License as +; published by the Free Software Foundation; either version 2 of the License, +; or (at your option) any later version. +; +; Quake III Arena source code is distributed in the hope that it will be +; useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +; GNU General Public License for more details. +; +; You should have received a copy of the GNU General Public License +; along with Quake III Arena source code; if not, write to the Free Software +; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +; =========================================================================== + +; Call wrapper for vm_x86 when built with MSVC in 64 bit mode, +; since MSVC does not support inline x64 assembler code anymore. +; +; assumes __fastcall calling convention + +DoSyscall PROTO + +.code + +; Call to static void DoSyscall(int syscallNum, int programStack, int *opStackBase, uint8_t opStackOfs, intptr_t arg) + +qsyscall64 PROC + sub rsp, 28h ; after this esp will be aligned to 16 byte boundary + mov qword ptr [rsp + 20h], rcx ; 5th parameter "arg" is passed on stack + mov r9b, bl ; opStackOfs + mov r8, rdi ; opStackBase + mov edx, esi ; programStack + mov ecx, eax ; syscallNum + mov rax, DoSyscall ; store call address of DoSyscall in rax + call rax + add rsp, 28h + ret +qsyscall64 ENDP + + +; Call to compiled code after setting up the register environment for the VM +; prototype: +; uint8_t qvmcall64(int *programStack, int *opStack, intptr_t *instructionPointers, byte *dataBase); + +qvmcall64 PROC + push rsi ; push non-volatile registers to stack + push rdi + push rbx + ; need to save pointer in rcx so we can write back the programData value to caller + push rcx + + ; registers r8 and r9 have correct value already thanx to __fastcall + xor rbx, rbx ; opStackOfs starts out being 0 + mov rdi, rdx ; opStack + mov esi, dword ptr [rcx] ; programStack + + call qword ptr [r8] ; instructionPointers[0] is also the entry point + + pop rcx + + mov dword ptr [rcx], esi ; write back the programStack value + mov al, bl ; return opStack offset + + pop rbx + pop rdi + pop rsi + + ret +qvmcall64 ENDP + +end diff --git a/code/botlib/aasfile.h b/code/botlib/aasfile.h new file mode 100644 index 0000000..8f2fbf6 --- /dev/null +++ b/code/botlib/aasfile.h @@ -0,0 +1,267 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +//NOTE: int = default signed +// default long + +#define AASID (('S'<<24)+('A'<<16)+('A'<<8)+'E') +#define AASVERSION_OLD 4 +#define AASVERSION 5 + +//presence types +#define PRESENCE_NONE 1 +#define PRESENCE_NORMAL 2 +#define PRESENCE_CROUCH 4 + +//travel types +#define MAX_TRAVELTYPES 32 +#define TRAVEL_INVALID 1 //temporary not possible +#define TRAVEL_WALK 2 //walking +#define TRAVEL_CROUCH 3 //crouching +#define TRAVEL_BARRIERJUMP 4 //jumping onto a barrier +#define TRAVEL_JUMP 5 //jumping +#define TRAVEL_LADDER 6 //climbing a ladder +#define TRAVEL_WALKOFFLEDGE 7 //walking of a ledge +#define TRAVEL_SWIM 8 //swimming +#define TRAVEL_WATERJUMP 9 //jump out of the water +#define TRAVEL_TELEPORT 10 //teleportation +#define TRAVEL_ELEVATOR 11 //travel by elevator +#define TRAVEL_ROCKETJUMP 12 //rocket jumping required for travel +#define TRAVEL_BFGJUMP 13 //bfg jumping required for travel +#define TRAVEL_GRAPPLEHOOK 14 //grappling hook required for travel +#define TRAVEL_DOUBLEJUMP 15 //double jump +#define TRAVEL_RAMPJUMP 16 //ramp jump +#define TRAVEL_STRAFEJUMP 17 //strafe jump +#define TRAVEL_JUMPPAD 18 //jump pad +#define TRAVEL_FUNCBOB 19 //func bob + +//additional travel flags +#define TRAVELTYPE_MASK 0xFFFFFF +#define TRAVELFLAG_NOTTEAM1 (1 << 24) +#define TRAVELFLAG_NOTTEAM2 (2 << 24) + +//face flags +#define FACE_SOLID 1 //just solid at the other side +#define FACE_LADDER 2 //ladder +#define FACE_GROUND 4 //standing on ground when in this face +#define FACE_GAP 8 //gap in the ground +#define FACE_LIQUID 16 //face seperating two areas with liquid +#define FACE_LIQUIDSURFACE 32 //face seperating liquid and air +#define FACE_BRIDGE 64 //can walk over this face if bridge is closed + +//area contents +#define AREACONTENTS_WATER 1 +#define AREACONTENTS_LAVA 2 +#define AREACONTENTS_SLIME 4 +#define AREACONTENTS_CLUSTERPORTAL 8 +#define AREACONTENTS_TELEPORTAL 16 +#define AREACONTENTS_ROUTEPORTAL 32 +#define AREACONTENTS_TELEPORTER 64 +#define AREACONTENTS_JUMPPAD 128 +#define AREACONTENTS_DONOTENTER 256 +#define AREACONTENTS_VIEWPORTAL 512 +#define AREACONTENTS_MOVER 1024 +#define AREACONTENTS_NOTTEAM1 2048 +#define AREACONTENTS_NOTTEAM2 4096 +//number of model of the mover inside this area +#define AREACONTENTS_MODELNUMSHIFT 24 +#define AREACONTENTS_MAXMODELNUM 0xFF +#define AREACONTENTS_MODELNUM (AREACONTENTS_MAXMODELNUM << AREACONTENTS_MODELNUMSHIFT) + +//area flags +#define AREA_GROUNDED 1 //bot can stand on the ground +#define AREA_LADDER 2 //area contains one or more ladder faces +#define AREA_LIQUID 4 //area contains a liquid +#define AREA_DISABLED 8 //area is disabled for routing when set +#define AREA_BRIDGE 16 //area ontop of a bridge + +//aas file header lumps +#define AAS_LUMPS 14 +#define AASLUMP_BBOXES 0 +#define AASLUMP_VERTEXES 1 +#define AASLUMP_PLANES 2 +#define AASLUMP_EDGES 3 +#define AASLUMP_EDGEINDEX 4 +#define AASLUMP_FACES 5 +#define AASLUMP_FACEINDEX 6 +#define AASLUMP_AREAS 7 +#define AASLUMP_AREASETTINGS 8 +#define AASLUMP_REACHABILITY 9 +#define AASLUMP_NODES 10 +#define AASLUMP_PORTALS 11 +#define AASLUMP_PORTALINDEX 12 +#define AASLUMP_CLUSTERS 13 + +//========== bounding box ========= + +//bounding box +typedef struct aas_bbox_s +{ + int presencetype; + int flags; + vec3_t mins, maxs; +} aas_bbox_t; + +//============ settings =========== + +//reachability to another area +typedef struct aas_reachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime;//travel time of the inter area movement +} aas_reachability_t; + +//area settings +typedef struct aas_areasettings_s +{ + //could also add all kind of statistic fields + int contents; //contents of the area + int areaflags; //several area flags + int presencetype; //how a bot can be present in this area + int cluster; //cluster the area belongs to, if negative it's a portal + int clusterareanum; //number of the area in the cluster + int numreachableareas; //number of reachable areas from this one + int firstreachablearea; //first reachable area in the reachable area index +} aas_areasettings_t; + +//cluster portal +typedef struct aas_portal_s +{ + int areanum; //area that is the actual portal + int frontcluster; //cluster at front of portal + int backcluster; //cluster at back of portal + int clusterareanum[2]; //number of the area in the front and back cluster +} aas_portal_t; + +//cluster portal index +typedef int aas_portalindex_t; + +//cluster +typedef struct aas_cluster_s +{ + int numareas; //number of areas in the cluster + int numreachabilityareas; //number of areas with reachabilities + int numportals; //number of cluster portals + int firstportal; //first cluster portal in the index +} aas_cluster_t; + +//============ 3d definition ============ + +typedef vec3_t aas_vertex_t; + +//just a plane in the third dimension +typedef struct aas_plane_s +{ + vec3_t normal; //normal vector of the plane + float dist; //distance of the plane (normal vector * distance = point in plane) + int type; +} aas_plane_t; + +//edge +typedef struct aas_edge_s +{ + int v[2]; //numbers of the vertexes of this edge +} aas_edge_t; + +//edge index, negative if vertexes are reversed +typedef int aas_edgeindex_t; + +//a face bounds an area, often it will also seperate two areas +typedef struct aas_face_s +{ + int planenum; //number of the plane this face is in + int faceflags; //face flags (no use to create face settings for just this field) + int numedges; //number of edges in the boundary of the face + int firstedge; //first edge in the edge index + int frontarea; //area at the front of this face + int backarea; //area at the back of this face +} aas_face_t; + +//face index, stores a negative index if backside of face +typedef int aas_faceindex_t; + +//area with a boundary of faces +typedef struct aas_area_s +{ + int areanum; //number of this area + //3d definition + int numfaces; //number of faces used for the boundary of the area + int firstface; //first face in the face index used for the boundary of the area + vec3_t mins; //mins of the area + vec3_t maxs; //maxs of the area + vec3_t center; //'center' of the area +} aas_area_t; + +//nodes of the bsp tree +typedef struct aas_node_s +{ + int planenum; + int children[2]; //child nodes of this node, or areas as leaves when negative + //when a child is zero it's a solid leaf +} aas_node_t; + +//=========== aas file =============== + +//header lump +typedef struct +{ + int fileofs; + int filelen; +} aas_lump_t; + +//aas file header +typedef struct aas_header_s +{ + int ident; + int version; + int bspchecksum; + //data entries + aas_lump_t lumps[AAS_LUMPS]; +} aas_header_t; + + +//====== additional information ====== +/* + +- when a node child is a solid leaf the node child number is zero +- two adjacent areas (sharing a plane at opposite sides) share a face + this face is a portal between the areas +- when an area uses a face from the faceindex with a positive index + then the face plane normal points into the area +- the face edges are stored counter clockwise using the edgeindex +- two adjacent convex areas (sharing a face) only share One face + this is a simple result of the areas being convex +- the areas can't have a mixture of ground and gap faces + other mixtures of faces in one area are allowed +- areas with the AREACONTENTS_CLUSTERPORTAL in the settings have + the cluster number set to the negative portal number +- edge zero is a dummy +- face zero is a dummy +- area zero is a dummy +- node zero is a dummy +*/ diff --git a/code/botlib/be_aas.h b/code/botlib/be_aas.h new file mode 100644 index 0000000..dbf24bc --- /dev/null +++ b/code/botlib/be_aas.h @@ -0,0 +1,221 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * 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/code/botlib/be_aas_bsp.h b/code/botlib/be_aas_bsp.h new file mode 100644 index 0000000..932874a --- /dev/null +++ b/code/botlib/be_aas_bsp.h @@ -0,0 +1,89 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_bsp.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_bsp.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//loads the given BSP file +int AAS_LoadBSPFile(void); +//dump the loaded BSP data +void AAS_DumpBSPData(void); +//unlink the given entity from the bsp tree leaves +void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves); +//link the given entity to the bsp tree leaves of the given model +bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, + vec3_t absmaxs, + int entnum, + int modelnum); + +//calculates collision with given entity +qboolean AAS_EntityCollision(int entnum, + vec3_t start, + vec3_t boxmins, + vec3_t boxmaxs, + vec3_t end, + int contentmask, + bsp_trace_t *trace); +//for debugging +void AAS_PrintFreeBSPLinks(char *str); +// +#endif //AASINTERN + +#define MAX_EPAIRKEY 128 + +//trace through the world +bsp_trace_t AAS_Trace( vec3_t start, + vec3_t mins, + vec3_t maxs, + vec3_t end, + int passent, + int contentmask); +//returns the contents at the given point +int AAS_PointContents(vec3_t point); +//returns true when p2 is in the PVS of p1 +qboolean AAS_inPVS(vec3_t p1, vec3_t p2); +//returns true when p2 is in the PHS of p1 +qboolean AAS_inPHS(vec3_t p1, vec3_t p2); +//returns true if the given areas are connected +qboolean AAS_AreasConnected(int area1, int area2); +//creates a list with entities totally or partly within the given box +int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount); +//gets the mins, maxs and origin of a BSP model +void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin); +//handle to the next bsp entity +int AAS_NextBSPEntity(int ent); +//return the value of the BSP epair key +int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size); +//get a vector for the BSP epair key +int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v); +//get a float for the BSP epair key +int AAS_FloatForBSPEpairKey(int ent, char *key, float *value); +//get an integer for the BSP epair key +int AAS_IntForBSPEpairKey(int ent, char *key, int *value); + diff --git a/code/botlib/be_aas_bspq3.c b/code/botlib/be_aas_bspq3.c new file mode 100644 index 0000000..9bfc824 --- /dev/null +++ b/code/botlib/be_aas_bspq3.c @@ -0,0 +1,487 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_bspq3.c + * + * desc: BSP, Environment Sampling + * + * $Archive: /MissionPack/code/botlib/be_aas_bspq3.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +//#define TRACE_DEBUG + +#define ON_EPSILON 0.005 +//#define DEG2RAD( a ) (( a * M_PI ) / 180.0F) + +#define MAX_BSPENTITIES 2048 + +typedef struct rgb_s +{ + int red; + int green; + int blue; +} rgb_t; + +//bsp entity epair +typedef struct bsp_epair_s +{ + char *key; + char *value; + struct bsp_epair_s *next; +} bsp_epair_t; + +//bsp data entity +typedef struct bsp_entity_s +{ + bsp_epair_t *epairs; +} bsp_entity_t; + +//id Sofware BSP data +typedef struct bsp_s +{ + //true when bsp file is loaded + int loaded; + //entity data + int entdatasize; + char *dentdata; + //bsp entities + int numentities; + bsp_entity_t entities[MAX_BSPENTITIES]; +} bsp_t; + +//global bsp +bsp_t bspworld; + + +#ifdef BSP_DEBUG +typedef struct cname_s +{ + int value; + char *name; +} cname_t; + +cname_t contentnames[] = +{ + {CONTENTS_SOLID,"CONTENTS_SOLID"}, + {CONTENTS_WINDOW,"CONTENTS_WINDOW"}, + {CONTENTS_AUX,"CONTENTS_AUX"}, + {CONTENTS_LAVA,"CONTENTS_LAVA"}, + {CONTENTS_SLIME,"CONTENTS_SLIME"}, + {CONTENTS_WATER,"CONTENTS_WATER"}, + {CONTENTS_MIST,"CONTENTS_MIST"}, + {LAST_VISIBLE_CONTENTS,"LAST_VISIBLE_CONTENTS"}, + + {CONTENTS_AREAPORTAL,"CONTENTS_AREAPORTAL"}, + {CONTENTS_PLAYERCLIP,"CONTENTS_PLAYERCLIP"}, + {CONTENTS_MONSTERCLIP,"CONTENTS_MONSTERCLIP"}, + {CONTENTS_CURRENT_0,"CONTENTS_CURRENT_0"}, + {CONTENTS_CURRENT_90,"CONTENTS_CURRENT_90"}, + {CONTENTS_CURRENT_180,"CONTENTS_CURRENT_180"}, + {CONTENTS_CURRENT_270,"CONTENTS_CURRENT_270"}, + {CONTENTS_CURRENT_UP,"CONTENTS_CURRENT_UP"}, + {CONTENTS_CURRENT_DOWN,"CONTENTS_CURRENT_DOWN"}, + {CONTENTS_ORIGIN,"CONTENTS_ORIGIN"}, + {CONTENTS_MONSTER,"CONTENTS_MONSTER"}, + {CONTENTS_DEADMONSTER,"CONTENTS_DEADMONSTER"}, + {CONTENTS_DETAIL,"CONTENTS_DETAIL"}, + {CONTENTS_TRANSLUCENT,"CONTENTS_TRANSLUCENT"}, + {CONTENTS_LADDER,"CONTENTS_LADDER"}, + {0, 0} +}; + +void PrintContents(int contents) +{ + int i; + + for (i = 0; contentnames[i].value; i++) + { + if (contents & contentnames[i].value) + { + botimport.Print(PRT_MESSAGE, "%s\n", contentnames[i].name); + } //end if + } //end for +} //end of the function PrintContents + +#endif // BSP_DEBUG +//=========================================================================== +// traces axial boxes of any size through the world +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bsp_trace_t AAS_Trace(vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask) +{ + bsp_trace_t bsptrace; + botimport.Trace(&bsptrace, start, mins, maxs, end, passent, contentmask); + return bsptrace; +} //end of the function AAS_Trace +//=========================================================================== +// returns the contents at the given point +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointContents(vec3_t point) +{ + return botimport.PointContents(point); +} //end of the function AAS_PointContents +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_EntityCollision(int entnum, + vec3_t start, vec3_t boxmins, vec3_t boxmaxs, vec3_t end, + int contentmask, bsp_trace_t *trace) +{ + bsp_trace_t enttrace; + + botimport.EntityTrace(&enttrace, start, boxmins, boxmaxs, end, entnum, contentmask); + if (enttrace.fraction < trace->fraction) + { + Com_Memcpy(trace, &enttrace, sizeof(bsp_trace_t)); + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_EntityCollision +//=========================================================================== +// returns true if in Potentially Hearable Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPVS(vec3_t p1, vec3_t p2) +{ + return botimport.inPVS(p1, p2); +} //end of the function AAS_InPVS +//=========================================================================== +// returns true if in Potentially Visible Set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_inPHS(vec3_t p1, vec3_t p2) +{ + return qtrue; +} //end of the function AAS_inPHS +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_BSPModelMinsMaxsOrigin(int modelnum, vec3_t angles, vec3_t mins, vec3_t maxs, vec3_t origin) +{ + botimport.BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); +} //end of the function AAS_BSPModelMinsMaxs +//=========================================================================== +// unlinks the entity from all leaves +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromBSPLeaves(bsp_link_t *leaves) +{ +} //end of the function AAS_UnlinkFromBSPLeaves +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bsp_link_t *AAS_BSPLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum, int modelnum) +{ + return NULL; +} //end of the function AAS_BSPLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxEntities(vec3_t absmins, vec3_t absmaxs, int *list, int maxcount) +{ + return 0; +} //end of the function AAS_BoxEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextBSPEntity(int ent) +{ + ent++; + if (ent >= 1 && ent < bspworld.numentities) return ent; + return 0; +} //end of the function AAS_NextBSPEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPEntityInRange(int ent) +{ + if (ent <= 0 || ent >= bspworld.numentities) + { + botimport.Print(PRT_MESSAGE, "bsp entity out of range\n"); + return qfalse; + } //end if + return qtrue; +} //end of the function AAS_BSPEntityInRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ValueForBSPEpairKey(int ent, char *key, char *value, int size) +{ + bsp_epair_t *epair; + + value[0] = '\0'; + if (!AAS_BSPEntityInRange(ent)) return qfalse; + for (epair = bspworld.entities[ent].epairs; epair; epair = epair->next) + { + if (!strcmp(epair->key, key)) + { + strncpy(value, epair->value, size-1); + value[size-1] = '\0'; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_FindBSPEpair +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_VectorForBSPEpairKey(int ent, char *key, vec3_t v) +{ + char buf[MAX_EPAIRKEY]; + double v1, v2, v3; + + VectorClear(v); + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; + //scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf(buf, "%lf %lf %lf", &v1, &v2, &v3); + v[0] = v1; + v[1] = v2; + v[2] = v3; + return qtrue; +} //end of the function AAS_VectorForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloatForBSPEpairKey(int ent, char *key, float *value) +{ + char buf[MAX_EPAIRKEY]; + + *value = 0; + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; + *value = atof(buf); + return qtrue; +} //end of the function AAS_FloatForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IntForBSPEpairKey(int ent, char *key, int *value) +{ + char buf[MAX_EPAIRKEY]; + + *value = 0; + if (!AAS_ValueForBSPEpairKey(ent, key, buf, MAX_EPAIRKEY)) return qfalse; + *value = atoi(buf); + return qtrue; +} //end of the function AAS_IntForBSPEpairKey +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeBSPEntities(void) +{ + int i; + bsp_entity_t *ent; + bsp_epair_t *epair, *nextepair; + + for (i = 1; i < bspworld.numentities; i++) + { + ent = &bspworld.entities[i]; + for (epair = ent->epairs; epair; epair = nextepair) + { + nextepair = epair->next; + // + if (epair->key) FreeMemory(epair->key); + if (epair->value) FreeMemory(epair->value); + FreeMemory(epair); + } //end for + } //end for + bspworld.numentities = 0; +} //end of the function AAS_FreeBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ParseBSPEntities(void) +{ + script_t *script; + token_t token; + bsp_entity_t *ent; + bsp_epair_t *epair; + + script = LoadScriptMemory(bspworld.dentdata, bspworld.entdatasize, "entdata"); + SetScriptFlags(script, SCFL_NOSTRINGWHITESPACES|SCFL_NOSTRINGESCAPECHARS);//SCFL_PRIMITIVE); + + bspworld.numentities = 1; + + while(PS_ReadToken(script, &token)) + { + if (strcmp(token.string, "{")) + { + ScriptError(script, "invalid %s\n", token.string); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + if (bspworld.numentities >= MAX_BSPENTITIES) + { + botimport.Print(PRT_MESSAGE, "too many entities in BSP file\n"); + break; + } //end if + ent = &bspworld.entities[bspworld.numentities]; + bspworld.numentities++; + ent->epairs = NULL; + while(PS_ReadToken(script, &token)) + { + if (!strcmp(token.string, "}")) break; + epair = (bsp_epair_t *) GetClearedHunkMemory(sizeof(bsp_epair_t)); + epair->next = ent->epairs; + ent->epairs = epair; + if (token.type != TT_STRING) + { + ScriptError(script, "invalid %s\n", token.string); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + StripDoubleQuotes(token.string); + epair->key = (char *) GetHunkMemory(strlen(token.string) + 1); + strcpy(epair->key, token.string); + if (!PS_ExpectTokenType(script, TT_STRING, 0, &token)) + { + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + StripDoubleQuotes(token.string); + epair->value = (char *) GetHunkMemory(strlen(token.string) + 1); + strcpy(epair->value, token.string); + } //end while + if (strcmp(token.string, "}")) + { + ScriptError(script, "missing }\n"); + AAS_FreeBSPEntities(); + FreeScript(script); + return; + } //end if + } //end while + FreeScript(script); +} //end of the function AAS_ParseBSPEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BSPTraceLight(vec3_t start, vec3_t end, vec3_t endpos, int *red, int *green, int *blue) +{ + return 0; +} //end of the function AAS_BSPTraceLight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpBSPData(void) +{ + AAS_FreeBSPEntities(); + + if (bspworld.dentdata) FreeMemory(bspworld.dentdata); + bspworld.dentdata = NULL; + bspworld.entdatasize = 0; + // + bspworld.loaded = qfalse; + Com_Memset( &bspworld, 0, sizeof(bspworld) ); +} //end of the function AAS_DumpBSPData +//=========================================================================== +// load an bsp file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadBSPFile(void) +{ + AAS_DumpBSPData(); + bspworld.entdatasize = strlen(botimport.BSPEntityData()) + 1; + bspworld.dentdata = (char *) GetClearedHunkMemory(bspworld.entdatasize); + Com_Memcpy(bspworld.dentdata, botimport.BSPEntityData(), bspworld.entdatasize); + AAS_ParseBSPEntities(); + bspworld.loaded = qtrue; + return BLERR_NOERROR; +} //end of the function AAS_LoadBSPFile diff --git a/code/botlib/be_aas_cluster.c b/code/botlib/be_aas_cluster.c new file mode 100644 index 0000000..6c029ee --- /dev/null +++ b/code/botlib/be_aas_cluster.c @@ -0,0 +1,1546 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_cluster.c + * + * desc: area clustering + * + * $Archive: /MissionPack/code/botlib/be_aas_cluster.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" +#include "be_aas_cluster.h" + +extern botlib_import_t botimport; + +#define AAS_MAX_PORTALS 65536 +#define AAS_MAX_PORTALINDEXSIZE 65536 +#define AAS_MAX_CLUSTERS 65536 +// +#define MAX_PORTALAREAS 1024 + +// do not flood through area faces, only use reachabilities +int nofaceflood = qtrue; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveClusterAreas(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + aasworld.areasettings[i].cluster = 0; + } //end for +} //end of the function AAS_RemoveClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearCluster(int clusternum) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].cluster == clusternum) + { + aasworld.areasettings[i].cluster = 0; + } //end if + } //end for +} //end of the function AAS_ClearCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemovePortalsClusterReference(int clusternum) +{ + int portalnum; + + for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) + { + if (aasworld.portals[portalnum].frontcluster == clusternum) + { + aasworld.portals[portalnum].frontcluster = 0; + } //end if + if (aasworld.portals[portalnum].backcluster == clusternum) + { + aasworld.portals[portalnum].backcluster = 0; + } //end if + } //end for +} //end of the function AAS_RemovePortalsClusterReference +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdatePortal(int areanum, int clusternum) +{ + int portalnum; + aas_portal_t *portal; + aas_cluster_t *cluster; + + //find the portal of the area + for (portalnum = 1; portalnum < aasworld.numportals; portalnum++) + { + if (aasworld.portals[portalnum].areanum == areanum) break; + } //end for + // + if (portalnum == aasworld.numportals) + { + AAS_Error("no portal of area %d", areanum); + return qtrue; + } //end if + // + portal = &aasworld.portals[portalnum]; + //if the portal is already fully updated + if (portal->frontcluster == clusternum) return qtrue; + if (portal->backcluster == clusternum) return qtrue; + //if the portal has no front cluster yet + if (!portal->frontcluster) + { + portal->frontcluster = clusternum; + } //end if + //if the portal has no back cluster yet + else if (!portal->backcluster) + { + portal->backcluster = clusternum; + } //end else if + else + { + //remove the cluster portal flag contents + aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d is seperating more than two clusters\r\n", areanum); + return qfalse; + } //end else + if (aasworld.portalindexsize >= AAS_MAX_PORTALINDEXSIZE) + { + AAS_Error("AAS_MAX_PORTALINDEXSIZE"); + return qtrue; + } //end if + //set the area cluster number to the negative portal number + aasworld.areasettings[areanum].cluster = -portalnum; + //add the portal to the cluster using the portal index + cluster = &aasworld.clusters[clusternum]; + aasworld.portalindex[cluster->firstportal + cluster->numportals] = portalnum; + aasworld.portalindexsize++; + cluster->numportals++; + return qtrue; +} //end of the function AAS_UpdatePortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreas_r(int areanum, int clusternum) +{ + aas_area_t *area; + aas_face_t *face; + int facenum, i; + + // + if (areanum <= 0 || areanum >= aasworld.numareas) + { + AAS_Error("AAS_FloodClusterAreas_r: areanum out of range"); + return qfalse; + } //end if + //if the area is already part of a cluster + if (aasworld.areasettings[areanum].cluster > 0) + { + if (aasworld.areasettings[areanum].cluster == clusternum) return qtrue; + // + //there's a reachability going from one cluster to another only in one direction + // + AAS_Error("cluster %d touched cluster %d at area %d\r\n", + clusternum, aasworld.areasettings[areanum].cluster, areanum); + return qfalse; + } //end if + //don't add the cluster portal areas to the clusters + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + return AAS_UpdatePortal(areanum, clusternum); + } //end if + //set the area cluster number + aasworld.areasettings[areanum].cluster = clusternum; + aasworld.areasettings[areanum].clusterareanum = + aasworld.clusters[clusternum].numareas; + //the cluster has an extra area + aasworld.clusters[clusternum].numareas++; + + area = &aasworld.areas[areanum]; + //use area faces to flood into adjacent areas + if (!nofaceflood) + { + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + if (face->frontarea == areanum) + { + if (face->backarea) if (!AAS_FloodClusterAreas_r(face->backarea, clusternum)) return qfalse; + } //end if + else + { + if (face->frontarea) if (!AAS_FloodClusterAreas_r(face->frontarea, clusternum)) return qfalse; + } //end else + } //end for + } //end if + //use the reachabilities to flood into other areas + for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) + { + if (!aasworld.reachability[ + aasworld.areasettings[areanum].firstreachablearea + i].areanum) + { + continue; + } //end if + if (!AAS_FloodClusterAreas_r(aasworld.reachability[ + aasworld.areasettings[areanum].firstreachablearea + i].areanum, clusternum)) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreas_r +//=========================================================================== +// try to flood from all areas without cluster into areas with a cluster set +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FloodClusterAreasUsingReachabilities(int clusternum) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + //if this area already has a cluster set + if (aasworld.areasettings[i].cluster) + continue; + //if this area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + continue; + //loop over the reachable areas from this area + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + //the reachable area + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) + continue; + //if this area has a cluster set + if (aasworld.areasettings[areanum].cluster) + { + if (!AAS_FloodClusterAreas_r(i, clusternum)) + return qfalse; + i = 0; + break; + } //end if + } //end for + } //end for + return qtrue; +} //end of the function AAS_FloodClusterAreasUsingReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterPortals(int clusternum) +{ + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_NumberClusterAreas(int clusternum) +{ + int i, portalnum; + aas_cluster_t *cluster; + aas_portal_t *portal; + + aasworld.clusters[clusternum].numareas = 0; + aasworld.clusters[clusternum].numreachabilityareas = 0; + //number all areas in this cluster WITH reachabilities + for (i = 1; i < aasworld.numareas; i++) + { + // + if (aasworld.areasettings[i].cluster != clusternum) continue; + // + if (!AAS_AreaReachability(i)) continue; + // + aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; + //the cluster has an extra area + aasworld.clusters[clusternum].numareas++; + aasworld.clusters[clusternum].numreachabilityareas++; + } //end for + //number all portals in this cluster WITH reachabilities + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + if (!AAS_AreaReachability(portal->areanum)) continue; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + aasworld.clusters[clusternum].numreachabilityareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + aasworld.clusters[clusternum].numreachabilityareas++; + } //end else + } //end for + //number all areas in this cluster WITHOUT reachabilities + for (i = 1; i < aasworld.numareas; i++) + { + // + if (aasworld.areasettings[i].cluster != clusternum) continue; + // + if (AAS_AreaReachability(i)) continue; + // + aasworld.areasettings[i].clusterareanum = aasworld.clusters[clusternum].numareas; + //the cluster has an extra area + aasworld.clusters[clusternum].numareas++; + } //end for + //number all portals in this cluster WITHOUT reachabilities + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + if (AAS_AreaReachability(portal->areanum)) continue; + if (portal->frontcluster == clusternum) + { + portal->clusterareanum[0] = cluster->numareas++; + } //end if + else + { + portal->clusterareanum[1] = cluster->numareas++; + } //end else + } //end for +} //end of the function AAS_NumberClusterAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FindClusters(void) +{ + int i; + aas_cluster_t *cluster; + + AAS_RemoveClusterAreas(); + // + for (i = 1; i < aasworld.numareas; i++) + { + //if the area is already part of a cluster + if (aasworld.areasettings[i].cluster) + continue; + // if not flooding through faces only use areas that have reachabilities + if (nofaceflood) + { + if (!aasworld.areasettings[i].numreachableareas) + continue; + } //end if + //if the area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + continue; + if (aasworld.numclusters >= AAS_MAX_CLUSTERS) + { + AAS_Error("AAS_MAX_CLUSTERS"); + return qfalse; + } //end if + cluster = &aasworld.clusters[aasworld.numclusters]; + cluster->numareas = 0; + cluster->numreachabilityareas = 0; + cluster->firstportal = aasworld.portalindexsize; + cluster->numportals = 0; + //flood the areas in this cluster + if (!AAS_FloodClusterAreas_r(i, aasworld.numclusters)) + return qfalse; + if (!AAS_FloodClusterAreasUsingReachabilities(aasworld.numclusters)) + return qfalse; + //number the cluster areas + //AAS_NumberClusterPortals(aasworld.numclusters); + AAS_NumberClusterAreas(aasworld.numclusters); + //Log_Write("cluster %d has %d areas\r\n", aasworld.numclusters, cluster->numareas); + aasworld.numclusters++; + } //end for + return qtrue; +} //end of the function AAS_FindClusters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreatePortals(void) +{ + int i; + aas_portal_t *portal; + + for (i = 1; i < aasworld.numareas; i++) + { + //if the area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + if (aasworld.numportals >= AAS_MAX_PORTALS) + { + AAS_Error("AAS_MAX_PORTALS"); + return; + } //end if + portal = &aasworld.portals[aasworld.numportals]; + portal->areanum = i; + portal->frontcluster = 0; + portal->backcluster = 0; + aasworld.numportals++; + } //end if + } //end for +} //end of the function AAS_CreatePortals +/* +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_MapContainsTeleporters(void) +{ + bsp_entity_t *entities, *ent; + char *classname; + + entities = AAS_ParseBSPEntities(); + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (classname && !strcmp(classname, "misc_teleporter")) + { + AAS_FreeBSPEntities(entities); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_MapContainsTeleporters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NonConvexFaces(aas_face_t *face1, aas_face_t *face2, int side1, int side2) +{ + int i, j, edgenum; + aas_plane_t *plane1, *plane2; + aas_edge_t *edge; + + + plane1 = &aasworld.planes[face1->planenum ^ side1]; + plane2 = &aasworld.planes[face2->planenum ^ side2]; + + //check if one of the points of face1 is at the back of the plane of face2 + for (i = 0; i < face1->numedges; i++) + { + edgenum = abs(aasworld.edgeindex[face1->firstedge + i]); + edge = &aasworld.edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane2->normal, aasworld.vertexes[edge->v[j]]) - + plane2->dist < -0.01) return qtrue; + } //end for + } //end for + for (i = 0; i < face2->numedges; i++) + { + edgenum = abs(aasworld.edgeindex[face2->firstedge + i]); + edge = &aasworld.edges[edgenum]; + for (j = 0; j < 2; j++) + { + if (DotProduct(plane1->normal, aasworld.vertexes[edge->v[j]]) - + plane1->dist < -0.01) return qtrue; + } //end for + } //end for + + return qfalse; +} //end of the function AAS_NonConvexFaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeAreas(int *areanums, int numareas) +{ + int i, j, s, face1num, face2num, side1, side2, fn1, fn2; + aas_face_t *face1, *face2; + aas_area_t *area1, *area2; + + for (i = 0; i < numareas; i++) + { + area1 = &aasworld.areas[areanums[i]]; + for (fn1 = 0; fn1 < area1->numfaces; fn1++) + { + face1num = abs(aasworld.faceindex[area1->firstface + fn1]); + face1 = &aasworld.faces[face1num]; + side1 = face1->frontarea != areanums[i]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == i) continue; + if (face1->frontarea == s || face1->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + for (j = 0; j < numareas; j++) + { + if (j == i) continue; + area2 = &aasworld.areas[areanums[j]]; + for (fn2 = 0; fn2 < area2->numfaces; fn2++) + { + face2num = abs(aasworld.faceindex[area2->firstface + fn2]); + face2 = &aasworld.faces[face2num]; + side2 = face2->frontarea != areanums[j]; + //check if the face isn't a shared one with one of the other areas + for (s = 0; s < numareas; s++) + { + if (s == j) continue; + if (face2->frontarea == s || face2->backarea == s) break; + } //end for + //if the face was a shared one + if (s != numareas) continue; + // + if (AAS_NonConvexFaces(face1, face2, side1, side2)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_NonConvexEdges(aas_edge_t *edge1, aas_edge_t *edge2, int side1, int side2, int planenum) +{ + int i; + vec3_t edgevec1, edgevec2, normal1, normal2; + float dist1, dist2; + aas_plane_t *plane; + + plane = &aasworld.planes[planenum]; + VectorSubtract(aasworld.vertexes[edge1->v[1]], aasworld.vertexes[edge1->v[0]], edgevec1); + VectorSubtract(aasworld.vertexes[edge2->v[1]], aasworld.vertexes[edge2->v[0]], edgevec2); + if (side1) VectorInverse(edgevec1); + if (side2) VectorInverse(edgevec2); + // + CrossProduct(edgevec1, plane->normal, normal1); + dist1 = DotProduct(normal1, aasworld.vertexes[edge1->v[0]]); + CrossProduct(edgevec2, plane->normal, normal2); + dist2 = DotProduct(normal2, aasworld.vertexes[edge2->v[0]]); + + for (i = 0; i < 2; i++) + { + if (DotProduct(aasworld.vertexes[edge1->v[i]], normal2) - dist2 < -0.01) return qfalse; + } //end for + for (i = 0; i < 2; i++) + { + if (DotProduct(aasworld.vertexes[edge2->v[i]], normal1) - dist1 < -0.01) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_NonConvexEdges +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_CanMergeFaces(int *facenums, int numfaces, int planenum) +{ + int i, j, s, edgenum1, edgenum2, side1, side2, en1, en2, ens; + aas_face_t *face1, *face2, *otherface; + aas_edge_t *edge1, *edge2; + + for (i = 0; i < numfaces; i++) + { + face1 = &aasworld.faces[facenums[i]]; + for (en1 = 0; en1 < face1->numedges; en1++) + { + edgenum1 = aasworld.edgeindex[face1->firstedge + en1]; + side1 = (edgenum1 < 0) ^ (face1->planenum != planenum); + edgenum1 = abs(edgenum1); + edge1 = &aasworld.edges[edgenum1]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &aasworld.faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum1 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + for (j = 0; j < numfaces; j++) + { + if (j == i) continue; + face2 = &aasworld.faces[facenums[j]]; + for (en2 = 0; en2 < face2->numedges; en2++) + { + edgenum2 = aasworld.edgeindex[face2->firstedge + en2]; + side2 = (edgenum2 < 0) ^ (face2->planenum != planenum); + edgenum2 = abs(edgenum2); + edge2 = &aasworld.edges[edgenum2]; + //check if the edge is shared with another face + for (s = 0; s < numfaces; s++) + { + if (s == i) continue; + otherface = &aasworld.faces[facenums[s]]; + for (ens = 0; ens < otherface->numedges; ens++) + { + if (edgenum2 == abs(aasworld.edgeindex[otherface->firstedge + ens])) break; + } //end for + if (ens != otherface->numedges) break; + } //end for + //if the edge was shared + if (s != numfaces) continue; + // + if (AAS_NonConvexEdges(edge1, edge2, side1, side2, planenum)) return qfalse; + } //end for + } //end for + } //end for + } //end for + return qtrue; +} //end of the function AAS_CanMergeFaces*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ConnectedAreas_r(int *areanums, int numareas, int *connectedareas, int curarea) +{ + int i, j, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + connectedareas[curarea] = qtrue; + area = &aasworld.areas[areanums[curarea]]; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) continue; + //get the area at the other side of the face + if (face->frontarea != areanums[curarea]) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //check if the face is leading to one of the other areas + for (j = 0; j < numareas; j++) + { + if (areanums[j] == otherareanum) break; + } //end for + //if the face isn't leading to one of the other areas + if (j == numareas) continue; + //if the other area is already connected + if (connectedareas[j]) continue; + //recursively proceed with the other area + AAS_ConnectedAreas_r(areanums, numareas, connectedareas, j); + } //end for +} //end of the function AAS_ConnectedAreas_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ConnectedAreas(int *areanums, int numareas) +{ + int connectedareas[MAX_PORTALAREAS], i; + + Com_Memset(connectedareas, 0, sizeof(connectedareas)); + if (numareas < 1) return qfalse; + if (numareas == 1) return qtrue; + AAS_ConnectedAreas_r(areanums, numareas, connectedareas, 0); + for (i = 0; i < numareas; i++) + { + if (!connectedareas[i]) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_ConnectedAreas +//=========================================================================== +// gets adjacent areas with less presence types recursively +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetAdjacentAreasWithLessPresenceTypes_r(int *areanums, int numareas, int curareanum) +{ + int i, j, presencetype, otherpresencetype, otherareanum, facenum; + aas_area_t *area; + aas_face_t *face; + + areanums[numareas++] = curareanum; + area = &aasworld.areas[curareanum]; + presencetype = aasworld.areasettings[curareanum].presencetype; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) continue; + //the area at the other side of the face + if (face->frontarea != curareanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + otherpresencetype = aasworld.areasettings[otherareanum].presencetype; + //if the other area has less presence types + if ((presencetype & ~otherpresencetype) && + !(otherpresencetype & ~presencetype)) + { + //check if the other area isn't already in the list + for (j = 0; j < numareas; j++) + { + if (otherareanum == areanums[j]) break; + } //end for + //if the other area isn't already in the list + if (j == numareas) + { + if (numareas >= MAX_PORTALAREAS) + { + AAS_Error("MAX_PORTALAREAS"); + return numareas; + } //end if + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, numareas, otherareanum); + } //end if + } //end if + } //end for + return numareas; +} //end of the function AAS_GetAdjacentAreasWithLessPresenceTypes_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_CheckAreaForPossiblePortals(int areanum) +{ + int i, j, k, fen, ben, frontedgenum, backedgenum, facenum; + int areanums[MAX_PORTALAREAS], numareas, otherareanum; + int numareafrontfaces[MAX_PORTALAREAS], numareabackfaces[MAX_PORTALAREAS]; + int frontfacenums[MAX_PORTALAREAS], backfacenums[MAX_PORTALAREAS]; + int numfrontfaces, numbackfaces; + int frontareanums[MAX_PORTALAREAS], backareanums[MAX_PORTALAREAS]; + int numfrontareas, numbackareas; + int frontplanenum, backplanenum, faceplanenum; + aas_area_t *area; + aas_face_t *frontface, *backface, *face; + + //if it isn't already a portal + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; + //it must be a grounded area + if (!(aasworld.areasettings[areanum].areaflags & AREA_GROUNDED)) return 0; + // + Com_Memset(numareafrontfaces, 0, sizeof(numareafrontfaces)); + Com_Memset(numareabackfaces, 0, sizeof(numareabackfaces)); + numfrontfaces = numbackfaces = 0; + numfrontareas = numbackareas = 0; + frontplanenum = backplanenum = -1; + //add any adjacent areas with less presence types + numareas = AAS_GetAdjacentAreasWithLessPresenceTypes_r(areanums, 0, areanum); + // + for (i = 0; i < numareas; i++) + { + area = &aasworld.areas[areanums[i]]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + //if the face is solid + if (face->faceflags & FACE_SOLID) continue; + //check if the face is shared with one of the other areas + for (k = 0; k < numareas; k++) + { + if (k == i) continue; + if (face->frontarea == areanums[k] || face->backarea == areanums[k]) break; + } //end for + //if the face is shared + if (k != numareas) continue; + //the number of the area at the other side of the face + if (face->frontarea == areanums[i]) otherareanum = face->backarea; + else otherareanum = face->frontarea; + //if the other area already is a cluter portal + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) return 0; + //number of the plane of the area + faceplanenum = face->planenum & ~1; + // + if (frontplanenum < 0 || faceplanenum == frontplanenum) + { + frontplanenum = faceplanenum; + frontfacenums[numfrontfaces++] = facenum; + for (k = 0; k < numfrontareas; k++) + { + if (frontareanums[k] == otherareanum) break; + } //end for + if (k == numfrontareas) frontareanums[numfrontareas++] = otherareanum; + numareafrontfaces[i]++; + } //end if + else if (backplanenum < 0 || faceplanenum == backplanenum) + { + backplanenum = faceplanenum; + backfacenums[numbackfaces++] = facenum; + for (k = 0; k < numbackareas; k++) + { + if (backareanums[k] == otherareanum) break; + } //end for + if (k == numbackareas) backareanums[numbackareas++] = otherareanum; + numareabackfaces[i]++; + } //end else + else + { + return 0; + } //end else + } //end for + } //end for + //every area should have at least one front face and one back face + for (i = 0; i < numareas; i++) + { + if (!numareafrontfaces[i] || !numareabackfaces[i]) return 0; + } //end for + //the front areas should all be connected + if (!AAS_ConnectedAreas(frontareanums, numfrontareas)) return 0; + //the back areas should all be connected + if (!AAS_ConnectedAreas(backareanums, numbackareas)) return 0; + //none of the front faces should have a shared edge with a back face + for (i = 0; i < numfrontfaces; i++) + { + frontface = &aasworld.faces[frontfacenums[i]]; + for (fen = 0; fen < frontface->numedges; fen++) + { + frontedgenum = abs(aasworld.edgeindex[frontface->firstedge + fen]); + for (j = 0; j < numbackfaces; j++) + { + backface = &aasworld.faces[backfacenums[j]]; + for (ben = 0; ben < backface->numedges; ben++) + { + backedgenum = abs(aasworld.edgeindex[backface->firstedge + ben]); + if (frontedgenum == backedgenum) break; + } //end for + if (ben != backface->numedges) break; + } //end for + if (j != numbackfaces) break; + } //end for + if (fen != frontface->numedges) break; + } //end for + if (i != numfrontfaces) return 0; + //set the cluster portal contents + for (i = 0; i < numareas; i++) + { + aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_CLUSTERPORTAL; + //this area can be used as a route portal + aasworld.areasettings[areanums[i]].contents |= AREACONTENTS_ROUTEPORTAL; + Log_Write("possible portal: %d\r\n", areanums[i]); + } //end for + // + return numareas; +} //end of the function AAS_CheckAreaForPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FindPossiblePortals(void) +{ + int i, numpossibleportals; + + numpossibleportals = 0; + for (i = 1; i < aasworld.numareas; i++) + { + numpossibleportals += AAS_CheckAreaForPossiblePortals(i); + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d possible portal areas\n", numpossibleportals); +} //end of the function AAS_FindPossiblePortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveAllPortals(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + } //end for +} //end of the function AAS_RemoveAllPortals + +#if 0 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodCluster_r(int areanum, int clusternum) +{ + int i, otherareanum; + aas_face_t *face; + aas_area_t *area; + + //set cluster mark + aasworld.areasettings[areanum].cluster = clusternum; + //if the area is a portal + //if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) return; + // + area = &aasworld.areas[areanum]; + //use area faces to flood into adjacent areas + for (i = 0; i < area->numfaces; i++) + { + face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; + // + if (face->frontarea != areanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //if there's no area at the other side + if (!otherareanum) continue; + //if the area is a portal + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area is already marked + if (aasworld.areasettings[otherareanum].cluster) continue; + // + AAS_FloodCluster_r(otherareanum, clusternum); + } //end for + //use the reachabilities to flood into other areas + for (i = 0; i < aasworld.areasettings[areanum].numreachableareas; i++) + { + otherareanum = aasworld.reachability[ + aasworld.areasettings[areanum].firstreachablearea + i].areanum; + if (!otherareanum) + { + continue; + AAS_Error("reachability %d has zero area\n", aasworld.areasettings[areanum].firstreachablearea + i); + } //end if + //if the area is a portal + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area is already marked + if (aasworld.areasettings[otherareanum].cluster) continue; + // + AAS_FloodCluster_r(otherareanum, clusternum); + } //end for +} //end of the function AAS_FloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveTeleporterPortals(void) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype == TRAVEL_TELEPORT) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + aasworld.areasettings[areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_RemoveTeleporterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FloodClusterReachabilities(int clusternum) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + //if this area already has a cluster set + if (aasworld.areasettings[i].cluster) continue; + //if this area is a cluster portal + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //loop over the reachable areas from this area + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + //the reachable area + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + //if this area is a cluster portal + if (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if this area has a cluster set + if (aasworld.areasettings[areanum].cluster == clusternum) + { + AAS_FloodCluster_r(i, clusternum); + i = 0; + break; + } //end if + } //end for + } //end for +} //end of the function AAS_FloodClusterReachabilities + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveNotClusterClosingPortals(void) +{ + int i, j, k, facenum, otherareanum, nonclosingportals; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for (i = 1; i < aasworld.numareas; i++) + { + if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &aasworld.areas[i]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + //reset all cluster fields + AAS_RemoveClusterAreas(); + // + AAS_FloodCluster_r(otherareanum, 1); + AAS_FloodClusterReachabilities(1); + //check if all adjacent non-portal areas have a cluster set + for (k = 0; k < area->numfaces; k++) + { + facenum = abs(aasworld.faceindex[area->firstface + k]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + // + if (!aasworld.areasettings[otherareanum].cluster) break; + } //end for + //if all adjacent non-portal areas have a cluster set then the portal + //didn't seal a cluster + if (k >= area->numfaces) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + break; + } //end if + } //end for + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); +} //end of the function AAS_RemoveNotClusterClosingPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_RemoveNotClusterClosingPortals(void) +{ + int i, j, facenum, otherareanum, nonclosingportals, numseperatedclusters; + aas_area_t *area; + aas_face_t *face; + + AAS_RemoveTeleporterPortals(); + // + nonclosingportals = 0; + for (i = 1; i < aasworld.numareas; i++) + { + if (!(aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL)) continue; + // + numseperatedclusters = 0; + //reset all cluster fields + AAS_RemoveClusterAreas(); + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &aasworld.areas[i]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != i) otherareanum = face->frontarea; + else otherareanum = face->backarea; + //if not solid at the other side of the face + if (!otherareanum) continue; + //don't flood into other portals + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area already has a cluster set + if (aasworld.areasettings[otherareanum].cluster) continue; + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r(otherareanum, numseperatedclusters); + AAS_FloodClusterReachabilities(numseperatedclusters); + } //end for + //use the reachabilities to flood into other areas + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + otherareanum = aasworld.reachability[ + aasworld.areasettings[i].firstreachablearea + j].areanum; + //this should never be qtrue but we check anyway + if (!otherareanum) continue; + //don't flood into other portals + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) continue; + //if the area already has a cluster set + if (aasworld.areasettings[otherareanum].cluster) continue; + //another cluster is seperated by this portal + numseperatedclusters++; + //flood the cluster + AAS_FloodCluster_r(otherareanum, numseperatedclusters); + AAS_FloodClusterReachabilities(numseperatedclusters); + } //end for + //a portal must seperate no more and no less than 2 clusters + if (numseperatedclusters != 2) + { + aasworld.areasettings[i].contents &= ~AREACONTENTS_CLUSTERPORTAL; + nonclosingportals++; + //recheck all the other portals again + i = 0; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "\r%6d non closing portals removed\n", nonclosingportals); +} //end of the function AAS_RemoveNotClusterClosingPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +void AAS_AddTeleporterPortals(void) +{ + int j, area2num, facenum, otherareanum; + char *target, *targetname, *classname; + bsp_entity_t *entities, *ent, *dest; + vec3_t origin, destorigin, mins, maxs, end; + vec3_t bbmins, bbmaxs; + aas_area_t *area; + aas_face_t *face; + aas_trace_t trace; + aas_link_t *areas, *link; + + entities = AAS_ParseBSPEntities(); + + for (ent = entities; ent; ent = ent->next) + { + classname = AAS_ValueForBSPEpairKey(ent, "classname"); + if (classname && !strcmp(classname, "misc_teleporter")) + { + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + botimport.Print(PRT_ERROR, "teleporter (%s) without origin\n", target); + continue; + } //end if + // + target = AAS_ValueForBSPEpairKey(ent, "target"); + if (!target) + { + botimport.Print(PRT_ERROR, "teleporter (%s) without target\n", target); + continue; + } //end if + for (dest = entities; dest; dest = dest->next) + { + classname = AAS_ValueForBSPEpairKey(dest, "classname"); + if (classname && !strcmp(classname, "misc_teleporter_dest")) + { + targetname = AAS_ValueForBSPEpairKey(dest, "targetname"); + if (targetname && !strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + botimport.Print(PRT_ERROR, "teleporter without destination (%s)\n", target); + continue; + } //end if + if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); + continue; + } //end if + destorigin[2] += 24; //just for q2e1m2, the dork has put the telepads in the ground + VectorCopy(destorigin, end); + end[2] -= 100; + trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); + continue; + } //end if + VectorCopy(trace.endpos, destorigin); + area2num = AAS_PointAreaNum(destorigin); + //reset all cluster fields + for (j = 0; j < aasworld.numareas; j++) + { + aasworld.areasettings[j].cluster = 0; + } //end for + // + VectorSet(mins, -8, -8, 8); + VectorSet(maxs, 8, 8, 24); + // + AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); + // + VectorAdd(origin, mins, mins); + VectorAdd(origin, maxs, maxs); + //add bounding box size + VectorSubtract(mins, bbmaxs, mins); + VectorSubtract(maxs, bbmins, maxs); + //link an invalid (-1) entity + areas = AAS_AASLinkEntity(mins, maxs, -1); + // + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //add the teleporter portal mark + aasworld.areasettings[link->areanum].contents |= AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL; + } //end for + // + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //find a non-portal area adjacent to the portal area and flood + //the cluster from there + area = &aasworld.areas[link->areanum]; + for (j = 0; j < area->numfaces; j++) + { + facenum = abs(aasworld.faceindex[area->firstface + j]); + face = &aasworld.faces[facenum]; + // + if (face->frontarea != link->areanum) otherareanum = face->frontarea; + else otherareanum = face->backarea; + // + if (!otherareanum) continue; + // + if (aasworld.areasettings[otherareanum].contents & AREACONTENTS_CLUSTERPORTAL) + { + continue; + } //end if + // + AAS_FloodCluster_r(otherareanum, 1); + } //end for + } //end for + //if the teleport destination IS in the same cluster + if (aasworld.areasettings[area2num].cluster) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaGrounded(link->areanum)) continue; + //add the teleporter portal mark + aasworld.areasettings[link->areanum].contents &= ~(AREACONTENTS_CLUSTERPORTAL | + AREACONTENTS_TELEPORTAL); + } //end for + } //end if + } //end if + } //end for + AAS_FreeBSPEntities(entities); +} //end of the function AAS_AddTeleporterPortals + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AddTeleporterPortals(void) +{ + int i, j, areanum; + + for (i = 1; i < aasworld.numareas; i++) + { + for (j = 0; j < aasworld.areasettings[i].numreachableareas; j++) + { + if (aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].traveltype != TRAVEL_TELEPORT) continue; + areanum = aasworld.reachability[aasworld.areasettings[i].firstreachablearea + j].areanum; + aasworld.areasettings[areanum].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end for + } //end for +} //end of the function AAS_AddTeleporterPortals + +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TestPortals(void) +{ + int i; + aas_portal_t *portal; + + for (i = 1; i < aasworld.numportals; i++) + { + portal = &aasworld.portals[i]; + if (!portal->frontcluster) + { + aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d has no front cluster\r\n", portal->areanum); + return qfalse; + } //end if + if (!portal->backcluster) + { + aasworld.areasettings[portal->areanum].contents &= ~AREACONTENTS_CLUSTERPORTAL; + Log_Write("portal area %d has no back cluster\r\n", portal->areanum); + return qfalse; + } //end if + } //end for + return qtrue; +} //end of the function +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CountForcedClusterPortals(void) +{ + int num, i; + + num = 0; + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + Log_Write("area %d is a forced portal area\r\n", i); + num++; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "%6d forced portal areas\n", num); +} //end of the function AAS_CountForcedClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateViewPortals(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL) + { + aasworld.areasettings[i].contents |= AREACONTENTS_VIEWPORTAL; + } //end if + } //end for +} //end of the function AAS_CreateViewPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetViewPortalsAsClusterPortals(void) +{ + int i; + + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL) + { + aasworld.areasettings[i].contents |= AREACONTENTS_CLUSTERPORTAL; + } //end if + } //end for +} //end of the function AAS_SetViewPortalsAsClusterPortals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClustering(void) +{ + int i, removedPortalAreas; + int n, total, numreachabilityareas; + + if (!aasworld.loaded) return; + //if there are clusters + if (aasworld.numclusters >= 1) + { +#ifndef BSPC + //if clustering isn't forced + if (!((int)LibVarGetValue("forceclustering")) && + !((int)LibVarGetValue("forcereachability"))) return; +#endif + } //end if + //set all view portals as cluster portals in case we re-calculate the reachabilities and clusters (with -reach) + AAS_SetViewPortalsAsClusterPortals(); + //count the number of forced cluster portals + AAS_CountForcedClusterPortals(); + //remove all area cluster marks + AAS_RemoveClusterAreas(); + //find possible cluster portals + AAS_FindPossiblePortals(); + //craete portals to for the bot view + AAS_CreateViewPortals(); + //remove all portals that are not closing a cluster + //AAS_RemoveNotClusterClosingPortals(); + //initialize portal memory + if (aasworld.portals) FreeMemory(aasworld.portals); + aasworld.portals = (aas_portal_t *) GetClearedMemory(AAS_MAX_PORTALS * sizeof(aas_portal_t)); + //initialize portal index memory + if (aasworld.portalindex) FreeMemory(aasworld.portalindex); + aasworld.portalindex = (aas_portalindex_t *) GetClearedMemory(AAS_MAX_PORTALINDEXSIZE * sizeof(aas_portalindex_t)); + //initialize cluster memory + if (aasworld.clusters) FreeMemory(aasworld.clusters); + aasworld.clusters = (aas_cluster_t *) GetClearedMemory(AAS_MAX_CLUSTERS * sizeof(aas_cluster_t)); + // + removedPortalAreas = 0; + botimport.Print(PRT_MESSAGE, "\r%6d removed portal areas", removedPortalAreas); + while(1) + { + botimport.Print(PRT_MESSAGE, "\r%6d", removedPortalAreas); + //initialize the number of portals and clusters + aasworld.numportals = 1; //portal 0 is a dummy + aasworld.portalindexsize = 0; + aasworld.numclusters = 1; //cluster 0 is a dummy + //create the portals from the portal areas + AAS_CreatePortals(); + // + removedPortalAreas++; + //find the clusters + if (!AAS_FindClusters()) + continue; + //test the portals + if (!AAS_TestPortals()) + continue; + // + break; + } //end while + botimport.Print(PRT_MESSAGE, "\n"); + //the AAS file should be saved + aasworld.savefile = qtrue; + //write the portal areas to the log file + for (i = 1; i < aasworld.numportals; i++) + { + Log_Write("portal %d: area %d\r\n", i, aasworld.portals[i].areanum); + } //end for + // report cluster info + botimport.Print(PRT_MESSAGE, "%6d portals created\n", aasworld.numportals); + botimport.Print(PRT_MESSAGE, "%6d clusters created\n", aasworld.numclusters); + for (i = 1; i < aasworld.numclusters; i++) + { + botimport.Print(PRT_MESSAGE, "cluster %d has %d reachability areas\n", i, + aasworld.clusters[i].numreachabilityareas); + } //end for + // report AAS file efficiency + numreachabilityareas = 0; + total = 0; + for (i = 0; i < aasworld.numclusters; i++) { + n = aasworld.clusters[i].numreachabilityareas; + numreachabilityareas += n; + total += n * n; + } + total += numreachabilityareas * aasworld.numportals; + // + botimport.Print(PRT_MESSAGE, "%6i total reachability areas\n", numreachabilityareas); + botimport.Print(PRT_MESSAGE, "%6i AAS memory/CPU usage (the lower the better)\n", total * 3); +} //end of the function AAS_InitClustering diff --git a/code/botlib/be_aas_cluster.h b/code/botlib/be_aas_cluster.h new file mode 100644 index 0000000..e36697d --- /dev/null +++ b/code/botlib/be_aas_cluster.h @@ -0,0 +1,38 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_cluster.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_cluster.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize the AAS clustering +void AAS_InitClustering(void); +// +void AAS_SetViewPortalsAsClusterPortals(void); +#endif //AASINTERN + diff --git a/code/botlib/be_aas_debug.c b/code/botlib/be_aas_debug.c new file mode 100644 index 0000000..ab44bc0 --- /dev/null +++ b/code/botlib/be_aas_debug.c @@ -0,0 +1,777 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_debug.c + * + * desc: AAS debug code + * + * $Archive: /MissionPack/code/botlib/be_aas_debug.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_interface.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +#define MAX_DEBUGLINES 1024 +#define MAX_DEBUGPOLYGONS 8192 + +int debuglines[MAX_DEBUGLINES]; +int debuglinevisible[MAX_DEBUGLINES]; +int numdebuglines; + +static int debugpolygons[MAX_DEBUGPOLYGONS]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownPolygons(void) +{ + int i; +//* + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + if (debugpolygons[i]) botimport.DebugPolygonDelete(debugpolygons[i]); + debugpolygons[i] = 0; + } //end for +//*/ +/* + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + botimport.DebugPolygonDelete(i); + debugpolygons[i] = 0; + } //end for +*/ +} //end of the function AAS_ClearShownPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowPolygon(int color, int numpoints, vec3_t *points) +{ + int i; + + for (i = 0; i < MAX_DEBUGPOLYGONS; i++) + { + if (!debugpolygons[i]) + { + debugpolygons[i] = botimport.DebugPolygonCreate(color, numpoints, points); + break; + } //end if + } //end for +} //end of the function AAS_ShowPolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ClearShownDebugLines(void) +{ + int i; + + //make all lines invisible + for (i = 0; i < MAX_DEBUGLINES; i++) + { + if (debuglines[i]) + { + //botimport.DebugLineShow(debuglines[i], NULL, NULL, LINECOLOR_NONE); + botimport.DebugLineDelete(debuglines[i]); + debuglines[i] = 0; + debuglinevisible[i] = qfalse; + } //end if + } //end for +} //end of the function AAS_ClearShownDebugLines +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DebugLine(vec3_t start, vec3_t end, int color) +{ + int line; + + for (line = 0; line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if (!debuglinevisible[line]) + { + botimport.DebugLineShow(debuglines[line], start, end, color); + debuglinevisible[line] = qtrue; + return; + } //end else + } //end for +} //end of the function AAS_DebugLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PermanentLine(vec3_t start, vec3_t end, int color) +{ + int line; + + line = botimport.DebugLineCreate(); + botimport.DebugLineShow(line, start, end, color); +} //end of the function AAS_PermenentLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPermanentCross(vec3_t origin, float size, int color) +{ + int i, debugline; + vec3_t start, end; + + for (i = 0; i < 3; i++) + { + VectorCopy(origin, start); + start[i] += size; + VectorCopy(origin, end); + end[i] -= size; + AAS_DebugLine(start, end, color); + debugline = botimport.DebugLineCreate(); + botimport.DebugLineShow(debugline, start, end, color); + } //end for +} //end of the function AAS_DrawPermanentCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color) +{ + int n0, n1, n2, j, line, lines[2]; + vec3_t start1, end1, start2, end2; + + //make a cross in the hit plane at the hit point + VectorCopy(point, start1); + VectorCopy(point, end1); + VectorCopy(point, start2); + VectorCopy(point, end2); + + n0 = type % 3; + n1 = (type + 1) % 3; + n2 = (type + 2) % 3; + start1[n1] -= 6; + start1[n2] -= 6; + end1[n1] += 6; + end1[n2] += 6; + start2[n1] += 6; + start2[n2] -= 6; + end2[n1] -= 6; + end2[n2] += 6; + + start1[n0] = (dist - (start1[n1] * normal[n1] + + start1[n2] * normal[n2])) / normal[n0]; + end1[n0] = (dist - (end1[n1] * normal[n1] + + end1[n2] * normal[n2])) / normal[n0]; + start2[n0] = (dist - (start2[n1] * normal[n1] + + start2[n2] * normal[n2])) / normal[n0]; + end2[n0] = (dist - (end2[n1] * normal[n1] + + end2[n2] * normal[n2])) / normal[n0]; + + for (j = 0, line = 0; j < 2 && line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if (!debuglinevisible[line]) + { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + botimport.DebugLineShow(lines[0], start1, end1, color); + botimport.DebugLineShow(lines[1], start2, end2, color); +} //end of the function AAS_DrawPlaneCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + vec3_t bboxcorners[8]; + int lines[3]; + int i, j, line; + + //upper corners + bboxcorners[0][0] = origin[0] + maxs[0]; + bboxcorners[0][1] = origin[1] + maxs[1]; + bboxcorners[0][2] = origin[2] + maxs[2]; + // + bboxcorners[1][0] = origin[0] + mins[0]; + bboxcorners[1][1] = origin[1] + maxs[1]; + bboxcorners[1][2] = origin[2] + maxs[2]; + // + bboxcorners[2][0] = origin[0] + mins[0]; + bboxcorners[2][1] = origin[1] + mins[1]; + bboxcorners[2][2] = origin[2] + maxs[2]; + // + bboxcorners[3][0] = origin[0] + maxs[0]; + bboxcorners[3][1] = origin[1] + mins[1]; + bboxcorners[3][2] = origin[2] + maxs[2]; + //lower corners + Com_Memcpy(bboxcorners[4], bboxcorners[0], sizeof(vec3_t) * 4); + for (i = 0; i < 4; i++) bboxcorners[4 + i][2] = origin[2] + mins[2]; + //draw bounding box + for (i = 0; i < 4; i++) + { + for (j = 0, line = 0; j < 3 && line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + numdebuglines++; + } //end if + else if (!debuglinevisible[line]) + { + lines[j++] = debuglines[line]; + debuglinevisible[line] = qtrue; + } //end else + } //end for + //top plane + botimport.DebugLineShow(lines[0], bboxcorners[i], + bboxcorners[(i+1)&3], LINECOLOR_RED); + //bottom plane + botimport.DebugLineShow(lines[1], bboxcorners[4+i], + bboxcorners[4+((i+1)&3)], LINECOLOR_RED); + //vertical lines + botimport.DebugLineShow(lines[2], bboxcorners[i], + bboxcorners[4+i], LINECOLOR_RED); + } //end for +} //end of the function AAS_ShowBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFace(int facenum) +{ + int i, color, edgenum; + aas_edge_t *edge; + aas_face_t *face; + aas_plane_t *plane; + vec3_t start, end; + + color = LINECOLOR_YELLOW; + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //walk through the edges of the face + for (i = 0; i < face->numedges; i++) + { + //edge number + edgenum = abs(aasworld.edgeindex[face->firstedge + i]); + //check if edge number is in range + if (edgenum >= aasworld.numedges) + { + botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); + } //end if + edge = &aasworld.edges[edgenum]; + if (color == LINECOLOR_RED) color = LINECOLOR_GREEN; + else if (color == LINECOLOR_GREEN) color = LINECOLOR_BLUE; + else if (color == LINECOLOR_BLUE) color = LINECOLOR_YELLOW; + else color = LINECOLOR_RED; + AAS_DebugLine(aasworld.vertexes[edge->v[0]], + aasworld.vertexes[edge->v[1]], + color); + } //end for + plane = &aasworld.planes[face->planenum]; + edgenum = abs(aasworld.edgeindex[face->firstedge]); + edge = &aasworld.edges[edgenum]; + VectorCopy(aasworld.vertexes[edge->v[0]], start); + VectorMA(start, 20, plane->normal, end); + AAS_DebugLine(start, end, LINECOLOR_RED); +} //end of the function AAS_ShowFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowFacePolygon(int facenum, int color, int flip) +{ + int i, edgenum, numpoints; + vec3_t points[128]; + aas_edge_t *edge; + aas_face_t *face; + + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //walk through the edges of the face + numpoints = 0; + if (flip) + { + for (i = face->numedges-1; i >= 0; i--) + { + //edge number + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); + numpoints++; + } //end for + } //end if + else + { + for (i = 0; i < face->numedges; i++) + { + //edge number + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + VectorCopy(aasworld.vertexes[edge->v[edgenum < 0]], points[numpoints]); + numpoints++; + } //end for + } //end else + AAS_ShowPolygon(color, numpoints, points); +} //end of the function AAS_ShowFacePolygon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowArea(int areanum, int groundfacesonly) +{ + int areaedges[MAX_DEBUGLINES]; + int numareaedges, i, j, n, color = 0, line; + int facenum, edgenum; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + + // + numareaedges = 0; + // + if (areanum < 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", + areanum, aasworld.numareas); + return; + } //end if + //pointer to the convex area + area = &aasworld.areas[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //ground faces only + if (groundfacesonly) + { + if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; + } //end if + //walk through the edges of the face + for (j = 0; j < face->numedges; j++) + { + //edge number + edgenum = abs(aasworld.edgeindex[face->firstedge + j]); + //check if edge number is in range + if (edgenum >= aasworld.numedges) + { + botimport.Print(PRT_ERROR, "edgenum %d out of range\n", edgenum); + } //end if + //check if the edge is stored already + for (n = 0; n < numareaedges; n++) + { + if (areaedges[n] == edgenum) break; + } //end for + if (n == numareaedges && numareaedges < MAX_DEBUGLINES) + { + areaedges[numareaedges++] = edgenum; + } //end if + } //end for + //AAS_ShowFace(facenum); + } //end for + //draw all the edges + for (n = 0; n < numareaedges; n++) + { + for (line = 0; line < MAX_DEBUGLINES; line++) + { + if (!debuglines[line]) + { + debuglines[line] = botimport.DebugLineCreate(); + debuglinevisible[line] = qfalse; + numdebuglines++; + } //end if + if (!debuglinevisible[line]) + { + break; + } //end else + } //end for + if (line >= MAX_DEBUGLINES) return; + edge = &aasworld.edges[areaedges[n]]; + if (color == LINECOLOR_RED) color = LINECOLOR_BLUE; + else if (color == LINECOLOR_BLUE) color = LINECOLOR_GREEN; + else if (color == LINECOLOR_GREEN) color = LINECOLOR_YELLOW; + else color = LINECOLOR_RED; + botimport.DebugLineShow(debuglines[line], + aasworld.vertexes[edge->v[0]], + aasworld.vertexes[edge->v[1]], + color); + debuglinevisible[line] = qtrue; + } //end for*/ +} //end of the function AAS_ShowArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly) +{ + int i, facenum; + aas_area_t *area; + aas_face_t *face; + + // + if (areanum < 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "area %d out of range [0, %d]\n", + areanum, aasworld.numareas); + return; + } //end if + //pointer to the convex area + area = &aasworld.areas[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + //check if face number is in range + if (facenum >= aasworld.numfaces) + { + botimport.Print(PRT_ERROR, "facenum %d out of range\n", facenum); + } //end if + face = &aasworld.faces[facenum]; + //ground faces only + if (groundfacesonly) + { + if (!(face->faceflags & (FACE_GROUND | FACE_LADDER))) continue; + } //end if + AAS_ShowFacePolygon(facenum, color, face->frontarea != areanum); + } //end for +} //end of the function AAS_ShowAreaPolygons +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawCross(vec3_t origin, float size, int color) +{ + int i; + vec3_t start, end; + + for (i = 0; i < 3; i++) + { + VectorCopy(origin, start); + start[i] += size; + VectorCopy(origin, end); + end[i] -= size; + AAS_DebugLine(start, end, color); + } //end for +} //end of the function AAS_DrawCross +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PrintTravelType(int traveltype) +{ +#ifdef DEBUG + char *str; + // + switch(traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_INVALID: str = "TRAVEL_INVALID"; break; + case TRAVEL_WALK: str = "TRAVEL_WALK"; break; + case TRAVEL_CROUCH: str = "TRAVEL_CROUCH"; break; + case TRAVEL_BARRIERJUMP: str = "TRAVEL_BARRIERJUMP"; break; + case TRAVEL_JUMP: str = "TRAVEL_JUMP"; break; + case TRAVEL_LADDER: str = "TRAVEL_LADDER"; break; + case TRAVEL_WALKOFFLEDGE: str = "TRAVEL_WALKOFFLEDGE"; break; + case TRAVEL_SWIM: str = "TRAVEL_SWIM"; break; + case TRAVEL_WATERJUMP: str = "TRAVEL_WATERJUMP"; break; + case TRAVEL_TELEPORT: str = "TRAVEL_TELEPORT"; break; + case TRAVEL_ELEVATOR: str = "TRAVEL_ELEVATOR"; break; + case TRAVEL_ROCKETJUMP: str = "TRAVEL_ROCKETJUMP"; break; + case TRAVEL_BFGJUMP: str = "TRAVEL_BFGJUMP"; break; + case TRAVEL_GRAPPLEHOOK: str = "TRAVEL_GRAPPLEHOOK"; break; + case TRAVEL_JUMPPAD: str = "TRAVEL_JUMPPAD"; break; + case TRAVEL_FUNCBOB: str = "TRAVEL_FUNCBOB"; break; + default: str = "UNKNOWN TRAVEL TYPE"; break; + } //end switch + botimport.Print(PRT_MESSAGE, "%s", str); +#endif +} //end of the function AAS_PrintTravelType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor) +{ + vec3_t dir, cross, p1, p2, up = {0, 0, 1}; + float dot; + + VectorSubtract(end, start, dir); + VectorNormalize(dir); + dot = DotProduct(dir, up); + if (dot > 0.99 || dot < -0.99) VectorSet(cross, 1, 0, 0); + else CrossProduct(dir, up, cross); + + VectorMA(end, -6, dir, p1); + VectorCopy(p1, p2); + VectorMA(p1, 6, cross, p1); + VectorMA(p2, -6, cross, p2); + + AAS_DebugLine(start, end, linecolor); + AAS_DebugLine(p1, end, arrowcolor); + AAS_DebugLine(p2, end, arrowcolor); +} //end of the function AAS_DrawArrow +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachability(aas_reachability_t *reach) +{ + vec3_t dir, cmdmove, velocity; + float speed, zvel; + aas_clientmove_t move; + + AAS_ShowAreaPolygons(reach->areanum, 5, qtrue); + //AAS_ShowArea(reach->areanum, qtrue); + AAS_DrawArrow(reach->start, reach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); + // + if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP || + (reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) + { + AAS_HorizontalVelocityForJump(aassettings.phys_jumpvel, reach->start, reach->end, &speed); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //set the velocity + VectorScale(dir, speed, velocity); + //set the command movement + VectorClear(cmdmove); + cmdmove[2] = aassettings.phys_jumpvel; + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1f, + SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE, 0, qtrue); + // + if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) + { + AAS_JumpReachRunStart(reach, dir); + AAS_DrawCross(dir, 4, LINECOLOR_BLUE); + } //end if + } //end if + else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) + { + zvel = AAS_RocketJumpZVelocity(reach->start); + AAS_HorizontalVelocityForJump(zvel, reach->start, reach->end, &speed); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //get command movement + VectorScale(dir, speed, cmdmove); + VectorSet(velocity, 0, 0, zvel); + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); + } //end else if + else if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) + { + VectorSet(cmdmove, 0, 0, 0); + // + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + VectorNormalize(dir); + //set the velocity + //NOTE: the edgenum is the horizontal velocity + VectorScale(dir, reach->edgenum, velocity); + //NOTE: the facenum is the Z velocity + velocity[2] = reach->facenum; + // + AAS_PredictClientMovement(&move, -1, reach->start, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_HITGROUNDAREA, reach->areanum, qtrue); + } //end else if +} //end of the function AAS_ShowReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShowReachableAreas(int areanum) +{ + aas_areasettings_t *settings; + static aas_reachability_t reach; + static int index, lastareanum; + static float lasttime; + + if (areanum != lastareanum) + { + index = 0; + lastareanum = areanum; + } //end if + settings = &aasworld.areasettings[areanum]; + // + if (!settings->numreachableareas) return; + // + if (index >= settings->numreachableareas) index = 0; + // + if (AAS_Time() - lasttime > 1.5) + { + Com_Memcpy(&reach, &aasworld.reachability[settings->firstreachablearea + index], sizeof(aas_reachability_t)); + index++; + lasttime = AAS_Time(); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + AAS_ShowReachability(&reach); +} //end of the function ShowReachableAreas + +void AAS_FloodAreas_r(int areanum, int cluster, int *done) +{ + int nextareanum, i, facenum; + aas_area_t *area; + aas_face_t *face; + aas_areasettings_t *settings; + aas_reachability_t *reach; + + AAS_ShowAreaPolygons(areanum, 1, qtrue); + //pointer to the convex area + area = &aasworld.areas[areanum]; + settings = &aasworld.areasettings[areanum]; + //walk through the faces of the area + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + if (face->frontarea == areanum) + nextareanum = face->backarea; + else + nextareanum = face->frontarea; + if (!nextareanum) + continue; + if (done[nextareanum]) + continue; + done[nextareanum] = qtrue; + if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) + continue; + if (AAS_AreaCluster(nextareanum) != cluster) + continue; + AAS_FloodAreas_r(nextareanum, cluster, done); + } //end for + // + for (i = 0; i < settings->numreachableareas; i++) + { + reach = &aasworld.reachability[settings->firstreachablearea + i]; + nextareanum = reach->areanum; + if (!nextareanum) + continue; + if (done[nextareanum]) + continue; + done[nextareanum] = qtrue; + if (aasworld.areasettings[nextareanum].contents & AREACONTENTS_VIEWPORTAL) + continue; + if (AAS_AreaCluster(nextareanum) != cluster) + continue; + /* + if ((reach->traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE) + { + AAS_DebugLine(reach->start, reach->end, 1); + } + */ + AAS_FloodAreas_r(nextareanum, cluster, done); + } +} + +void AAS_FloodAreas(vec3_t origin) +{ + int areanum, cluster, *done; + + done = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); + areanum = AAS_PointAreaNum(origin); + cluster = AAS_AreaCluster(areanum); + AAS_FloodAreas_r(areanum, cluster, done); +} diff --git a/code/botlib/be_aas_debug.h b/code/botlib/be_aas_debug.h new file mode 100644 index 0000000..008eeba --- /dev/null +++ b/code/botlib/be_aas_debug.h @@ -0,0 +1,62 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_debug.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_debug.h $ + * + *****************************************************************************/ + +//clear the shown debug lines +void AAS_ClearShownDebugLines(void); +// +void AAS_ClearShownPolygons(void); +//show a debug line +void AAS_DebugLine(vec3_t start, vec3_t end, int color); +//show a permenent line +void AAS_PermanentLine(vec3_t start, vec3_t end, int color); +//show a permanent cross +void AAS_DrawPermanentCross(vec3_t origin, float size, int color); +//draw a cross in the plane +void AAS_DrawPlaneCross(vec3_t point, vec3_t normal, float dist, int type, int color); +//show a bounding box +void AAS_ShowBoundingBox(vec3_t origin, vec3_t mins, vec3_t maxs); +//show a face +void AAS_ShowFace(int facenum); +//show an area +void AAS_ShowArea(int areanum, int groundfacesonly); +// +void AAS_ShowAreaPolygons(int areanum, int color, int groundfacesonly); +//draw a cros +void AAS_DrawCross(vec3_t origin, float size, int color); +//print the travel type +void AAS_PrintTravelType(int traveltype); +//draw an arrow +void AAS_DrawArrow(vec3_t start, vec3_t end, int linecolor, int arrowcolor); +//visualize the given reachability +void AAS_ShowReachability(struct aas_reachability_s *reach); +//show the reachable areas from the given area +void AAS_ShowReachableAreas(int areanum); + diff --git a/code/botlib/be_aas_def.h b/code/botlib/be_aas_def.h new file mode 100644 index 0000000..4f5655b --- /dev/null +++ b/code/botlib/be_aas_def.h @@ -0,0 +1,303 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_def.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_def.h $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" + +//debugging on +#define AAS_DEBUG + +#define CS_SCORES 32 +#define CS_MODELS (CS_SCORES+MAX_CLIENTS) +#define CS_SOUNDS (CS_MODELS+MAX_MODELS) + +#define DF_AASENTNUMBER(x) (x - aasworld.entities) +#define DF_NUMBERAASENT(x) (&aasworld.entities[x]) +#define DF_AASENTCLIENT(x) (x - aasworld.entities - 1) +#define DF_CLIENTAASENT(x) (&aasworld.entities[x + 1]) + +#ifndef MAX_PATH + #define MAX_PATH MAX_QPATH +#endif + +//string index (for model, sound and image index) +typedef struct aas_stringindex_s +{ + int numindexes; + char **index; +} aas_stringindex_t; + +//structure to link entities to areas and areas to entities +typedef struct aas_link_s +{ + int entnum; + int areanum; + struct aas_link_s *next_ent, *prev_ent; + struct aas_link_s *next_area, *prev_area; +} aas_link_t; + +//structure to link entities to leaves and leaves to entities +typedef struct bsp_link_s +{ + int entnum; + int leafnum; + struct bsp_link_s *next_ent, *prev_ent; + struct bsp_link_s *next_leaf, *prev_leaf; +} bsp_link_t; + +typedef struct bsp_entdata_s +{ + vec3_t origin; + vec3_t angles; + vec3_t absmins; + vec3_t absmaxs; + int solid; + int modelnum; +} bsp_entdata_t; + +//entity +typedef struct aas_entity_s +{ + //entity info + aas_entityinfo_t i; + //links into the AAS areas + aas_link_t *areas; + //links into the BSP leaves + bsp_link_t *leaves; +} aas_entity_t; + +typedef struct aas_settings_s +{ + vec3_t phys_gravitydirection; + float phys_friction; + float phys_stopspeed; + float phys_gravity; + float phys_waterfriction; + float phys_watergravity; + float phys_maxvelocity; + float phys_maxwalkvelocity; + float phys_maxcrouchvelocity; + float phys_maxswimvelocity; + float phys_walkaccelerate; + float phys_airaccelerate; + float phys_swimaccelerate; + float phys_maxstep; + float phys_maxsteepness; + float phys_maxwaterjump; + float phys_maxbarrier; + float phys_jumpvel; + float phys_falldelta5; + float phys_falldelta10; + float rs_waterjump; + float rs_teleport; + float rs_barrierjump; + float rs_startcrouch; + float rs_startgrapple; + float rs_startwalkoffledge; + float rs_startjump; + float rs_rocketjump; + float rs_bfgjump; + float rs_jumppad; + float rs_aircontrolledjumppad; + float rs_funcbob; + float rs_startelevator; + float rs_falldamage5; + float rs_falldamage10; + float rs_maxfallheight; + float rs_maxjumpfallheight; +} aas_settings_t; + +#define CACHETYPE_PORTAL 0 +#define CACHETYPE_AREA 1 + +//routing cache +typedef struct aas_routingcache_s +{ + byte type; //portal or area cache + float time; //last time accessed or updated + int size; //size of the routing cache + int cluster; //cluster the cache is for + int areanum; //area the cache is created for + vec3_t origin; //origin within the area + float starttraveltime; //travel time to start with + int travelflags; //combinations of the travel flags + struct aas_routingcache_s *prev, *next; + struct aas_routingcache_s *time_prev, *time_next; + unsigned char *reachabilities; //reachabilities used for routing + unsigned short int traveltimes[1]; //travel time for every area (variable sized) +} aas_routingcache_t; + +//fields for the routing algorithm +typedef struct aas_routingupdate_s +{ + int cluster; + int areanum; //area number of the update + vec3_t start; //start point the area was entered + unsigned short int tmptraveltime; //temporary travel time + unsigned short int *areatraveltimes; //travel times within the area + qboolean inlist; //true if the update is in the list + struct aas_routingupdate_s *next; + struct aas_routingupdate_s *prev; +} aas_routingupdate_t; + +//reversed reachability link +typedef struct aas_reversedlink_s +{ + int linknum; //the aas_areareachability_t + int areanum; //reachable from this area + struct aas_reversedlink_s *next; //next link +} aas_reversedlink_t; + +//reversed area reachability +typedef struct aas_reversedreachability_s +{ + int numlinks; + aas_reversedlink_t *first; +} aas_reversedreachability_t; + +//areas a reachability goes through +typedef struct aas_reachabilityareas_s +{ + int firstarea, numareas; +} aas_reachabilityareas_t; + +typedef struct aas_s +{ + int loaded; //true when an AAS file is loaded + int initialized; //true when AAS has been initialized + int savefile; //set true when file should be saved + int bspchecksum; + //current time + float time; + int numframes; + //name of the aas file + char filename[MAX_PATH]; + char mapname[MAX_PATH]; + //bounding boxes + int numbboxes; + aas_bbox_t *bboxes; + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //planes + int numplanes; + aas_plane_t *planes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + //convex area settings + int numareasettings; + aas_areasettings_t *areasettings; + //reachablity list + int reachabilitysize; + aas_reachability_t *reachability; + //nodes of the bsp tree + int numnodes; + aas_node_t *nodes; + //cluster portals + int numportals; + aas_portal_t *portals; + //cluster portal index + int portalindexsize; + aas_portalindex_t *portalindex; + //clusters + int numclusters; + aas_cluster_t *clusters; + // + int numreachabilityareas; + float reachabilitytime; + //enities linked in the areas + aas_link_t *linkheap; //heap with link structures + int linkheapsize; //size of the link heap + aas_link_t *freelinks; //first free link + aas_link_t **arealinkedentities; //entities linked into areas + //entities + int maxentities; + int maxclients; + aas_entity_t *entities; + //string indexes + char *configstrings[MAX_CONFIGSTRINGS]; + int indexessetup; + //index to retrieve travel flag for a travel type + int travelflagfortype[MAX_TRAVELTYPES]; + //travel flags for each area based on contents + int *areacontentstravelflags; + //routing update + aas_routingupdate_t *areaupdate; + aas_routingupdate_t *portalupdate; + //number of routing updates during a frame (reset every frame) + int frameroutingupdates; + //reversed reachability links + aas_reversedreachability_t *reversedreachability; + //travel times within the areas + unsigned short ***areatraveltimes; + //array of size numclusters with cluster cache + aas_routingcache_t ***clusterareacache; + aas_routingcache_t **portalcache; + //cache list sorted on time + aas_routingcache_t *oldestcache; // start of cache list sorted on time + aas_routingcache_t *newestcache; // end of cache list sorted on time + //maximum travel time through portal areas + int *portalmaxtraveltimes; + //areas the reachabilities go through + int *reachabilityareaindex; + aas_reachabilityareas_t *reachabilityareas; +} aas_t; + +#define AASINTERN + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +#endif //BSPCINCLUDE diff --git a/code/botlib/be_aas_entity.c b/code/botlib/be_aas_entity.c new file mode 100644 index 0000000..02699bd --- /dev/null +++ b/code/botlib/be_aas_entity.c @@ -0,0 +1,437 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_entity.c + * + * desc: AAS entities + * + * $Archive: /MissionPack/code/botlib/be_aas_entity.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define MASK_SOLID CONTENTS_PLAYERCLIP + +//FIXME: these might change +enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_MOVER +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_UpdateEntity(int entnum, bot_entitystate_t *state) +{ + int relink; + aas_entity_t *ent; + vec3_t absmins, absmaxs; + + if (!aasworld.loaded) + { + botimport.Print(PRT_MESSAGE, "AAS_UpdateEntity: not loaded\n"); + return BLERR_NOAASFILE; + } //end if + + ent = &aasworld.entities[entnum]; + + if (!state) { + //unlink the entity + AAS_UnlinkFromAreas(ent->areas); + //unlink the entity from the BSP leaves + AAS_UnlinkFromBSPLeaves(ent->leaves); + // + ent->areas = NULL; + // + ent->leaves = NULL; + return BLERR_NOERROR; + } + + ent->i.update_time = AAS_Time() - ent->i.ltime; + ent->i.type = state->type; + ent->i.flags = state->flags; + ent->i.ltime = AAS_Time(); + VectorCopy(ent->i.origin, ent->i.lastvisorigin); + VectorCopy(state->old_origin, ent->i.old_origin); + ent->i.solid = state->solid; + ent->i.groundent = state->groundent; + ent->i.modelindex = state->modelindex; + ent->i.modelindex2 = state->modelindex2; + ent->i.frame = state->frame; + ent->i.event = state->event; + ent->i.eventParm = state->eventParm; + ent->i.powerups = state->powerups; + ent->i.weapon = state->weapon; + ent->i.legsAnim = state->legsAnim; + ent->i.torsoAnim = state->torsoAnim; + //number of the entity + ent->i.number = entnum; + //updated so set valid flag + ent->i.valid = qtrue; + //link everything the first frame + if (aasworld.numframes == 1) relink = qtrue; + else relink = qfalse; + // + if (ent->i.solid == SOLID_BSP) + { + //if the angles of the model changed + if (!VectorCompare(state->angles, ent->i.angles)) + { + VectorCopy(state->angles, ent->i.angles); + relink = qtrue; + } //end if + //get the mins and maxs of the model + //FIXME: rotate mins and maxs + AAS_BSPModelMinsMaxsOrigin(ent->i.modelindex, ent->i.angles, ent->i.mins, ent->i.maxs, NULL); + } //end if + else if (ent->i.solid == SOLID_BBOX) + { + //if the bounding box size changed + if (!VectorCompare(state->mins, ent->i.mins) || + !VectorCompare(state->maxs, ent->i.maxs)) + { + VectorCopy(state->mins, ent->i.mins); + VectorCopy(state->maxs, ent->i.maxs); + relink = qtrue; + } //end if + VectorCopy(state->angles, ent->i.angles); + } //end if + //if the origin changed + if (!VectorCompare(state->origin, ent->i.origin)) + { + VectorCopy(state->origin, ent->i.origin); + relink = qtrue; + } //end if + //if the entity should be relinked + if (relink) + { + //don't link the world model + if (entnum != ENTITYNUM_WORLD) + { + //absolute mins and maxs + VectorAdd(ent->i.mins, ent->i.origin, absmins); + VectorAdd(ent->i.maxs, ent->i.origin, absmaxs); + //unlink the entity + AAS_UnlinkFromAreas(ent->areas); + //relink the entity to the AAS areas (use the larges bbox) + ent->areas = AAS_LinkEntityClientBBox(absmins, absmaxs, entnum, PRESENCE_NORMAL); + //unlink the entity from the BSP leaves + AAS_UnlinkFromBSPLeaves(ent->leaves); + //link the entity to the world BSP tree + ent->leaves = AAS_BSPLinkEntity(absmins, absmaxs, entnum, 0); + } //end if + } //end if + return BLERR_NOERROR; +} //end of the function AAS_UpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityInfo(int entnum, aas_entityinfo_t *info) +{ + if (!aasworld.initialized) + { + botimport.Print(PRT_FATAL, "AAS_EntityInfo: aasworld not initialized\n"); + Com_Memset(info, 0, sizeof(aas_entityinfo_t)); + return; + } //end if + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityInfo: entnum %d out of range\n", entnum); + Com_Memset(info, 0, sizeof(aas_entityinfo_t)); + return; + } //end if + + Com_Memcpy(info, &aasworld.entities[entnum].i, sizeof(aas_entityinfo_t)); +} //end of the function AAS_EntityInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityOrigin(int entnum, vec3_t origin) +{ + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityOrigin: entnum %d out of range\n", entnum); + VectorClear(origin); + return; + } //end if + + VectorCopy(aasworld.entities[entnum].i.origin, origin); +} //end of the function AAS_EntityOrigin +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelindex(int entnum) +{ + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityModelindex: entnum %d out of range\n", entnum); + return 0; + } //end if + return aasworld.entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelindex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityType(int entnum) +{ + if (!aasworld.initialized) return 0; + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityType: entnum %d out of range\n", entnum); + return 0; + } //end if + return aasworld.entities[entnum].i.type; +} //end of the AAS_EntityType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EntityModelNum(int entnum) +{ + if (!aasworld.initialized) return 0; + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntityModelNum: entnum %d out of range\n", entnum); + return 0; + } //end if + return aasworld.entities[entnum].i.modelindex; +} //end of the function AAS_EntityModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin) +{ + int i; + aas_entity_t *ent; + + for (i = 0; i < aasworld.maxentities; i++) + { + ent = &aasworld.entities[i]; + if (ent->i.type == ET_MOVER) + { + if (ent->i.modelindex == modelnum) + { + VectorCopy(ent->i.origin, origin); + return qtrue; + } //end if + } //end if + } //end for + return qfalse; +} //end of the function AAS_OriginOfMoverWithModelNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs) +{ + aas_entity_t *ent; + + if (!aasworld.initialized) return; + + if (entnum < 0 || entnum >= aasworld.maxentities) + { + botimport.Print(PRT_FATAL, "AAS_EntitySize: entnum %d out of range\n", entnum); + return; + } //end if + + ent = &aasworld.entities[entnum]; + VectorCopy(ent->i.mins, mins); + VectorCopy(ent->i.maxs, maxs); +} //end of the function AAS_EntitySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata) +{ + aas_entity_t *ent; + + ent = &aasworld.entities[entnum]; + VectorCopy(ent->i.origin, entdata->origin); + VectorCopy(ent->i.angles, entdata->angles); + VectorAdd(ent->i.origin, ent->i.mins, entdata->absmins); + VectorAdd(ent->i.origin, ent->i.maxs, entdata->absmaxs); + entdata->solid = ent->i.solid; + entdata->modelnum = ent->i.modelindex - 1; +} //end of the function AAS_EntityBSPData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ResetEntityLinks(void) +{ + int i; + for (i = 0; i < aasworld.maxentities; i++) + { + aasworld.entities[i].areas = NULL; + aasworld.entities[i].leaves = NULL; + } //end for +} //end of the function AAS_ResetEntityLinks +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InvalidateEntities(void) +{ + int i; + for (i = 0; i < aasworld.maxentities; i++) + { + aasworld.entities[i].i.valid = qfalse; + aasworld.entities[i].i.number = i; + } //end for +} //end of the function AAS_InvalidateEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkInvalidEntities(void) +{ + int i; + aas_entity_t *ent; + + for (i = 0; i < aasworld.maxentities; i++) + { + ent = &aasworld.entities[i]; + if (!ent->i.valid) + { + AAS_UnlinkFromAreas( ent->areas ); + ent->areas = NULL; + AAS_UnlinkFromBSPLeaves( ent->leaves ); + ent->leaves = NULL; + } //end for + } //end for +} //end of the function AAS_UnlinkInvalidEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearestEntity(vec3_t origin, int modelindex) +{ + int i, bestentnum; + float dist, bestdist; + aas_entity_t *ent; + vec3_t dir; + + bestentnum = 0; + bestdist = 99999; + for (i = 0; i < aasworld.maxentities; i++) + { + ent = &aasworld.entities[i]; + if (ent->i.modelindex != modelindex) continue; + VectorSubtract(ent->i.origin, origin, dir); + if (abs(dir[0]) < 40) + { + if (abs(dir[1]) < 40) + { + dist = VectorLength(dir); + if (dist < bestdist) + { + bestdist = dist; + bestentnum = i; + } //end if + } //end if + } //end if + } //end for + return bestentnum; +} //end of the function AAS_NearestEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableEntityArea(int entnum) +{ + aas_entity_t *ent; + + ent = &aasworld.entities[entnum]; + return AAS_BestReachableLinkArea(ent->areas); +} //end of the function AAS_BestReachableEntityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextEntity(int entnum) +{ + if (!aasworld.loaded) return 0; + + if (entnum < 0) entnum = -1; + while(++entnum < aasworld.maxentities) + { + if (aasworld.entities[entnum].i.valid) return entnum; + } //end while + return 0; +} //end of the function AAS_NextEntity diff --git a/code/botlib/be_aas_entity.h b/code/botlib/be_aas_entity.h new file mode 100644 index 0000000..01ec54c --- /dev/null +++ b/code/botlib/be_aas_entity.h @@ -0,0 +1,63 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_entity.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_entity.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//invalidates all entity infos +void AAS_InvalidateEntities(void); +//unlink not updated entities +void AAS_UnlinkInvalidEntities(void); +//resets the entity AAS and BSP links (sets areas and leaves pointers to NULL) +void AAS_ResetEntityLinks(void); +//updates an entity +int AAS_UpdateEntity(int ent, bot_entitystate_t *state); +//gives the entity data used for collision detection +void AAS_EntityBSPData(int entnum, bsp_entdata_t *entdata); +#endif //AASINTERN + +//returns the size of the entity bounding box in mins and maxs +void AAS_EntitySize(int entnum, vec3_t mins, vec3_t maxs); +//returns the BSP model number of the entity +int AAS_EntityModelNum(int entnum); +//returns the origin of an entity with the given model number +int AAS_OriginOfMoverWithModelNum(int modelnum, vec3_t origin); +//returns the best reachable area the entity is situated in +int AAS_BestReachableEntityArea(int entnum); +//returns the info of the given entity +void AAS_EntityInfo(int entnum, aas_entityinfo_t *info); +//returns the next entity +int AAS_NextEntity(int entnum); +//returns the origin of the entity +void AAS_EntityOrigin(int entnum, vec3_t origin); +//returns the entity type +int AAS_EntityType(int entnum); +//returns the model index of the entity +int AAS_EntityModelindex(int entnum); + diff --git a/code/botlib/be_aas_file.c b/code/botlib/be_aas_file.c new file mode 100644 index 0000000..9395b1d --- /dev/null +++ b/code/botlib/be_aas_file.c @@ -0,0 +1,582 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_file.c + * + * desc: AAS file loading/writing + * + * $Archive: /MissionPack/code/botlib/be_aas_file.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +//#define AASFILEDEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SwapAASData(void) +{ + int i, j; + //bounding boxes + for (i = 0; i < aasworld.numbboxes; i++) + { + aasworld.bboxes[i].presencetype = LittleLong(aasworld.bboxes[i].presencetype); + aasworld.bboxes[i].flags = LittleLong(aasworld.bboxes[i].flags); + for (j = 0; j < 3; j++) + { + aasworld.bboxes[i].mins[j] = LittleLong(aasworld.bboxes[i].mins[j]); + aasworld.bboxes[i].maxs[j] = LittleLong(aasworld.bboxes[i].maxs[j]); + } //end for + } //end for + //vertexes + for (i = 0; i < aasworld.numvertexes; i++) + { + for (j = 0; j < 3; j++) + aasworld.vertexes[i][j] = LittleFloat(aasworld.vertexes[i][j]); + } //end for + //planes + for (i = 0; i < aasworld.numplanes; i++) + { + for (j = 0; j < 3; j++) + aasworld.planes[i].normal[j] = LittleFloat(aasworld.planes[i].normal[j]); + aasworld.planes[i].dist = LittleFloat(aasworld.planes[i].dist); + aasworld.planes[i].type = LittleLong(aasworld.planes[i].type); + } //end for + //edges + for (i = 0; i < aasworld.numedges; i++) + { + aasworld.edges[i].v[0] = LittleLong(aasworld.edges[i].v[0]); + aasworld.edges[i].v[1] = LittleLong(aasworld.edges[i].v[1]); + } //end for + //edgeindex + for (i = 0; i < aasworld.edgeindexsize; i++) + { + aasworld.edgeindex[i] = LittleLong(aasworld.edgeindex[i]); + } //end for + //faces + for (i = 0; i < aasworld.numfaces; i++) + { + aasworld.faces[i].planenum = LittleLong(aasworld.faces[i].planenum); + aasworld.faces[i].faceflags = LittleLong(aasworld.faces[i].faceflags); + aasworld.faces[i].numedges = LittleLong(aasworld.faces[i].numedges); + aasworld.faces[i].firstedge = LittleLong(aasworld.faces[i].firstedge); + aasworld.faces[i].frontarea = LittleLong(aasworld.faces[i].frontarea); + aasworld.faces[i].backarea = LittleLong(aasworld.faces[i].backarea); + } //end for + //face index + for (i = 0; i < aasworld.faceindexsize; i++) + { + aasworld.faceindex[i] = LittleLong(aasworld.faceindex[i]); + } //end for + //convex areas + for (i = 0; i < aasworld.numareas; i++) + { + aasworld.areas[i].areanum = LittleLong(aasworld.areas[i].areanum); + aasworld.areas[i].numfaces = LittleLong(aasworld.areas[i].numfaces); + aasworld.areas[i].firstface = LittleLong(aasworld.areas[i].firstface); + for (j = 0; j < 3; j++) + { + aasworld.areas[i].mins[j] = LittleFloat(aasworld.areas[i].mins[j]); + aasworld.areas[i].maxs[j] = LittleFloat(aasworld.areas[i].maxs[j]); + aasworld.areas[i].center[j] = LittleFloat(aasworld.areas[i].center[j]); + } //end for + } //end for + //area settings + for (i = 0; i < aasworld.numareasettings; i++) + { + aasworld.areasettings[i].contents = LittleLong(aasworld.areasettings[i].contents); + aasworld.areasettings[i].areaflags = LittleLong(aasworld.areasettings[i].areaflags); + aasworld.areasettings[i].presencetype = LittleLong(aasworld.areasettings[i].presencetype); + aasworld.areasettings[i].cluster = LittleLong(aasworld.areasettings[i].cluster); + aasworld.areasettings[i].clusterareanum = LittleLong(aasworld.areasettings[i].clusterareanum); + aasworld.areasettings[i].numreachableareas = LittleLong(aasworld.areasettings[i].numreachableareas); + aasworld.areasettings[i].firstreachablearea = LittleLong(aasworld.areasettings[i].firstreachablearea); + } //end for + //area reachability + for (i = 0; i < aasworld.reachabilitysize; i++) + { + aasworld.reachability[i].areanum = LittleLong(aasworld.reachability[i].areanum); + aasworld.reachability[i].facenum = LittleLong(aasworld.reachability[i].facenum); + aasworld.reachability[i].edgenum = LittleLong(aasworld.reachability[i].edgenum); + for (j = 0; j < 3; j++) + { + aasworld.reachability[i].start[j] = LittleFloat(aasworld.reachability[i].start[j]); + aasworld.reachability[i].end[j] = LittleFloat(aasworld.reachability[i].end[j]); + } //end for + aasworld.reachability[i].traveltype = LittleLong(aasworld.reachability[i].traveltype); + aasworld.reachability[i].traveltime = LittleShort(aasworld.reachability[i].traveltime); + } //end for + //nodes + for (i = 0; i < aasworld.numnodes; i++) + { + aasworld.nodes[i].planenum = LittleLong(aasworld.nodes[i].planenum); + aasworld.nodes[i].children[0] = LittleLong(aasworld.nodes[i].children[0]); + aasworld.nodes[i].children[1] = LittleLong(aasworld.nodes[i].children[1]); + } //end for + //cluster portals + for (i = 0; i < aasworld.numportals; i++) + { + aasworld.portals[i].areanum = LittleLong(aasworld.portals[i].areanum); + aasworld.portals[i].frontcluster = LittleLong(aasworld.portals[i].frontcluster); + aasworld.portals[i].backcluster = LittleLong(aasworld.portals[i].backcluster); + aasworld.portals[i].clusterareanum[0] = LittleLong(aasworld.portals[i].clusterareanum[0]); + aasworld.portals[i].clusterareanum[1] = LittleLong(aasworld.portals[i].clusterareanum[1]); + } //end for + //cluster portal index + for (i = 0; i < aasworld.portalindexsize; i++) + { + aasworld.portalindex[i] = LittleLong(aasworld.portalindex[i]); + } //end for + //cluster + for (i = 0; i < aasworld.numclusters; i++) + { + aasworld.clusters[i].numareas = LittleLong(aasworld.clusters[i].numareas); + aasworld.clusters[i].numreachabilityareas = LittleLong(aasworld.clusters[i].numreachabilityareas); + aasworld.clusters[i].numportals = LittleLong(aasworld.clusters[i].numportals); + aasworld.clusters[i].firstportal = LittleLong(aasworld.clusters[i].firstportal); + } //end for +} //end of the function AAS_SwapAASData +//=========================================================================== +// dump the current loaded aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DumpAASData(void) +{ + aasworld.numbboxes = 0; + if (aasworld.bboxes) FreeMemory(aasworld.bboxes); + aasworld.bboxes = NULL; + aasworld.numvertexes = 0; + if (aasworld.vertexes) FreeMemory(aasworld.vertexes); + aasworld.vertexes = NULL; + aasworld.numplanes = 0; + if (aasworld.planes) FreeMemory(aasworld.planes); + aasworld.planes = NULL; + aasworld.numedges = 0; + if (aasworld.edges) FreeMemory(aasworld.edges); + aasworld.edges = NULL; + aasworld.edgeindexsize = 0; + if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); + aasworld.edgeindex = NULL; + aasworld.numfaces = 0; + if (aasworld.faces) FreeMemory(aasworld.faces); + aasworld.faces = NULL; + aasworld.faceindexsize = 0; + if (aasworld.faceindex) FreeMemory(aasworld.faceindex); + aasworld.faceindex = NULL; + aasworld.numareas = 0; + if (aasworld.areas) FreeMemory(aasworld.areas); + aasworld.areas = NULL; + aasworld.numareasettings = 0; + if (aasworld.areasettings) FreeMemory(aasworld.areasettings); + aasworld.areasettings = NULL; + aasworld.reachabilitysize = 0; + if (aasworld.reachability) FreeMemory(aasworld.reachability); + aasworld.reachability = NULL; + aasworld.numnodes = 0; + if (aasworld.nodes) FreeMemory(aasworld.nodes); + aasworld.nodes = NULL; + aasworld.numportals = 0; + if (aasworld.portals) FreeMemory(aasworld.portals); + aasworld.portals = NULL; + aasworld.numportals = 0; + if (aasworld.portalindex) FreeMemory(aasworld.portalindex); + aasworld.portalindex = NULL; + aasworld.portalindexsize = 0; + if (aasworld.clusters) FreeMemory(aasworld.clusters); + aasworld.clusters = NULL; + aasworld.numclusters = 0; + // + aasworld.loaded = qfalse; + aasworld.initialized = qfalse; + aasworld.savefile = qfalse; +} //end of the function AAS_DumpAASData +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef AASFILEDEBUG +void AAS_FileInfo(void) +{ + int i, n, optimized; + + botimport.Print(PRT_MESSAGE, "version = %d\n", AASVERSION); + botimport.Print(PRT_MESSAGE, "numvertexes = %d\n", aasworld.numvertexes); + botimport.Print(PRT_MESSAGE, "numplanes = %d\n", aasworld.numplanes); + botimport.Print(PRT_MESSAGE, "numedges = %d\n", aasworld.numedges); + botimport.Print(PRT_MESSAGE, "edgeindexsize = %d\n", aasworld.edgeindexsize); + botimport.Print(PRT_MESSAGE, "numfaces = %d\n", aasworld.numfaces); + botimport.Print(PRT_MESSAGE, "faceindexsize = %d\n", aasworld.faceindexsize); + botimport.Print(PRT_MESSAGE, "numareas = %d\n", aasworld.numareas); + botimport.Print(PRT_MESSAGE, "numareasettings = %d\n", aasworld.numareasettings); + botimport.Print(PRT_MESSAGE, "reachabilitysize = %d\n", aasworld.reachabilitysize); + botimport.Print(PRT_MESSAGE, "numnodes = %d\n", aasworld.numnodes); + botimport.Print(PRT_MESSAGE, "numportals = %d\n", aasworld.numportals); + botimport.Print(PRT_MESSAGE, "portalindexsize = %d\n", aasworld.portalindexsize); + botimport.Print(PRT_MESSAGE, "numclusters = %d\n", aasworld.numclusters); + // + for (n = 0, i = 0; i < aasworld.numareasettings; i++) + { + if (aasworld.areasettings[i].areaflags & AREA_GROUNDED) n++; + } //end for + botimport.Print(PRT_MESSAGE, "num grounded areas = %d\n", n); + // + botimport.Print(PRT_MESSAGE, "planes size %d bytes\n", aasworld.numplanes * sizeof(aas_plane_t)); + botimport.Print(PRT_MESSAGE, "areas size %d bytes\n", aasworld.numareas * sizeof(aas_area_t)); + botimport.Print(PRT_MESSAGE, "areasettings size %d bytes\n", aasworld.numareasettings * sizeof(aas_areasettings_t)); + botimport.Print(PRT_MESSAGE, "nodes size %d bytes\n", aasworld.numnodes * sizeof(aas_node_t)); + botimport.Print(PRT_MESSAGE, "reachability size %d bytes\n", aasworld.reachabilitysize * sizeof(aas_reachability_t)); + botimport.Print(PRT_MESSAGE, "portals size %d bytes\n", aasworld.numportals * sizeof(aas_portal_t)); + botimport.Print(PRT_MESSAGE, "clusters size %d bytes\n", aasworld.numclusters * sizeof(aas_cluster_t)); + + optimized = aasworld.numplanes * sizeof(aas_plane_t) + + aasworld.numareas * sizeof(aas_area_t) + + aasworld.numareasettings * sizeof(aas_areasettings_t) + + aasworld.numnodes * sizeof(aas_node_t) + + aasworld.reachabilitysize * sizeof(aas_reachability_t) + + aasworld.numportals * sizeof(aas_portal_t) + + aasworld.numclusters * sizeof(aas_cluster_t); + botimport.Print(PRT_MESSAGE, "optimzed size %d KB\n", optimized >> 10); +} //end of the function AAS_FileInfo +#endif //AASFILEDEBUG +//=========================================================================== +// allocate memory and read a lump of a AAS file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_LoadAASLump(fileHandle_t fp, int offset, int length, int *lastoffset, int size) +{ + char *buf; + // + if (!length) + { + //just alloc a dummy + return (char *) GetClearedHunkMemory(size+1); + } //end if + //seek to the data + if (offset != *lastoffset) + { + botimport.Print(PRT_WARNING, "AAS file not sequentially read\n"); + if (botimport.FS_Seek(fp, offset, FS_SEEK_SET)) + { + AAS_Error("can't seek to aas lump\n"); + AAS_DumpAASData(); + botimport.FS_FCloseFile(fp); + return NULL; + } //end if + } //end if + //allocate memory + buf = (char *) GetClearedHunkMemory(length+1); + //read the data + if (length) + { + botimport.FS_Read(buf, length, fp ); + *lastoffset += length; + } //end if + return buf; +} //end of the function AAS_LoadAASLump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DData(unsigned char *data, int size) +{ + int i; + + for (i = 0; i < size; i++) + { + data[i] ^= (unsigned char) i * 119; + } //end for +} //end of the function AAS_DData +//=========================================================================== +// load an aas file +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadAASFile(char *filename) +{ + fileHandle_t fp; + aas_header_t header; + int offset, length, lastoffset; + + botimport.Print(PRT_MESSAGE, "trying to load %s\n", filename); + //dump current loaded aas file + AAS_DumpAASData(); + //open the file + botimport.FS_FOpenFile( filename, &fp, FS_READ ); + if (!fp) + { + AAS_Error("can't open %s\n", filename); + return BLERR_CANNOTOPENAASFILE; + } //end if + //read the header + botimport.FS_Read(&header, sizeof(aas_header_t), fp ); + lastoffset = sizeof(aas_header_t); + //check header identification + header.ident = LittleLong(header.ident); + if (header.ident != AASID) + { + AAS_Error("%s is not an AAS file\n", filename); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEID; + } //end if + //check the version + header.version = LittleLong(header.version); + // + if (header.version != AASVERSION_OLD && header.version != AASVERSION) + { + AAS_Error("aas file %s is version %i, not %i\n", filename, header.version, AASVERSION); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEVERSION; + } //end if + // + if (header.version == AASVERSION) + { + AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); + } //end if + // + aasworld.bspchecksum = atoi(LibVarGetString( "sv_mapChecksum")); + if (LittleLong(header.bspchecksum) != aasworld.bspchecksum) + { + AAS_Error("aas file %s is out of date\n", filename); + botimport.FS_FCloseFile(fp); + return BLERR_WRONGAASFILEVERSION; + } //end if + //load the lumps: + //bounding boxes + offset = LittleLong(header.lumps[AASLUMP_BBOXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_BBOXES].filelen); + aasworld.bboxes = (aas_bbox_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_bbox_t)); + aasworld.numbboxes = length / sizeof(aas_bbox_t); + if (aasworld.numbboxes && !aasworld.bboxes) return BLERR_CANNOTREADAASLUMP; + //vertexes + offset = LittleLong(header.lumps[AASLUMP_VERTEXES].fileofs); + length = LittleLong(header.lumps[AASLUMP_VERTEXES].filelen); + aasworld.vertexes = (aas_vertex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_vertex_t)); + aasworld.numvertexes = length / sizeof(aas_vertex_t); + if (aasworld.numvertexes && !aasworld.vertexes) return BLERR_CANNOTREADAASLUMP; + //planes + offset = LittleLong(header.lumps[AASLUMP_PLANES].fileofs); + length = LittleLong(header.lumps[AASLUMP_PLANES].filelen); + aasworld.planes = (aas_plane_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_plane_t)); + aasworld.numplanes = length / sizeof(aas_plane_t); + if (aasworld.numplanes && !aasworld.planes) return BLERR_CANNOTREADAASLUMP; + //edges + offset = LittleLong(header.lumps[AASLUMP_EDGES].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGES].filelen); + aasworld.edges = (aas_edge_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edge_t)); + aasworld.numedges = length / sizeof(aas_edge_t); + if (aasworld.numedges && !aasworld.edges) return BLERR_CANNOTREADAASLUMP; + //edgeindex + offset = LittleLong(header.lumps[AASLUMP_EDGEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_EDGEINDEX].filelen); + aasworld.edgeindex = (aas_edgeindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_edgeindex_t)); + aasworld.edgeindexsize = length / sizeof(aas_edgeindex_t); + if (aasworld.edgeindexsize && !aasworld.edgeindex) return BLERR_CANNOTREADAASLUMP; + //faces + offset = LittleLong(header.lumps[AASLUMP_FACES].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACES].filelen); + aasworld.faces = (aas_face_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_face_t)); + aasworld.numfaces = length / sizeof(aas_face_t); + if (aasworld.numfaces && !aasworld.faces) return BLERR_CANNOTREADAASLUMP; + //faceindex + offset = LittleLong(header.lumps[AASLUMP_FACEINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_FACEINDEX].filelen); + aasworld.faceindex = (aas_faceindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_faceindex_t)); + aasworld.faceindexsize = length / sizeof(aas_faceindex_t); + if (aasworld.faceindexsize && !aasworld.faceindex) return BLERR_CANNOTREADAASLUMP; + //convex areas + offset = LittleLong(header.lumps[AASLUMP_AREAS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREAS].filelen); + aasworld.areas = (aas_area_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_area_t)); + aasworld.numareas = length / sizeof(aas_area_t); + if (aasworld.numareas && !aasworld.areas) return BLERR_CANNOTREADAASLUMP; + //area settings + offset = LittleLong(header.lumps[AASLUMP_AREASETTINGS].fileofs); + length = LittleLong(header.lumps[AASLUMP_AREASETTINGS].filelen); + aasworld.areasettings = (aas_areasettings_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_areasettings_t)); + aasworld.numareasettings = length / sizeof(aas_areasettings_t); + if (aasworld.numareasettings && !aasworld.areasettings) return BLERR_CANNOTREADAASLUMP; + //reachability list + offset = LittleLong(header.lumps[AASLUMP_REACHABILITY].fileofs); + length = LittleLong(header.lumps[AASLUMP_REACHABILITY].filelen); + aasworld.reachability = (aas_reachability_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_reachability_t)); + aasworld.reachabilitysize = length / sizeof(aas_reachability_t); + if (aasworld.reachabilitysize && !aasworld.reachability) return BLERR_CANNOTREADAASLUMP; + //nodes + offset = LittleLong(header.lumps[AASLUMP_NODES].fileofs); + length = LittleLong(header.lumps[AASLUMP_NODES].filelen); + aasworld.nodes = (aas_node_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_node_t)); + aasworld.numnodes = length / sizeof(aas_node_t); + if (aasworld.numnodes && !aasworld.nodes) return BLERR_CANNOTREADAASLUMP; + //cluster portals + offset = LittleLong(header.lumps[AASLUMP_PORTALS].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALS].filelen); + aasworld.portals = (aas_portal_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portal_t)); + aasworld.numportals = length / sizeof(aas_portal_t); + if (aasworld.numportals && !aasworld.portals) return BLERR_CANNOTREADAASLUMP; + //cluster portal index + offset = LittleLong(header.lumps[AASLUMP_PORTALINDEX].fileofs); + length = LittleLong(header.lumps[AASLUMP_PORTALINDEX].filelen); + aasworld.portalindex = (aas_portalindex_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_portalindex_t)); + aasworld.portalindexsize = length / sizeof(aas_portalindex_t); + if (aasworld.portalindexsize && !aasworld.portalindex) return BLERR_CANNOTREADAASLUMP; + //clusters + offset = LittleLong(header.lumps[AASLUMP_CLUSTERS].fileofs); + length = LittleLong(header.lumps[AASLUMP_CLUSTERS].filelen); + aasworld.clusters = (aas_cluster_t *) AAS_LoadAASLump(fp, offset, length, &lastoffset, sizeof(aas_cluster_t)); + aasworld.numclusters = length / sizeof(aas_cluster_t); + if (aasworld.numclusters && !aasworld.clusters) return BLERR_CANNOTREADAASLUMP; + //swap everything + AAS_SwapAASData(); + //aas file is loaded + aasworld.loaded = qtrue; + //close the file + botimport.FS_FCloseFile(fp); + // +#ifdef AASFILEDEBUG + AAS_FileInfo(); +#endif //AASFILEDEBUG + // + return BLERR_NOERROR; +} //end of the function AAS_LoadAASFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +static int AAS_WriteAASLump_offset; + +int AAS_WriteAASLump(fileHandle_t fp, aas_header_t *h, int lumpnum, void *data, int length) +{ + aas_lump_t *lump; + + lump = &h->lumps[lumpnum]; + + lump->fileofs = LittleLong(AAS_WriteAASLump_offset); //LittleLong(ftell(fp)); + lump->filelen = LittleLong(length); + + if (length > 0) + { + botimport.FS_Write(data, length, fp ); + } //end if + + AAS_WriteAASLump_offset += length; + + return qtrue; +} //end of the function AAS_WriteAASLump +//=========================================================================== +// aas data is useless after writing to file because it is byte swapped +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_WriteAASFile(char *filename) +{ + aas_header_t header; + fileHandle_t fp; + + botimport.Print(PRT_MESSAGE, "writing %s\n", filename); + //swap the aas data + AAS_SwapAASData(); + //initialize the file header + Com_Memset(&header, 0, sizeof(aas_header_t)); + header.ident = LittleLong(AASID); + header.version = LittleLong(AASVERSION); + header.bspchecksum = LittleLong(aasworld.bspchecksum); + //open a new file + botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); + if (!fp) + { + botimport.Print(PRT_ERROR, "error opening %s\n", filename); + return qfalse; + } //end if + //write the header + botimport.FS_Write(&header, sizeof(aas_header_t), fp); + AAS_WriteAASLump_offset = sizeof(aas_header_t); + //add the data lumps to the file + if (!AAS_WriteAASLump(fp, &header, AASLUMP_BBOXES, aasworld.bboxes, + aasworld.numbboxes * sizeof(aas_bbox_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_VERTEXES, aasworld.vertexes, + aasworld.numvertexes * sizeof(aas_vertex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PLANES, aasworld.planes, + aasworld.numplanes * sizeof(aas_plane_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGES, aasworld.edges, + aasworld.numedges * sizeof(aas_edge_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_EDGEINDEX, aasworld.edgeindex, + aasworld.edgeindexsize * sizeof(aas_edgeindex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACES, aasworld.faces, + aasworld.numfaces * sizeof(aas_face_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_FACEINDEX, aasworld.faceindex, + aasworld.faceindexsize * sizeof(aas_faceindex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREAS, aasworld.areas, + aasworld.numareas * sizeof(aas_area_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_AREASETTINGS, aasworld.areasettings, + aasworld.numareasettings * sizeof(aas_areasettings_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_REACHABILITY, aasworld.reachability, + aasworld.reachabilitysize * sizeof(aas_reachability_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_NODES, aasworld.nodes, + aasworld.numnodes * sizeof(aas_node_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALS, aasworld.portals, + aasworld.numportals * sizeof(aas_portal_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_PORTALINDEX, aasworld.portalindex, + aasworld.portalindexsize * sizeof(aas_portalindex_t))) return qfalse; + if (!AAS_WriteAASLump(fp, &header, AASLUMP_CLUSTERS, aasworld.clusters, + aasworld.numclusters * sizeof(aas_cluster_t))) return qfalse; + //rewrite the header with the added lumps + botimport.FS_Seek(fp, 0, FS_SEEK_SET); + AAS_DData((unsigned char *) &header + 8, sizeof(aas_header_t) - 8); + botimport.FS_Write(&header, sizeof(aas_header_t), fp); + //close the file + botimport.FS_FCloseFile(fp); + return qtrue; +} //end of the function AAS_WriteAASFile diff --git a/code/botlib/be_aas_file.h b/code/botlib/be_aas_file.h new file mode 100644 index 0000000..c2271fc --- /dev/null +++ b/code/botlib/be_aas_file.h @@ -0,0 +1,42 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_file.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_file.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//loads the AAS file with the given name +int AAS_LoadAASFile(char *filename); +//writes an AAS file with the given name +qboolean AAS_WriteAASFile(char *filename); +//dumps the loaded AAS data +void AAS_DumpAASData(void); +//print AAS file information +void AAS_FileInfo(void); +#endif //AASINTERN + diff --git a/code/botlib/be_aas_funcs.h b/code/botlib/be_aas_funcs.h new file mode 100644 index 0000000..87c7636 --- /dev/null +++ b/code/botlib/be_aas_funcs.h @@ -0,0 +1,47 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_funcs.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_funcs.h $ + * + *****************************************************************************/ + +#ifndef BSPCINCLUDE + +#include "be_aas_main.h" +#include "be_aas_entity.h" +#include "be_aas_sample.h" +#include "be_aas_cluster.h" +#include "be_aas_reach.h" +#include "be_aas_route.h" +#include "be_aas_routealt.h" +#include "be_aas_debug.h" +#include "be_aas_file.h" +#include "be_aas_optimize.h" +#include "be_aas_bsp.h" +#include "be_aas_move.h" + +#endif //BSPCINCLUDE diff --git a/code/botlib/be_aas_main.c b/code/botlib/be_aas_main.c new file mode 100644 index 0000000..0dab57c --- /dev/null +++ b/code/botlib/be_aas_main.c @@ -0,0 +1,429 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_main.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_main.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_log.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +aas_t aasworld; + +libvar_t *saveroutingcache; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL AAS_Error(char *fmt, ...) +{ + char str[1024]; + va_list arglist; + + va_start(arglist, fmt); + Q_vsnprintf(str, sizeof(str), fmt, arglist); + va_end(arglist); + botimport.Print(PRT_FATAL, "%s", str); +} //end of the function AAS_Error +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_StringFromIndex(char *indexname, char *stringindex[], int numindexes, int index) +{ + if (!aasworld.indexessetup) + { + botimport.Print(PRT_ERROR, "%s: index %d not setup\n", indexname, index); + return ""; + } //end if + if (index < 0 || index >= numindexes) + { + botimport.Print(PRT_ERROR, "%s: index %d out of range\n", indexname, index); + return ""; + } //end if + if (!stringindex[index]) + { + if (index) + { + botimport.Print(PRT_ERROR, "%s: reference to unused index %d\n", indexname, index); + } //end if + return ""; + } //end if + return stringindex[index]; +} //end of the function AAS_StringFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromString(char *indexname, char *stringindex[], int numindexes, char *string) +{ + int i; + if (!aasworld.indexessetup) + { + botimport.Print(PRT_ERROR, "%s: index not setup \"%s\"\n", indexname, string); + return 0; + } //end if + for (i = 0; i < numindexes; i++) + { + if (!stringindex[i]) continue; + if (!Q_stricmp(stringindex[i], string)) return i; + } //end for + return 0; +} //end of the function AAS_IndexFromString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *AAS_ModelFromIndex(int index) +{ + return AAS_StringFromIndex("ModelFromIndex", &aasworld.configstrings[CS_MODELS], MAX_MODELS, index); +} //end of the function AAS_ModelFromIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_IndexFromModel(char *modelname) +{ + return AAS_IndexFromString("IndexFromModel", &aasworld.configstrings[CS_MODELS], MAX_MODELS, modelname); +} //end of the function AAS_IndexFromModel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateStringIndexes(int numconfigstrings, char *configstrings[]) +{ + int i; + //set string pointers and copy the strings + for (i = 0; i < numconfigstrings; i++) + { + if (configstrings[i]) + { + //if (aasworld.configstrings[i]) FreeMemory(aasworld.configstrings[i]); + aasworld.configstrings[i] = (char *) GetMemory(strlen(configstrings[i]) + 1); + strcpy(aasworld.configstrings[i], configstrings[i]); + } //end if + } //end for + aasworld.indexessetup = qtrue; +} //end of the function AAS_UpdateStringIndexes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Loaded(void) +{ + return aasworld.loaded; +} //end of the function AAS_Loaded +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Initialized(void) +{ + return aasworld.initialized; +} //end of the function AAS_Initialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetInitialized(void) +{ + aasworld.initialized = qtrue; + botimport.Print(PRT_MESSAGE, "AAS initialized.\n"); +#ifdef DEBUG + //create all the routing cache + //AAS_CreateAllRoutingCache(); + // + //AAS_RoutingInfo(); +#endif +} //end of the function AAS_SetInitialized +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ContinueInit(float time) +{ + //if no AAS file loaded + if (!aasworld.loaded) return; + //if AAS is already initialized + if (aasworld.initialized) return; + //calculate reachability, if not finished return + if (AAS_ContinueInitReachability(time)) return; + //initialize clustering for the new map + AAS_InitClustering(); + //if reachability has been calculated and an AAS file should be written + //or there is a forced data optimization + if (aasworld.savefile || ((int)LibVarGetValue("forcewrite"))) + { + //optimize the AAS data + if ((int)LibVarValue("aasoptimize", "0")) AAS_Optimize(); + //save the AAS file + if (AAS_WriteAASFile(aasworld.filename)) + { + botimport.Print(PRT_MESSAGE, "%s written successfully\n", aasworld.filename); + } //end if + else + { + botimport.Print(PRT_ERROR, "couldn't write %s\n", aasworld.filename); + } //end else + } //end if + //initialize the routing + AAS_InitRouting(); + //at this point AAS is initialized + AAS_SetInitialized(); +} //end of the function AAS_ContinueInit +//=========================================================================== +// called at the start of every frame +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_StartFrame(float time) +{ + aasworld.time = time; + //unlink all entities that were not updated last frame + AAS_UnlinkInvalidEntities(); + //invalidate the entities + AAS_InvalidateEntities(); + //initialize AAS + AAS_ContinueInit(time); + // + aasworld.frameroutingupdates = 0; + // + if (botDeveloper) + { + if (LibVarGetValue("showcacheupdates")) + { + AAS_RoutingInfo(); + LibVarSet("showcacheupdates", "0"); + } //end if + if (LibVarGetValue("showmemoryusage")) + { + PrintUsedMemorySize(); + LibVarSet("showmemoryusage", "0"); + } //end if + if (LibVarGetValue("memorydump")) + { + PrintMemoryLabels(); + LibVarSet("memorydump", "0"); + } //end if + } //end if + // + if (saveroutingcache->value) + { + AAS_WriteRouteCache(); + LibVarSet("saveroutingcache", "0"); + } //end if + // + aasworld.numframes++; + return BLERR_NOERROR; +} //end of the function AAS_StartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_Time(void) +{ + return aasworld.time; +} //end of the function AAS_Time +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) +{ + vec3_t pVec, vec; + + VectorSubtract( point, vStart, pVec ); + VectorSubtract( vEnd, vStart, vec ); + VectorNormalize( vec ); + // project onto the directional vector for this segment + VectorMA( vStart, DotProduct( pVec, vec ), vec, vProj ); +} //end of the function AAS_ProjectPointOntoVector +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadFiles(const char *mapname) +{ + int errnum; + char aasfile[MAX_PATH]; +// char bspfile[MAX_PATH]; + + strcpy(aasworld.mapname, mapname); + //NOTE: first reset the entity links into the AAS areas and BSP leaves + // the AAS link heap and BSP link heap are reset after respectively the + // AAS file and BSP file are loaded + AAS_ResetEntityLinks(); + // load bsp info + AAS_LoadBSPFile(); + + //load the aas file + Com_sprintf(aasfile, MAX_PATH, "maps/%s.aas", mapname); + errnum = AAS_LoadAASFile(aasfile); + if (errnum != BLERR_NOERROR) + return errnum; + + botimport.Print(PRT_MESSAGE, "loaded %s\n", aasfile); + strncpy(aasworld.filename, aasfile, MAX_PATH); + return BLERR_NOERROR; +} //end of the function AAS_LoadFiles +//=========================================================================== +// called everytime a map changes +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_LoadMap(const char *mapname) +{ + int errnum; + + //if no mapname is provided then the string indexes are updated + if (!mapname) + { + return 0; + } //end if + // + aasworld.initialized = qfalse; + //NOTE: free the routing caches before loading a new map because + // to free the caches the old number of areas, number of clusters + // and number of areas in a clusters must be available + AAS_FreeRoutingCaches(); + //load the map + errnum = AAS_LoadFiles(mapname); + if (errnum != BLERR_NOERROR) + { + aasworld.loaded = qfalse; + return errnum; + } //end if + // + AAS_InitSettings(); + //initialize the AAS link heap for the new map + AAS_InitAASLinkHeap(); + //initialize the AAS linked entities for the new map + AAS_InitAASLinkedEntities(); + //initialize reachability for the new map + AAS_InitReachability(); + //initialize the alternative routing + AAS_InitAlternativeRouting(); + //everything went ok + return 0; +} //end of the function AAS_LoadMap +//=========================================================================== +// called when the library is first loaded +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Setup(void) +{ + aasworld.maxclients = (int) LibVarValue("maxclients", "128"); + aasworld.maxentities = (int) LibVarValue("maxentities", "1024"); + // as soon as it's set to 1 the routing cache will be saved + saveroutingcache = LibVar("saveroutingcache", "0"); + //allocate memory for the entities + if (aasworld.entities) FreeMemory(aasworld.entities); + aasworld.entities = (aas_entity_t *) GetClearedHunkMemory(aasworld.maxentities * sizeof(aas_entity_t)); + //invalidate all the entities + AAS_InvalidateEntities(); + //force some recalculations + //LibVarSet("forceclustering", "1"); //force clustering calculation + //LibVarSet("forcereachability", "1"); //force reachability calculation + aasworld.numframes = 0; + return BLERR_NOERROR; +} //end of the function AAS_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Shutdown(void) +{ + AAS_ShutdownAlternativeRouting(); + // + AAS_DumpBSPData(); + //free routing caches + AAS_FreeRoutingCaches(); + //free aas link heap + AAS_FreeAASLinkHeap(); + //free aas linked entities + AAS_FreeAASLinkedEntities(); + //free the aas data + AAS_DumpAASData(); + //free the entities + if (aasworld.entities) FreeMemory(aasworld.entities); + //clear the aasworld structure + Com_Memset(&aasworld, 0, sizeof(aas_t)); + //aas has not been initialized + aasworld.initialized = qfalse; + //NOTE: as soon as a new .bsp file is loaded the .bsp file memory is + // freed an reallocated, so there's no need to free that memory here + //print shutdown + botimport.Print(PRT_MESSAGE, "AAS shutdown.\n"); +} //end of the function AAS_Shutdown diff --git a/code/botlib/be_aas_main.h b/code/botlib/be_aas_main.h new file mode 100644 index 0000000..0748ad1 --- /dev/null +++ b/code/botlib/be_aas_main.h @@ -0,0 +1,61 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_main.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_main.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN + +extern aas_t aasworld; + +//AAS error message +void QDECL AAS_Error(char *fmt, ...) __attribute__ ((format (printf, 1, 2))); +//set AAS initialized +void AAS_SetInitialized(void); +//setup AAS with the given number of entities and clients +int AAS_Setup(void); +//shutdown AAS +void AAS_Shutdown(void); +//start a new map +int AAS_LoadMap(const char *mapname); +//start a new time frame +int AAS_StartFrame(float time); +#endif //AASINTERN + +//returns true if AAS is initialized +int AAS_Initialized(void); +//returns true if the AAS file is loaded +int AAS_Loaded(void); +//returns the model name from the given index +char *AAS_ModelFromIndex(int index); +//returns the index from the given model name +int AAS_IndexFromModel(char *modelname); +//returns the current time +float AAS_Time(void); +// +void AAS_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ); diff --git a/code/botlib/be_aas_move.c b/code/botlib/be_aas_move.c new file mode 100644 index 0000000..c42b6cc --- /dev/null +++ b/code/botlib/be_aas_move.c @@ -0,0 +1,1090 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_move.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_move.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern botlib_import_t botimport; + +aas_settings_t aassettings; + +//#define AAS_MOVE_DEBUG + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + vec3_t end; + bsp_trace_t trace; + + VectorCopy(origin, end); + end[2] -= 100; + trace = AAS_Trace(origin, mins, maxs, end, 0, CONTENTS_SOLID); + if (trace.startsolid) return qfalse; + VectorCopy(trace.endpos, origin); + return qtrue; +} //end of the function AAS_DropToFloor +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitSettings(void) +{ + aassettings.phys_gravitydirection[0] = 0; + aassettings.phys_gravitydirection[1] = 0; + aassettings.phys_gravitydirection[2] = -1; + aassettings.phys_friction = LibVarValue("phys_friction", "6"); + aassettings.phys_stopspeed = LibVarValue("phys_stopspeed", "100"); + aassettings.phys_gravity = LibVarValue("phys_gravity", "800"); + aassettings.phys_waterfriction = LibVarValue("phys_waterfriction", "1"); + aassettings.phys_watergravity = LibVarValue("phys_watergravity", "400"); + aassettings.phys_maxvelocity = LibVarValue("phys_maxvelocity", "320"); + aassettings.phys_maxwalkvelocity = LibVarValue("phys_maxwalkvelocity", "320"); + aassettings.phys_maxcrouchvelocity = LibVarValue("phys_maxcrouchvelocity", "100"); + aassettings.phys_maxswimvelocity = LibVarValue("phys_maxswimvelocity", "150"); + aassettings.phys_walkaccelerate = LibVarValue("phys_walkaccelerate", "10"); + aassettings.phys_airaccelerate = LibVarValue("phys_airaccelerate", "1"); + aassettings.phys_swimaccelerate = LibVarValue("phys_swimaccelerate", "4"); + aassettings.phys_maxstep = LibVarValue("phys_maxstep", "19"); + aassettings.phys_maxsteepness = LibVarValue("phys_maxsteepness", "0.7"); + aassettings.phys_maxwaterjump = LibVarValue("phys_maxwaterjump", "18"); + aassettings.phys_maxbarrier = LibVarValue("phys_maxbarrier", "33"); + aassettings.phys_jumpvel = LibVarValue("phys_jumpvel", "270"); + aassettings.phys_falldelta5 = LibVarValue("phys_falldelta5", "40"); + aassettings.phys_falldelta10 = LibVarValue("phys_falldelta10", "60"); + aassettings.rs_waterjump = LibVarValue("rs_waterjump", "400"); + aassettings.rs_teleport = LibVarValue("rs_teleport", "50"); + aassettings.rs_barrierjump = LibVarValue("rs_barrierjump", "100"); + aassettings.rs_startcrouch = LibVarValue("rs_startcrouch", "300"); + aassettings.rs_startgrapple = LibVarValue("rs_startgrapple", "500"); + aassettings.rs_startwalkoffledge = LibVarValue("rs_startwalkoffledge", "70"); + aassettings.rs_startjump = LibVarValue("rs_startjump", "300"); + aassettings.rs_rocketjump = LibVarValue("rs_rocketjump", "500"); + aassettings.rs_bfgjump = LibVarValue("rs_bfgjump", "500"); + aassettings.rs_jumppad = LibVarValue("rs_jumppad", "250"); + aassettings.rs_aircontrolledjumppad = LibVarValue("rs_aircontrolledjumppad", "300"); + aassettings.rs_funcbob = LibVarValue("rs_funcbob", "300"); + aassettings.rs_startelevator = LibVarValue("rs_startelevator", "50"); + aassettings.rs_falldamage5 = LibVarValue("rs_falldamage5", "300"); + aassettings.rs_falldamage10 = LibVarValue("rs_falldamage10", "500"); + aassettings.rs_maxfallheight = LibVarValue("rs_maxfallheight", "0"); + aassettings.rs_maxjumpfallheight = LibVarValue("rs_maxjumpfallheight", "450"); +} //end of the function AAS_InitSettings +//=========================================================================== +// returns qtrue if the bot is against a ladder +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AgainstLadder(vec3_t origin) +{ + int areanum, i, facenum, side; + vec3_t org; + aas_plane_t *plane; + aas_face_t *face; + aas_area_t *area; + + VectorCopy(origin, org); + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[0] += 1; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[1] += 1; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[0] -= 2; + areanum = AAS_PointAreaNum(org); + if (!areanum) + { + org[1] -= 2; + areanum = AAS_PointAreaNum(org); + } //end if + } //end if + } //end if + } //end if + //if in solid... wrrr shouldn't happen + if (!areanum) return qfalse; + //if not in a ladder area + if (!(aasworld.areasettings[areanum].areaflags & AREA_LADDER)) return qfalse; + //if a crouch only area + if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qfalse; + // + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + side = facenum < 0; + face = &aasworld.faces[abs(facenum)]; + //if the face isn't a ladder face + if (!(face->faceflags & FACE_LADDER)) continue; + //get the plane the face is in + plane = &aasworld.planes[face->planenum ^ side]; + //if the origin is pretty close to the plane + if (abs(DotProduct(plane->normal, origin) - plane->dist) < 3) + { + if (AAS_PointInsideFace(abs(facenum), origin, 0.1f)) return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function AAS_AgainstLadder +//=========================================================================== +// returns qtrue if the bot is on the ground +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OnGround(vec3_t origin, int presencetype, int passent) +{ + aas_trace_t trace; + vec3_t end, up = {0, 0, 1}; + aas_plane_t *plane; + + VectorCopy(origin, end); + end[2] -= 10; + + trace = AAS_TraceClientBBox(origin, end, presencetype, passent); + + //if in solid + if (trace.startsolid) return qfalse; + //if nothing hit at all + if (trace.fraction >= 1.0) return qfalse; + //if too far from the hit plane + if (origin[2] - trace.endpos[2] > 10) return qfalse; + //check if the plane isn't too steep + plane = AAS_PlaneFromNum(trace.planenum); + if (DotProduct(plane->normal, up) < aassettings.phys_maxsteepness) return qfalse; + //the bot is on the ground + return qtrue; +} //end of the function AAS_OnGround +//=========================================================================== +// returns qtrue if a bot at the given position is swimming +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Swimming(vec3_t origin) +{ + vec3_t testorg; + + VectorCopy(origin, testorg); + testorg[2] -= 2; + if (AAS_PointContents(testorg) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) return qtrue; + return qfalse; +} //end of the function AAS_Swimming +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +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 AAS_SetMovedir(vec3_t angles, vec3_t movedir) +{ + if (VectorCompare(angles, VEC_UP)) + { + VectorCopy(MOVEDIR_UP, movedir); + } //end if + else if (VectorCompare(angles, VEC_DOWN)) + { + VectorCopy(MOVEDIR_DOWN, movedir); + } //end else if + else + { + AngleVectors(angles, movedir, NULL, NULL); + } //end else +} //end of the function AAS_SetMovedir +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_JumpReachRunStart(aas_reachability_t *reach, vec3_t runstart) +{ + vec3_t hordir, start, cmdmove; + aas_clientmove_t move; + + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //start point + VectorCopy(reach->start, start); + start[2] += 1; + //get command movement + VectorScale(hordir, 400, cmdmove); + // + AAS_PredictClientMovement(&move, -1, start, PRESENCE_NORMAL, qtrue, + vec3_origin, cmdmove, 1, 2, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA| + SE_HITGROUNDDAMAGE|SE_GAP, 0, qfalse); + VectorCopy(move.endpos, runstart); + //don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + { + VectorCopy(start, runstart); + } //end if +} //end of the function AAS_JumpReachRunStart +//=========================================================================== +// returns the Z velocity when rocket jumping at the origin +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_WeaponJumpZVelocity(vec3_t origin, float radiusdamage) +{ + vec3_t kvel, v, start, end, forward, right, viewangles, dir; + float mass, knockback, points; + vec3_t rocketoffset = {8, 8, -8}; + vec3_t botmins = {-16, -16, -24}; + vec3_t botmaxs = {16, 16, 32}; + bsp_trace_t bsptrace; + + //look down (90 degrees) + viewangles[PITCH] = 90; + viewangles[YAW] = 0; + viewangles[ROLL] = 0; + //get the start point shooting from + VectorCopy(origin, start); + start[2] += 8; //view offset Z + AngleVectors(viewangles, forward, right, NULL); + start[0] += forward[0] * rocketoffset[0] + right[0] * rocketoffset[1]; + start[1] += forward[1] * rocketoffset[0] + right[1] * rocketoffset[1]; + start[2] += forward[2] * rocketoffset[0] + right[2] * rocketoffset[1] + rocketoffset[2]; + //end point of the trace + VectorMA(start, 500, forward, end); + //trace a line to get the impact point + bsptrace = AAS_Trace(start, NULL, NULL, end, 1, CONTENTS_SOLID); + //calculate the damage the bot will get from the rocket impact + VectorAdd(botmins, botmaxs, v); + VectorMA(origin, 0.5, v, v); + VectorSubtract(bsptrace.endpos, v, v); + // + points = radiusdamage - 0.5 * VectorLength(v); + if (points < 0) points = 0; + //the owner of the rocket gets half the damage + points *= 0.5; + //mass of the bot (p_client.c: PutClientInServer) + mass = 200; + //knockback is the same as the damage points + knockback = points; + //direction of the damage (from trace.endpos to bot origin) + VectorSubtract(origin, bsptrace.endpos, dir); + VectorNormalize(dir); + //damage velocity + VectorScale(dir, 1600.0 * (float)knockback / mass, kvel); //the rocket jump hack... + //rocket impact velocity + jump velocity + return kvel[2] + aassettings.phys_jumpvel; +} //end of the function AAS_WeaponJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_RocketJumpZVelocity(vec3_t origin) +{ + //rocket radius damage is 120 (p_weapon.c: Weapon_RocketLauncher_Fire) + return AAS_WeaponJumpZVelocity(origin, 120); +} //end of the function AAS_RocketJumpZVelocity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_BFGJumpZVelocity(vec3_t origin) +{ + //bfg radius damage is 1000 (p_weapon.c: weapon_bfg_fire) + return AAS_WeaponJumpZVelocity(origin, 120); +} //end of the function AAS_BFGJumpZVelocity +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Accelerate(vec3_t velocity, float frametime, vec3_t wishdir, float wishspeed, float accel) +{ + // q2 style + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct(velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) { + return; + } + accelspeed = accel*frametime*wishspeed; + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + + for (i=0 ; i<3 ; i++) { + velocity[i] += accelspeed*wishdir[i]; + } +} //end of the function AAS_Accelerate +//=========================================================================== +// applies ground friction to the given velocity +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ApplyFriction(vec3_t vel, float friction, float stopspeed, + float frametime) +{ + float speed, control, newspeed; + + //horizontal speed + speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]); + if (speed) + { + control = speed < stopspeed ? stopspeed : speed; + newspeed = speed - frametime * control * friction; + if (newspeed < 0) newspeed = 0; + newspeed /= speed; + vel[0] *= newspeed; + vel[1] *= newspeed; + } //end if +} //end of the function AAS_ApplyFriction +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ClipToBBox(aas_trace_t *trace, vec3_t start, vec3_t end, int presencetype, vec3_t mins, vec3_t maxs) +{ + int i, j, side; + float front, back, frac, planedist; + vec3_t bboxmins, bboxmaxs, absmins, absmaxs, dir, mid; + + AAS_PresenceTypeBoundingBox(presencetype, bboxmins, bboxmaxs); + VectorSubtract(mins, bboxmaxs, absmins); + VectorSubtract(maxs, bboxmins, absmaxs); + // + VectorCopy(end, trace->endpos); + trace->fraction = 1; + for (i = 0; i < 3; i++) + { + if (start[i] < absmins[i] && end[i] < absmins[i]) return qfalse; + if (start[i] > absmaxs[i] && end[i] > absmaxs[i]) return qfalse; + } //end for + //check bounding box collision + VectorSubtract(end, start, dir); + frac = 1; + for (i = 0; i < 3; i++) + { + //get plane to test collision with for the current axis direction + if (dir[i] > 0) planedist = absmins[i]; + else planedist = absmaxs[i]; + //calculate collision fraction + front = start[i] - planedist; + back = end[i] - planedist; + frac = front / (front-back); + //check if between bounding planes of next axis + side = i + 1; + if (side > 2) side = 0; + mid[side] = start[side] + dir[side] * frac; + if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) + { + //check if between bounding planes of next axis + side++; + if (side > 2) side = 0; + mid[side] = start[side] + dir[side] * frac; + if (mid[side] > absmins[side] && mid[side] < absmaxs[side]) + { + mid[i] = planedist; + break; + } //end if + } //end if + } //end for + //if there was a collision + if (i != 3) + { + trace->startsolid = qfalse; + trace->fraction = frac; + trace->ent = 0; + trace->planenum = 0; + trace->area = 0; + trace->lastarea = 0; + //trace endpos + for (j = 0; j < 3; j++) trace->endpos[j] = start[j] + dir[j] * frac; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_ClipToBBox +//=========================================================================== +// predicts the movement +// assumes regular bounding box sizes +// NOTE: out of water jumping is not included +// NOTE: grappling hook is not included +// +// Parameter: origin : origin to start with +// presencetype : presence type to start with +// velocity : velocity to start with +// cmdmove : client command movement +// cmdframes : number of frame cmdmove is valid +// maxframes : maximum number of predicted frames +// frametime : duration of one predicted frame +// stopevent : events that stop the prediction +// stopareanum : stop as soon as entered this area +// Returns: aas_clientmove_t +// Changes Globals: - +//=========================================================================== +int AAS_ClientMovementPrediction(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, + vec3_t mins, vec3_t maxs, int visualize) +{ + float phys_friction, phys_stopspeed, phys_gravity, phys_waterfriction; + float phys_watergravity; + float phys_walkaccelerate, phys_airaccelerate, phys_swimaccelerate; + float phys_maxwalkvelocity, phys_maxcrouchvelocity, phys_maxswimvelocity; + float phys_maxstep, phys_maxsteepness, phys_jumpvel, friction; + float gravity, delta, maxvel, wishspeed, accelerate; + //float velchange, newvel; + //int ax; + int n, i, j, pc, step, swimming, crouch, event, jump_frame, areanum; + int areas[20], numareas; + vec3_t points[20]; + vec3_t org, end, feet, start, stepend, lastorg, wishdir; + vec3_t frame_test_vel, old_frame_test_vel, left_test_vel; + vec3_t up = {0, 0, 1}; + aas_plane_t *plane, *plane2; + aas_trace_t trace, steptrace; + + if (frametime <= 0) frametime = 0.1f; + // + phys_friction = aassettings.phys_friction; + phys_stopspeed = aassettings.phys_stopspeed; + phys_gravity = aassettings.phys_gravity; + phys_waterfriction = aassettings.phys_waterfriction; + phys_watergravity = aassettings.phys_watergravity; + phys_maxwalkvelocity = aassettings.phys_maxwalkvelocity;// * frametime; + phys_maxcrouchvelocity = aassettings.phys_maxcrouchvelocity;// * frametime; + phys_maxswimvelocity = aassettings.phys_maxswimvelocity;// * frametime; + phys_walkaccelerate = aassettings.phys_walkaccelerate; + phys_airaccelerate = aassettings.phys_airaccelerate; + phys_swimaccelerate = aassettings.phys_swimaccelerate; + phys_maxstep = aassettings.phys_maxstep; + phys_maxsteepness = aassettings.phys_maxsteepness; + phys_jumpvel = aassettings.phys_jumpvel * frametime; + // + Com_Memset(move, 0, sizeof(aas_clientmove_t)); + Com_Memset(&trace, 0, sizeof(aas_trace_t)); + //start at the current origin + VectorCopy(origin, org); + org[2] += 0.25; + //velocity to test for the first frame + VectorScale(velocity, frametime, frame_test_vel); + // + jump_frame = -1; + //predict a maximum of 'maxframes' ahead + for (n = 0; n < maxframes; n++) + { + swimming = AAS_Swimming(org); + //get gravity depending on swimming or not + gravity = swimming ? phys_watergravity : phys_gravity; + //apply gravity at the START of the frame + frame_test_vel[2] = frame_test_vel[2] - (gravity * 0.1 * frametime); + //if on the ground or swimming + if (onground || swimming) + { + friction = swimming ? phys_friction : phys_waterfriction; + //apply friction + VectorScale(frame_test_vel, 1/frametime, frame_test_vel); + AAS_ApplyFriction(frame_test_vel, friction, phys_stopspeed, frametime); + VectorScale(frame_test_vel, frametime, frame_test_vel); + } //end if + crouch = qfalse; + //apply command movement + if (n < cmdframes) + { + //ax = 0; + maxvel = phys_maxwalkvelocity; + accelerate = phys_airaccelerate; + VectorCopy(cmdmove, wishdir); + if (onground) + { + if (cmdmove[2] < -300) + { + crouch = qtrue; + maxvel = phys_maxcrouchvelocity; + } //end if + //if not swimming and upmove is positive then jump + if (!swimming && cmdmove[2] > 1) + { + //jump velocity minus the gravity for one frame + 5 for safety + frame_test_vel[2] = phys_jumpvel - (gravity * 0.1 * frametime) + 5; + jump_frame = n; + //jumping so air accelerate + accelerate = phys_airaccelerate; + } //end if + else + { + accelerate = phys_walkaccelerate; + } //end else + //ax = 2; + } //end if + if (swimming) + { + maxvel = phys_maxswimvelocity; + accelerate = phys_swimaccelerate; + //ax = 3; + } //end if + else + { + wishdir[2] = 0; + } //end else + // + wishspeed = VectorNormalize(wishdir); + if (wishspeed > maxvel) wishspeed = maxvel; + VectorScale(frame_test_vel, 1/frametime, frame_test_vel); + AAS_Accelerate(frame_test_vel, frametime, wishdir, wishspeed, accelerate); + VectorScale(frame_test_vel, frametime, frame_test_vel); + /* + for (i = 0; i < ax; i++) + { + velchange = (cmdmove[i] * frametime) - frame_test_vel[i]; + if (velchange > phys_maxacceleration) velchange = phys_maxacceleration; + else if (velchange < -phys_maxacceleration) velchange = -phys_maxacceleration; + newvel = frame_test_vel[i] + velchange; + // + if (frame_test_vel[i] <= maxvel && newvel > maxvel) frame_test_vel[i] = maxvel; + else if (frame_test_vel[i] >= -maxvel && newvel < -maxvel) frame_test_vel[i] = -maxvel; + else frame_test_vel[i] = newvel; + } //end for + */ + } //end if + if (crouch) + { + presencetype = PRESENCE_CROUCH; + } //end if + else if (presencetype == PRESENCE_CROUCH) + { + if (AAS_PointPresenceType(org) & PRESENCE_NORMAL) + { + presencetype = PRESENCE_NORMAL; + } //end if + } //end else + //save the current origin + VectorCopy(org, lastorg); + //move linear during one frame + VectorCopy(frame_test_vel, left_test_vel); + j = 0; + do + { + VectorAdd(org, left_test_vel, end); + //trace a bounding box + trace = AAS_TraceClientBBox(org, end, presencetype, entnum); + // +//#ifdef AAS_MOVE_DEBUG + if (visualize) + { + if (trace.startsolid) botimport.Print(PRT_MESSAGE, "PredictMovement: start solid\n"); + AAS_DebugLine(org, trace.endpos, LINECOLOR_RED); + } //end if +//#endif //AAS_MOVE_DEBUG + // + if (stopevent & (SE_ENTERAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_TOUCHCLUSTERPORTAL)) + { + numareas = AAS_TraceAreas(org, trace.endpos, areas, points, 20); + for (i = 0; i < numareas; i++) + { + if (stopevent & SE_ENTERAREA) + { + if (areas[i] == stopareanum) + { + VectorCopy(points[i], move->endpos); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->endarea = areas[i]; + move->trace = trace; + move->stopevent = SE_ENTERAREA; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + //NOTE: if not the first frame + if ((stopevent & SE_TOUCHJUMPPAD) && n) + { + if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_JUMPPAD) + { + VectorCopy(points[i], move->endpos); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->endarea = areas[i]; + move->trace = trace; + move->stopevent = SE_TOUCHJUMPPAD; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + if (stopevent & SE_TOUCHTELEPORTER) + { + if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_TELEPORTER) + { + VectorCopy(points[i], move->endpos); + move->endarea = areas[i]; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_TOUCHTELEPORTER; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + if (stopevent & SE_TOUCHCLUSTERPORTAL) + { + if (aasworld.areasettings[areas[i]].contents & AREACONTENTS_CLUSTERPORTAL) + { + VectorCopy(points[i], move->endpos); + move->endarea = areas[i]; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_TOUCHCLUSTERPORTAL; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end for + } //end if + // + if (stopevent & SE_HITBOUNDINGBOX) + { + if (AAS_ClipToBBox(&trace, org, trace.endpos, presencetype, mins, maxs)) + { + VectorCopy(trace.endpos, move->endpos); + move->endarea = AAS_PointAreaNum(move->endpos); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITBOUNDINGBOX; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + //move the entity to the trace end point + VectorCopy(trace.endpos, org); + //if there was a collision + if (trace.fraction < 1.0) + { + //get the plane the bounding box collided with + plane = AAS_PlaneFromNum(trace.planenum); + // + if (stopevent & SE_HITGROUNDAREA) + { + if (DotProduct(plane->normal, up) > phys_maxsteepness) + { + VectorCopy(org, start); + start[2] += 0.5; + if (AAS_PointAreaNum(start) == stopareanum) + { + VectorCopy(start, move->endpos); + move->endarea = stopareanum; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUNDAREA; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + //assume there's no step + step = qfalse; + //if it is a vertical plane and the bot didn't jump recently + if (plane->normal[2] == 0 && (jump_frame < 0 || n - jump_frame > 2)) + { + //check for a step + VectorMA(org, -0.25, plane->normal, start); + VectorCopy(start, stepend); + start[2] += phys_maxstep; + steptrace = AAS_TraceClientBBox(start, stepend, presencetype, entnum); + // + if (!steptrace.startsolid) + { + plane2 = AAS_PlaneFromNum(steptrace.planenum); + if (DotProduct(plane2->normal, up) > phys_maxsteepness) + { + VectorSubtract(end, steptrace.endpos, left_test_vel); + left_test_vel[2] = 0; + frame_test_vel[2] = 0; +//#ifdef AAS_MOVE_DEBUG + if (visualize) + { + if (steptrace.endpos[2] - org[2] > 0.125) + { + VectorCopy(org, start); + start[2] = steptrace.endpos[2]; + AAS_DebugLine(org, start, LINECOLOR_BLUE); + } //end if + } //end if +//#endif //AAS_MOVE_DEBUG + org[2] = steptrace.endpos[2]; + step = qtrue; + } //end if + } //end if + } //end if + // + if (!step) + { + //velocity left to test for this frame is the projection + //of the current test velocity into the hit plane + VectorMA(left_test_vel, -DotProduct(left_test_vel, plane->normal), + plane->normal, left_test_vel); + //store the old velocity for landing check + VectorCopy(frame_test_vel, old_frame_test_vel); + //test velocity for the next frame is the projection + //of the velocity of the current frame into the hit plane + VectorMA(frame_test_vel, -DotProduct(frame_test_vel, plane->normal), + plane->normal, frame_test_vel); + //check for a landing on an almost horizontal floor + if (DotProduct(plane->normal, up) > phys_maxsteepness) + { + onground = qtrue; + } //end if + if (stopevent & SE_HITGROUNDDAMAGE) + { + delta = 0; + if (old_frame_test_vel[2] < 0 && + frame_test_vel[2] > old_frame_test_vel[2] && + !onground) + { + delta = old_frame_test_vel[2]; + } //end if + else if (onground) + { + delta = frame_test_vel[2] - old_frame_test_vel[2]; + } //end else + if (delta) + { + delta = delta * 10; + delta = delta * delta * 0.0001; + if (swimming) delta = 0; + // never take falling damage if completely underwater + /* + if (ent->waterlevel == 3) return; + if (ent->waterlevel == 2) delta *= 0.25; + if (ent->waterlevel == 1) delta *= 0.5; + */ + if (delta > 40) + { + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorCopy(frame_test_vel, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUNDDAMAGE; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end if + } //end if + //extra check to prevent endless loop + if (++j > 20) return qfalse; + //while there is a plane hit + } while(trace.fraction < 1.0); + //if going down + if (frame_test_vel[2] <= 10) + { + //check for a liquid at the feet of the bot + VectorCopy(org, feet); + feet[2] -= 22; + pc = AAS_PointContents(feet); + //get event from pc + event = SE_NONE; + if (pc & CONTENTS_LAVA) event |= SE_ENTERLAVA; + if (pc & CONTENTS_SLIME) event |= SE_ENTERSLIME; + if (pc & CONTENTS_WATER) event |= SE_ENTERWATER; + // + areanum = AAS_PointAreaNum(org); + if (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA) + event |= SE_ENTERLAVA; + if (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME) + event |= SE_ENTERSLIME; + if (aasworld.areasettings[areanum].contents & AREACONTENTS_WATER) + event |= SE_ENTERWATER; + //if in lava or slime + if (event & stopevent) + { + VectorCopy(org, move->endpos); + move->endarea = areanum; + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->stopevent = event & stopevent; + move->presencetype = presencetype; + move->endcontents = pc; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + // + onground = AAS_OnGround(org, presencetype, entnum); + //if onground and on the ground for at least one whole frame + if (onground) + { + if (stopevent & SE_HITGROUND) + { + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_HITGROUND; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + else if (stopevent & SE_LEAVEGROUND) + { + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_LEAVEGROUND; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end else if + else if (stopevent & SE_GAP) + { + aas_trace_t gaptrace; + + VectorCopy(org, start); + VectorCopy(start, end); + end[2] -= 48 + aassettings.phys_maxbarrier; + gaptrace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + //if solid is found the bot cannot walk any further and will not fall into a gap + if (!gaptrace.startsolid) + { + //if it is a gap (lower than one step height) + if (gaptrace.endpos[2] < org[2] - aassettings.phys_maxstep - 1) + { + if (!(AAS_PointContents(end) & CONTENTS_WATER)) + { + VectorCopy(lastorg, move->endpos); + move->endarea = AAS_PointAreaNum(lastorg); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->trace = trace; + move->stopevent = SE_GAP; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + return qtrue; + } //end if + } //end if + } //end if + } //end else if + } //end for + // + VectorCopy(org, move->endpos); + move->endarea = AAS_PointAreaNum(org); + VectorScale(frame_test_vel, 1/frametime, move->velocity); + move->stopevent = SE_NONE; + move->presencetype = presencetype; + move->endcontents = 0; + move->time = n * frametime; + move->frames = n; + // + return qtrue; +} //end of the function AAS_ClientMovementPrediction +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +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) +{ + vec3_t mins, maxs; + return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, + velocity, cmdmove, cmdframes, maxframes, + frametime, stopevent, stopareanum, + mins, maxs, visualize); +} //end of the function AAS_PredictClientMovement +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ClientMovementHitBBox(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, + vec3_t mins, vec3_t maxs, int visualize) +{ + return AAS_ClientMovementPrediction(move, entnum, origin, presencetype, onground, + velocity, cmdmove, cmdframes, maxframes, + frametime, SE_HITBOUNDINGBOX, 0, + mins, maxs, visualize); +} //end of the function AAS_ClientMovementHitBBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir) +{ + vec3_t velocity, cmdmove; + aas_clientmove_t move; + + VectorClear(velocity); + if (!AAS_Swimming(origin)) dir[2] = 0; + VectorNormalize(dir); + VectorScale(dir, 400, cmdmove); + cmdmove[2] = 224; + AAS_ClearShownDebugLines(); + AAS_PredictClientMovement(&move, entnum, origin, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 13, 13, 0.1f, SE_HITGROUND, 0, qtrue);//SE_LEAVEGROUND); + if (move.stopevent & SE_LEAVEGROUND) + { + botimport.Print(PRT_MESSAGE, "leave ground\n"); + } //end if +} //end of the function TestMovementPrediction +//=========================================================================== +// calculates the horizontal velocity needed to perform a jump from start +// to end +// +// Parameter: zvel : z velocity for jump +// start : start position of jump +// end : end position of jump +// *speed : returned speed for jump +// Returns: qfalse if too high or too far from start to end +// Changes Globals: - +//=========================================================================== +int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity) +{ + float phys_gravity, phys_maxvelocity; + float maxjump, height2fall, t, top; + vec3_t dir; + + phys_gravity = aassettings.phys_gravity; + phys_maxvelocity = aassettings.phys_maxvelocity; + + //maximum height a player can jump with the given initial z velocity + maxjump = 0.5 * phys_gravity * (zvel / phys_gravity) * (zvel / phys_gravity); + //top of the parabolic jump + top = start[2] + maxjump; + //height the bot will fall from the top + height2fall = top - end[2]; + //if the goal is to high to jump to + if (height2fall < 0) + { + *velocity = phys_maxvelocity; + return 0; + } //end if + //time a player takes to fall the height + t = sqrt(height2fall / (0.5 * phys_gravity)); + //direction from start to end + VectorSubtract(end, start, dir); + // + if ( (t + zvel / phys_gravity) == 0.0f ) { + *velocity = phys_maxvelocity; + return 0; + } + //calculate horizontal speed + *velocity = sqrt(dir[0]*dir[0] + dir[1]*dir[1]) / (t + zvel / phys_gravity); + //the horizontal speed must be lower than the max speed + if (*velocity > phys_maxvelocity) + { + *velocity = phys_maxvelocity; + return 0; + } //end if + return 1; +} //end of the function AAS_HorizontalVelocityForJump diff --git a/code/botlib/be_aas_move.h b/code/botlib/be_aas_move.h new file mode 100644 index 0000000..b00e41a --- /dev/null +++ b/code/botlib/be_aas_move.h @@ -0,0 +1,71 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_move.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_move.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +extern aas_settings_t aassettings; +#endif //AASINTERN + +//movement prediction +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); +//predict movement until bounding box is hit +int AAS_ClientMovementHitBBox(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, + vec3_t mins, vec3_t maxs, int visualize); +//returns true if on the ground at the given origin +int AAS_OnGround(vec3_t origin, int presencetype, int passent); +//returns true if swimming at the given origin +int AAS_Swimming(vec3_t origin); +//returns the jump reachability run start point +void AAS_JumpReachRunStart(struct aas_reachability_s *reach, vec3_t runstart); +//returns true if against a ladder at the given origin +int AAS_AgainstLadder(vec3_t origin); +//rocket jump Z velocity when rocket-jumping at origin +float AAS_RocketJumpZVelocity(vec3_t origin); +//bfg jump Z velocity when bfg-jumping at origin +float AAS_BFGJumpZVelocity(vec3_t origin); +//calculates the horizontal velocity needed for a jump and returns true this velocity could be calculated +int AAS_HorizontalVelocityForJump(float zvel, vec3_t start, vec3_t end, float *velocity); +// +void AAS_SetMovedir(vec3_t angles, vec3_t movedir); +// +int AAS_DropToFloor(vec3_t origin, vec3_t mins, vec3_t maxs); +// +void AAS_InitSettings(void); diff --git a/code/botlib/be_aas_optimize.c b/code/botlib/be_aas_optimize.c new file mode 100644 index 0000000..ea0d2da --- /dev/null +++ b/code/botlib/be_aas_optimize.c @@ -0,0 +1,312 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_optimize.c + * + * desc: decreases the .aas file size after the reachabilities have + * been calculated, just dumps all the faces, edges and vertexes + * + * $Archive: /MissionPack/code/botlib/be_aas_optimize.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_libvar.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +typedef struct optimized_s +{ + //vertexes + int numvertexes; + aas_vertex_t *vertexes; + //edges + int numedges; + aas_edge_t *edges; + //edge index + int edgeindexsize; + aas_edgeindex_t *edgeindex; + //faces + int numfaces; + aas_face_t *faces; + //face index + int faceindexsize; + aas_faceindex_t *faceindex; + //convex areas + int numareas; + aas_area_t *areas; + // + int *vertexoptimizeindex; + int *edgeoptimizeindex; + int *faceoptimizeindex; +} optimized_t; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepEdge(aas_edge_t *edge) +{ + return 1; +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeEdge(optimized_t *optimized, int edgenum) +{ + int i, optedgenum; + aas_edge_t *edge, *optedge; + + edge = &aasworld.edges[abs(edgenum)]; + if (!AAS_KeepEdge(edge)) return 0; + + optedgenum = optimized->edgeoptimizeindex[abs(edgenum)]; + if (optedgenum) + { + //keep the edge reversed sign + if (edgenum > 0) return optedgenum; + else return -optedgenum; + } //end if + + optedge = &optimized->edges[optimized->numedges]; + + for (i = 0; i < 2; i++) + { + if (optimized->vertexoptimizeindex[edge->v[i]]) + { + optedge->v[i] = optimized->vertexoptimizeindex[edge->v[i]]; + } //end if + else + { + VectorCopy(aasworld.vertexes[edge->v[i]], optimized->vertexes[optimized->numvertexes]); + optedge->v[i] = optimized->numvertexes; + optimized->vertexoptimizeindex[edge->v[i]] = optimized->numvertexes; + optimized->numvertexes++; + } //end else + } //end for + optimized->edgeoptimizeindex[abs(edgenum)] = optimized->numedges; + optedgenum = optimized->numedges; + optimized->numedges++; + //keep the edge reversed sign + if (edgenum > 0) return optedgenum; + else return -optedgenum; +} //end of the function AAS_OptimizeEdge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_KeepFace(aas_face_t *face) +{ + if (!(face->faceflags & FACE_LADDER)) return 0; + else return 1; +} //end of the function AAS_KeepFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_OptimizeFace(optimized_t *optimized, int facenum) +{ + int i, edgenum, optedgenum, optfacenum; + aas_face_t *face, *optface; + + face = &aasworld.faces[abs(facenum)]; + if (!AAS_KeepFace(face)) return 0; + + optfacenum = optimized->faceoptimizeindex[abs(facenum)]; + if (optfacenum) + { + //keep the face side sign + if (facenum > 0) return optfacenum; + else return -optfacenum; + } //end if + + optface = &optimized->faces[optimized->numfaces]; + Com_Memcpy(optface, face, sizeof(aas_face_t)); + + optface->numedges = 0; + optface->firstedge = optimized->edgeindexsize; + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + optedgenum = AAS_OptimizeEdge(optimized, edgenum); + if (optedgenum) + { + optimized->edgeindex[optface->firstedge + optface->numedges] = optedgenum; + optface->numedges++; + optimized->edgeindexsize++; + } //end if + } //end for + optimized->faceoptimizeindex[abs(facenum)] = optimized->numfaces; + optfacenum = optimized->numfaces; + optimized->numfaces++; + //keep the face side sign + if (facenum > 0) return optfacenum; + else return -optfacenum; +} //end of the function AAS_OptimizeFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeArea(optimized_t *optimized, int areanum) +{ + int i, facenum, optfacenum; + aas_area_t *area, *optarea; + + area = &aasworld.areas[areanum]; + optarea = &optimized->areas[areanum]; + Com_Memcpy(optarea, area, sizeof(aas_area_t)); + + optarea->numfaces = 0; + optarea->firstface = optimized->faceindexsize; + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + optfacenum = AAS_OptimizeFace(optimized, facenum); + if (optfacenum) + { + optimized->faceindex[optarea->firstface + optarea->numfaces] = optfacenum; + optarea->numfaces++; + optimized->faceindexsize++; + } //end if + } //end for +} //end of the function AAS_OptimizeArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeAlloc(optimized_t *optimized) +{ + optimized->vertexes = (aas_vertex_t *) GetClearedMemory(aasworld.numvertexes * sizeof(aas_vertex_t)); + optimized->numvertexes = 0; + optimized->edges = (aas_edge_t *) GetClearedMemory(aasworld.numedges * sizeof(aas_edge_t)); + optimized->numedges = 1; //edge zero is a dummy + optimized->edgeindex = (aas_edgeindex_t *) GetClearedMemory(aasworld.edgeindexsize * sizeof(aas_edgeindex_t)); + optimized->edgeindexsize = 0; + optimized->faces = (aas_face_t *) GetClearedMemory(aasworld.numfaces * sizeof(aas_face_t)); + optimized->numfaces = 1; //face zero is a dummy + optimized->faceindex = (aas_faceindex_t *) GetClearedMemory(aasworld.faceindexsize * sizeof(aas_faceindex_t)); + optimized->faceindexsize = 0; + optimized->areas = (aas_area_t *) GetClearedMemory(aasworld.numareas * sizeof(aas_area_t)); + optimized->numareas = aasworld.numareas; + // + optimized->vertexoptimizeindex = (int *) GetClearedMemory(aasworld.numvertexes * sizeof(int)); + optimized->edgeoptimizeindex = (int *) GetClearedMemory(aasworld.numedges * sizeof(int)); + optimized->faceoptimizeindex = (int *) GetClearedMemory(aasworld.numfaces * sizeof(int)); +} //end of the function AAS_OptimizeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_OptimizeStore(optimized_t *optimized) +{ + //store the optimized vertexes + if (aasworld.vertexes) FreeMemory(aasworld.vertexes); + aasworld.vertexes = optimized->vertexes; + aasworld.numvertexes = optimized->numvertexes; + //store the optimized edges + if (aasworld.edges) FreeMemory(aasworld.edges); + aasworld.edges = optimized->edges; + aasworld.numedges = optimized->numedges; + //store the optimized edge index + if (aasworld.edgeindex) FreeMemory(aasworld.edgeindex); + aasworld.edgeindex = optimized->edgeindex; + aasworld.edgeindexsize = optimized->edgeindexsize; + //store the optimized faces + if (aasworld.faces) FreeMemory(aasworld.faces); + aasworld.faces = optimized->faces; + aasworld.numfaces = optimized->numfaces; + //store the optimized face index + if (aasworld.faceindex) FreeMemory(aasworld.faceindex); + aasworld.faceindex = optimized->faceindex; + aasworld.faceindexsize = optimized->faceindexsize; + //store the optimized areas + if (aasworld.areas) FreeMemory(aasworld.areas); + aasworld.areas = optimized->areas; + aasworld.numareas = optimized->numareas; + //free optimize indexes + FreeMemory(optimized->vertexoptimizeindex); + FreeMemory(optimized->edgeoptimizeindex); + FreeMemory(optimized->faceoptimizeindex); +} //end of the function AAS_OptimizeStore +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Optimize(void) +{ + int i, sign; + optimized_t optimized; + + AAS_OptimizeAlloc(&optimized); + for (i = 1; i < aasworld.numareas; i++) + { + AAS_OptimizeArea(&optimized, i); + } //end for + //reset the reachability face pointers + for (i = 0; i < aasworld.reachabilitysize; i++) + { + //NOTE: for TRAVEL_ELEVATOR the facenum is the model number of + // the elevator + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) continue; + //NOTE: for TRAVEL_JUMPPAD the facenum is the Z velocity and the edgenum is the hor velocity + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) continue; + //NOTE: for TRAVEL_FUNCBOB the facenum and edgenum contain other coded information + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) continue; + // + sign = aasworld.reachability[i].facenum; + aasworld.reachability[i].facenum = optimized.faceoptimizeindex[abs(aasworld.reachability[i].facenum)]; + if (sign < 0) aasworld.reachability[i].facenum = -aasworld.reachability[i].facenum; + sign = aasworld.reachability[i].edgenum; + aasworld.reachability[i].edgenum = optimized.edgeoptimizeindex[abs(aasworld.reachability[i].edgenum)]; + if (sign < 0) aasworld.reachability[i].edgenum = -aasworld.reachability[i].edgenum; + } //end for + //store the optimized AAS data into aasworld + AAS_OptimizeStore(&optimized); + //print some nice stuff :) + botimport.Print(PRT_MESSAGE, "AAS data optimized.\n"); +} //end of the function AAS_Optimize diff --git a/code/botlib/be_aas_optimize.h b/code/botlib/be_aas_optimize.h new file mode 100644 index 0000000..799c28a --- /dev/null +++ b/code/botlib/be_aas_optimize.h @@ -0,0 +1,33 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_optimize.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_optimize.h $ + * + *****************************************************************************/ + +void AAS_Optimize(void); + diff --git a/code/botlib/be_aas_reach.c b/code/botlib/be_aas_reach.c new file mode 100644 index 0000000..6c45795 --- /dev/null +++ b/code/botlib/be_aas_reach.c @@ -0,0 +1,4535 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_reach.c + * + * desc: reachability calculations + * + * $Archive: /MissionPack/code/botlib/be_aas_reach.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_libvar.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + +extern int Sys_MilliSeconds(void); + + +extern botlib_import_t botimport; + +//#define REACH_DEBUG + +//NOTE: all travel times are in hundreth of a second +//maximum number of reachability links +#define AAS_MAX_REACHABILITYSIZE 65536 +//number of areas reachability is calculated for each frame +#define REACHABILITYAREASPERCYCLE 15 +//number of units reachability points are placed inside the areas +#define INSIDEUNITS 2 +#define INSIDEUNITS_WALKEND 5 +#define INSIDEUNITS_WALKSTART 0.1 +#define INSIDEUNITS_WATERJUMP 15 +//area flag used for weapon jumping +#define AREA_WEAPONJUMP 8192 //valid area to weapon jump to +//number of reachabilities of each type +int reach_swim; //swim +int reach_equalfloor; //walk on floors with equal height +int reach_step; //step up +int reach_walk; //walk of step +int reach_barrier; //jump up to a barrier +int reach_waterjump; //jump out of water +int reach_walkoffledge; //walk of a ledge +int reach_jump; //jump +int reach_ladder; //climb or descent a ladder +int reach_teleport; //teleport +int reach_elevator; //use an elevator +int reach_funcbob; //use a func bob +int reach_grapple; //grapple hook +int reach_doublejump; //double jump +int reach_rampjump; //ramp jump +int reach_strafejump; //strafe jump (just normal jump but further) +int reach_rocketjump; //rocket jump +int reach_bfgjump; //bfg jump +int reach_jumppad; //jump pads +//if true grapple reachabilities are skipped +int calcgrapplereach; +//linked reachability +typedef struct aas_lreachability_s +{ + int areanum; //number of the reachable area + int facenum; //number of the face towards the other area + int edgenum; //number of the edge towards the other area + vec3_t start; //start point of inter area movement + vec3_t end; //end point of inter area movement + int traveltype; //type of travel required to get to the area + unsigned short int traveltime; //travel time of the inter area movement + // + struct aas_lreachability_s *next; +} aas_lreachability_t; +//temporary reachabilities +aas_lreachability_t *reachabilityheap; //heap with reachabilities +aas_lreachability_t *nextreachability; //next free reachability from the heap +aas_lreachability_t **areareachability; //reachability links for every area +int numlreachabilities; + +//=========================================================================== +// returns the surface area of the given face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FaceArea(aas_face_t *face) +{ + int i, edgenum, side; + float total; + vec_t *v; + vec3_t d1, d2, cross; + aas_edge_t *edge; + + edgenum = aasworld.edgeindex[face->firstedge]; + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + v = aasworld.vertexes[edge->v[side]]; + + total = 0; + for (i = 1; i < face->numedges - 1; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + VectorSubtract(aasworld.vertexes[edge->v[side]], v, d1); + VectorSubtract(aasworld.vertexes[edge->v[!side]], v, d2); + CrossProduct(d1, d2, cross); + total += 0.5 * VectorLength(cross); + } //end for + return total; +} //end of the function AAS_FaceArea +//=========================================================================== +// returns the volume of an area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaVolume(int areanum) +{ + int i, edgenum, facenum, side; + vec_t d, a, volume; + vec3_t corner; + aas_plane_t *plane; + aas_edge_t *edge; + aas_face_t *face; + aas_area_t *area; + + area = &aasworld.areas[areanum]; + facenum = aasworld.faceindex[area->firstface]; + face = &aasworld.faces[abs(facenum)]; + edgenum = aasworld.edgeindex[face->firstedge]; + edge = &aasworld.edges[abs(edgenum)]; + // + VectorCopy(aasworld.vertexes[edge->v[0]], corner); + + //make tetrahedrons to all other faces + volume = 0; + for (i = 0; i < area->numfaces; i++) + { + facenum = abs(aasworld.faceindex[area->firstface + i]); + face = &aasworld.faces[facenum]; + side = face->backarea != areanum; + plane = &aasworld.planes[face->planenum ^ side]; + d = -(DotProduct (corner, plane->normal) - plane->dist); + a = AAS_FaceArea(face); + volume += d * a; + } //end for + + volume /= 3; + return volume; +} //end of the function AAS_AreaVolume +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableLinkArea(aas_link_t *areas) +{ + aas_link_t *link; + + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaGrounded(link->areanum) || AAS_AreaSwim(link->areanum)) + { + return link->areanum; + } //end if + } //end for + // + for (link = areas; link; link = link->next_area) + { + if (link->areanum) return link->areanum; + //FIXME: this is a bad idea when the reachability is not yet + // calculated when the level items are loaded + if (AAS_AreaReachability(link->areanum)) + return link->areanum; + } //end for + return 0; +} //end of the function AAS_BestReachableLinkArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetJumpPadInfo(int ent, vec3_t areastart, vec3_t absmins, vec3_t absmaxs, vec3_t velocity) +{ + int modelnum, ent2; + float speed, height, gravity, time, dist, forward; + vec3_t origin, angles, teststart, ent2origin; + aas_trace_t trace; + char model[MAX_EPAIRKEY]; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + + // + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) speed = 1000; + VectorClear(angles); + //get the mins, maxs and origin of the model + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); + if (model[0]) modelnum = atoi(model+1); + else modelnum = 0; + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); + VectorAdd(origin, absmins, absmins); + VectorAdd(origin, absmaxs, absmaxs); + VectorAdd(absmins, absmaxs, origin); + VectorScale (origin, 0.5, origin); + + //get the start areas + VectorCopy(origin, teststart); + teststart[2] += 64; + trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); + VectorCopy(origin, areastart); + } //end if + else + { + VectorCopy(trace.endpos, areastart); + } //end else + areastart[2] += 0.125; + // + //AAS_DrawPermanentCross(origin, 4, 4); + //get the target entity + AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); + for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) + { + if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; + if (!strcmp(targetname, target)) break; + } //end for + if (!ent2) + { + botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); + return qfalse; + } //end if + AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); + // + height = ent2origin[2] - origin[2]; + gravity = aassettings.phys_gravity; + time = sqrt( height / ( 0.5 * gravity ) ); + if (!time) + { + botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); + return qfalse; + } //end if + // set s.origin2 to the push velocity + VectorSubtract ( ent2origin, origin, velocity); + dist = VectorNormalize( velocity); + forward = dist / time; + //FIXME: why multiply by 1.1 + forward *= 1.1f; + VectorScale(velocity, forward, velocity); + velocity[2] = time * gravity; + return qtrue; +} //end of the function AAS_GetJumpPadInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs) +{ + int ent, bot_visualizejumppads, bestareanum; + float volume, bestareavolume; + vec3_t areastart, cmdmove, bboxmins, bboxmaxs; + vec3_t absmins, absmaxs, velocity; + aas_clientmove_t move; + aas_link_t *areas, *link; + char classname[MAX_EPAIRKEY]; + +#ifdef BSPC + bot_visualizejumppads = 0; +#else + bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); +#endif + VectorAdd(origin, mins, bboxmins); + VectorAdd(origin, maxs, bboxmaxs); + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (strcmp(classname, "trigger_push")) continue; + // + if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; + //get the areas the jump pad brush is in + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaJumpPad(link->areanum)) break; + } //end for + if (!link) + { + botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); + AAS_UnlinkFromAreas(areas); + continue; + } //end if + // + //botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); + // + VectorSet(cmdmove, 0, 0, 0); + Com_Memset(&move, 0, sizeof(aas_clientmove_t)); + AAS_ClientMovementHitBBox(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1f, bboxmins, bboxmaxs, bot_visualizejumppads); + if (move.frames < 30) + { + bestareanum = 0; + bestareavolume = 0; + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + volume = AAS_AreaVolume(link->areanum); + if (volume >= bestareavolume) + { + bestareanum = link->areanum; + bestareavolume = volume; + } //end if + } //end if + AAS_UnlinkFromAreas(areas); + return bestareanum; + } //end if + AAS_UnlinkFromAreas(areas); + } //end for + return 0; +} //end of the function AAS_BestReachableFromJumpPadArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin) +{ + int areanum, i, j, k, l; + aas_link_t *areas; + vec3_t absmins, absmaxs; + //vec3_t bbmins, bbmaxs; + vec3_t start, end; + aas_trace_t trace; + + if (!aasworld.loaded) + { + botimport.Print(PRT_ERROR, "AAS_BestReachableArea: aas not loaded\n"); + return 0; + } //end if + //find a point in an area + VectorCopy(origin, start); + areanum = AAS_PointAreaNum(start); + //while no area found fudge around a little + for (i = 0; i < 5 && !areanum; i++) + { + for (j = 0; j < 5 && !areanum; j++) + { + for (k = -1; k <= 1 && !areanum; k++) + { + for (l = -1; l <= 1 && !areanum; l++) + { + VectorCopy(origin, start); + start[0] += (float) j * 4 * k; + start[1] += (float) j * 4 * l; + start[2] += (float) i * 4; + areanum = AAS_PointAreaNum(start); + } //end for + } //end for + } //end for + } //end for + //if an area was found + if (areanum) + { + //drop client bbox down and try again + VectorCopy(start, end); + start[2] += 0.25; + end[2] -= 50; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + areanum = AAS_PointAreaNum(trace.endpos); + VectorCopy(trace.endpos, goalorigin); + //FIXME: cannot enable next line right now because the reachability + // does not have to be calculated when the level items are loaded + //if the origin is in an area with reachability + //if (AAS_AreaReachability(areanum)) return areanum; + if (areanum) return areanum; + } //end if + else + { + //it can very well happen that the AAS_PointAreaNum function tells that + //a point is in an area and that starting a AAS_TraceClientBBox from that + //point will return trace.startsolid qtrue +#if 0 + if (AAS_PointAreaNum(start)) + { + Log_Write("point %f %f %f in area %d but trace startsolid", start[0], start[1], start[2], areanum); + AAS_DrawPermanentCross(start, 4, LINECOLOR_RED); + } //end if + botimport.Print(PRT_MESSAGE, "AAS_BestReachableArea: start solid\n"); +#endif + VectorCopy(start, goalorigin); + return areanum; + } //end else + } //end if + // + //AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, bbmins, bbmaxs); + //NOTE: the goal origin does not have to be in the goal area + // because the bot will have to move towards the item origin anyway + VectorCopy(origin, goalorigin); + // + VectorAdd(origin, mins, absmins); + VectorAdd(origin, maxs, absmaxs); + //add bounding box size + //VectorSubtract(absmins, bbmaxs, absmins); + //VectorSubtract(absmaxs, bbmins, absmaxs); + //link an invalid (-1) entity + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + //get the reachable link arae + areanum = AAS_BestReachableLinkArea(areas); + //unlink the invalid entity + AAS_UnlinkFromAreas(areas); + // + return areanum; +} //end of the function AAS_BestReachableArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetupReachabilityHeap(void) +{ + int i; + + reachabilityheap = (aas_lreachability_t *) GetClearedMemory( + AAS_MAX_REACHABILITYSIZE * sizeof(aas_lreachability_t)); + for (i = 0; i < AAS_MAX_REACHABILITYSIZE-1; i++) + { + reachabilityheap[i].next = &reachabilityheap[i+1]; + } //end for + reachabilityheap[AAS_MAX_REACHABILITYSIZE-1].next = NULL; + nextreachability = reachabilityheap; + numlreachabilities = 0; +} //end of the function AAS_InitReachabilityHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutDownReachabilityHeap(void) +{ + FreeMemory(reachabilityheap); + numlreachabilities = 0; +} //end of the function AAS_ShutDownReachabilityHeap +//=========================================================================== +// returns a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_AllocReachability(void) +{ + aas_lreachability_t *r; + + if (!nextreachability) return NULL; + //make sure the error message only shows up once + if (!nextreachability->next) AAS_Error("AAS_MAX_REACHABILITYSIZE"); + // + r = nextreachability; + nextreachability = nextreachability->next; + numlreachabilities++; + return r; +} //end of the function AAS_AllocReachability +//=========================================================================== +// frees a reachability link +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeReachability(aas_lreachability_t *lreach) +{ + Com_Memset(lreach, 0, sizeof(aas_lreachability_t)); + + lreach->next = nextreachability; + nextreachability = lreach; + numlreachabilities--; +} //end of the function AAS_FreeReachability +//=========================================================================== +// returns qtrue if the area has reachability links +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachability(int areanum) +{ + if (areanum < 0 || areanum >= aasworld.numareas) + { + AAS_Error("AAS_AreaReachability: areanum %d out of range", areanum); + return 0; + } //end if + return aasworld.areasettings[areanum].numreachableareas; +} //end of the function AAS_AreaReachability +//=========================================================================== +// returns the surface area of all ground faces together of the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_AreaGroundFaceArea(int areanum) +{ + int i; + float total; + aas_area_t *area; + aas_face_t *face; + + total = 0; + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; + if (!(face->faceflags & FACE_GROUND)) continue; + // + total += AAS_FaceArea(face); + } //end for + return total; +} //end of the function AAS_AreaGroundFaceArea +//=========================================================================== +// returns the center of a face +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FaceCenter(int facenum, vec3_t center) +{ + int i; + float scale; + aas_face_t *face; + aas_edge_t *edge; + + face = &aasworld.faces[facenum]; + + VectorClear(center); + for (i = 0; i < face->numedges; i++) + { + edge = &aasworld.edges[abs(aasworld.edgeindex[face->firstedge + i])]; + VectorAdd(center, aasworld.vertexes[edge->v[0]], center); + VectorAdd(center, aasworld.vertexes[edge->v[1]], center); + } //end for + scale = 0.5 / face->numedges; + VectorScale(center, scale, center); +} //end of the function AAS_FaceCenter +//=========================================================================== +// returns the maximum distance a player can fall before being damaged +// damage = deltavelocity*deltavelocity * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FallDamageDistance(void) +{ + float maxzvelocity, gravity, t; + + maxzvelocity = sqrt(30 * 10000); + gravity = aassettings.phys_gravity; + t = maxzvelocity / gravity; + return 0.5 * gravity * t * t; +} //end of the function AAS_FallDamageDistance +//=========================================================================== +// distance = 0.5 * gravity * t * t +// vel = t * gravity +// damage = vel * vel * 0.0001 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_FallDelta(float distance) +{ + float t, delta, gravity; + + gravity = aassettings.phys_gravity; + t = sqrt(fabs(distance) * 2 / gravity); + delta = t * gravity; + return delta * delta * 0.0001; +} //end of the function AAS_FallDelta +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpHeight(float phys_jumpvel) +{ + float phys_gravity; + + phys_gravity = aassettings.phys_gravity; + //maximum height a player can jump with the given initial z velocity + return 0.5 * phys_gravity * (phys_jumpvel / phys_gravity) * (phys_jumpvel / phys_gravity); +} //end of the function MaxJumpHeight +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float AAS_MaxJumpDistance(float phys_jumpvel) +{ + float phys_gravity, phys_maxvelocity, t; + + phys_gravity = aassettings.phys_gravity; + phys_maxvelocity = aassettings.phys_maxvelocity; + //time a player takes to fall the height + t = sqrt(aassettings.rs_maxjumpfallheight / (0.5 * phys_gravity)); + //maximum distance + return phys_maxvelocity * (t + phys_jumpvel / phys_gravity); +} //end of the function AAS_MaxJumpDistance +//=========================================================================== +// returns true if a player can only crouch in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaCrouch(int areanum) +{ + if (!(aasworld.areasettings[areanum].presencetype & PRESENCE_NORMAL)) return qtrue; + else return qfalse; +} //end of the function AAS_AreaCrouch +//=========================================================================== +// returns qtrue if it is possible to swim in the area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaSwim(int areanum) +{ + if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; + else return qfalse; +} //end of the function AAS_AreaSwim +//=========================================================================== +// returns qtrue if the area contains a liquid +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLiquid(int areanum) +{ + if (aasworld.areasettings[areanum].areaflags & AREA_LIQUID) return qtrue; + else return qfalse; +} //end of the function AAS_AreaLiquid +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLava(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_LAVA); +} //end of the function AAS_AreaLava +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaSlime(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_SLIME); +} //end of the function AAS_AreaSlime +//=========================================================================== +// returns qtrue if the area contains ground faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaGrounded(int areanum) +{ + return (aasworld.areasettings[areanum].areaflags & AREA_GROUNDED); +} //end of the function AAS_AreaGround +//=========================================================================== +// returns true if the area contains ladder faces +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaLadder(int areanum) +{ + return (aasworld.areasettings[areanum].areaflags & AREA_LADDER); +} //end of the function AAS_AreaLadder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaJumpPad(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_JUMPPAD); +} //end of the function AAS_AreaJumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTeleporter(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_TELEPORTER); +} //end of the function AAS_AreaTeleporter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaClusterPortal(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_CLUSTERPORTAL); +} //end of the function AAS_AreaClusterPortal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaDoNotEnter(int areanum) +{ + return (aasworld.areasettings[areanum].contents & AREACONTENTS_DONOTENTER); +} //end of the function AAS_AreaDoNotEnter +//=========================================================================== +// returns the time it takes perform a barrier jump +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_BarrierJumpTravelTime(void) +{ + return aassettings.phys_jumpvel / (aassettings.phys_gravity * 0.1); +} //end op the function AAS_BarrierJumpTravelTime +//=========================================================================== +// returns true if there already exists a reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_ReachabilityExists(int area1num, int area2num) +{ + aas_lreachability_t *r; + + for (r = areareachability[area1num]; r; r = r->next) + { + if (r->areanum == area2num) return qtrue; + } //end for + return qfalse; +} //end of the function AAS_ReachabilityExists +//=========================================================================== +// returns true if there is a solid just after the end point when going +// from start to end +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearbySolidOrGap(vec3_t start, vec3_t end) +{ + vec3_t dir, testpoint; + int areanum; + + VectorSubtract(end, start, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorMA(end, 48, dir, testpoint); + + areanum = AAS_PointAreaNum(testpoint); + if (!areanum) + { + testpoint[2] += 16; + areanum = AAS_PointAreaNum(testpoint); + if (!areanum) return qtrue; + } //end if + VectorMA(end, 64, dir, testpoint); + areanum = AAS_PointAreaNum(testpoint); + if (areanum) + { + if (!AAS_AreaSwim(areanum) && !AAS_AreaGrounded(areanum)) return qtrue; + } //end if + return qfalse; +} //end of the function AAS_SolidGapTime +//=========================================================================== +// searches for swim reachabilities between adjacent areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Swim(int area1num, int area2num) +{ + int i, j, face1num, face2num, side1; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + aas_face_t *face1; + aas_plane_t *plane; + vec3_t start; + + if (!AAS_AreaSwim(area1num) || !AAS_AreaSwim(area2num)) return qfalse; + //if the second area is crouch only + if (!(aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) return qfalse; + + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + + //if the areas are not near anough + for (i = 0; i < 3; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; + if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; + } //end for + //find a shared face and create a reachability link + for (i = 0; i < area1->numfaces; i++) + { + face1num = aasworld.faceindex[area1->firstface + i]; + side1 = face1num < 0; + face1num = abs(face1num); + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = abs(aasworld.faceindex[area2->firstface + j]); + // + if (face1num == face2num) + { + AAS_FaceCenter(face1num, start); + // + if (AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER)) + { + // + face1 = &aasworld.faces[face1num]; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = face1num; + lreach->edgenum = 0; + VectorCopy(start, lreach->start); + plane = &aasworld.planes[face1->planenum ^ side1]; + VectorMA(lreach->start, -INSIDEUNITS, plane->normal, lreach->end); + lreach->traveltype = TRAVEL_SWIM; + lreach->traveltime = 1; + //if the volume of the area is rather small + if (AAS_AreaVolume(area2num) < 800) + lreach->traveltime += 200; + //if (!(AAS_PointContents(start) & MASK_WATER)) lreach->traveltime += 500; + //link the reachability + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + reach_swim++; + return qtrue; + } //end if + } //end if + } //end for + } //end for + return qfalse; +} //end of the function AAS_Reachability_Swim +//=========================================================================== +// searches for reachabilities between adjacent areas with equal floor +// heights +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_EqualFloorHeight(int area1num, int area2num) +{ + int i, j, edgenum, edgenum1, edgenum2, foundreach, side; + float height, bestheight, length, bestlength; + vec3_t dir, start, end, normal, invgravity, gravitydirection = {0, 0, -1}; + vec3_t edgevec; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge; + aas_plane_t *plane2; + aas_lreachability_t lr, *lreach; + + if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; + + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; + if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; + } //end for + //if area 2 is too high above area 1 + if (area2->mins[2] > area1->maxs[2]) return qfalse; + // + VectorCopy(gravitydirection, invgravity); + VectorInverse(invgravity); + // + bestheight = 99999; + bestlength = 0; + foundreach = qfalse; + Com_Memset(&lr, 0, sizeof(aas_lreachability_t)); //make the compiler happy + // + //check if the areas have ground faces with a common edge + //if existing use the lowest common edge for a reachability link + for (i = 0; i < area1->numfaces; i++) + { + face1 = &aasworld.faces[abs(aasworld.faceindex[area1->firstface + i])]; + if (!(face1->faceflags & FACE_GROUND)) continue; + // + for (j = 0; j < area2->numfaces; j++) + { + face2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; + if (!(face2->faceflags & FACE_GROUND)) continue; + //if there is a common edge + for (edgenum1 = 0; edgenum1 < face1->numedges; edgenum1++) + { + for (edgenum2 = 0; edgenum2 < face2->numedges; edgenum2++) + { + if (abs(aasworld.edgeindex[face1->firstedge + edgenum1]) != + abs(aasworld.edgeindex[face2->firstedge + edgenum2])) + continue; + edgenum = aasworld.edgeindex[face1->firstedge + edgenum1]; + side = edgenum < 0; + edge = &aasworld.edges[abs(edgenum)]; + //get the length of the edge + VectorSubtract(aasworld.vertexes[edge->v[1]], + aasworld.vertexes[edge->v[0]], dir); + length = VectorLength(dir); + //get the start point + VectorAdd(aasworld.vertexes[edge->v[0]], + aasworld.vertexes[edge->v[1]], start); + VectorScale(start, 0.5, start); + VectorCopy(start, end); + //get the end point several units inside area2 + //and the start point several units inside area1 + //NOTE: normal is pointing into area2 because the + //face edges are stored counter clockwise + VectorSubtract(aasworld.vertexes[edge->v[side]], + aasworld.vertexes[edge->v[!side]], edgevec); + plane2 = &aasworld.planes[face2->planenum]; + CrossProduct(edgevec, plane2->normal, normal); + VectorNormalize(normal); + // + //VectorMA(start, -1, normal, start); + VectorMA(end, INSIDEUNITS_WALKEND, normal, end); + VectorMA(start, INSIDEUNITS_WALKSTART, normal, start); + end[2] += 0.125; + // + height = DotProduct(invgravity, start); + //NOTE: if there's nearby solid or a gap area after this area + //disabled this crap + //if (AAS_NearbySolidOrGap(start, end)) height += 200; + //NOTE: disabled because it disables reachabilities to very small areas + //if (AAS_PointAreaNum(end) != area2num) continue; + //get the longest lowest edge + if (height < bestheight || + (height < bestheight + 1 && length > bestlength)) + { + bestheight = height; + bestlength = length; + //create a new reachability link + lr.areanum = area2num; + lr.facenum = 0; + lr.edgenum = edgenum; + VectorCopy(start, lr.start); + VectorCopy(end, lr.end); + lr.traveltype = TRAVEL_WALK; + lr.traveltime = 1; + foundreach = qtrue; + } //end if + } //end for + } //end for + } //end for + } //end for + if (foundreach) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = lr.areanum; + lreach->facenum = lr.facenum; + lreach->edgenum = lr.edgenum; + VectorCopy(lr.start, lreach->start); + VectorCopy(lr.end, lreach->end); + lreach->traveltype = lr.traveltype; + lreach->traveltime = lr.traveltime; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //if going into a crouch area + if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) + { + lreach->traveltime += aassettings.rs_startcrouch; + } //end if + /* + //NOTE: if there's nearby solid or a gap area after this area + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_equalfloor++; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_Reachability_EqualFloorHeight +//=========================================================================== +// searches step, barrier, waterjump and walk off ledge reachabilities +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(int area1num, int area2num) +{ + int i, j, k, l, edge1num, edge2num, areas[10], numareas; + int ground_bestarea2groundedgenum, ground_foundreach; + int water_bestarea2groundedgenum, water_foundreach; + int side1, area1swim, faceside1, groundface1num; + float dist, dist1, dist2, diff, ortdot; + //float invgravitydot; + float x1, x2, x3, x4, y1, y2, y3, y4, tmp, y; + float length, ground_bestlength, water_bestlength, ground_bestdist, water_bestdist; + vec3_t v1, v2, v3, v4, tmpv, p1area1, p1area2, p2area1, p2area2; + vec3_t normal, ort, edgevec, start, end, dir; + vec3_t ground_beststart = {0, 0, 0}, ground_bestend = {0, 0, 0}, ground_bestnormal = {0, 0, 0}; + vec3_t water_beststart = {0, 0, 0}, water_bestend = {0, 0, 0}, water_bestnormal = {0, 0, 0}; + vec3_t invgravity = {0, 0, 1}; + vec3_t testpoint; + aas_plane_t *plane; + aas_area_t *area1, *area2; + aas_face_t *groundface1, *groundface2; + aas_edge_t *edge1, *edge2; + aas_lreachability_t *lreach; + aas_trace_t trace; + + //must be able to walk or swim in the first area + if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; + // + if (!AAS_AreaGrounded(area2num) && !AAS_AreaSwim(area2num)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //if the first area contains a liquid + area1swim = AAS_AreaSwim(area1num); + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + 10) return qfalse; + if (area1->maxs[i] < area2->mins[i] - 10) return qfalse; + } //end for + // + ground_foundreach = qfalse; + ground_bestdist = 99999; + ground_bestlength = 0; + ground_bestarea2groundedgenum = 0; + // + water_foundreach = qfalse; + water_bestdist = 99999; + water_bestlength = 0; + water_bestarea2groundedgenum = 0; + // + for (i = 0; i < area1->numfaces; i++) + { + groundface1num = aasworld.faceindex[area1->firstface + i]; + faceside1 = groundface1num < 0; + groundface1 = &aasworld.faces[abs(groundface1num)]; + //if this isn't a ground face + if (!(groundface1->faceflags & FACE_GROUND)) + { + //if we can swim in the first area + if (area1swim) + { + //face plane must be more or less horizontal + plane = &aasworld.planes[groundface1->planenum ^ (!faceside1)]; + if (DotProduct(plane->normal, invgravity) < 0.7) continue; + } //end if + else + { + //if we can't swim in the area it must be a ground face + continue; + } //end else + } //end if + // + for (k = 0; k < groundface1->numedges; k++) + { + edge1num = aasworld.edgeindex[groundface1->firstedge + k]; + side1 = (edge1num < 0); + //NOTE: for water faces we must take the side area 1 is + // on into account because the face is shared and doesn't + // have to be oriented correctly + if (!(groundface1->faceflags & FACE_GROUND)) side1 = (side1 == faceside1); + edge1num = abs(edge1num); + edge1 = &aasworld.edges[edge1num]; + //vertexes of the edge + VectorCopy(aasworld.vertexes[edge1->v[!side1]], v1); + VectorCopy(aasworld.vertexes[edge1->v[side1]], v2); + //get a vertical plane through the edge + //NOTE: normal is pointing into area 2 because the + //face edges are stored counter clockwise + VectorSubtract(v2, v1, edgevec); + CrossProduct(edgevec, invgravity, normal); + VectorNormalize(normal); + dist = DotProduct(normal, v1); + //check the faces from the second area + for (j = 0; j < area2->numfaces; j++) + { + groundface2 = &aasworld.faces[abs(aasworld.faceindex[area2->firstface + j])]; + //must be a ground face + if (!(groundface2->faceflags & FACE_GROUND)) continue; + //check the edges of this ground face + for (l = 0; l < groundface2->numedges; l++) + { + edge2num = abs(aasworld.edgeindex[groundface2->firstedge + l]); + edge2 = &aasworld.edges[edge2num]; + //vertexes of the edge + VectorCopy(aasworld.vertexes[edge2->v[0]], v3); + VectorCopy(aasworld.vertexes[edge2->v[1]], v4); + //check the distance between the two points and the vertical plane + //through the edge of area1 + diff = DotProduct(normal, v3) - dist; + if (diff < -0.1 || diff > 0.1) continue; + diff = DotProduct(normal, v4) - dist; + if (diff < -0.1 || diff > 0.1) continue; + // + //project the two ground edges into the step side plane + //and calculate the shortest distance between the two + //edges if they overlap in the direction orthogonal to + //the gravity direction + CrossProduct(invgravity, normal, ort); + //invgravitydot = DotProduct(invgravity, invgravity); + ortdot = DotProduct(ort, ort); + //projection into the step plane + //NOTE: since gravity is vertical this is just the z coordinate + y1 = v1[2];//DotProduct(v1, invgravity) / invgravitydot; + y2 = v2[2];//DotProduct(v2, invgravity) / invgravitydot; + y3 = v3[2];//DotProduct(v3, invgravity) / invgravitydot; + y4 = v4[2];//DotProduct(v4, invgravity) / invgravitydot; + // + x1 = DotProduct(v1, ort) / ortdot; + x2 = DotProduct(v2, ort) / ortdot; + x3 = DotProduct(v3, ort) / ortdot; + x4 = DotProduct(v4, ort) / ortdot; + // + if (x1 > x2) + { + tmp = x1; x1 = x2; x2 = tmp; + tmp = y1; y1 = y2; y2 = tmp; + VectorCopy(v1, tmpv); VectorCopy(v2, v1); VectorCopy(tmpv, v2); + } //end if + if (x3 > x4) + { + tmp = x3; x3 = x4; x4 = tmp; + tmp = y3; y3 = y4; y4 = tmp; + VectorCopy(v3, tmpv); VectorCopy(v4, v3); VectorCopy(tmpv, v4); + } //end if + //if the two projected edge lines have no overlap + if (x2 <= x3 || x4 <= x1) + { +// Log_Write("lines no overlap: from area %d to %d\r\n", area1num, area2num); + continue; + } //end if + //if the two lines fully overlap + if ((x1 - 0.5 < x3 && x4 < x2 + 0.5) && + (x3 - 0.5 < x1 && x2 < x4 + 0.5)) + { + dist1 = y3 - y1; + dist2 = y4 - y2; + VectorCopy(v1, p1area1); + VectorCopy(v2, p2area1); + VectorCopy(v3, p1area2); + VectorCopy(v4, p2area2); + } //end if + else + { + //if the points are equal + if (x1 > x3 - 0.1 && x1 < x3 + 0.1) + { + dist1 = y3 - y1; + VectorCopy(v1, p1area1); + VectorCopy(v3, p1area2); + } //end if + else if (x1 < x3) + { + y = y1 + (x3 - x1) * (y2 - y1) / (x2 - x1); + dist1 = y3 - y; + VectorCopy(v3, p1area1); + p1area1[2] = y; + VectorCopy(v3, p1area2); + } //end if + else + { + y = y3 + (x1 - x3) * (y4 - y3) / (x4 - x3); + dist1 = y - y1; + VectorCopy(v1, p1area1); + VectorCopy(v1, p1area2); + p1area2[2] = y; + } //end if + //if the points are equal + if (x2 > x4 - 0.1 && x2 < x4 + 0.1) + { + dist2 = y4 - y2; + VectorCopy(v2, p2area1); + VectorCopy(v4, p2area2); + } //end if + else if (x2 < x4) + { + y = y3 + (x2 - x3) * (y4 - y3) / (x4 - x3); + dist2 = y - y2; + VectorCopy(v2, p2area1); + VectorCopy(v2, p2area2); + p2area2[2] = y; + } //end if + else + { + y = y1 + (x4 - x1) * (y2 - y1) / (x2 - x1); + dist2 = y4 - y; + VectorCopy(v4, p2area1); + p2area1[2] = y; + VectorCopy(v4, p2area2); + } //end else + } //end else + //if both distances are pretty much equal + //then we take the middle of the points + if (dist1 > dist2 - 1 && dist1 < dist2 + 1) + { + dist = dist1; + VectorAdd(p1area1, p2area1, start); + VectorScale(start, 0.5, start); + VectorAdd(p1area2, p2area2, end); + VectorScale(end, 0.5, end); + } //end if + else if (dist1 < dist2) + { + dist = dist1; + VectorCopy(p1area1, start); + VectorCopy(p1area2, end); + } //end else if + else + { + dist = dist2; + VectorCopy(p2area1, start); + VectorCopy(p2area2, end); + } //end else + //get the length of the overlapping part of the edges of the two areas + VectorSubtract(p2area2, p1area2, dir); + length = VectorLength(dir); + // + if (groundface1->faceflags & FACE_GROUND) + { + //if the vertical distance is smaller + if (dist < ground_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + (dist < ground_bestdist + 1 && length > ground_bestlength)) + { + ground_bestdist = dist; + ground_bestlength = length; + ground_foundreach = qtrue; + ground_bestarea2groundedgenum = edge1num; + //best point towards area1 + VectorCopy(start, ground_beststart); + //normal is pointing into area2 + VectorCopy(normal, ground_bestnormal); + //best point towards area2 + VectorCopy(end, ground_bestend); + } //end if + } //end if + else + { + //if the vertical distance is smaller + if (dist < water_bestdist || + //or the vertical distance is pretty much the same + //but the overlapping part of the edges is longer + (dist < water_bestdist + 1 && length > water_bestlength)) + { + water_bestdist = dist; + water_bestlength = length; + water_foundreach = qtrue; + water_bestarea2groundedgenum = edge1num; + //best point towards area1 + VectorCopy(start, water_beststart); + //normal is pointing into area2 + VectorCopy(normal, water_bestnormal); + //best point towards area2 + VectorCopy(end, water_bestend); + } //end if + } //end else + } //end for + } //end for + } //end for + } //end for + // + // NOTE: swim reachabilities are already filtered out + // + // Steps + // + // --------- + // | step height -> TRAVEL_WALK + //--------| + // + // --------- + //~~~~~~~~| step height and low water -> TRAVEL_WALK + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | step height and low water up to the step -> TRAVEL_WALK + //--------| + // + //check for a step reachability + if (ground_foundreach) + { + //if area2 is higher but lower than the maximum step height + //NOTE: ground_bestdist >= 0 also catches equal floor reachabilities + if (ground_bestdist >= 0 && ground_bestdist < aassettings.phys_maxstep) + { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 0;//1; + //if going into a crouch area + if (!AAS_AreaCrouch(area1num) && AAS_AreaCrouch(area2num)) + { + lreach->traveltime += aassettings.rs_startcrouch; + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //NOTE: if there's nearby solid or a gap area after this area + /* + if (!AAS_NearbySolidOrGap(lreach->start, lreach->end)) + { + lreach->traveltime += 100; + } //end if + */ + //avoid rather small areas + //if (AAS_AreaGroundFaceArea(lreach->areanum) < 500) lreach->traveltime += 100; + // + reach_step++; + return qtrue; + } //end if + } //end if + // + // Water Jumps + // + // --------- + // | + //~~~~~~~~| + // | + // | higher than step height and water up to waterjump height -> TRAVEL_WATERJUMP + //--------| + // + //~~~~~~~~~~~~~~~~~~ + // --------- + // | + // | + // | + // | higher than step height and low water up to the step -> TRAVEL_WATERJUMP + //--------| + // + //check for a waterjump reachability + if (water_foundreach) + { + //get a test point a little bit towards area1 + VectorMA(water_bestend, -INSIDEUNITS, water_bestnormal, testpoint); + //go down the maximum waterjump height + testpoint[2] -= aassettings.phys_maxwaterjump; + //if there IS water the sv_maxwaterjump height below the bestend point + if (aasworld.areasettings[AAS_PointAreaNum(testpoint)].areaflags & AREA_LIQUID) + { + //don't create rediculous water jump reachabilities from areas very far below + //the water surface + if (water_bestdist < aassettings.phys_maxwaterjump + 24) + { + //waterjumping from or towards a crouch only area is not possible in Quake2 + if ((aasworld.areasettings[area1num].presencetype & PRESENCE_NORMAL) && + (aasworld.areasettings[area2num].presencetype & PRESENCE_NORMAL)) + { + //create water jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = water_bestarea2groundedgenum; + VectorCopy(water_beststart, lreach->start); + VectorMA(water_bestend, INSIDEUNITS_WATERJUMP, water_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WATERJUMP; + lreach->traveltime = aassettings.rs_waterjump; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another waterjump reachability + reach_waterjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Barrier Jumps + // + // --------- + // | + // | + // | + // | higher than step height lower than barrier height -> TRAVEL_BARRIERJUMP + //--------| + // + // --------- + // | + // | + // | + //~~~~~~~~| higher than step height lower than barrier height + //--------| and a thin layer of water in the area to jump from -> TRAVEL_BARRIERJUMP + // + //check for a barrier jump reachability + if (ground_foundreach) + { + //if area2 is higher but lower than the maximum barrier jump height + if (ground_bestdist > 0 && ground_bestdist < aassettings.phys_maxbarrier) + { + //if no water in area1 or a very thin layer of water on the ground + if (!water_foundreach || (ground_bestdist - water_bestdist < 16)) + { + //cannot perform a barrier jump towards or from a crouch area in Quake2 + if (!AAS_AreaCrouch(area1num) && !AAS_AreaCrouch(area2num)) + { + //create barrier jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_BARRIERJUMP; + lreach->traveltime = aassettings.rs_barrierjump;//AAS_BarrierJumpTravelTime(); + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another barrierjump reachability + reach_barrier++; + return qtrue; + } //end if + } //end if + } //end if + } //end if + // + // Walk and Walk Off Ledge + // + //--------| + // | can walk or step back -> TRAVEL_WALK + // --------- + // + //--------| + // | + // | + // | + // | cannot walk/step back -> TRAVEL_WALKOFFLEDGE + // --------- + // + //--------| + // | + // |~~~~~~~~ + // | + // | cannot step back but can waterjump back -> TRAVEL_WALKOFFLEDGE + // --------- FIXME: create TRAVEL_WALK reach?? + // + //check for a walk or walk off ledge reachability + if (ground_foundreach) + { + if (ground_bestdist < 0) + { + if (ground_bestdist > -aassettings.phys_maxstep) + { + //create walk reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorMA(ground_beststart, INSIDEUNITS_WALKSTART, ground_bestnormal, lreach->start); + VectorMA(ground_bestend, INSIDEUNITS_WALKEND, ground_bestnormal, lreach->end); + lreach->traveltype = TRAVEL_WALK; + lreach->traveltime = 1; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //we've got another walk reachability + reach_walk++; + return qtrue; + } //end if + // if no maximum fall height set or less than the max + if (!aassettings.rs_maxfallheight || fabs(ground_bestdist) < aassettings.rs_maxfallheight) { + //trace a bounding box vertically to check for solids + VectorMA(ground_bestend, INSIDEUNITS, ground_bestnormal, ground_bestend); + VectorCopy(ground_bestend, start); + start[2] = ground_beststart[2]; + VectorCopy(ground_bestend, end); + end[2] += 4; + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + //if no solids were found + if (!trace.startsolid && trace.fraction >= 1.0) + { + //the trace end point must be in the goal area + trace.endpos[2] += 1; + if (AAS_PointAreaNum(trace.endpos) == area2num) + { + //if not going through a cluster portal + numareas = AAS_TraceAreas(start, end, areas, NULL, ARRAY_LEN(areas)); + for (i = 0; i < numareas; i++) + if (AAS_AreaClusterPortal(areas[i])) + break; + if (i >= numareas) + { + //create a walk off ledge reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = ground_bestarea2groundedgenum; + VectorCopy(ground_beststart, lreach->start); + VectorCopy(ground_bestend, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(ground_bestdist) * 50 / aassettings.phys_gravity; + //if falling from too high and not falling into water + if (!AAS_AreaSwim(area2num) && !AAS_AreaJumpPad(area2num)) + { + if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta5) + { + lreach->traveltime += aassettings.rs_falldamage5; + } //end if + if (AAS_FallDelta(ground_bestdist) > aassettings.phys_falldelta10) + { + lreach->traveltime += aassettings.rs_falldamage10; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_walkoffledge++; + //NOTE: don't create a weapon (rl, bfg) jump reachability here + //because it interferes with other reachabilities + //like the ladder reachability + return qtrue; + } //end if + } //end if + } //end if + } //end if + } //end else + } //end if + return qfalse; +} //end of the function AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge +//=========================================================================== +// returns the distance between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float VectorDistance(vec3_t v1, vec3_t v2) +{ + vec3_t dir; + + VectorSubtract(v2, v1, dir); + return VectorLength(dir); +} //end of the function VectorDistance +//=========================================================================== +// returns true if the first vector is between the last two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int VectorBetweenVectors(vec3_t v, vec3_t v1, vec3_t v2) +{ + vec3_t dir1, dir2; + + VectorSubtract(v, v1, dir1); + VectorSubtract(v, v2, dir2); + return (DotProduct(dir1, dir2) <= 0); +} //end of the function VectorBetweenVectors +//=========================================================================== +// returns the mid point between the two vectors +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void VectorMiddle(vec3_t v1, vec3_t v2, vec3_t middle) +{ + VectorAdd(v1, v2, middle); + VectorScale(middle, 0.5, middle); +} //end of the function VectorMiddle +//=========================================================================== +// calculate a range of points closest to each other on both edges +// +// Parameter: beststart1 start of the range of points on edge v1-v2 +// beststart2 end of the range of points on edge v1-v2 +// bestend1 start of the range of points on edge v3-v4 +// bestend2 end of the range of points on edge v3-v4 +// bestdist best distance so far +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart, vec3_t bestend, float bestdist) +{ + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist; + int founddist; + + //edge vectors + VectorSubtract(v2, v1, dir1); + VectorSubtract(v4, v3, dir2); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if (dir2[0]) + { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if (dir1[0]) + { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; + p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; + p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; + p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; + // + founddist = qfalse; + // + if (VectorBetweenVectors(p1, v3, v4)) + { + dist = VectorDistance(v1, p1); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v1, beststart); + VectorMiddle(bestend, p1, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(p1, bestend); + } //end if + founddist = qtrue; + } //end if + if (VectorBetweenVectors(p2, v3, v4)) + { + dist = VectorDistance(v2, p2); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, v2, beststart); + VectorMiddle(bestend, p2, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(p2, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p3, v1, v2)) + { + dist = VectorDistance(v3, p3); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p3, beststart); + VectorMiddle(bestend, v3, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p3, beststart); + VectorCopy(v3, bestend); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p4, v1, v2)) + { + dist = VectorDistance(v4, p4); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + VectorMiddle(beststart, p4, beststart); + VectorMiddle(bestend, v4, bestend); + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p4, beststart); + VectorCopy(v4, bestend); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if (!founddist) + { + dist = VectorDistance(v1, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v1, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart); + VectorCopy(v4, bestend); + } //end if + dist = VectorDistance(v2, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v3, bestend); + } //end if + dist = VectorDistance(v2, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart); + VectorCopy(v4, bestend); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints*/ + +float AAS_ClosestEdgePoints(vec3_t v1, vec3_t v2, vec3_t v3, vec3_t v4, + aas_plane_t *plane1, aas_plane_t *plane2, + vec3_t beststart1, vec3_t bestend1, + vec3_t beststart2, vec3_t bestend2, float bestdist) +{ + vec3_t dir1, dir2, p1, p2, p3, p4; + float a1, a2, b1, b2, dist, dist1, dist2; + int founddist; + + //edge vectors + VectorSubtract(v2, v1, dir1); + VectorSubtract(v4, v3, dir2); + //get the horizontal directions + dir1[2] = 0; + dir2[2] = 0; + // + // p1 = point on an edge vector of area2 closest to v1 + // p2 = point on an edge vector of area2 closest to v2 + // p3 = point on an edge vector of area1 closest to v3 + // p4 = point on an edge vector of area1 closest to v4 + // + if (dir2[0]) + { + a2 = dir2[1] / dir2[0]; + b2 = v3[1] - a2 * v3[0]; + //point on the edge vector of area2 closest to v1 + p1[0] = (DotProduct(v1, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p1[1] = a2 * p1[0] + b2; + //point on the edge vector of area2 closest to v2 + p2[0] = (DotProduct(v2, dir2) - (a2 * dir2[0] + b2 * dir2[1])) / dir2[0]; + p2[1] = a2 * p2[0] + b2; + } //end if + else + { + //point on the edge vector of area2 closest to v1 + p1[0] = v3[0]; + p1[1] = v1[1]; + //point on the edge vector of area2 closest to v2 + p2[0] = v3[0]; + p2[1] = v2[1]; + } //end else + // + if (dir1[0]) + { + // + a1 = dir1[1] / dir1[0]; + b1 = v1[1] - a1 * v1[0]; + //point on the edge vector of area1 closest to v3 + p3[0] = (DotProduct(v3, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p3[1] = a1 * p3[0] + b1; + //point on the edge vector of area1 closest to v4 + p4[0] = (DotProduct(v4, dir1) - (a1 * dir1[0] + b1 * dir1[1])) / dir1[0]; + p4[1] = a1 * p4[0] + b1; + } //end if + else + { + //point on the edge vector of area1 closest to v3 + p3[0] = v1[0]; + p3[1] = v3[1]; + //point on the edge vector of area1 closest to v4 + p4[0] = v1[0]; + p4[1] = v4[1]; + } //end else + //start with zero z-coordinates + p1[2] = 0; + p2[2] = 0; + p3[2] = 0; + p4[2] = 0; + //calculate the z-coordinates from the ground planes + p1[2] = (plane2->dist - DotProduct(plane2->normal, p1)) / plane2->normal[2]; + p2[2] = (plane2->dist - DotProduct(plane2->normal, p2)) / plane2->normal[2]; + p3[2] = (plane1->dist - DotProduct(plane1->normal, p3)) / plane1->normal[2]; + p4[2] = (plane1->dist - DotProduct(plane1->normal, p4)) / plane1->normal[2]; + // + founddist = qfalse; + // + if (VectorBetweenVectors(p1, v3, v4)) + { + dist = VectorDistance(v1, p1); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, v1); + dist2 = VectorDistance(beststart2, v1); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v1, beststart1); + } //end else + dist1 = VectorDistance(bestend1, p1); + dist2 = VectorDistance(bestend2, p1); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p1, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(p1, bestend1); + VectorCopy(p1, bestend2); + } //end if + founddist = qtrue; + } //end if + if (VectorBetweenVectors(p2, v3, v4)) + { + dist = VectorDistance(v2, p2); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, v2); + dist2 = VectorDistance(beststart2, v2); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(v2, beststart1); + } //end else + dist1 = VectorDistance(bestend1, p2); + dist2 = VectorDistance(bestend2, p2); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(p2, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(p2, bestend1); + VectorCopy(p2, bestend2); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p3, v1, v2)) + { + dist = VectorDistance(v3, p3); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, p3); + dist2 = VectorDistance(beststart2, p3); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p3, beststart1); + } //end else + dist1 = VectorDistance(bestend1, v3); + dist2 = VectorDistance(bestend2, v3); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v3, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p3, beststart1); + VectorCopy(p3, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + founddist = qtrue; + } //end else if + if (VectorBetweenVectors(p4, v1, v2)) + { + dist = VectorDistance(v4, p4); + if (dist > bestdist - 0.5 && dist < bestdist + 0.5) + { + dist1 = VectorDistance(beststart1, p4); + dist2 = VectorDistance(beststart2, p4); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart2); + } //end if + else + { + if (dist2 > VectorDistance(beststart1, beststart2)) VectorCopy(p4, beststart1); + } //end else + dist1 = VectorDistance(bestend1, v4); + dist2 = VectorDistance(bestend2, v4); + if (dist1 > dist2) + { + if (dist1 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend2); + } //end if + else + { + if (dist2 > VectorDistance(bestend1, bestend2)) VectorCopy(v4, bestend1); + } //end else + } //end if + else if (dist < bestdist) + { + bestdist = dist; + VectorCopy(p4, beststart1); + VectorCopy(p4, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + founddist = qtrue; + } //end else if + //if no shortest distance was found the shortest distance + //is between one of the vertexes of edge1 and one of edge2 + if (!founddist) + { + dist = VectorDistance(v1, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + dist = VectorDistance(v1, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v1, beststart1); + VectorCopy(v1, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + dist = VectorDistance(v2, v3); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(v3, bestend1); + VectorCopy(v3, bestend2); + } //end if + dist = VectorDistance(v2, v4); + if (dist < bestdist) + { + bestdist = dist; + VectorCopy(v2, beststart1); + VectorCopy(v2, beststart2); + VectorCopy(v4, bestend1); + VectorCopy(v4, bestend2); + } //end if + } //end if + return bestdist; +} //end of the function AAS_ClosestEdgePoints +//=========================================================================== +// creates possible jump reachabilities between the areas +// +// The two closest points on the ground of the areas are calculated +// One of the points will be on an edge of a ground face of area1 and +// one on an edge of a ground face of area2. +// If there is a range of closest points the point in the middle of this range +// is selected. +// Between these two points there must be one or more gaps. +// If the gaps exist a potential jump is predicted. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Jump(int area1num, int area2num) +{ + int i, j, k, l, face1num, face2num, edge1num, edge2num, traveltype; + int stopevent, areas[10], numareas; + float phys_jumpvel, maxjumpdistance, maxjumpheight, height, bestdist, speed; + vec_t *v1, *v2, *v3, *v4; + vec3_t beststart, beststart2, bestend, bestend2; + vec3_t teststart, testend, dir, velocity, cmdmove, up = {0, 0, 1}, sidewards; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2; + aas_edge_t *edge1, *edge2; + aas_plane_t *plane1, *plane2, *plane; + aas_trace_t trace; + aas_clientmove_t move; + aas_lreachability_t *lreach; + + if (!AAS_AreaGrounded(area1num) || !AAS_AreaGrounded(area2num)) return qfalse; + //cannot jump from or to a crouch area + if (AAS_AreaCrouch(area1num) || AAS_AreaCrouch(area2num)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + // + phys_jumpvel = aassettings.phys_jumpvel; + //maximum distance a player can jump + maxjumpdistance = 2 * AAS_MaxJumpDistance(phys_jumpvel); + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); + + //if the areas are not near anough in the x-y direction + for (i = 0; i < 2; i++) + { + if (area1->mins[i] > area2->maxs[i] + maxjumpdistance) return qfalse; + if (area1->maxs[i] < area2->mins[i] - maxjumpdistance) return qfalse; + } //end for + //if area2 is way to high to jump up to + if (area2->mins[2] > area1->maxs[2] + maxjumpheight) return qfalse; + // + bestdist = 999999; + // + for (i = 0; i < area1->numfaces; i++) + { + face1num = aasworld.faceindex[area1->firstface + i]; + face1 = &aasworld.faces[abs(face1num)]; + //if not a ground face + if (!(face1->faceflags & FACE_GROUND)) continue; + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = aasworld.faceindex[area2->firstface + j]; + face2 = &aasworld.faces[abs(face2num)]; + //if not a ground face + if (!(face2->faceflags & FACE_GROUND)) continue; + // + for (k = 0; k < face1->numedges; k++) + { + edge1num = abs(aasworld.edgeindex[face1->firstedge + k]); + edge1 = &aasworld.edges[edge1num]; + for (l = 0; l < face2->numedges; l++) + { + edge2num = abs(aasworld.edgeindex[face2->firstedge + l]); + edge2 = &aasworld.edges[edge2num]; + //calculate the minimum distance between the two edges + v1 = aasworld.vertexes[edge1->v[0]]; + v2 = aasworld.vertexes[edge1->v[1]]; + v3 = aasworld.vertexes[edge2->v[0]]; + v4 = aasworld.vertexes[edge2->v[1]]; + //get the ground planes + plane1 = &aasworld.planes[face1->planenum]; + plane2 = &aasworld.planes[face2->planenum]; + // + bestdist = AAS_ClosestEdgePoints(v1, v2, v3, v4, plane1, plane2, + beststart, bestend, + beststart2, bestend2, bestdist); + } //end for + } //end for + } //end for + } //end for + VectorMiddle(beststart, beststart2, beststart); + VectorMiddle(bestend, bestend2, bestend); + if (bestdist > 4 && bestdist < maxjumpdistance) + { +// Log_Write("shortest distance between %d and %d is %f\r\n", area1num, area2num, bestdist); + // if very close and almost no height difference then the bot can walk + if (bestdist <= 48 && fabs(beststart[2] - bestend[2]) < 8) + { + speed = 400; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end if + else if (AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) + { + //FIXME: why multiply with 1.2??? + speed *= 1.2f; + traveltype = TRAVEL_WALKOFFLEDGE; + } //end else if + else + { + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + if (!AAS_HorizontalVelocityForJump(phys_jumpvel, beststart, bestend, &speed)) + return qfalse; + speed *= 1.05f; + traveltype = TRAVEL_JUMP; + // + //NOTE: test if the horizontal distance isn't too small + VectorSubtract(bestend, beststart, dir); + dir[2] = 0; + if (VectorLength(dir) < 10) + return qfalse; + } //end if + // + VectorSubtract(bestend, beststart, dir); + VectorNormalize(dir); + VectorMA(beststart, 1, dir, teststart); + // + VectorCopy(teststart, testend); + testend[2] -= 100; + trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) + return qfalse; + if (trace.fraction < 1) + { + plane = &aasworld.planes[trace.planenum]; + // if the bot can stand on the surface + if (DotProduct(plane->normal, up) >= 0.7) + { + // if no lava or slime below + if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) + { + if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) + return qfalse; + } //end if + } //end if + } //end if + // + VectorMA(bestend, -1, dir, teststart); + // + VectorCopy(teststart, testend); + testend[2] -= 100; + trace = AAS_TraceClientBBox(teststart, testend, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) + return qfalse; + if (trace.fraction < 1) + { + plane = &aasworld.planes[trace.planenum]; + // if the bot can stand on the surface + if (DotProduct(plane->normal, up) >= 0.7) + { + // if no lava or slime below + if (!(AAS_PointContents(trace.endpos) & (CONTENTS_LAVA|CONTENTS_SLIME))) + { + if (teststart[2] - trace.endpos[2] <= aassettings.phys_maxbarrier) + return qfalse; + } //end if + } //end if + } //end if + // + // get command movement + VectorClear(cmdmove); + if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) + cmdmove[2] = aassettings.phys_jumpvel; + else + cmdmove[2] = 0; + // + VectorSubtract(bestend, beststart, dir); + dir[2] = 0; + VectorNormalize(dir); + CrossProduct(dir, up, sidewards); + // + stopevent = SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE; + if (!AAS_AreaClusterPortal(area1num) && !AAS_AreaClusterPortal(area2num)) + stopevent |= SE_TOUCHCLUSTERPORTAL; + // + for (i = 0; i < 3; i++) + { + // + if (i == 1) + VectorAdd(testend, sidewards, testend); + else if (i == 2) + VectorSubtract(bestend, sidewards, testend); + else + VectorCopy(bestend, testend); + VectorSubtract(testend, beststart, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorScale(dir, speed, velocity); + // + AAS_PredictClientMovement(&move, -1, beststart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 3, 30, 0.1f, + stopevent, 0, qfalse); + // if prediction time wasn't enough to fully predict the movement + if (move.frames >= 30) + return qfalse; + // don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) + return qfalse; + // never jump or fall through a cluster portal + if (move.stopevent & SE_TOUCHCLUSTERPORTAL) + return qfalse; + //the end position should be in area2, also test a little bit back + //because the predicted jump could have rushed through the area + VectorMA(move.endpos, -64, dir, teststart); + teststart[2] += 1; + numareas = AAS_TraceAreas(move.endpos, teststart, areas, NULL, ARRAY_LEN(areas)); + for (j = 0; j < numareas; j++) + { + if (areas[j] == area2num) + break; + } //end for + if (j < numareas) + break; + } + if (i >= 3) + return qfalse; + // +#ifdef REACH_DEBUG + //create the reachability + Log_Write("jump reachability between %d and %d\r\n", area1num, area2num); +#endif //REACH_DEBUG + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(beststart, lreach->start); + VectorCopy(bestend, lreach->end); + lreach->traveltype = traveltype; + + VectorSubtract(bestend, beststart, dir); + height = dir[2]; + dir[2] = 0; + if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_WALKOFFLEDGE && height > VectorLength(dir)) + { + lreach->traveltime = aassettings.rs_startwalkoffledge + height * 50 / aassettings.phys_gravity; + } + else + { + lreach->traveltime = aassettings.rs_startjump + VectorDistance(bestend, beststart) * 240 / aassettings.phys_maxwalkvelocity; + } //end if + // + if (!AAS_AreaJumpPad(area2num)) + { + if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta5) + { + lreach->traveltime += aassettings.rs_falldamage5; + } //end if + else if (AAS_FallDelta(beststart[2] - bestend[2]) > aassettings.phys_falldelta10) + { + lreach->traveltime += aassettings.rs_falldamage10; + } //end if + } //end if + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + if ((traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMP) + reach_jump++; + else + reach_walkoffledge++; + } //end if + return qfalse; +} //end of the function AAS_Reachability_Jump +//=========================================================================== +// create a possible ladder reachability from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Ladder(int area1num, int area2num) +{ + int i, j, k, l, edge1num, edge2num, sharededgenum = 0, lowestedgenum = 0; + int face1num, face2num, ladderface1num = 0, ladderface2num = 0; + int ladderface1vertical, ladderface2vertical, firstv; + float face1area, face2area, bestface1area = -9999, bestface2area = -9999; + float phys_jumpvel, maxjumpheight; + vec3_t area1point, area2point, v1, v2, up = {0, 0, 1}; + vec3_t mid, lowestpoint = {0, 0}, start, end, sharededgevec, dir; + aas_area_t *area1, *area2; + aas_face_t *face1, *face2, *ladderface1 = NULL, *ladderface2 = NULL; + aas_plane_t *plane1, *plane2; + aas_edge_t *sharededge, *edge1; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if (!AAS_AreaLadder(area1num) || !AAS_AreaLadder(area2num)) return qfalse; + // + phys_jumpvel = aassettings.phys_jumpvel; + //maximum height a player can jump with the given initial z velocity + maxjumpheight = AAS_MaxJumpHeight(phys_jumpvel); + + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + + for (i = 0; i < area1->numfaces; i++) + { + face1num = aasworld.faceindex[area1->firstface + i]; + face1 = &aasworld.faces[abs(face1num)]; + //if not a ladder face + if (!(face1->faceflags & FACE_LADDER)) continue; + // + for (j = 0; j < area2->numfaces; j++) + { + face2num = aasworld.faceindex[area2->firstface + j]; + face2 = &aasworld.faces[abs(face2num)]; + //if not a ladder face + if (!(face2->faceflags & FACE_LADDER)) continue; + //check if the faces share an edge + for (k = 0; k < face1->numedges; k++) + { + edge1num = aasworld.edgeindex[face1->firstedge + k]; + for (l = 0; l < face2->numedges; l++) + { + edge2num = aasworld.edgeindex[face2->firstedge + l]; + if (abs(edge1num) == abs(edge2num)) + { + //get the face with the largest area + face1area = AAS_FaceArea(face1); + face2area = AAS_FaceArea(face2); + if (face1area > bestface1area && face2area > bestface2area) + { + bestface1area = face1area; + bestface2area = face2area; + ladderface1 = face1; + ladderface2 = face2; + ladderface1num = face1num; + ladderface2num = face2num; + sharededgenum = edge1num; + } //end if + break; + } //end if + } //end for + if (l != face2->numedges) break; + } //end for + } //end for + } //end for + // + if (ladderface1 && ladderface2) + { + //get the middle of the shared edge + sharededge = &aasworld.edges[abs(sharededgenum)]; + firstv = sharededgenum < 0; + // + VectorCopy(aasworld.vertexes[sharededge->v[firstv]], v1); + VectorCopy(aasworld.vertexes[sharededge->v[!firstv]], v2); + VectorAdd(v1, v2, area1point); + VectorScale(area1point, 0.5, area1point); + VectorCopy(area1point, area2point); + // + //if the face plane in area 1 is pretty much vertical + plane1 = &aasworld.planes[ladderface1->planenum ^ (ladderface1num < 0)]; + plane2 = &aasworld.planes[ladderface2->planenum ^ (ladderface2num < 0)]; + // + //get the points really into the areas + VectorSubtract(v2, v1, sharededgevec); + CrossProduct(plane1->normal, sharededgevec, dir); + VectorNormalize(dir); + //NOTE: 32 because that's larger than 16 (bot bbox x,y) + VectorMA(area1point, -32, dir, area1point); + VectorMA(area2point, 32, dir, area2point); + // + ladderface1vertical = abs(DotProduct(plane1->normal, up)) < 0.1; + ladderface2vertical = abs(DotProduct(plane2->normal, up)) < 0.1; + //there's only reachability between vertical ladder faces + if (!ladderface1vertical && !ladderface2vertical) return qfalse; + //if both vertical ladder faces + if (ladderface1vertical && ladderface2vertical + //and the ladder faces do not make a sharp corner + && DotProduct(plane1->normal, plane2->normal) > 0.7 + //and the shared edge is not too vertical + && abs(DotProduct(sharededgevec, up)) < 0.7) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area1point, lreach->start); + //VectorCopy(area2point, lreach->end); + VectorMA(area2point, -3, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area2point, lreach->start); + //VectorCopy(area1point, lreach->end); + VectorMA(area1point, -3, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_ladder++; + // + return qtrue; + } //end if + //if the second ladder face is also a ground face + //create ladder end (just ladder) reachability and + //walk off a ladder (ledge) reachability + if (ladderface1vertical && (ladderface2->faceflags & FACE_GROUND)) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area1point, lreach->start); + VectorCopy(area2point, lreach->end); + lreach->end[2] += 16; + VectorMA(lreach->end, -15, plane1->normal, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface2num; + lreach->edgenum = abs(sharededgenum); + VectorCopy(area2point, lreach->start); + VectorCopy(area1point, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_walkoffledge++; + // + return qtrue; + } //end if + // + if (ladderface1vertical) + { + //find lowest edge of the ladder face + lowestpoint[2] = 99999; + for (i = 0; i < ladderface1->numedges; i++) + { + edge1num = abs(aasworld.edgeindex[ladderface1->firstedge + i]); + edge1 = &aasworld.edges[edge1num]; + // + VectorCopy(aasworld.vertexes[edge1->v[0]], v1); + VectorCopy(aasworld.vertexes[edge1->v[1]], v2); + // + VectorAdd(v1, v2, mid); + VectorScale(mid, 0.5, mid); + // + if (mid[2] < lowestpoint[2]) + { + VectorCopy(mid, lowestpoint); + lowestedgenum = edge1num; + } //end if + } //end for + // + plane1 = &aasworld.planes[ladderface1->planenum]; + //trace down in the middle of this edge + VectorMA(lowestpoint, 5, plane1->normal, start); + VectorCopy(start, end); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + // + // +#ifdef REACH_DEBUG + if (trace.startsolid) + { + Log_Write("trace from area %d started in solid\r\n", area1num); + } //end if +#endif //REACH_DEBUG + // + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum(trace.endpos); + // + area2 = &aasworld.areas[area2num]; + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + // + if (face2->faceflags & FACE_LADDER) + { + plane2 = &aasworld.planes[face2->planenum]; + if (abs(DotProduct(plane2->normal, up)) < 0.1) break; + } //end if + } //end for + //if from another area without vertical ladder faces + if (i >= area2->numfaces && area2num != area1num && + //the reachabilities shouldn't exist already + !AAS_ReachabilityExists(area1num, area2num) && + !AAS_ReachabilityExists(area2num, area1num)) + { + //if the height is jumpable + if (start[2] - trace.endpos[2] < maxjumpheight) + { + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(lowestpoint, lreach->start); + VectorCopy(trace.endpos, lreach->end); + lreach->traveltype = TRAVEL_LADDER; + lreach->traveltime = 10; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_ladder++; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(trace.endpos, lreach->start); + //get the end point a little bit into the ladder + VectorMA(lowestpoint, -5, plane1->normal, lreach->end); + //get the end point a little higher + lreach->end[2] += 10; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + return qtrue; +#ifdef REACH_DEBUG + Log_Write("jump up to ladder reach between %d and %d\r\n", area2num, area1num); +#endif //REACH_DEBUG + } //end if +#ifdef REACH_DEBUG + else Log_Write("jump too high between area %d and %d\r\n", area2num, area1num); +#endif //REACH_DEBUG + } //end if + /*//if slime or lava below the ladder + //try jump reachability from far towards the ladder + if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) + { + for (i = 20; i <= 120; i += 20) + { + //trace down in the middle of this edge + VectorMA(lowestpoint, i, plane1->normal, start); + VectorCopy(start, end); + start[2] += 5; + end[2] -= 100; + //trace without entity collision + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + // + if (trace.startsolid) break; + trace.endpos[2] += 1; + area2num = AAS_PointAreaNum(trace.endpos); + if (area2num == area1num) continue; + // + if (start[2] - trace.endpos[2] > maxjumpheight) continue; + if (aasworld.areasettings[area2num].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) continue; + // + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area1num; + lreach->facenum = ladderface1num; + lreach->edgenum = lowestedgenum; + VectorCopy(trace.endpos, lreach->start); + VectorCopy(lowestpoint, lreach->end); + lreach->end[2] += 5; + lreach->traveltype = TRAVEL_JUMP; + lreach->traveltime = 10; + lreach->next = areareachability[area2num]; + areareachability[area2num] = lreach; + // + reach_jump++; + // + Log_Write("jump far to ladder reach between %d and %d\r\n", area2num, area1num); + // + break; + } //end for + } //end if*/ + } //end if + } //end if + return qfalse; +} //end of the function AAS_Reachability_Ladder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TravelFlagsForTeam(int ent) +{ + int notteam; + + if (!AAS_IntForBSPEpairKey(ent, "bot_notteam", ¬team)) + return 0; + if (notteam == 1) + return TRAVELFLAG_NOTTEAM1; + if (notteam == 2) + return TRAVELFLAG_NOTTEAM2; + return 0; +} //end of the function AAS_TravelFlagsForTeam +//=========================================================================== +// create possible teleporter reachabilities +// this is very game dependent.... :( +// +// classname = trigger_multiple or trigger_teleport +// target = "t1" +// +// classname = target_teleporter +// targetname = "t1" +// target = "t2" +// +// classname = misc_teleporter_dest +// targetname = "t2" +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_Teleport(void) +{ + int area1num, area2num; + char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + int ent, dest; + float angle; + vec3_t origin, destorigin, mins, maxs, end, angles; + vec3_t mid, velocity, cmdmove; + aas_lreachability_t *lreach; + aas_clientmove_t move; + aas_trace_t trace; + aas_link_t *areas, *link; + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (!strcmp(classname, "trigger_multiple")) + { + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); +//#ifdef REACH_DEBUG + botimport.Print(PRT_MESSAGE, "trigger_multiple model = \"%s\"\n", model); +//#endif REACH_DEBUG + VectorClear(angles); + AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); + // + if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "trigger_multiple at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2]); + continue; + } //end if + for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) + { + if (!AAS_ValueForBSPEpairKey(dest, "classname", classname, MAX_EPAIRKEY)) continue; + if (!strcmp(classname, "target_teleporter")) + { + if (!AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) continue; + if (!strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + continue; + } //end if + if (!AAS_ValueForBSPEpairKey(dest, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "target_teleporter without target\n"); + continue; + } //end if + } //end else + else if (!strcmp(classname, "trigger_teleport")) + { + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); +//#ifdef REACH_DEBUG + botimport.Print(PRT_MESSAGE, "trigger_teleport model = \"%s\"\n", model); +//#endif REACH_DEBUG + VectorClear(angles); + AAS_BSPModelMinsMaxsOrigin(atoi(model+1), angles, mins, maxs, origin); + // + if (!AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "trigger_teleport at %1.0f %1.0f %1.0f without target\n", + origin[0], origin[1], origin[2]); + continue; + } //end if + } //end if + else + { + continue; + } //end else + // + for (dest = AAS_NextBSPEntity(0); dest; dest = AAS_NextBSPEntity(dest)) + { + //classname should be misc_teleporter_dest + //but I've also seen target_position and actually any + //entity could be used... burp + if (AAS_ValueForBSPEpairKey(dest, "targetname", targetname, MAX_EPAIRKEY)) + { + if (!strcmp(targetname, target)) + { + break; + } //end if + } //end if + } //end for + if (!dest) + { + botimport.Print(PRT_ERROR, "teleporter without misc_teleporter_dest (%s)\n", target); + continue; + } //end if + if (!AAS_VectorForBSPEpairKey(dest, "origin", destorigin)) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) without origin\n", target); + continue; + } //end if + // + area2num = AAS_PointAreaNum(destorigin); + //if not teleported into a teleporter or into a jumppad + if (!AAS_AreaTeleporter(area2num) && !AAS_AreaJumpPad(area2num)) + { + VectorCopy(destorigin, end); + end[2] -= 64; + trace = AAS_TraceClientBBox(destorigin, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_ERROR, "teleporter destination (%s) in solid\n", target); + continue; + } //end if + /* + area2num = AAS_PointAreaNum(trace.endpos); + // + if (!AAS_AreaTeleporter(area2num) && + !AAS_AreaJumpPad(area2num) && + !AAS_AreaGrounded(area2num)) + { + VectorCopy(trace.endpos, destorigin); + } + else*/ + { + //predict where you'll end up + AAS_FloatForBSPEpairKey(dest, "angle", &angle); + if (angle) + { + VectorSet(angles, 0, angle, 0); + AngleVectors(angles, velocity, NULL, NULL); + VectorScale(velocity, 400, velocity); + } //end if + else + { + VectorClear(velocity); + } //end else + VectorClear(cmdmove); + AAS_PredictClientMovement(&move, -1, destorigin, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1f, + SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, qfalse); //qtrue); + area2num = AAS_PointAreaNum(move.endpos); + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA)) + { + botimport.Print(PRT_WARNING, "teleported into slime or lava at dest %s\n", target); + } //end if + VectorCopy(move.endpos, destorigin); + } //end else + } //end if + // + //botimport.Print(PRT_MESSAGE, "teleporter brush origin at %f %f %f\n", origin[0], origin[1], origin[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush mins = %f %f %f\n", mins[0], mins[1], mins[2]); + //botimport.Print(PRT_MESSAGE, "teleporter brush maxs = %f %f %f\n", maxs[0], maxs[1], maxs[2]); + VectorAdd(origin, mins, mins); + VectorAdd(origin, maxs, maxs); + // + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + //link an invalid (-1) entity + areas = AAS_LinkEntityClientBBox(mins, maxs, -1, PRESENCE_CROUCH); + if (!areas) botimport.Print(PRT_MESSAGE, "trigger_multiple not in any area\n"); + // + for (link = areas; link; link = link->next_area) + { + //if (!AAS_AreaGrounded(link->areanum)) continue; + if (!AAS_AreaTeleporter(link->areanum)) continue; + // + area1num = link->areanum; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) break; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(mid, lreach->start); + VectorCopy(destorigin, lreach->end); + lreach->traveltype = TRAVEL_TELEPORT; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_teleport; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_teleport++; + } //end for + //unlink the invalid entity + AAS_UnlinkFromAreas(areas); + } //end for +} //end of the function AAS_Reachability_Teleport +//=========================================================================== +// create possible elevator (func_plat) reachabilities +// this is very game dependent.... :( +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_Elevator(void) +{ + int area1num, area2num, modelnum, i, j, k, l, n, p; + float lip, height, speed; + char model[MAX_EPAIRKEY], classname[MAX_EPAIRKEY]; + int ent; + vec3_t mins, maxs, origin, angles = {0, 0, 0}; + vec3_t pos1, pos2, mids, platbottom, plattop; + vec3_t bottomorg, toporg, start, end, dir; + vec_t xvals[8], yvals[8], xvals_top[8], yvals_top[8]; + aas_lreachability_t *lreach; + aas_trace_t trace; + +#ifdef REACH_DEBUG + Log_Write("AAS_Reachability_Elevator\r\n"); +#endif //REACH_DEBUG + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (!strcmp(classname, "func_plat")) + { +#ifdef REACH_DEBUG + Log_Write("found func plat\r\n"); +#endif //REACH_DEBUG + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "func_plat without model\n"); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi(model+1); + if (modelnum <= 0) + { + botimport.Print(PRT_ERROR, "func_plat with invalid model number\n"); + continue; + } //end if + //get the mins, maxs and origin of the model + //NOTE: the origin is usually (0,0,0) and the mins and maxs + // are the absolute mins and maxs + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + AAS_VectorForBSPEpairKey(ent, "origin", origin); + //pos1 is the top position, pos2 is the bottom + VectorCopy(origin, pos1); + VectorCopy(origin, pos2); + //get the lip of the plat + AAS_FloatForBSPEpairKey(ent, "lip", &lip); + if (!lip) lip = 8; + //get the movement height of the plat + AAS_FloatForBSPEpairKey(ent, "height", &height); + if (!height) height = (maxs[2] - mins[2]) - lip; + //get the speed of the plat + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) speed = 200; + //get bottom position below pos1 + pos2[2] -= height; + // + //get a point just above the plat in the bottom position + VectorAdd(mins, maxs, mids); + VectorMA(pos2, 0.5, mids, platbottom); + platbottom[2] = maxs[2] - (pos1[2] - pos2[2]) + 2; + //get a point just above the plat in the top position + VectorAdd(mins, maxs, mids); + VectorMA(pos2, 0.5, mids, plattop); + plattop[2] = maxs[2] + 2; + // + /*if (!area1num) + { + Log_Write("no grounded area near plat bottom\r\n"); + continue; + } //end if*/ + //get the mins and maxs a little larger + for (i = 0; i < 3; i++) + { + mins[i] -= 1; + maxs[i] += 1; + } //end for + // + //botimport.Print(PRT_MESSAGE, "platbottom[2] = %1.1f plattop[2] = %1.1f\n", platbottom[2], plattop[2]); + // + VectorAdd(mins, maxs, mids); + VectorScale(mids, 0.5, mids); + // + xvals[0] = mins[0]; xvals[1] = mids[0]; xvals[2] = maxs[0]; xvals[3] = mids[0]; + yvals[0] = mids[1]; yvals[1] = maxs[1]; yvals[2] = mids[1]; yvals[3] = mins[1]; + // + xvals[4] = mins[0]; xvals[5] = maxs[0]; xvals[6] = maxs[0]; xvals[7] = mins[0]; + yvals[4] = maxs[1]; yvals[5] = maxs[1]; yvals[6] = mins[1]; yvals[7] = mins[1]; + //find adjacent areas around the bottom of the plat + for (i = 0; i < 9; i++) + { + if (i < 8) //check at the sides of the plat + { + bottomorg[0] = origin[0] + xvals[i]; + bottomorg[1] = origin[1] + yvals[i]; + bottomorg[2] = platbottom[2] + 16; + //get a grounded or swim area near the plat in the bottom position + area1num = AAS_PointAreaNum(bottomorg); + for (k = 0; k < 16; k++) + { + if (area1num) + { + if (AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) break; + } //end if + bottomorg[2] += 4; + area1num = AAS_PointAreaNum(bottomorg); + } //end if + //if in solid + if (k >= 16) + { + continue; + } //end if + } //end if + else //at the middle of the plat + { + VectorCopy(plattop, bottomorg); + bottomorg[2] += 24; + area1num = AAS_PointAreaNum(bottomorg); + if (!area1num) continue; + VectorCopy(platbottom, bottomorg); + bottomorg[2] += 24; + } //end else + //look at adjacent areas around the top of the plat + //make larger steps to outside the plat everytime + for (n = 0; n < 3; n++) + { + for (k = 0; k < 3; k++) + { + mins[k] -= 4; + maxs[k] += 4; + } //end for + xvals_top[0] = mins[0]; xvals_top[1] = mids[0]; xvals_top[2] = maxs[0]; xvals_top[3] = mids[0]; + yvals_top[0] = mids[1]; yvals_top[1] = maxs[1]; yvals_top[2] = mids[1]; yvals_top[3] = mins[1]; + // + xvals_top[4] = mins[0]; xvals_top[5] = maxs[0]; xvals_top[6] = maxs[0]; xvals_top[7] = mins[0]; + yvals_top[4] = maxs[1]; yvals_top[5] = maxs[1]; yvals_top[6] = mins[1]; yvals_top[7] = mins[1]; + // + for (j = 0; j < 8; j++) + { + toporg[0] = origin[0] + xvals_top[j]; + toporg[1] = origin[1] + yvals_top[j]; + toporg[2] = plattop[2] + 16; + //get a grounded or swim area near the plat in the top position + area2num = AAS_PointAreaNum(toporg); + for (l = 0; l < 16; l++) + { + if (area2num) + { + if (AAS_AreaGrounded(area2num) || AAS_AreaSwim(area2num)) + { + VectorCopy(plattop, start); + start[2] += 32; + VectorCopy(toporg, end); + end[2] += 1; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.fraction >= 1) break; + } //end if + } //end if + toporg[2] += 4; + area2num = AAS_PointAreaNum(toporg); + } //end if + //if in solid + if (l >= 16) continue; + //never create a reachability in the same area + if (area2num == area1num) continue; + //if the area isn't grounded + if (!AAS_AreaGrounded(area2num)) continue; + //if there already exists reachability between the areas + if (AAS_ReachabilityExists(area1num, area2num)) continue; + //if the reachability start is within the elevator bounding box + VectorSubtract(bottomorg, platbottom, dir); + VectorNormalize(dir); + dir[0] = bottomorg[0] + 24 * dir[0]; + dir[1] = bottomorg[1] + 24 * dir[1]; + dir[2] = bottomorg[2]; + // + for (p = 0; p < 3; p++) + if (dir[p] < origin[p] + mins[p] || dir[p] > origin[p] + maxs[p]) break; + if (p >= 3) continue; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) continue; + lreach->areanum = area2num; + //the facenum is the model number + lreach->facenum = modelnum; + //the edgenum is the height + lreach->edgenum = (int) height; + // + VectorCopy(dir, lreach->start); + VectorCopy(toporg, lreach->end); + lreach->traveltype = TRAVEL_ELEVATOR; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_startelevator + height * 100 / speed; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + //don't go any further to the outside + n = 9999; + // +#ifdef REACH_DEBUG + Log_Write("elevator reach from %d to %d\r\n", area1num, area2num); +#endif //REACH_DEBUG + // + reach_elevator++; + } //end for + } //end for + } //end for + } //end if + } //end for +} //end of the function AAS_Reachability_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_lreachability_t *AAS_FindFaceReachabilities(vec3_t *facepoints, int numpoints, aas_plane_t *plane, int towardsface) +{ + int i, j, k, l; + int facenum, edgenum, bestfacenum; + float *v1, *v2, *v3, *v4; + float bestdist, speed, hordist, dist; + vec3_t beststart, beststart2, bestend, bestend2, tmp, hordir, testpoint; + aas_lreachability_t *lreach, *lreachabilities; + aas_area_t *area; + aas_face_t *face; + aas_edge_t *edge; + aas_plane_t *faceplane, *bestfaceplane; + + // + lreachabilities = NULL; + bestfacenum = 0; + bestfaceplane = NULL; + // + for (i = 1; i < aasworld.numareas; i++) + { + area = &aasworld.areas[i]; + // get the shortest distance between one of the func_bob start edges and + // one of the face edges of area1 + bestdist = 999999; + for (j = 0; j < area->numfaces; j++) + { + facenum = aasworld.faceindex[area->firstface + j]; + face = &aasworld.faces[abs(facenum)]; + //if not a ground face + if (!(face->faceflags & FACE_GROUND)) continue; + //get the ground planes + faceplane = &aasworld.planes[face->planenum]; + // + for (k = 0; k < face->numedges; k++) + { + edgenum = abs(aasworld.edgeindex[face->firstedge + k]); + edge = &aasworld.edges[edgenum]; + //calculate the minimum distance between the two edges + v1 = aasworld.vertexes[edge->v[0]]; + v2 = aasworld.vertexes[edge->v[1]]; + // + for (l = 0; l < numpoints; l++) + { + v3 = facepoints[l]; + v4 = facepoints[(l+1) % numpoints]; + dist = AAS_ClosestEdgePoints(v1, v2, v3, v4, faceplane, plane, + beststart, bestend, + beststart2, bestend2, bestdist); + if (dist < bestdist) + { + bestfacenum = facenum; + bestfaceplane = faceplane; + bestdist = dist; + } //end if + } //end for + } //end for + } //end for + // + if (bestdist > 192) continue; + // + VectorMiddle(beststart, beststart2, beststart); + VectorMiddle(bestend, bestend2, bestend); + // + if (!towardsface) + { + VectorCopy(beststart, tmp); + VectorCopy(bestend, beststart); + VectorCopy(tmp, bestend); + } //end if + // + VectorSubtract(bestend, beststart, hordir); + hordir[2] = 0; + hordist = VectorLength(hordir); + // + if (hordist > 2 * AAS_MaxJumpDistance(aassettings.phys_jumpvel)) continue; + //the end point should not be significantly higher than the start point + if (bestend[2] - 32 > beststart[2]) continue; + //don't fall down too far + if (bestend[2] < beststart[2] - 128) continue; + //the distance should not be too far + if (hordist > 32) + { + //check for walk off ledge + if (!AAS_HorizontalVelocityForJump(0, beststart, bestend, &speed)) continue; + } //end if + // + beststart[2] += 1; + bestend[2] += 1; + // + if (towardsface) VectorCopy(bestend, testpoint); + else VectorCopy(beststart, testpoint); + testpoint[2] = 0; + testpoint[2] = (bestfaceplane->dist - DotProduct(bestfaceplane->normal, testpoint)) / bestfaceplane->normal[2]; + // + if (!AAS_PointInsideFace(bestfacenum, testpoint, 0.1f)) + { + //if the faces are not overlapping then only go down + if (bestend[2] - 16 > beststart[2]) continue; + } //end if + lreach = AAS_AllocReachability(); + if (!lreach) return lreachabilities; + lreach->areanum = i; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(beststart, lreach->start); + VectorCopy(bestend, lreach->end); + lreach->traveltype = 0; + lreach->traveltime = 0; + lreach->next = lreachabilities; + lreachabilities = lreach; +#ifndef BSPC + if (towardsface) AAS_PermanentLine(lreach->start, lreach->end, 1); + else AAS_PermanentLine(lreach->start, lreach->end, 2); +#endif + } //end for + return lreachabilities; +} //end of the function AAS_FindFaceReachabilities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_FuncBobbing(void) +{ + int ent, spawnflags, modelnum, axis; + int i, numareas, areas[10]; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + vec3_t origin, move_end, move_start, move_start_top, move_end_top; + vec3_t mins, maxs, angles = {0, 0, 0}; + vec3_t start_edgeverts[4], end_edgeverts[4], mid; + vec3_t org, start, end, dir, points[10]; + float height; + aas_plane_t start_plane, end_plane; + aas_lreachability_t *startreach, *endreach, *nextstartreach, *nextendreach, *lreach; + aas_lreachability_t *firststartreach, *firstendreach; + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (strcmp(classname, "func_bobbing")) continue; + AAS_FloatForBSPEpairKey(ent, "height", &height); + if (!height) height = 32; + // + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) + { + botimport.Print(PRT_ERROR, "func_bobbing without model\n"); + continue; + } //end if + //get the model number, and skip the leading * + modelnum = atoi(model+1); + if (modelnum <= 0) + { + botimport.Print(PRT_ERROR, "func_bobbing with invalid model number\n"); + continue; + } //end if + //if the entity has an origin set then use it + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + VectorSet(origin, 0, 0, 0); + // + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + // + VectorAdd(mins, origin, mins); + VectorAdd(maxs, origin, maxs); + // + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + VectorCopy(mid, origin); + // + VectorCopy(origin, move_end); + VectorCopy(origin, move_start); + // + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // set the axis of bobbing + if (spawnflags & 1) axis = 0; + else if (spawnflags & 2) axis = 1; + else axis = 2; + // + move_start[axis] -= height; + move_end[axis] += height; + // + Log_Write("funcbob model %d, start = {%1.1f, %1.1f, %1.1f} end = {%1.1f, %1.1f, %1.1f}\n", + modelnum, move_start[0], move_start[1], move_start[2], move_end[0], move_end[1], move_end[2]); + // +#ifndef BSPC + /* + AAS_DrawPermanentCross(move_start, 4, 1); + AAS_DrawPermanentCross(move_end, 4, 2); + */ +#endif + // + for (i = 0; i < 4; i++) + { + VectorCopy(move_start, start_edgeverts[i]); + start_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + start_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + start_edgeverts[0][0] += maxs[0] - mid[0]; + start_edgeverts[0][1] += maxs[1] - mid[1]; + start_edgeverts[1][0] += maxs[0] - mid[0]; + start_edgeverts[1][1] += mins[1] - mid[1]; + start_edgeverts[2][0] += mins[0] - mid[0]; + start_edgeverts[2][1] += mins[1] - mid[1]; + start_edgeverts[3][0] += mins[0] - mid[0]; + start_edgeverts[3][1] += maxs[1] - mid[1]; + // + start_plane.dist = start_edgeverts[0][2]; + VectorSet(start_plane.normal, 0, 0, 1); + // + for (i = 0; i < 4; i++) + { + VectorCopy(move_end, end_edgeverts[i]); + end_edgeverts[i][2] += maxs[2] - mid[2]; //+ bbox maxs z + end_edgeverts[i][2] += 24; //+ player origin to ground dist + } //end for + end_edgeverts[0][0] += maxs[0] - mid[0]; + end_edgeverts[0][1] += maxs[1] - mid[1]; + end_edgeverts[1][0] += maxs[0] - mid[0]; + end_edgeverts[1][1] += mins[1] - mid[1]; + end_edgeverts[2][0] += mins[0] - mid[0]; + end_edgeverts[2][1] += mins[1] - mid[1]; + end_edgeverts[3][0] += mins[0] - mid[0]; + end_edgeverts[3][1] += maxs[1] - mid[1]; + // + end_plane.dist = end_edgeverts[0][2]; + VectorSet(end_plane.normal, 0, 0, 1); + // +#ifndef BSPC +#if 0 + for (i = 0; i < 4; i++) + { + AAS_PermanentLine(start_edgeverts[i], start_edgeverts[(i+1)%4], 1); + AAS_PermanentLine(end_edgeverts[i], end_edgeverts[(i+1)%4], 1); + } //end for +#endif +#endif + VectorCopy(move_start, move_start_top); + move_start_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + VectorCopy(move_end, move_end_top); + move_end_top[2] += maxs[2] - mid[2] + 24; //+ bbox maxs z + // + if (!AAS_PointAreaNum(move_start_top)) continue; + if (!AAS_PointAreaNum(move_end_top)) continue; + // + for (i = 0; i < 2; i++) + { + // + if (i == 0) + { + firststartreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qtrue); + firstendreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qfalse); + } //end if + else + { + firststartreach = AAS_FindFaceReachabilities(end_edgeverts, 4, &end_plane, qtrue); + firstendreach = AAS_FindFaceReachabilities(start_edgeverts, 4, &start_plane, qfalse); + } //end else + // + //create reachabilities from start to end + for (startreach = firststartreach; startreach; startreach = nextstartreach) + { + nextstartreach = startreach->next; + // + //trace = AAS_TraceClientBBox(startreach->start, move_start_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + for (endreach = firstendreach; endreach; endreach = nextendreach) + { + nextendreach = endreach->next; + // + //trace = AAS_TraceClientBBox(endreach->end, move_end_top, PRESENCE_NORMAL, -1); + //if (trace.fraction < 1) continue; + // + Log_Write("funcbob reach from area %d to %d\n", startreach->areanum, endreach->areanum); + // + // + if (i == 0) VectorCopy(move_start_top, org); + else VectorCopy(move_end_top, org); + VectorSubtract(startreach->start, org, dir); + dir[2] = 0; + VectorNormalize(dir); + VectorCopy(startreach->start, start); + VectorMA(startreach->start, 1, dir, start); + start[2] += 1; + VectorMA(startreach->start, 16, dir, end); + end[2] += 1; + // + numareas = AAS_TraceAreas(start, end, areas, points, 10); + if (numareas <= 0) continue; + if (numareas > 1) VectorCopy(points[1], startreach->start); + else VectorCopy(end, startreach->start); + // + if (!AAS_PointAreaNum(startreach->start)) continue; + if (!AAS_PointAreaNum(endreach->end)) continue; + // + lreach = AAS_AllocReachability(); + lreach->areanum = endreach->areanum; + if (i == 0) lreach->edgenum = ((int)move_start[axis] << 16) | ((int) move_end[axis] & 0x0000ffff); + else lreach->edgenum = ((int)move_end[axis] << 16) | ((int) move_start[axis] & 0x0000ffff); + lreach->facenum = (spawnflags << 16) | modelnum; + VectorCopy(startreach->start, lreach->start); + VectorCopy(endreach->end, lreach->end); +#ifndef BSPC +// AAS_DrawArrow(lreach->start, lreach->end, LINECOLOR_BLUE, LINECOLOR_YELLOW); +// AAS_PermanentLine(lreach->start, lreach->end, 1); +#endif + lreach->traveltype = TRAVEL_FUNCBOB; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_funcbob; + reach_funcbob++; + lreach->next = areareachability[startreach->areanum]; + areareachability[startreach->areanum] = lreach; + // + } //end for + } //end for + for (startreach = firststartreach; startreach; startreach = nextstartreach) + { + nextstartreach = startreach->next; + AAS_FreeReachability(startreach); + } //end for + for (endreach = firstendreach; endreach; endreach = nextendreach) + { + nextendreach = endreach->next; + AAS_FreeReachability(endreach); + } //end for + //only go up with func_bobbing entities that go up and down + if (!(spawnflags & 1) && !(spawnflags & 2)) break; + } //end for + } //end for +} //end of the function AAS_Reachability_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_JumpPad(void) +{ + int face2num, i, ret, area2num, visualize, ent, bot_visualizejumppads; + //int modelnum, ent2; + //float dist, time, height, gravity, forward; + float speed, zvel; + //float hordist; + aas_face_t *face2; + aas_area_t *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, dir, cmdmove; + vec3_t velocity, absmins, absmaxs; + //vec3_t origin, ent2origin, angles, teststart; + aas_clientmove_t move; + //aas_trace_t trace; + aas_link_t *areas, *link; + //char target[MAX_EPAIRKEY], targetname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + char classname[MAX_EPAIRKEY]; + +#ifdef BSPC + bot_visualizejumppads = 0; +#else + bot_visualizejumppads = LibVarValue("bot_visualizejumppads", "0"); +#endif + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (strcmp(classname, "trigger_push")) continue; + // + if (!AAS_GetJumpPadInfo(ent, areastart, absmins, absmaxs, velocity)) continue; + /* + // + AAS_FloatForBSPEpairKey(ent, "speed", &speed); + if (!speed) speed = 1000; +// AAS_VectorForBSPEpairKey(ent, "angles", angles); +// AAS_SetMovedir(angles, velocity); +// VectorScale(velocity, speed, velocity); + VectorClear(angles); + //get the mins, maxs and origin of the model + AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY); + if (model[0]) modelnum = atoi(model+1); + else modelnum = 0; + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, absmins, absmaxs, origin); + VectorAdd(origin, absmins, absmins); + VectorAdd(origin, absmaxs, absmaxs); + // +#ifdef REACH_DEBUG + botimport.Print(PRT_MESSAGE, "absmins = %f %f %f\n", absmins[0], absmins[1], absmins[2]); + botimport.Print(PRT_MESSAGE, "absmaxs = %f %f %f\n", absmaxs[0], absmaxs[1], absmaxs[2]); +#endif REACH_DEBUG + VectorAdd(absmins, absmaxs, origin); + VectorScale (origin, 0.5, origin); + + //get the start areas + VectorCopy(origin, teststart); + teststart[2] += 64; + trace = AAS_TraceClientBBox(teststart, origin, PRESENCE_CROUCH, -1); + if (trace.startsolid) + { + botimport.Print(PRT_MESSAGE, "trigger_push start solid\n"); + VectorCopy(origin, areastart); + } //end if + else + { + VectorCopy(trace.endpos, areastart); + } //end else + areastart[2] += 0.125; + // + //AAS_DrawPermanentCross(origin, 4, 4); + //get the target entity + AAS_ValueForBSPEpairKey(ent, "target", target, MAX_EPAIRKEY); + for (ent2 = AAS_NextBSPEntity(0); ent2; ent2 = AAS_NextBSPEntity(ent2)) + { + if (!AAS_ValueForBSPEpairKey(ent2, "targetname", targetname, MAX_EPAIRKEY)) continue; + if (!strcmp(targetname, target)) break; + } //end for + if (!ent2) + { + botimport.Print(PRT_MESSAGE, "trigger_push without target entity %s\n", target); + continue; + } //end if + AAS_VectorForBSPEpairKey(ent2, "origin", ent2origin); + // + height = ent2origin[2] - origin[2]; + gravity = aassettings.sv_gravity; + time = sqrt( height / ( 0.5 * gravity ) ); + if (!time) + { + botimport.Print(PRT_MESSAGE, "trigger_push without time\n"); + continue; + } //end if + // set s.origin2 to the push velocity + VectorSubtract ( ent2origin, origin, velocity); + dist = VectorNormalize( velocity); + forward = dist / time; + //FIXME: why multiply by 1.1 + forward *= 1.1; + VectorScale(velocity, forward, velocity); + velocity[2] = time * gravity; + */ + //get the areas the jump pad brush is in + areas = AAS_LinkEntityClientBBox(absmins, absmaxs, -1, PRESENCE_CROUCH); + /* + for (link = areas; link; link = link->next_area) + { + if (link->areanum == 563) + { + ret = qfalse; + } + } + */ + for (link = areas; link; link = link->next_area) + { + if (AAS_AreaJumpPad(link->areanum)) break; + } //end for + if (!link) + { + botimport.Print(PRT_MESSAGE, "trigger_push not in any jump pad area\n"); + AAS_UnlinkFromAreas(areas); + continue; + } //end if + // + botimport.Print(PRT_MESSAGE, "found a trigger_push with velocity %f %f %f\n", velocity[0], velocity[1], velocity[2]); + //if there is a horizontal velocity check for a reachability without air control + if (velocity[0] || velocity[1]) + { + VectorSet(cmdmove, 0, 0, 0); + //VectorCopy(velocity, cmdmove); + //cmdmove[2] = 0; + Com_Memset(&move, 0, sizeof(aas_clientmove_t)); + area2num = 0; + for (i = 0; i < 20; i++) + { + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 0, 30, 0.1f, + SE_HITGROUND|SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER, 0, bot_visualizejumppads); + area2num = move.endarea; + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + if (link->areanum == area2num) break; + } //end if + if (!link) break; + VectorCopy(move.endpos, areastart); + VectorCopy(move.velocity, velocity); + } //end for + if (area2num && i < 20) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + if (AAS_ReachabilityExists(link->areanum, area2num)) continue; + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + AAS_UnlinkFromAreas(areas); + return; + } //end if + lreach->areanum = area2num; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt(velocity[0] * velocity[0] + velocity[1] * velocity[1]); + VectorCopy(areastart, lreach->start); + VectorCopy(move.endpos, lreach->end); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_jumppad; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } //end if + } //end if + // + if (fabs(velocity[0]) > 100 || fabs(velocity[1]) > 100) continue; + //check for areas we can reach with air control + for (area2num = 1; area2num < aasworld.numareas; area2num++) + { + visualize = qfalse; + /* + if (area2num == 3568) + { + for (link = areas; link; link = link->next_area) + { + if (link->areanum == 3380) + { + visualize = qtrue; + botimport.Print(PRT_MESSAGE, "bah\n"); + } //end if + } //end for + } //end if*/ + //never try to go back to one of the original jumppad areas + //and don't create reachabilities if they already exist + for (link = areas; link; link = link->next_area) + { + if (AAS_ReachabilityExists(link->areanum, area2num)) break; + if (AAS_AreaJumpPad(link->areanum)) + { + if (link->areanum == area2num) break; + } //end if + } //end if + if (link) continue; + // + area2 = &aasworld.areas[area2num]; + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + //if it is not a ground face + if (!(face2->faceflags & FACE_GROUND)) continue; + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up + if (facecenter[2] < areastart[2]) continue; + //get the jumppad jump z velocity + zvel = velocity[2]; + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed + ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); + if (ret && speed < 150) + { + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + dir[2] = 0; + //hordist = VectorNormalize(dir); + //if (hordist < 1.6 * facecenter[2] - areastart[2]) + { + //get command movement + VectorScale(dir, speed, cmdmove); + // + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qfalse, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER|SE_HITGROUNDAREA, area2num, visualize); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if (move.frames < 30 && + !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD|SE_TOUCHTELEPORTER))) + { + //never go back to the same jumppad + for (link = areas; link; link = link->next_area) + { + if (link->areanum == move.endarea) break; + } + if (!link) + { + for (link = areas; link; link = link->next_area) + { + if (!AAS_AreaJumpPad(link->areanum)) continue; + if (AAS_ReachabilityExists(link->areanum, area2num)) continue; + //create a jumppad reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) + { + AAS_UnlinkFromAreas(areas); + return; + } //end if + lreach->areanum = move.endarea; + //NOTE: the facenum is the Z velocity + lreach->facenum = velocity[2]; + //NOTE: the edgenum is the horizontal velocity + lreach->edgenum = sqrt(cmdmove[0] * cmdmove[0] + cmdmove[1] * cmdmove[1]); + VectorCopy(areastart, lreach->start); + VectorCopy(facecenter, lreach->end); + lreach->traveltype = TRAVEL_JUMPPAD; + lreach->traveltype |= AAS_TravelFlagsForTeam(ent); + lreach->traveltime = aassettings.rs_aircontrolledjumppad; + lreach->next = areareachability[link->areanum]; + areareachability[link->areanum] = lreach; + // + reach_jumppad++; + } //end for + } + } //end if + } //end if + } //end for + } //end for + } //end for + AAS_UnlinkFromAreas(areas); + } //end for +} //end of the function AAS_Reachability_JumpPad +//=========================================================================== +// never point at ground faces +// always a higher and pretty far area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_Grapple(int area1num, int area2num) +{ + int face2num, i, j, areanum, numareas, areas[20]; + float mingrappleangle, z, hordist; + bsp_trace_t bsptrace; + aas_trace_t trace; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, down = {0, 0, -1}; + vec_t *v; + + //only grapple when on the ground or swimming + if (!AAS_AreaGrounded(area1num) && !AAS_AreaSwim(area1num)) return qfalse; + //don't grapple from a crouch area + if (!(AAS_AreaPresenceType(area1num) & PRESENCE_NORMAL)) return qfalse; + //NOTE: disabled area swim it doesn't work right + if (AAS_AreaSwim(area1num)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //don't grapple towards way lower areas + if (area2->maxs[2] < area1->mins[2]) return qfalse; + // + VectorCopy(aasworld.areas[area1num].center, start); + //if not a swim area + if (!AAS_AreaSwim(area1num)) + { + if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2]); + VectorCopy(start, end); + end[2] -= 1000; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) return qfalse; + VectorCopy(trace.endpos, areastart); + } //end if + else + { + if (!(AAS_PointContents(start) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return qfalse; + } //end else + // + //start is now the start point + // + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + //if it is not a solid face + if (!(face2->faceflags & FACE_SOLID)) continue; + //direction towards the first vertex of the face + v = aasworld.vertexes[aasworld.edges[abs(aasworld.edgeindex[face2->firstedge])].v[0]]; + VectorSubtract(v, areastart, dir); + //if the face plane is facing away + if (DotProduct(aasworld.planes[face2->planenum].normal, dir) > 0) continue; + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up with the grapple + if (facecenter[2] < areastart[2] + 64) continue; + //only use vertical faces or downward facing faces + if (DotProduct(aasworld.planes[face2->planenum].normal, down) < 0) continue; + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + // + z = dir[2]; + dir[2] = 0; + hordist = VectorLength(dir); + if (!hordist) continue; + //if too far + if (hordist > 2000) continue; + //check the minimal angle of the movement + mingrappleangle = 15; //15 degrees + if (z / hordist < tan(2 * M_PI * mingrappleangle / 360)) continue; + // + VectorCopy(facecenter, start); + VectorMA(facecenter, -500, aasworld.planes[face2->planenum].normal, end); + // + bsptrace = AAS_Trace(start, NULL, NULL, end, 0, CONTENTS_SOLID); + //the grapple won't stick to the sky and the grapple point should be near the AAS wall + if ((bsptrace.surface.flags & SURF_SKY) || (bsptrace.fraction * 500 > 32)) continue; + //trace a full bounding box from the area center on the ground to + //the center of the face + VectorSubtract(facecenter, areastart, dir); + VectorNormalize(dir); + VectorMA(areastart, 4, dir, start); + VectorCopy(bsptrace.endpos, end); + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + VectorSubtract(trace.endpos, facecenter, dir); + if (VectorLength(dir) > 24) continue; + // + VectorCopy(trace.endpos, start); + VectorCopy(trace.endpos, end); + end[2] -= AAS_FallDamageDistance(); + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, -1); + if (trace.fraction >= 1) continue; + //area to end in + areanum = AAS_PointAreaNum(trace.endpos); + //if not in lava or slime + if (aasworld.areasettings[areanum].contents & (AREACONTENTS_SLIME|AREACONTENTS_LAVA)) + { + continue; + } //end if + //do not go the the source area + if (areanum == area1num) continue; + //don't create reachabilities if they already exist + if (AAS_ReachabilityExists(area1num, areanum)) continue; + //only end in areas we can stand + if (!AAS_AreaGrounded(areanum)) continue; + //never go through cluster portals!! + numareas = AAS_TraceAreas(areastart, bsptrace.endpos, areas, NULL, 20); + if (numareas >= 20) continue; + for (j = 0; j < numareas; j++) + { + if (aasworld.areasettings[areas[j]].contents & AREACONTENTS_CLUSTERPORTAL) break; + } //end for + if (j < numareas) continue; + //create a new reachability link + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = areanum; + lreach->facenum = face2num; + lreach->edgenum = 0; + VectorCopy(areastart, lreach->start); + //VectorCopy(facecenter, lreach->end); + VectorCopy(bsptrace.endpos, lreach->end); + lreach->traveltype = TRAVEL_GRAPPLEHOOK; + VectorSubtract(lreach->end, lreach->start, dir); + lreach->traveltime = aassettings.rs_startgrapple + VectorLength(dir) * 0.25; + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_grapple++; + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_SetWeaponJumpAreaFlags(void) +{ + int ent, i; + vec3_t mins = {-15, -15, -15}, maxs = {15, 15, 15}; + vec3_t origin; + int areanum, weaponjumpareas, spawnflags; + char classname[MAX_EPAIRKEY]; + + weaponjumpareas = 0; + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if ( + !strcmp(classname, "item_armor_body") || + !strcmp(classname, "item_armor_combat") || + !strcmp(classname, "item_health_mega") || + !strcmp(classname, "weapon_grenadelauncher") || + !strcmp(classname, "weapon_rocketlauncher") || + !strcmp(classname, "weapon_lightning") || + !strcmp(classname, "weapon_plasmagun") || + !strcmp(classname, "weapon_railgun") || + !strcmp(classname, "weapon_bfg") || + !strcmp(classname, "item_quad") || + !strcmp(classname, "item_regen") || + !strcmp(classname, "item_invulnerability")) + { + if (AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + spawnflags = 0; + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(origin, mins, maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end if + //areanum = AAS_PointAreaNum(origin); + areanum = AAS_BestReachableArea(origin, mins, maxs, origin); + //the bot may rocket jump towards this area + aasworld.areasettings[areanum].areaflags |= AREA_WEAPONJUMP; + // + //if (!AAS_AreaGrounded(areanum)) + // botimport.Print(PRT_MESSAGE, "area not grounded\n"); + // + weaponjumpareas++; + } //end if + } //end if + } //end for + for (i = 1; i < aasworld.numareas; i++) + { + if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + aasworld.areasettings[i].areaflags |= AREA_WEAPONJUMP; + weaponjumpareas++; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "%d weapon jump areas\n", weaponjumpareas); +} //end of the function AAS_SetWeaponJumpAreaFlags +//=========================================================================== +// create a possible weapon jump reachability from area1 to area2 +// +// check if there's a cool item in the second area +// check if area1 is lower than area2 +// check if the bot can rocketjump from area1 to area2 +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_Reachability_WeaponJump(int area1num, int area2num) +{ + int face2num, i, n, ret, visualize; + float speed, zvel; + //float hordist; + aas_face_t *face2; + aas_area_t *area1, *area2; + aas_lreachability_t *lreach; + vec3_t areastart, facecenter, start, end, dir, cmdmove;// teststart; + vec3_t velocity; + aas_clientmove_t move; + aas_trace_t trace; + + visualize = qfalse; +// if (area1num == 4436 && area2num == 4318) +// { +// visualize = qtrue; +// } + if (!AAS_AreaGrounded(area1num) || AAS_AreaSwim(area1num)) return qfalse; + if (!AAS_AreaGrounded(area2num)) return qfalse; + //NOTE: only weapon jump towards areas with an interesting item in it?? + if (!(aasworld.areasettings[area2num].areaflags & AREA_WEAPONJUMP)) return qfalse; + // + area1 = &aasworld.areas[area1num]; + area2 = &aasworld.areas[area2num]; + //don't weapon jump towards way lower areas + if (area2->maxs[2] < area1->mins[2]) return qfalse; + // + VectorCopy(aasworld.areas[area1num].center, start); + //if not a swim area + if (!AAS_PointAreaNum(start)) Log_Write("area %d center %f %f %f in solid?\r\n", area1num, + start[0], start[1], start[2]); + VectorCopy(start, end); + end[2] -= 1000; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (trace.startsolid) return qfalse; + VectorCopy(trace.endpos, areastart); + // + //areastart is now the start point + // + for (i = 0; i < area2->numfaces; i++) + { + face2num = aasworld.faceindex[area2->firstface + i]; + face2 = &aasworld.faces[abs(face2num)]; + //if it is not a solid face + if (!(face2->faceflags & FACE_GROUND)) continue; + //get the center of the face + AAS_FaceCenter(face2num, facecenter); + //only go higher up with weapon jumps + if (facecenter[2] < areastart[2] + 64) continue; + //NOTE: set to 2 to allow bfg jump reachabilities + for (n = 0; n < 1/*2*/; n++) + { + //get the rocket jump z velocity + if (n) zvel = AAS_BFGJumpZVelocity(areastart); + else zvel = AAS_RocketJumpZVelocity(areastart); + //get the horizontal speed for the jump, if it isn't possible to calculate this + //speed (the jump is not possible) then there's no jump reachability created + ret = AAS_HorizontalVelocityForJump(zvel, areastart, facecenter, &speed); + if (ret && speed < 300) + { + //direction towards the face center + VectorSubtract(facecenter, areastart, dir); + dir[2] = 0; + //hordist = VectorNormalize(dir); + //if (hordist < 1.6 * (facecenter[2] - areastart[2])) + { + //get command movement + VectorScale(dir, speed, cmdmove); + VectorSet(velocity, 0, 0, zvel); + /* + //get command movement + VectorScale(dir, speed, velocity); + velocity[2] = zvel; + VectorSet(cmdmove, 0, 0, 0); + */ + // + AAS_PredictClientMovement(&move, -1, areastart, PRESENCE_NORMAL, qtrue, + velocity, cmdmove, 30, 30, 0.1f, + SE_ENTERWATER|SE_ENTERSLIME| + SE_ENTERLAVA|SE_HITGROUNDDAMAGE| + SE_TOUCHJUMPPAD|SE_HITGROUND|SE_HITGROUNDAREA, area2num, visualize); + //if prediction time wasn't enough to fully predict the movement + //don't enter slime or lava and don't fall from too high + if (move.frames < 30 && + !(move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + && (move.stopevent & (SE_HITGROUNDAREA|SE_TOUCHJUMPPAD))) + { + //create a rocket or bfg jump reachability from area1 to area2 + lreach = AAS_AllocReachability(); + if (!lreach) return qfalse; + lreach->areanum = area2num; + lreach->facenum = 0; + lreach->edgenum = 0; + VectorCopy(areastart, lreach->start); + VectorCopy(facecenter, lreach->end); + if (n) + { + lreach->traveltype = TRAVEL_BFGJUMP; + lreach->traveltime = aassettings.rs_bfgjump; + } //end if + else + { + lreach->traveltype = TRAVEL_ROCKETJUMP; + lreach->traveltime = aassettings.rs_rocketjump; + } //end else + lreach->next = areareachability[area1num]; + areareachability[area1num] = lreach; + // + reach_rocketjump++; + return qtrue; + } //end if + } //end if + } //end if + } //end for + } //end for + // + return qfalse; +} //end of the function AAS_Reachability_WeaponJump +//=========================================================================== +// calculates additional walk off ledge reachabilities for the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_Reachability_WalkOffLedge(int areanum) +{ + int i, j, k, l, m, n, p, areas[10], numareas; + int face1num, face2num, face3num, edge1num, edge2num, edge3num; + int otherareanum, gap, reachareanum, side; + aas_area_t *area, *area2; + aas_face_t *face1, *face2, *face3; + aas_edge_t *edge; + aas_plane_t *plane; + vec_t *v1, *v2; + vec3_t sharededgevec, mid, dir, testend; + aas_lreachability_t *lreach; + aas_trace_t trace; + + if (!AAS_AreaGrounded(areanum) || AAS_AreaSwim(areanum)) return; + // + area = &aasworld.areas[areanum]; + // + for (i = 0; i < area->numfaces; i++) + { + face1num = aasworld.faceindex[area->firstface + i]; + face1 = &aasworld.faces[abs(face1num)]; + //face 1 must be a ground face + if (!(face1->faceflags & FACE_GROUND)) continue; + //go through all the edges of this ground face + for (k = 0; k < face1->numedges; k++) + { + edge1num = aasworld.edgeindex[face1->firstedge + k]; + //find another not ground face using this same edge + for (j = 0; j < area->numfaces; j++) + { + face2num = aasworld.faceindex[area->firstface + j]; + face2 = &aasworld.faces[abs(face2num)]; + //face 2 may not be a ground face + if (face2->faceflags & FACE_GROUND) continue; + //compare all the edges + for (l = 0; l < face2->numedges; l++) + { + edge2num = aasworld.edgeindex[face2->firstedge + l]; + if (abs(edge1num) == abs(edge2num)) + { + //get the area at the other side of the face + if (face2->frontarea == areanum) otherareanum = face2->backarea; + else otherareanum = face2->frontarea; + // + area2 = &aasworld.areas[otherareanum]; + //if the other area is grounded! + if (aasworld.areasettings[otherareanum].areaflags & AREA_GROUNDED) + { + //check for a possible gap + gap = qfalse; + for (n = 0; n < area2->numfaces; n++) + { + face3num = aasworld.faceindex[area2->firstface + n]; + //may not be the shared face of the two areas + if (abs(face3num) == abs(face2num)) continue; + // + face3 = &aasworld.faces[abs(face3num)]; + //find an edge shared by all three faces + for (m = 0; m < face3->numedges; m++) + { + edge3num = aasworld.edgeindex[face3->firstedge + m]; + //but the edge should be shared by all three faces + if (abs(edge3num) == abs(edge1num)) + { + if (!(face3->faceflags & FACE_SOLID)) + { + gap = qtrue; + break; + } //end if + // + if (face3->faceflags & FACE_GROUND) + { + gap = qfalse; + break; + } //end if + //FIXME: there are more situations to be handled + gap = qtrue; + break; + } //end if + } //end for + if (m < face3->numedges) break; + } //end for + if (!gap) break; + } //end if + //check for a walk off ledge reachability + edge = &aasworld.edges[abs(edge1num)]; + side = edge1num < 0; + // + v1 = aasworld.vertexes[edge->v[side]]; + v2 = aasworld.vertexes[edge->v[!side]]; + // + plane = &aasworld.planes[face1->planenum]; + //get the points really into the areas + VectorSubtract(v2, v1, sharededgevec); + CrossProduct(plane->normal, sharededgevec, dir); + VectorNormalize(dir); + // + VectorAdd(v1, v2, mid); + VectorScale(mid, 0.5, mid); + VectorMA(mid, 8, dir, mid); + // + VectorCopy(mid, testend); + testend[2] -= 1000; + trace = AAS_TraceClientBBox(mid, testend, PRESENCE_CROUCH, -1); + // + if (trace.startsolid) + { + //Log_Write("area %d: trace.startsolid\r\n", areanum); + break; + } //end if + reachareanum = AAS_PointAreaNum(trace.endpos); + if (reachareanum == areanum) + { + //Log_Write("area %d: same area\r\n", areanum); + break; + } //end if + if (AAS_ReachabilityExists(areanum, reachareanum)) + { + //Log_Write("area %d: reachability already exists\r\n", areanum); + break; + } //end if + if (!AAS_AreaGrounded(reachareanum) && !AAS_AreaSwim(reachareanum)) + { + //Log_Write("area %d, reach area %d: not grounded and not swim\r\n", areanum, reachareanum); + break; + } //end if + // + if (aasworld.areasettings[reachareanum].contents & (AREACONTENTS_SLIME + | AREACONTENTS_LAVA)) + { + //Log_Write("area %d, reach area %d: lava or slime\r\n", areanum, reachareanum); + break; + } //end if + //if not going through a cluster portal + numareas = AAS_TraceAreas(mid, testend, areas, NULL, ARRAY_LEN(areas)); + for (p = 0; p < numareas; p++) + if (AAS_AreaClusterPortal(areas[p])) + break; + if (p < numareas) + break; + // if a maximum fall height is set and the bot would fall down further + if (aassettings.rs_maxfallheight && fabs(mid[2] - trace.endpos[2]) > aassettings.rs_maxfallheight) + break; + // + lreach = AAS_AllocReachability(); + if (!lreach) break; + lreach->areanum = reachareanum; + lreach->facenum = 0; + lreach->edgenum = edge1num; + VectorCopy(mid, lreach->start); + VectorCopy(trace.endpos, lreach->end); + lreach->traveltype = TRAVEL_WALKOFFLEDGE; + lreach->traveltime = aassettings.rs_startwalkoffledge + fabs(mid[2] - trace.endpos[2]) * 50 / aassettings.phys_gravity; + if (!AAS_AreaSwim(reachareanum) && !AAS_AreaJumpPad(reachareanum)) + { + if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta5) + { + lreach->traveltime += aassettings.rs_falldamage5; + } //end if + else if (AAS_FallDelta(mid[2] - trace.endpos[2]) > aassettings.phys_falldelta10) + { + lreach->traveltime += aassettings.rs_falldamage10; + } //end if + } //end if + lreach->next = areareachability[areanum]; + areareachability[areanum] = lreach; + //we've got another walk off ledge reachability + reach_walkoffledge++; + } //end if + } //end for + } //end for + } //end for + } //end for +} //end of the function AAS_Reachability_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_StoreReachability(void) +{ + int i; + aas_areasettings_t *areasettings; + aas_lreachability_t *lreach; + aas_reachability_t *reach; + + if (aasworld.reachability) FreeMemory(aasworld.reachability); + aasworld.reachability = (aas_reachability_t *) GetClearedMemory((numlreachabilities + 10) * sizeof(aas_reachability_t)); + aasworld.reachabilitysize = 1; + for (i = 0; i < aasworld.numareas; i++) + { + areasettings = &aasworld.areasettings[i]; + areasettings->firstreachablearea = aasworld.reachabilitysize; + areasettings->numreachableareas = 0; + for (lreach = areareachability[i]; lreach; lreach = lreach->next) + { + reach = &aasworld.reachability[areasettings->firstreachablearea + + areasettings->numreachableareas]; + reach->areanum = lreach->areanum; + reach->facenum = lreach->facenum; + reach->edgenum = lreach->edgenum; + VectorCopy(lreach->start, reach->start); + VectorCopy(lreach->end, reach->end); + reach->traveltype = lreach->traveltype; + reach->traveltime = lreach->traveltime; + // + areasettings->numreachableareas++; + } //end for + aasworld.reachabilitysize += areasettings->numreachableareas; + } //end for +} //end of the function AAS_StoreReachability +//=========================================================================== +// +// TRAVEL_WALK 100% equal floor height + steps +// TRAVEL_CROUCH 100% +// TRAVEL_BARRIERJUMP 100% +// TRAVEL_JUMP 80% +// TRAVEL_LADDER 100% + fall down from ladder + jump up to ladder +// TRAVEL_WALKOFFLEDGE 90% walk off very steep walls? +// TRAVEL_SWIM 100% +// TRAVEL_WATERJUMP 100% +// TRAVEL_TELEPORT 100% +// TRAVEL_ELEVATOR 100% +// TRAVEL_GRAPPLEHOOK 100% +// TRAVEL_DOUBLEJUMP 0% +// TRAVEL_RAMPJUMP 0% +// TRAVEL_STRAFEJUMP 0% +// TRAVEL_ROCKETJUMP 100% (currently limited towards areas with items) +// TRAVEL_BFGJUMP 0% (currently disabled) +// TRAVEL_JUMPPAD 100% +// TRAVEL_FUNCBOB 100% +// +// Parameter: - +// Returns: true if NOT finished +// Changes Globals: - +//=========================================================================== +int AAS_ContinueInitReachability(float time) +{ + int i, j, todo, start_time; + static float framereachability, reachability_delay; + static int lastpercentage; + + if (!aasworld.loaded) return qfalse; + //if reachability is calculated for all areas + if (aasworld.numreachabilityareas >= aasworld.numareas + 2) return qfalse; + //if starting with area 1 (area 0 is a dummy) + if (aasworld.numreachabilityareas == 1) + { + botimport.Print(PRT_MESSAGE, "calculating reachability...\n"); + lastpercentage = 0; + framereachability = 2000; + reachability_delay = 1000; + } //end if + //number of areas to calculate reachability for this cycle + todo = aasworld.numreachabilityareas + (int) framereachability; + start_time = Sys_MilliSeconds(); + //loop over the areas + for (i = aasworld.numreachabilityareas; i < aasworld.numareas && i < todo; i++) + { + aasworld.numreachabilityareas++; + //only create jumppad reachabilities from jumppad areas + if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + continue; + } //end if + //loop over the areas + for (j = 1; j < aasworld.numareas; j++) + { + if (i == j) continue; + //never create reachabilities from teleporter or jumppad areas to regular areas + if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) + { + if (!(aasworld.areasettings[j].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD))) + { + continue; + } //end if + } //end if + //if there already is a reachability link from area i to j + if (AAS_ReachabilityExists(i, j)) continue; + //check for a swim reachability + if (AAS_Reachability_Swim(i, j)) continue; + //check for a simple walk on equal floor height reachability + if (AAS_Reachability_EqualFloorHeight(i, j)) continue; + //check for step, barrier, waterjump and walk off ledge reachabilities + if (AAS_Reachability_Step_Barrier_WaterJump_WalkOffLedge(i, j)) continue; + //check for ladder reachabilities + if (AAS_Reachability_Ladder(i, j)) continue; + //check for a jump reachability + if (AAS_Reachability_Jump(i, j)) continue; + } //end for + //never create these reachabilities from teleporter or jumppad areas + if (aasworld.areasettings[i].contents & (AREACONTENTS_TELEPORTER|AREACONTENTS_JUMPPAD)) + { + continue; + } //end if + //loop over the areas + for (j = 1; j < aasworld.numareas; j++) + { + if (i == j) continue; + // + if (AAS_ReachabilityExists(i, j)) continue; + //check for a grapple hook reachability + if (calcgrapplereach) AAS_Reachability_Grapple(i, j); + //check for a weapon jump reachability + AAS_Reachability_WeaponJump(i, j); + } //end for + //if the calculation took more time than the max reachability delay + if (Sys_MilliSeconds() - start_time > (int) reachability_delay) break; + // + if (aasworld.numreachabilityareas * 1000 / aasworld.numareas > lastpercentage) break; + } //end for + // + if (aasworld.numreachabilityareas == aasworld.numareas) + { + botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) 100.0); + botimport.Print(PRT_MESSAGE, "\nplease wait while storing reachability...\n"); + aasworld.numreachabilityareas++; + } //end if + //if this is the last step in the reachability calculations + else if (aasworld.numreachabilityareas == aasworld.numareas + 1) + { + //create additional walk off ledge reachabilities for every area + for (i = 1; i < aasworld.numareas; i++) + { + //only create jumppad reachabilities from jumppad areas + if (aasworld.areasettings[i].contents & AREACONTENTS_JUMPPAD) + { + continue; + } //end if + AAS_Reachability_WalkOffLedge(i); + } //end for + //create jump pad reachabilities + AAS_Reachability_JumpPad(); + //create teleporter reachabilities + AAS_Reachability_Teleport(); + //create elevator (func_plat) reachabilities + AAS_Reachability_Elevator(); + //create func_bobbing reachabilities + AAS_Reachability_FuncBobbing(); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "%6d reach swim\n", reach_swim); + botimport.Print(PRT_MESSAGE, "%6d reach equal floor\n", reach_equalfloor); + botimport.Print(PRT_MESSAGE, "%6d reach step\n", reach_step); + botimport.Print(PRT_MESSAGE, "%6d reach barrier\n", reach_barrier); + botimport.Print(PRT_MESSAGE, "%6d reach waterjump\n", reach_waterjump); + botimport.Print(PRT_MESSAGE, "%6d reach walkoffledge\n", reach_walkoffledge); + botimport.Print(PRT_MESSAGE, "%6d reach jump\n", reach_jump); + botimport.Print(PRT_MESSAGE, "%6d reach ladder\n", reach_ladder); + botimport.Print(PRT_MESSAGE, "%6d reach walk\n", reach_walk); + botimport.Print(PRT_MESSAGE, "%6d reach teleport\n", reach_teleport); + botimport.Print(PRT_MESSAGE, "%6d reach funcbob\n", reach_funcbob); + botimport.Print(PRT_MESSAGE, "%6d reach elevator\n", reach_elevator); + botimport.Print(PRT_MESSAGE, "%6d reach grapple\n", reach_grapple); + botimport.Print(PRT_MESSAGE, "%6d reach rocketjump\n", reach_rocketjump); + botimport.Print(PRT_MESSAGE, "%6d reach jumppad\n", reach_jumppad); +#endif + //*/ + //store all the reachabilities + AAS_StoreReachability(); + //free the reachability link heap + AAS_ShutDownReachabilityHeap(); + // + FreeMemory(areareachability); + // + aasworld.numreachabilityareas++; + // + botimport.Print(PRT_MESSAGE, "calculating clusters...\n"); + } //end if + else + { + lastpercentage = aasworld.numreachabilityareas * 1000 / aasworld.numareas; + botimport.Print(PRT_MESSAGE, "\r%6.1f%%", (float) lastpercentage / 10); + } //end else + //not yet finished + return qtrue; +} //end of the function AAS_ContinueInitReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitReachability(void) +{ + if (!aasworld.loaded) return; + + if (aasworld.reachabilitysize) + { +#ifndef BSPC + if (!((int)LibVarGetValue("forcereachability"))) + { + aasworld.numreachabilityareas = aasworld.numareas + 2; + return; + } //end if +#else + aasworld.numreachabilityareas = aasworld.numareas + 2; + return; +#endif //BSPC + } //end if +#ifndef BSPC + calcgrapplereach = LibVarGetValue("grapplereach"); +#endif + aasworld.savefile = qtrue; + //start with area 1 because area zero is a dummy + aasworld.numreachabilityareas = 1; + ////aasworld.numreachabilityareas = aasworld.numareas + 1; //only calculate entity reachabilities + //setup the heap with reachability links + AAS_SetupReachabilityHeap(); + //allocate area reachability link array + areareachability = (aas_lreachability_t **) GetClearedMemory( + aasworld.numareas * sizeof(aas_lreachability_t *)); + // + AAS_SetWeaponJumpAreaFlags(); +} //end of the function AAS_InitReachable diff --git a/code/botlib/be_aas_reach.h b/code/botlib/be_aas_reach.h new file mode 100644 index 0000000..0ab8740 --- /dev/null +++ b/code/botlib/be_aas_reach.h @@ -0,0 +1,68 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_reach.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_reach.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize calculating the reachabilities +void AAS_InitReachability(void); +//continue calculating the reachabilities +int AAS_ContinueInitReachability(float time); +// +int AAS_BestReachableLinkArea(aas_link_t *areas); +#endif //AASINTERN + +//returns true if the are has reachabilities to other areas +int AAS_AreaReachability(int areanum); +//returns the best reachable area and goal origin for a bounding box at the given origin +int AAS_BestReachableArea(vec3_t origin, vec3_t mins, vec3_t maxs, vec3_t goalorigin); +//returns the best jumppad area from which the bbox at origin is reachable +int AAS_BestReachableFromJumpPadArea(vec3_t origin, vec3_t mins, vec3_t maxs); +//returns the next reachability using the given model +int AAS_NextModelReachability(int num, int modelnum); +//returns the total area of the ground faces of the given area +float AAS_AreaGroundFaceArea(int areanum); +//returns true if the area is crouch only +int AAS_AreaCrouch(int areanum); +//returns true if a player can swim in this area +int AAS_AreaSwim(int areanum); +//returns true if the area is filled with a liquid +int AAS_AreaLiquid(int areanum); +//returns true if the area contains lava +int AAS_AreaLava(int areanum); +//returns true if the area contains slime +int AAS_AreaSlime(int areanum); +//returns true if the area has one or more ground faces +int AAS_AreaGrounded(int areanum); +//returns true if the area has one or more ladder faces +int AAS_AreaLadder(int areanum); +//returns true if the area is a jump pad +int AAS_AreaJumpPad(int areanum); +//returns true if the area is donotenter +int AAS_AreaDoNotEnter(int areanum); diff --git a/code/botlib/be_aas_route.c b/code/botlib/be_aas_route.c new file mode 100644 index 0000000..1d57b53 --- /dev/null +++ b/code/botlib/be_aas_route.c @@ -0,0 +1,2214 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_route.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_route.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_crc.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define ROUTING_DEBUG + +//travel time in hundreths of a second = distance * 100 / speed +#define DISTANCEFACTOR_CROUCH 1.3f //crouch speed = 100 +#define DISTANCEFACTOR_SWIM 1 //should be 0.66, swim speed = 150 +#define DISTANCEFACTOR_WALK 0.33f //walk speed = 300 + +//cache refresh time +#define CACHE_REFRESHTIME 15.0f //15 seconds refresh time + +//maximum number of routing updates each frame +#define MAX_FRAMEROUTINGUPDATES 10 + + +/* + + area routing cache: + stores the distances within one cluster to a specific goal area + this goal area is in this same cluster and could be a cluster portal + for every cluster there's a list with routing cache for every area + in that cluster (including the portals of that cluster) + area cache stores aasworld.clusters[?].numreachabilityareas travel times + + portal routing cache: + stores the distances of all portals to a specific goal area + this goal area could be in any cluster and could also be a cluster portal + for every area (aasworld.numareas) the portal cache stores + aasworld.numportals travel times + +*/ + +#ifdef ROUTING_DEBUG +int numareacacheupdates; +int numportalcacheupdates; +#endif //ROUTING_DEBUG + +int routingcachesize; +int max_routingcachesize; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef ROUTING_DEBUG +void AAS_RoutingInfo(void) +{ + botimport.Print(PRT_MESSAGE, "%d area cache updates\n", numareacacheupdates); + botimport.Print(PRT_MESSAGE, "%d portal cache updates\n", numportalcacheupdates); + botimport.Print(PRT_MESSAGE, "%d bytes routing cache\n", routingcachesize); +} //end of the function AAS_RoutingInfo +#endif //ROUTING_DEBUG +//=========================================================================== +// returns the number of the area in the cluster +// assumes the given area is in the given cluster or a portal of the cluster +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +ID_INLINE int AAS_ClusterAreaNum(int cluster, int areanum) +{ + int side, areacluster; + + areacluster = aasworld.areasettings[areanum].cluster; + if (areacluster > 0) return aasworld.areasettings[areanum].clusterareanum; + else + { +/*#ifdef ROUTING_DEBUG + if (aasworld.portals[-areacluster].frontcluster != cluster && + aasworld.portals[-areacluster].backcluster != cluster) + { + botimport.Print(PRT_ERROR, "portal %d: does not belong to cluster %d\n" + , -areacluster, cluster); + } //end if +#endif //ROUTING_DEBUG*/ + side = aasworld.portals[-areacluster].frontcluster != cluster; + return aasworld.portals[-areacluster].clusterareanum[side]; + } //end else +} //end of the function AAS_ClusterAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitTravelFlagFromType(void) +{ + int i; + + for (i = 0; i < MAX_TRAVELTYPES; i++) + { + aasworld.travelflagfortype[i] = TFL_INVALID; + } //end for + aasworld.travelflagfortype[TRAVEL_INVALID] = TFL_INVALID; + aasworld.travelflagfortype[TRAVEL_WALK] = TFL_WALK; + aasworld.travelflagfortype[TRAVEL_CROUCH] = TFL_CROUCH; + aasworld.travelflagfortype[TRAVEL_BARRIERJUMP] = TFL_BARRIERJUMP; + aasworld.travelflagfortype[TRAVEL_JUMP] = TFL_JUMP; + aasworld.travelflagfortype[TRAVEL_LADDER] = TFL_LADDER; + aasworld.travelflagfortype[TRAVEL_WALKOFFLEDGE] = TFL_WALKOFFLEDGE; + aasworld.travelflagfortype[TRAVEL_SWIM] = TFL_SWIM; + aasworld.travelflagfortype[TRAVEL_WATERJUMP] = TFL_WATERJUMP; + aasworld.travelflagfortype[TRAVEL_TELEPORT] = TFL_TELEPORT; + aasworld.travelflagfortype[TRAVEL_ELEVATOR] = TFL_ELEVATOR; + aasworld.travelflagfortype[TRAVEL_ROCKETJUMP] = TFL_ROCKETJUMP; + aasworld.travelflagfortype[TRAVEL_BFGJUMP] = TFL_BFGJUMP; + aasworld.travelflagfortype[TRAVEL_GRAPPLEHOOK] = TFL_GRAPPLEHOOK; + aasworld.travelflagfortype[TRAVEL_DOUBLEJUMP] = TFL_DOUBLEJUMP; + aasworld.travelflagfortype[TRAVEL_RAMPJUMP] = TFL_RAMPJUMP; + aasworld.travelflagfortype[TRAVEL_STRAFEJUMP] = TFL_STRAFEJUMP; + aasworld.travelflagfortype[TRAVEL_JUMPPAD] = TFL_JUMPPAD; + aasworld.travelflagfortype[TRAVEL_FUNCBOB] = TFL_FUNCBOB; +} //end of the function AAS_InitTravelFlagFromType +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +ID_INLINE int AAS_TravelFlagForType_inline(int traveltype) +{ + int tfl; + + tfl = 0; + if (tfl & TRAVELFLAG_NOTTEAM1) + tfl |= TFL_NOTTEAM1; + if (tfl & TRAVELFLAG_NOTTEAM2) + tfl |= TFL_NOTTEAM2; + traveltype &= TRAVELTYPE_MASK; + if (traveltype < 0 || traveltype >= MAX_TRAVELTYPES) + return TFL_INVALID; + tfl |= aasworld.travelflagfortype[traveltype]; + return tfl; +} //end of the function AAS_TravelFlagForType_inline +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TravelFlagForType(int traveltype) +{ + return AAS_TravelFlagForType_inline(traveltype); +} //end of the function AAS_TravelFlagForType_inline +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkCache(aas_routingcache_t *cache) +{ + if (cache->time_next) cache->time_next->time_prev = cache->time_prev; + else aasworld.newestcache = cache->time_prev; + if (cache->time_prev) cache->time_prev->time_next = cache->time_next; + else aasworld.oldestcache = cache->time_next; + cache->time_next = NULL; + cache->time_prev = NULL; +} //end of the function AAS_UnlinkCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_LinkCache(aas_routingcache_t *cache) +{ + if (aasworld.newestcache) + { + aasworld.newestcache->time_next = cache; + cache->time_prev = aasworld.newestcache; + } //end if + else + { + aasworld.oldestcache = cache; + cache->time_prev = NULL; + } //end else + cache->time_next = NULL; + aasworld.newestcache = cache; +} //end of the function AAS_LinkCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCache(aas_routingcache_t *cache) +{ + AAS_UnlinkCache(cache); + routingcachesize -= cache->size; + FreeMemory(cache); +} //end of the function AAS_FreeRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheInCluster( int clusternum ) +{ + int i; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + if (!aasworld.clusterareacache) + return; + cluster = &aasworld.clusters[clusternum]; + for (i = 0; i < cluster->numareas; i++) + { + for (cache = aasworld.clusterareacache[clusternum][i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.clusterareacache[clusternum][i] = NULL; + } //end for +} //end of the function AAS_RemoveRoutingCacheInCluster +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_RemoveRoutingCacheUsingArea( int areanum ) +{ + int i, clusternum; + aas_routingcache_t *cache, *nextcache; + + clusternum = aasworld.areasettings[areanum].cluster; + if (clusternum > 0) + { + //remove all the cache in the cluster the area is in + AAS_RemoveRoutingCacheInCluster( clusternum ); + } //end if + else + { + // if this is a portal remove all cache in both the front and back cluster + AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].frontcluster ); + AAS_RemoveRoutingCacheInCluster( aasworld.portals[-clusternum].backcluster ); + } //end else + // remove all portal cache + for (i = 0; i < aasworld.numareas; i++) + { + //refresh portal cache + for (cache = aasworld.portalcache[i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.portalcache[i] = NULL; + } //end for +} //end of the function AAS_RemoveRoutingCacheUsingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_EnableRoutingArea(int areanum, int enable) +{ + int flags; + + if (areanum <= 0 || areanum >= aasworld.numareas) + { + if (botDeveloper) + { + botimport.Print(PRT_ERROR, "AAS_EnableRoutingArea: areanum %d out of range\n", areanum); + } //end if + return 0; + } //end if + flags = aasworld.areasettings[areanum].areaflags & AREA_DISABLED; + if (enable < 0) + return !flags; + + if (enable) + aasworld.areasettings[areanum].areaflags &= ~AREA_DISABLED; + else + aasworld.areasettings[areanum].areaflags |= AREA_DISABLED; + // if the status of the area changed + if ( (flags & AREA_DISABLED) != (aasworld.areasettings[areanum].areaflags & AREA_DISABLED) ) + { + //remove all routing cache involving this area + AAS_RemoveRoutingCacheUsingArea( areanum ); + } //end if + return !flags; +} //end of the function AAS_EnableRoutingArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +ID_INLINE float AAS_RoutingTime(void) +{ + return AAS_Time(); +} //end of the function AAS_RoutingTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_GetAreaContentsTravelFlags(int areanum) +{ + int contents, tfl; + + contents = aasworld.areasettings[areanum].contents; + tfl = 0; + if (contents & AREACONTENTS_WATER) + tfl |= TFL_WATER; + else if (contents & AREACONTENTS_SLIME) + tfl |= TFL_SLIME; + else if (contents & AREACONTENTS_LAVA) + tfl |= TFL_LAVA; + else + tfl |= TFL_AIR; + if (contents & AREACONTENTS_DONOTENTER) + tfl |= TFL_DONOTENTER; + if (contents & AREACONTENTS_NOTTEAM1) + tfl |= TFL_NOTTEAM1; + if (contents & AREACONTENTS_NOTTEAM2) + tfl |= TFL_NOTTEAM2; + if (aasworld.areasettings[areanum].areaflags & AREA_BRIDGE) + tfl |= TFL_BRIDGE; + return tfl; +} //end of the function AAS_GetAreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +ID_INLINE int AAS_AreaContentsTravelFlags_inline(int areanum) +{ + return aasworld.areacontentstravelflags[areanum]; +} //end of the function AAS_AreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaContentsTravelFlags(int areanum) +{ + return aasworld.areacontentstravelflags[areanum]; +} //end of the function AAS_AreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAreaContentsTravelFlags(void) +{ + int i; + + if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); + aasworld.areacontentstravelflags = (int *) GetClearedMemory(aasworld.numareas * sizeof(int)); + // + for (i = 0; i < aasworld.numareas; i++) { + aasworld.areacontentstravelflags[i] = AAS_GetAreaContentsTravelFlags(i); + } +} //end of the function AAS_InitAreaContentsTravelFlags +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateReversedReachability(void) +{ + int i, n; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; + char *ptr; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif + //free reversed links that have already been created + if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); + //allocate memory for the reversed reachability links + ptr = (char *) GetClearedMemory(aasworld.numareas * sizeof(aas_reversedreachability_t) + + aasworld.reachabilitysize * sizeof(aas_reversedlink_t)); + // + aasworld.reversedreachability = (aas_reversedreachability_t *) ptr; + //pointer to the memory for the reversed links + ptr += aasworld.numareas * sizeof(aas_reversedreachability_t); + //check all reachabilities of all areas + for (i = 1; i < aasworld.numareas; i++) + { + //settings of the area + settings = &aasworld.areasettings[i]; + // + if (settings->numreachableareas >= 128) + botimport.Print(PRT_WARNING, "area %d has more than 128 reachabilities\n", i); + //create reversed links for the reachabilities + for (n = 0; n < settings->numreachableareas && n < 128; n++) + { + //reachability link + reach = &aasworld.reachability[settings->firstreachablearea + n]; + // + revlink = (aas_reversedlink_t *) ptr; + ptr += sizeof(aas_reversedlink_t); + // + revlink->areanum = i; + revlink->linknum = settings->firstreachablearea + n; + revlink->next = aasworld.reversedreachability[reach->areanum].first; + aasworld.reversedreachability[reach->areanum].first = revlink; + aasworld.reversedreachability[reach->areanum].numlinks++; + } //end for + } //end for +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "reversed reachability %d msec\n", Sys_MilliSeconds() - starttime); +#endif +} //end of the function AAS_CreateReversedReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end) +{ + int intdist; + float dist; + vec3_t dir; + + VectorSubtract(start, end, dir); + dist = VectorLength(dir); + //if crouch only area + if (AAS_AreaCrouch(areanum)) dist *= DISTANCEFACTOR_CROUCH; + //if swim area + else if (AAS_AreaSwim(areanum)) dist *= DISTANCEFACTOR_SWIM; + //normal walk area + else dist *= DISTANCEFACTOR_WALK; + // + intdist = (int) dist; + //make sure the distance isn't zero + if (intdist <= 0) intdist = 1; + return intdist; +} //end of the function AAS_AreaTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CalculateAreaTravelTimes(void) +{ + int i, l, n, size; + char *ptr; + vec3_t end; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_reachability_t *reach; + aas_areasettings_t *settings; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif + //if there are still area travel times, free the memory + if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); + //get the total size of all the area travel times + size = aasworld.numareas * sizeof(unsigned short **); + for (i = 0; i < aasworld.numareas; i++) + { + revreach = &aasworld.reversedreachability[i]; + //settings of the area + settings = &aasworld.areasettings[i]; + // + size += settings->numreachableareas * sizeof(unsigned short *); + // + size += settings->numreachableareas * + PAD(revreach->numlinks, sizeof(long)) * sizeof(unsigned short); + } //end for + //allocate memory for the area travel times + ptr = (char *) GetClearedMemory(size); + aasworld.areatraveltimes = (unsigned short ***) ptr; + ptr += aasworld.numareas * sizeof(unsigned short **); + //calcluate the travel times for all the areas + for (i = 0; i < aasworld.numareas; i++) + { + //reversed reachabilities of this area + revreach = &aasworld.reversedreachability[i]; + //settings of the area + settings = &aasworld.areasettings[i]; + // + aasworld.areatraveltimes[i] = (unsigned short **) ptr; + ptr += settings->numreachableareas * sizeof(unsigned short *); + // + for (l = 0; l < settings->numreachableareas; l++) + { + aasworld.areatraveltimes[i][l] = (unsigned short *) ptr; + ptr += PAD(revreach->numlinks, sizeof(long)) * sizeof(unsigned short); + //reachability link + reach = &aasworld.reachability[settings->firstreachablearea + l]; + // + for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) + { + VectorCopy(aasworld.reachability[revlink->linknum].end, end); + // + aasworld.areatraveltimes[i][l][n] = AAS_AreaTravelTime(i, end, reach->start); + } //end for + } //end for + } //end for +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "area travel times %d msec\n", Sys_MilliSeconds() - starttime); +#endif +} //end of the function AAS_CalculateAreaTravelTimes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PortalMaxTravelTime(int portalnum) +{ + int l, n, t, maxt; + aas_portal_t *portal; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + aas_areasettings_t *settings; + + portal = &aasworld.portals[portalnum]; + //reversed reachabilities of this portal area + revreach = &aasworld.reversedreachability[portal->areanum]; + //settings of the portal area + settings = &aasworld.areasettings[portal->areanum]; + // + maxt = 0; + for (l = 0; l < settings->numreachableareas; l++) + { + for (n = 0, revlink = revreach->first; revlink; revlink = revlink->next, n++) + { + t = aasworld.areatraveltimes[portal->areanum][l][n]; + if (t > maxt) + { + maxt = t; + } //end if + } //end for + } //end for + return maxt; +} //end of the function AAS_PortalMaxTravelTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalMaxTravelTimes(void) +{ + int i; + + if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); + + aasworld.portalmaxtraveltimes = (int *) GetClearedMemory(aasworld.numportals * sizeof(int)); + + for (i = 0; i < aasworld.numportals; i++) + { + aasworld.portalmaxtraveltimes[i] = AAS_PortalMaxTravelTime(i); + //botimport.Print(PRT_MESSAGE, "portal %d max tt = %d\n", i, aasworld.portalmaxtraveltimes[i]); + } //end for +} //end of the function AAS_InitPortalMaxTravelTimes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int AAS_FreeOldestCache(void) +{ + int i, j, bestcluster, bestarea, freed; + float besttime; + aas_routingcache_t *cache, *bestcache; + + freed = qfalse; + besttime = 999999999; + bestcache = NULL; + bestcluster = 0; + bestarea = 0; + //refresh cluster cache + for (i = 0; i < aasworld.numclusters; i++) + { + for (j = 0; j < aasworld.clusters[i].numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) + { + //never remove cache leading towards a portal + if (aasworld.areasettings[cache->areanum].cluster < 0) continue; + //if this cache is older than the cache we found so far + if (cache->time < besttime) + { + bestcache = cache; + bestcluster = i; + bestarea = j; + besttime = cache->time; + } //end if + } //end for + } //end for + } //end for + if (bestcache) + { + cache = bestcache; + if (cache->prev) cache->prev->next = cache->next; + else aasworld.clusterareacache[bestcluster][bestarea] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + AAS_FreeRoutingCache(cache); + freed = qtrue; + } //end if + besttime = 999999999; + bestcache = NULL; + bestarea = 0; + for (i = 0; i < aasworld.numareas; i++) + { + //refresh portal cache + for (cache = aasworld.portalcache[i]; cache; cache = cache->next) + { + if (cache->time < besttime) + { + bestcache = cache; + bestarea = i; + besttime = cache->time; + } //end if + } //end for + } //end for + if (bestcache) + { + cache = bestcache; + if (cache->prev) cache->prev->next = cache->next; + else aasworld.portalcache[bestarea] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + AAS_FreeRoutingCache(cache); + freed = qtrue; + } //end if + return freed; +} //end of the function AAS_FreeOldestCache +*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_FreeOldestCache(void) +{ + int clusterareanum; + aas_routingcache_t *cache; + + for (cache = aasworld.oldestcache; cache; cache = cache->time_next) { + // never free area cache leading towards a portal + if (cache->type == CACHETYPE_AREA && aasworld.areasettings[cache->areanum].cluster < 0) { + continue; + } + break; + } + if (cache) { + // unlink the cache + if (cache->type == CACHETYPE_AREA) { + //number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); + // unlink from cluster area cache + if (cache->prev) cache->prev->next = cache->next; + else aasworld.clusterareacache[cache->cluster][clusterareanum] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + } + else { + // unlink from portal cache + if (cache->prev) cache->prev->next = cache->next; + else aasworld.portalcache[cache->areanum] = cache->next; + if (cache->next) cache->next->prev = cache->prev; + } + AAS_FreeRoutingCache(cache); + return qtrue; + } + return qfalse; +} //end of the function AAS_FreeOldestCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_AllocRoutingCache(int numtraveltimes) +{ + aas_routingcache_t *cache; + int size; + + // + size = sizeof(aas_routingcache_t) + + numtraveltimes * sizeof(unsigned short int) + + numtraveltimes * sizeof(unsigned char); + // + routingcachesize += size; + // + cache = (aas_routingcache_t *) GetClearedMemory(size); + cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) + + numtraveltimes * sizeof(unsigned short int); + cache->size = size; + return cache; +} //end of the function AAS_AllocRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllClusterAreaCache(void) +{ + int i, j; + aas_routingcache_t *cache, *nextcache; + aas_cluster_t *cluster; + + //free all cluster cache if existing + if (!aasworld.clusterareacache) return; + //free caches + for (i = 0; i < aasworld.numclusters; i++) + { + cluster = &aasworld.clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.clusterareacache[i][j] = NULL; + } //end for + } //end for + //free the cluster cache array + FreeMemory(aasworld.clusterareacache); + aasworld.clusterareacache = NULL; +} //end of the function AAS_FreeAllClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitClusterAreaCache(void) +{ + int i, size; + char *ptr; + + // + for (size = 0, i = 0; i < aasworld.numclusters; i++) + { + size += aasworld.clusters[i].numareas; + } //end for + //two dimensional array with pointers for every cluster to routing cache + //for every area in that cluster + ptr = (char *) GetClearedMemory( + aasworld.numclusters * sizeof(aas_routingcache_t **) + + size * sizeof(aas_routingcache_t *)); + aasworld.clusterareacache = (aas_routingcache_t ***) ptr; + ptr += aasworld.numclusters * sizeof(aas_routingcache_t **); + for (i = 0; i < aasworld.numclusters; i++) + { + aasworld.clusterareacache[i] = (aas_routingcache_t **) ptr; + ptr += aasworld.clusters[i].numareas * sizeof(aas_routingcache_t *); + } //end for +} //end of the function AAS_InitClusterAreaCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAllPortalCache(void) +{ + int i; + aas_routingcache_t *cache, *nextcache; + + //free all portal cache if existing + if (!aasworld.portalcache) return; + //free portal caches + for (i = 0; i < aasworld.numareas; i++) + { + for (cache = aasworld.portalcache[i]; cache; cache = nextcache) + { + nextcache = cache->next; + AAS_FreeRoutingCache(cache); + } //end for + aasworld.portalcache[i] = NULL; + } //end for + FreeMemory(aasworld.portalcache); + aasworld.portalcache = NULL; +} //end of the function AAS_FreeAllPortalCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitPortalCache(void) +{ + // + aasworld.portalcache = (aas_routingcache_t **) GetClearedMemory( + aasworld.numareas * sizeof(aas_routingcache_t *)); +} //end of the function AAS_InitPortalCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitRoutingUpdate(void) +{ + int i, maxreachabilityareas; + + //free routing update fields if already existing + if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); + // + maxreachabilityareas = 0; + for (i = 0; i < aasworld.numclusters; i++) + { + if (aasworld.clusters[i].numreachabilityareas > maxreachabilityareas) + { + maxreachabilityareas = aasworld.clusters[i].numreachabilityareas; + } //end if + } //end for + //allocate memory for the routing update fields + aasworld.areaupdate = (aas_routingupdate_t *) GetClearedMemory( + maxreachabilityareas * sizeof(aas_routingupdate_t)); + // + if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); + //allocate memory for the portal update fields + aasworld.portalupdate = (aas_routingupdate_t *) GetClearedMemory( + (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); +} //end of the function AAS_InitRoutingUpdate +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_CreateAllRoutingCache(void) +{ + int i, j; + //int t; + + aasworld.initialized = qtrue; + botimport.Print(PRT_MESSAGE, "AAS_CreateAllRoutingCache\n"); + for (i = 1; i < aasworld.numareas; i++) + { + if (!AAS_AreaReachability(i)) continue; + for (j = 1; j < aasworld.numareas; j++) + { + if (i == j) continue; + if (!AAS_AreaReachability(j)) continue; + AAS_AreaTravelTimeToGoalArea(i, aasworld.areas[i].center, j, TFL_DEFAULT); + //t = AAS_AreaTravelTimeToGoalArea(i, aasworld.areas[i].center, j, TFL_DEFAULT); + //Log_Write("traveltime from %d to %d is %d", i, j, t); + } //end for + } //end for + aasworld.initialized = qfalse; +} //end of the function AAS_CreateAllRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +//the route cache header +//this header is followed by numportalcache + numareacache aas_routingcache_t +//structures that store routing cache +typedef struct routecacheheader_s +{ + int ident; + int version; + int numareas; + int numclusters; + int areacrc; + int clustercrc; + int numportalcache; + int numareacache; +} routecacheheader_t; + +#define RCID (('C'<<24)+('R'<<16)+('E'<<8)+'M') +#define RCVERSION 2 + +//void AAS_DecompressVis(byte *in, int numareas, byte *decompressed); +//int AAS_CompressVis(byte *vis, int numareas, byte *dest); + +void AAS_WriteRouteCache(void) +{ + int i, j, numportalcache, numareacache, totalsize; + aas_routingcache_t *cache; + aas_cluster_t *cluster; + fileHandle_t fp; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + + numportalcache = 0; + for (i = 0; i < aasworld.numareas; i++) + { + for (cache = aasworld.portalcache[i]; cache; cache = cache->next) + { + numportalcache++; + } //end for + } //end for + numareacache = 0; + for (i = 0; i < aasworld.numclusters; i++) + { + cluster = &aasworld.clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) + { + numareacache++; + } //end for + } //end for + } //end for + // open the file for writing + Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); + botimport.FS_FOpenFile( filename, &fp, FS_WRITE ); + if (!fp) + { + AAS_Error("Unable to open file: %s\n", filename); + return; + } //end if + //create the header + routecacheheader.ident = RCID; + routecacheheader.version = RCVERSION; + routecacheheader.numareas = aasworld.numareas; + routecacheheader.numclusters = aasworld.numclusters; + routecacheheader.areacrc = CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas ); + routecacheheader.clustercrc = CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters ); + routecacheheader.numportalcache = numportalcache; + routecacheheader.numareacache = numareacache; + //write the header + botimport.FS_Write(&routecacheheader, sizeof(routecacheheader_t), fp); + // + totalsize = 0; + //write all the cache + for (i = 0; i < aasworld.numareas; i++) + { + for (cache = aasworld.portalcache[i]; cache; cache = cache->next) + { + botimport.FS_Write(cache, cache->size, fp); + totalsize += cache->size; + } //end for + } //end for + for (i = 0; i < aasworld.numclusters; i++) + { + cluster = &aasworld.clusters[i]; + for (j = 0; j < cluster->numareas; j++) + { + for (cache = aasworld.clusterareacache[i][j]; cache; cache = cache->next) + { + botimport.FS_Write(cache, cache->size, fp); + totalsize += cache->size; + } //end for + } //end for + } //end for + // write the visareas + /* + for (i = 0; i < aasworld.numareas; i++) + { + if (!aasworld.areavisibility[i]) { + size = 0; + botimport.FS_Write(&size, sizeof(int), fp); + continue; + } + AAS_DecompressVis( aasworld.areavisibility[i], aasworld.numareas, aasworld.decompressedvis ); + size = AAS_CompressVis( aasworld.decompressedvis, aasworld.numareas, aasworld.decompressedvis ); + botimport.FS_Write(&size, sizeof(int), fp); + botimport.FS_Write(aasworld.decompressedvis, size, fp); + } + */ + // + botimport.FS_FCloseFile(fp); + botimport.Print(PRT_MESSAGE, "\nroute cache written to %s\n", filename); + botimport.Print(PRT_MESSAGE, "written %d bytes of routing cache\n", totalsize); +} //end of the function AAS_WriteRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_ReadCache(fileHandle_t fp) +{ + int size; + aas_routingcache_t *cache; + + botimport.FS_Read(&size, sizeof(size), fp); + cache = (aas_routingcache_t *) GetMemory(size); + cache->size = size; + botimport.FS_Read((unsigned char *)cache + sizeof(size), size - sizeof(size), fp); + cache->reachabilities = (unsigned char *) cache + sizeof(aas_routingcache_t) - sizeof(unsigned short) + + (size - sizeof(aas_routingcache_t) + sizeof(unsigned short)) / 3 * 2; + return cache; +} //end of the function AAS_ReadCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_ReadRouteCache(void) +{ + int i, clusterareanum;//, size; + fileHandle_t fp; + char filename[MAX_QPATH]; + routecacheheader_t routecacheheader; + aas_routingcache_t *cache; + + Com_sprintf(filename, MAX_QPATH, "maps/%s.rcd", aasworld.mapname); + botimport.FS_FOpenFile( filename, &fp, FS_READ ); + if (!fp) + { + return qfalse; + } //end if + botimport.FS_Read(&routecacheheader, sizeof(routecacheheader_t), fp ); + if (routecacheheader.ident != RCID) + { + AAS_Error("%s is not a route cache dump\n", filename); + return qfalse; + } //end if + if (routecacheheader.version != RCVERSION) + { + AAS_Error("route cache dump has wrong version %d, should be %d", routecacheheader.version, RCVERSION); + return qfalse; + } //end if + if (routecacheheader.numareas != aasworld.numareas) + { + //AAS_Error("route cache dump has wrong number of areas\n"); + return qfalse; + } //end if + if (routecacheheader.numclusters != aasworld.numclusters) + { + //AAS_Error("route cache dump has wrong number of clusters\n"); + return qfalse; + } //end if + if (routecacheheader.areacrc != + CRC_ProcessString( (unsigned char *)aasworld.areas, sizeof(aas_area_t) * aasworld.numareas )) + { + //AAS_Error("route cache dump area CRC incorrect\n"); + return qfalse; + } //end if + if (routecacheheader.clustercrc != + CRC_ProcessString( (unsigned char *)aasworld.clusters, sizeof(aas_cluster_t) * aasworld.numclusters )) + { + //AAS_Error("route cache dump cluster CRC incorrect\n"); + return qfalse; + } //end if + //read all the portal cache + for (i = 0; i < routecacheheader.numportalcache; i++) + { + cache = AAS_ReadCache(fp); + cache->next = aasworld.portalcache[cache->areanum]; + cache->prev = NULL; + if (aasworld.portalcache[cache->areanum]) + aasworld.portalcache[cache->areanum]->prev = cache; + aasworld.portalcache[cache->areanum] = cache; + } //end for + //read all the cluster area cache + for (i = 0; i < routecacheheader.numareacache; i++) + { + cache = AAS_ReadCache(fp); + clusterareanum = AAS_ClusterAreaNum(cache->cluster, cache->areanum); + cache->next = aasworld.clusterareacache[cache->cluster][clusterareanum]; + cache->prev = NULL; + if (aasworld.clusterareacache[cache->cluster][clusterareanum]) + aasworld.clusterareacache[cache->cluster][clusterareanum]->prev = cache; + aasworld.clusterareacache[cache->cluster][clusterareanum] = cache; + } //end for + // read the visareas + /* + aasworld.areavisibility = (byte **) GetClearedMemory(aasworld.numareas * sizeof(byte *)); + aasworld.decompressedvis = (byte *) GetClearedMemory(aasworld.numareas * sizeof(byte)); + for (i = 0; i < aasworld.numareas; i++) + { + botimport.FS_Read(&size, sizeof(size), fp ); + if (size) { + aasworld.areavisibility[i] = (byte *) GetMemory(size); + botimport.FS_Read(aasworld.areavisibility[i], size, fp ); + } + } + */ + // + botimport.FS_FCloseFile(fp); + return qtrue; +} //end of the function AAS_ReadRouteCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#define MAX_REACHABILITYPASSAREAS 32 + +void AAS_InitReachabilityAreas(void) +{ + int i, j, numareas, areas[MAX_REACHABILITYPASSAREAS]; + int numreachareas; + aas_reachability_t *reach; + vec3_t start, end; + + if (aasworld.reachabilityareas) + FreeMemory(aasworld.reachabilityareas); + if (aasworld.reachabilityareaindex) + FreeMemory(aasworld.reachabilityareaindex); + + aasworld.reachabilityareas = (aas_reachabilityareas_t *) + GetClearedMemory(aasworld.reachabilitysize * sizeof(aas_reachabilityareas_t)); + aasworld.reachabilityareaindex = (int *) + GetClearedMemory(aasworld.reachabilitysize * MAX_REACHABILITYPASSAREAS * sizeof(int)); + numreachareas = 0; + for (i = 0; i < aasworld.reachabilitysize; i++) + { + reach = &aasworld.reachability[i]; + numareas = 0; + switch(reach->traveltype & TRAVELTYPE_MASK) + { + //trace areas from start to end + case TRAVEL_BARRIERJUMP: + case TRAVEL_WATERJUMP: + VectorCopy(reach->start, end); + end[2] = reach->end[2]; + numareas = AAS_TraceAreas(reach->start, end, areas, NULL, MAX_REACHABILITYPASSAREAS); + break; + case TRAVEL_WALKOFFLEDGE: + VectorCopy(reach->end, start); + start[2] = reach->start[2]; + numareas = AAS_TraceAreas(start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); + break; + case TRAVEL_GRAPPLEHOOK: + numareas = AAS_TraceAreas(reach->start, reach->end, areas, NULL, MAX_REACHABILITYPASSAREAS); + break; + + //trace arch + case TRAVEL_JUMP: break; + case TRAVEL_ROCKETJUMP: break; + case TRAVEL_BFGJUMP: break; + case TRAVEL_JUMPPAD: break; + + //trace from reach->start to entity center, along entity movement + //and from entity center to reach->end + case TRAVEL_ELEVATOR: break; + case TRAVEL_FUNCBOB: break; + + //no areas in between + case TRAVEL_WALK: break; + case TRAVEL_CROUCH: break; + case TRAVEL_LADDER: break; + case TRAVEL_SWIM: break; + case TRAVEL_TELEPORT: break; + default: break; + } //end switch + aasworld.reachabilityareas[i].firstarea = numreachareas; + aasworld.reachabilityareas[i].numareas = numareas; + for (j = 0; j < numareas; j++) + { + aasworld.reachabilityareaindex[numreachareas++] = areas[j]; + } //end for + } //end for +} //end of the function AAS_InitReachabilityAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitRouting(void) +{ + AAS_InitTravelFlagFromType(); + // + AAS_InitAreaContentsTravelFlags(); + //initialize the routing update fields + AAS_InitRoutingUpdate(); + //create reversed reachability links used by the routing update algorithm + AAS_CreateReversedReachability(); + //initialize the cluster cache + AAS_InitClusterAreaCache(); + //initialize portal cache + AAS_InitPortalCache(); + //initialize the area travel times + AAS_CalculateAreaTravelTimes(); + //calculate the maximum travel times through portals + AAS_InitPortalMaxTravelTimes(); + //get the areas reachabilities go through + AAS_InitReachabilityAreas(); + // +#ifdef ROUTING_DEBUG + numareacacheupdates = 0; + numportalcacheupdates = 0; +#endif //ROUTING_DEBUG + // + routingcachesize = 0; + max_routingcachesize = 1024 * (int) LibVarValue("max_routingcache", "4096"); + // read any routing cache if available + AAS_ReadRouteCache(); +} //end of the function AAS_InitRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeRoutingCaches(void) +{ + // free all the existing cluster area cache + AAS_FreeAllClusterAreaCache(); + // free all the existing portal cache + AAS_FreeAllPortalCache(); + // free cached travel times within areas + if (aasworld.areatraveltimes) FreeMemory(aasworld.areatraveltimes); + aasworld.areatraveltimes = NULL; + // free cached maximum travel time through cluster portals + if (aasworld.portalmaxtraveltimes) FreeMemory(aasworld.portalmaxtraveltimes); + aasworld.portalmaxtraveltimes = NULL; + // free reversed reachability links + if (aasworld.reversedreachability) FreeMemory(aasworld.reversedreachability); + aasworld.reversedreachability = NULL; + // free routing algorithm memory + if (aasworld.areaupdate) FreeMemory(aasworld.areaupdate); + aasworld.areaupdate = NULL; + if (aasworld.portalupdate) FreeMemory(aasworld.portalupdate); + aasworld.portalupdate = NULL; + // free lists with areas the reachabilities go through + if (aasworld.reachabilityareas) FreeMemory(aasworld.reachabilityareas); + aasworld.reachabilityareas = NULL; + // free the reachability area index + if (aasworld.reachabilityareaindex) FreeMemory(aasworld.reachabilityareaindex); + aasworld.reachabilityareaindex = NULL; + // free area contents travel flags look up table + if (aasworld.areacontentstravelflags) FreeMemory(aasworld.areacontentstravelflags); + aasworld.areacontentstravelflags = NULL; +} //end of the function AAS_FreeRoutingCaches +//=========================================================================== +// update the given routing cache +// +// Parameter: areacache : routing cache to update +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdateAreaRoutingCache(aas_routingcache_t *areacache) +{ + int i, nextareanum, cluster, badtravelflags, clusterareanum, linknum; + int numreachabilityareas; + unsigned short int t, startareatraveltimes[128]; //NOTE: not more than 128 reachabilities per area allowed + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + aas_reversedreachability_t *revreach; + aas_reversedlink_t *revlink; + +#ifdef ROUTING_DEBUG + numareacacheupdates++; +#endif //ROUTING_DEBUG + //number of reachability areas within this cluster + numreachabilityareas = aasworld.clusters[areacache->cluster].numreachabilityareas; + // + aasworld.frameroutingupdates++; + //clear the routing update fields +// Com_Memset(aasworld.areaupdate, 0, aasworld.numareas * sizeof(aas_routingupdate_t)); + // + badtravelflags = ~areacache->travelflags; + // + clusterareanum = AAS_ClusterAreaNum(areacache->cluster, areacache->areanum); + if (clusterareanum >= numreachabilityareas) return; + // + Com_Memset(startareatraveltimes, 0, sizeof(startareatraveltimes)); + // + curupdate = &aasworld.areaupdate[clusterareanum]; + curupdate->areanum = areacache->areanum; + //VectorCopy(areacache->origin, curupdate->start); + curupdate->areatraveltimes = startareatraveltimes; + curupdate->tmptraveltime = areacache->starttraveltime; + // + areacache->traveltimes[clusterareanum] = areacache->starttraveltime; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) curupdate->next->prev = NULL; + else updatelistend = NULL; + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + revreach = &aasworld.reversedreachability[curupdate->areanum]; + // + for (i = 0, revlink = revreach->first; revlink; revlink = revlink->next, i++) + { + linknum = revlink->linknum; + reach = &aasworld.reachability[linknum]; + //if there is used an undesired travel type + if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; + //if not allowed to enter the next area + if (aasworld.areasettings[reach->areanum].areaflags & AREA_DISABLED) continue; + //if the next area has a not allowed travel flag + if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; + //number of the area the reversed reachability leads to + nextareanum = revlink->areanum; + //get the cluster number of the area + cluster = aasworld.areasettings[nextareanum].cluster; + //don't leave the cluster + if (cluster > 0 && cluster != areacache->cluster) continue; + //get the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(areacache->cluster, nextareanum); + if (clusterareanum >= numreachabilityareas) continue; + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + //AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->end) + + curupdate->areatraveltimes[i] + + reach->traveltime; + // + if (!areacache->traveltimes[clusterareanum] || + areacache->traveltimes[clusterareanum] > t) + { + areacache->traveltimes[clusterareanum] = t; + areacache->reachabilities[clusterareanum] = linknum - aasworld.areasettings[nextareanum].firstreachablearea; + nextupdate = &aasworld.areaupdate[clusterareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //VectorCopy(reach->start, nextupdate->start); + nextupdate->areatraveltimes = aasworld.areatraveltimes[nextareanum][linknum - + aasworld.areasettings[nextareanum].firstreachablearea]; + if (!nextupdate->inlist) + { + // we add the update to the end of the list + // we could also use a B+ tree to have a real sorted list + // on travel time which makes for faster routing updates + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) updatelistend->next = nextupdate; + else updateliststart = nextupdate; + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdateAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetAreaRoutingCache(int clusternum, int areanum, int travelflags) +{ + int clusterareanum; + aas_routingcache_t *cache, *clustercache; + + //number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //pointer to the cache for the area in the cluster + clustercache = aasworld.clusterareacache[clusternum][clusterareanum]; + //find the cache without undesired travel flags + for (cache = clustercache; cache; cache = cache->next) + { + //if there aren't used any undesired travel types for the cache + if (cache->travelflags == travelflags) break; + } //end for + //if there was no cache + if (!cache) + { + cache = AAS_AllocRoutingCache(aasworld.clusters[clusternum].numreachabilityareas); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy(aasworld.areas[areanum].center, cache->origin); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + cache->prev = NULL; + cache->next = clustercache; + if (clustercache) clustercache->prev = cache; + aasworld.clusterareacache[clusternum][clusterareanum] = cache; + AAS_UpdateAreaRoutingCache(cache); + } //end if + else + { + AAS_UnlinkCache(cache); + } //end else + //the cache has been accessed + cache->time = AAS_RoutingTime(); + cache->type = CACHETYPE_AREA; + AAS_LinkCache(cache); + return cache; +} //end of the function AAS_GetAreaRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UpdatePortalRoutingCache(aas_routingcache_t *portalcache) +{ + int i, portalnum, clusterareanum, clusternum; + unsigned short int t; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *cache; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + +#ifdef ROUTING_DEBUG + numportalcacheupdates++; +#endif //ROUTING_DEBUG + //clear the routing update fields +// Com_Memset(aasworld.portalupdate, 0, (aasworld.numportals+1) * sizeof(aas_routingupdate_t)); + // + curupdate = &aasworld.portalupdate[aasworld.numportals]; + curupdate->cluster = portalcache->cluster; + curupdate->areanum = portalcache->areanum; + curupdate->tmptraveltime = portalcache->starttraveltime; + //if the start area is a cluster portal, store the travel time for that portal + clusternum = aasworld.areasettings[portalcache->areanum].cluster; + if (clusternum < 0) + { + portalcache->traveltimes[-clusternum] = portalcache->starttraveltime; + } //end if + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the current list + while (updateliststart) + { + curupdate = updateliststart; + //remove the current update from the list + if (curupdate->next) curupdate->next->prev = NULL; + else updatelistend = NULL; + updateliststart = curupdate->next; + //current update is removed from the list + curupdate->inlist = qfalse; + // + cluster = &aasworld.clusters[curupdate->cluster]; + // + cache = AAS_GetAreaRoutingCache(curupdate->cluster, + curupdate->areanum, portalcache->travelflags); + //take all portals of the cluster + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + portal = &aasworld.portals[portalnum]; + //if this is the portal of the current update continue + if (portal->areanum == curupdate->areanum) continue; + // + clusterareanum = AAS_ClusterAreaNum(curupdate->cluster, portal->areanum); + if (clusterareanum >= cluster->numreachabilityareas) continue; + // + t = cache->traveltimes[clusterareanum]; + if (!t) continue; + t += curupdate->tmptraveltime; + // + if (!portalcache->traveltimes[portalnum] || + portalcache->traveltimes[portalnum] > t) + { + portalcache->traveltimes[portalnum] = t; + nextupdate = &aasworld.portalupdate[portalnum]; + if (portal->frontcluster == curupdate->cluster) + { + nextupdate->cluster = portal->backcluster; + } //end if + else + { + nextupdate->cluster = portal->frontcluster; + } //end else + nextupdate->areanum = portal->areanum; + //add travel time through the actual portal area for the next update + nextupdate->tmptraveltime = t + aasworld.portalmaxtraveltimes[portalnum]; + if (!nextupdate->inlist) + { + // we add the update to the end of the list + // we could also use a B+ tree to have a real sorted list + // on travel time which makes for faster routing updates + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) updatelistend->next = nextupdate; + else updateliststart = nextupdate; + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while +} //end of the function AAS_UpdatePortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_routingcache_t *AAS_GetPortalRoutingCache(int clusternum, int areanum, int travelflags) +{ + aas_routingcache_t *cache; + + //find the cached portal routing if existing + for (cache = aasworld.portalcache[areanum]; cache; cache = cache->next) + { + if (cache->travelflags == travelflags) break; + } //end for + //if the portal routing isn't cached + if (!cache) + { + cache = AAS_AllocRoutingCache(aasworld.numportals); + cache->cluster = clusternum; + cache->areanum = areanum; + VectorCopy(aasworld.areas[areanum].center, cache->origin); + cache->starttraveltime = 1; + cache->travelflags = travelflags; + //add the cache to the cache list + cache->prev = NULL; + cache->next = aasworld.portalcache[areanum]; + if (aasworld.portalcache[areanum]) aasworld.portalcache[areanum]->prev = cache; + aasworld.portalcache[areanum] = cache; + //update the cache + AAS_UpdatePortalRoutingCache(cache); + } //end if + else + { + AAS_UnlinkCache(cache); + } //end else + //the cache has been accessed + cache->time = AAS_RoutingTime(); + cache->type = CACHETYPE_PORTAL; + AAS_LinkCache(cache); + return cache; +} //end of the function AAS_GetPortalRoutingCache +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaRouteToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags, int *traveltime, int *reachnum) +{ + int clusternum, goalclusternum, portalnum, i, clusterareanum, bestreachnum; + unsigned short int t, besttime; + aas_portal_t *portal; + aas_cluster_t *cluster; + aas_routingcache_t *areacache, *portalcache; + aas_reachability_t *reach; + + if (!aasworld.initialized) return qfalse; + + if (areanum == goalareanum) + { + *traveltime = 1; + *reachnum = 0; + return qtrue; + } + // + if (areanum <= 0 || areanum >= aasworld.numareas) + { + if (botDeveloper) + { + botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: areanum %d out of range\n", areanum); + } //end if + return qfalse; + } //end if + if (goalareanum <= 0 || goalareanum >= aasworld.numareas) + { + if (botDeveloper) + { + botimport.Print(PRT_ERROR, "AAS_AreaTravelTimeToGoalArea: goalareanum %d out of range\n", goalareanum); + } //end if + return qfalse; + } //end if + // make sure the routing cache doesn't grow to large + while(AvailableMemory() < 1 * 1024 * 1024) { + if (!AAS_FreeOldestCache()) break; + } + // + if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goalareanum)) + { + travelflags |= TFL_DONOTENTER; + } //end if + //NOTE: the number of routing updates is limited per frame + /* + if (aasworld.frameroutingupdates > MAX_FRAMEROUTINGUPDATES) + { +#ifdef DEBUG + //Log_Write("WARNING: AAS_AreaTravelTimeToGoalArea: frame routing updates overflowed"); +#endif + return 0; + } //end if + */ + // + clusternum = aasworld.areasettings[areanum].cluster; + goalclusternum = aasworld.areasettings[goalareanum].cluster; + //check if the area is a portal of the goal area cluster + if (clusternum < 0 && goalclusternum > 0) + { + portal = &aasworld.portals[-clusternum]; + if (portal->frontcluster == goalclusternum || + portal->backcluster == goalclusternum) + { + clusternum = goalclusternum; + } //end if + } //end if + //check if the goalarea is a portal of the area cluster + else if (clusternum > 0 && goalclusternum < 0) + { + portal = &aasworld.portals[-goalclusternum]; + if (portal->frontcluster == clusternum || + portal->backcluster == clusternum) + { + goalclusternum = clusternum; + } //end if + } //end if + //if both areas are in the same cluster + //NOTE: there might be a shorter route via another cluster!!! but we don't care + if (clusternum > 0 && goalclusternum > 0 && clusternum == goalclusternum) + { + // + areacache = AAS_GetAreaRoutingCache(clusternum, goalareanum, travelflags); + //the number of the area in the cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //the cluster the area is in + cluster = &aasworld.clusters[clusternum]; + //if the area is NOT a reachability area + if (clusterareanum >= cluster->numreachabilityareas) return 0; + //if it is possible to travel to the goal area through this cluster + if (areacache->traveltimes[clusterareanum] != 0) + { + *reachnum = aasworld.areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + if (!origin) { + *traveltime = areacache->traveltimes[clusterareanum]; + return qtrue; + } + reach = &aasworld.reachability[*reachnum]; + *traveltime = areacache->traveltimes[clusterareanum] + + AAS_AreaTravelTime(areanum, origin, reach->start); + // + return qtrue; + } //end if + } //end if + // + clusternum = aasworld.areasettings[areanum].cluster; + goalclusternum = aasworld.areasettings[goalareanum].cluster; + //if the goal area is a portal + if (goalclusternum < 0) + { + //just assume the goal area is part of the front cluster + portal = &aasworld.portals[-goalclusternum]; + goalclusternum = portal->frontcluster; + } //end if + //get the portal routing cache + portalcache = AAS_GetPortalRoutingCache(goalclusternum, goalareanum, travelflags); + //if the area is a cluster portal, read directly from the portal cache + if (clusternum < 0) + { + *traveltime = portalcache->traveltimes[-clusternum]; + *reachnum = aasworld.areasettings[areanum].firstreachablearea + + portalcache->reachabilities[-clusternum]; + return qtrue; + } //end if + // + besttime = 0; + bestreachnum = -1; + //the cluster the area is in + cluster = &aasworld.clusters[clusternum]; + //find the portal of the area cluster leading towards the goal area + for (i = 0; i < cluster->numportals; i++) + { + portalnum = aasworld.portalindex[cluster->firstportal + i]; + //if the goal area isn't reachable from the portal + if (!portalcache->traveltimes[portalnum]) continue; + // + portal = &aasworld.portals[portalnum]; + //get the cache of the portal area + areacache = AAS_GetAreaRoutingCache(clusternum, portal->areanum, travelflags); + //current area inside the current cluster + clusterareanum = AAS_ClusterAreaNum(clusternum, areanum); + //if the area is NOT a reachability area + if (clusterareanum >= cluster->numreachabilityareas) continue; + //if the portal is NOT reachable from this area + if (!areacache->traveltimes[clusterareanum]) continue; + //total travel time is the travel time the portal area is from + //the goal area plus the travel time towards the portal area + t = portalcache->traveltimes[portalnum] + areacache->traveltimes[clusterareanum]; + //FIXME: add the exact travel time through the actual portal area + //NOTE: for now we just add the largest travel time through the portal area + // because we can't directly calculate the exact travel time + // to be more specific we don't know which reachability was used to travel + // into the portal area + t += aasworld.portalmaxtraveltimes[portalnum]; + // + if (origin) + { + *reachnum = aasworld.areasettings[areanum].firstreachablearea + + areacache->reachabilities[clusterareanum]; + reach = aasworld.reachability + *reachnum; + t += AAS_AreaTravelTime(areanum, origin, reach->start); + } //end if + //if the time is better than the one already found + if (!besttime || t < besttime) + { + bestreachnum = *reachnum; + besttime = t; + } //end if + } //end for + if (bestreachnum < 0) { + return qfalse; + } + *reachnum = bestreachnum; + *traveltime = besttime; + return qtrue; +} //end of the function AAS_AreaRouteToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) +{ + int traveltime, reachnum; + + if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) + { + return traveltime; + } + return 0; +} //end of the function AAS_AreaTravelTimeToGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaReachabilityToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags) +{ + int traveltime, reachnum; + + if (AAS_AreaRouteToGoalArea(areanum, origin, goalareanum, travelflags, &traveltime, &reachnum)) + { + return reachnum; + } + return 0; +} //end of the function AAS_AreaReachabilityToGoalArea +//=========================================================================== +// predict the route and stop on one of the stop events +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +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) +{ + int curareanum, reachnum, i, j, testareanum; + vec3_t curorigin; + aas_reachability_t *reach; + aas_reachabilityareas_t *reachareas; + + //init output + route->stopevent = RSE_NONE; + route->endarea = goalareanum; + route->endcontents = 0; + route->endtravelflags = 0; + VectorCopy(origin, route->endpos); + route->time = 0; + + curareanum = areanum; + VectorCopy(origin, curorigin); + + for (i = 0; curareanum != goalareanum && (!maxareas || i < maxareas) && i < aasworld.numareas; i++) + { + reachnum = AAS_AreaReachabilityToGoalArea(curareanum, curorigin, goalareanum, travelflags); + if (!reachnum) + { + route->stopevent = RSE_NOROUTE; + return qfalse; + } //end if + reach = &aasworld.reachability[reachnum]; + // + if (stopevent & RSE_USETRAVELTYPE) + { + if (AAS_TravelFlagForType_inline(reach->traveltype) & stoptfl) + { + route->stopevent = RSE_USETRAVELTYPE; + route->endarea = curareanum; + route->endcontents = aasworld.areasettings[curareanum].contents; + route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); + VectorCopy(reach->start, route->endpos); + return qtrue; + } //end if + if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & stoptfl) + { + route->stopevent = RSE_USETRAVELTYPE; + route->endarea = reach->areanum; + route->endcontents = aasworld.areasettings[reach->areanum].contents; + route->endtravelflags = AAS_AreaContentsTravelFlags_inline(reach->areanum); + VectorCopy(reach->end, route->endpos); + route->time += AAS_AreaTravelTime(areanum, origin, reach->start); + route->time += reach->traveltime; + return qtrue; + } //end if + } //end if + reachareas = &aasworld.reachabilityareas[reachnum]; + for (j = 0; j < reachareas->numareas + 1; j++) + { + if (j >= reachareas->numareas) + testareanum = reach->areanum; + else + testareanum = aasworld.reachabilityareaindex[reachareas->firstarea + j]; + if (stopevent & RSE_ENTERCONTENTS) + { + if (aasworld.areasettings[testareanum].contents & stopcontents) + { + route->stopevent = RSE_ENTERCONTENTS; + route->endarea = testareanum; + route->endcontents = aasworld.areasettings[testareanum].contents; + VectorCopy(reach->end, route->endpos); + route->time += AAS_AreaTravelTime(areanum, origin, reach->start); + route->time += reach->traveltime; + return qtrue; + } //end if + } //end if + if (stopevent & RSE_ENTERAREA) + { + if (testareanum == stopareanum) + { + route->stopevent = RSE_ENTERAREA; + route->endarea = testareanum; + route->endcontents = aasworld.areasettings[testareanum].contents; + VectorCopy(reach->start, route->endpos); + return qtrue; + } //end if + } //end if + } //end for + + route->time += AAS_AreaTravelTime(areanum, origin, reach->start); + route->time += reach->traveltime; + route->endarea = reach->areanum; + route->endcontents = aasworld.areasettings[reach->areanum].contents; + route->endtravelflags = AAS_TravelFlagForType_inline(reach->traveltype); + VectorCopy(reach->end, route->endpos); + // + curareanum = reach->areanum; + VectorCopy(reach->end, curorigin); + // + if (maxtime && route->time > maxtime) + break; + } //end while + if (curareanum != goalareanum) + return qfalse; + return qtrue; +} //end of the function AAS_PredictRoute +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BridgeWalkable(int areanum) +{ + return qfalse; +} //end of the function AAS_BridgeWalkable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach) +{ + if (!aasworld.initialized) + { + Com_Memset(reach, 0, sizeof(aas_reachability_t)); + return; + } //end if + if (num < 0 || num >= aasworld.reachabilitysize) + { + Com_Memset(reach, 0, sizeof(aas_reachability_t)); + return; + } //end if + Com_Memcpy(reach, &aasworld.reachability[num], sizeof(aas_reachability_t));; +} //end of the function AAS_ReachabilityFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextAreaReachability(int areanum, int reachnum) +{ + aas_areasettings_t *settings; + + if (!aasworld.initialized) return 0; + + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_NextAreaReachability: areanum %d out of range\n", areanum); + return 0; + } //end if + + settings = &aasworld.areasettings[areanum]; + if (!reachnum) + { + return settings->firstreachablearea; + } //end if + if (reachnum < settings->firstreachablearea) + { + botimport.Print(PRT_FATAL, "AAS_NextAreaReachability: reachnum < settings->firstreachableara"); + return 0; + } //end if + reachnum++; + if (reachnum >= settings->firstreachablearea + settings->numreachableareas) + { + return 0; + } //end if + return reachnum; +} //end of the function AAS_NextAreaReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NextModelReachability(int num, int modelnum) +{ + int i; + + if (num <= 0) num = 1; + else if (num >= aasworld.reachabilitysize) return 0; + else num++; + // + for (i = num; i < aasworld.reachabilitysize; i++) + { + if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) + { + if (aasworld.reachability[i].facenum == modelnum) return i; + } //end if + else if ((aasworld.reachability[i].traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) + { + if ((aasworld.reachability[i].facenum & 0x0000FFFF) == modelnum) return i; + } //end if + } //end for + return 0; +} //end of the function AAS_NextModelReachability +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin) +{ + int i, n, t; + vec3_t start, end; + aas_trace_t trace; + + //if the area has no reachabilities + if (!AAS_AreaReachability(areanum)) return qfalse; + // + n = aasworld.numareas * random(); + for (i = 0; i < aasworld.numareas; i++) + { + if (n <= 0) n = 1; + if (n >= aasworld.numareas) n = 1; + if (AAS_AreaReachability(n)) + { + t = AAS_AreaTravelTimeToGoalArea(areanum, aasworld.areas[areanum].center, n, travelflags); + //if the goal is reachable + if (t > 0) + { + if (AAS_AreaSwim(n)) + { + *goalareanum = n; + VectorCopy(aasworld.areas[n].center, goalorigin); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + VectorCopy(aasworld.areas[n].center, start); + if (!AAS_PointAreaNum(start)) + Log_Write("area %d center %f %f %f in solid?", n, start[0], start[1], start[2]); + VectorCopy(start, end); + end[2] -= 300; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid && trace.fraction < 1 && AAS_PointAreaNum(trace.endpos) == n) + { + if (AAS_AreaGroundFaceArea(n) > 300) + { + *goalareanum = n; + VectorCopy(trace.endpos, goalorigin); + //botimport.Print(PRT_MESSAGE, "found random goal area %d\n", *goalareanum); + return qtrue; + } //end if + } //end if + } //end if + } //end if + n++; + } //end for + return qfalse; +} //end of the function AAS_RandomGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaVisible(int srcarea, int destarea) +{ + return qfalse; +} //end of the function AAS_AreaVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float DistancePointToLine(vec3_t v1, vec3_t v2, vec3_t point) +{ + vec3_t vec, p2; + + AAS_ProjectPointOntoVector(point, v1, v2, p2); + VectorSubtract(point, p2, vec); + return VectorLength(vec); +} //end of the function DistancePointToLine +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_NearestHideArea(int srcnum, vec3_t origin, int areanum, int enemynum, vec3_t enemyorigin, int enemyareanum, int travelflags) +{ + int i, j, nextareanum, badtravelflags, numreach, bestarea; + unsigned short int t, besttraveltime; + static unsigned short int *hidetraveltimes; + aas_routingupdate_t *updateliststart, *updatelistend, *curupdate, *nextupdate; + aas_reachability_t *reach; + float dist1, dist2; + vec3_t v1, v2, p; + qboolean startVisible; + + // + if (!hidetraveltimes) + { + hidetraveltimes = (unsigned short int *) GetClearedMemory(aasworld.numareas * sizeof(unsigned short int)); + } //end if + else + { + Com_Memset(hidetraveltimes, 0, aasworld.numareas * sizeof(unsigned short int)); + } //end else + besttraveltime = 0; + bestarea = 0; + //assume visible + startVisible = qtrue; + // + badtravelflags = ~travelflags; + // + curupdate = &aasworld.areaupdate[areanum]; + curupdate->areanum = areanum; + VectorCopy(origin, curupdate->start); + curupdate->areatraveltimes = aasworld.areatraveltimes[areanum][0]; + curupdate->tmptraveltime = 0; + //put the area to start with in the current read list + curupdate->next = NULL; + curupdate->prev = NULL; + updateliststart = curupdate; + updatelistend = curupdate; + //while there are updates in the list + while (updateliststart) + { + curupdate = updateliststart; + // + if (curupdate->next) curupdate->next->prev = NULL; + else updatelistend = NULL; + updateliststart = curupdate->next; + // + curupdate->inlist = qfalse; + //check all reversed reachability links + numreach = aasworld.areasettings[curupdate->areanum].numreachableareas; + reach = &aasworld.reachability[aasworld.areasettings[curupdate->areanum].firstreachablearea]; + // + for (i = 0; i < numreach; i++, reach++) + { + //if an undesired travel type is used + if (AAS_TravelFlagForType_inline(reach->traveltype) & badtravelflags) continue; + // + if (AAS_AreaContentsTravelFlags_inline(reach->areanum) & badtravelflags) continue; + //number of the area the reachability leads to + nextareanum = reach->areanum; + // if this moves us into the enemies area, skip it + if (nextareanum == enemyareanum) continue; + //time already travelled plus the traveltime through + //the current area plus the travel time from the reachability + t = curupdate->tmptraveltime + + AAS_AreaTravelTime(curupdate->areanum, curupdate->start, reach->start) + + reach->traveltime; + + //avoid going near the enemy + AAS_ProjectPointOntoVector(enemyorigin, curupdate->start, reach->end, p); + for (j = 0; j < 3; j++) + if ((p[j] > curupdate->start[j] && p[j] > reach->end[j]) || + (p[j] < curupdate->start[j] && p[j] < reach->end[j])) + break; + if (j < 3) + { + VectorSubtract(enemyorigin, reach->end, v2); + } //end if + else + { + VectorSubtract(enemyorigin, p, v2); + } //end else + dist2 = VectorLength(v2); + //never go through the enemy + if (dist2 < 40) continue; + // + VectorSubtract(enemyorigin, curupdate->start, v1); + dist1 = VectorLength(v1); + // + if (dist2 < dist1) + { + t += (dist1 - dist2) * 10; + } + // if we weren't visible when starting, make sure we don't move into their view + if (!startVisible && AAS_AreaVisible(enemyareanum, nextareanum)) { + continue; + } + // + if (besttraveltime && t >= besttraveltime) continue; + // + if (!hidetraveltimes[nextareanum] || + hidetraveltimes[nextareanum] > t) + { + //if the nextarea is not visible from the enemy area + if (!AAS_AreaVisible(enemyareanum, nextareanum)) + { + besttraveltime = t; + bestarea = nextareanum; + } //end if + hidetraveltimes[nextareanum] = t; + nextupdate = &aasworld.areaupdate[nextareanum]; + nextupdate->areanum = nextareanum; + nextupdate->tmptraveltime = t; + //remember where we entered this area + VectorCopy(reach->end, nextupdate->start); + //if this update is not in the list yet + if (!nextupdate->inlist) + { + //add the new update to the end of the list + nextupdate->next = NULL; + nextupdate->prev = updatelistend; + if (updatelistend) updatelistend->next = nextupdate; + else updateliststart = nextupdate; + updatelistend = nextupdate; + nextupdate->inlist = qtrue; + } //end if + } //end if + } //end for + } //end while + return bestarea; +} //end of the function AAS_NearestHideArea diff --git a/code/botlib/be_aas_route.h b/code/botlib/be_aas_route.h new file mode 100644 index 0000000..8805c66 --- /dev/null +++ b/code/botlib/be_aas_route.h @@ -0,0 +1,67 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_route.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_route.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +//initialize the AAS routing +void AAS_InitRouting(void); +//free the AAS routing caches +void AAS_FreeRoutingCaches(void); +//returns the travel time from start to end in the given area +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); +// +void AAS_CreateAllRoutingCache(void); +void AAS_WriteRouteCache(void); +// +void AAS_RoutingInfo(void); +#endif //AASINTERN + +//returns the travel flag for the given travel type +int AAS_TravelFlagForType(int traveltype); +//return the travel flag(s) for traveling through this area +int AAS_AreaContentsTravelFlags(int areanum); +//returns the index of the next reachability for the given area +int AAS_NextAreaReachability(int areanum, int reachnum); +//returns the reachability with the given index +void AAS_ReachabilityFromNum(int num, struct aas_reachability_s *reach); +//returns a random goal area and goal origin +int AAS_RandomGoalArea(int areanum, int travelflags, int *goalareanum, vec3_t goalorigin); +//enable or disable an area for routing +int AAS_EnableRoutingArea(int areanum, int enable); +//returns the travel time within the given area from start to end +unsigned short int AAS_AreaTravelTime(int areanum, vec3_t start, vec3_t end); +//returns the travel time from the area to the goal area using the given travel flags +int AAS_AreaTravelTimeToGoalArea(int areanum, vec3_t origin, int goalareanum, int travelflags); +//predict a route up to a stop event +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); + + diff --git a/code/botlib/be_aas_routealt.c b/code/botlib/be_aas_routealt.c new file mode 100644 index 0000000..e4f79ee --- /dev/null +++ b/code/botlib/be_aas_routealt.c @@ -0,0 +1,240 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_routealt.c + * + * desc: AAS + * + * $Archive: /MissionPack/code/botlib/be_aas_routealt.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_utils.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_aas_def.h" + +#define ENABLE_ALTROUTING +//#define ALTROUTE_DEBUG + +typedef struct midrangearea_s +{ + int valid; + unsigned short starttime; + unsigned short goaltime; +} midrangearea_t; + +midrangearea_t *midrangeareas; +int *clusterareas; +int numclusterareas; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_AltRoutingFloodCluster_r(int areanum) +{ + int i, otherareanum; + aas_area_t *area; + aas_face_t *face; + + //add the current area to the areas of the current cluster + clusterareas[numclusterareas] = areanum; + numclusterareas++; + //remove the area from the mid range areas + midrangeareas[areanum].valid = qfalse; + //flood to other areas through the faces of this area + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + face = &aasworld.faces[abs(aasworld.faceindex[area->firstface + i])]; + //get the area at the other side of the face + if (face->frontarea == areanum) otherareanum = face->backarea; + else otherareanum = face->frontarea; + //if there is an area at the other side of this face + if (!otherareanum) continue; + //if the other area is not a midrange area + if (!midrangeareas[otherareanum].valid) continue; + // + AAS_AltRoutingFloodCluster_r(otherareanum); + } //end for +} //end of the function AAS_AltRoutingFloodCluster_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int type) +{ +#ifndef ENABLE_ALTROUTING + return 0; +#else + int i, j, bestareanum; + int numaltroutegoals, nummidrangeareas; + int starttime, goaltime, goaltraveltime; + float dist, bestdist; + vec3_t mid, dir; +#ifdef ALTROUTE_DEBUG + int startmillisecs; + + startmillisecs = Sys_MilliSeconds(); +#endif + + if (!startareanum || !goalareanum) + return 0; + //travel time towards the goal area + goaltraveltime = AAS_AreaTravelTimeToGoalArea(startareanum, start, goalareanum, travelflags); + //clear the midrange areas + Com_Memset(midrangeareas, 0, aasworld.numareas * sizeof(midrangearea_t)); + numaltroutegoals = 0; + // + nummidrangeareas = 0; + // + for (i = 1; i < aasworld.numareas; i++) + { + // + if (!(type & ALTROUTEGOAL_ALL)) + { + if (!(type & ALTROUTEGOAL_CLUSTERPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_CLUSTERPORTAL))) + { + if (!(type & ALTROUTEGOAL_VIEWPORTALS && (aasworld.areasettings[i].contents & AREACONTENTS_VIEWPORTAL))) + { + continue; + } //end if + } //end if + } //end if + //if the area has no reachabilities + if (!AAS_AreaReachability(i)) continue; + //tavel time from the area to the start area + starttime = AAS_AreaTravelTimeToGoalArea(startareanum, start, i, travelflags); + if (!starttime) continue; + //if the travel time from the start to the area is greater than the shortest goal travel time + if (starttime > (float) 1.1 * goaltraveltime) continue; + //travel time from the area to the goal area + goaltime = AAS_AreaTravelTimeToGoalArea(i, NULL, goalareanum, travelflags); + if (!goaltime) continue; + //if the travel time from the area to the goal is greater than the shortest goal travel time + if (goaltime > (float) 0.8 * goaltraveltime) continue; + //this is a mid range area + midrangeareas[i].valid = qtrue; + midrangeareas[i].starttime = starttime; + midrangeareas[i].goaltime = goaltime; + Log_Write("%d midrange area %d", nummidrangeareas, i); + nummidrangeareas++; + } //end for + // + for (i = 1; i < aasworld.numareas; i++) + { + if (!midrangeareas[i].valid) continue; + //get the areas in one cluster + numclusterareas = 0; + AAS_AltRoutingFloodCluster_r(i); + //now we've got a cluster with areas through which an alternative route could go + //get the 'center' of the cluster + VectorClear(mid); + for (j = 0; j < numclusterareas; j++) + { + VectorAdd(mid, aasworld.areas[clusterareas[j]].center, mid); + } //end for + VectorScale(mid, 1.0 / numclusterareas, mid); + //get the area closest to the center of the cluster + bestdist = 999999; + bestareanum = 0; + for (j = 0; j < numclusterareas; j++) + { + VectorSubtract(mid, aasworld.areas[clusterareas[j]].center, dir); + dist = VectorLength(dir); + if (dist < bestdist) + { + bestdist = dist; + bestareanum = clusterareas[j]; + } //end if + } //end for + //now we've got an area for an alternative route + //FIXME: add alternative goal origin + VectorCopy(aasworld.areas[bestareanum].center, altroutegoals[numaltroutegoals].origin); + altroutegoals[numaltroutegoals].areanum = bestareanum; + altroutegoals[numaltroutegoals].starttraveltime = midrangeareas[bestareanum].starttime; + altroutegoals[numaltroutegoals].goaltraveltime = midrangeareas[bestareanum].goaltime; + altroutegoals[numaltroutegoals].extratraveltime = + (midrangeareas[bestareanum].starttime + midrangeareas[bestareanum].goaltime) - + goaltraveltime; + numaltroutegoals++; + // +#ifdef ALTROUTE_DEBUG + AAS_ShowAreaPolygons(bestareanum, 1, qtrue); +#endif + //don't return more than the maximum alternative route goals + if (numaltroutegoals >= maxaltroutegoals) break; + } //end for +#ifdef ALTROUTE_DEBUG + botimport.Print(PRT_MESSAGE, "alternative route goals in %d msec\n", Sys_MilliSeconds() - startmillisecs); +#endif + return numaltroutegoals; +#endif +} //end of the function AAS_AlternativeRouteGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAlternativeRouting(void) +{ +#ifdef ENABLE_ALTROUTING + if (midrangeareas) FreeMemory(midrangeareas); + midrangeareas = (midrangearea_t *) GetMemory(aasworld.numareas * sizeof(midrangearea_t)); + if (clusterareas) FreeMemory(clusterareas); + clusterareas = (int *) GetMemory(aasworld.numareas * sizeof(int)); +#endif +} //end of the function AAS_InitAlternativeRouting +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_ShutdownAlternativeRouting(void) +{ +#ifdef ENABLE_ALTROUTING + if (midrangeareas) FreeMemory(midrangeareas); + midrangeareas = NULL; + if (clusterareas) FreeMemory(clusterareas); + clusterareas = NULL; + numclusterareas = 0; +#endif +} //end of the function AAS_ShutdownAlternativeRouting diff --git a/code/botlib/be_aas_routealt.h b/code/botlib/be_aas_routealt.h new file mode 100644 index 0000000..160966a --- /dev/null +++ b/code/botlib/be_aas_routealt.h @@ -0,0 +1,40 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_routealt.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_routealt.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +void AAS_InitAlternativeRouting(void); +void AAS_ShutdownAlternativeRouting(void); +#endif //AASINTERN + + +int AAS_AlternativeRouteGoals(vec3_t start, int startareanum, vec3_t goal, int goalareanum, int travelflags, + aas_altroutegoal_t *altroutegoals, int maxaltroutegoals, + int type); diff --git a/code/botlib/be_aas_sample.c b/code/botlib/be_aas_sample.c new file mode 100644 index 0000000..095641a --- /dev/null +++ b/code/botlib/be_aas_sample.c @@ -0,0 +1,1393 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_sample.c + * + * desc: AAS environment sampling + * + * $Archive: /MissionPack/code/botlib/be_aas_sample.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#ifndef BSPC +#include "l_libvar.h" +#endif +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_interface.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" + + +//#define AAS_SAMPLE_DEBUG + +#define BBOX_NORMAL_EPSILON 0.001 + +#define ON_EPSILON 0 //0.0005 + +#define TRACEPLANE_EPSILON 0.125 + +typedef struct aas_tracestack_s +{ + vec3_t start; //start point of the piece of line to trace + vec3_t end; //end point of the piece of line to trace + int planenum; //last plane used as splitter + int nodenum; //node found after splitting with planenum +} aas_tracestack_t; + +int numaaslinks; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs) +{ + int index; + //bounding box size for each presence type + vec3_t boxmins[3] = {{0, 0, 0}, {-15, -15, -24}, {-15, -15, -24}}; + vec3_t boxmaxs[3] = {{0, 0, 0}, { 15, 15, 32}, { 15, 15, 8}}; + + if (presencetype == PRESENCE_NORMAL) index = 1; + else if (presencetype == PRESENCE_CROUCH) index = 2; + else + { + botimport.Print(PRT_FATAL, "AAS_PresenceTypeBoundingBox: unknown presence type\n"); + index = 2; + } //end if + VectorCopy(boxmins[index], mins); + VectorCopy(boxmaxs[index], maxs); +} //end of the function AAS_PresenceTypeBoundingBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkHeap(void) +{ + int i, max_aaslinks; + + max_aaslinks = aasworld.linkheapsize; + //if there's no link heap present + if (!aasworld.linkheap) + { +#ifdef BSPC + max_aaslinks = 6144; +#else + max_aaslinks = (int) LibVarValue("max_aaslinks", "6144"); +#endif + if (max_aaslinks < 0) max_aaslinks = 0; + aasworld.linkheapsize = max_aaslinks; + aasworld.linkheap = (aas_link_t *) GetHunkMemory(max_aaslinks * sizeof(aas_link_t)); + } //end if + //link the links on the heap + aasworld.linkheap[0].prev_ent = NULL; + aasworld.linkheap[0].next_ent = &aasworld.linkheap[1]; + for (i = 1; i < max_aaslinks-1; i++) + { + aasworld.linkheap[i].prev_ent = &aasworld.linkheap[i - 1]; + aasworld.linkheap[i].next_ent = &aasworld.linkheap[i + 1]; + } //end for + aasworld.linkheap[max_aaslinks-1].prev_ent = &aasworld.linkheap[max_aaslinks-2]; + aasworld.linkheap[max_aaslinks-1].next_ent = NULL; + //pointer to the first free link + aasworld.freelinks = &aasworld.linkheap[0]; + // + numaaslinks = max_aaslinks; +} //end of the function AAS_InitAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkHeap(void) +{ + if (aasworld.linkheap) FreeMemory(aasworld.linkheap); + aasworld.linkheap = NULL; + aasworld.linkheapsize = 0; +} //end of the function AAS_FreeAASLinkHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_AllocAASLink(void) +{ + aas_link_t *link; + + link = aasworld.freelinks; + if (!link) + { +#ifndef BSPC + if (botDeveloper) +#endif + { + botimport.Print(PRT_FATAL, "empty aas link heap\n"); + } //end if + return NULL; + } //end if + if (aasworld.freelinks) aasworld.freelinks = aasworld.freelinks->next_ent; + if (aasworld.freelinks) aasworld.freelinks->prev_ent = NULL; + numaaslinks--; + return link; +} //end of the function AAS_AllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_DeAllocAASLink(aas_link_t *link) +{ + if (aasworld.freelinks) aasworld.freelinks->prev_ent = link; + link->prev_ent = NULL; + link->next_ent = aasworld.freelinks; + link->prev_area = NULL; + link->next_area = NULL; + aasworld.freelinks = link; + numaaslinks++; +} //end of the function AAS_DeAllocAASLink +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_InitAASLinkedEntities(void) +{ + if (!aasworld.loaded) return; + if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); + aasworld.arealinkedentities = (aas_link_t **) GetClearedHunkMemory( + aasworld.numareas * sizeof(aas_link_t *)); +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FreeAASLinkedEntities(void) +{ + if (aasworld.arealinkedentities) FreeMemory(aasworld.arealinkedentities); + aasworld.arealinkedentities = NULL; +} //end of the function AAS_InitAASLinkedEntities +//=========================================================================== +// returns the AAS area the point is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointAreaNum(vec3_t point) +{ + int nodenum; + vec_t dist; + aas_node_t *node; + aas_plane_t *plane; + + if (!aasworld.loaded) + { + botimport.Print(PRT_ERROR, "AAS_PointAreaNum: aas not loaded\n"); + return 0; + } //end if + + //start with node 1 because node zero is a dummy used for solid leafs + nodenum = 1; + while (nodenum > 0) + { +// botimport.Print(PRT_MESSAGE, "[%d]", nodenum); +#ifdef AAS_SAMPLE_DEBUG + if (nodenum >= aasworld.numnodes) + { + botimport.Print(PRT_ERROR, "nodenum = %d >= aasworld.numnodes = %d\n", nodenum, aasworld.numnodes); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + node = &aasworld.nodes[nodenum]; +#ifdef AAS_SAMPLE_DEBUG + if (node->planenum < 0 || node->planenum >= aasworld.numplanes) + { + botimport.Print(PRT_ERROR, "node->planenum = %d >= aasworld.numplanes = %d\n", node->planenum, aasworld.numplanes); + return 0; + } //end if +#endif //AAS_SAMPLE_DEBUG + plane = &aasworld.planes[node->planenum]; + dist = DotProduct(point, plane->normal) - plane->dist; + if (dist > 0) nodenum = node->children[0]; + else nodenum = node->children[1]; + } //end while + if (!nodenum) + { +#ifdef AAS_SAMPLE_DEBUG + botimport.Print(PRT_MESSAGE, "in solid\n"); +#endif //AAS_SAMPLE_DEBUG + return 0; + } //end if + return -nodenum; +} //end of the function AAS_PointAreaNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointReachabilityAreaIndex( vec3_t origin ) +{ + int areanum, cluster, i, index; + + if (!aasworld.initialized) + return 0; + + if ( !origin ) + { + index = 0; + for (i = 0; i < aasworld.numclusters; i++) + { + index += aasworld.clusters[i].numreachabilityareas; + } //end for + return index; + } //end if + + areanum = AAS_PointAreaNum( origin ); + if ( !areanum || !AAS_AreaReachability(areanum) ) + return 0; + cluster = aasworld.areasettings[areanum].cluster; + areanum = aasworld.areasettings[areanum].clusterareanum; + if (cluster < 0) + { + cluster = aasworld.portals[-cluster].frontcluster; + areanum = aasworld.portals[-cluster].clusterareanum[0]; + } //end if + + index = 0; + for (i = 0; i < cluster; i++) + { + index += aasworld.clusters[i].numreachabilityareas; + } //end for + index += areanum; + return index; +} //end of the function AAS_PointReachabilityAreaIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaCluster(int areanum) +{ + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaCluster: invalid area number\n"); + return 0; + } //end if + return aasworld.areasettings[areanum].cluster; +} //end of the function AAS_AreaCluster +//=========================================================================== +// returns the presence types of the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaPresenceType(int areanum) +{ + if (!aasworld.loaded) return 0; + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaPresenceType: invalid area number\n"); + return 0; + } //end if + return aasworld.areasettings[areanum].presencetype; +} //end of the function AAS_AreaPresenceType +//=========================================================================== +// returns the presence type at the given point +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_PointPresenceType(vec3_t point) +{ + int areanum; + + if (!aasworld.loaded) return 0; + + areanum = AAS_PointAreaNum(point); + if (!areanum) return PRESENCE_NONE; + return aasworld.areasettings[areanum].presencetype; +} //end of the function AAS_PointPresenceType +//=========================================================================== +// calculates the minimum distance between the origin of the box and the +// given plane when both will collide on the given side of the plane +// +// normal = normal vector of plane to calculate distance from +// mins = minimums of box relative to origin +// maxs = maximums of box relative to origin +// side = side of the plane we want to calculate the distance from +// 0 normal vector side +// 1 not normal vector side +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +vec_t AAS_BoxOriginDistanceFromPlane(vec3_t normal, vec3_t mins, vec3_t maxs, int side) +{ + vec3_t v1, v2; + int i; + + //swap maxs and mins when on the other side of the plane + if (side) + { + //get a point of the box that would be one of the first + //to collide with the plane + for (i = 0; i < 3; i++) + { + if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; + else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = mins[i]; + else v1[i] = 0; + } //end for + } //end if + else + { + //get a point of the box that would be one of the first + //to collide with the plane + for (i = 0; i < 3; i++) + { + if (normal[i] > BBOX_NORMAL_EPSILON) v1[i] = mins[i]; + else if (normal[i] < -BBOX_NORMAL_EPSILON) v1[i] = maxs[i]; + else v1[i] = 0; + } //end for + } //end else + // + VectorCopy(normal, v2); + VectorInverse(v2); +// VectorNegate(normal, v2); + return DotProduct(v1, v2); +} //end of the function AAS_BoxOriginDistanceFromPlane +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_AreaEntityCollision(int areanum, vec3_t start, vec3_t end, + int presencetype, int passent, aas_trace_t *trace) +{ + int collision; + vec3_t boxmins, boxmaxs; + aas_link_t *link; + bsp_trace_t bsptrace; + + AAS_PresenceTypeBoundingBox(presencetype, boxmins, boxmaxs); + + Com_Memset(&bsptrace, 0, sizeof(bsp_trace_t)); //make compiler happy + //assume no collision + bsptrace.fraction = 1; + collision = qfalse; + for (link = aasworld.arealinkedentities[areanum]; link; link = link->next_ent) + { + //ignore the pass entity + if (link->entnum == passent) continue; + // + if (AAS_EntityCollision(link->entnum, start, boxmins, boxmaxs, end, + CONTENTS_SOLID|CONTENTS_PLAYERCLIP, &bsptrace)) + { + collision = qtrue; + } //end if + } //end for + if (collision) + { + trace->startsolid = bsptrace.startsolid; + trace->ent = bsptrace.ent; + VectorCopy(bsptrace.endpos, trace->endpos); + trace->area = 0; + trace->planenum = 0; + return qtrue; + } //end if + return qfalse; +} //end of the function AAS_AreaEntityCollision +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, + int passent) +{ + int side, nodenum, tmpplanenum; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid, v1, v2; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_trace_t trace; + + //clear the trace structure + Com_Memset(&trace, 0, sizeof(aas_trace_t)); + + if (!aasworld.loaded) return trace; + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy(start, tstack_p->start); + VectorCopy(end, tstack_p->end); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while (1) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (tstack_p < tracestack) + { + tstack_p++; + //nothing was hit + trace.startsolid = qfalse; + trace.fraction = 1.0; + //endpos is the end of the line + VectorCopy(end, trace.endpos); + //nothing hit + trace.ent = 0; + trace.area = 0; + trace.planenum = 0; + return trace; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { +#ifdef AAS_SAMPLE_DEBUG + if (-nodenum > aasworld.numareasettings) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: -nodenum out of range\n"); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); + //if can't enter the area because it hasn't got the right presence type + if (!(aasworld.areasettings[-nodenum].presencetype & presencetype)) + { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if (tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2]) + { + trace.startsolid = qtrue; + trace.fraction = 0.0; + VectorClear(v1); + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract(end, start, v1); + VectorSubtract(tstack_p->start, start, v2); + trace.fraction = VectorLength(v2) / VectorNormalize(v1); + VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); + } //end else + VectorCopy(tstack_p->start, trace.endpos); + trace.ent = 0; + trace.area = -nodenum; +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &aasworld.planes[trace.planenum]; + if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; + return trace; + } //end if + else + { + if (passent >= 0) + { + if (AAS_AreaEntityCollision(-nodenum, tstack_p->start, + tstack_p->end, presencetype, passent, + &trace)) + { + if (!trace.startsolid) + { + VectorSubtract(end, start, v1); + VectorSubtract(trace.endpos, start, v2); + trace.fraction = VectorLength(v2) / VectorLength(v1); + } //end if + return trace; + } //end if + } //end if + } //end else + trace.lastarea = -nodenum; + continue; + } //end if + //if it is a solid leaf + if (!nodenum) + { + //if the start point is still the initial start point + //NOTE: no need for epsilons because the points will be + //exactly the same when they're both the start point + if (tstack_p->start[0] == start[0] && + tstack_p->start[1] == start[1] && + tstack_p->start[2] == start[2]) + { + trace.startsolid = qtrue; + trace.fraction = 0.0; + VectorClear(v1); + } //end if + else + { + trace.startsolid = qfalse; + VectorSubtract(end, start, v1); + VectorSubtract(tstack_p->start, start, v2); + trace.fraction = VectorLength(v2) / VectorNormalize(v1); + VectorMA(tstack_p->start, -0.125, v1, tstack_p->start); + } //end else + VectorCopy(tstack_p->start, trace.endpos); + trace.ent = 0; + trace.area = 0; //hit solid leaf +// VectorSubtract(end, start, v1); + trace.planenum = tstack_p->planenum; + //always take the plane with normal facing towards the trace start + plane = &aasworld.planes[trace.planenum]; + if (DotProduct(v1, plane->normal) > 0) trace.planenum ^= 1; + return trace; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if (nodenum > aasworld.numnodes) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: nodenum out of range\n"); + return trace; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &aasworld.nodes[nodenum]; + //start point of current line to test against node + VectorCopy(tstack_p->start, cur_start); + //end point of the current line to test against node + VectorCopy(tstack_p->end, cur_end); + //the current node plane + plane = &aasworld.planes[aasnode->planenum]; + + switch(plane->type) + {/*FIXME: wtf doesn't this work? obviously the axial node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ + default: //gee it's not an axial plane + { + front = DotProduct(cur_start, plane->normal) - plane->dist; + back = DotProduct(cur_end, plane->normal) - plane->dist; + break; + } //end default + } //end switch + // bk010221 - old location of FPE hack and divide by zero expression + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if ((front >= -ON_EPSILON && back >= -ON_EPSILON)) + { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if ((front < ON_EPSILON && back < ON_EPSILON)) + { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + // bk010221 - new location of divide by zero (see above) + if ( front == back ) front -= 0.001f; // bk0101022 - hack/FPE + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if (front < 0) frac = (front + TRACEPLANE_EPSILON)/(front-back); + else frac = (front - TRACEPLANE_EPSILON)/(front-back); // bk010221 + // + if (frac < 0) + frac = 0.001f; //0 + else if (frac > 1) + frac = 0.999f; //1 + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; + cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; + cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy(cur_mid, tstack_p->start); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy(cur_start, tstack_p->start); + VectorCopy(cur_mid, tstack_p->end); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceBoundingBox: stack overflow\n"); + return trace; + } //end if + } //end else + } //end while +// return trace; +} //end of the function AAS_TraceClientBBox +//=========================================================================== +// recursive subdivision of the line by the BSP tree. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas) +{ + int side, nodenum, tmpplanenum; + int numareas; + float front, back, frac; + vec3_t cur_start, cur_end, cur_mid; + aas_tracestack_t tracestack[127]; + aas_tracestack_t *tstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + + numareas = 0; + areas[0] = 0; + if (!aasworld.loaded) return numareas; + + tstack_p = tracestack; + //we start with the whole line on the stack + VectorCopy(start, tstack_p->start); + VectorCopy(end, tstack_p->end); + tstack_p->planenum = 0; + //start with node 1 because node zero is a dummy for a solid leaf + tstack_p->nodenum = 1; //starting at the root of the tree + tstack_p++; + + while (1) + { + //pop up the stack + tstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (tstack_p < tracestack) + { + return numareas; + } //end if + //number of the current node to test the line against + nodenum = tstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { +#ifdef AAS_SAMPLE_DEBUG + if (-nodenum > aasworld.numareasettings) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: -nodenum = %d out of range\n", -nodenum); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //botimport.Print(PRT_MESSAGE, "areanum = %d, must be %d\n", -nodenum, AAS_PointAreaNum(start)); + areas[numareas] = -nodenum; + if (points) VectorCopy(tstack_p->start, points[numareas]); + numareas++; + if (numareas >= maxareas) return numareas; + continue; + } //end if + //if it is a solid leaf + if (!nodenum) + { + continue; + } //end if +#ifdef AAS_SAMPLE_DEBUG + if (nodenum > aasworld.numnodes) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: nodenum out of range\n"); + return numareas; + } //end if +#endif //AAS_SAMPLE_DEBUG + //the node to test against + aasnode = &aasworld.nodes[nodenum]; + //start point of current line to test against node + VectorCopy(tstack_p->start, cur_start); + //end point of the current line to test against node + VectorCopy(tstack_p->end, cur_end); + //the current node plane + plane = &aasworld.planes[aasnode->planenum]; + + switch(plane->type) + {/*FIXME: wtf doesn't this work? obviously the node planes aren't always facing positive!!! + //check for axial planes + case PLANE_X: + { + front = cur_start[0] - plane->dist; + back = cur_end[0] - plane->dist; + break; + } //end case + case PLANE_Y: + { + front = cur_start[1] - plane->dist; + back = cur_end[1] - plane->dist; + break; + } //end case + case PLANE_Z: + { + front = cur_start[2] - plane->dist; + back = cur_end[2] - plane->dist; + break; + } //end case*/ + default: //gee it's not an axial plane + { + front = DotProduct(cur_start, plane->normal) - plane->dist; + back = DotProduct(cur_end, plane->normal) - plane->dist; + break; + } //end default + } //end switch + + //if the whole to be traced line is totally at the front of this node + //only go down the tree with the front child + if (front > 0 && back > 0) + { + //keep the current start and end point on the stack + //and go down the tree with the front child + tstack_p->nodenum = aasnode->children[0]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end if + //if the whole to be traced line is totally at the back of this node + //only go down the tree with the back child + else if (front <= 0 && back <= 0) + { + //keep the current start and end point on the stack + //and go down the tree with the back child + tstack_p->nodenum = aasnode->children[1]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end if + //go down the tree both at the front and back of the node + else + { + tmpplanenum = tstack_p->planenum; + //calculate the hitpoint with the node (split point of the line) + //put the crosspoint TRACEPLANE_EPSILON pixels on the near side + if (front < 0) frac = (front)/(front-back); + else frac = (front)/(front-back); + if (frac < 0) frac = 0; + else if (frac > 1) frac = 1; + //frac = front / (front-back); + // + cur_mid[0] = cur_start[0] + (cur_end[0] - cur_start[0]) * frac; + cur_mid[1] = cur_start[1] + (cur_end[1] - cur_start[1]) * frac; + cur_mid[2] = cur_start[2] + (cur_end[2] - cur_start[2]) * frac; + +// AAS_DrawPlaneCross(cur_mid, plane->normal, plane->dist, plane->type, LINECOLOR_RED); + //side the front part of the line is on + side = front < 0; + //first put the end part of the line on the stack (back side) + VectorCopy(cur_mid, tstack_p->start); + //not necesary to store because still on stack + //VectorCopy(cur_end, tstack_p->end); + tstack_p->planenum = aasnode->planenum; + tstack_p->nodenum = aasnode->children[!side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + //now put the part near the start of the line on the stack so we will + //continue with thats part first. This way we'll find the first + //hit of the bbox + VectorCopy(cur_start, tstack_p->start); + VectorCopy(cur_mid, tstack_p->end); + tstack_p->planenum = tmpplanenum; + tstack_p->nodenum = aasnode->children[side]; + tstack_p++; + if (tstack_p >= &tracestack[127]) + { + botimport.Print(PRT_ERROR, "AAS_TraceAreas: stack overflow\n"); + return numareas; + } //end if + } //end else + } //end while +// return numareas; +} //end of the function AAS_TraceAreas +//=========================================================================== +// a simple cross product +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +// void AAS_OrthogonalToVectors(vec3_t v1, vec3_t v2, vec3_t res) +#define AAS_OrthogonalToVectors(v1, v2, res) \ + (res)[0] = ((v1)[1] * (v2)[2]) - ((v1)[2] * (v2)[1]);\ + (res)[1] = ((v1)[2] * (v2)[0]) - ((v1)[0] * (v2)[2]);\ + (res)[2] = ((v1)[0] * (v2)[1]) - ((v1)[1] * (v2)[0]); +//=========================================================================== +// tests if the given point is within the face boundaries +// +// Parameter: face : face to test if the point is in it +// pnormal : normal of the plane to use for the face +// point : point to test if inside face boundaries +// Returns: qtrue if the point is within the face boundaries +// Changes Globals: - +//=========================================================================== +qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon) +{ + int i, firstvertex, edgenum; + vec3_t v0; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; +#ifdef AAS_SAMPLE_DEBUG + int lastvertex = 0; +#endif //AAS_SAMPLE_DEBUG + + if (!aasworld.loaded) return qfalse; + + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + VectorCopy(aasworld.vertexes[edge->v[firstvertex]], v0); + //edge vector + VectorSubtract(aasworld.vertexes[edge->v[!firstvertex]], v0, edgevec); + // +#ifdef AAS_SAMPLE_DEBUG + if (lastvertex && lastvertex != edge->v[firstvertex]) + { + botimport.Print(PRT_MESSAGE, "winding not counter clockwise\n"); + } //end if + lastvertex = edge->v[!firstvertex]; +#endif //AAS_SAMPLE_DEBUG + //vector from first edge point to point possible in face + VectorSubtract(point, v0, pointvec); + //get a vector pointing inside the face orthogonal to both the + //edge vector and the normal vector of the plane the face is in + //this vector defines a plane through the origin (first vertex of + //edge) and through both the edge vector and the normal vector + //of the plane + AAS_OrthogonalToVectors(edgevec, pnormal, sepnormal); + //check on wich side of the above plane the point is + //this is done by checking the sign of the dot product of the + //vector orthogonal vector from above and the vector from the + //origin (first vertex of edge) to the point + //if the dotproduct is smaller than zero the point is outside the face + if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_InsideFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon) +{ + int i, firstvertex, edgenum; + vec_t *v1, *v2; + vec3_t edgevec, pointvec, sepnormal; + aas_edge_t *edge; + aas_plane_t *plane; + aas_face_t *face; + + if (!aasworld.loaded) return qfalse; + + face = &aasworld.faces[facenum]; + plane = &aasworld.planes[face->planenum]; + // + for (i = 0; i < face->numedges; i++) + { + edgenum = aasworld.edgeindex[face->firstedge + i]; + edge = &aasworld.edges[abs(edgenum)]; + //get the first vertex of the edge + firstvertex = edgenum < 0; + v1 = aasworld.vertexes[edge->v[firstvertex]]; + v2 = aasworld.vertexes[edge->v[!firstvertex]]; + //edge vector + VectorSubtract(v2, v1, edgevec); + //vector from first edge point to point possible in face + VectorSubtract(point, v1, pointvec); + // + CrossProduct(edgevec, plane->normal, sepnormal); + // + if (DotProduct(pointvec, sepnormal) < -epsilon) return qfalse; + } //end for + return qtrue; +} //end of the function AAS_PointInsideFace +//=========================================================================== +// returns the ground face the given point is above in the given area +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point) +{ + int i, facenum; + vec3_t up = {0, 0, 1}; + vec3_t normal; + aas_area_t *area; + aas_face_t *face; + + if (!aasworld.loaded) return NULL; + + area = &aasworld.areas[areanum]; + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + face = &aasworld.faces[abs(facenum)]; + //if this is a ground face + if (face->faceflags & FACE_GROUND) + { + //get the up or down normal + if (aasworld.planes[face->planenum].normal[2] < 0) VectorNegate(up, normal); + else VectorCopy(up, normal); + //check if the point is in the face + if (AAS_InsideFace(face, normal, point, 0.01f)) return face; + } //end if + } //end for + return NULL; +} //end of the function AAS_AreaGroundFace +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_FacePlane(int facenum, vec3_t normal, float *dist) +{ + aas_plane_t *plane; + + plane = &aasworld.planes[aasworld.faces[facenum].planenum]; + VectorCopy(plane->normal, normal); + *dist = plane->dist; +} //end of the function AAS_FacePlane +//=========================================================================== +// returns the face the trace end position is situated in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_face_t *AAS_TraceEndFace(aas_trace_t *trace) +{ + int i, facenum; + aas_area_t *area; + aas_face_t *face, *firstface = NULL; + + if (!aasworld.loaded) return NULL; + + //if started in solid no face was hit + if (trace->startsolid) return NULL; + //trace->lastarea is the last area the trace was in + area = &aasworld.areas[trace->lastarea]; + //check which face the trace.endpos was in + for (i = 0; i < area->numfaces; i++) + { + facenum = aasworld.faceindex[area->firstface + i]; + face = &aasworld.faces[abs(facenum)]; + //if the face is in the same plane as the trace end point + if ((face->planenum & ~1) == (trace->planenum & ~1)) + { + //firstface is used for optimization, if theres only one + //face in the plane then it has to be the good one + //if there are more faces in the same plane then always + //check the one with the fewest edges first +/* if (firstface) + { + if (firstface->numedges < face->numedges) + { + if (AAS_InsideFace(firstface, + aasworld.planes[face->planenum].normal, trace->endpos)) + { + return firstface; + } //end if + firstface = face; + } //end if + else + { + if (AAS_InsideFace(face, + aasworld.planes[face->planenum].normal, trace->endpos)) + { + return face; + } //end if + } //end else + } //end if + else + { + firstface = face; + } //end else*/ + if (AAS_InsideFace(face, + aasworld.planes[face->planenum].normal, trace->endpos, 0.01f)) return face; + } //end if + } //end for + return firstface; +} //end of the function AAS_TraceEndFace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BoxOnPlaneSide2(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) +{ + int i, sides; + float dist1, dist2; + vec3_t corners[2]; + + for (i = 0; i < 3; i++) + { + if (p->normal[i] < 0) + { + corners[0][i] = absmins[i]; + corners[1][i] = absmaxs[i]; + } //end if + else + { + corners[1][i] = absmins[i]; + corners[0][i] = absmaxs[i]; + } //end else + } //end for + dist1 = DotProduct(p->normal, corners[0]) - p->dist; + dist2 = DotProduct(p->normal, corners[1]) - p->dist; + sides = 0; + if (dist1 >= 0) sides = 1; + if (dist2 < 0) sides |= 2; + + return sides; +} //end of the function AAS_BoxOnPlaneSide2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +//int AAS_BoxOnPlaneSide(vec3_t absmins, vec3_t absmaxs, aas_plane_t *p) +#define AAS_BoxOnPlaneSide(absmins, absmaxs, p) (\ + ( (p)->type < 3) ?\ + (\ + ( (p)->dist <= (absmins)[(p)->type]) ?\ + (\ + 1\ + )\ + :\ + (\ + ( (p)->dist >= (absmaxs)[(p)->type]) ?\ + (\ + 2\ + )\ + :\ + (\ + 3\ + )\ + )\ + )\ + :\ + (\ + AAS_BoxOnPlaneSide2((absmins), (absmaxs), (p))\ + )\ +) //end of the function AAS_BoxOnPlaneSide +//=========================================================================== +// remove the links to this entity from all areas +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_UnlinkFromAreas(aas_link_t *areas) +{ + aas_link_t *link, *nextlink; + + for (link = areas; link; link = nextlink) + { + //next area the entity is linked in + nextlink = link->next_area; + //remove the entity from the linked list of this area + if (link->prev_ent) link->prev_ent->next_ent = link->next_ent; + else aasworld.arealinkedentities[link->areanum] = link->next_ent; + if (link->next_ent) link->next_ent->prev_ent = link->prev_ent; + //deallocate the link structure + AAS_DeAllocAASLink(link); + } //end for +} //end of the function AAS_UnlinkFromAreas +//=========================================================================== +// link the entity to the areas the bounding box is totally or partly +// situated in. This is done with recursion down the tree using the +// bounding box to test for plane sides +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +typedef struct +{ + int nodenum; //node found after splitting +} aas_linkstack_t; + +aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum) +{ + int side, nodenum; + aas_linkstack_t linkstack[128]; + aas_linkstack_t *lstack_p; + aas_node_t *aasnode; + aas_plane_t *plane; + aas_link_t *link, *areas; + + if (!aasworld.loaded) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: aas not loaded\n"); + return NULL; + } //end if + + areas = NULL; + // + lstack_p = linkstack; + //we start with the whole line on the stack + //start with node 1 because node zero is a dummy used for solid leafs + lstack_p->nodenum = 1; //starting at the root of the tree + lstack_p++; + + while (1) + { + //pop up the stack + lstack_p--; + //if the trace stack is empty (ended up with a piece of the + //line to be traced in an area) + if (lstack_p < linkstack) break; + //number of the current node to test the line against + nodenum = lstack_p->nodenum; + //if it is an area + if (nodenum < 0) + { + //NOTE: the entity might have already been linked into this area + // because several node children can point to the same area + for (link = aasworld.arealinkedentities[-nodenum]; link; link = link->next_ent) + { + if (link->entnum == entnum) break; + } //end for + if (link) continue; + // + link = AAS_AllocAASLink(); + if (!link) return areas; + link->entnum = entnum; + link->areanum = -nodenum; + //put the link into the double linked area list of the entity + link->prev_area = NULL; + link->next_area = areas; + if (areas) areas->prev_area = link; + areas = link; + //put the link into the double linked entity list of the area + link->prev_ent = NULL; + link->next_ent = aasworld.arealinkedentities[-nodenum]; + if (aasworld.arealinkedentities[-nodenum]) + aasworld.arealinkedentities[-nodenum]->prev_ent = link; + aasworld.arealinkedentities[-nodenum] = link; + // + continue; + } //end if + //if solid leaf + if (!nodenum) continue; + //the node to test against + aasnode = &aasworld.nodes[nodenum]; + //the current node plane + plane = &aasworld.planes[aasnode->planenum]; + //get the side(s) the box is situated relative to the plane + side = AAS_BoxOnPlaneSide2(absmins, absmaxs, plane); + //if on the front side of the node + if (side & 1) + { + lstack_p->nodenum = aasnode->children[0]; + lstack_p++; + } //end if + if (lstack_p >= &linkstack[127]) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); + break; + } //end if + //if on the back side of the node + if (side & 2) + { + lstack_p->nodenum = aasnode->children[1]; + lstack_p++; + } //end if + if (lstack_p >= &linkstack[127]) + { + botimport.Print(PRT_ERROR, "AAS_LinkEntity: stack overflow\n"); + break; + } //end if + } //end while + return areas; +} //end of the function AAS_AASLinkEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype) +{ + vec3_t mins, maxs; + vec3_t newabsmins, newabsmaxs; + + AAS_PresenceTypeBoundingBox(presencetype, mins, maxs); + VectorSubtract(absmins, maxs, newabsmins); + VectorSubtract(absmaxs, mins, newabsmaxs); + //relink the entity + return AAS_AASLinkEntity(newabsmins, newabsmaxs, entnum); +} //end of the function AAS_LinkEntityClientBBox +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas) +{ + aas_link_t *linkedareas, *link; + int num; + + linkedareas = AAS_AASLinkEntity(absmins, absmaxs, -1); + num = 0; + for (link = linkedareas; link; link = link->next_area) + { + areas[num] = link->areanum; + num++; + if (num >= maxareas) + break; + } //end for + AAS_UnlinkFromAreas(linkedareas); + return num; +} //end of the function AAS_BBoxAreas +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AAS_AreaInfo( int areanum, aas_areainfo_t *info ) +{ + aas_areasettings_t *settings; + if (!info) + return 0; + if (areanum <= 0 || areanum >= aasworld.numareas) + { + botimport.Print(PRT_ERROR, "AAS_AreaInfo: areanum %d out of range\n", areanum); + return 0; + } //end if + settings = &aasworld.areasettings[areanum]; + info->cluster = settings->cluster; + info->contents = settings->contents; + info->flags = settings->areaflags; + info->presencetype = settings->presencetype; + VectorCopy(aasworld.areas[areanum].mins, info->mins); + VectorCopy(aasworld.areas[areanum].maxs, info->maxs); + VectorCopy(aasworld.areas[areanum].center, info->center); + return sizeof(aas_areainfo_t); +} //end of the function AAS_AreaInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +aas_plane_t *AAS_PlaneFromNum(int planenum) +{ + if (!aasworld.loaded) return NULL; + + return &aasworld.planes[planenum]; +} //end of the function AAS_PlaneFromNum diff --git a/code/botlib/be_aas_sample.h b/code/botlib/be_aas_sample.h new file mode 100644 index 0000000..ed6c237 --- /dev/null +++ b/code/botlib/be_aas_sample.h @@ -0,0 +1,69 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_aas_sample.h + * + * desc: AAS + * + * $Archive: /source/code/botlib/be_aas_sample.h $ + * + *****************************************************************************/ + +#ifdef AASINTERN +void AAS_InitAASLinkHeap(void); +void AAS_InitAASLinkedEntities(void); +void AAS_FreeAASLinkHeap(void); +void AAS_FreeAASLinkedEntities(void); +aas_face_t *AAS_AreaGroundFace(int areanum, vec3_t point); +aas_face_t *AAS_TraceEndFace(aas_trace_t *trace); +aas_plane_t *AAS_PlaneFromNum(int planenum); +aas_link_t *AAS_AASLinkEntity(vec3_t absmins, vec3_t absmaxs, int entnum); +aas_link_t *AAS_LinkEntityClientBBox(vec3_t absmins, vec3_t absmaxs, int entnum, int presencetype); +qboolean AAS_PointInsideFace(int facenum, vec3_t point, float epsilon); +qboolean AAS_InsideFace(aas_face_t *face, vec3_t pnormal, vec3_t point, float epsilon); +void AAS_UnlinkFromAreas(aas_link_t *areas); +#endif //AASINTERN + +//returns the mins and maxs of the bounding box for the given presence type +void AAS_PresenceTypeBoundingBox(int presencetype, vec3_t mins, vec3_t maxs); +//returns the cluster the area is in (negative portal number if the area is a portal) +int AAS_AreaCluster(int areanum); +//returns the presence type(s) of the area +int AAS_AreaPresenceType(int areanum); +//returns the presence type(s) at the given point +int AAS_PointPresenceType(vec3_t point); +//returns the result of the trace of a client bbox +aas_trace_t AAS_TraceClientBBox(vec3_t start, vec3_t end, int presencetype, int passent); +//stores the areas the trace went through and returns the number of passed areas +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); +//returns the areas the bounding box is in +int AAS_BBoxAreas(vec3_t absmins, vec3_t absmaxs, int *areas, int maxareas); +//return area information +int AAS_AreaInfo( int areanum, aas_areainfo_t *info ); +//returns the area the point is in +int AAS_PointAreaNum(vec3_t point); +// +int AAS_PointReachabilityAreaIndex( vec3_t point ); +//returns the plane the given face is in +void AAS_FacePlane(int facenum, vec3_t normal, float *dist); + diff --git a/code/botlib/be_ai_char.c b/code/botlib/be_ai_char.c new file mode 100644 index 0000000..b5d044c --- /dev/null +++ b/code/botlib/be_ai_char.c @@ -0,0 +1,790 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_char.c + * + * desc: bot characters + * + * $Archive: /MissionPack/code/botlib/be_ai_char.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_char.h" + +#define MAX_CHARACTERISTICS 80 + +#define CT_INTEGER 1 +#define CT_FLOAT 2 +#define CT_STRING 3 + +#define DEFAULT_CHARACTER "bots/default_c.c" + +//characteristic value +union cvalue +{ + int integer; + float _float; + char *string; +}; +//a characteristic +typedef struct bot_characteristic_s +{ + char type; //characteristic type + union cvalue value; //characteristic value +} bot_characteristic_t; + +//a bot character +typedef struct bot_character_s +{ + char filename[MAX_QPATH]; + float skill; + bot_characteristic_t c[1]; //variable sized +} bot_character_t; + +bot_character_t *botcharacters[MAX_CLIENTS + 1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_character_t *BotCharacterFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); + return NULL; + } //end if + if (!botcharacters[handle]) + { + botimport.Print(PRT_FATAL, "invalid character %d\n", handle); + return NULL; + } //end if + return botcharacters[handle]; +} //end of the function BotCharacterFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpCharacter(bot_character_t *ch) +{ + int i; + + Log_Write("%s\n", ch->filename); + Log_Write("skill %.1f\n", ch->skill); + Log_Write("{\n"); + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + switch(ch->c[i].type) + { + case CT_INTEGER: Log_Write(" %4d %d\n", i, ch->c[i].value.integer); break; + case CT_FLOAT: Log_Write(" %4d %f\n", i, ch->c[i].value._float); break; + case CT_STRING: Log_Write(" %4d %s\n", i, ch->c[i].value.string); break; + } //end case + } //end for + Log_Write("}\n"); +} //end of the function BotDumpCharacter +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacterStrings(bot_character_t *ch) +{ + int i; + + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + if (ch->c[i].type == CT_STRING) + { + FreeMemory(ch->c[i].value.string); + } //end if + } //end for +} //end of the function BotFreeCharacterStrings +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter2(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "character handle %d out of range\n", handle); + return; + } //end if + if (!botcharacters[handle]) + { + botimport.Print(PRT_FATAL, "invalid character %d\n", handle); + return; + } //end if + BotFreeCharacterStrings(botcharacters[handle]); + FreeMemory(botcharacters[handle]); + botcharacters[handle] = NULL; +} //end of the function BotFreeCharacter2 +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeCharacter(int handle) +{ + if (!LibVarGetValue("bot_reloadcharacters")) return; + BotFreeCharacter2(handle); +} //end of the function BotFreeCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDefaultCharacteristics(bot_character_t *ch, bot_character_t *defaultch) +{ + int i; + + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + if (ch->c[i].type) continue; + // + if (defaultch->c[i].type == CT_FLOAT) + { + ch->c[i].type = CT_FLOAT; + ch->c[i].value._float = defaultch->c[i].value._float; + } //end if + else if (defaultch->c[i].type == CT_INTEGER) + { + ch->c[i].type = CT_INTEGER; + ch->c[i].value.integer = defaultch->c[i].value.integer; + } //end else if + else if (defaultch->c[i].type == CT_STRING) + { + ch->c[i].type = CT_STRING; + ch->c[i].value.string = (char *) GetMemory(strlen(defaultch->c[i].value.string)+1); + strcpy(ch->c[i].value.string, defaultch->c[i].value.string); + } //end else if + } //end for +} //end of the function BotDefaultCharacteristics +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_character_t *BotLoadCharacterFromFile(char *charfile, int skill) +{ + int indent, index, foundcharacter; + bot_character_t *ch; + source_t *source; + token_t token; + + foundcharacter = qfalse; + //a bot character is parsed in two phases + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(charfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", charfile); + return NULL; + } //end if + ch = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); + strcpy(ch->filename, charfile); + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "skill")) + { + if (!PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + //if it's the correct skill + if (skill < 0 || token.intvalue == skill) + { + foundcharacter = qtrue; + ch->skill = token.intvalue; + while(PC_ExpectAnyToken(source, &token)) + { + if (!strcmp(token.string, "}")) break; + if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) + { + SourceError(source, "expected integer index, found %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + index = token.intvalue; + if (index < 0 || index > MAX_CHARACTERISTICS) + { + SourceError(source, "characteristic index out of range [0, %d]\n", MAX_CHARACTERISTICS); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (ch->c[index].type) + { + SourceError(source, "characteristic %d already initialized\n", index); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (token.type == TT_NUMBER) + { + if (token.subtype & TT_FLOAT) + { + ch->c[index].value._float = token.floatvalue; + ch->c[index].type = CT_FLOAT; + } //end if + else + { + ch->c[index].value.integer = token.intvalue; + ch->c[index].type = CT_INTEGER; + } //end else + } //end if + else if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + ch->c[index].value.string = GetMemory(strlen(token.string)+1); + strcpy(ch->c[index].value.string, token.string); + ch->c[index].type = CT_STRING; + } //end else if + else + { + SourceError(source, "expected integer, float or string, found %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end else + } //end if + break; + } //end if + else + { + indent = 1; + while(indent) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + if (!strcmp(token.string, "{")) indent++; + else if (!strcmp(token.string, "}")) indent--; + } //end while + } //end else + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeSource(source); + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end else + } //end while + FreeSource(source); + // + if (!foundcharacter) + { + BotFreeCharacterStrings(ch); + FreeMemory(ch); + return NULL; + } //end if + return ch; +} //end of the function BotLoadCharacterFromFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindCachedCharacter(char *charfile, float skill) +{ + int handle; + + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if ( !botcharacters[handle] ) continue; + if ( strcmp( botcharacters[handle]->filename, charfile ) == 0 && + (skill < 0 || fabs(botcharacters[handle]->skill - skill) < 0.01) ) + { + return handle; + } //end if + } //end for + return 0; +} //end of the function BotFindCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCachedCharacter(char *charfile, float skill, int reload) +{ + int handle, cachedhandle, intskill; + bot_character_t *ch = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + //find a free spot for a character + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (!botcharacters[handle]) break; + } //end for + if (handle > MAX_CLIENTS) return 0; + //try to load a cached character with the given skill + if (!reload) + { + cachedhandle = BotFindCachedCharacter(charfile, skill); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); + return cachedhandle; + } //end if + } //end else + // + intskill = (int) (skill + 0.5); + //try to load the character with the given skill + ch = BotLoadCharacterFromFile(charfile, intskill); + if (ch) + { + botcharacters[handle] = ch; + // + botimport.Print(PRT_MESSAGE, "loaded skill %d from %s\n", intskill, charfile); +#ifdef DEBUG + if (botDeveloper) + { + botimport.Print(PRT_MESSAGE, "skill %d loaded in %d msec from %s\n", intskill, Sys_MilliSeconds() - starttime, charfile); + } //end if +#endif //DEBUG + return handle; + } //end if + // + botimport.Print(PRT_WARNING, "couldn't find skill %d in %s\n", intskill, charfile); + // + if (!reload) + { + //try to load a cached default character with the given skill + cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, skill); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached default skill %d from %s\n", intskill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load the default character with the given skill + ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, intskill); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded default skill %d from %s\n", intskill, charfile); + return handle; + } //end if + // + if (!reload) + { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter(charfile, -1); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile(charfile, -1); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded skill %f from %s\n", ch->skill, charfile); + return handle; + } //end if + // + if (!reload) + { + //try to load a cached character with any skill + cachedhandle = BotFindCachedCharacter(DEFAULT_CHARACTER, -1); + if (cachedhandle) + { + botimport.Print(PRT_MESSAGE, "loaded cached default skill %f from %s\n", botcharacters[cachedhandle]->skill, charfile); + return cachedhandle; + } //end if + } //end if + //try to load a character with any skill + ch = BotLoadCharacterFromFile(DEFAULT_CHARACTER, -1); + if (ch) + { + botcharacters[handle] = ch; + botimport.Print(PRT_MESSAGE, "loaded default skill %f from %s\n", ch->skill, charfile); + return handle; + } //end if + // + botimport.Print(PRT_WARNING, "couldn't load any skill from %s\n", charfile); + //couldn't load any character + return 0; +} //end of the function BotLoadCachedCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacterSkill(char *charfile, float skill) +{ + int ch, defaultch; + + defaultch = BotLoadCachedCharacter(DEFAULT_CHARACTER, skill, qfalse); + ch = BotLoadCachedCharacter(charfile, skill, LibVarGetValue("bot_reloadcharacters")); + + if (defaultch && ch) + { + BotDefaultCharacteristics(botcharacters[ch], botcharacters[defaultch]); + } //end if + + return ch; +} //end of the function BotLoadCharacterSkill +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotInterpolateCharacters(int handle1, int handle2, float desiredskill) +{ + bot_character_t *ch1, *ch2, *out; + int i, handle; + float scale; + + ch1 = BotCharacterFromHandle(handle1); + ch2 = BotCharacterFromHandle(handle2); + if (!ch1 || !ch2) + return 0; + //find a free spot for a character + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (!botcharacters[handle]) break; + } //end for + if (handle > MAX_CLIENTS) return 0; + out = (bot_character_t *) GetClearedMemory(sizeof(bot_character_t) + + MAX_CHARACTERISTICS * sizeof(bot_characteristic_t)); + out->skill = desiredskill; + strcpy(out->filename, ch1->filename); + botcharacters[handle] = out; + + scale = (float) (desiredskill - ch1->skill) / (ch2->skill - ch1->skill); + for (i = 0; i < MAX_CHARACTERISTICS; i++) + { + // + if (ch1->c[i].type == CT_FLOAT && ch2->c[i].type == CT_FLOAT) + { + out->c[i].type = CT_FLOAT; + out->c[i].value._float = ch1->c[i].value._float + + (ch2->c[i].value._float - ch1->c[i].value._float) * scale; + } //end if + else if (ch1->c[i].type == CT_INTEGER) + { + out->c[i].type = CT_INTEGER; + out->c[i].value.integer = ch1->c[i].value.integer; + } //end else if + else if (ch1->c[i].type == CT_STRING) + { + out->c[i].type = CT_STRING; + out->c[i].value.string = (char *) GetMemory(strlen(ch1->c[i].value.string)+1); + strcpy(out->c[i].value.string, ch1->c[i].value.string); + } //end else if + } //end for + return handle; +} //end of the function BotInterpolateCharacters +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadCharacter(char *charfile, float skill) +{ + int firstskill, secondskill, handle; + + //make sure the skill is in the valid range + if (skill < 1.0) skill = 1.0; + else if (skill > 5.0) skill = 5.0; + //skill 1, 4 and 5 should be available in the character files + if (skill == 1.0 || skill == 4.0 || skill == 5.0) + { + return BotLoadCharacterSkill(charfile, skill); + } //end if + //check if there's a cached skill + handle = BotFindCachedCharacter(charfile, skill); + if (handle) + { + botimport.Print(PRT_MESSAGE, "loaded cached skill %f from %s\n", skill, charfile); + return handle; + } //end if + if (skill < 4.0) + { + //load skill 1 and 4 + firstskill = BotLoadCharacterSkill(charfile, 1); + if (!firstskill) return 0; + secondskill = BotLoadCharacterSkill(charfile, 4); + if (!secondskill) return firstskill; + } //end if + else + { + //load skill 4 and 5 + firstskill = BotLoadCharacterSkill(charfile, 4); + if (!firstskill) return 0; + secondskill = BotLoadCharacterSkill(charfile, 5); + if (!secondskill) return firstskill; + } //end else + //interpolate between the two skills + handle = BotInterpolateCharacters(firstskill, secondskill, skill); + if (!handle) return 0; + //write the character to the log file + BotDumpCharacter(botcharacters[handle]); + // + return handle; +} //end of the function BotLoadCharacter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int CheckCharacteristicIndex(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return qfalse; + if (index < 0 || index >= MAX_CHARACTERISTICS) + { + botimport.Print(PRT_ERROR, "characteristic %d does not exist\n", index); + return qfalse; + } //end if + if (!ch->c[index].type) + { + botimport.Print(PRT_ERROR, "characteristic %d is not initialized\n", index); + return qfalse; + } //end if + return qtrue; +} //end of the function CheckCharacteristicIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_Float(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) return 0; + //an integer will be converted to a float + if (ch->c[index].type == CT_INTEGER) + { + return (float) ch->c[index].value.integer; + } //end if + //floats are just returned + else if (ch->c[index].type == CT_FLOAT) + { + return ch->c[index].value._float; + } //end else if + //cannot convert a string pointer to a float + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a float\n", index); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Float +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float Characteristic_BFloat(int character, int index, float min, float max) +{ + float value; + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + if (min > max) + { + botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %f and %f\n", index, min, max); + return 0; + } //end if + value = Characteristic_Float(character, index); + if (value < min) return min; + if (value > max) return max; + return value; +} //end of the function Characteristic_BFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_Integer(int character, int index) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) return 0; + //an integer will just be returned + if (ch->c[index].type == CT_INTEGER) + { + return ch->c[index].value.integer; + } //end if + //floats are casted to integers + else if (ch->c[index].type == CT_FLOAT) + { + return (int) ch->c[index].value._float; + } //end else if + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a integer\n", index); + return 0; + } //end else if +// return 0; +} //end of the function Characteristic_Integer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Characteristic_BInteger(int character, int index, int min, int max) +{ + int value; + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return 0; + if (min > max) + { + botimport.Print(PRT_ERROR, "cannot bound characteristic %d between %d and %d\n", index, min, max); + return 0; + } //end if + value = Characteristic_Integer(character, index); + if (value < min) return min; + if (value > max) return max; + return value; +} //end of the function Characteristic_BInteger +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Characteristic_String(int character, int index, char *buf, int size) +{ + bot_character_t *ch; + + ch = BotCharacterFromHandle(character); + if (!ch) return; + //check if the index is in range + if (!CheckCharacteristicIndex(character, index)) return; + //an integer will be converted to a float + if (ch->c[index].type == CT_STRING) + { + strncpy(buf, ch->c[index].value.string, size-1); + buf[size-1] = '\0'; + return; + } //end if + else + { + botimport.Print(PRT_ERROR, "characteristic %d is not a string\n", index); + return; + } //end else if + return; +} //end of the function Characteristic_String +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownCharacters(void) +{ + int handle; + + for (handle = 1; handle <= MAX_CLIENTS; handle++) + { + if (botcharacters[handle]) + { + BotFreeCharacter2(handle); + } //end if + } //end for +} //end of the function BotShutdownCharacters + diff --git a/code/botlib/be_ai_char.h b/code/botlib/be_ai_char.h new file mode 100644 index 0000000..719d68f --- /dev/null +++ b/code/botlib/be_ai_char.h @@ -0,0 +1,48 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * 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/code/botlib/be_ai_chat.c b/code/botlib/be_ai_chat.c new file mode 100644 index 0000000..10950e9 --- /dev/null +++ b/code/botlib/be_ai_chat.c @@ -0,0 +1,3039 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_chat.c + * + * desc: bot chat AI + * + * $Archive: /MissionPack/code/botlib/be_ai_chat.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "l_log.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ea.h" +#include "be_ai_chat.h" + + +//escape character +#define ESCAPE_CHAR 0x01 //'_' +// +// "hi ", people, " ", 0, " entered the game" +//becomes: +// "hi _rpeople_ _v0_ entered the game" +// + +//match piece types +#define MT_VARIABLE 1 //variable match piece +#define MT_STRING 2 //string match piece +//reply chat key flags +#define RCKFL_AND 1 //key must be present +#define RCKFL_NOT 2 //key must be absent +#define RCKFL_NAME 4 //name of bot must be present +#define RCKFL_STRING 8 //key is a string +#define RCKFL_VARIABLES 16 //key is a match template +#define RCKFL_BOTNAMES 32 //key is a series of botnames +#define RCKFL_GENDERFEMALE 64 //bot must be female +#define RCKFL_GENDERMALE 128 //bot must be male +#define RCKFL_GENDERLESS 256 //bot must be genderless +//time to ignore a chat message after using it +#define CHATMESSAGE_RECENTTIME 20 + +//the actuall chat messages +typedef struct bot_chatmessage_s +{ + char *chatmessage; //chat message string + float time; //last time used + struct bot_chatmessage_s *next; //next chat message in a list +} bot_chatmessage_t; +//bot chat type with chat lines +typedef struct bot_chattype_s +{ + char name[MAX_CHATTYPE_NAME]; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_chattype_s *next; +} bot_chattype_t; +//bot chat lines +typedef struct bot_chat_s +{ + bot_chattype_t *types; +} bot_chat_t; + +//random string +typedef struct bot_randomstring_s +{ + char *string; + struct bot_randomstring_s *next; +} bot_randomstring_t; +//list with random strings +typedef struct bot_randomlist_s +{ + char *string; + int numstrings; + bot_randomstring_t *firstrandomstring; + struct bot_randomlist_s *next; +} bot_randomlist_t; + +//synonym +typedef struct bot_synonym_s +{ + char *string; + float weight; + struct bot_synonym_s *next; +} bot_synonym_t; +//list with synonyms +typedef struct bot_synonymlist_s +{ + unsigned long int context; + float totalweight; + bot_synonym_t *firstsynonym; + struct bot_synonymlist_s *next; +} bot_synonymlist_t; + +//fixed match string +typedef struct bot_matchstring_s +{ + char *string; + struct bot_matchstring_s *next; +} bot_matchstring_t; + +//piece of a match template +typedef struct bot_matchpiece_s +{ + int type; + bot_matchstring_t *firststring; + int variable; + struct bot_matchpiece_s *next; +} bot_matchpiece_t; +//match template +typedef struct bot_matchtemplate_s +{ + unsigned long int context; + int type; + int subtype; + bot_matchpiece_t *first; + struct bot_matchtemplate_s *next; +} bot_matchtemplate_t; + +//reply chat key +typedef struct bot_replychatkey_s +{ + int flags; + char *string; + bot_matchpiece_t *match; + struct bot_replychatkey_s *next; +} bot_replychatkey_t; +//reply chat +typedef struct bot_replychat_s +{ + bot_replychatkey_t *keys; + float priority; + int numchatmessages; + bot_chatmessage_t *firstchatmessage; + struct bot_replychat_s *next; +} bot_replychat_t; + +//string list +typedef struct bot_stringlist_s +{ + char *string; + struct bot_stringlist_s *next; +} bot_stringlist_t; + +//chat state of a bot +typedef struct bot_chatstate_s +{ + int gender; //0=it, 1=female, 2=male + int client; //client number + char name[32]; //name of the bot + char chatmessage[MAX_MESSAGE_SIZE]; + int handle; + //the console messages visible to the bot + bot_consolemessage_t *firstmessage; //first message is the first typed message + bot_consolemessage_t *lastmessage; //last message is the last typed message, bottom of console + //number of console messages stored in the state + int numconsolemessages; + //the bot chat lines + bot_chat_t *chat; +} bot_chatstate_t; + +typedef struct { + bot_chat_t *chat; + char filename[MAX_QPATH]; + char chatname[MAX_QPATH]; +} bot_ichatdata_t; + +bot_ichatdata_t *ichatdata[MAX_CLIENTS]; + +bot_chatstate_t *botchatstates[MAX_CLIENTS+1]; +//console message heap +bot_consolemessage_t *consolemessageheap = NULL; +bot_consolemessage_t *freeconsolemessages = NULL; +//list with match strings +bot_matchtemplate_t *matchtemplates = NULL; +//list with synonyms +bot_synonymlist_t *synonyms = NULL; +//list with random strings +bot_randomlist_t *randomstrings = NULL; +//reply chats +bot_replychat_t *replychats = NULL; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_chatstate_t *BotChatStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botchatstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); + return NULL; + } //end if + return botchatstates[handle]; +} //end of the function BotChatStateFromHandle +//=========================================================================== +// initialize the heap with unused console messages +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitConsoleMessageHeap(void) +{ + int i, max_messages; + + if (consolemessageheap) FreeMemory(consolemessageheap); + // + max_messages = (int) LibVarValue("max_messages", "1024"); + consolemessageheap = (bot_consolemessage_t *) GetClearedHunkMemory(max_messages * + sizeof(bot_consolemessage_t)); + consolemessageheap[0].prev = NULL; + consolemessageheap[0].next = &consolemessageheap[1]; + for (i = 1; i < max_messages-1; i++) + { + consolemessageheap[i].prev = &consolemessageheap[i - 1]; + consolemessageheap[i].next = &consolemessageheap[i + 1]; + } //end for + consolemessageheap[max_messages-1].prev = &consolemessageheap[max_messages-2]; + consolemessageheap[max_messages-1].next = NULL; + //pointer to the free console messages + freeconsolemessages = consolemessageheap; +} //end of the function InitConsoleMessageHeap +//=========================================================================== +// allocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_consolemessage_t *AllocConsoleMessage(void) +{ + bot_consolemessage_t *message; + message = freeconsolemessages; + if (freeconsolemessages) freeconsolemessages = freeconsolemessages->next; + if (freeconsolemessages) freeconsolemessages->prev = NULL; + return message; +} //end of the function AllocConsoleMessage +//=========================================================================== +// deallocate one console message from the heap +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeConsoleMessage(bot_consolemessage_t *message) +{ + if (freeconsolemessages) freeconsolemessages->prev = message; + message->prev = NULL; + message->next = freeconsolemessages; + freeconsolemessages = message; +} //end of the function FreeConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveConsoleMessage(int chatstate, int handle) +{ + bot_consolemessage_t *m, *nextm; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + for (m = cs->firstmessage; m; m = nextm) + { + nextm = m->next; + if (m->handle == handle) + { + if (m->next) m->next->prev = m->prev; + else cs->lastmessage = m->prev; + if (m->prev) m->prev->next = m->next; + else cs->firstmessage = m->next; + + FreeConsoleMessage(m); + cs->numconsolemessages--; + break; + } //end if + } //end for +} //end of the function BotRemoveConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotQueueConsoleMessage(int chatstate, int type, char *message) +{ + bot_consolemessage_t *m; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + m = AllocConsoleMessage(); + if (!m) + { + botimport.Print(PRT_ERROR, "empty console message heap\n"); + return; + } //end if + cs->handle++; + if (cs->handle <= 0 || cs->handle > 8192) cs->handle = 1; + m->handle = cs->handle; + m->time = AAS_Time(); + m->type = type; + strncpy(m->message, message, MAX_MESSAGE_SIZE); + m->next = NULL; + if (cs->lastmessage) + { + cs->lastmessage->next = m; + m->prev = cs->lastmessage; + cs->lastmessage = m; + } //end if + else + { + cs->lastmessage = m; + cs->firstmessage = m; + m->prev = NULL; + } //end if + cs->numconsolemessages++; +} //end of the function BotQueueConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNextConsoleMessage(int chatstate, bot_consolemessage_t *cm) +{ + bot_chatstate_t *cs; + bot_consolemessage_t *firstmsg; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + if ((firstmsg = cs->firstmessage)) + { + cm->handle = firstmsg->handle; + cm->time = firstmsg->time; + cm->type = firstmsg->type; + Q_strncpyz(cm->message, firstmsg->message, + sizeof(cm->message)); + + /* We omit setting the two pointers in cm because pointer + * size in the VM differs between the size in the engine on + * 64 bit machines, which would lead to a buffer overflow if + * this functions is called from the VM. The pointers are + * of no interest to functions calling + * BotNextConsoleMessage anyways. + */ + + return cm->handle; + } //end if + return 0; +} //end of the function BotConsoleMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumConsoleMessages(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + return cs->numconsolemessages; +} //end of the function BotNumConsoleMessages +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int IsWhiteSpace(char c) +{ + if ((c >= 'a' && c <= 'z') + || (c >= 'A' && c <= 'Z') + || (c >= '0' && c <= '9') + || c == '(' || c == ')' + || c == '?' || c == ':' + || c == '\''|| c == '/' + || c == ',' || c == '.' + || c == '[' || c == ']' + || c == '-' || c == '_' + || c == '+' || c == '=') return qfalse; + return qtrue; +} //end of the function IsWhiteSpace +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveTildes(char *message) +{ + int i; + + //remove all tildes from the chat message + for (i = 0; message[i]; i++) + { + if (message[i] == '~') + { + memmove(&message[i], &message[i+1], strlen(&message[i+1])+1); + } //end if + } //end for +} //end of the function BotRemoveTildes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnifyWhiteSpaces(char *string) +{ + char *ptr, *oldptr; + + for (ptr = oldptr = string; *ptr; oldptr = ptr) + { + while(*ptr && IsWhiteSpace(*ptr)) ptr++; + if (ptr > oldptr) + { + //if not at the start and not at the end of the string + //write only one space + if (oldptr > string && *ptr) *oldptr++ = ' '; + //remove all other white spaces + if (ptr > oldptr) memmove(oldptr, ptr, strlen(ptr)+1); + } //end if + while(*ptr && !IsWhiteSpace(*ptr)) ptr++; + } //end while +} //end of the function UnifyWhiteSpaces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringContains(char *str1, char *str2, int casesensitive) +{ + int len, i, j, index; + + if (str1 == NULL || str2 == NULL) return -1; + + len = strlen(str1) - strlen(str2); + index = 0; + for (i = 0; i <= len; i++, str1++, index++) + { + for (j = 0; str2[j]; j++) + { + if (casesensitive) + { + if (str1[j] != str2[j]) break; + } //end if + else + { + if (toupper(str1[j]) != toupper(str2[j])) break; + } //end else + } //end for + if (!str2[j]) return index; + } //end for + return -1; +} //end of the function StringContains +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *StringContainsWord(char *str1, char *str2, int casesensitive) +{ + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) + { + //if not at the start of the string + if (i) + { + //skip to the start of the next word + while(*str1 && *str1 != ' ' && *str1 != '.' && *str1 != ',' && *str1 != '!') str1++; + if (!*str1) break; + str1++; + } //end for + //compare the word + for (j = 0; str2[j]; j++) + { + if (casesensitive) + { + if (str1[j] != str2[j]) break; + } //end if + else + { + if (toupper(str1[j]) != toupper(str2[j])) break; + } //end else + } //end for + //if there was a word match + if (!str2[j]) + { + //if the first string has an end of word + if (!str1[j] || str1[j] == ' ' || str1[j] == '.' || str1[j] == ',' || str1[j] == '!') return str1; + } //end if + } //end for + return NULL; +} //end of the function StringContainsWord +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void StringReplaceWords(char *string, char *synonym, char *replacement) +{ + char *str, *str2; + + //find the synonym in the string + str = StringContainsWord(string, synonym, qfalse); + //if the synonym occured in the string + while(str) + { + //if the synonym isn't part of the replacement which is already in the string + //usefull for abreviations + str2 = StringContainsWord(string, replacement, qfalse); + while(str2) + { + if (str2 <= str && str < str2 + strlen(replacement)) break; + str2 = StringContainsWord(str2+1, replacement, qfalse); + } //end while + if (!str2) + { + memmove(str + strlen(replacement), str+strlen(synonym), strlen(str+strlen(synonym))+1); + //append the synonum replacement + Com_Memcpy(str, replacement, strlen(replacement)); + } //end if + //find the next synonym in the string + str = StringContainsWord(str+strlen(replacement), synonym, qfalse); + } //end if +} //end of the function StringReplaceWords +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpSynonymList(bot_synonymlist_t *synlist) +{ + FILE *fp; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + fp = Log_FilePointer(); + if (!fp) return; + for (syn = synlist; syn; syn = syn->next) + { + fprintf(fp, "%ld : [", syn->context); + for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) + { + fprintf(fp, "(\"%s\", %1.2f)", synonym->string, synonym->weight); + if (synonym->next) fprintf(fp, ", "); + } //end for + fprintf(fp, "]\n"); + } //end for +} //end of the function BotDumpSynonymList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_synonymlist_t *BotLoadSynonyms(char *filename) +{ + int pass, size, contextlevel, numsynonyms; + unsigned long int context, contextstack[32]; + char *ptr = NULL; + source_t *source; + token_t token; + bot_synonymlist_t *synlist, *lastsyn, *syn; + bot_synonym_t *synonym, *lastsynonym; + + size = 0; + synlist = NULL; //make compiler happy + syn = NULL; //make compiler happy + synonym = NULL; //make compiler happy + //the synonyms are parsed in two phases + for (pass = 0; pass < 2; pass++) + { + // + if (pass && size) ptr = (char *) GetClearedHunkMemory(size); + // + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + context = 0; + contextlevel = 0; + synlist = NULL; //list synonyms + lastsyn = NULL; //last synonym in the list + // + while(PC_ReadToken(source, &token)) + { + if (token.type == TT_NUMBER) + { + context |= token.intvalue; + contextstack[contextlevel] = token.intvalue; + contextlevel++; + if (contextlevel >= 32) + { + SourceError(source, "more than 32 context levels"); + FreeSource(source); + return NULL; + } //end if + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + } //end if + else if (token.type == TT_PUNCTUATION) + { + if (!strcmp(token.string, "}")) + { + contextlevel--; + if (contextlevel < 0) + { + SourceError(source, "too many }"); + FreeSource(source); + return NULL; + } //end if + context &= ~contextstack[contextlevel]; + } //end if + else if (!strcmp(token.string, "[")) + { + size += sizeof(bot_synonymlist_t); + if (pass) + { + syn = (bot_synonymlist_t *) ptr; + ptr += sizeof(bot_synonymlist_t); + syn->context = context; + syn->firstsynonym = NULL; + syn->next = NULL; + if (lastsyn) lastsyn->next = syn; + else synlist = syn; + lastsyn = syn; + } //end if + numsynonyms = 0; + lastsynonym = NULL; + while(1) + { + size_t len; + if (!PC_ExpectTokenString(source, "(") || + !PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (strlen(token.string) <= 0) + { + SourceError(source, "empty string"); + FreeSource(source); + return NULL; + } //end if + len = strlen(token.string) + 1; + len = PAD(len, sizeof(long)); + size += sizeof(bot_synonym_t) + len; + if (pass) + { + synonym = (bot_synonym_t *) ptr; + ptr += sizeof(bot_synonym_t); + synonym->string = ptr; + ptr += len; + strcpy(synonym->string, token.string); + // + if (lastsynonym) lastsynonym->next = synonym; + else syn->firstsynonym = synonym; + lastsynonym = synonym; + } //end if + numsynonyms++; + if (!PC_ExpectTokenString(source, ",") || + !PC_ExpectTokenType(source, TT_NUMBER, 0, &token) || + !PC_ExpectTokenString(source, ")")) + { + FreeSource(source); + return NULL; + } //end if + if (pass) + { + synonym->weight = token.floatvalue; + syn->totalweight += synonym->weight; + } //end if + if (PC_CheckTokenString(source, "]")) break; + if (!PC_ExpectTokenString(source, ",")) + { + FreeSource(source); + return NULL; + } //end if + } //end while + if (numsynonyms < 2) + { + SourceError(source, "synonym must have at least two entries\n"); + FreeSource(source); + return NULL; + } //end if + } //end else + else + { + SourceError(source, "unexpected %s", token.string); + FreeSource(source); + return NULL; + } //end if + } //end else if + } //end while + // + FreeSource(source); + // + if (contextlevel > 0) + { + SourceError(source, "missing }"); + return NULL; + } //end if + } //end for + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // + //BotDumpSynonymList(synlist); + // + return synlist; +} //end of the function BotLoadSynonyms +//=========================================================================== +// replace all the synonyms in the string +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceSynonyms(char *string, unsigned long int context) +{ + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) continue; + for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) + { + StringReplaceWords(string, synonym->string, syn->firstsynonym->string); + } //end for + } //end for +} //end of the function BotReplaceSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceWeightedSynonyms(char *string, unsigned long int context) +{ + bot_synonymlist_t *syn; + bot_synonym_t *synonym, *replacement; + float weight, curweight; + + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) continue; + //choose a weighted random replacement synonym + weight = random() * syn->totalweight; + if (!weight) continue; + curweight = 0; + for (replacement = syn->firstsynonym; replacement; replacement = replacement->next) + { + curweight += replacement->weight; + if (weight < curweight) break; + } //end for + if (!replacement) continue; + //replace all synonyms with the replacement + for (synonym = syn->firstsynonym; synonym; synonym = synonym->next) + { + if (synonym == replacement) continue; + StringReplaceWords(string, synonym->string, replacement->string); + } //end for + } //end for +} //end of the function BotReplaceWeightedSynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotReplaceReplySynonyms(char *string, unsigned long int context) +{ + char *str1, *str2, *replacement; + bot_synonymlist_t *syn; + bot_synonym_t *synonym; + + for (str1 = string; *str1; ) + { + //go to the start of the next word + while(*str1 && *str1 <= ' ') str1++; + if (!*str1) break; + // + for (syn = synonyms; syn; syn = syn->next) + { + if (!(syn->context & context)) continue; + for (synonym = syn->firstsynonym->next; synonym; synonym = synonym->next) + { + //if the synonym is not at the front of the string continue + str2 = StringContainsWord(str1, synonym->string, qfalse); + if (!str2 || str2 != str1) continue; + // + replacement = syn->firstsynonym->string; + //if the replacement IS in front of the string continue + str2 = StringContainsWord(str1, replacement, qfalse); + if (str2 && str2 == str1) continue; + // + memmove(str1 + strlen(replacement), str1+strlen(synonym->string), + strlen(str1+strlen(synonym->string)) + 1); + //append the synonum replacement + Com_Memcpy(str1, replacement, strlen(replacement)); + // + break; + } //end for + //if a synonym has been replaced + if (synonym) break; + } //end for + //skip over this word + while(*str1 && *str1 > ' ') str1++; + if (!*str1) break; + } //end while +} //end of the function BotReplaceReplySynonyms +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatMessage(source_t *source, char *chatmessagestring) +{ + char *ptr; + token_t token; + + ptr = chatmessagestring; + *ptr = 0; + // + while(1) + { + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + //fixed string + if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + if (strlen(ptr) + strlen(token.string) + 1 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + strcat(ptr, token.string); + } //end else if + //variable string + else if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) + { + if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + sprintf(&ptr[strlen(ptr)], "%cv%ld%c", ESCAPE_CHAR, token.intvalue, ESCAPE_CHAR); + } //end if + //random string + else if (token.type == TT_NAME) + { + if (strlen(ptr) + 7 > MAX_MESSAGE_SIZE) + { + SourceError(source, "chat message too long\n"); + return qfalse; + } //end if + sprintf(&ptr[strlen(ptr)], "%cr%s%c", ESCAPE_CHAR, token.string, ESCAPE_CHAR); + } //end else if + else + { + SourceError(source, "unknown message component %s\n", token.string); + return qfalse; + } //end else + if (PC_CheckTokenString(source, ";")) break; + if (!PC_ExpectTokenString(source, ",")) return qfalse; + } //end while + // + return qtrue; +} //end of the function BotLoadChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpRandomStringList(bot_randomlist_t *randomlist) +{ + FILE *fp; + bot_randomlist_t *random; + bot_randomstring_t *rs; + + fp = Log_FilePointer(); + if (!fp) return; + for (random = randomlist; random; random = random->next) + { + fprintf(fp, "%s = {", random->string); + for (rs = random->firstrandomstring; rs; rs = rs->next) + { + fprintf(fp, "\"%s\"", rs->string); + if (rs->next) fprintf(fp, ", "); + else fprintf(fp, "}\n"); + } //end for + } //end for +} //end of the function BotDumpRandomStringList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_randomlist_t *BotLoadRandomStrings(char *filename) +{ + int pass, size; + char *ptr = NULL, chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_randomlist_t *randomlist, *lastrandom, *random; + bot_randomstring_t *randomstring; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + size = 0; + randomlist = NULL; + random = NULL; + //the synonyms are parsed in two phases + for (pass = 0; pass < 2; pass++) + { + // + if (pass && size) ptr = (char *) GetClearedHunkMemory(size); + // + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + randomlist = NULL; //list + lastrandom = NULL; //last + // + while(PC_ReadToken(source, &token)) + { + size_t len; + if (token.type != TT_NAME) + { + SourceError(source, "unknown random %s", token.string); + FreeSource(source); + return NULL; + } //end if + len = strlen(token.string) + 1; + len = PAD(len, sizeof(long)); + size += sizeof(bot_randomlist_t) + len; + if (pass) + { + random = (bot_randomlist_t *) ptr; + ptr += sizeof(bot_randomlist_t); + random->string = ptr; + ptr += len; + strcpy(random->string, token.string); + random->firstrandomstring = NULL; + random->numstrings = 0; + // + if (lastrandom) lastrandom->next = random; + else randomlist = random; + lastrandom = random; + } //end if + if (!PC_ExpectTokenString(source, "=") || + !PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + while(!PC_CheckTokenString(source, "}")) + { + size_t len; + if (!BotLoadChatMessage(source, chatmessagestring)) + { + FreeSource(source); + return NULL; + } //end if + len = strlen(chatmessagestring) + 1; + len = PAD(len, sizeof(long)); + size += sizeof(bot_randomstring_t) + len; + if (pass) + { + randomstring = (bot_randomstring_t *) ptr; + ptr += sizeof(bot_randomstring_t); + randomstring->string = ptr; + ptr += len; + strcpy(randomstring->string, chatmessagestring); + // + random->numstrings++; + randomstring->next = random->firstrandomstring; + random->firstrandomstring = randomstring; + } //end if + } //end while + } //end while + //free the source after one pass + FreeSource(source); + } //end for + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "random strings %d msec\n", Sys_MilliSeconds() - starttime); + //BotDumpRandomStringList(randomlist); +#endif //DEBUG + // + return randomlist; +} //end of the function BotLoadRandomStrings +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *RandomString(char *name) +{ + bot_randomlist_t *random; + bot_randomstring_t *rs; + int i; + + for (random = randomstrings; random; random = random->next) + { + if (!strcmp(random->string, name)) + { + i = random() * random->numstrings; + for (rs = random->firstrandomstring; rs; rs = rs->next) + { + if (--i < 0) break; + } //end for + if (rs) + { + return rs->string; + } //end if + } //end for + } //end for + return NULL; +} //end of the function RandomString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpMatchTemplates(bot_matchtemplate_t *matches) +{ + FILE *fp; + bot_matchtemplate_t *mt; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + fp = Log_FilePointer(); + if (!fp) return; + for (mt = matches; mt; mt = mt->next) + { + fprintf(fp, "{ " ); + for (mp = mt->first; mp; mp = mp->next) + { + if (mp->type == MT_STRING) + { + for (ms = mp->firststring; ms; ms = ms->next) + { + fprintf(fp, "\"%s\"", ms->string); + if (ms->next) fprintf(fp, "|"); + } //end for + } //end if + else if (mp->type == MT_VARIABLE) + { + fprintf(fp, "%d", mp->variable); + } //end else if + if (mp->next) fprintf(fp, ", "); + } //end for + fprintf(fp, " = (%d, %d);}\n", mt->type, mt->subtype); + } //end for +} //end of the function BotDumpMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchPieces(bot_matchpiece_t *matchpieces) +{ + bot_matchpiece_t *mp, *nextmp; + bot_matchstring_t *ms, *nextms; + + for (mp = matchpieces; mp; mp = nextmp) + { + nextmp = mp->next; + if (mp->type == MT_STRING) + { + for (ms = mp->firststring; ms; ms = nextms) + { + nextms = ms->next; + FreeMemory(ms); + } //end for + } //end if + FreeMemory(mp); + } //end for +} //end of the function BotFreeMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchpiece_t *BotLoadMatchPieces(source_t *source, char *endtoken) +{ + int lastwasvariable, emptystring; + token_t token; + bot_matchpiece_t *matchpiece, *firstpiece, *lastpiece; + bot_matchstring_t *matchstring, *lastmatchstring; + + firstpiece = NULL; + lastpiece = NULL; + // + lastwasvariable = qfalse; + // + while(PC_ReadToken(source, &token)) + { + if (token.type == TT_NUMBER && (token.subtype & TT_INTEGER)) + { + if (token.intvalue < 0 || token.intvalue >= MAX_MATCHVARIABLES) + { + SourceError(source, "can't have more than %d match variables\n", MAX_MATCHVARIABLES); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + if (lastwasvariable) + { + SourceError(source, "not allowed to have adjacent variables\n"); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + lastwasvariable = qtrue; + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); + matchpiece->type = MT_VARIABLE; + matchpiece->variable = token.intvalue; + matchpiece->next = NULL; + if (lastpiece) lastpiece->next = matchpiece; + else firstpiece = matchpiece; + lastpiece = matchpiece; + } //end if + else if (token.type == TT_STRING) + { + // + matchpiece = (bot_matchpiece_t *) GetClearedHunkMemory(sizeof(bot_matchpiece_t)); + matchpiece->firststring = NULL; + matchpiece->type = MT_STRING; + matchpiece->variable = 0; + matchpiece->next = NULL; + if (lastpiece) lastpiece->next = matchpiece; + else firstpiece = matchpiece; + lastpiece = matchpiece; + // + lastmatchstring = NULL; + emptystring = qfalse; + // + do + { + if (matchpiece->firststring) + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + } //end if + StripDoubleQuotes(token.string); + matchstring = (bot_matchstring_t *) GetClearedHunkMemory(sizeof(bot_matchstring_t) + strlen(token.string) + 1); + matchstring->string = (char *) matchstring + sizeof(bot_matchstring_t); + strcpy(matchstring->string, token.string); + if (!strlen(token.string)) emptystring = qtrue; + matchstring->next = NULL; + if (lastmatchstring) lastmatchstring->next = matchstring; + else matchpiece->firststring = matchstring; + lastmatchstring = matchstring; + } while(PC_CheckTokenString(source, "|")); + //if there was no empty string found + if (!emptystring) lastwasvariable = qfalse; + } //end if + else + { + SourceError(source, "invalid token %s\n", token.string); + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end else + if (PC_CheckTokenString(source, endtoken)) break; + if (!PC_ExpectTokenString(source, ",")) + { + FreeSource(source); + BotFreeMatchPieces(firstpiece); + return NULL; + } //end if + } //end while + return firstpiece; +} //end of the function BotLoadMatchPieces +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeMatchTemplates(bot_matchtemplate_t *mt) +{ + bot_matchtemplate_t *nextmt; + + for (; mt; mt = nextmt) + { + nextmt = mt->next; + BotFreeMatchPieces(mt->first); + FreeMemory(mt); + } //end for +} //end of the function BotFreeMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_matchtemplate_t *BotLoadMatchTemplates(char *matchfile) +{ + source_t *source; + token_t token; + bot_matchtemplate_t *matchtemplate, *matches, *lastmatch; + unsigned long int context; + + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(matchfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", matchfile); + return NULL; + } //end if + // + matches = NULL; //list with matches + lastmatch = NULL; //last match in the list + + while(PC_ReadToken(source, &token)) + { + if (token.type != TT_NUMBER || !(token.subtype & TT_INTEGER)) + { + SourceError(source, "expected integer, found %s\n", token.string); + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + //the context + context = token.intvalue; + // + if (!PC_ExpectTokenString(source, "{")) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + // + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "}")) break; + // + PC_UnreadLastToken(source); + // + matchtemplate = (bot_matchtemplate_t *) GetClearedHunkMemory(sizeof(bot_matchtemplate_t)); + matchtemplate->context = context; + matchtemplate->next = NULL; + //add the match template to the list + if (lastmatch) lastmatch->next = matchtemplate; + else matches = matchtemplate; + lastmatch = matchtemplate; + //load the match template + matchtemplate->first = BotLoadMatchPieces(source, "="); + if (!matchtemplate->first) + { + BotFreeMatchTemplates(matches); + return NULL; + } //end if + //read the match type + if (!PC_ExpectTokenString(source, "(") || + !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + matchtemplate->type = token.intvalue; + //read the match subtype + if (!PC_ExpectTokenString(source, ",") || + !PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + matchtemplate->subtype = token.intvalue; + //read trailing punctuations + if (!PC_ExpectTokenString(source, ")") || + !PC_ExpectTokenString(source, ";")) + { + BotFreeMatchTemplates(matches); + FreeSource(source); + return NULL; + } //end if + } //end while + } //end while + //free the source + FreeSource(source); + botimport.Print(PRT_MESSAGE, "loaded %s\n", matchfile); + // + //BotDumpMatchTemplates(matches); + // + return matches; +} //end of the function BotLoadMatchTemplates +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int StringsMatch(bot_matchpiece_t *pieces, bot_match_t *match) +{ + int lastvariable, index; + char *strptr, *newstrptr; + bot_matchpiece_t *mp; + bot_matchstring_t *ms; + + //no last variable + lastvariable = -1; + //pointer to the string to compare the match string with + strptr = match->string; + //Log_Write("match: %s", strptr); + //compare the string with the current match string + for (mp = pieces; mp; mp = mp->next) + { + //if it is a piece of string + if (mp->type == MT_STRING) + { + newstrptr = NULL; + for (ms = mp->firststring; ms; ms = ms->next) + { + if (!strlen(ms->string)) + { + newstrptr = strptr; + break; + } //end if + //Log_Write("MT_STRING: %s", mp->string); + index = StringContains(strptr, ms->string, qfalse); + if (index >= 0) + { + newstrptr = strptr + index; + if (lastvariable >= 0) + { + match->variables[lastvariable].length = + (newstrptr - match->string) - match->variables[lastvariable].offset; + //newstrptr - match->variables[lastvariable].ptr; + lastvariable = -1; + break; + } //end if + else if (index == 0) + { + break; + } //end else + newstrptr = NULL; + } //end if + } //end for + if (!newstrptr) return qfalse; + strptr = newstrptr + strlen(ms->string); + } //end if + //if it is a variable piece of string + else if (mp->type == MT_VARIABLE) + { + //Log_Write("MT_VARIABLE"); + match->variables[mp->variable].offset = strptr - match->string; + lastvariable = mp->variable; + } //end else if + } //end for + //if a match was found + if (!mp && (lastvariable >= 0 || !strlen(strptr))) + { + //if the last piece was a variable string + if (lastvariable >= 0) + { + assert( match->variables[lastvariable].offset >= 0 ); + match->variables[lastvariable].length = + strlen(&match->string[ (int) match->variables[lastvariable].offset]); + } //end if + return qtrue; + } //end if + return qfalse; +} //end of the function StringsMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFindMatch(char *str, bot_match_t *match, unsigned long int context) +{ + int i; + bot_matchtemplate_t *ms; + + strncpy(match->string, str, MAX_MESSAGE_SIZE); + //remove any trailing enters + while(strlen(match->string) && + match->string[strlen(match->string)-1] == '\n') + { + match->string[strlen(match->string)-1] = '\0'; + } //end while + //compare the string with all the match strings + for (ms = matchtemplates; ms; ms = ms->next) + { + if (!(ms->context & context)) continue; + //reset the match variable offsets + for (i = 0; i < MAX_MATCHVARIABLES; i++) match->variables[i].offset = -1; + // + if (StringsMatch(ms->first, match)) + { + match->type = ms->type; + match->subtype = ms->subtype; + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotFindMatch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMatchVariable(bot_match_t *match, int variable, char *buf, int size) +{ + if (variable < 0 || variable >= MAX_MATCHVARIABLES) + { + botimport.Print(PRT_FATAL, "BotMatchVariable: variable out of range\n"); + strcpy(buf, ""); + return; + } //end if + + if (match->variables[variable].offset >= 0) + { + if (match->variables[variable].length < size) + size = match->variables[variable].length+1; + assert( match->variables[variable].offset >= 0 ); + strncpy(buf, &match->string[ (int) match->variables[variable].offset], size-1); + buf[size-1] = '\0'; + } //end if + else + { + strcpy(buf, ""); + } //end else + return; +} //end of the function BotMatchVariable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotFindStringInList(bot_stringlist_t *list, char *string) +{ + bot_stringlist_t *s; + + for (s = list; s; s = s->next) + { + if (!strcmp(s->string, string)) return s; + } //end for + return NULL; +} //end of the function BotFindStringInList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_stringlist_t *BotCheckChatMessageIntegrety(char *message, bot_stringlist_t *stringlist) +{ + int i; + char *msgptr; + char temp[MAX_MESSAGE_SIZE]; + bot_stringlist_t *s; + + msgptr = message; + // + while(*msgptr) + { + if (*msgptr == ESCAPE_CHAR) + { + msgptr++; + switch(*msgptr) + { + case 'v': //variable + { + //step over the 'v' + msgptr++; + while(*msgptr && *msgptr != ESCAPE_CHAR) msgptr++; + //step over the trailing escape char + if (*msgptr) msgptr++; + break; + } //end case + case 'r': //random + { + //step over the 'r' + msgptr++; + for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if (*msgptr) msgptr++; + //find the random keyword + if (!RandomString(temp)) + { + if (!BotFindStringInList(stringlist, temp)) + { + Log_Write("%s = {\"%s\"} //MISSING RANDOM\r\n", temp, temp); + s = GetClearedMemory(sizeof(bot_stringlist_t) + strlen(temp) + 1); + s->string = (char *) s + sizeof(bot_stringlist_t); + strcpy(s->string, temp); + s->next = stringlist; + stringlist = s; + } //end if + } //end if + break; + } //end case + default: + { + botimport.Print(PRT_FATAL, "BotCheckChatMessageIntegrety: message \"%s\" invalid escape char\n", message); + break; + } //end default + } //end switch + } //end if + else + { + msgptr++; + } //end else + } //end while + return stringlist; +} //end of the function BotCheckChatMessageIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckInitialChatIntegrety(bot_chat_t *chat) +{ + bot_chattype_t *t; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for (t = chat->types; t; t = t->next) + { + for (cm = t->firstchatmessage; cm; cm = cm->next) + { + stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); + } //end for + } //end for + for (s = stringlist; s; s = nexts) + { + nexts = s->next; + FreeMemory(s); + } //end for +} //end of the function BotCheckInitialChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckReplyChatIntegrety(bot_replychat_t *replychat) +{ + bot_replychat_t *rp; + bot_chatmessage_t *cm; + bot_stringlist_t *stringlist, *s, *nexts; + + stringlist = NULL; + for (rp = replychat; rp; rp = rp->next) + { + for (cm = rp->firstchatmessage; cm; cm = cm->next) + { + stringlist = BotCheckChatMessageIntegrety(cm->chatmessage, stringlist); + } //end for + } //end for + for (s = stringlist; s; s = nexts) + { + nexts = s->next; + FreeMemory(s); + } //end for +} //end of the function BotCheckReplyChatIntegrety +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpReplyChat(bot_replychat_t *replychat) +{ + FILE *fp; + bot_replychat_t *rp; + bot_replychatkey_t *key; + bot_chatmessage_t *cm; + bot_matchpiece_t *mp; + + fp = Log_FilePointer(); + if (!fp) return; + fprintf(fp, "BotDumpReplyChat:\n"); + for (rp = replychat; rp; rp = rp->next) + { + fprintf(fp, "["); + for (key = rp->keys; key; key = key->next) + { + if (key->flags & RCKFL_AND) fprintf(fp, "&"); + else if (key->flags & RCKFL_NOT) fprintf(fp, "!"); + // + if (key->flags & RCKFL_NAME) fprintf(fp, "name"); + else if (key->flags & RCKFL_GENDERFEMALE) fprintf(fp, "female"); + else if (key->flags & RCKFL_GENDERMALE) fprintf(fp, "male"); + else if (key->flags & RCKFL_GENDERLESS) fprintf(fp, "it"); + else if (key->flags & RCKFL_VARIABLES) + { + fprintf(fp, "("); + for (mp = key->match; mp; mp = mp->next) + { + if (mp->type == MT_STRING) fprintf(fp, "\"%s\"", mp->firststring->string); + else fprintf(fp, "%d", mp->variable); + if (mp->next) fprintf(fp, ", "); + } //end for + fprintf(fp, ")"); + } //end if + else if (key->flags & RCKFL_STRING) + { + fprintf(fp, "\"%s\"", key->string); + } //end if + if (key->next) fprintf(fp, ", "); + else fprintf(fp, "] = %1.0f\n", rp->priority); + } //end for + fprintf(fp, "{\n"); + for (cm = rp->firstchatmessage; cm; cm = cm->next) + { + fprintf(fp, "\t\"%s\";\n", cm->chatmessage); + } //end for + fprintf(fp, "}\n"); + } //end for +} //end of the function BotDumpReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeReplyChat(bot_replychat_t *replychat) +{ + bot_replychat_t *rp, *nextrp; + bot_replychatkey_t *key, *nextkey; + bot_chatmessage_t *cm, *nextcm; + + for (rp = replychat; rp; rp = nextrp) + { + nextrp = rp->next; + for (key = rp->keys; key; key = nextkey) + { + nextkey = key->next; + if (key->match) BotFreeMatchPieces(key->match); + if (key->string) FreeMemory(key->string); + FreeMemory(key); + } //end for + for (cm = rp->firstchatmessage; cm; cm = nextcm) + { + nextcm = cm->next; + FreeMemory(cm); + } //end for + FreeMemory(rp); + } //end for +} //end of the function BotFreeReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckValidReplyChatKeySet(source_t *source, bot_replychatkey_t *keys) +{ + int allprefixed, hasvariableskey, hasstringkey; + bot_matchpiece_t *m; + bot_matchstring_t *ms; + bot_replychatkey_t *key, *key2; + + // + allprefixed = qtrue; + hasvariableskey = hasstringkey = qfalse; + for (key = keys; key; key = key->next) + { + if (!(key->flags & (RCKFL_AND|RCKFL_NOT))) + { + allprefixed = qfalse; + if (key->flags & RCKFL_VARIABLES) + { + for (m = key->match; m; m = m->next) + { + if (m->type == MT_VARIABLE) hasvariableskey = qtrue; + } //end for + } //end if + else if (key->flags & RCKFL_STRING) + { + hasstringkey = qtrue; + } //end else if + } //end if + else if ((key->flags & RCKFL_AND) && (key->flags & RCKFL_STRING)) + { + for (key2 = keys; key2; key2 = key2->next) + { + if (key2 == key) continue; + if (key2->flags & RCKFL_NOT) continue; + if (key2->flags & RCKFL_VARIABLES) + { + for (m = key2->match; m; m = m->next) + { + if (m->type == MT_STRING) + { + for (ms = m->firststring; ms; ms = ms->next) + { + if (StringContains(ms->string, key->string, qfalse) != -1) + { + break; + } //end if + } //end for + if (ms) break; + } //end if + else if (m->type == MT_VARIABLE) + { + break; + } //end if + } //end for + if (!m) + { + SourceWarning(source, "one of the match templates does not " + "leave space for the key %s with the & prefix", key->string); + } //end if + } //end if + } //end for + } //end else + if ((key->flags & RCKFL_NOT) && (key->flags & RCKFL_STRING)) + { + for (key2 = keys; key2; key2 = key2->next) + { + if (key2 == key) continue; + if (key2->flags & RCKFL_NOT) continue; + if (key2->flags & RCKFL_STRING) + { + if (StringContains(key2->string, key->string, qfalse) != -1) + { + SourceWarning(source, "the key %s with prefix ! is inside the key %s", key->string, key2->string); + } //end if + } //end if + else if (key2->flags & RCKFL_VARIABLES) + { + for (m = key2->match; m; m = m->next) + { + if (m->type == MT_STRING) + { + for (ms = m->firststring; ms; ms = ms->next) + { + if (StringContains(ms->string, key->string, qfalse) != -1) + { + SourceWarning(source, "the key %s with prefix ! is inside " + "the match template string %s", key->string, ms->string); + } //end if + } //end for + } //end if + } //end for + } //end else if + } //end for + } //end if + } //end for + if (allprefixed) SourceWarning(source, "all keys have a & or ! prefix"); + if (hasvariableskey && hasstringkey) + { + SourceWarning(source, "variables from the match template(s) could be " + "invalid when outputting one of the chat messages"); + } //end if +} //end of the function BotCheckValidReplyChatKeySet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_replychat_t *BotLoadReplyChat(char *filename) +{ + char chatmessagestring[MAX_MESSAGE_SIZE]; + char namebuffer[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chatmessage_t *chatmessage = NULL; + bot_replychat_t *replychat, *replychatlist; + bot_replychatkey_t *key; + + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + replychatlist = NULL; + // + while(PC_ReadToken(source, &token)) + { + if (strcmp(token.string, "[")) + { + SourceError(source, "expected [, found %s", token.string); + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + // + replychat = GetClearedHunkMemory(sizeof(bot_replychat_t)); + replychat->keys = NULL; + replychat->next = replychatlist; + replychatlist = replychat; + //read the keys, there must be at least one key + do + { + //allocate a key + key = (bot_replychatkey_t *) GetClearedHunkMemory(sizeof(bot_replychatkey_t)); + key->flags = 0; + key->string = NULL; + key->match = NULL; + key->next = replychat->keys; + replychat->keys = key; + //check for MUST BE PRESENT and MUST BE ABSENT keys + if (PC_CheckTokenString(source, "&")) key->flags |= RCKFL_AND; + else if (PC_CheckTokenString(source, "!")) key->flags |= RCKFL_NOT; + //special keys + if (PC_CheckTokenString(source, "name")) key->flags |= RCKFL_NAME; + else if (PC_CheckTokenString(source, "female")) key->flags |= RCKFL_GENDERFEMALE; + else if (PC_CheckTokenString(source, "male")) key->flags |= RCKFL_GENDERMALE; + else if (PC_CheckTokenString(source, "it")) key->flags |= RCKFL_GENDERLESS; + else if (PC_CheckTokenString(source, "(")) //match key + { + key->flags |= RCKFL_VARIABLES; + key->match = BotLoadMatchPieces(source, ")"); + if (!key->match) + { + BotFreeReplyChat(replychatlist); + return NULL; + } //end if + } //end else if + else if (PC_CheckTokenString(source, "<")) //bot names + { + key->flags |= RCKFL_BOTNAMES; + strcpy(namebuffer, ""); + do + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (strlen(namebuffer)) strcat(namebuffer, "\\"); + strcat(namebuffer, token.string); + } while(PC_CheckTokenString(source, ",")); + if (!PC_ExpectTokenString(source, ">")) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + key->string = (char *) GetClearedHunkMemory(strlen(namebuffer) + 1); + strcpy(key->string, namebuffer); + } //end else if + else //normal string key + { + key->flags |= RCKFL_STRING; + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + key->string = (char *) GetClearedHunkMemory(strlen(token.string) + 1); + strcpy(key->string, token.string); + } //end else + // + PC_CheckTokenString(source, ","); + } while(!PC_CheckTokenString(source, "]")); + // + BotCheckValidReplyChatKeySet(source, replychat->keys); + //read the = sign and the priority + if (!PC_ExpectTokenString(source, "=") || + !PC_ExpectTokenType(source, TT_NUMBER, 0, &token)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + replychat->priority = token.floatvalue; + //read the leading { + if (!PC_ExpectTokenString(source, "{")) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + replychat->numchatmessages = 0; + //while the trailing } is not found + while(!PC_CheckTokenString(source, "}")) + { + if (!BotLoadChatMessage(source, chatmessagestring)) + { + BotFreeReplyChat(replychatlist); + FreeSource(source); + return NULL; + } //end if + chatmessage = (bot_chatmessage_t *) GetClearedHunkMemory(sizeof(bot_chatmessage_t) + strlen(chatmessagestring) + 1); + chatmessage->chatmessage = (char *) chatmessage + sizeof(bot_chatmessage_t); + strcpy(chatmessage->chatmessage, chatmessagestring); + chatmessage->time = -2*CHATMESSAGE_RECENTTIME; + chatmessage->next = replychat->firstchatmessage; + //add the chat message to the reply chat + replychat->firstchatmessage = chatmessage; + replychat->numchatmessages++; + } //end while + } //end while + FreeSource(source); + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); + // + //BotDumpReplyChat(replychatlist); + if (botDeveloper) + { + BotCheckReplyChatIntegrety(replychatlist); + } //end if + // + if (!replychatlist) botimport.Print(PRT_MESSAGE, "no rchats\n"); + // + return replychatlist; +} //end of the function BotLoadReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpInitialChat(bot_chat_t *chat) +{ + bot_chattype_t *t; + bot_chatmessage_t *m; + + Log_Write("{"); + for (t = chat->types; t; t = t->next) + { + Log_Write(" type \"%s\"", t->name); + Log_Write(" {"); + Log_Write(" numchatmessages = %d", t->numchatmessages); + for (m = t->firstchatmessage; m; m = m->next) + { + Log_Write(" \"%s\"", m->chatmessage); + } //end for + Log_Write(" }"); + } //end for + Log_Write("}"); +} //end of the function BotDumpInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_chat_t *BotLoadInitialChat(char *chatfile, char *chatname) +{ + int pass, foundchat, indent, size; + char *ptr = NULL; + char chatmessagestring[MAX_MESSAGE_SIZE]; + source_t *source; + token_t token; + bot_chat_t *chat = NULL; + bot_chattype_t *chattype = NULL; + bot_chatmessage_t *chatmessage = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + // + size = 0; + foundchat = qfalse; + //a bot chat is parsed in two phases + for (pass = 0; pass < 2; pass++) + { + //allocate memory + if (pass && size) ptr = (char *) GetClearedMemory(size); + //load the source file + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(chatfile); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", chatfile); + return NULL; + } //end if + //chat structure + if (pass) + { + chat = (bot_chat_t *) ptr; + ptr += sizeof(bot_chat_t); + } //end if + size = sizeof(bot_chat_t); + // + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "chat")) + { + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + //after the chat name we expect a opening brace + if (!PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + //if the chat name is found + if (!Q_stricmp(token.string, chatname)) + { + foundchat = qtrue; + //read the chat types + while(1) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + return NULL; + } //end if + if (!strcmp(token.string, "}")) break; + if (strcmp(token.string, "type")) + { + SourceError(source, "expected type found %s\n", token.string); + FreeSource(source); + return NULL; + } //end if + //expect the chat type name + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token) || + !PC_ExpectTokenString(source, "{")) + { + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + if (pass) + { + chattype = (bot_chattype_t *) ptr; + strncpy(chattype->name, token.string, MAX_CHATTYPE_NAME); + chattype->firstchatmessage = NULL; + //add the chat type to the chat + chattype->next = chat->types; + chat->types = chattype; + // + ptr += sizeof(bot_chattype_t); + } //end if + size += sizeof(bot_chattype_t); + //read the chat messages + while(!PC_CheckTokenString(source, "}")) + { + size_t len; + if (!BotLoadChatMessage(source, chatmessagestring)) + { + FreeSource(source); + return NULL; + } //end if + len = strlen(chatmessagestring) + 1; + len = PAD(len, sizeof(long)); + if (pass) + { + chatmessage = (bot_chatmessage_t *) ptr; + chatmessage->time = -2*CHATMESSAGE_RECENTTIME; + //put the chat message in the list + chatmessage->next = chattype->firstchatmessage; + chattype->firstchatmessage = chatmessage; + //store the chat message + ptr += sizeof(bot_chatmessage_t); + chatmessage->chatmessage = ptr; + strcpy(chatmessage->chatmessage, chatmessagestring); + ptr += len; + //the number of chat messages increased + chattype->numchatmessages++; + } //end if + size += sizeof(bot_chatmessage_t) + len; + } //end if + } //end while + } //end if + else //skip the bot chat + { + indent = 1; + while(indent) + { + if (!PC_ExpectAnyToken(source, &token)) + { + FreeSource(source); + return NULL; + } //end if + if (!strcmp(token.string, "{")) indent++; + else if (!strcmp(token.string, "}")) indent--; + } //end while + } //end else + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeSource(source); + return NULL; + } //end else + } //end while + //free the source + FreeSource(source); + //if the requested character is not found + if (!foundchat) + { + botimport.Print(PRT_ERROR, "couldn't find chat %s in %s\n", chatname, chatfile); + return NULL; + } //end if + } //end for + // + botimport.Print(PRT_MESSAGE, "loaded %s from %s\n", chatname, chatfile); + // + //BotDumpInitialChat(chat); + if (botDeveloper) + { + BotCheckInitialChatIntegrety(chat); + } //end if +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "initial chats loaded in %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG + //character was read successfully + return chat; +} //end of the function BotLoadInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeChatFile(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + if (cs->chat) FreeMemory(cs->chat); + cs->chat = NULL; +} //end of the function BotFreeChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadChatFile(int chatstate, char *chatfile, char *chatname) +{ + bot_chatstate_t *cs; + int n, avail = 0; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return BLERR_CANNOTLOADICHAT; + BotFreeChatFile(chatstate); + + if (!LibVarGetValue("bot_reloadcharacters")) + { + avail = -1; + for( n = 0; n < MAX_CLIENTS; n++ ) { + if( !ichatdata[n] ) { + if( avail == -1 ) { + avail = n; + } + continue; + } + if( strcmp( chatfile, ichatdata[n]->filename ) != 0 ) { + continue; + } + if( strcmp( chatname, ichatdata[n]->chatname ) != 0 ) { + continue; + } + cs->chat = ichatdata[n]->chat; + // botimport.Print( PRT_MESSAGE, "retained %s from %s\n", chatname, chatfile ); + return BLERR_NOERROR; + } + + if( avail == -1 ) { + botimport.Print(PRT_FATAL, "ichatdata table full; couldn't load chat %s from %s\n", chatname, chatfile); + return BLERR_CANNOTLOADICHAT; + } + } + + cs->chat = BotLoadInitialChat(chatfile, chatname); + if (!cs->chat) + { + botimport.Print(PRT_FATAL, "couldn't load chat %s from %s\n", chatname, chatfile); + return BLERR_CANNOTLOADICHAT; + } //end if + if (!LibVarGetValue("bot_reloadcharacters")) + { + ichatdata[avail] = GetClearedMemory( sizeof(bot_ichatdata_t) ); + ichatdata[avail]->chat = cs->chat; + Q_strncpyz( ichatdata[avail]->chatname, chatname, sizeof(ichatdata[avail]->chatname) ); + Q_strncpyz( ichatdata[avail]->filename, chatfile, sizeof(ichatdata[avail]->filename) ); + } //end if + + return BLERR_NOERROR; +} //end of the function BotLoadChatFile +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotExpandChatMessage(char *outmessage, char *message, unsigned long mcontext, + bot_match_t *match, unsigned long vcontext, int reply) +{ + int num, len, i, expansion; + char *outputbuf, *ptr, *msgptr; + char temp[MAX_MESSAGE_SIZE]; + + expansion = qfalse; + msgptr = message; + outputbuf = outmessage; + len = 0; + // + while(*msgptr) + { + if (*msgptr == ESCAPE_CHAR) + { + msgptr++; + switch(*msgptr) + { + case 'v': //variable + { + msgptr++; + num = 0; + while(*msgptr && *msgptr != ESCAPE_CHAR) + { + num = num * 10 + (*msgptr++) - '0'; + } //end while + //step over the trailing escape char + if (*msgptr) msgptr++; + if (num > MAX_MATCHVARIABLES) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message %s variable %d out of range\n", message, num); + return qfalse; + } //end if + if (match->variables[num].offset >= 0) + { + assert( match->variables[num].offset >= 0 ); + ptr = &match->string[ (int) match->variables[num].offset]; + for (i = 0; i < match->variables[num].length; i++) + { + temp[i] = ptr[i]; + } //end for + temp[i] = 0; + //if it's a reply message + if (reply) + { + //replace the reply synonyms in the variables + BotReplaceReplySynonyms(temp, vcontext); + } //end if + else + { + //replace synonyms in the variable context + BotReplaceSynonyms(temp, vcontext); + } //end else + // + if (len + strlen(temp) >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message %s too long\n", message); + return qfalse; + } //end if + strcpy(&outputbuf[len], temp); + len += strlen(temp); + } //end if + break; + } //end case + case 'r': //random + { + msgptr++; + for (i = 0; (*msgptr && *msgptr != ESCAPE_CHAR); i++) + { + temp[i] = *msgptr++; + } //end while + temp[i] = '\0'; + //step over the trailing escape char + if (*msgptr) msgptr++; + //find the random keyword + ptr = RandomString(temp); + if (!ptr) + { + botimport.Print(PRT_ERROR, "BotConstructChat: unknown random string %s\n", temp); + return qfalse; + } //end if + if (len + strlen(ptr) >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); + return qfalse; + } //end if + strcpy(&outputbuf[len], ptr); + len += strlen(ptr); + expansion = qtrue; + break; + } //end case + default: + { + botimport.Print(PRT_FATAL, "BotConstructChat: message \"%s\" invalid escape char\n", message); + break; + } //end default + } //end switch + } //end if + else + { + outputbuf[len++] = *msgptr++; + if (len >= MAX_MESSAGE_SIZE) + { + botimport.Print(PRT_ERROR, "BotConstructChat: message \"%s\" too long\n", message); + break; + } //end if + } //end else + } //end while + outputbuf[len] = '\0'; + //replace synonyms weighted in the message context + BotReplaceWeightedSynonyms(outputbuf, mcontext); + //return true if a random was expanded + return expansion; +} //end of the function BotExpandChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotConstructChatMessage(bot_chatstate_t *chatstate, char *message, unsigned long mcontext, + bot_match_t *match, unsigned long vcontext, int reply) +{ + int i; + char srcmessage[MAX_MESSAGE_SIZE]; + + strcpy(srcmessage, message); + for (i = 0; i < 10; i++) + { + if (!BotExpandChatMessage(chatstate->chatmessage, srcmessage, mcontext, match, vcontext, reply)) + { + break; + } //end if + strcpy(srcmessage, chatstate->chatmessage); + } //end for + if (i >= 10) + { + botimport.Print(PRT_WARNING, "too many expansions in chat message\n"); + botimport.Print(PRT_WARNING, "%s\n", chatstate->chatmessage); + } //end if +} //end of the function BotConstructChatMessage +//=========================================================================== +// randomly chooses one of the chat message of the given type +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *BotChooseInitialChatMessage(bot_chatstate_t *cs, char *type) +{ + int n, numchatmessages; + float besttime; + bot_chattype_t *t; + bot_chatmessage_t *m, *bestchatmessage; + bot_chat_t *chat; + + chat = cs->chat; + for (t = chat->types; t; t = t->next) + { + if (!Q_stricmp(t->name, type)) + { + numchatmessages = 0; + for (m = t->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) continue; + numchatmessages++; + } //end if + //if all chat messages have been used recently + if (numchatmessages <= 0) + { + besttime = 0; + bestchatmessage = NULL; + for (m = t->firstchatmessage; m; m = m->next) + { + if (!besttime || m->time < besttime) + { + bestchatmessage = m; + besttime = m->time; + } //end if + } //end for + if (bestchatmessage) return bestchatmessage->chatmessage; + } //end if + else //choose a chat message randomly + { + n = random() * numchatmessages; + for (m = t->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) continue; + if (--n < 0) + { + m->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + return m->chatmessage; + } //end if + } //end for + } //end else + return NULL; + } //end if + } //end for + return NULL; +} //end of the function BotChooseInitialChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotNumInitialChats(int chatstate, char *type) +{ + bot_chatstate_t *cs; + bot_chattype_t *t; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + + for (t = cs->chat->types; t; t = t->next) + { + if (!Q_stricmp(t->name, type)) + { + if (LibVarGetValue("bot_testichat")) { + botimport.Print(PRT_MESSAGE, "%s has %d chat lines\n", type, t->numchatmessages); + botimport.Print(PRT_MESSAGE, "-------------------\n"); + } + return t->numchatmessages; + } //end if + } //end for + return 0; +} //end of the function BotNumInitialChats +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitialChat(int chatstate, char *type, int mcontext, char *var0, char *var1, char *var2, char *var3, char *var4, char *var5, char *var6, char *var7) +{ + char *message; + int index; + bot_match_t match; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + //if no chat file is loaded + if (!cs->chat) return; + //choose a chat message randomly of the given type + message = BotChooseInitialChatMessage(cs, type); + //if there's no message of the given type + if (!message) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "no chat messages of type %s\n", type); +#endif //DEBUG + return; + } //end if + // + Com_Memset(&match, 0, sizeof(match)); + index = 0; + if( var0 ) { + strcat(match.string, var0); + match.variables[0].offset = index; + match.variables[0].length = strlen(var0); + index += strlen(var0); + } + if( var1 ) { + strcat(match.string, var1); + match.variables[1].offset = index; + match.variables[1].length = strlen(var1); + index += strlen(var1); + } + if( var2 ) { + strcat(match.string, var2); + match.variables[2].offset = index; + match.variables[2].length = strlen(var2); + index += strlen(var2); + } + if( var3 ) { + strcat(match.string, var3); + match.variables[3].offset = index; + match.variables[3].length = strlen(var3); + index += strlen(var3); + } + if( var4 ) { + strcat(match.string, var4); + match.variables[4].offset = index; + match.variables[4].length = strlen(var4); + index += strlen(var4); + } + if( var5 ) { + strcat(match.string, var5); + match.variables[5].offset = index; + match.variables[5].length = strlen(var5); + index += strlen(var5); + } + if( var6 ) { + strcat(match.string, var6); + match.variables[6].offset = index; + match.variables[6].length = strlen(var6); + index += strlen(var6); + } + if( var7 ) { + strcat(match.string, var7); + match.variables[7].offset = index; + match.variables[7].length = strlen(var7); + index += strlen(var7); + } + // + BotConstructChatMessage(cs, message, mcontext, &match, 0, qfalse); +} //end of the function BotInitialChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPrintReplyChatKeys(bot_replychat_t *replychat) +{ + bot_replychatkey_t *key; + bot_matchpiece_t *mp; + + botimport.Print(PRT_MESSAGE, "["); + for (key = replychat->keys; key; key = key->next) + { + if (key->flags & RCKFL_AND) botimport.Print(PRT_MESSAGE, "&"); + else if (key->flags & RCKFL_NOT) botimport.Print(PRT_MESSAGE, "!"); + // + if (key->flags & RCKFL_NAME) botimport.Print(PRT_MESSAGE, "name"); + else if (key->flags & RCKFL_GENDERFEMALE) botimport.Print(PRT_MESSAGE, "female"); + else if (key->flags & RCKFL_GENDERMALE) botimport.Print(PRT_MESSAGE, "male"); + else if (key->flags & RCKFL_GENDERLESS) botimport.Print(PRT_MESSAGE, "it"); + else if (key->flags & RCKFL_VARIABLES) + { + botimport.Print(PRT_MESSAGE, "("); + for (mp = key->match; mp; mp = mp->next) + { + if (mp->type == MT_STRING) botimport.Print(PRT_MESSAGE, "\"%s\"", mp->firststring->string); + else botimport.Print(PRT_MESSAGE, "%d", mp->variable); + if (mp->next) botimport.Print(PRT_MESSAGE, ", "); + } //end for + botimport.Print(PRT_MESSAGE, ")"); + } //end if + else if (key->flags & RCKFL_STRING) + { + botimport.Print(PRT_MESSAGE, "\"%s\"", key->string); + } //end if + if (key->next) botimport.Print(PRT_MESSAGE, ", "); + else botimport.Print(PRT_MESSAGE, "] = %1.0f\n", replychat->priority); + } //end for + botimport.Print(PRT_MESSAGE, "{\n"); +} //end of the function BotPrintReplyChatKeys +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +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) +{ + bot_replychat_t *rchat, *bestrchat; + bot_replychatkey_t *key; + bot_chatmessage_t *m, *bestchatmessage; + bot_match_t match, bestmatch; + int bestpriority, num, found, res, numchatmessages, index; + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return qfalse; + Com_Memset(&match, 0, sizeof(bot_match_t)); + strcpy(match.string, message); + bestpriority = -1; + bestchatmessage = NULL; + bestrchat = NULL; + //go through all the reply chats + for (rchat = replychats; rchat; rchat = rchat->next) + { + found = qfalse; + for (key = rchat->keys; key; key = key->next) + { + res = qfalse; + //get the match result + if (key->flags & RCKFL_NAME) res = (StringContains(message, cs->name, qfalse) != -1); + else if (key->flags & RCKFL_BOTNAMES) res = (StringContains(key->string, cs->name, qfalse) != -1); + else if (key->flags & RCKFL_GENDERFEMALE) res = (cs->gender == CHAT_GENDERFEMALE); + else if (key->flags & RCKFL_GENDERMALE) res = (cs->gender == CHAT_GENDERMALE); + else if (key->flags & RCKFL_GENDERLESS) res = (cs->gender == CHAT_GENDERLESS); + else if (key->flags & RCKFL_VARIABLES) res = StringsMatch(key->match, &match); + else if (key->flags & RCKFL_STRING) res = (StringContainsWord(message, key->string, qfalse) != NULL); + //if the key must be present + if (key->flags & RCKFL_AND) + { + if (!res) + { + found = qfalse; + break; + } //end if + } //end else if + //if the key must be absent + else if (key->flags & RCKFL_NOT) + { + if (res) + { + found = qfalse; + break; + } //end if + } //end if + else if (res) + { + found = qtrue; + } //end else + } //end for + // + if (found) + { + if (rchat->priority > bestpriority) + { + numchatmessages = 0; + for (m = rchat->firstchatmessage; m; m = m->next) + { + if (m->time > AAS_Time()) continue; + numchatmessages++; + } //end if + num = random() * numchatmessages; + for (m = rchat->firstchatmessage; m; m = m->next) + { + if (--num < 0) break; + if (m->time > AAS_Time()) continue; + } //end for + //if the reply chat has a message + if (m) + { + Com_Memcpy(&bestmatch, &match, sizeof(bot_match_t)); + bestchatmessage = m; + bestrchat = rchat; + bestpriority = rchat->priority; + } //end if + } //end if + } //end if + } //end for + if (bestchatmessage) + { + index = strlen(bestmatch.string); + if( var0 ) { + strcat(bestmatch.string, var0); + bestmatch.variables[0].offset = index; + bestmatch.variables[0].length = strlen(var0); + index += strlen(var0); + } + if( var1 ) { + strcat(bestmatch.string, var1); + bestmatch.variables[1].offset = index; + bestmatch.variables[1].length = strlen(var1); + index += strlen(var1); + } + if( var2 ) { + strcat(bestmatch.string, var2); + bestmatch.variables[2].offset = index; + bestmatch.variables[2].length = strlen(var2); + index += strlen(var2); + } + if( var3 ) { + strcat(bestmatch.string, var3); + bestmatch.variables[3].offset = index; + bestmatch.variables[3].length = strlen(var3); + index += strlen(var3); + } + if( var4 ) { + strcat(bestmatch.string, var4); + bestmatch.variables[4].offset = index; + bestmatch.variables[4].length = strlen(var4); + index += strlen(var4); + } + if( var5 ) { + strcat(bestmatch.string, var5); + bestmatch.variables[5].offset = index; + bestmatch.variables[5].length = strlen(var5); + index += strlen(var5); + } + if( var6 ) { + strcat(bestmatch.string, var6); + bestmatch.variables[6].offset = index; + bestmatch.variables[6].length = strlen(var6); + index += strlen(var6); + } + if( var7 ) { + strcat(bestmatch.string, var7); + bestmatch.variables[7].offset = index; + bestmatch.variables[7].length = strlen(var7); + index += strlen(var7); + } + if (LibVarGetValue("bot_testrchat")) + { + for (m = bestrchat->firstchatmessage; m; m = m->next) + { + BotConstructChatMessage(cs, m->chatmessage, mcontext, &bestmatch, vcontext, qtrue); + BotRemoveTildes(cs->chatmessage); + botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); + } //end if + } //end if + else + { + bestchatmessage->time = AAS_Time() + CHATMESSAGE_RECENTTIME; + BotConstructChatMessage(cs, bestchatmessage->chatmessage, mcontext, &bestmatch, vcontext, qtrue); + } //end else + return qtrue; + } //end if + return qfalse; +} //end of the function BotReplyChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChatLength(int chatstate) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return 0; + return strlen(cs->chatmessage); +} //end of the function BotChatLength +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEnterChat(int chatstate, int clientto, int sendto) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + if (strlen(cs->chatmessage)) + { + BotRemoveTildes(cs->chatmessage); + if (LibVarGetValue("bot_testichat")) { + botimport.Print(PRT_MESSAGE, "%s\n", cs->chatmessage); + } + else { + switch(sendto) { + case CHAT_TEAM: + EA_Command(cs->client, va("say_team %s", cs->chatmessage)); + break; + case CHAT_TELL: + EA_Command(cs->client, va("tell %d %s", clientto, cs->chatmessage)); + break; + default: //CHAT_ALL + EA_Command(cs->client, va("say %s", cs->chatmessage)); + break; + } + } + //clear the chat message from the state + strcpy(cs->chatmessage, ""); + } //end if +} //end of the function BotEnterChat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetChatMessage(int chatstate, char *buf, int size) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + + BotRemoveTildes(cs->chatmessage); + strncpy(buf, cs->chatmessage, size-1); + buf[size-1] = '\0'; + //clear the chat message from the state + strcpy(cs->chatmessage, ""); +} //end of the function BotGetChatMessage +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatGender(int chatstate, int gender) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + switch(gender) + { + case CHAT_GENDERFEMALE: cs->gender = CHAT_GENDERFEMALE; break; + case CHAT_GENDERMALE: cs->gender = CHAT_GENDERMALE; break; + default: cs->gender = CHAT_GENDERLESS; break; + } //end switch +} //end of the function BotSetChatGender +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetChatName(int chatstate, char *name, int client) +{ + bot_chatstate_t *cs; + + cs = BotChatStateFromHandle(chatstate); + if (!cs) return; + cs->client = client; + Com_Memset(cs->name, 0, sizeof(cs->name)); + strncpy(cs->name, name, sizeof(cs->name)); + cs->name[sizeof(cs->name)-1] = '\0'; +} //end of the function BotSetChatName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetChatAI(void) +{ + bot_replychat_t *rchat; + bot_chatmessage_t *m; + + for (rchat = replychats; rchat; rchat = rchat->next) + { + for (m = rchat->firstchatmessage; m; m = m->next) + { + m->time = 0; + } //end for + } //end for +} //end of the function BotResetChatAI +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocChatState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botchatstates[i]) + { + botchatstates[i] = GetClearedMemory(sizeof(bot_chatstate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocChatState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeChatState(int handle) +{ + bot_consolemessage_t m; + int h; + + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "chat state handle %d out of range\n", handle); + return; + } //end if + if (!botchatstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid chat state %d\n", handle); + return; + } //end if + if (LibVarGetValue("bot_reloadcharacters")) + { + BotFreeChatFile(handle); + } //end if + //free all the console messages left in the chat state + for (h = BotNextConsoleMessage(handle, &m); h; h = BotNextConsoleMessage(handle, &m)) + { + //remove the console message + BotRemoveConsoleMessage(handle, h); + } //end for + FreeMemory(botchatstates[handle]); + botchatstates[handle] = NULL; +} //end of the function BotFreeChatState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupChatAI(void) +{ + char *file; + +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif //DEBUG + + file = LibVarString("synfile", "syn.c"); + synonyms = BotLoadSynonyms(file); + file = LibVarString("rndfile", "rnd.c"); + randomstrings = BotLoadRandomStrings(file); + file = LibVarString("matchfile", "match.c"); + matchtemplates = BotLoadMatchTemplates(file); + // + if (!LibVarValue("nochat", "0")) + { + file = LibVarString("rchatfile", "rchat.c"); + replychats = BotLoadReplyChat(file); + } //end if + + InitConsoleMessageHeap(); + +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "setup chat AI %d msec\n", Sys_MilliSeconds() - starttime); +#endif //DEBUG + return BLERR_NOERROR; +} //end of the function BotSetupChatAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownChatAI(void) +{ + int i; + + //free all remaining chat states + for(i = 0; i < MAX_CLIENTS; i++) + { + if (botchatstates[i]) + { + BotFreeChatState(i); + } //end if + } //end for + //free all cached chats + for(i = 0; i < MAX_CLIENTS; i++) + { + if (ichatdata[i]) + { + FreeMemory(ichatdata[i]->chat); + FreeMemory(ichatdata[i]); + ichatdata[i] = NULL; + } //end if + } //end for + if (consolemessageheap) FreeMemory(consolemessageheap); + consolemessageheap = NULL; + if (matchtemplates) BotFreeMatchTemplates(matchtemplates); + matchtemplates = NULL; + if (randomstrings) FreeMemory(randomstrings); + randomstrings = NULL; + if (synonyms) FreeMemory(synonyms); + synonyms = NULL; + if (replychats) BotFreeReplyChat(replychats); + replychats = NULL; +} //end of the function BotShutdownChatAI diff --git a/code/botlib/be_ai_chat.h b/code/botlib/be_ai_chat.h new file mode 100644 index 0000000..53a56d7 --- /dev/null +++ b/code/botlib/be_ai_chat.h @@ -0,0 +1,113 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +/***************************************************************************** + * 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/code/botlib/be_ai_gen.c b/code/botlib/be_ai_gen.c new file mode 100644 index 0000000..5839f8a --- /dev/null +++ b/code/botlib/be_ai_gen.c @@ -0,0 +1,134 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_gen.c + * + * desc: genetic selection + * + * $Archive: /MissionPack/code/botlib/be_ai_gen.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_gen.h" + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticSelection(int numranks, float *rankings) +{ + float sum; + int i, index; + + sum = 0; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + sum += rankings[i]; + } //end for + if (sum > 0) + { + //select a bot where the ones with the higest rankings have + //the highest chance of being selected + //sum *= random(); + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + sum -= rankings[i]; + if (sum <= 0) return i; + } //end for + } //end if + //select a bot randomly + index = random() * numranks; + for (i = 0; i < numranks; i++) + { + if (rankings[index] >= 0) return index; + index = (index + 1) % numranks; + } //end for + return 0; +} //end of the function GeneticSelection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child) +{ + float rankings[256], max; + int i; + + if (numranks > 256) + { + botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too many bots\n"); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + for (max = 0, i = 0; i < numranks; i++) + { + if (ranks[i] < 0) continue; + max++; + } //end for + if (max < 3) + { + botimport.Print(PRT_WARNING, "GeneticParentsAndChildSelection: too few valid bots\n"); + *parent1 = *parent2 = *child = 0; + return qfalse; + } //end if + Com_Memcpy(rankings, ranks, sizeof(float) * numranks); + //select first parent + *parent1 = GeneticSelection(numranks, rankings); + rankings[*parent1] = -1; + //select second parent + *parent2 = GeneticSelection(numranks, rankings); + rankings[*parent2] = -1; + //reverse the rankings + max = 0; + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + if (rankings[i] > max) max = rankings[i]; + } //end for + for (i = 0; i < numranks; i++) + { + if (rankings[i] < 0) continue; + rankings[i] = max - rankings[i]; + } //end for + //select child + *child = GeneticSelection(numranks, rankings); + return qtrue; +} //end of the function GeneticParentsAndChildSelection diff --git a/code/botlib/be_ai_gen.h b/code/botlib/be_ai_gen.h new file mode 100644 index 0000000..ce9ba92 --- /dev/null +++ b/code/botlib/be_ai_gen.h @@ -0,0 +1,33 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * 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/code/botlib/be_ai_goal.c b/code/botlib/be_ai_goal.c new file mode 100644 index 0000000..538a82e --- /dev/null +++ b/code/botlib/be_ai_goal.c @@ -0,0 +1,1821 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_goal.c + * + * desc: goal AI + * + * $Archive: /MissionPack/code/botlib/be_ai_goal.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_utils.h" +#include "l_libvar.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" + +//#define DEBUG_AI_GOAL +#ifdef RANDOMIZE +#define UNDECIDEDFUZZY +#endif //RANDOMIZE +#define DROPPEDWEIGHT +//minimum avoid goal time +#define AVOID_MINIMUM_TIME 10 +//default avoid goal time +#define AVOID_DEFAULT_TIME 30 +//avoid dropped goal time +#define AVOID_DROPPED_TIME 10 +// +#define TRAVELTIME_SCALE 0.01 +//item flags +#define IFL_NOTFREE 1 //not in free for all +#define IFL_NOTTEAM 2 //not in team play +#define IFL_NOTSINGLE 4 //not in single player +#define IFL_NOTBOT 8 //bot should never go for this +#define IFL_ROAM 16 //bot roam goal + +//location in the map "target_location" +typedef struct maplocation_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + struct maplocation_s *next; +} maplocation_t; + +//camp spots "info_camp" +typedef struct campspot_s +{ + vec3_t origin; + int areanum; + char name[MAX_EPAIRKEY]; + float range; + float weight; + float wait; + float random; + struct campspot_s *next; +} campspot_t; + +//FIXME: these are game specific +typedef enum { + GT_FFA, // free for all + GT_TOURNAMENT, // one on one tournament + GT_SINGLE_PLAYER, // single player tournament + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_CTF, // capture the flag +#ifdef MISSIONPACK + GT_1FCTF, + GT_OBELISK, + GT_HARVESTER, +#endif + GT_MAX_GAME_TYPE +} gametype_t; + +typedef struct levelitem_s +{ + int number; //number of the level item + int iteminfo; //index into the item info + int flags; //item flags + float weight; //fixed roam weight + vec3_t origin; //origin of the item + int goalareanum; //area the item is in + vec3_t goalorigin; //goal origin within the area + int entitynum; //entity number + float timeout; //item is removed after this time + struct levelitem_s *prev, *next; +} levelitem_t; + +typedef struct iteminfo_s +{ + char classname[32]; //classname of the item + char name[MAX_STRINGFIELD]; //name of the item + char model[MAX_STRINGFIELD]; //model of the item + int modelindex; //model index + int type; //item type + int index; //index in the inventory + float respawntime; //respawn time + vec3_t mins; //mins of the item + vec3_t maxs; //maxs of the item + int number; //number of the item info +} iteminfo_t; + +#define ITEMINFO_OFS(x) (size_t)&(((iteminfo_t *)0)->x) + +fielddef_t iteminfo_fields[] = +{ +{"name", ITEMINFO_OFS(name), FT_STRING}, +{"model", ITEMINFO_OFS(model), FT_STRING}, +{"modelindex", ITEMINFO_OFS(modelindex), FT_INT}, +{"type", ITEMINFO_OFS(type), FT_INT}, +{"index", ITEMINFO_OFS(index), FT_INT}, +{"respawntime", ITEMINFO_OFS(respawntime), FT_FLOAT}, +{"mins", ITEMINFO_OFS(mins), FT_FLOAT|FT_ARRAY, 3}, +{"maxs", ITEMINFO_OFS(maxs), FT_FLOAT|FT_ARRAY, 3}, +{NULL, 0, 0} +}; + +structdef_t iteminfo_struct = +{ + sizeof(iteminfo_t), iteminfo_fields +}; + +typedef struct itemconfig_s +{ + int numiteminfo; + iteminfo_t *iteminfo; +} itemconfig_t; + +//goal state +typedef struct bot_goalstate_s +{ + struct weightconfig_s *itemweightconfig; //weight config + int *itemweightindex; //index from item to weight + // + int client; //client using this goal state + int lastreachabilityarea; //last area with reachabilities the bot was in + // + bot_goal_t goalstack[MAX_GOALSTACK]; //goal stack + int goalstacktop; //the top of the goal stack + // + int avoidgoals[MAX_AVOIDGOALS]; //goals to avoid + float avoidgoaltimes[MAX_AVOIDGOALS]; //times to avoid the goals +} bot_goalstate_t; + +bot_goalstate_t *botgoalstates[MAX_CLIENTS + 1]; // FIXME: init? +//item configuration +itemconfig_t *itemconfig = NULL; +//level items +levelitem_t *levelitemheap = NULL; +levelitem_t *freelevelitems = NULL; +levelitem_t *levelitems = NULL; +int numlevelitems = 0; +//map locations +maplocation_t *maplocations = NULL; +//camp spots +campspot_t *campspots = NULL; +//the game type +int g_gametype = 0; +//additional dropped item weight +libvar_t *droppedweight = NULL; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_goalstate_t *BotGoalStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botgoalstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid goal state %d\n", handle); + return NULL; + } //end if + return botgoalstates[handle]; +} //end of the function BotGoalStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInterbreedGoalFuzzyLogic(int parent1, int parent2, int child) +{ + bot_goalstate_t *p1, *p2, *c; + + p1 = BotGoalStateFromHandle(parent1); + p2 = BotGoalStateFromHandle(parent2); + c = BotGoalStateFromHandle(child); + + InterbreedWeightConfigs(p1->itemweightconfig, p2->itemweightconfig, + c->itemweightconfig); +} //end of the function BotInterbreedingGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSaveGoalFuzzyLogic(int goalstate, char *filename) +{ + //bot_goalstate_t *gs; + + //gs = BotGoalStateFromHandle(goalstate); + + //WriteWeightConfig(filename, gs->itemweightconfig); +} //end of the function BotSaveGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMutateGoalFuzzyLogic(int goalstate, float range) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + + EvolveWeightConfig(gs->itemweightconfig); +} //end of the function BotMutateGoalFuzzyLogic +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +itemconfig_t *LoadItemConfig(char *filename) +{ + int max_iteminfo; + token_t token; + char path[MAX_PATH]; + source_t *source; + itemconfig_t *ic; + iteminfo_t *ii; + + max_iteminfo = (int) LibVarValue("max_iteminfo", "256"); + if (max_iteminfo < 0) + { + botimport.Print(PRT_ERROR, "max_iteminfo = %d\n", max_iteminfo); + max_iteminfo = 256; + LibVarSet( "max_iteminfo", "256" ); + } + + strncpy( path, filename, MAX_PATH ); + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile( path ); + if( !source ) { + botimport.Print( PRT_ERROR, "counldn't load %s\n", path ); + return NULL; + } //end if + //initialize item config + ic = (itemconfig_t *) GetClearedHunkMemory(sizeof(itemconfig_t) + + max_iteminfo * sizeof(iteminfo_t)); + ic->iteminfo = (iteminfo_t *) ((char *) ic + sizeof(itemconfig_t)); + ic->numiteminfo = 0; + //parse the item config file + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "iteminfo")) + { + if (ic->numiteminfo >= max_iteminfo) + { + SourceError(source, "more than %d item info defined\n", max_iteminfo); + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end if + ii = &ic->iteminfo[ic->numiteminfo]; + Com_Memset(ii, 0, sizeof(iteminfo_t)); + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeMemory(ic); + FreeMemory(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + strncpy(ii->classname, token.string, sizeof(ii->classname)-1); + if (!ReadStructure(source, &iteminfo_struct, (char *) ii)) + { + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end if + ii->number = ic->numiteminfo; + ic->numiteminfo++; + } //end if + else + { + SourceError(source, "unknown definition %s\n", token.string); + FreeMemory(ic); + FreeSource(source); + return NULL; + } //end else + } //end while + FreeSource(source); + // + if (!ic->numiteminfo) botimport.Print(PRT_WARNING, "no item info loaded\n"); + botimport.Print(PRT_MESSAGE, "loaded %s\n", path); + return ic; +} //end of the function LoadItemConfig +//=========================================================================== +// index to find the weight function of an iteminfo +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *ItemWeightIndex(weightconfig_t *iwc, itemconfig_t *ic) +{ + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory(sizeof(int) * ic->numiteminfo); + + for (i = 0; i < ic->numiteminfo; i++) + { + index[i] = FindFuzzyWeight(iwc, ic->iteminfo[i].classname); + if (index[i] < 0) + { + Log_Write("item info %d \"%s\" has no fuzzy weight\r\n", i, ic->iteminfo[i].classname); + } //end if + } //end for + return index; +} //end of the function ItemWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InitLevelItemHeap(void) +{ + int i, max_levelitems; + + if (levelitemheap) FreeMemory(levelitemheap); + + max_levelitems = (int) LibVarValue("max_levelitems", "256"); + levelitemheap = (levelitem_t *) GetClearedMemory(max_levelitems * sizeof(levelitem_t)); + + for (i = 0; i < max_levelitems-1; i++) + { + levelitemheap[i].next = &levelitemheap[i + 1]; + } //end for + levelitemheap[max_levelitems-1].next = NULL; + // + freelevelitems = levelitemheap; +} //end of the function InitLevelItemHeap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +levelitem_t *AllocLevelItem(void) +{ + levelitem_t *li; + + li = freelevelitems; + if (!li) + { + botimport.Print(PRT_FATAL, "out of level items\n"); + return NULL; + } //end if + // + freelevelitems = freelevelitems->next; + Com_Memset(li, 0, sizeof(levelitem_t)); + return li; +} //end of the function AllocLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeLevelItem(levelitem_t *li) +{ + li->next = freelevelitems; + freelevelitems = li; +} //end of the function FreeLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AddLevelItemToList(levelitem_t *li) +{ + if (levelitems) levelitems->prev = li; + li->prev = NULL; + li->next = levelitems; + levelitems = li; +} //end of the function AddLevelItemToList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void RemoveLevelItemFromList(levelitem_t *li) +{ + if (li->prev) li->prev->next = li->next; + else levelitems = li->next; + if (li->next) li->next->prev = li->prev; +} //end of the function RemoveLevelItemFromList +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeInfoEntities(void) +{ + maplocation_t *ml, *nextml; + campspot_t *cs, *nextcs; + + for (ml = maplocations; ml; ml = nextml) + { + nextml = ml->next; + FreeMemory(ml); + } //end for + maplocations = NULL; + for (cs = campspots; cs; cs = nextcs) + { + nextcs = cs->next; + FreeMemory(cs); + } //end for + campspots = NULL; +} //end of the function BotFreeInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitInfoEntities(void) +{ + char classname[MAX_EPAIRKEY]; + maplocation_t *ml; + campspot_t *cs; + int ent, numlocations, numcampspots; + + BotFreeInfoEntities(); + // + numlocations = 0; + numcampspots = 0; + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + + //map locations + if (!strcmp(classname, "target_location")) + { + ml = (maplocation_t *) GetClearedMemory(sizeof(maplocation_t)); + AAS_VectorForBSPEpairKey(ent, "origin", ml->origin); + AAS_ValueForBSPEpairKey(ent, "message", ml->name, sizeof(ml->name)); + ml->areanum = AAS_PointAreaNum(ml->origin); + ml->next = maplocations; + maplocations = ml; + numlocations++; + } //end if + //camp spots + else if (!strcmp(classname, "info_camp")) + { + cs = (campspot_t *) GetClearedMemory(sizeof(campspot_t)); + AAS_VectorForBSPEpairKey(ent, "origin", cs->origin); + //cs->origin[2] += 16; + AAS_ValueForBSPEpairKey(ent, "message", cs->name, sizeof(cs->name)); + AAS_FloatForBSPEpairKey(ent, "range", &cs->range); + AAS_FloatForBSPEpairKey(ent, "weight", &cs->weight); + AAS_FloatForBSPEpairKey(ent, "wait", &cs->wait); + AAS_FloatForBSPEpairKey(ent, "random", &cs->random); + cs->areanum = AAS_PointAreaNum(cs->origin); + if (!cs->areanum) + { + botimport.Print(PRT_MESSAGE, "camp spot at %1.1f %1.1f %1.1f in solid\n", cs->origin[0], cs->origin[1], cs->origin[2]); + FreeMemory(cs); + continue; + } //end if + cs->next = campspots; + campspots = cs; + //AAS_DrawPermanentCross(cs->origin, 4, LINECOLOR_YELLOW); + numcampspots++; + } //end else if + } //end for + if (botDeveloper) + { + botimport.Print(PRT_MESSAGE, "%d map locations\n", numlocations); + botimport.Print(PRT_MESSAGE, "%d camp spots\n", numcampspots); + } //end if +} //end of the function BotInitInfoEntities +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotInitLevelItems(void) +{ + int i, spawnflags, value; + char classname[MAX_EPAIRKEY]; + vec3_t origin, end; + int ent, goalareanum; + itemconfig_t *ic; + levelitem_t *li; + bsp_trace_t trace; + + //initialize the map locations and camp spots + BotInitInfoEntities(); + + //initialize the level item heap + InitLevelItemHeap(); + levelitems = NULL; + numlevelitems = 0; + // + ic = itemconfig; + if (!ic) return; + + //if there's no AAS file loaded + if (!AAS_Loaded()) return; + + //update the modelindexes of the item info + for (i = 0; i < ic->numiteminfo; i++) + { + //ic->iteminfo[i].modelindex = AAS_IndexFromModel(ic->iteminfo[i].model); + if (!ic->iteminfo[i].modelindex) + { + Log_Write("item %s has modelindex 0", ic->iteminfo[i].classname); + } //end if + } //end for + + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + // + spawnflags = 0; + AAS_IntForBSPEpairKey(ent, "spawnflags", &spawnflags); + // + for (i = 0; i < ic->numiteminfo; i++) + { + if (!strcmp(classname, ic->iteminfo[i].classname)) break; + } //end for + if (i >= ic->numiteminfo) + { + Log_Write("entity %s unknown item\r\n", classname); + continue; + } //end if + //get the origin of the item + if (!AAS_VectorForBSPEpairKey(ent, "origin", origin)) + { + botimport.Print(PRT_ERROR, "item %s without origin\n", classname); + continue; + } //end else + // + goalareanum = 0; + //if it is a floating item + if (spawnflags & 1) + { + //if the item is not floating in water + if (!(AAS_PointContents(origin) & CONTENTS_WATER)) + { + VectorCopy(origin, end); + end[2] -= 32; + trace = AAS_Trace(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs, end, -1, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + //if the item not near the ground + if (trace.fraction >= 1) + { + //if the item is not reachable from a jumppad + goalareanum = AAS_BestReachableFromJumpPadArea(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs); + Log_Write("item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); + //botimport.Print(PRT_MESSAGE, "item %s reachable from jumppad area %d\r\n", ic->iteminfo[i].classname, goalareanum); + if (!goalareanum) continue; + } //end if + } //end if + } //end if + + li = AllocLevelItem(); + if (!li) return; + // + li->number = ++numlevelitems; + li->timeout = 0; + li->entitynum = 0; + // + li->flags = 0; + AAS_IntForBSPEpairKey(ent, "notfree", &value); + if (value) li->flags |= IFL_NOTFREE; + AAS_IntForBSPEpairKey(ent, "notteam", &value); + if (value) li->flags |= IFL_NOTTEAM; + AAS_IntForBSPEpairKey(ent, "notsingle", &value); + if (value) li->flags |= IFL_NOTSINGLE; + AAS_IntForBSPEpairKey(ent, "notbot", &value); + if (value) li->flags |= IFL_NOTBOT; + if (!strcmp(classname, "item_botroam")) + { + li->flags |= IFL_ROAM; + AAS_FloatForBSPEpairKey(ent, "weight", &li->weight); + } //end if + //if not a stationary item + if (!(spawnflags & 1)) + { + if (!AAS_DropToFloor(origin, ic->iteminfo[i].mins, ic->iteminfo[i].maxs)) + { + botimport.Print(PRT_MESSAGE, "%s in solid at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end if + //item info of the level item + li->iteminfo = i; + //origin of the item + VectorCopy(origin, li->origin); + // + if (goalareanum) + { + li->goalareanum = goalareanum; + VectorCopy(origin, li->goalorigin); + } //end if + else + { + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea(origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin); + if (!li->goalareanum) + { + botimport.Print(PRT_MESSAGE, "%s not reachable for bots at (%1.1f %1.1f %1.1f)\n", + classname, origin[0], origin[1], origin[2]); + } //end if + } //end else + // + AddLevelItemToList(li); + } //end for + botimport.Print(PRT_MESSAGE, "found %d level items\n", numlevelitems); +} //end of the function BotInitLevelItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGoalName(int number, char *name, int size) +{ + levelitem_t *li; + + if (!itemconfig) return; + // + for (li = levelitems; li; li = li->next) + { + if (li->number == number) + { + strncpy(name, itemconfig->iteminfo[li->iteminfo].name, size-1); + name[size-1] = '\0'; + return; + } //end for + } //end for + strcpy(name, ""); + return; +} //end of the function BotGoalName +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidGoals(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + Com_Memset(gs->avoidgoals, 0, MAX_AVOIDGOALS * sizeof(int)); + Com_Memset(gs->avoidgoaltimes, 0, MAX_AVOIDGOALS * sizeof(float)); +} //end of the function BotResetAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpAvoidGoals(int goalstate) +{ + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoaltimes[i] >= AAS_Time()) + { + BotGoalName(gs->avoidgoals[i], name, 32); + Log_Write("avoid goal %s, number %d for %f seconds", name, + gs->avoidgoals[i], gs->avoidgoaltimes[i] - AAS_Time()); + } //end if + } //end for +} //end of the function BotDumpAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidGoals(bot_goalstate_t *gs, int number, float avoidtime) +{ + int i; + + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + //if the avoid goal is already stored + if (gs->avoidgoals[i] == number) + { + gs->avoidgoals[i] = number; + gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for + + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + //if this avoid goal has expired + if (gs->avoidgoaltimes[i] < AAS_Time()) + { + gs->avoidgoals[i] = number; + gs->avoidgoaltimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotRemoveFromAvoidGoals(int goalstate, int number) +{ + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + //don't use the goals the bot wants to avoid + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) + { + gs->avoidgoaltimes[i] = 0; + return; + } //end if + } //end for +} //end of the function BotRemoveFromAvoidGoals +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotAvoidGoalTime(int goalstate, int number) +{ + int i; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return 0; + //don't use the goals the bot wants to avoid + for (i = 0; i < MAX_AVOIDGOALS; i++) + { + if (gs->avoidgoals[i] == number && gs->avoidgoaltimes[i] >= AAS_Time()) + { + return gs->avoidgoaltimes[i] - AAS_Time(); + } //end if + } //end for + return 0; +} //end of the function BotAvoidGoalTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotSetAvoidGoalTime(int goalstate, int number, float avoidtime) +{ + bot_goalstate_t *gs; + levelitem_t *li; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + return; + if (avoidtime < 0) + { + if (!itemconfig) + return; + // + for (li = levelitems; li; li = li->next) + { + if (li->number == number) + { + avoidtime = itemconfig->iteminfo[li->iteminfo].respawntime; + if (!avoidtime) + avoidtime = AVOID_DEFAULT_TIME; + if (avoidtime < AVOID_MINIMUM_TIME) + avoidtime = AVOID_MINIMUM_TIME; + BotAddToAvoidGoals(gs, number, avoidtime); + return; + } //end for + } //end for + return; + } //end if + else + { + BotAddToAvoidGoals(gs, number, avoidtime); + } //end else +} //end of the function BotSetAvoidGoalTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetLevelItemGoal(int index, char *name, bot_goal_t *goal) +{ + levelitem_t *li; + + if (!itemconfig) return -1; + li = levelitems; + if (index >= 0) + { + for (; li; li = li->next) + { + if (li->number == index) + { + li = li->next; + break; + } //end if + } //end for + } //end for + for (; li; li = li->next) + { + // + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) continue; + } + else { + if (li->flags & IFL_NOTFREE) continue; + } + if (li->flags & IFL_NOTBOT) continue; + // + if (!Q_stricmp(name, itemconfig->iteminfo[li->iteminfo].name)) + { + goal->areanum = li->goalareanum; + VectorCopy(li->goalorigin, goal->origin); + goal->entitynum = li->entitynum; + VectorCopy(itemconfig->iteminfo[li->iteminfo].mins, goal->mins); + VectorCopy(itemconfig->iteminfo[li->iteminfo].maxs, goal->maxs); + goal->number = li->number; + goal->flags = GFL_ITEM; + if (li->timeout) goal->flags |= GFL_DROPPED; + //botimport.Print(PRT_MESSAGE, "found li %s\n", itemconfig->iteminfo[li->iteminfo].name); + return li->number; + } //end if + } //end for + return -1; +} //end of the function BotGetLevelItemGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetMapLocationGoal(char *name, bot_goal_t *goal) +{ + maplocation_t *ml; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + for (ml = maplocations; ml; ml = ml->next) + { + if (!Q_stricmp(ml->name, name)) + { + goal->areanum = ml->areanum; + VectorCopy(ml->origin, goal->origin); + goal->entitynum = 0; + VectorCopy(mins, goal->mins); + VectorCopy(maxs, goal->maxs); + return qtrue; + } //end if + } //end for + return qfalse; +} //end of the function BotGetMapLocationGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetNextCampSpotGoal(int num, bot_goal_t *goal) +{ + int i; + campspot_t *cs; + vec3_t mins = {-8, -8, -8}, maxs = {8, 8, 8}; + + if (num < 0) num = 0; + i = num; + for (cs = campspots; cs; cs = cs->next) + { + if (--i < 0) + { + goal->areanum = cs->areanum; + VectorCopy(cs->origin, goal->origin); + goal->entitynum = 0; + VectorCopy(mins, goal->mins); + VectorCopy(maxs, goal->maxs); + return num+1; + } //end if + } //end for + return 0; +} //end of the function BotGetNextCampSpotGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFindEntityForLevelItem(levelitem_t *li) +{ + int ent, modelindex; + itemconfig_t *ic; + aas_entityinfo_t entinfo; + vec3_t dir; + + ic = itemconfig; + if (!itemconfig) return; + for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) + { + //get the model index of the entity + modelindex = AAS_EntityModelindex(ent); + // + if (!modelindex) continue; + //get info about the entity + AAS_EntityInfo(ent, &entinfo); + //if the entity is still moving + if (entinfo.origin[0] != entinfo.lastvisorigin[0] || + entinfo.origin[1] != entinfo.lastvisorigin[1] || + entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; + // + if (ic->iteminfo[li->iteminfo].modelindex == modelindex) + { + //check if the entity is very close + VectorSubtract(li->origin, entinfo.origin, dir); + if (VectorLength(dir) < 30) + { + //found an entity for this level item + li->entitynum = ent; + } //end if + } //end if + } //end for +} //end of the function BotFindEntityForLevelItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== + +//NOTE: enum entityType_t in bg_public.h +#define ET_ITEM 2 + +void BotUpdateEntityItems(void) +{ + int ent, i, modelindex; + vec3_t dir; + levelitem_t *li, *nextli; + aas_entityinfo_t entinfo; + itemconfig_t *ic; + + //timeout current entity items if necessary + for (li = levelitems; li; li = nextli) + { + nextli = li->next; + //if it is a item that will time out + if (li->timeout) + { + //timeout the item + if (li->timeout < AAS_Time()) + { + RemoveLevelItemFromList(li); + FreeLevelItem(li); + } //end if + } //end if + } //end for + //find new entity items + ic = itemconfig; + if (!itemconfig) return; + // + for (ent = AAS_NextEntity(0); ent; ent = AAS_NextEntity(ent)) + { + if (AAS_EntityType(ent) != ET_ITEM) continue; + //get the model index of the entity + modelindex = AAS_EntityModelindex(ent); + // + if (!modelindex) continue; + //get info about the entity + AAS_EntityInfo(ent, &entinfo); + //FIXME: don't do this + //skip all floating items for now + //if (entinfo.groundent != ENTITYNUM_WORLD) continue; + //if the entity is still moving + if (entinfo.origin[0] != entinfo.lastvisorigin[0] || + entinfo.origin[1] != entinfo.lastvisorigin[1] || + entinfo.origin[2] != entinfo.lastvisorigin[2]) continue; + //check if the entity is already stored as a level item + for (li = levelitems; li; li = li->next) + { + //if the level item is linked to an entity + if (li->entitynum && li->entitynum == ent) + { + //the entity is re-used if the models are different + if (ic->iteminfo[li->iteminfo].modelindex != modelindex) + { + //remove this level item + RemoveLevelItemFromList(li); + FreeLevelItem(li); + li = NULL; + break; + } //end if + else + { + if (entinfo.origin[0] != li->origin[0] || + entinfo.origin[1] != li->origin[1] || + entinfo.origin[2] != li->origin[2]) + { + VectorCopy(entinfo.origin, li->origin); + //also update the goal area number + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, + li->goalorigin); + } //end if + break; + } //end else + } //end if + } //end for + if (li) continue; + //try to link the entity to a level item + for (li = levelitems; li; li = li->next) + { + //if this level item is already linked + if (li->entitynum) continue; + // + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) continue; + } + else { + if (li->flags & IFL_NOTFREE) continue; + } + //if the model of the level item and the entity are the same + if (ic->iteminfo[li->iteminfo].modelindex == modelindex) + { + //check if the entity is very close + VectorSubtract(li->origin, entinfo.origin, dir); + if (VectorLength(dir) < 30) + { + //found an entity for this level item + li->entitynum = ent; + //if the origin is different + if (entinfo.origin[0] != li->origin[0] || + entinfo.origin[1] != li->origin[1] || + entinfo.origin[2] != li->origin[2]) + { + //update the level item origin + VectorCopy(entinfo.origin, li->origin); + //also update the goal area number + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[li->iteminfo].mins, ic->iteminfo[li->iteminfo].maxs, + li->goalorigin); + } //end if +#ifdef DEBUG + Log_Write("linked item %s to an entity", ic->iteminfo[li->iteminfo].classname); +#endif //DEBUG + break; + } //end if + } //end else + } //end for + if (li) continue; + //check if the model is from a known item + for (i = 0; i < ic->numiteminfo; i++) + { + if (ic->iteminfo[i].modelindex == modelindex) + { + break; + } //end if + } //end for + //if the model is not from a known item + if (i >= ic->numiteminfo) continue; + //allocate a new level item + li = AllocLevelItem(); + // + if (!li) continue; + //entity number of the level item + li->entitynum = ent; + //number for the level item + li->number = numlevelitems + ent; + //set the item info index for the level item + li->iteminfo = i; + //origin of the item + VectorCopy(entinfo.origin, li->origin); + //get the item goal area and goal origin + li->goalareanum = AAS_BestReachableArea(li->origin, + ic->iteminfo[i].mins, ic->iteminfo[i].maxs, + li->goalorigin); + //never go for items dropped into jumppads + if (AAS_AreaJumpPad(li->goalareanum)) + { + FreeLevelItem(li); + continue; + } //end if + //time this item out after 30 seconds + //dropped items disappear after 30 seconds + li->timeout = AAS_Time() + 30; + //add the level item to the list + AddLevelItemToList(li); + //botimport.Print(PRT_MESSAGE, "found new level item %s\n", ic->iteminfo[i].classname); + } //end for + /* + for (li = levelitems; li; li = li->next) + { + if (!li->entitynum) + { + BotFindEntityForLevelItem(li); + } //end if + } //end for*/ +} //end of the function BotUpdateEntityItems +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotDumpGoalStack(int goalstate) +{ + int i; + bot_goalstate_t *gs; + char name[32]; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + for (i = 1; i <= gs->goalstacktop; i++) + { + BotGoalName(gs->goalstack[i].number, name, 32); + Log_Write("%d: %s", i, name); + } //end for +} //end of the function BotDumpGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPushGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + if (gs->goalstacktop >= MAX_GOALSTACK-1) + { + botimport.Print(PRT_ERROR, "goal heap overflow\n"); + BotDumpGoalStack(goalstate); + return; + } //end if + gs->goalstacktop++; + Com_Memcpy(&gs->goalstack[gs->goalstacktop], goal, sizeof(bot_goal_t)); +} //end of the function BotPushGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotPopGoal(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + if (gs->goalstacktop > 0) gs->goalstacktop--; +} //end of the function BotPopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotEmptyGoalStack(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + gs->goalstacktop = 0; +} //end of the function BotEmptyGoalStack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetTopGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return qfalse; + if (!gs->goalstacktop) return qfalse; + Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop], sizeof(bot_goal_t)); + return qtrue; +} //end of the function BotGetTopGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetSecondGoal(int goalstate, bot_goal_t *goal) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return qfalse; + if (gs->goalstacktop <= 1) return qfalse; + Com_Memcpy(goal, &gs->goalstack[gs->goalstacktop-1], sizeof(bot_goal_t)); + return qtrue; +} //end of the function BotGetSecondGoal +//=========================================================================== +// pops a new long term goal on the goal stack in the goalstate +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseLTGItem(int goalstate, vec3_t origin, int *inventory, int travelflags) +{ + int areanum, t, weightnum; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + return qfalse; + if (!gs->itemweightconfig) + return qfalse; + //get the area the bot is in + areanum = BotReachabilityArea(origin, gs->client); + //if the bot is in solid or if the area the bot is in has no reachability links + if (!areanum || !AAS_AreaReachability(areanum)) + { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if (!areanum) + return qfalse; + //the item configuration + ic = itemconfig; + if (!itemconfig) + return qfalse; + //best weight and item so far + bestweight = 0; + bestitem = NULL; + Com_Memset(&goal, 0, sizeof(bot_goal_t)); + //go through the items in the level + for (li = levelitems; li; li = li->next) + { + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) + continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) + continue; + } + else { + if (li->flags & IFL_NOTFREE) + continue; + } + if (li->flags & IFL_NOTBOT) + continue; + //if the item is not in a possible goal area + if (!li->goalareanum) + continue; + //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) + if (!li->entitynum && !(li->flags & IFL_ROAM)) + continue; + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if (weightnum < 0) + continue; + +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); +#else + weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if (li->timeout) + weight += droppedweight->value; +#endif //DROPPEDWEIGHT + //use weight scale for item_botroam + if (li->flags & IFL_ROAM) weight *= li->weight; + // + if (weight > 0) + { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); + //if the goal is reachable + if (t > 0) + { + //if this item won't respawn before we get there + avoidtime = BotAvoidGoalTime(goalstate, li->number); + if (avoidtime - t * 0.009 > 0) + continue; + // + weight /= (float) t * TRAVELTIME_SCALE; + // + if (weight > bestweight) + { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if (!bestitem) + { + /* + //if not in lava or slime + if (!AAS_AreaLava(areanum) && !AAS_AreaSlime(areanum)) + { + if (AAS_RandomGoalArea(areanum, travelflags, &goal.areanum, goal.origin)) + { + VectorSet(goal.mins, -15, -15, -15); + VectorSet(goal.maxs, 15, 15, 15); + goal.entitynum = 0; + goal.number = 0; + goal.flags = GFL_ROAM; + goal.iteminfo = 0; + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "chosen roam goal area %d\n", goal.areanum); +#endif //DEBUG + return qtrue; + } //end if + } //end if + */ + return qfalse; + } //end if + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy(bestitem->goalorigin, goal.origin); + VectorCopy(iteminfo->mins, goal.mins); + VectorCopy(iteminfo->maxs, goal.maxs); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + if (bestitem->timeout) + goal.flags |= GFL_DROPPED; + if (bestitem->flags & IFL_ROAM) + goal.flags |= GFL_ROAM; + goal.iteminfo = bestitem->iteminfo; + //if it's a dropped item + if (bestitem->timeout) + { + avoidtime = AVOID_DROPPED_TIME; + } //end if + else + { + avoidtime = iteminfo->respawntime; + if (!avoidtime) + avoidtime = AVOID_DEFAULT_TIME; + if (avoidtime < AVOID_MINIMUM_TIME) + avoidtime = AVOID_MINIMUM_TIME; + } //end else + //add the chosen goal to the goals to avoid for a while + BotAddToAvoidGoals(gs, bestitem->number, avoidtime); + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // + return qtrue; +} //end of the function BotChooseLTGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseNBGItem(int goalstate, vec3_t origin, int *inventory, int travelflags, + bot_goal_t *ltg, float maxtime) +{ + int areanum, t, weightnum, ltg_time; + float weight, bestweight, avoidtime; + iteminfo_t *iteminfo; + itemconfig_t *ic; + levelitem_t *li, *bestitem; + bot_goal_t goal; + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) + return qfalse; + if (!gs->itemweightconfig) + return qfalse; + //get the area the bot is in + areanum = BotReachabilityArea(origin, gs->client); + //if the bot is in solid or if the area the bot is in has no reachability links + if (!areanum || !AAS_AreaReachability(areanum)) + { + //use the last valid area the bot was in + areanum = gs->lastreachabilityarea; + } //end if + //remember the last area with reachabilities the bot was in + gs->lastreachabilityarea = areanum; + //if still in solid + if (!areanum) + return qfalse; + // + if (ltg) ltg_time = AAS_AreaTravelTimeToGoalArea(areanum, origin, ltg->areanum, travelflags); + else ltg_time = 99999; + //the item configuration + ic = itemconfig; + if (!itemconfig) + return qfalse; + //best weight and item so far + bestweight = 0; + bestitem = NULL; + Com_Memset(&goal, 0, sizeof(bot_goal_t)); + //go through the items in the level + for (li = levelitems; li; li = li->next) + { + if (g_gametype == GT_SINGLE_PLAYER) { + if (li->flags & IFL_NOTSINGLE) + continue; + } + else if (g_gametype >= GT_TEAM) { + if (li->flags & IFL_NOTTEAM) + continue; + } + else { + if (li->flags & IFL_NOTFREE) + continue; + } + if (li->flags & IFL_NOTBOT) + continue; + //if the item is in a possible goal area + if (!li->goalareanum) + continue; + //FIXME: is this a good thing? added this for items that never spawned into the game (f.i. CTF flags in obelisk) + if (!li->entitynum && !(li->flags & IFL_ROAM)) + continue; + //get the fuzzy weight function for this item + iteminfo = &ic->iteminfo[li->iteminfo]; + weightnum = gs->itemweightindex[iteminfo->number]; + if (weightnum < 0) + continue; + // +#ifdef UNDECIDEDFUZZY + weight = FuzzyWeightUndecided(inventory, gs->itemweightconfig, weightnum); +#else + weight = FuzzyWeight(inventory, gs->itemweightconfig, weightnum); +#endif //UNDECIDEDFUZZY +#ifdef DROPPEDWEIGHT + //HACK: to make dropped items more attractive + if (li->timeout) + weight += droppedweight->value; +#endif //DROPPEDWEIGHT + //use weight scale for item_botroam + if (li->flags & IFL_ROAM) weight *= li->weight; + // + if (weight > 0) + { + //get the travel time towards the goal area + t = AAS_AreaTravelTimeToGoalArea(areanum, origin, li->goalareanum, travelflags); + //if the goal is reachable + if (t > 0 && t < maxtime) + { + //if this item won't respawn before we get there + avoidtime = BotAvoidGoalTime(goalstate, li->number); + if (avoidtime - t * 0.009 > 0) + continue; + // + weight /= (float) t * TRAVELTIME_SCALE; + // + if (weight > bestweight) + { + t = 0; + if (ltg && !li->timeout) + { + //get the travel time from the goal to the long term goal + t = AAS_AreaTravelTimeToGoalArea(li->goalareanum, li->goalorigin, ltg->areanum, travelflags); + } //end if + //if the travel back is possible and doesn't take too long + if (t <= ltg_time) + { + bestweight = weight; + bestitem = li; + } //end if + } //end if + } //end if + } //end if + } //end for + //if no goal item found + if (!bestitem) + return qfalse; + //create a bot goal for this item + iteminfo = &ic->iteminfo[bestitem->iteminfo]; + VectorCopy(bestitem->goalorigin, goal.origin); + VectorCopy(iteminfo->mins, goal.mins); + VectorCopy(iteminfo->maxs, goal.maxs); + goal.areanum = bestitem->goalareanum; + goal.entitynum = bestitem->entitynum; + goal.number = bestitem->number; + goal.flags = GFL_ITEM; + if (bestitem->timeout) + goal.flags |= GFL_DROPPED; + if (bestitem->flags & IFL_ROAM) + goal.flags |= GFL_ROAM; + goal.iteminfo = bestitem->iteminfo; + //if it's a dropped item + if (bestitem->timeout) + { + avoidtime = AVOID_DROPPED_TIME; + } //end if + else + { + avoidtime = iteminfo->respawntime; + if (!avoidtime) + avoidtime = AVOID_DEFAULT_TIME; + if (avoidtime < AVOID_MINIMUM_TIME) + avoidtime = AVOID_MINIMUM_TIME; + } //end else + //add the chosen goal to the goals to avoid for a while + BotAddToAvoidGoals(gs, bestitem->number, avoidtime); + //push the goal on the stack + BotPushGoal(goalstate, &goal); + // + return qtrue; +} //end of the function BotChooseNBGItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotTouchingGoal(vec3_t origin, bot_goal_t *goal) +{ + int i; + vec3_t boxmins, boxmaxs; + vec3_t absmins, absmaxs; + vec3_t safety_maxs = {0, 0, 0}; //{4, 4, 10}; + vec3_t safety_mins = {0, 0, 0}; //{-4, -4, 0}; + + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, boxmins, boxmaxs); + VectorSubtract(goal->mins, boxmaxs, absmins); + VectorSubtract(goal->maxs, boxmins, absmaxs); + VectorAdd(absmins, goal->origin, absmins); + VectorAdd(absmaxs, goal->origin, absmaxs); + //make the box a little smaller for safety + VectorSubtract(absmaxs, safety_maxs, absmaxs); + VectorSubtract(absmins, safety_mins, absmins); + + for (i = 0; i < 3; i++) + { + if (origin[i] < absmins[i] || origin[i] > absmaxs[i]) return qfalse; + } //end for + return qtrue; +} //end of the function BotTouchingGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotItemGoalInVisButNotVisible(int viewer, vec3_t eye, vec3_t viewangles, bot_goal_t *goal) +{ + aas_entityinfo_t entinfo; + bsp_trace_t trace; + vec3_t middle; + + if (!(goal->flags & GFL_ITEM)) return qfalse; + // + VectorAdd(goal->mins, goal->mins, middle); + VectorScale(middle, 0.5, middle); + VectorAdd(goal->origin, middle, middle); + // + trace = AAS_Trace(eye, NULL, NULL, middle, viewer, CONTENTS_SOLID); + //if the goal middle point is visible + if (trace.fraction >= 1) + { + //the goal entity number doesn't have to be valid + //just assume it's valid + if (goal->entitynum <= 0) + return qfalse; + // + //if the entity data isn't valid + AAS_EntityInfo(goal->entitynum, &entinfo); + //NOTE: for some wacko reason entities are sometimes + // not updated + //if (!entinfo.valid) return qtrue; + if (entinfo.ltime < AAS_Time() - 0.5) + return qtrue; + } //end if + return qfalse; +} //end of the function BotItemGoalInVisButNotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGoalState(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + Com_Memset(gs->goalstack, 0, MAX_GOALSTACK * sizeof(bot_goal_t)); + gs->goalstacktop = 0; + BotResetAvoidGoals(goalstate); +} //end of the function BotResetGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadItemWeights(int goalstate, char *filename) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return BLERR_CANNOTLOADITEMWEIGHTS; + //load the weight configuration + gs->itemweightconfig = ReadWeightConfig(filename); + if (!gs->itemweightconfig) + { + botimport.Print(PRT_FATAL, "couldn't load weights\n"); + return BLERR_CANNOTLOADITEMWEIGHTS; + } //end if + //if there's no item configuration + if (!itemconfig) return BLERR_CANNOTLOADITEMWEIGHTS; + //create the item weight index + gs->itemweightindex = ItemWeightIndex(gs->itemweightconfig, itemconfig); + //everything went ok + return BLERR_NOERROR; +} //end of the function BotLoadItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeItemWeights(int goalstate) +{ + bot_goalstate_t *gs; + + gs = BotGoalStateFromHandle(goalstate); + if (!gs) return; + if (gs->itemweightconfig) FreeWeightConfig(gs->itemweightconfig); + if (gs->itemweightindex) FreeMemory(gs->itemweightindex); +} //end of the function BotFreeItemWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAllocGoalState(int client) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botgoalstates[i]) + { + botgoalstates[i] = GetClearedMemory(sizeof(bot_goalstate_t)); + botgoalstates[i]->client = client; + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocGoalState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeGoalState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "goal state handle %d out of range\n", handle); + return; + } //end if + if (!botgoalstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid goal state handle %d\n", handle); + return; + } //end if + BotFreeItemWeights(handle); + FreeMemory(botgoalstates[handle]); + botgoalstates[handle] = NULL; +} //end of the function BotFreeGoalState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupGoalAI(void) +{ + char *filename; + + //check if teamplay is on + g_gametype = LibVarValue("g_gametype", "0"); + //item configuration file + filename = LibVarString("itemconfig", "items.c"); + //load the item configuration + itemconfig = LoadItemConfig(filename); + if (!itemconfig) + { + botimport.Print(PRT_FATAL, "couldn't load item config\n"); + return BLERR_CANNOTLOADITEMCONFIG; + } //end if + // + droppedweight = LibVar("droppedweight", "1000"); + //everything went ok + return BLERR_NOERROR; +} //end of the function BotSetupGoalAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownGoalAI(void) +{ + int i; + + if (itemconfig) FreeMemory(itemconfig); + itemconfig = NULL; + if (levelitemheap) FreeMemory(levelitemheap); + levelitemheap = NULL; + freelevelitems = NULL; + levelitems = NULL; + numlevelitems = 0; + + BotFreeInfoEntities(); + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botgoalstates[i]) + { + BotFreeGoalState(i); + } //end if + } //end for +} //end of the function BotShutdownGoalAI diff --git a/code/botlib/be_ai_goal.h b/code/botlib/be_ai_goal.h new file mode 100644 index 0000000..80dad08 --- /dev/null +++ b/code/botlib/be_ai_goal.h @@ -0,0 +1,118 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +/***************************************************************************** + * 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/code/botlib/be_ai_move.c b/code/botlib/be_ai_move.c new file mode 100644 index 0000000..e633ebc --- /dev/null +++ b/code/botlib/be_ai_move.c @@ -0,0 +1,3564 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_move.c + * + * desc: bot movement AI + * + * $Archive: /MissionPack/code/botlib/be_ai_move.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" + +#include "be_ea.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" + + +//#define DEBUG_AI_MOVE +//#define DEBUG_ELEVATOR +//#define DEBUG_GRAPPLE + +//movement state +//NOTE: the moveflags MFL_ONGROUND, MFL_TELEPORTED, MFL_WATERJUMP and +// MFL_GRAPPLEPULL must be set outside the movement code +typedef struct bot_movestate_s +{ + //input vars (all set outside the movement code) + 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 + //state vars + int areanum; //area the bot is in + int lastareanum; //last area the bot was in + int lastgoalareanum; //last goal area number + int lastreachnum; //last reachability number + vec3_t lastorigin; //origin previous cycle + int reachareanum; //area number of the reachabilty + int moveflags; //movement flags + int jumpreach; //set when jumped + float grapplevisible_time; //last time the grapple was visible + float lastgrappledist; //last distance to the grapple end + float reachability_time; //time to use current reachability + int avoidreach[MAX_AVOIDREACH]; //reachabilities to avoid + float avoidreachtimes[MAX_AVOIDREACH]; //times to avoid the reachabilities + int avoidreachtries[MAX_AVOIDREACH]; //number of tries before avoiding + // + bot_avoidspot_t avoidspots[MAX_AVOIDSPOTS]; //spots to avoid + int numavoidspots; +} bot_movestate_t; + +//used to avoid reachability links for some time after being used +#define AVOIDREACH +#define AVOIDREACH_TIME 6 //avoid links for 6 seconds after use +#define AVOIDREACH_TRIES 4 +//prediction times +#define PREDICTIONTIME_JUMP 3 //in seconds +#define PREDICTIONTIME_MOVE 2 //in seconds +//weapon indexes for weapon jumping +#define WEAPONINDEX_ROCKET_LAUNCHER 5 +#define WEAPONINDEX_BFG 9 + +#define MODELTYPE_FUNC_PLAT 1 +#define MODELTYPE_FUNC_BOB 2 +#define MODELTYPE_FUNC_DOOR 3 +#define MODELTYPE_FUNC_STATIC 4 + +libvar_t *sv_maxstep; +libvar_t *sv_maxbarrier; +libvar_t *sv_gravity; +libvar_t *weapindex_rocketlauncher; +libvar_t *weapindex_bfg10k; +libvar_t *weapindex_grapple; +libvar_t *entitytypemissile; +libvar_t *offhandgrapple; +libvar_t *cmd_grappleoff; +libvar_t *cmd_grappleon; +//type of model, func_plat or func_bobbing +int modeltypes[MAX_MODELS]; + +bot_movestate_t *botmovestates[MAX_CLIENTS+1]; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocMoveState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botmovestates[i]) + { + botmovestates[i] = GetClearedMemory(sizeof(bot_movestate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeMoveState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return; + } //end if + if (!botmovestates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return; + } //end if + FreeMemory(botmovestates[handle]); + botmovestates[handle] = NULL; +} //end of the function BotFreeMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_movestate_t *BotMoveStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botmovestates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return NULL; + } //end if + return botmovestates[handle]; +} //end of the function BotMoveStateFromHandle +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotInitMoveState(int handle, bot_initmove_t *initmove) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(handle); + if (!ms) return; + VectorCopy(initmove->origin, ms->origin); + VectorCopy(initmove->velocity, ms->velocity); + VectorCopy(initmove->viewoffset, ms->viewoffset); + ms->entitynum = initmove->entitynum; + ms->client = initmove->client; + ms->thinktime = initmove->thinktime; + ms->presencetype = initmove->presencetype; + VectorCopy(initmove->viewangles, ms->viewangles); + // + ms->moveflags &= ~MFL_ONGROUND; + if (initmove->or_moveflags & MFL_ONGROUND) ms->moveflags |= MFL_ONGROUND; + ms->moveflags &= ~MFL_TELEPORTED; + if (initmove->or_moveflags & MFL_TELEPORTED) ms->moveflags |= MFL_TELEPORTED; + ms->moveflags &= ~MFL_WATERJUMP; + if (initmove->or_moveflags & MFL_WATERJUMP) ms->moveflags |= MFL_WATERJUMP; + ms->moveflags &= ~MFL_WALK; + if (initmove->or_moveflags & MFL_WALK) ms->moveflags |= MFL_WALK; + ms->moveflags &= ~MFL_GRAPPLEPULL; + if (initmove->or_moveflags & MFL_GRAPPLEPULL) ms->moveflags |= MFL_GRAPPLEPULL; +} //end of the function BotInitMoveState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +float AngleDiff(float ang1, float ang2) +{ + float diff; + + diff = ang1 - ang2; + if (ang1 > ang2) + { + if (diff > 180.0) diff -= 360.0; + } //end if + else + { + if (diff < -180.0) diff += 360.0; + } //end else + return diff; +} //end of the function AngleDiff +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotFuzzyPointReachabilityArea(vec3_t origin) +{ + int firstareanum, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t points[10], v, end; + + firstareanum = 0; + areanum = AAS_PointAreaNum(origin); + if (areanum) + { + firstareanum = areanum; + if (AAS_AreaReachability(areanum)) return areanum; + } //end if + VectorCopy(origin, end); + end[2] += 4; + numareas = AAS_TraceAreas(origin, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) return areas[j]; + } //end for + bestdist = 999999; + bestareanum = 0; + for (z = 1; z >= -1; z -= 1) + { + for (x = 1; x >= -1; x -= 1) + { + for (y = 1; y >= -1; y -= 1) + { + VectorCopy(origin, end); + end[0] += x * 8; + end[1] += y * 8; + end[2] += z * 12; + numareas = AAS_TraceAreas(origin, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) + { + VectorSubtract(points[j], origin, v); + dist = VectorLength(v); + if (dist < bestdist) + { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + if (!firstareanum) firstareanum = areas[j]; + } //end for + } //end for + } //end for + if (bestareanum) return bestareanum; + } //end for + return firstareanum; +} //end of the function BotFuzzyPointReachabilityArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityArea(vec3_t origin, int client) +{ + int modelnum, modeltype, reachnum, areanum; + aas_reachability_t reach; + vec3_t org, end, mins, maxs, up = {0, 0, 1}; + bsp_trace_t bsptrace; + aas_trace_t trace; + + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox(PRESENCE_CROUCH, mins, maxs); + VectorMA(origin, -3, up, end); + bsptrace = AAS_Trace(origin, mins, maxs, end, client, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!bsptrace.startsolid && bsptrace.fraction < 1 && bsptrace.ent != ENTITYNUM_NONE) + { + //if standing on the world the bot should be in a valid area + if (bsptrace.ent == ENTITYNUM_WORLD) + { + return BotFuzzyPointReachabilityArea(origin); + } //end if + + modelnum = AAS_EntityModelindex(bsptrace.ent); + modeltype = modeltypes[modelnum]; + + //if standing on a func_plat or func_bobbing then the bot is assumed to be + //in the area the reachability points to + if (modeltype == MODELTYPE_FUNC_PLAT || modeltype == MODELTYPE_FUNC_BOB) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + return reach.areanum; + } //end if + } //end else if + + //if the bot is swimming the bot should be in a valid area + if (AAS_Swimming(origin)) + { + return BotFuzzyPointReachabilityArea(origin); + } //end if + // + areanum = BotFuzzyPointReachabilityArea(origin); + //if the bot is in an area with reachabilities + if (areanum && AAS_AreaReachability(areanum)) return areanum; + //trace down till the ground is hit because the bot is standing on some other entity + VectorCopy(origin, org); + VectorCopy(org, end); + end[2] -= 800; + trace = AAS_TraceClientBBox(org, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, org); + } //end if + // + return BotFuzzyPointReachabilityArea(org); + } //end if + // + return BotFuzzyPointReachabilityArea(origin); +} //end of the function BotReachabilityArea +//=========================================================================== +// returns the reachability area the bot is in +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +int BotReachabilityArea(vec3_t origin, int testground) +{ + int firstareanum, i, j, x, y, z; + int areas[10], numareas, areanum, bestareanum; + float dist, bestdist; + vec3_t org, end, points[10], v; + aas_trace_t trace; + + firstareanum = 0; + for (i = 0; i < 2; i++) + { + VectorCopy(origin, org); + //if test at the ground (used when bot is standing on an entity) + if (i > 0) + { + VectorCopy(origin, end); + end[2] -= 800; + trace = AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); + if (!trace.startsolid) + { + VectorCopy(trace.endpos, org); + } //end if + } //end if + + firstareanum = 0; + areanum = AAS_PointAreaNum(org); + if (areanum) + { + firstareanum = areanum; + if (AAS_AreaReachability(areanum)) return areanum; + } //end if + bestdist = 999999; + bestareanum = 0; + for (z = 1; z >= -1; z -= 1) + { + for (x = 1; x >= -1; x -= 1) + { + for (y = 1; y >= -1; y -= 1) + { + VectorCopy(org, end); + end[0] += x * 8; + end[1] += y * 8; + end[2] += z * 12; + numareas = AAS_TraceAreas(org, end, areas, points, 10); + for (j = 0; j < numareas; j++) + { + if (AAS_AreaReachability(areas[j])) + { + VectorSubtract(points[j], org, v); + dist = VectorLength(v); + if (dist < bestdist) + { + bestareanum = areas[j]; + bestdist = dist; + } //end if + } //end if + } //end for + } //end for + } //end for + if (bestareanum) return bestareanum; + } //end for + if (!testground) break; + } //end for +//#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "no reachability area\n"); +//#endif //DEBUG + return firstareanum; +} //end of the function BotReachabilityArea*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnMover(vec3_t origin, int entnum, aas_reachability_t *reach) +{ + int i, modelnum; + vec3_t mins, maxs, modelorigin, org, end; + vec3_t angles = {0, 0, 0}; + vec3_t boxmins = {-16, -16, -8}, boxmaxs = {16, 16, 8}; + bsp_trace_t trace; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + // + if (!AAS_OriginOfMoverWithModelNum(modelnum, modelorigin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + return qfalse; + } //end if + // + for (i = 0; i < 2; i++) + { + if (origin[i] > modelorigin[i] + maxs[i] + 16) return qfalse; + if (origin[i] < modelorigin[i] + mins[i] - 16) return qfalse; + } //end for + // + VectorCopy(origin, org); + org[2] += 24; + VectorCopy(origin, end); + end[2] -= 48; + // + trace = AAS_Trace(org, boxmins, boxmaxs, end, entnum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!trace.startsolid && !trace.allsolid) + { + //NOTE: the reachability face number is the model number of the elevator + if (trace.ent != ENTITYNUM_NONE && AAS_EntityModelNum(trace.ent) == modelnum) + { + return qtrue; + } //end if + } //end if + return qfalse; +} //end of the function BotOnMover +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MoverDown(aas_reachability_t *reach) +{ + int modelnum; + vec3_t mins, maxs, origin; + vec3_t angles = {0, 0, 0}; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + return qfalse; + } //end if + //if the top of the plat is below the reachability start point + if (origin[2] + maxs[2] < reach->start[2]) return qtrue; + return qfalse; +} //end of the function MoverDown +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotSetBrushModelTypes(void) +{ + int ent, modelnum; + char classname[MAX_EPAIRKEY], model[MAX_EPAIRKEY]; + + Com_Memset(modeltypes, 0, MAX_MODELS * sizeof(int)); + // + for (ent = AAS_NextBSPEntity(0); ent; ent = AAS_NextBSPEntity(ent)) + { + if (!AAS_ValueForBSPEpairKey(ent, "classname", classname, MAX_EPAIRKEY)) continue; + if (!AAS_ValueForBSPEpairKey(ent, "model", model, MAX_EPAIRKEY)) continue; + if (model[0]) modelnum = atoi(model+1); + else modelnum = 0; + + if (modelnum < 0 || modelnum > MAX_MODELS) + { + botimport.Print(PRT_MESSAGE, "entity %s model number out of range\n", classname); + continue; + } //end if + + if (!Q_stricmp(classname, "func_bobbing")) + modeltypes[modelnum] = MODELTYPE_FUNC_BOB; + else if (!Q_stricmp(classname, "func_plat")) + modeltypes[modelnum] = MODELTYPE_FUNC_PLAT; + else if (!Q_stricmp(classname, "func_door")) + modeltypes[modelnum] = MODELTYPE_FUNC_DOOR; + else if (!Q_stricmp(classname, "func_static")) + modeltypes[modelnum] = MODELTYPE_FUNC_STATIC; + } //end for +} //end of the function BotSetBrushModelTypes +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotOnTopOfEntity(bot_movestate_t *ms) +{ + vec3_t mins, maxs, end, up = {0, 0, 1}; + bsp_trace_t trace; + + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + VectorMA(ms->origin, -3, up, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) + { + return trace.ent; + } //end if + return -1; +} //end of the function BotOnTopOfEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotValidTravel(vec3_t origin, aas_reachability_t *reach, int travelflags) +{ + //if the reachability uses an unwanted travel type + if (AAS_TravelFlagForType(reach->traveltype) & ~travelflags) return qfalse; + //don't go into areas with bad travel types + if (AAS_AreaContentsTravelFlags(reach->areanum) & ~travelflags) return qfalse; + return qtrue; +} //end of the function BotValidTravel +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddToAvoidReach(bot_movestate_t *ms, int number, float avoidtime) +{ + int i; + + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreach[i] == number) + { + if (ms->avoidreachtimes[i] > AAS_Time()) ms->avoidreachtries[i]++; + else ms->avoidreachtries[i] = 1; + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + return; + } //end if + } //end for + //add the reachability to the reachabilities to avoid for a while + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreachtimes[i] < AAS_Time()) + { + ms->avoidreach[i] = number; + ms->avoidreachtimes[i] = AAS_Time() + avoidtime; + ms->avoidreachtries[i] = 1; + return; + } //end if + } //end for +} //end of the function BotAddToAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2) +{ + vec3_t proj, dir; + int j; + + AAS_ProjectPointOntoVector(p, lp1, lp2, proj); + for (j = 0; j < 3; j++) + if ((proj[j] > lp1[j] && proj[j] > lp2[j]) || + (proj[j] < lp1[j] && proj[j] < lp2[j])) + break; + if (j < 3) { + if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j])) + VectorSubtract(p, lp1, dir); + else + VectorSubtract(p, lp2, dir); + return VectorLengthSquared(dir); + } + VectorSubtract(p, proj, dir); + return VectorLengthSquared(dir); +} //end of the function DistanceFromLineSquared +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float VectorDistanceSquared(vec3_t p1, vec3_t p2) +{ + vec3_t dir; + VectorSubtract(p2, p1, dir); + return VectorLengthSquared(dir); +} //end of the function VectorDistanceSquared +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAvoidSpots(vec3_t origin, aas_reachability_t *reach, bot_avoidspot_t *avoidspots, int numavoidspots) +{ + int checkbetween, i, type; + float squareddist, squaredradius; + + switch(reach->traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: checkbetween = qtrue; break; + case TRAVEL_CROUCH: checkbetween = qtrue; break; + case TRAVEL_BARRIERJUMP: checkbetween = qtrue; break; + case TRAVEL_LADDER: checkbetween = qtrue; break; + case TRAVEL_WALKOFFLEDGE: checkbetween = qfalse; break; + case TRAVEL_JUMP: checkbetween = qfalse; break; + case TRAVEL_SWIM: checkbetween = qtrue; break; + case TRAVEL_WATERJUMP: checkbetween = qtrue; break; + case TRAVEL_TELEPORT: checkbetween = qfalse; break; + case TRAVEL_ELEVATOR: checkbetween = qfalse; break; + case TRAVEL_GRAPPLEHOOK: checkbetween = qfalse; break; + case TRAVEL_ROCKETJUMP: checkbetween = qfalse; break; + case TRAVEL_BFGJUMP: checkbetween = qfalse; break; + case TRAVEL_JUMPPAD: checkbetween = qfalse; break; + case TRAVEL_FUNCBOB: checkbetween = qfalse; break; + default: checkbetween = qtrue; break; + } //end switch + + type = AVOID_CLEAR; + for (i = 0; i < numavoidspots; i++) + { + squaredradius = Square(avoidspots[i].radius); + squareddist = DistanceFromLineSquared(avoidspots[i].origin, origin, reach->start); + // if moving towards the avoid spot + if (squareddist < squaredradius && + VectorDistanceSquared(avoidspots[i].origin, origin) > squareddist) + { + type = avoidspots[i].type; + } //end if + else if (checkbetween) { + squareddist = DistanceFromLineSquared(avoidspots[i].origin, reach->start, reach->end); + // if moving towards the avoid spot + if (squareddist < squaredradius && + VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) + { + type = avoidspots[i].type; + } //end if + } //end if + else + { + VectorDistanceSquared(avoidspots[i].origin, reach->end); + // if the reachability leads closer to the avoid spot + if (squareddist < squaredradius && + VectorDistanceSquared(avoidspots[i].origin, reach->start) > squareddist) + { + type = avoidspots[i].type; + } //end if + } //end else + if (type == AVOID_ALWAYS) + return type; + } //end for + return type; +} //end of the function BotAvoidSpots +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotAddAvoidSpot(int movestate, vec3_t origin, float radius, int type) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + if (type == AVOID_CLEAR) + { + ms->numavoidspots = 0; + return; + } //end if + + if (ms->numavoidspots >= MAX_AVOIDSPOTS) + return; + VectorCopy(origin, ms->avoidspots[ms->numavoidspots].origin); + ms->avoidspots[ms->numavoidspots].radius = radius; + ms->avoidspots[ms->numavoidspots].type = type; + ms->numavoidspots++; +} //end of the function BotAddAvoidSpot +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotGetReachabilityToGoal(vec3_t origin, int areanum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags, + struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags) +{ + int i, t, besttime, bestreachnum, reachnum; + aas_reachability_t reach; + + //if not in a valid area + if (!areanum) return 0; + // + if (AAS_AreaDoNotEnter(areanum) || AAS_AreaDoNotEnter(goal->areanum)) + { + travelflags |= TFL_DONOTENTER; + movetravelflags |= TFL_DONOTENTER; + } //end if + //use the routing to find the next area to go to + besttime = 0; + bestreachnum = 0; + // + for (reachnum = AAS_NextAreaReachability(areanum, 0); reachnum; + reachnum = AAS_NextAreaReachability(areanum, reachnum)) + { +#ifdef AVOIDREACH + //check if it isn't an reachability to avoid + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (avoidreach[i] == reachnum && avoidreachtimes[i] >= AAS_Time()) break; + } //end for + if (i != MAX_AVOIDREACH && avoidreachtries[i] > AVOIDREACH_TRIES) + { +#ifdef DEBUG + if (botDeveloper) + { + botimport.Print(PRT_MESSAGE, "avoiding reachability %d\n", avoidreach[i]); + } //end if +#endif //DEBUG + continue; + } //end if +#endif //AVOIDREACH + //get the reachability from the number + AAS_ReachabilityFromNum(reachnum, &reach); + //NOTE: do not go back to the previous area if the goal didn't change + //NOTE: is this actually avoidance of local routing minima between two areas??? + if (lastgoalareanum == goal->areanum && reach.areanum == lastareanum) continue; + //if (AAS_AreaContentsTravelFlags(reach.areanum) & ~travelflags) continue; + //if the travel isn't valid + if (!BotValidTravel(origin, &reach, movetravelflags)) continue; + //get the travel time + t = AAS_AreaTravelTimeToGoalArea(reach.areanum, reach.end, goal->areanum, travelflags); + //if the goal area isn't reachable from the reachable area + if (!t) continue; + //if the bot should not use this reachability to avoid bad spots + if (BotAvoidSpots(origin, &reach, avoidspots, numavoidspots)) { + if (flags) { + *flags |= MOVERESULT_BLOCKEDBYAVOIDSPOT; + } + continue; + } + //add the travel time towards the area + t += reach.traveltime;// + AAS_AreaTravelTime(areanum, origin, reach.start); + //if the travel time is better than the ones already found + if (!besttime || t < besttime) + { + besttime = t; + bestreachnum = reachnum; + } //end if + } //end for + // + return bestreachnum; +} //end of the function BotGetReachabilityToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAddToTarget(vec3_t start, vec3_t end, float maxdist, float *dist, vec3_t target) +{ + vec3_t dir; + float curdist; + + VectorSubtract(end, start, dir); + curdist = VectorNormalize(dir); + if (*dist + curdist < maxdist) + { + VectorCopy(end, target); + *dist += curdist; + return qfalse; + } //end if + else + { + VectorMA(start, maxdist - *dist, dir, target); + *dist = maxdist; + return qtrue; + } //end else +} //end of the function BotAddToTarget + +int BotMovementViewTarget(int movestate, bot_goal_t *goal, int travelflags, float lookahead, vec3_t target) +{ + aas_reachability_t reach; + int reachnum, lastareanum; + bot_movestate_t *ms; + vec3_t end; + float dist; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return qfalse; + //if the bot has no goal or no last reachability + if (!ms->lastreachnum || !goal) return qfalse; + + reachnum = ms->lastreachnum; + VectorCopy(ms->origin, end); + lastareanum = ms->lastareanum; + dist = 0; + while(reachnum && dist < lookahead) + { + AAS_ReachabilityFromNum(reachnum, &reach); + if (BotAddToTarget(end, reach.start, lookahead, &dist, target)) return qtrue; + //never look beyond teleporters + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_TELEPORT) return qtrue; + //never look beyond the weapon jump point + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ROCKETJUMP) return qtrue; + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_BFGJUMP) return qtrue; + //don't add jump pad distances + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_JUMPPAD && + (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR && + (reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB) + { + if (BotAddToTarget(reach.start, reach.end, lookahead, &dist, target)) return qtrue; + } //end if + reachnum = BotGetReachabilityToGoal(reach.end, reach.areanum, + ms->lastgoalareanum, lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags, NULL, 0, NULL); + VectorCopy(reach.end, end); + lastareanum = reach.areanum; + if (lastareanum == goal->areanum) + { + BotAddToTarget(reach.end, goal->origin, lookahead, &dist, target); + return qtrue; + } //end if + } //end while + // + return qfalse; +} //end of the function BotMovementViewTarget +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotVisible(int ent, vec3_t eye, vec3_t target) +{ + bsp_trace_t trace; + + trace = AAS_Trace(eye, NULL, NULL, target, ent, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (trace.fraction >= 1) return qtrue; + return qfalse; +} //end of the function BotVisible +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotPredictVisiblePosition(vec3_t origin, int areanum, bot_goal_t *goal, int travelflags, vec3_t target) +{ + aas_reachability_t reach; + int reachnum, lastgoalareanum, lastareanum, i; + int avoidreach[MAX_AVOIDREACH]; + float avoidreachtimes[MAX_AVOIDREACH]; + int avoidreachtries[MAX_AVOIDREACH]; + vec3_t end; + + //if the bot has no goal or no last reachability + if (!goal) return qfalse; + //if the areanum is not valid + if (!areanum) return qfalse; + //if the goal areanum is not valid + if (!goal->areanum) return qfalse; + + Com_Memset(avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); + lastgoalareanum = goal->areanum; + lastareanum = areanum; + VectorCopy(origin, end); + //only do 20 hops + for (i = 0; i < 20 && (areanum != goal->areanum); i++) + { + // + reachnum = BotGetReachabilityToGoal(end, areanum, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + goal, travelflags, travelflags, NULL, 0, NULL); + if (!reachnum) return qfalse; + AAS_ReachabilityFromNum(reachnum, &reach); + // + if (BotVisible(goal->entitynum, goal->origin, reach.start)) + { + VectorCopy(reach.start, target); + return qtrue; + } //end if + // + if (BotVisible(goal->entitynum, goal->origin, reach.end)) + { + VectorCopy(reach.end, target); + return qtrue; + } //end if + // + if (reach.areanum == goal->areanum) + { + VectorCopy(reach.end, target); + return qtrue; + } //end if + // + lastareanum = areanum; + areanum = reach.areanum; + VectorCopy(reach.end, end); + // + } //end while + // + return qfalse; +} //end of the function BotPredictVisiblePosition +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void MoverBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter) +{ + int modelnum; + vec3_t mins, maxs, origin, mids; + vec3_t angles = {0, 0, 0}; + + modelnum = reach->facenum & 0x0000FFFF; + //get some bsp model info + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, origin); + // + if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "no entity with model %d\n", modelnum); + } //end if + //get a point just above the plat in the bottom position + VectorAdd(mins, maxs, mids); + VectorMA(origin, 0.5, mids, bottomcenter); + bottomcenter[2] = reach->start[2]; +} //end of the function MoverBottomCenter +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum) +{ + int dist; + float startz; + vec3_t start, end; + aas_trace_t trace; + + //do gap checking + //startz = origin[2]; + //this enables walking down stairs more fluidly + { + VectorCopy(origin, start); + VectorCopy(origin, end); + end[2] -= 60; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); + if (trace.fraction >= 1) return 1; + startz = trace.endpos[2] + 1; + } + // + for (dist = 8; dist <= 100; dist += 8) + { + VectorMA(origin, dist, hordir, start); + start[2] = startz + 24; + VectorCopy(start, end); + end[2] -= 48 + sv_maxbarrier->value; + trace = AAS_TraceClientBBox(start, end, PRESENCE_CROUCH, entnum); + //if solid is found the bot can't walk any further and fall into a gap + if (!trace.startsolid) + { + //if it is a gap + if (trace.endpos[2] < startz - sv_maxstep->value - 8) + { + VectorCopy(trace.endpos, end); + end[2] -= 20; + if (AAS_PointContents(end) & CONTENTS_WATER) break; + //if a gap is found slow down + //botimport.Print(PRT_MESSAGE, "gap at %i\n", dist); + return dist; + } //end if + startz = trace.endpos[2]; + } //end if + } //end for + return 0; +} //end of the function BotGapDistance +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotCheckBarrierJump(bot_movestate_t *ms, vec3_t dir, float speed) +{ + vec3_t start, hordir, end; + aas_trace_t trace; + + VectorCopy(ms->origin, end); + end[2] += sv_maxbarrier->value; + //trace right up + trace = AAS_TraceClientBBox(ms->origin, end, PRESENCE_NORMAL, ms->entitynum); + //this shouldn't happen... but we check anyway + if (trace.startsolid) return qfalse; + //if very low ceiling it isn't possible to jump up to a barrier + if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; + // + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize(hordir); + VectorMA(ms->origin, ms->thinktime * speed * 0.5, hordir, end); + VectorCopy(trace.endpos, start); + end[2] = trace.endpos[2]; + //trace from previous trace end pos horizontally in the move direction + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); + //again this shouldn't happen + if (trace.startsolid) return qfalse; + // + VectorCopy(trace.endpos, start); + VectorCopy(trace.endpos, end); + end[2] = ms->origin[2]; + //trace down from the previous trace end pos + trace = AAS_TraceClientBBox(start, end, PRESENCE_NORMAL, ms->entitynum); + //if solid + if (trace.startsolid) return qfalse; + //if no obstacle at all + if (trace.fraction >= 1.0) return qfalse; + //if less than the maximum step height + if (trace.endpos[2] - ms->origin[2] < sv_maxstep->value) return qfalse; + // + EA_Jump(ms->client); + EA_Move(ms->client, hordir, speed); + ms->moveflags |= MFL_BARRIERJUMP; + //there is a barrier + return qtrue; +} //end of the function BotCheckBarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSwimInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) +{ + vec3_t normdir; + + VectorCopy(dir, normdir); + VectorNormalize(normdir); + EA_Move(ms->client, normdir, speed); + return qtrue; +} //end of the function BotSwimInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotWalkInDirection(bot_movestate_t *ms, vec3_t dir, float speed, int type) +{ + vec3_t hordir, cmdmove, velocity, tmpdir, origin; + int presencetype, maxframes, cmdframes, stopevent; + aas_clientmove_t move; + float dist; + + if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; + //if the bot is on the ground + if (ms->moveflags & MFL_ONGROUND) + { + //if there is a barrier the bot can jump on + if (BotCheckBarrierJump(ms, dir, speed)) return qtrue; + //remove barrier jump flag + ms->moveflags &= ~MFL_BARRIERJUMP; + //get the presence type for the movement + if ((type & MOVE_CROUCH) && !(type & MOVE_JUMP)) presencetype = PRESENCE_CROUCH; + else presencetype = PRESENCE_NORMAL; + //horizontal direction + hordir[0] = dir[0]; + hordir[1] = dir[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //if the bot is not supposed to jump + if (!(type & MOVE_JUMP)) + { + //if there is a gap, try to jump over it + if (BotGapDistance(ms->origin, hordir, ms->entitynum) > 0) type |= MOVE_JUMP; + } //end if + //get command movement + VectorScale(hordir, speed, cmdmove); + VectorCopy(ms->velocity, velocity); + // + if (type & MOVE_JUMP) + { + //botimport.Print(PRT_MESSAGE, "trying jump\n"); + cmdmove[2] = 400; + maxframes = PREDICTIONTIME_JUMP / 0.1; + cmdframes = 1; + stopevent = SE_HITGROUND|SE_HITGROUNDDAMAGE| + SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; + } //end if + else + { + maxframes = 2; + cmdframes = 2; + stopevent = SE_HITGROUNDDAMAGE| + SE_ENTERWATER|SE_ENTERSLIME|SE_ENTERLAVA; + } //end else + //AAS_ClearShownDebugLines(); + // + VectorCopy(ms->origin, origin); + origin[2] += 0.5; + AAS_PredictClientMovement(&move, ms->entitynum, origin, presencetype, qtrue, + velocity, cmdmove, cmdframes, maxframes, 0.1f, + stopevent, 0, qfalse);//qtrue); + //if prediction time wasn't enough to fully predict the movement + if (move.frames >= maxframes && (type & MOVE_JUMP)) + { + //botimport.Print(PRT_MESSAGE, "client %d: max prediction frames\n", ms->client); + return qfalse; + } //end if + //don't enter slime or lava and don't fall from too high + if (move.stopevent & (SE_ENTERSLIME|SE_ENTERLAVA|SE_HITGROUNDDAMAGE)) + { + //botimport.Print(PRT_MESSAGE, "client %d: would be hurt ", ms->client); + //if (move.stopevent & SE_ENTERSLIME) botimport.Print(PRT_MESSAGE, "slime\n"); + //if (move.stopevent & SE_ENTERLAVA) botimport.Print(PRT_MESSAGE, "lava\n"); + //if (move.stopevent & SE_HITGROUNDDAMAGE) botimport.Print(PRT_MESSAGE, "hitground\n"); + return qfalse; + } //end if + //if ground was hit + if (move.stopevent & SE_HITGROUND) + { + //check for nearby gap + VectorNormalize2(move.velocity, tmpdir); + dist = BotGapDistance(move.endpos, tmpdir, ms->entitynum); + if (dist > 0) return qfalse; + // + dist = BotGapDistance(move.endpos, hordir, ms->entitynum); + if (dist > 0) return qfalse; + } //end if + //get horizontal movement + tmpdir[0] = move.endpos[0] - ms->origin[0]; + tmpdir[1] = move.endpos[1] - ms->origin[1]; + tmpdir[2] = 0; + // + //AAS_DrawCross(move.endpos, 4, LINECOLOR_BLUE); + //the bot is blocked by something + if (VectorLength(tmpdir) < speed * ms->thinktime * 0.5) return qfalse; + //perform the movement + if (type & MOVE_JUMP) EA_Jump(ms->client); + if (type & MOVE_CROUCH) EA_Crouch(ms->client); + EA_Move(ms->client, hordir, speed); + //movement was succesfull + return qtrue; + } //end if + else + { + if (ms->moveflags & MFL_BARRIERJUMP) + { + //if near the top or going down + if (ms->velocity[2] < 50) + { + EA_Move(ms->client, dir, speed); + } //end if + } //end if + //FIXME: do air control to avoid hazards + return qtrue; + } //end else +} //end of the function BotWalkInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotMoveInDirection(int movestate, vec3_t dir, float speed, int type) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return qfalse; + //if swimming + if (AAS_Swimming(ms->origin)) + { + return BotSwimInDirection(ms, dir, speed, type); + } //end if + else + { + return BotWalkInDirection(ms, dir, speed, type); + } //end else +} //end of the function BotMoveInDirection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Intersection(vec2_t p1, vec2_t p2, vec2_t p3, vec2_t p4, vec2_t out) +{ + float x1, dx1, dy1, x2, dx2, dy2, d; + + dx1 = p2[0] - p1[0]; + dy1 = p2[1] - p1[1]; + dx2 = p4[0] - p3[0]; + dy2 = p4[1] - p3[1]; + + d = dy1 * dx2 - dx1 * dy2; + if (d != 0) + { + x1 = p1[1] * dx1 - p1[0] * dy1; + x2 = p3[1] * dx2 - p3[0] * dy2; + out[0] = (int) ((dx1 * x2 - dx2 * x1) / d); + out[1] = (int) ((dy1 * x2 - dy2 * x1) / d); + return qtrue; + } //end if + else + { + return qfalse; + } //end else +} //end of the function Intersection +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotCheckBlocked(bot_movestate_t *ms, vec3_t dir, int checkbottom, bot_moveresult_t *result) +{ + vec3_t mins, maxs, end, up = {0, 0, 1}; + bsp_trace_t trace; + + //test for entities obstructing the bot's path + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + // + if (fabs(DotProduct(dir, up)) < 0.7) + { + mins[2] += sv_maxstep->value; //if the bot can step on + maxs[2] -= 10; //a little lower to avoid low ceiling + } //end if + VectorMA(ms->origin, 3, dir, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY); + //if not started in solid and not hitting the world entity + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) + { + result->blocked = qtrue; + result->blockentity = trace.ent; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + //if not in an area with reachability + else if (checkbottom && !AAS_AreaReachability(ms->areanum)) + { + //check if the bot is standing on something + AAS_PresenceTypeBoundingBox(ms->presencetype, mins, maxs); + VectorMA(ms->origin, -3, up, end); + trace = AAS_Trace(ms->origin, mins, maxs, end, ms->entitynum, CONTENTS_SOLID|CONTENTS_PLAYERCLIP); + if (!trace.startsolid && (trace.ent != ENTITYNUM_WORLD && trace.ent != ENTITYNUM_NONE) ) + { + result->blocked = qtrue; + result->blockentity = trace.ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%d: BotCheckBlocked: I'm blocked\n", ms->client); +#endif //DEBUG + } //end if + } //end else +} //end of the function BotCheckBlocked +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t_cleared( result ); + + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + // + if (dist < 10) + { + //walk straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + } //end if + //if going towards a crouch area + if (!(AAS_AreaPresenceType(reach->areanum) & PRESENCE_NORMAL)) + { + //if pretty close to the reachable area + if (dist < 20) EA_Crouch(ms->client); + } //end if + // + dist = BotGapDistance(ms->origin, hordir, ms->entitynum); + // + if (ms->moveflags & MFL_WALK) + { + if (dist > 0) speed = 200 - (180 - 1 * dist); + else speed = 200; + EA_Walk(ms->client); + } //end if + else + { + if (dist > 0) speed = 400 - (360 - 2 * dist); + else speed = 400; + } //end else + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Walk(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t_cleared( result ); + //if not on the ground and changed areas... don't walk back!! + //(doesn't seem to help) + /* + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + if (ms->areanum == reach->areanum) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "BotFinishTravel_Walk: already in reach area\n"); +#endif //DEBUG + return result; + } //end if*/ + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 100) dist = 100; + speed = 400 - (400 - 3 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Crouch(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float speed; + vec3_t hordir; + bot_moveresult_t_cleared( result ); + + // + speed = 400; + //walk straight to reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary actions + EA_Crouch(ms->client); + EA_Move(ms->client, hordir, speed); + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float dist, speed; + vec3_t hordir; + bot_moveresult_t_cleared( result ); + + //walk straight to reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //if pretty close to the barrier + if (dist < 9) + { + EA_Jump(ms->client); + } //end if + else + { + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_BarrierJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + bot_moveresult_t_cleared( result ); + + //if near the top or going down + if (ms->velocity[2] < 250) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + // + BotCheckBlocked(ms, hordir, qtrue, &result); + // + EA_Move(ms->client, hordir, 400); + VectorCopy(hordir, result.movedir); + } //end if + // + return result; +} //end of the function BotFinishTravel_BarrierJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Swim(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir; + bot_moveresult_t_cleared( result ); + + //swim straight to reachability end + VectorSubtract(reach->start, ms->origin, dir); + VectorNormalize(dir); + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary actions + EA_Move(ms->client, dir, 400); + // + VectorCopy(dir, result.movedir); + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_SWIMVIEW; + // + return result; +} //end of the function BotTravel_Swim +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, hordir; + float dist; + bot_moveresult_t_cleared( result ); + + //swim straight to reachability end + VectorSubtract(reach->end, ms->origin, dir); + VectorCopy(dir, hordir); + hordir[2] = 0; + dir[2] += 15 + crandom() * 40; + //botimport.Print(PRT_MESSAGE, "BotTravel_WaterJump: dir[2] = %f\n", dir[2]); + VectorNormalize(dir); + dist = VectorNormalize(hordir); + //elemantary actions + //EA_Move(ms->client, dir, 400); + EA_MoveForward(ms->client); + //move up if close to the actual out of water jump spot + if (dist < 40) EA_MoveUp(ms->client); + //set the ideal view angles + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WaterJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, pnt; + bot_moveresult_t_cleared( result ); + + //botimport.Print(PRT_MESSAGE, "BotFinishTravel_WaterJump\n"); + //if waterjumping there's nothing to do + if (ms->moveflags & MFL_WATERJUMP) return result; + //if not touching any water anymore don't do anything + //otherwise the bot sometimes keeps jumping? + VectorCopy(ms->origin, pnt); + pnt[2] -= 32; //extra for q2dm4 near red armor/mega health + if (!(AAS_PointContents(pnt) & (CONTENTS_LAVA|CONTENTS_SLIME|CONTENTS_WATER))) return result; + //swim straight to reachability end + VectorSubtract(reach->end, ms->origin, dir); + dir[0] += crandom() * 10; + dir[1] += crandom() * 10; + dir[2] += 70 + crandom() * 10; + //elemantary actions + EA_Move(ms->client, dir, 400); + //set the ideal view angles + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WaterJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir; + float dist, speed, reachhordist; + bot_moveresult_t_cleared( result ); + + //check if the bot is blocked by anything + VectorSubtract(reach->start, ms->origin, dir); + VectorNormalize(dir); + BotCheckBlocked(ms, dir, qtrue, &result); + //if the reachability start and end are practially above each other + VectorSubtract(reach->end, reach->start, dir); + dir[2] = 0; + reachhordist = VectorLength(dir); + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + //if pretty close to the start focus on the reachability end + if (dist < 48) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (reachhordist < 20) + { + speed = 100; + } //end if + else if (!AAS_HorizontalVelocityForJump(0, reach->start, reach->end, &speed)) + { + speed = 400; + } //end if + } //end if + else + { + if (reachhordist < 20) + { + if (dist > 64) dist = 64; + speed = 400 - (256 - 4 * dist); + } //end if + else + { + speed = 400; + } //end else + } //end else + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary action + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotAirControl(vec3_t origin, vec3_t velocity, vec3_t goal, vec3_t dir, float *speed) +{ + vec3_t org, vel; + float dist; + int i; + + VectorCopy(origin, org); + VectorScale(velocity, 0.1, vel); + for (i = 0; i < 50; i++) + { + vel[2] -= sv_gravity->value * 0.01; + //if going down and next position would be below the goal + if (vel[2] < 0 && org[2] + vel[2] < goal[2]) + { + VectorScale(vel, (goal[2] - org[2]) / vel[2], vel); + VectorAdd(org, vel, org); + VectorSubtract(goal, org, dir); + dist = VectorNormalize(dir); + if (dist > 32) dist = 32; + *speed = 400 - (400 - 13 * dist); + return qtrue; + } //end if + else + { + VectorAdd(org, vel, org); + } //end else + } //end for + VectorSet(dir, 0, 0, 0); + *speed = 400; + return qfalse; +} //end of the function BotAirControl +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WalkOffLedge(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, hordir, end, v; + float dist, speed; + bot_moveresult_t_cleared( result ); + + // + VectorSubtract(reach->end, ms->origin, dir); + BotCheckBlocked(ms, dir, qtrue, &result); + // + VectorSubtract(reach->end, ms->origin, v); + v[2] = 0; + dist = VectorNormalize(v); + if (dist > 16) VectorMA(reach->end, 16, v, end); + else VectorCopy(reach->end, end); + // + if (!BotAirControl(ms->origin, ms->velocity, end, hordir, &speed)) + { + //go straight to the reachability end + VectorCopy(dir, hordir); + hordir[2] = 0; + // + speed = 400; + } //end if + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WalkOffLedge +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, gapdist, speed, horspeed, sv_jumpvel; + bot_moveresult_t_cleared( result ); + + // + sv_jumpvel = botlibglobals.sv_jumpvel->value; + //walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + speed = 350; + // + gapdist = BotGapDistance(ms, hordir, ms->entitynum); + //if pretty close to the start focus on the reachability end + if (dist < 50 || (gapdist && gapdist < 50)) + { + //NOTE: using max speed (400) works best + //if (AAS_HorizontalVelocityForJump(sv_jumpvel, ms->origin, reach->end, &horspeed)) + //{ + // speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + //} //end if + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + // + ms->jumpreach = ms->lastreachnum; + speed = 600; + } //end if + else + { + if (AAS_HorizontalVelocityForJump(sv_jumpvel, reach->start, reach->end, &horspeed)) + { + speed = horspeed * 400 / botlibglobals.sv_maxwalkvelocity->value; + } //end if + } //end else + //elemantary action + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +/* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir1, dir2, mins, maxs, start, end; + int gapdist; + float dist1, dist2, speed; + bot_moveresult_t_cleared( result ); + bsp_trace_t trace; + + // + hordir[0] = reach->start[0] - reach->end[0]; + hordir[1] = reach->start[1] - reach->end[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + //minus back the bouding box size plus 16 + VectorMA(reach->start, 80, hordir, end); + // + AAS_PresenceTypeBoundingBox(PRESENCE_NORMAL, mins, maxs); + //check for solids + trace = AAS_Trace(start, mins, maxs, end, ms->entitynum, MASK_PLAYERSOLID); + if (trace.startsolid) VectorCopy(start, trace.endpos); + //check for a gap + for (gapdist = 0; gapdist < 80; gapdist += 10) + { + VectorMA(start, gapdist+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (gapdist < 80) VectorMA(reach->start, gapdist, hordir, trace.endpos); +// dist1 = BotGapDistance(start, hordir, ms->entitynum); +// if (dist1 && dist1 <= trace.fraction * 80) VectorMA(reach->start, dist1-20, hordir, trace.endpos); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, trace.endpos, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run to point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Move(ms->client, hordir, 600); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + //botimport.Print(PRT_MESSAGE, "going towards run to point\n"); + hordir[0] = trace.endpos[0] - ms->origin[0]; + hordir[1] = trace.endpos[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +//* +bot_moveresult_t BotTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, dir1, dir2, start, end, runstart; +// vec3_t runstart, dir1, dir2, hordir; + int gapdist; + float dist1, dist2, speed; + bot_moveresult_t_cleared( result ); + + // + AAS_JumpReachRunStart(reach, runstart); + //* + hordir[0] = runstart[0] - reach->start[0]; + hordir[1] = runstart[1] - reach->start[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + VectorCopy(reach->start, start); + start[2] += 1; + VectorMA(reach->start, 80, hordir, runstart); + //check for a gap + for (gapdist = 0; gapdist < 80; gapdist += 10) + { + VectorMA(start, gapdist+10, hordir, end); + end[2] += 1; + if (AAS_PointAreaNum(end) != ms->reachareanum) break; + } //end for + if (gapdist < 80) VectorMA(reach->start, gapdist, hordir, runstart); + // + VectorSubtract(ms->origin, reach->start, dir1); + dir1[2] = 0; + dist1 = VectorNormalize(dir1); + VectorSubtract(ms->origin, runstart, dir2); + dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if just before the reachability start + if (DotProduct(dir1, dir2) < -0.8 || dist2 < 5) + { +// botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + if (dist1 < 24) EA_Jump(ms->client); + else if (dist1 < 32) EA_DelayedJump(ms->client); + EA_Move(ms->client, hordir, 600); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { +// botimport.Print(PRT_MESSAGE, "going towards run start point\n"); + hordir[0] = runstart[0] - ms->origin[0]; + hordir[1] = runstart[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + // + if (dist2 > 80) dist2 = 80; + speed = 400 - (400 - 5 * dist2); + EA_Move(ms->client, hordir, speed); + } //end else + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_Jump*/ +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Jump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir, hordir2; + float speed, dist; + bot_moveresult_t_cleared( result ); + + //if not jumped yet + if (!ms->jumpreach) return result; + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + hordir2[0] = reach->end[0] - reach->start[0]; + hordir2[1] = reach->end[1] - reach->start[1]; + hordir2[2] = 0; + VectorNormalize(hordir2); + // + if (DotProduct(hordir, hordir2) < -0.5 && dist < 24) return result; + //always use max speed when traveling through the air + speed = 800; + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Ladder(bot_movestate_t *ms, aas_reachability_t *reach) +{ + //float dist, speed; + vec3_t dir, viewdir;//, hordir; + vec3_t origin = {0, 0, 0}; +// vec3_t up = {0, 0, 1}; + bot_moveresult_t_cleared( result ); + + // +// if ((ms->moveflags & MFL_AGAINSTLADDER)) + //NOTE: not a good idea for ladders starting in water + // || !(ms->moveflags & MFL_ONGROUND)) + { + //botimport.Print(PRT_MESSAGE, "against ladder or not on ground\n"); + VectorSubtract(reach->end, ms->origin, dir); + VectorNormalize(dir); + //set the ideal view angles, facing the ladder up or down + viewdir[0] = dir[0]; + viewdir[1] = dir[1]; + viewdir[2] = 3 * dir[2]; + Vector2Angles(viewdir, result.ideal_viewangles); + //elemantary action + EA_Move(ms->client, origin, 0); + EA_MoveForward(ms->client); + //set movement view flag so the AI can see the view is focussed + result.flags |= MOVERESULT_MOVEMENTVIEW; + } //end if +/* else + { + //botimport.Print(PRT_MESSAGE, "moving towards ladder\n"); + VectorSubtract(reach->end, ms->origin, dir); + //make sure the horizontal movement is large anough + VectorCopy(dir, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + dir[0] = hordir[0]; + dir[1] = hordir[1]; + if (dir[2] > 0) dir[2] = 1; + else dir[2] = -1; + if (dist > 50) dist = 50; + speed = 400 - (200 - 4 * dist); + EA_Move(ms->client, dir, speed); + } //end else*/ + //save the movement direction + VectorCopy(dir, result.movedir); + // + return result; +} //end of the function BotTravel_Ladder +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Teleport(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist; + bot_moveresult_t_cleared( result ); + + //if the bot is being teleported + if (ms->moveflags & MFL_TELEPORTED) return result; + + //walk straight to center of the teleporter + VectorSubtract(reach->start, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; + dist = VectorNormalize(hordir); + // + BotCheckBlocked(ms, hordir, qtrue, &result); + + if (dist < 30) EA_Move(ms->client, hordir, 200); + else EA_Move(ms->client, hordir, 400); + + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + + VectorCopy(hordir, result.movedir); + return result; +} //end of the function BotTravel_Teleport +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, dir1, dir2, hordir, bottomcenter; + float dist, dist1, dist2, speed; + bot_moveresult_t_cleared( result ); + + //if standing on the plat + if (BotOnMover(ms->origin, ms->entitynum, reach)) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot on elevator\n"); +#endif //DEBUG_ELEVATOR + //if vertically not too far from the end point + if (abs(ms->origin[2] - reach->end[2]) < sv_maxbarrier->value) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to end\n"); +#endif //DEBUG_ELEVATOR + //move to the end point + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + if (!BotCheckBarrierJump(ms, hordir, 100)) + { + EA_Move(ms->client, hordir, 400); + } //end if + VectorCopy(hordir, result.movedir); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 10) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to center\n"); +#endif //DEBUG_ELEVATOR + //move to the center of the plat + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot not on elevator\n"); +#endif //DEBUG_ELEVATOR + //if very near the reachability end + VectorSubtract(reach->end, ms->origin, dir); + dist = VectorLength(dir); + if (dist < 64) + { + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract(reach->start, ms->origin, dir1); + if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; + dist1 = VectorNormalize(dir1); + //if the elevator isn't down + if (!MoverDown(reach)) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "elevator not down\n"); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy(dir1, dir); + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //this isn't a failure... just wait till the elevator comes down + result.type = RESULTTYPE_ELEVATORUP; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to elevator bottom center + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, dir2); + if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and elevator center + if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to center\n"); +#endif //DEBUG_ELEVATOR + dist = dist2; + VectorCopy(dir2, dir); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_ELEVATOR + botimport.Print(PRT_MESSAGE, "bot moving to start\n"); +#endif //DEBUG_ELEVATOR + dist = dist1; + VectorCopy(dir1, dir); + } //end else + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 400 - (400 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + } //end else + return result; +} //end of the function BotTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_Elevator(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t bottomcenter, bottomdir, topdir; + bot_moveresult_t_cleared( result ); + + // + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, bottomdir); + // + VectorSubtract(reach->end, ms->origin, topdir); + // + if (fabs(bottomdir[2]) < fabs(topdir[2])) + { + VectorNormalize(bottomdir); + EA_Move(ms->client, bottomdir, 300); + } //end if + else + { + VectorNormalize(topdir); + EA_Move(ms->client, topdir, 300); + } //end else + return result; +} //end of the function BotFinishTravel_Elevator +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFuncBobStartEnd(aas_reachability_t *reach, vec3_t start, vec3_t end, vec3_t origin) +{ + int spawnflags, modelnum; + vec3_t mins, maxs, mid, angles = {0, 0, 0}; + int num0, num1; + + modelnum = reach->facenum & 0x0000FFFF; + if (!AAS_OriginOfMoverWithModelNum(modelnum, origin)) + { + botimport.Print(PRT_MESSAGE, "BotFuncBobStartEnd: no entity with model %d\n", modelnum); + VectorSet(start, 0, 0, 0); + VectorSet(end, 0, 0, 0); + return; + } //end if + AAS_BSPModelMinsMaxsOrigin(modelnum, angles, mins, maxs, NULL); + VectorAdd(mins, maxs, mid); + VectorScale(mid, 0.5, mid); + VectorCopy(mid, start); + VectorCopy(mid, end); + spawnflags = reach->facenum >> 16; + num0 = reach->edgenum >> 16; + if (num0 > 0x00007FFF) num0 |= 0xFFFF0000; + num1 = reach->edgenum & 0x0000FFFF; + if (num1 > 0x00007FFF) num1 |= 0xFFFF0000; + if (spawnflags & 1) + { + start[0] = num0; + end[0] = num1; + // + origin[0] += mid[0]; + origin[1] = mid[1]; + origin[2] = mid[2]; + } //end if + else if (spawnflags & 2) + { + start[1] = num0; + end[1] = num1; + // + origin[0] = mid[0]; + origin[1] += mid[1]; + origin[2] = mid[2]; + } //end else if + else + { + start[2] = num0; + end[2] = num1; + // + origin[0] = mid[0]; + origin[1] = mid[1]; + origin[2] += mid[2]; + } //end else +} //end of the function BotFuncBobStartEnd +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t dir, dir1, dir2, hordir, bottomcenter, bob_start, bob_end, bob_origin; + float dist, dist1, dist2, speed; + bot_moveresult_t_cleared( result ); + + // + BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); + //if standing ontop of the func_bobbing + if (BotOnMover(ms->origin, ms->entitynum, reach)) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot on func_bobbing\n"); +#endif + //if near end point of reachability + VectorSubtract(bob_origin, bob_end, dir); + if (VectorLength(dir) < 24) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to reachability end\n"); +#endif + //move to the end point + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + if (!BotCheckBarrierJump(ms, hordir, 100)) + { + EA_Move(ms->client, hordir, 400); + } //end if + VectorCopy(hordir, result.movedir); + } //end else + //if not really close to the center of the elevator + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 10) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); +#endif + //move to the center of the plat + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + } //end if + else + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot not ontop of func_bobbing\n"); +#endif + //if very near the reachability end + VectorSubtract(reach->end, ms->origin, dir); + dist = VectorLength(dir); + if (dist < 64) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to end\n"); +#endif + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + //if swimming or no barrier jump + if ((ms->moveflags & MFL_SWIMMING) || !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //stop using this reachability + ms->reachability_time = 0; + return result; + } //end if + //get direction and distance to reachability start + VectorSubtract(reach->start, ms->origin, dir1); + if (!(ms->moveflags & MFL_SWIMMING)) dir1[2] = 0; + dist1 = VectorNormalize(dir1); + //if func_bobbing is Not its start position + VectorSubtract(bob_origin, bob_start, dir); + if (VectorLength(dir) > 16) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "func_bobbing not at start\n"); +#endif + dist = dist1; + VectorCopy(dir1, dir); + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + if (speed > 5) EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + //this isn't a failure... just wait till the func_bobbing arrives + result.type = RESULTTYPE_WAITFORFUNCBOBBING; + result.flags |= MOVERESULT_WAITING; + return result; + } //end if + //get direction and distance to func_bob bottom center + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, dir2); + if (!(ms->moveflags & MFL_SWIMMING)) dir2[2] = 0; + dist2 = VectorNormalize(dir2); + //if very close to the reachability start or + //closer to the elevator center or + //between reachability start and func_bobbing center + if (dist1 < 20 || dist2 < dist1 || DotProduct(dir1, dir2) < 0) + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to func_bobbing center\n"); +#endif + dist = dist2; + VectorCopy(dir2, dir); + } //end if + else //closer to the reachability start + { +#ifdef DEBUG_FUNCBOB + botimport.Print(PRT_MESSAGE, "bot moving to reachability start\n"); +#endif + dist = dist1; + VectorCopy(dir1, dir); + } //end else + // + BotCheckBlocked(ms, dir, qfalse, &result); + // + if (dist > 60) dist = 60; + speed = 400 - (400 - 6 * dist); + // + if (!(ms->moveflags & MFL_SWIMMING) && !BotCheckBarrierJump(ms, dir, 50)) + { + EA_Move(ms->client, dir, speed); + } //end if + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + } //end else + return result; +} //end of the function BotTravel_FuncBobbing +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_FuncBobbing(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t bob_origin, bob_start, bob_end, dir, hordir, bottomcenter; + bot_moveresult_t_cleared( result ); + float dist, speed; + + // + BotFuncBobStartEnd(reach, bob_start, bob_end, bob_origin); + // + VectorSubtract(bob_origin, bob_end, dir); + dist = VectorLength(dir); + //if the func_bobbing is near the end + if (dist < 16) + { + VectorSubtract(reach->end, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 60) dist = 60; + speed = 360 - (360 - 6 * dist); + // + if (speed > 5) EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) result.flags |= MOVERESULT_SWIMVIEW; + } //end if + else + { + MoverBottomCenter(reach, bottomcenter); + VectorSubtract(bottomcenter, ms->origin, hordir); + if (!(ms->moveflags & MFL_SWIMMING)) hordir[2] = 0; + dist = VectorNormalize(hordir); + // + if (dist > 5) + { + //move to the center of the plat + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + } //end if + } //end else + return result; +} //end of the function BotFinishTravel_FuncBobbing +//=========================================================================== +// 0 no valid grapple hook visible +// 1 the grapple hook is still flying +// 2 the grapple hooked into a wall +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int GrappleState(bot_movestate_t *ms, aas_reachability_t *reach) +{ + int i; + aas_entityinfo_t entinfo; + + //if the grapple hook is pulling + if (ms->moveflags & MFL_GRAPPLEPULL) + return 2; + //check for a visible grapple missile entity + //or visible grapple entity + for (i = AAS_NextEntity(0); i; i = AAS_NextEntity(i)) + { + if (AAS_EntityType(i) == (int) entitytypemissile->value) + { + AAS_EntityInfo(i, &entinfo); + if (entinfo.weapon == (int) weapindex_grapple->value) + { + return 1; + } //end if + } //end if + } //end for + //no valid grapple at all + return 0; +} //end of the function GrappleState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetGrapple(bot_movestate_t *ms) +{ + aas_reachability_t reach; + + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if not using the grapple hook reachability anymore + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_GRAPPLEHOOK) + { + if ((ms->moveflags & MFL_ACTIVEGRAPPLE) || ms->grapplevisible_time) + { + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->grapplevisible_time = 0; +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "reset grapple\n"); +#endif //DEBUG_GRAPPLE + } //end if + } //end if +} //end of the function BotResetGrapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_Grapple(bot_movestate_t *ms, aas_reachability_t *reach) +{ + bot_moveresult_t_cleared( result ); + float dist, speed; + vec3_t dir, viewdir, org; + int state, areanum; + bsp_trace_t trace; + +#ifdef DEBUG_GRAPPLE + static int debugline; + if (!debugline) debugline = botimport.DebugLineCreate(); + botimport.DebugLineShow(debugline, reach->start, reach->end, LINECOLOR_BLUE); +#endif //DEBUG_GRAPPLE + + // + if (ms->moveflags & MFL_GRAPPLERESET) + { + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + return result; + } //end if + // + if (!(int) offhandgrapple->value) + { + result.weapon = weapindex_grapple->value; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + } //end if + // + if (ms->moveflags & MFL_ACTIVEGRAPPLE) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: active grapple\n"); +#endif //DEBUG_GRAPPLE + // + state = GrappleState(ms, reach); + // + VectorSubtract(reach->end, ms->origin, dir); + dir[2] = 0; + dist = VectorLength(dir); + //if very close to the grapple end or the grappled is hooked and + //the bot doesn't get any closer + if (state && dist < 48) + { + if (ms->lastgrappledist - dist < 1) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_ERROR, "grapple normal end\n"); +#endif //DEBUG_GRAPPLE + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability + return result; + } //end if + } //end if + //if no valid grapple at all, or the grapple hooked and the bot + //isn't moving anymore + else if (!state || (state == 2 && dist > ms->lastgrappledist - 2)) + { + if (ms->grapplevisible_time < AAS_Time() - 0.4) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_ERROR, "grapple not visible\n"); +#endif //DEBUG_GRAPPLE + if (offhandgrapple->value) + EA_Command(ms->client, cmd_grappleoff->string); + ms->moveflags &= ~MFL_ACTIVEGRAPPLE; + ms->moveflags |= MFL_GRAPPLERESET; + ms->reachability_time = 0; //end the reachability + return result; + } //end if + } //end if + else + { + ms->grapplevisible_time = AAS_Time(); + } //end else + // + if (!(int) offhandgrapple->value) + { + EA_Attack(ms->client); + } //end if + //remember the current grapple distance + ms->lastgrappledist = dist; + } //end if + else + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: inactive grapple\n"); +#endif //DEBUG_GRAPPLE + // + ms->grapplevisible_time = AAS_Time(); + // + VectorSubtract(reach->start, ms->origin, dir); + if (!(ms->moveflags & MFL_SWIMMING)) dir[2] = 0; + VectorAdd(ms->origin, ms->viewoffset, org); + VectorSubtract(reach->end, org, viewdir); + // + dist = VectorNormalize(dir); + Vector2Angles(viewdir, result.ideal_viewangles); + result.flags |= MOVERESULT_MOVEMENTVIEW; + // + if (dist < 5 && + fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 2 && + fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 2) + { +#ifdef DEBUG_GRAPPLE + botimport.Print(PRT_MESSAGE, "BotTravel_Grapple: activating grapple\n"); +#endif //DEBUG_GRAPPLE + //check if the grapple missile path is clear + VectorAdd(ms->origin, ms->viewoffset, org); + trace = AAS_Trace(org, NULL, NULL, reach->end, ms->entitynum, CONTENTS_SOLID); + VectorSubtract(reach->end, trace.endpos, dir); + if (VectorLength(dir) > 16) + { + result.failure = qtrue; + return result; + } //end if + //activate the grapple + if (offhandgrapple->value) + { + EA_Command(ms->client, cmd_grappleon->string); + } //end if + else + { + EA_Attack(ms->client); + } //end else + ms->moveflags |= MFL_ACTIVEGRAPPLE; + ms->lastgrappledist = 999999; + } //end if + else + { + if (dist < 70) speed = 300 - (300 - 4 * dist); + else speed = 400; + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + } //end else + //if in another area before actually grappling + areanum = AAS_PointAreaNum(ms->origin); + if (areanum && areanum != ms->reachareanum) ms->reachability_time = 0; + } //end else + return result; +} //end of the function BotTravel_Grapple +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_RocketJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t_cleared( result ); + + //botimport.Print(PRT_MESSAGE, "BotTravel_RocketJump: bah\n"); + // + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + // + if (dist < 5 && + fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && + fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + EA_Attack(ms->client); + EA_Move(ms->client, hordir, 800); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + if (dist > 80) dist = 80; + speed = 400 - (400 - 5 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + //set the view angles directly + EA_View(ms->client, result.ideal_viewangles); + //view is important for the movment + result.flags |= MOVERESULT_MOVEMENTVIEWSET; + //select the rocket launcher + EA_SelectWeapon(ms->client, (int) weapindex_rocketlauncher->value); + //weapon is used for movement + result.weapon = (int) weapindex_rocketlauncher->value; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_RocketJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_BFGJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float dist, speed; + bot_moveresult_t_cleared( result ); + + //botimport.Print(PRT_MESSAGE, "BotTravel_BFGJump: bah\n"); + // + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + // + dist = VectorNormalize(hordir); + // + if (dist < 5 && + fabs(AngleDiff(result.ideal_viewangles[0], ms->viewangles[0])) < 5 && + fabs(AngleDiff(result.ideal_viewangles[1], ms->viewangles[1])) < 5) + { + //botimport.Print(PRT_MESSAGE, "between jump start and run start point\n"); + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //elemantary action jump + EA_Jump(ms->client); + EA_Attack(ms->client); + EA_Move(ms->client, hordir, 800); + // + ms->jumpreach = ms->lastreachnum; + } //end if + else + { + if (dist > 80) dist = 80; + speed = 400 - (400 - 5 * dist); + EA_Move(ms->client, hordir, speed); + } //end else + //look in the movement direction + Vector2Angles(hordir, result.ideal_viewangles); + //look straight down + result.ideal_viewangles[PITCH] = 90; + //set the view angles directly + EA_View(ms->client, result.ideal_viewangles); + //view is important for the movment + result.flags |= MOVERESULT_MOVEMENTVIEWSET; + //select the rocket launcher + EA_SelectWeapon(ms->client, (int) weapindex_bfg10k->value); + //weapon is used for movement + result.weapon = (int) weapindex_bfg10k->value; + result.flags |= MOVERESULT_MOVEMENTWEAPON; + // + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_BFGJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_WeaponJump(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + float speed; + bot_moveresult_t_cleared( result ); + + //if not jumped yet + if (!ms->jumpreach) return result; + /* + //go straight to the reachability end + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + //always use max speed when traveling through the air + EA_Move(ms->client, hordir, 800); + VectorCopy(hordir, result.movedir); + */ + // + if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) + { + //go straight to the reachability end + VectorSubtract(reach->end, ms->origin, hordir); + hordir[2] = 0; + VectorNormalize(hordir); + speed = 400; + } //end if + // + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_WeaponJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) +{ + vec3_t hordir; + bot_moveresult_t_cleared( result ); + + //first walk straight to the reachability start + hordir[0] = reach->start[0] - ms->origin[0]; + hordir[1] = reach->start[1] - ms->origin[1]; + hordir[2] = 0; + // + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, hordir, 400); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotTravel_JumpPad +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotFinishTravel_JumpPad(bot_movestate_t *ms, aas_reachability_t *reach) +{ + float speed; + vec3_t hordir; + bot_moveresult_t_cleared( result ); + + if (!BotAirControl(ms->origin, ms->velocity, reach->end, hordir, &speed)) + { + hordir[0] = reach->end[0] - ms->origin[0]; + hordir[1] = reach->end[1] - ms->origin[1]; + hordir[2] = 0; + VectorNormalize(hordir); + speed = 400; + } //end if + BotCheckBlocked(ms, hordir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, hordir, speed); + VectorCopy(hordir, result.movedir); + // + return result; +} //end of the function BotFinishTravel_JumpPad +//=========================================================================== +// time before the reachability times out +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotReachabilityTime(aas_reachability_t *reach) +{ + switch(reach->traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: return 5; + case TRAVEL_CROUCH: return 5; + case TRAVEL_BARRIERJUMP: return 5; + case TRAVEL_LADDER: return 6; + case TRAVEL_WALKOFFLEDGE: return 5; + case TRAVEL_JUMP: return 5; + case TRAVEL_SWIM: return 5; + case TRAVEL_WATERJUMP: return 5; + case TRAVEL_TELEPORT: return 5; + case TRAVEL_ELEVATOR: return 10; + case TRAVEL_GRAPPLEHOOK: return 8; + case TRAVEL_ROCKETJUMP: return 6; + case TRAVEL_BFGJUMP: return 6; + case TRAVEL_JUMPPAD: return 10; + case TRAVEL_FUNCBOB: return 10; + default: + { + botimport.Print(PRT_ERROR, "travel type %d not implemented yet\n", reach->traveltype); + return 8; + } //end case + } //end switch +} //end of the function BotReachabilityTime +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +bot_moveresult_t BotMoveInGoalArea(bot_movestate_t *ms, bot_goal_t *goal) +{ + bot_moveresult_t_cleared( result ); + vec3_t dir; + float dist, speed; + +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "%s: moving straight to goal\n", ClientName(ms->entitynum-1)); + //AAS_ClearShownDebugLines(); + //AAS_DebugLine(ms->origin, goal->origin, LINECOLOR_RED); +#endif //DEBUG + //walk straight to the goal origin + dir[0] = goal->origin[0] - ms->origin[0]; + dir[1] = goal->origin[1] - ms->origin[1]; + if (ms->moveflags & MFL_SWIMMING) + { + dir[2] = goal->origin[2] - ms->origin[2]; + result.traveltype = TRAVEL_SWIM; + } //end if + else + { + dir[2] = 0; + result.traveltype = TRAVEL_WALK; + } //endif + // + dist = VectorNormalize(dir); + if (dist > 100) dist = 100; + speed = 400 - (400 - 4 * dist); + if (speed < 10) speed = 0; + // + BotCheckBlocked(ms, dir, qtrue, &result); + //elemantary action move in direction + EA_Move(ms->client, dir, speed); + VectorCopy(dir, result.movedir); + // + if (ms->moveflags & MFL_SWIMMING) + { + Vector2Angles(dir, result.ideal_viewangles); + result.flags |= MOVERESULT_SWIMVIEW; + } //end if + //if (!debugline) debugline = botimport.DebugLineCreate(); + //botimport.DebugLineShow(debugline, ms->origin, goal->origin, LINECOLOR_BLUE); + // + ms->lastreachnum = 0; + ms->lastareanum = 0; + ms->lastgoalareanum = goal->areanum; + VectorCopy(ms->origin, ms->lastorigin); + // + return result; +} //end of the function BotMoveInGoalArea +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotMoveToGoal(bot_moveresult_t *result, int movestate, bot_goal_t *goal, int travelflags) +{ + int reachnum, lastreachnum, foundjumppad, ent, resultflags; + aas_reachability_t reach, lastreach; + bot_movestate_t *ms; + //vec3_t mins, maxs, up = {0, 0, 1}; + //bsp_trace_t trace; + //static int debugline; + + result->failure = qfalse; + result->type = 0; + result->blocked = qfalse; + result->blockentity = 0; + result->traveltype = 0; + result->flags = 0; + + // + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + //reset the grapple before testing if the bot has a valid goal + //because the bot could lose all its goals when stuck to a wall + BotResetGrapple(ms); + // + if (!goal) + { +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "client %d: movetogoal -> no goal\n", ms->client); +#endif //DEBUG + result->failure = qtrue; + return; + } //end if + //botimport.Print(PRT_MESSAGE, "numavoidreach = %d\n", ms->numavoidreach); + //remove some of the move flags + ms->moveflags &= ~(MFL_SWIMMING|MFL_AGAINSTLADDER); + //set some of the move flags + //NOTE: the MFL_ONGROUND flag is also set in the higher AI + if (AAS_OnGround(ms->origin, ms->presencetype, ms->entitynum)) ms->moveflags |= MFL_ONGROUND; + // + if (ms->moveflags & MFL_ONGROUND) + { + int modeltype, modelnum; + + ent = BotOnTopOfEntity(ms); + + if (ent != -1) + { + modelnum = AAS_EntityModelindex(ent); + if (modelnum >= 0 && modelnum < MAX_MODELS) + { + modeltype = modeltypes[modelnum]; + + if (modeltype == MODELTYPE_FUNC_PLAT) + { + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if the bot is Not using the elevator + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR || + //NOTE: the face number is the plat model number + (reach.facenum & 0x0000FFFF) != modelnum) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_plat\n", ms->client); + AAS_ReachabilityFromNum(reachnum, &reach); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + } //end if + else + { + if (botDeveloper) + { + botimport.Print(PRT_MESSAGE, "client %d: on func_plat without reachability\n", ms->client); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_ELEVATOR; + } //end if + else if (modeltype == MODELTYPE_FUNC_BOB) + { + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + //if the bot is Not using the func bobbing + if ((reach.traveltype & TRAVELTYPE_MASK) != TRAVEL_FUNCBOB || + //NOTE: the face number is the func_bobbing model number + (reach.facenum & 0x0000FFFF) != modelnum) + { + reachnum = AAS_NextModelReachability(0, modelnum); + if (reachnum) + { + //botimport.Print(PRT_MESSAGE, "client %d: accidentally ended up on func_bobbing\n", ms->client); + AAS_ReachabilityFromNum(reachnum, &reach); + ms->lastreachnum = reachnum; + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + } //end if + else + { + if (botDeveloper) + { + botimport.Print(PRT_MESSAGE, "client %d: on func_bobbing without reachability\n", ms->client); + } //end if + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + result->flags |= MOVERESULT_ONTOPOF_FUNCBOB; + } //end if + else if (modeltype == MODELTYPE_FUNC_STATIC || modeltype == MODELTYPE_FUNC_DOOR) + { + // check if ontop of a door bridge ? + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + // if not in a reachability area + if (!AAS_AreaReachability(ms->areanum)) + { + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end if + } //end else if + else + { + result->blocked = qtrue; + result->blockentity = ent; + result->flags |= MOVERESULT_ONTOPOFOBSTACLE; + return; + } //end else + } //end if + } //end if + } //end if + //if swimming + if (AAS_Swimming(ms->origin)) ms->moveflags |= MFL_SWIMMING; + //if against a ladder + if (AAS_AgainstLadder(ms->origin)) ms->moveflags |= MFL_AGAINSTLADDER; + //if the bot is on the ground, swimming or against a ladder + if (ms->moveflags & (MFL_ONGROUND|MFL_SWIMMING|MFL_AGAINSTLADDER)) + { + //botimport.Print(PRT_MESSAGE, "%s: onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + // + AAS_ReachabilityFromNum(ms->lastreachnum, &lastreach); + //reachability area the bot is in + //ms->areanum = BotReachabilityArea(ms->origin, ((lastreach.traveltype & TRAVELTYPE_MASK) != TRAVEL_ELEVATOR)); + ms->areanum = BotFuzzyPointReachabilityArea(ms->origin); + // + if ( !ms->areanum ) + { + result->failure = qtrue; + result->blocked = qtrue; + result->blockentity = 0; + result->type = RESULTTYPE_INSOLIDAREA; + return; + } //end if + //if the bot is in the goal area + if (ms->areanum == goal->areanum) + { + *result = BotMoveInGoalArea(ms, goal); + return; + } //end if + //assume we can use the reachability from the last frame + reachnum = ms->lastreachnum; + //if there is a last reachability + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + //check if the reachability is still valid + if (!(AAS_TravelFlagForType(reach.traveltype) & travelflags)) + { + reachnum = 0; + } //end if + //special grapple hook case + else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_GRAPPLEHOOK) + { + if (ms->reachability_time < AAS_Time() || + (ms->moveflags & MFL_GRAPPLERESET)) + { + reachnum = 0; + } //end if + } //end if + //special elevator case + else if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR || + (reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_FUNCBOB) + { + if ((result->flags & MOVERESULT_ONTOPOF_ELEVATOR) || + (result->flags & MOVERESULT_ONTOPOF_FUNCBOB)) + { + ms->reachability_time = AAS_Time() + 5; + } //end if + //if the bot was going for an elevator and reached the reachability area + if (ms->areanum == reach.areanum || + ms->reachability_time < AAS_Time()) + { + reachnum = 0; + } //end if + } //end if + else + { +#ifdef DEBUG + if (botDeveloper) + { + if (ms->reachability_time < AAS_Time()) + { + botimport.Print(PRT_MESSAGE, "client %d: reachability timeout in ", ms->client); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + /* + if (ms->lastareanum != ms->areanum) + { + botimport.Print(PRT_MESSAGE, "changed from area %d to %d\n", ms->lastareanum, ms->areanum); + } //end if*/ + } //end if +#endif //DEBUG + //if the goal area changed or the reachability timed out + //or the area changed + if (ms->lastgoalareanum != goal->areanum || + ms->reachability_time < AAS_Time() || + ms->lastareanum != ms->areanum) + { + reachnum = 0; + //botimport.Print(PRT_MESSAGE, "area change or timeout\n"); + } //end else if + } //end else + } //end if + resultflags = 0; + //if the bot needs a new reachability + if (!reachnum) + { + //if the area has no reachability links + if (!AAS_AreaReachability(ms->areanum)) + { +#ifdef DEBUG + if (botDeveloper) + { + botimport.Print(PRT_MESSAGE, "area %d no reachability\n", ms->areanum); + } //end if +#endif //DEBUG + } //end if + //get a new reachability leading towards the goal + reachnum = BotGetReachabilityToGoal(ms->origin, ms->areanum, + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, travelflags, + ms->avoidspots, ms->numavoidspots, &resultflags); + //the area number the reachability starts in + ms->reachareanum = ms->areanum; + //reset some state variables + ms->jumpreach = 0; //for TRAVEL_JUMP + ms->moveflags &= ~MFL_GRAPPLERESET; //for TRAVEL_GRAPPLEHOOK + //if there is a reachability to the goal + if (reachnum) + { + AAS_ReachabilityFromNum(reachnum, &reach); + //set a timeout for this reachability + ms->reachability_time = AAS_Time() + BotReachabilityTime(&reach); + // +#ifdef AVOIDREACH + //add the reachability to the reachabilities to avoid for a while + BotAddToAvoidReach(ms, reachnum, AVOIDREACH_TIME); +#endif //AVOIDREACH + } //end if +#ifdef DEBUG + + else if (botDeveloper) + { + botimport.Print(PRT_MESSAGE, "goal not reachable\n"); + Com_Memset(&reach, 0, sizeof(aas_reachability_t)); //make compiler happy + } //end else + if (botDeveloper) + { + //if still going for the same goal + if (ms->lastgoalareanum == goal->areanum) + { + if (ms->lastareanum == reach.areanum) + { + botimport.Print(PRT_MESSAGE, "same goal, going back to previous area\n"); + } //end if + } //end if + } //end if +#endif //DEBUG + } //end else + // + ms->lastreachnum = reachnum; + ms->lastgoalareanum = goal->areanum; + ms->lastareanum = ms->areanum; + //if the bot has a reachability + if (reachnum) + { + //get the reachability from the number + AAS_ReachabilityFromNum(reachnum, &reach); + result->traveltype = reach.traveltype; + // +#ifdef DEBUG_AI_MOVE + AAS_ClearShownDebugLines(); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + AAS_ShowReachability(&reach); +#endif //DEBUG_AI_MOVE + // +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d: ", ms->client); + //AAS_PrintTravelType(reach.traveltype); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + switch(reach.traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break; + case TRAVEL_CROUCH: *result = BotTravel_Crouch(ms, &reach); break; + case TRAVEL_BARRIERJUMP: *result = BotTravel_BarrierJump(ms, &reach); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; + case TRAVEL_WALKOFFLEDGE: *result = BotTravel_WalkOffLedge(ms, &reach); break; + case TRAVEL_JUMP: *result = BotTravel_Jump(ms, &reach); break; + case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; + case TRAVEL_WATERJUMP: *result = BotTravel_WaterJump(ms, &reach); break; + case TRAVEL_TELEPORT: *result = BotTravel_Teleport(ms, &reach); break; + case TRAVEL_ELEVATOR: *result = BotTravel_Elevator(ms, &reach); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; + case TRAVEL_ROCKETJUMP: *result = BotTravel_RocketJump(ms, &reach); break; + case TRAVEL_BFGJUMP: *result = BotTravel_BFGJump(ms, &reach); break; + case TRAVEL_JUMPPAD: *result = BotTravel_JumpPad(ms, &reach); break; + case TRAVEL_FUNCBOB: *result = BotTravel_FuncBobbing(ms, &reach); break; + default: + { + botimport.Print(PRT_FATAL, "travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); + break; + } //end case + } //end switch + result->traveltype = reach.traveltype; + result->flags |= resultflags; + } //end if + else + { + result->failure = qtrue; + result->flags |= resultflags; + Com_Memset(&reach, 0, sizeof(aas_reachability_t)); + } //end else +#ifdef DEBUG + if (botDeveloper) + { + if (result->failure) + { + botimport.Print(PRT_MESSAGE, "client %d: movement failure in ", ms->client); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + } //end if +#endif //DEBUG + } //end if + else + { + int i, numareas, areas[16]; + vec3_t end; + + //special handling of jump pads when the bot uses a jump pad without knowing it + foundjumppad = qfalse; + VectorMA(ms->origin, -2 * ms->thinktime, ms->velocity, end); + numareas = AAS_TraceAreas(ms->origin, end, areas, NULL, 16); + for (i = numareas-1; i >= 0; i--) + { + if (AAS_AreaJumpPad(areas[i])) + { + //botimport.Print(PRT_MESSAGE, "client %d used a jumppad without knowing, area %d\n", ms->client, areas[i]); + foundjumppad = qtrue; + lastreachnum = BotGetReachabilityToGoal(end, areas[i], + ms->lastgoalareanum, ms->lastareanum, + ms->avoidreach, ms->avoidreachtimes, ms->avoidreachtries, + goal, travelflags, TFL_JUMPPAD, ms->avoidspots, ms->numavoidspots, NULL); + if (lastreachnum) + { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability\n"); + break; + } //end if + else + { + for (lastreachnum = AAS_NextAreaReachability(areas[i], 0); lastreachnum; + lastreachnum = AAS_NextAreaReachability(areas[i], lastreachnum)) + { + //get the reachability from the number + AAS_ReachabilityFromNum(lastreachnum, &reach); + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_JUMPPAD) + { + ms->lastreachnum = lastreachnum; + ms->lastareanum = areas[i]; + //botimport.Print(PRT_MESSAGE, "found jumppad reachability hard!!\n"); + break; + } //end if + } //end for + if (lastreachnum) break; + } //end else + } //end if + } //end for + if (botDeveloper) + { + //if a jumppad is found with the trace but no reachability is found + if (foundjumppad && !ms->lastreachnum) + { + botimport.Print(PRT_MESSAGE, "client %d didn't find jumppad reachability\n", ms->client); + } //end if + } //end if + // + if (ms->lastreachnum) + { + //botimport.Print(PRT_MESSAGE, "%s: NOT onground, swimming or against ladder\n", ClientName(ms->entitynum-1)); + AAS_ReachabilityFromNum(ms->lastreachnum, &reach); + result->traveltype = reach.traveltype; +#ifdef DEBUG + //botimport.Print(PRT_MESSAGE, "client %d finish: ", ms->client); + //AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + //botimport.Print(PRT_MESSAGE, "\n"); +#endif //DEBUG + // + switch(reach.traveltype & TRAVELTYPE_MASK) + { + case TRAVEL_WALK: *result = BotTravel_Walk(ms, &reach); break;//BotFinishTravel_Walk(ms, &reach); break; + case TRAVEL_CROUCH: /*do nothing*/ break; + case TRAVEL_BARRIERJUMP: *result = BotFinishTravel_BarrierJump(ms, &reach); break; + case TRAVEL_LADDER: *result = BotTravel_Ladder(ms, &reach); break; + case TRAVEL_WALKOFFLEDGE: *result = BotFinishTravel_WalkOffLedge(ms, &reach); break; + case TRAVEL_JUMP: *result = BotFinishTravel_Jump(ms, &reach); break; + case TRAVEL_SWIM: *result = BotTravel_Swim(ms, &reach); break; + case TRAVEL_WATERJUMP: *result = BotFinishTravel_WaterJump(ms, &reach); break; + case TRAVEL_TELEPORT: /*do nothing*/ break; + case TRAVEL_ELEVATOR: *result = BotFinishTravel_Elevator(ms, &reach); break; + case TRAVEL_GRAPPLEHOOK: *result = BotTravel_Grapple(ms, &reach); break; + case TRAVEL_ROCKETJUMP: + case TRAVEL_BFGJUMP: *result = BotFinishTravel_WeaponJump(ms, &reach); break; + case TRAVEL_JUMPPAD: *result = BotFinishTravel_JumpPad(ms, &reach); break; + case TRAVEL_FUNCBOB: *result = BotFinishTravel_FuncBobbing(ms, &reach); break; + default: + { + botimport.Print(PRT_FATAL, "(last) travel type %d not implemented yet\n", (reach.traveltype & TRAVELTYPE_MASK)); + break; + } //end case + } //end switch + result->traveltype = reach.traveltype; +#ifdef DEBUG + if (botDeveloper) + { + if (result->failure) + { + botimport.Print(PRT_MESSAGE, "client %d: movement failure in finish ", ms->client); + AAS_PrintTravelType(reach.traveltype & TRAVELTYPE_MASK); + botimport.Print(PRT_MESSAGE, "\n"); + } //end if + } //end if +#endif //DEBUG + } //end if + } //end else + //FIXME: is it right to do this here? + if (result->blocked) ms->reachability_time -= 10 * ms->thinktime; + //copy the last origin + VectorCopy(ms->origin, ms->lastorigin); + //return the movement result + return; +} //end of the function BotMoveToGoal +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetAvoidReach(int movestate) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + Com_Memset(ms->avoidreach, 0, MAX_AVOIDREACH * sizeof(int)); + Com_Memset(ms->avoidreachtimes, 0, MAX_AVOIDREACH * sizeof(float)); + Com_Memset(ms->avoidreachtries, 0, MAX_AVOIDREACH * sizeof(int)); +} //end of the function BotResetAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetLastAvoidReach(int movestate) +{ + int i, latest; + float latesttime; + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + latesttime = 0; + latest = 0; + for (i = 0; i < MAX_AVOIDREACH; i++) + { + if (ms->avoidreachtimes[i] > latesttime) + { + latesttime = ms->avoidreachtimes[i]; + latest = i; + } //end if + } //end for + if (latesttime) + { + ms->avoidreachtimes[latest] = 0; + if (ms->avoidreachtries[latest] > 0) ms->avoidreachtries[latest]--; + } //end if +} //end of the function BotResetLastAvoidReach +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetMoveState(int movestate) +{ + bot_movestate_t *ms; + + ms = BotMoveStateFromHandle(movestate); + if (!ms) return; + Com_Memset(ms, 0, sizeof(bot_movestate_t)); +} //end of the function BotResetMoveState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupMoveAI(void) +{ + BotSetBrushModelTypes(); + sv_maxstep = LibVar("sv_step", "18"); + sv_maxbarrier = LibVar("sv_maxbarrier", "32"); + sv_gravity = LibVar("sv_gravity", "800"); + weapindex_rocketlauncher = LibVar("weapindex_rocketlauncher", "5"); + weapindex_bfg10k = LibVar("weapindex_bfg10k", "9"); + weapindex_grapple = LibVar("weapindex_grapple", "10"); + entitytypemissile = LibVar("entitytypemissile", "3"); + offhandgrapple = LibVar("offhandgrapple", "0"); + cmd_grappleon = LibVar("cmd_grappleon", "grappleon"); + cmd_grappleoff = LibVar("cmd_grappleoff", "grappleoff"); + return BLERR_NOERROR; +} //end of the function BotSetupMoveAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownMoveAI(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botmovestates[i]) + { + FreeMemory(botmovestates[i]); + botmovestates[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownMoveAI + + diff --git a/code/botlib/be_ai_move.h b/code/botlib/be_ai_move.h new file mode 100644 index 0000000..a32d939 --- /dev/null +++ b/code/botlib/be_ai_move.h @@ -0,0 +1,142 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * 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; + +#define bot_moveresult_t_cleared(x) bot_moveresult_t (x) = {0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, {0, 0, 0}} + +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/code/botlib/be_ai_weap.c b/code/botlib/be_ai_weap.c new file mode 100644 index 0000000..492f943 --- /dev/null +++ b/code/botlib/be_ai_weap.c @@ -0,0 +1,543 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_weap.c + * + * desc: weapon AI + * + * $Archive: /MissionPack/code/botlib/be_ai_weap.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_libvar.h" +#include "l_log.h" +#include "l_memory.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" //fuzzy weights +#include "be_ai_weap.h" + +//#define DEBUG_AI_WEAP + +//structure field offsets +#define WEAPON_OFS(x) (size_t)&(((weaponinfo_t *)0)->x) +#define PROJECTILE_OFS(x) (size_t)&(((projectileinfo_t *)0)->x) + +//weapon definition +static fielddef_t weaponinfo_fields[] = +{ +{"number", WEAPON_OFS(number), FT_INT}, //weapon number +{"name", WEAPON_OFS(name), FT_STRING}, //name of the weapon +{"level", WEAPON_OFS(level), FT_INT}, +{"model", WEAPON_OFS(model), FT_STRING}, //model of the weapon +{"weaponindex", WEAPON_OFS(weaponindex), FT_INT}, //index of weapon in inventory +{"flags", WEAPON_OFS(flags), FT_INT}, //special flags +{"projectile", WEAPON_OFS(projectile), FT_STRING}, //projectile used by the weapon +{"numprojectiles", WEAPON_OFS(numprojectiles), FT_INT}, //number of projectiles +{"hspread", WEAPON_OFS(hspread), FT_FLOAT}, //horizontal spread of projectiles (degrees from middle) +{"vspread", WEAPON_OFS(vspread), FT_FLOAT}, //vertical spread of projectiles (degrees from middle) +{"speed", WEAPON_OFS(speed), FT_FLOAT}, //speed of the projectile (0 = instant hit) +{"acceleration", WEAPON_OFS(acceleration), FT_FLOAT}, //"acceleration" * time (in seconds) + "speed" = projectile speed +{"recoil", WEAPON_OFS(recoil), FT_FLOAT|FT_ARRAY, 3}, //amount of recoil the player gets from the weapon +{"offset", WEAPON_OFS(offset), FT_FLOAT|FT_ARRAY, 3}, //projectile start offset relative to eye and view angles +{"angleoffset", WEAPON_OFS(angleoffset), FT_FLOAT|FT_ARRAY, 3},//offset of the shoot angles relative to the view angles +{"extrazvelocity", WEAPON_OFS(extrazvelocity), FT_FLOAT},//extra z velocity the projectile gets +{"ammoamount", WEAPON_OFS(ammoamount), FT_INT}, //ammo amount used per shot +{"ammoindex", WEAPON_OFS(ammoindex), FT_INT}, //index of ammo in inventory +{"activate", WEAPON_OFS(activate), FT_FLOAT}, //time it takes to select the weapon +{"reload", WEAPON_OFS(reload), FT_FLOAT}, //time it takes to reload the weapon +{"spinup", WEAPON_OFS(spinup), FT_FLOAT}, //time it takes before first shot +{"spindown", WEAPON_OFS(spindown), FT_FLOAT}, //time it takes before weapon stops firing +{NULL, 0, 0, 0} +}; + +//projectile definition +static fielddef_t projectileinfo_fields[] = +{ +{"name", PROJECTILE_OFS(name), FT_STRING}, //name of the projectile +{"model", PROJECTILE_OFS(model), FT_STRING}, //model of the projectile +{"flags", PROJECTILE_OFS(flags), FT_INT}, //special flags +{"gravity", PROJECTILE_OFS(gravity), FT_FLOAT}, //amount of gravity applied to the projectile [0,1] +{"damage", PROJECTILE_OFS(damage), FT_INT}, //damage of the projectile +{"radius", PROJECTILE_OFS(radius), FT_FLOAT}, //radius of damage +{"visdamage", PROJECTILE_OFS(visdamage), FT_INT}, //damage of the projectile to visible entities +{"damagetype", PROJECTILE_OFS(damagetype), FT_INT}, //type of damage (combination of the DAMAGETYPE_? flags) +{"healthinc", PROJECTILE_OFS(healthinc), FT_INT}, //health increase the owner gets +{"push", PROJECTILE_OFS(push), FT_FLOAT}, //amount a player is pushed away from the projectile impact +{"detonation", PROJECTILE_OFS(detonation), FT_FLOAT}, //time before projectile explodes after fire pressed +{"bounce", PROJECTILE_OFS(bounce), FT_FLOAT}, //amount the projectile bounces +{"bouncefric", PROJECTILE_OFS(bouncefric), FT_FLOAT}, //amount the bounce decreases per bounce +{"bouncestop", PROJECTILE_OFS(bouncestop), FT_FLOAT}, //minimum bounce value before bouncing stops +//recurive projectile definition?? +{NULL, 0, 0, 0} +}; + +static structdef_t weaponinfo_struct = +{ + sizeof(weaponinfo_t), weaponinfo_fields +}; +static structdef_t projectileinfo_struct = +{ + sizeof(projectileinfo_t), projectileinfo_fields +}; + +//weapon configuration: set of weapons with projectiles +typedef struct weaponconfig_s +{ + int numweapons; + int numprojectiles; + projectileinfo_t *projectileinfo; + weaponinfo_t *weaponinfo; +} weaponconfig_t; + +//the bot weapon state +typedef struct bot_weaponstate_s +{ + struct weightconfig_s *weaponweightconfig; //weapon weight configuration + int *weaponweightindex; //weapon weight index +} bot_weaponstate_t; + +static bot_weaponstate_t *botweaponstates[MAX_CLIENTS+1]; +static weaponconfig_t *weaponconfig; + +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotValidWeaponNumber(int weaponnum) +{ + if (weaponnum <= 0 || weaponnum > weaponconfig->numweapons) + { + botimport.Print(PRT_ERROR, "weapon number out of range\n"); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidWeaponNumber +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +bot_weaponstate_t *BotWeaponStateFromHandle(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return NULL; + } //end if + if (!botweaponstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return NULL; + } //end if + return botweaponstates[handle]; +} //end of the function BotWeaponStateFromHandle +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef DEBUG_AI_WEAP +void DumpWeaponConfig(weaponconfig_t *wc) +{ + FILE *fp; + int i; + + fp = Log_FileStruct(); + if (!fp) return; + for (i = 0; i < wc->numprojectiles; i++) + { + WriteStructure(fp, &projectileinfo_struct, (char *) &wc->projectileinfo[i]); + Log_Flush(); + } //end for + for (i = 0; i < wc->numweapons; i++) + { + WriteStructure(fp, &weaponinfo_struct, (char *) &wc->weaponinfo[i]); + Log_Flush(); + } //end for +} //end of the function DumpWeaponConfig +#endif //DEBUG_AI_WEAP +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weaponconfig_t *LoadWeaponConfig(char *filename) +{ + int max_weaponinfo, max_projectileinfo; + token_t token; + char path[MAX_PATH]; + int i, j; + source_t *source; + weaponconfig_t *wc; + weaponinfo_t weaponinfo; + + max_weaponinfo = (int) LibVarValue("max_weaponinfo", "32"); + if (max_weaponinfo < 0) + { + botimport.Print(PRT_ERROR, "max_weaponinfo = %d\n", max_weaponinfo); + max_weaponinfo = 32; + LibVarSet("max_weaponinfo", "32"); + } //end if + max_projectileinfo = (int) LibVarValue("max_projectileinfo", "32"); + if (max_projectileinfo < 0) + { + botimport.Print(PRT_ERROR, "max_projectileinfo = %d\n", max_projectileinfo); + max_projectileinfo = 32; + LibVarSet("max_projectileinfo", "32"); + } //end if + strncpy(path, filename, MAX_PATH); + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(path); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", path); + return NULL; + } //end if + //initialize weapon config + wc = (weaponconfig_t *) GetClearedHunkMemory(sizeof(weaponconfig_t) + + max_weaponinfo * sizeof(weaponinfo_t) + + max_projectileinfo * sizeof(projectileinfo_t)); + wc->weaponinfo = (weaponinfo_t *) ((char *) wc + sizeof(weaponconfig_t)); + wc->projectileinfo = (projectileinfo_t *) ((char *) wc->weaponinfo + + max_weaponinfo * sizeof(weaponinfo_t)); + wc->numweapons = max_weaponinfo; + wc->numprojectiles = 0; + //parse the source file + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "weaponinfo")) + { + Com_Memset(&weaponinfo, 0, sizeof(weaponinfo_t)); + if (!ReadStructure(source, &weaponinfo_struct, (char *) &weaponinfo)) + { + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + if (weaponinfo.number < 0 || weaponinfo.number >= max_weaponinfo) + { + botimport.Print(PRT_ERROR, "weapon info number %d out of range in %s\n", weaponinfo.number, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + Com_Memcpy(&wc->weaponinfo[weaponinfo.number], &weaponinfo, sizeof(weaponinfo_t)); + wc->weaponinfo[weaponinfo.number].valid = qtrue; + } //end if + else if (!strcmp(token.string, "projectileinfo")) + { + if (wc->numprojectiles >= max_projectileinfo) + { + botimport.Print(PRT_ERROR, "more than %d projectiles defined in %s\n", max_projectileinfo, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + Com_Memset(&wc->projectileinfo[wc->numprojectiles], 0, sizeof(projectileinfo_t)); + if (!ReadStructure(source, &projectileinfo_struct, (char *) &wc->projectileinfo[wc->numprojectiles])) + { + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end if + wc->numprojectiles++; + } //end if + else + { + botimport.Print(PRT_ERROR, "unknown definition %s in %s\n", token.string, path); + FreeMemory(wc); + FreeSource(source); + return NULL; + } //end else + } //end while + FreeSource(source); + //fix up weapons + for (i = 0; i < wc->numweapons; i++) + { + if (!wc->weaponinfo[i].valid) continue; + if (!wc->weaponinfo[i].name[0]) + { + botimport.Print(PRT_ERROR, "weapon %d has no name in %s\n", i, path); + FreeMemory(wc); + return NULL; + } //end if + if (!wc->weaponinfo[i].projectile[0]) + { + botimport.Print(PRT_ERROR, "weapon %s has no projectile in %s\n", wc->weaponinfo[i].name, path); + FreeMemory(wc); + return NULL; + } //end if + //find the projectile info and copy it to the weapon info + for (j = 0; j < wc->numprojectiles; j++) + { + if (!strcmp(wc->projectileinfo[j].name, wc->weaponinfo[i].projectile)) + { + Com_Memcpy(&wc->weaponinfo[i].proj, &wc->projectileinfo[j], sizeof(projectileinfo_t)); + break; + } //end if + } //end for + if (j == wc->numprojectiles) + { + botimport.Print(PRT_ERROR, "weapon %s uses undefined projectile in %s\n", wc->weaponinfo[i].name, path); + FreeMemory(wc); + return NULL; + } //end if + } //end for + if (!wc->numweapons) botimport.Print(PRT_WARNING, "no weapon info loaded\n"); + botimport.Print(PRT_MESSAGE, "loaded %s\n", path); + return wc; +} //end of the function LoadWeaponConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int *WeaponWeightIndex(weightconfig_t *wwc, weaponconfig_t *wc) +{ + int *index, i; + + //initialize item weight index + index = (int *) GetClearedMemory(sizeof(int) * wc->numweapons); + + for (i = 0; i < wc->numweapons; i++) + { + index[i] = FindFuzzyWeight(wwc, wc->weaponinfo[i].name); + } //end for + return index; +} //end of the function WeaponWeightIndex +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotFreeWeaponWeights(int weaponstate) +{ + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return; + if (ws->weaponweightconfig) FreeWeightConfig(ws->weaponweightconfig); + if (ws->weaponweightindex) FreeMemory(ws->weaponweightindex); +} //end of the function BotFreeWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotLoadWeaponWeights(int weaponstate, char *filename) +{ + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return BLERR_CANNOTLOADWEAPONWEIGHTS; + BotFreeWeaponWeights(weaponstate); + // + ws->weaponweightconfig = ReadWeightConfig(filename); + if (!ws->weaponweightconfig) + { + botimport.Print(PRT_FATAL, "couldn't load weapon config %s\n", filename); + return BLERR_CANNOTLOADWEAPONWEIGHTS; + } //end if + if (!weaponconfig) return BLERR_CANNOTLOADWEAPONCONFIG; + ws->weaponweightindex = WeaponWeightIndex(ws->weaponweightconfig, weaponconfig); + return BLERR_NOERROR; +} //end of the function BotLoadWeaponWeights +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo) +{ + bot_weaponstate_t *ws; + + if (!BotValidWeaponNumber(weapon)) return; + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return; + if (!weaponconfig) return; + Com_Memcpy(weaponinfo, &weaponconfig->weaponinfo[weapon], sizeof(weaponinfo_t)); +} //end of the function BotGetWeaponInfo +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotChooseBestFightWeapon(int weaponstate, int *inventory) +{ + int i, index, bestweapon; + float weight, bestweight; + weaponconfig_t *wc; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return 0; + wc = weaponconfig; + if (!weaponconfig) return 0; + + //if the bot has no weapon weight configuration + if (!ws->weaponweightconfig) return 0; + + bestweight = 0; + bestweapon = 0; + for (i = 0; i < wc->numweapons; i++) + { + if (!wc->weaponinfo[i].valid) continue; + index = ws->weaponweightindex[i]; + if (index < 0) continue; + weight = FuzzyWeight(inventory, ws->weaponweightconfig, index); + if (weight > bestweight) + { + bestweight = weight; + bestweapon = i; + } //end if + } //end for + return bestweapon; +} //end of the function BotChooseBestFightWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotResetWeaponState(int weaponstate) +{ + struct weightconfig_s *weaponweightconfig; + int *weaponweightindex; + bot_weaponstate_t *ws; + + ws = BotWeaponStateFromHandle(weaponstate); + if (!ws) return; + weaponweightconfig = ws->weaponweightconfig; + weaponweightindex = ws->weaponweightindex; + + //Com_Memset(ws, 0, sizeof(bot_weaponstate_t)); + ws->weaponweightconfig = weaponweightconfig; + ws->weaponweightindex = weaponweightindex; +} //end of the function BotResetWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +int BotAllocWeaponState(void) +{ + int i; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (!botweaponstates[i]) + { + botweaponstates[i] = GetClearedMemory(sizeof(bot_weaponstate_t)); + return i; + } //end if + } //end for + return 0; +} //end of the function BotAllocWeaponState +//======================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//======================================================================== +void BotFreeWeaponState(int handle) +{ + if (handle <= 0 || handle > MAX_CLIENTS) + { + botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle); + return; + } //end if + if (!botweaponstates[handle]) + { + botimport.Print(PRT_FATAL, "invalid move state %d\n", handle); + return; + } //end if + BotFreeWeaponWeights(handle); + FreeMemory(botweaponstates[handle]); + botweaponstates[handle] = NULL; +} //end of the function BotFreeWeaponState +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int BotSetupWeaponAI(void) +{ + char *file; + + file = LibVarString("weaponconfig", "weapons.c"); + weaponconfig = LoadWeaponConfig(file); + if (!weaponconfig) + { + botimport.Print(PRT_FATAL, "couldn't load the weapon config\n"); + return BLERR_CANNOTLOADWEAPONCONFIG; + } //end if + +#ifdef DEBUG_AI_WEAP + DumpWeaponConfig(weaponconfig); +#endif //DEBUG_AI_WEAP + // + return BLERR_NOERROR; +} //end of the function BotSetupWeaponAI +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeaponAI(void) +{ + int i; + + if (weaponconfig) FreeMemory(weaponconfig); + weaponconfig = NULL; + + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (botweaponstates[i]) + { + BotFreeWeaponState(i); + } //end if + } //end for +} //end of the function BotShutdownWeaponAI + diff --git a/code/botlib/be_ai_weap.h b/code/botlib/be_ai_weap.h new file mode 100644 index 0000000..59067fb --- /dev/null +++ b/code/botlib/be_ai_weap.h @@ -0,0 +1,104 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * 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/code/botlib/be_ai_weight.c b/code/botlib/be_ai_weight.c new file mode 100644 index 0000000..17216ab --- /dev/null +++ b/code/botlib/be_ai_weight.c @@ -0,0 +1,925 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_weight.c + * + * desc: fuzzy logic + * + * $Archive: /MissionPack/code/botlib/be_ai_weight.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_utils.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_libvar.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_interface.h" +#include "be_ai_weight.h" + +#define MAX_INVENTORYVALUE 999999 +#define EVALUATERECURSIVELY + +#define MAX_WEIGHT_FILES 128 +weightconfig_t *weightFileList[MAX_WEIGHT_FILES]; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadValue(source_t *source, float *value) +{ + token_t token; + + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + if (!strcmp(token.string, "-")) + { + SourceWarning(source, "negative value set to zero\n"); + + if(!PC_ExpectAnyToken(source, &token)) + { + SourceError(source, "Missing return value\n"); + return qfalse; + } + } + + if (token.type != TT_NUMBER) + { + SourceError(source, "invalid return value %s\n", token.string); + return qfalse; + } + + *value = token.floatvalue; + return qtrue; +} //end of the function ReadValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadFuzzyWeight(source_t *source, fuzzyseperator_t *fs) +{ + if (PC_CheckTokenString(source, "balance")) + { + fs->type = WT_BALANCE; + if (!PC_ExpectTokenString(source, "(")) return qfalse; + if (!ReadValue(source, &fs->weight)) return qfalse; + if (!PC_ExpectTokenString(source, ",")) return qfalse; + if (!ReadValue(source, &fs->minweight)) return qfalse; + if (!PC_ExpectTokenString(source, ",")) return qfalse; + if (!ReadValue(source, &fs->maxweight)) return qfalse; + if (!PC_ExpectTokenString(source, ")")) return qfalse; + } //end if + else + { + fs->type = 0; + if (!ReadValue(source, &fs->weight)) return qfalse; + fs->minweight = fs->weight; + fs->maxweight = fs->weight; + } //end if + if (!PC_ExpectTokenString(source, ";")) return qfalse; + return qtrue; +} //end of the function ReadFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeFuzzySeperators_r(fuzzyseperator_t *fs) +{ + if (!fs) return; + if (fs->child) FreeFuzzySeperators_r(fs->child); + if (fs->next) FreeFuzzySeperators_r(fs->next); + FreeMemory(fs); +} //end of the function FreeFuzzySeperators +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig2(weightconfig_t *config) +{ + int i; + + for (i = 0; i < config->numweights; i++) + { + FreeFuzzySeperators_r(config->weights[i].firstseperator); + if (config->weights[i].name) FreeMemory(config->weights[i].name); + } //end for + FreeMemory(config); +} //end of the function FreeWeightConfig2 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeWeightConfig(weightconfig_t *config) +{ + if (!LibVarGetValue("bot_reloadcharacters")) return; + FreeWeightConfig2(config); +} //end of the function FreeWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +fuzzyseperator_t *ReadFuzzySeperators_r(source_t *source) +{ + int newindent, index, def, founddefault; + token_t token; + fuzzyseperator_t *fs, *lastfs, *firstfs; + + founddefault = qfalse; + firstfs = NULL; + lastfs = NULL; + if (!PC_ExpectTokenString(source, "(")) return NULL; + if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) return NULL; + index = token.intvalue; + if (!PC_ExpectTokenString(source, ")")) return NULL; + if (!PC_ExpectTokenString(source, "{")) return NULL; + if (!PC_ExpectAnyToken(source, &token)) return NULL; + do + { + def = !strcmp(token.string, "default"); + if (def || !strcmp(token.string, "case")) + { + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = index; + if (lastfs) lastfs->next = fs; + else firstfs = fs; + lastfs = fs; + if (def) + { + if (founddefault) + { + SourceError(source, "switch already has a default\n"); + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + fs->value = MAX_INVENTORYVALUE; + founddefault = qtrue; + } //end if + else + { + if (!PC_ExpectTokenType(source, TT_NUMBER, TT_INTEGER, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + fs->value = token.intvalue; + } //end else + if (!PC_ExpectTokenString(source, ":") || !PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + newindent = qfalse; + if (!strcmp(token.string, "{")) + { + newindent = qtrue; + if (!PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + if (!strcmp(token.string, "return")) + { + if (!ReadFuzzyWeight(source, fs)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + else if (!strcmp(token.string, "switch")) + { + fs->child = ReadFuzzySeperators_r(source); + if (!fs->child) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end else if + else + { + SourceError(source, "invalid name %s\n", token.string); + return NULL; + } //end else + if (newindent) + { + if (!PC_ExpectTokenString(source, "}")) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } //end if + } //end if + else + { + FreeFuzzySeperators_r(firstfs); + SourceError(source, "invalid name %s\n", token.string); + return NULL; + } //end else + if (!PC_ExpectAnyToken(source, &token)) + { + FreeFuzzySeperators_r(firstfs); + return NULL; + } //end if + } while(strcmp(token.string, "}")); + // + if (!founddefault) + { + SourceWarning(source, "switch without default\n"); + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = index; + fs->value = MAX_INVENTORYVALUE; + fs->weight = 0; + fs->next = NULL; + fs->child = NULL; + if (lastfs) lastfs->next = fs; + else firstfs = fs; + lastfs = fs; + } //end if + // + return firstfs; +} //end of the function ReadFuzzySeperators_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +weightconfig_t *ReadWeightConfig(char *filename) +{ + int newindent, avail = 0, n; + token_t token; + source_t *source; + fuzzyseperator_t *fs; + weightconfig_t *config = NULL; +#ifdef DEBUG + int starttime; + + starttime = Sys_MilliSeconds(); +#endif //DEBUG + + if (!LibVarGetValue("bot_reloadcharacters")) + { + avail = -1; + for( n = 0; n < MAX_WEIGHT_FILES; n++ ) + { + config = weightFileList[n]; + if( !config ) + { + if( avail == -1 ) + { + avail = n; + } //end if + continue; + } //end if + if( strcmp( filename, config->filename ) == 0 ) + { + //botimport.Print( PRT_MESSAGE, "retained %s\n", filename ); + return config; + } //end if + } //end for + + if( avail == -1 ) + { + botimport.Print( PRT_ERROR, "weightFileList was full trying to load %s\n", filename ); + return NULL; + } //end if + } //end if + + PC_SetBaseFolder(BOTFILESBASEFOLDER); + source = LoadSourceFile(filename); + if (!source) + { + botimport.Print(PRT_ERROR, "counldn't load %s\n", filename); + return NULL; + } //end if + // + config = (weightconfig_t *) GetClearedMemory(sizeof(weightconfig_t)); + config->numweights = 0; + Q_strncpyz( config->filename, filename, sizeof(config->filename) ); + //parse the item config file + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, "weight")) + { + if (config->numweights >= MAX_WEIGHTS) + { + SourceWarning(source, "too many fuzzy weights\n"); + break; + } //end if + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + StripDoubleQuotes(token.string); + config->weights[config->numweights].name = (char *) GetClearedMemory(strlen(token.string) + 1); + strcpy(config->weights[config->numweights].name, token.string); + if (!PC_ExpectAnyToken(source, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + newindent = qfalse; + if (!strcmp(token.string, "{")) + { + newindent = qtrue; + if (!PC_ExpectAnyToken(source, &token)) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + } //end if + if (!strcmp(token.string, "switch")) + { + fs = ReadFuzzySeperators_r(source); + if (!fs) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end if + else if (!strcmp(token.string, "return")) + { + fs = (fuzzyseperator_t *) GetClearedMemory(sizeof(fuzzyseperator_t)); + fs->index = 0; + fs->value = MAX_INVENTORYVALUE; + fs->next = NULL; + fs->child = NULL; + if (!ReadFuzzyWeight(source, fs)) + { + FreeMemory(fs); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + config->weights[config->numweights].firstseperator = fs; + } //end else if + else + { + SourceError(source, "invalid name %s\n", token.string); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end else + if (newindent) + { + if (!PC_ExpectTokenString(source, "}")) + { + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end if + } //end if + config->numweights++; + } //end if + else + { + SourceError(source, "invalid name %s\n", token.string); + FreeWeightConfig(config); + FreeSource(source); + return NULL; + } //end else + } //end while + //free the source at the end of a pass + FreeSource(source); + //if the file was located in a pak file + botimport.Print(PRT_MESSAGE, "loaded %s\n", filename); +#ifdef DEBUG + if (botDeveloper) + { + botimport.Print(PRT_MESSAGE, "weights loaded in %d msec\n", Sys_MilliSeconds() - starttime); + } //end if +#endif //DEBUG + // + if (!LibVarGetValue("bot_reloadcharacters")) + { + weightFileList[avail] = config; + } //end if + // + return config; +} //end of the function ReadWeightConfig +#if 0 +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzyWeight(FILE *fp, fuzzyseperator_t *fs) +{ + if (fs->type == WT_BALANCE) + { + if (fprintf(fp, " return balance(") < 0) return qfalse; + if (!WriteFloat(fp, fs->weight)) return qfalse; + if (fprintf(fp, ",") < 0) return qfalse; + if (!WriteFloat(fp, fs->minweight)) return qfalse; + if (fprintf(fp, ",") < 0) return qfalse; + if (!WriteFloat(fp, fs->maxweight)) return qfalse; + if (fprintf(fp, ");\n") < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, " return ") < 0) return qfalse; + if (!WriteFloat(fp, fs->weight)) return qfalse; + if (fprintf(fp, ";\n") < 0) return qfalse; + } //end else + return qtrue; +} //end of the function WriteFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteFuzzySeperators_r(FILE *fp, fuzzyseperator_t *fs, int indent) +{ + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "switch(%d)\n", fs->index) < 0) return qfalse; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "{\n") < 0) return qfalse; + indent++; + do + { + if (!WriteIndent(fp, indent)) return qfalse; + if (fs->next) + { + if (fprintf(fp, "case %d:", fs->value) < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, "default:") < 0) return qfalse; + } //end else + if (fs->child) + { + if (fprintf(fp, "\n") < 0) return qfalse; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "{\n") < 0) return qfalse; + if (!WriteFuzzySeperators_r(fp, fs->child, indent + 1)) return qfalse; + if (!WriteIndent(fp, indent)) return qfalse; + if (fs->next) + { + if (fprintf(fp, "} //end case\n") < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, "} //end default\n") < 0) return qfalse; + } //end else + } //end if + else + { + if (!WriteFuzzyWeight(fp, fs)) return qfalse; + } //end else + fs = fs->next; + } while(fs); + indent--; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "} //end switch\n") < 0) return qfalse; + return qtrue; +} //end of the function WriteItemFuzzyWeights_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean WriteWeightConfig(char *filename, weightconfig_t *config) +{ + int i; + FILE *fp; + weight_t *ifw; + + fp = fopen(filename, "wb"); + if (!fp) return qfalse; + + for (i = 0; i < config->numweights; i++) + { + ifw = &config->weights[i]; + if (fprintf(fp, "\nweight \"%s\"\n", ifw->name) < 0) return qfalse; + if (fprintf(fp, "{\n") < 0) return qfalse; + if (ifw->firstseperator->index > 0) + { + if (!WriteFuzzySeperators_r(fp, ifw->firstseperator, 1)) return qfalse; + } //end if + else + { + if (!WriteIndent(fp, 1)) return qfalse; + if (!WriteFuzzyWeight(fp, ifw->firstseperator)) return qfalse; + } //end else + if (fprintf(fp, "} //end weight\n") < 0) return qfalse; + } //end for + fclose(fp); + return qtrue; +} //end of the function WriteWeightConfig +#endif +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int FindFuzzyWeight(weightconfig_t *wc, char *name) +{ + int i; + + for (i = 0; i < wc->numweights; i++) + { + if (!strcmp(wc->weights[i].name, name)) + { + return i; + } //end if + } //end if + return -1; +} //end of the function FindFuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight_r(int *inventory, fuzzyseperator_t *fs) +{ + float scale, w1, w2; + + if (inventory[fs->index] < fs->value) + { + if (fs->child) return FuzzyWeight_r(inventory, fs->child); + else return fs->weight; + } //end if + else if (fs->next) + { + if (inventory[fs->index] < fs->next->value) + { + //first weight + if (fs->child) w1 = FuzzyWeight_r(inventory, fs->child); + else w1 = fs->weight; + //second weight + if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); + else w2 = fs->next->weight; + //the scale factor + if(fs->next->value == MAX_INVENTORYVALUE) // is fs->next the default case? + return w2; // can't interpolate, return default weight + else + scale = (float) (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); + //scale between the two weights + return (1 - scale) * w1 + scale * w2; + } //end if + return FuzzyWeight_r(inventory, fs->next); + } //end else if + return fs->weight; +} //end of the function FuzzyWeight_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided_r(int *inventory, fuzzyseperator_t *fs) +{ + float scale, w1, w2; + + if (inventory[fs->index] < fs->value) + { + if (fs->child) return FuzzyWeightUndecided_r(inventory, fs->child); + else return fs->minweight + random() * (fs->maxweight - fs->minweight); + } //end if + else if (fs->next) + { + if (inventory[fs->index] < fs->next->value) + { + //first weight + if (fs->child) w1 = FuzzyWeightUndecided_r(inventory, fs->child); + else w1 = fs->minweight + random() * (fs->maxweight - fs->minweight); + //second weight + if (fs->next->child) w2 = FuzzyWeight_r(inventory, fs->next->child); + else w2 = fs->next->minweight + random() * (fs->next->maxweight - fs->next->minweight); + //the scale factor + if(fs->next->value == MAX_INVENTORYVALUE) // is fs->next the default case? + return w2; // can't interpolate, return default weight + else + scale = (float) (inventory[fs->index] - fs->value) / (fs->next->value - fs->value); + //scale between the two weights + return (1 - scale) * w1 + scale * w2; + } //end if + return FuzzyWeightUndecided_r(inventory, fs->next); + } //end else if + return fs->weight; +} //end of the function FuzzyWeightUndecided_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum) +{ +#ifdef EVALUATERECURSIVELY + return FuzzyWeight_r(inventory, wc->weights[weightnum].firstseperator); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if (!s) return 0; + while(1) + { + if (inventory[s->index] < s->value) + { + if (s->child) s = s->child; + else return s->weight; + } //end if + else + { + if (s->next) s = s->next; + else return s->weight; + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum) +{ +#ifdef EVALUATERECURSIVELY + return FuzzyWeightUndecided_r(inventory, wc->weights[weightnum].firstseperator); +#else + fuzzyseperator_t *s; + + s = wc->weights[weightnum].firstseperator; + if (!s) return 0; + while(1) + { + if (inventory[s->index] < s->value) + { + if (s->child) s = s->child; + else return s->minweight + random() * (s->maxweight - s->minweight); + } //end if + else + { + if (s->next) s = s->next; + else return s->minweight + random() * (s->maxweight - s->minweight); + } //end else + } //end if + return 0; +#endif +} //end of the function FuzzyWeightUndecided +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveFuzzySeperator_r(fuzzyseperator_t *fs) +{ + if (fs->child) + { + EvolveFuzzySeperator_r(fs->child); + } //end if + else if (fs->type == WT_BALANCE) + { + //every once in a while an evolution leap occurs, mutation + if (random() < 0.01) fs->weight += crandom() * (fs->maxweight - fs->minweight); + else fs->weight += crandom() * (fs->maxweight - fs->minweight) * 0.5; + //modify bounds if necesary because of mutation + if (fs->weight < fs->minweight) fs->minweight = fs->weight; + else if (fs->weight > fs->maxweight) fs->maxweight = fs->weight; + } //end else if + if (fs->next) EvolveFuzzySeperator_r(fs->next); +} //end of the function EvolveFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EvolveWeightConfig(weightconfig_t *config) +{ + int i; + + for (i = 0; i < config->numweights; i++) + { + EvolveFuzzySeperator_r(config->weights[i].firstseperator); + } //end for +} //end of the function EvolveWeightConfig +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperator_r(fuzzyseperator_t *fs, float scale) +{ + if (fs->child) + { + ScaleFuzzySeperator_r(fs->child, scale); + } //end if + else if (fs->type == WT_BALANCE) + { + // + fs->weight = (float) (fs->maxweight + fs->minweight) * scale; + //get the weight between bounds + if (fs->weight < fs->minweight) fs->weight = fs->minweight; + else if (fs->weight > fs->maxweight) fs->weight = fs->maxweight; + } //end else if + if (fs->next) ScaleFuzzySeperator_r(fs->next, scale); +} //end of the function ScaleFuzzySeperator_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleWeight(weightconfig_t *config, char *name, float scale) +{ + int i; + + if (scale < 0) scale = 0; + else if (scale > 1) scale = 1; + for (i = 0; i < config->numweights; i++) + { + if (!strcmp(name, config->weights[i].name)) + { + ScaleFuzzySeperator_r(config->weights[i].firstseperator, scale); + break; + } //end if + } //end for +} //end of the function ScaleWeight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzySeperatorBalanceRange_r(fuzzyseperator_t *fs, float scale) +{ + if (fs->child) + { + ScaleFuzzySeperatorBalanceRange_r(fs->child, scale); + } //end if + else if (fs->type == WT_BALANCE) + { + float mid = (fs->minweight + fs->maxweight) * 0.5; + //get the weight between bounds + fs->maxweight = mid + (fs->maxweight - mid) * scale; + fs->minweight = mid + (fs->minweight - mid) * scale; + if (fs->maxweight < fs->minweight) + { + fs->maxweight = fs->minweight; + } //end if + } //end else if + if (fs->next) ScaleFuzzySeperatorBalanceRange_r(fs->next, scale); +} //end of the function ScaleFuzzySeperatorBalanceRange_r +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void ScaleFuzzyBalanceRange(weightconfig_t *config, float scale) +{ + int i; + + if (scale < 0) scale = 0; + else if (scale > 100) scale = 100; + for (i = 0; i < config->numweights; i++) + { + ScaleFuzzySeperatorBalanceRange_r(config->weights[i].firstseperator, scale); + } //end for +} //end of the function ScaleFuzzyBalanceRange +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int InterbreedFuzzySeperator_r(fuzzyseperator_t *fs1, fuzzyseperator_t *fs2, + fuzzyseperator_t *fsout) +{ + if (fs1->child) + { + if (!fs2->child || !fsout->child) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal child\n"); + return qfalse; + } //end if + if (!InterbreedFuzzySeperator_r(fs2->child, fs2->child, fsout->child)) + { + return qfalse; + } //end if + } //end if + else if (fs1->type == WT_BALANCE) + { + if (fs2->type != WT_BALANCE || fsout->type != WT_BALANCE) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal balance\n"); + return qfalse; + } //end if + fsout->weight = (fs1->weight + fs2->weight) / 2; + if (fsout->weight > fsout->maxweight) fsout->maxweight = fsout->weight; + if (fsout->weight > fsout->minweight) fsout->minweight = fsout->weight; + } //end else if + if (fs1->next) + { + if (!fs2->next || !fsout->next) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal next\n"); + return qfalse; + } //end if + if (!InterbreedFuzzySeperator_r(fs1->next, fs2->next, fsout->next)) + { + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function InterbreedFuzzySeperator_r +//=========================================================================== +// config1 and config2 are interbreeded and stored in configout +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, + weightconfig_t *configout) +{ + int i; + + if (config1->numweights != config2->numweights || + config1->numweights != configout->numweights) + { + botimport.Print(PRT_ERROR, "cannot interbreed weight configs, unequal numweights\n"); + return; + } //end if + for (i = 0; i < config1->numweights; i++) + { + InterbreedFuzzySeperator_r(config1->weights[i].firstseperator, + config2->weights[i].firstseperator, + configout->weights[i].firstseperator); + } //end for +} //end of the function InterbreedWeightConfigs +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void BotShutdownWeights(void) +{ + int i; + + for( i = 0; i < MAX_WEIGHT_FILES; i++ ) + { + if (weightFileList[i]) + { + FreeWeightConfig2(weightFileList[i]); + weightFileList[i] = NULL; + } //end if + } //end for +} //end of the function BotShutdownWeights diff --git a/code/botlib/be_ai_weight.h b/code/botlib/be_ai_weight.h new file mode 100644 index 0000000..fb1c885 --- /dev/null +++ b/code/botlib/be_ai_weight.h @@ -0,0 +1,83 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ai_weight.h + * + * desc: fuzzy weights + * + * $Archive: /source/code/botlib/be_ai_weight.h $ + * + *****************************************************************************/ + +#define WT_BALANCE 1 +#define MAX_WEIGHTS 128 + +//fuzzy seperator +typedef struct fuzzyseperator_s +{ + int index; + int value; + int type; + float weight; + float minweight; + float maxweight; + struct fuzzyseperator_s *child; + struct fuzzyseperator_s *next; +} fuzzyseperator_t; + +//fuzzy weight +typedef struct weight_s +{ + char *name; + struct fuzzyseperator_s *firstseperator; +} weight_t; + +//weight configuration +typedef struct weightconfig_s +{ + int numweights; + weight_t weights[MAX_WEIGHTS]; + char filename[MAX_QPATH]; +} weightconfig_t; + +//reads a weight configuration +weightconfig_t *ReadWeightConfig(char *filename); +//free a weight configuration +void FreeWeightConfig(weightconfig_t *config); +//writes a weight configuration, returns true if successfull +qboolean WriteWeightConfig(char *filename, weightconfig_t *config); +//find the fuzzy weight with the given name +int FindFuzzyWeight(weightconfig_t *wc, char *name); +//returns the fuzzy weight for the given inventory and weight +float FuzzyWeight(int *inventory, weightconfig_t *wc, int weightnum); +float FuzzyWeightUndecided(int *inventory, weightconfig_t *wc, int weightnum); +//scales the weight with the given name +void ScaleWeight(weightconfig_t *config, char *name, float scale); +//scale the balance range +void ScaleBalanceRange(weightconfig_t *config, float scale); +//evolves the weight configuration +void EvolveWeightConfig(weightconfig_t *config); +//interbreed the weight configurations and stores the interbreeded one in configout +void InterbreedWeightConfigs(weightconfig_t *config1, weightconfig_t *config2, weightconfig_t *configout); +//frees cached weight configurations +void BotShutdownWeights(void); diff --git a/code/botlib/be_ea.c b/code/botlib/be_ea.c new file mode 100644 index 0000000..41653fd --- /dev/null +++ b/code/botlib/be_ea.c @@ -0,0 +1,476 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_ea.c + * + * desc: elementary actions + * + * $Archive: /MissionPack/code/botlib/be_ea.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "botlib.h" +#include "be_interface.h" +#include "be_ea.h" + +#define MAX_USERMOVE 400 +#define MAX_COMMANDARGUMENTS 10 + +bot_input_t *botinputs; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Say(int client, char *str) +{ + botimport.BotClientCommand(client, va("say %s", str) ); +} //end of the function EA_Say +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_SayTeam(int client, char *str) +{ + botimport.BotClientCommand(client, va("say_team %s", str)); +} //end of the function EA_SayTeam +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Tell(int client, int clientto, char *str) +{ + botimport.BotClientCommand(client, va("tell %d, %s", clientto, str)); +} //end of the function EA_SayTeam +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_UseItem(int client, char *it) +{ + botimport.BotClientCommand(client, va("use %s", it)); +} //end of the function EA_UseItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DropItem(int client, char *it) +{ + botimport.BotClientCommand(client, va("drop %s", it)); +} //end of the function EA_DropItem +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_UseInv(int client, char *inv) +{ + botimport.BotClientCommand(client, va("invuse %s", inv)); +} //end of the function EA_UseInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DropInv(int client, char *inv) +{ + botimport.BotClientCommand(client, va("invdrop %s", inv)); +} //end of the function EA_DropInv +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Gesture(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_GESTURE; +} //end of the function EA_Gesture +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Command(int client, char *command) +{ + botimport.BotClientCommand(client, command); +} //end of the function EA_Command +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_SelectWeapon(int client, int weapon) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->weapon = weapon; +} //end of the function EA_SelectWeapon +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Attack(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_ATTACK; +} //end of the function EA_Attack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Talk(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_TALK; +} //end of the function EA_Talk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Use(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_USE; +} //end of the function EA_Use +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Respawn(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_RESPAWN; +} //end of the function EA_Respawn +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Jump(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + if (bi->actionflags & ACTION_JUMPEDLASTFRAME) + { + bi->actionflags &= ~ACTION_JUMP; + } //end if + else + { + bi->actionflags |= ACTION_JUMP; + } //end if +} //end of the function EA_Jump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_DelayedJump(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + if (bi->actionflags & ACTION_JUMPEDLASTFRAME) + { + bi->actionflags &= ~ACTION_DELAYEDJUMP; + } //end if + else + { + bi->actionflags |= ACTION_DELAYEDJUMP; + } //end if +} //end of the function EA_DelayedJump +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Crouch(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_CROUCH; +} //end of the function EA_Crouch +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Walk(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_WALK; +} //end of the function EA_Walk +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Action(int client, int action) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= action; +} //end of function EA_Action +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveUp(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEUP; +} //end of the function EA_MoveUp +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveDown(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEDOWN; +} //end of the function EA_MoveDown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveForward(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEFORWARD; +} //end of the function EA_MoveForward +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveBack(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVEBACK; +} //end of the function EA_MoveBack +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveLeft(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVELEFT; +} //end of the function EA_MoveLeft +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_MoveRight(int client) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + bi->actionflags |= ACTION_MOVERIGHT; +} //end of the function EA_MoveRight +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Move(int client, vec3_t dir, float speed) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy(dir, bi->dir); + //cap speed + if (speed > MAX_USERMOVE) speed = MAX_USERMOVE; + else if (speed < -MAX_USERMOVE) speed = -MAX_USERMOVE; + bi->speed = speed; +} //end of the function EA_Move +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_View(int client, vec3_t viewangles) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + + VectorCopy(viewangles, bi->viewangles); +} //end of the function EA_View +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_EndRegular(int client, float thinktime) +{ +} //end of the function EA_EndRegular +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_GetInput(int client, float thinktime, bot_input_t *input) +{ + bot_input_t *bi; + + bi = &botinputs[client]; + bi->thinktime = thinktime; + Com_Memcpy(input, bi, sizeof(bot_input_t)); +} //end of the function EA_GetInput +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_ResetInput(int client) +{ + bot_input_t *bi; + int jumped = qfalse; + + bi = &botinputs[client]; + + bi->thinktime = 0; + VectorClear(bi->dir); + bi->speed = 0; + jumped = bi->actionflags & ACTION_JUMP; + bi->actionflags = 0; + if (jumped) bi->actionflags |= ACTION_JUMPEDLASTFRAME; +} //end of the function EA_ResetInput +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int EA_Setup(void) +{ + //initialize the bot inputs + botinputs = (bot_input_t *) GetClearedHunkMemory( + botlibglobals.maxclients * sizeof(bot_input_t)); + return BLERR_NOERROR; +} //end of the function EA_Setup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void EA_Shutdown(void) +{ + FreeMemory(botinputs); + botinputs = NULL; +} //end of the function EA_Shutdown diff --git a/code/botlib/be_ea.h b/code/botlib/be_ea.h new file mode 100644 index 0000000..4fb3704 --- /dev/null +++ b/code/botlib/be_ea.h @@ -0,0 +1,66 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * 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/code/botlib/be_interface.c b/code/botlib/be_interface.c new file mode 100644 index 0000000..227d696 --- /dev/null +++ b/code/botlib/be_interface.c @@ -0,0 +1,908 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_interface.c + * + * desc: bot library interface + * + * $Archive: /MissionPack/code/botlib/be_interface.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "aasfile.h" +#include "botlib.h" +#include "be_aas.h" +#include "be_aas_funcs.h" +#include "be_aas_def.h" +#include "be_interface.h" + +#include "be_ea.h" +#include "be_ai_weight.h" +#include "be_ai_goal.h" +#include "be_ai_move.h" +#include "be_ai_weap.h" +#include "be_ai_chat.h" +#include "be_ai_char.h" +#include "be_ai_gen.h" + +//library globals in a structure +botlib_globals_t botlibglobals; + +botlib_export_t be_botlib_export; +botlib_import_t botimport; +// +int botDeveloper; +//qtrue if the library is setup +int botlibsetup = qfalse; + +//=========================================================================== +// +// several functions used by the exported functions +// +//=========================================================================== + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Sys_MilliSeconds(void) +{ + return clock() * 1000 / CLOCKS_PER_SEC; +} //end of the function Sys_MilliSeconds +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidClientNumber(int num, char *str) +{ + if (num < 0 || num > botlibglobals.maxclients) + { + //weird: the disabled stuff results in a crash + botimport.Print(PRT_ERROR, "%s: invalid client number %d, [0, %d]\n", + str, num, botlibglobals.maxclients); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ValidEntityNumber(int num, char *str) +{ + if (num < 0 || num > botlibglobals.maxentities) + { + botimport.Print(PRT_ERROR, "%s: invalid entity number %d, [0, %d]\n", + str, num, botlibglobals.maxentities); + return qfalse; + } //end if + return qtrue; +} //end of the function BotValidateClientNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean BotLibSetup(char *str) +{ + if (!botlibglobals.botlibsetup) + { + botimport.Print(PRT_ERROR, "%s: bot library used before being setup\n", str); + return qfalse; + } //end if + return qtrue; +} //end of the function BotLibSetup + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibSetup(void) +{ + int errnum; + + botDeveloper = LibVarGetValue("bot_developer"); + memset( &botlibglobals, 0, sizeof(botlibglobals) ); + //initialize byte swapping (litte endian etc.) +// Swap_Init(); + + if(botDeveloper) + { + char *homedir, *gamedir, *basedir; + char logfilename[MAX_OSPATH]; + + homedir = LibVarGetString("homedir"); + gamedir = LibVarGetString("gamedir"); + basedir = LibVarGetString("com_basegame"); + + if (*homedir) + { + if(*gamedir) + Com_sprintf(logfilename, sizeof(logfilename), "%s%c%s%cbotlib.log", homedir, PATH_SEP, gamedir, PATH_SEP); + else if(*basedir) + Com_sprintf(logfilename, sizeof(logfilename), "%s%c%s%cbotlib.log", homedir, PATH_SEP, basedir, PATH_SEP); + else + Com_sprintf(logfilename, sizeof(logfilename), "%s%c" BASEGAME "%cbotlib.log", homedir, PATH_SEP, PATH_SEP); + } + else + Com_sprintf(logfilename, sizeof(logfilename), "botlib.log"); + + Log_Open(logfilename); + } + + botimport.Print(PRT_MESSAGE, "------- BotLib Initialization -------\n"); + + botlibglobals.maxclients = (int) LibVarValue("maxclients", "128"); + botlibglobals.maxentities = (int) LibVarValue("maxentities", "1024"); + + errnum = AAS_Setup(); //be_aas_main.c + if (errnum != BLERR_NOERROR) return errnum; + errnum = EA_Setup(); //be_ea.c + if (errnum != BLERR_NOERROR) return errnum; + /* + errnum = BotSetupWeaponAI(); //be_ai_weap.c + if (errnum != BLERR_NOERROR)return errnum; + errnum = BotSetupGoalAI(); //be_ai_goal.c + if (errnum != BLERR_NOERROR) return errnum; + errnum = BotSetupChatAI(); //be_ai_chat.c + if (errnum != BLERR_NOERROR) return errnum; + errnum = BotSetupMoveAI(); //be_ai_move.c + if (errnum != BLERR_NOERROR) return errnum; + */ + + botlibsetup = qtrue; + botlibglobals.botlibsetup = qtrue; + + return BLERR_NOERROR; +} //end of the function Export_BotLibSetup +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibShutdown(void) +{ + if (!BotLibSetup("BotLibShutdown")) return BLERR_LIBRARYNOTSETUP; +#ifndef DEMO + //DumpFileCRCs(); +#endif //DEMO + // + BotShutdownChatAI(); //be_ai_chat.c + BotShutdownMoveAI(); //be_ai_move.c + BotShutdownGoalAI(); //be_ai_goal.c + BotShutdownWeaponAI(); //be_ai_weap.c + BotShutdownWeights(); //be_ai_weight.c + BotShutdownCharacters(); //be_ai_char.c + //shud down aas + AAS_Shutdown(); + //shut down bot elemantary actions + EA_Shutdown(); + //free all libvars + LibVarDeAllocAll(); + //remove all global defines from the pre compiler + PC_RemoveAllGlobalDefines(); + + //dump all allocated memory +// DumpMemory(); +#ifdef DEBUG + PrintMemoryLabels(); +#endif + //shut down library log file + Log_Shutdown(); + // + botlibsetup = qfalse; + botlibglobals.botlibsetup = qfalse; + // print any files still open + PC_CheckOpenSourceHandles(); + // + return BLERR_NOERROR; +} //end of the function Export_BotLibShutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarSet(char *var_name, char *value) +{ + LibVarSet(var_name, value); + return BLERR_NOERROR; +} //end of the function Export_BotLibVarSet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibVarGet(char *var_name, char *value, int size) +{ + char *varvalue; + + varvalue = LibVarGetString(var_name); + strncpy(value, varvalue, size-1); + value[size-1] = '\0'; + return BLERR_NOERROR; +} //end of the function Export_BotLibVarGet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibStartFrame(float time) +{ + if (!BotLibSetup("BotStartFrame")) return BLERR_LIBRARYNOTSETUP; + return AAS_StartFrame(time); +} //end of the function Export_BotLibStartFrame +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibLoadMap(const char *mapname) +{ +#ifdef DEBUG + int starttime = Sys_MilliSeconds(); +#endif + int errnum; + + if (!BotLibSetup("BotLoadMap")) return BLERR_LIBRARYNOTSETUP; + // + botimport.Print(PRT_MESSAGE, "------------ Map Loading ------------\n"); + //startup AAS for the current map, model and sound index + errnum = AAS_LoadMap(mapname); + if (errnum != BLERR_NOERROR) return errnum; + //initialize the items in the level + BotInitLevelItems(); //be_ai_goal.h + BotSetBrushModelTypes(); //be_ai_move.h + // + botimport.Print(PRT_MESSAGE, "-------------------------------------\n"); +#ifdef DEBUG + botimport.Print(PRT_MESSAGE, "map loaded in %d msec\n", Sys_MilliSeconds() - starttime); +#endif + // + return BLERR_NOERROR; +} //end of the function Export_BotLibLoadMap +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int Export_BotLibUpdateEntity(int ent, bot_entitystate_t *state) +{ + if (!BotLibSetup("BotUpdateEntity")) return BLERR_LIBRARYNOTSETUP; + if (!ValidEntityNumber(ent, "BotUpdateEntity")) return BLERR_INVALIDENTITYNUMBER; + + return AAS_UpdateEntity(ent, state); +} //end of the function Export_BotLibUpdateEntity +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void AAS_TestMovementPrediction(int entnum, vec3_t origin, vec3_t dir); +void ElevatorBottomCenter(aas_reachability_t *reach, vec3_t bottomcenter); +int BotGetReachabilityToGoal(vec3_t origin, int areanum, + int lastgoalareanum, int lastareanum, + int *avoidreach, float *avoidreachtimes, int *avoidreachtries, + bot_goal_t *goal, int travelflags, int movetravelflags, + struct bot_avoidspot_s *avoidspots, int numavoidspots, int *flags); + +int AAS_PointLight(vec3_t origin, int *red, int *green, int *blue); + +int AAS_TraceAreas(vec3_t start, vec3_t end, int *areas, vec3_t *points, int maxareas); + +int AAS_Reachability_WeaponJump(int area1num, int area2num); + +int BotFuzzyPointReachabilityArea(vec3_t origin); + +float BotGapDistance(vec3_t origin, vec3_t hordir, int entnum); + +void AAS_FloodAreas(vec3_t origin); + +int BotExportTest(int parm0, char *parm1, vec3_t parm2, vec3_t parm3) +{ + +// return AAS_PointLight(parm2, NULL, NULL, NULL); + +#ifdef DEBUG + static int area = -1; + static int line[2]; + int newarea, i, highlightarea, flood; +// int reachnum; + vec3_t eye, forward, right, end, origin; +// vec3_t bottomcenter; +// aas_trace_t trace; +// aas_face_t *face; +// aas_entity_t *ent; +// bsp_trace_t bsptrace; +// aas_reachability_t reach; +// bot_goal_t goal; + + // clock_t start_time, end_time; + vec3_t mins = {-16, -16, -24}; + vec3_t maxs = {16, 16, 32}; + +// int areas[10], numareas; + + + //return 0; + + if (!aasworld.loaded) return 0; + + /* + if (parm0 & 1) + { + AAS_ClearShownPolygons(); + AAS_FloodAreas(parm2); + } //end if + return 0; + */ + for (i = 0; i < 2; i++) if (!line[i]) line[i] = botimport.DebugLineCreate(); + +// AAS_ClearShownDebugLines(); + + //if (AAS_AgainstLadder(parm2)) botimport.Print(PRT_MESSAGE, "against ladder\n"); + //BotOnGround(parm2, PRESENCE_NORMAL, 1, &newarea, &newarea); + //botimport.Print(PRT_MESSAGE, "%f %f %f\n", parm2[0], parm2[1], parm2[2]); + //* + highlightarea = LibVarGetValue("bot_highlightarea"); + if (highlightarea > 0) + { + newarea = highlightarea; + } //end if + else + { + VectorCopy(parm2, origin); + origin[2] += 0.5; + //newarea = AAS_PointAreaNum(origin); + newarea = BotFuzzyPointReachabilityArea(origin); + } //end else + + botimport.Print(PRT_MESSAGE, "\rtravel time to goal (%d) = %d ", botlibglobals.goalareanum, + AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT)); + //newarea = BotReachabilityArea(origin, qtrue); + if (newarea != area) + { + botimport.Print(PRT_MESSAGE, "origin = %f, %f, %f\n", origin[0], origin[1], origin[2]); + area = newarea; + botimport.Print(PRT_MESSAGE, "new area %d, cluster %d, presence type %d\n", + area, AAS_AreaCluster(area), AAS_PointPresenceType(origin)); + botimport.Print(PRT_MESSAGE, "area contents: "); + if (aasworld.areasettings[area].contents & AREACONTENTS_WATER) + { + botimport.Print(PRT_MESSAGE, "water &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_LAVA) + { + botimport.Print(PRT_MESSAGE, "lava &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_SLIME) + { + botimport.Print(PRT_MESSAGE, "slime &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_JUMPPAD) + { + botimport.Print(PRT_MESSAGE, "jump pad &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_CLUSTERPORTAL) + { + botimport.Print(PRT_MESSAGE, "cluster portal &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_VIEWPORTAL) + { + botimport.Print(PRT_MESSAGE, "view portal &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_DONOTENTER) + { + botimport.Print(PRT_MESSAGE, "do not enter &"); + } //end if + if (aasworld.areasettings[area].contents & AREACONTENTS_MOVER) + { + botimport.Print(PRT_MESSAGE, "mover &"); + } //end if + if (!aasworld.areasettings[area].contents) + { + botimport.Print(PRT_MESSAGE, "empty"); + } //end if + botimport.Print(PRT_MESSAGE, "\n"); + botimport.Print(PRT_MESSAGE, "travel time to goal (%d) = %d\n", botlibglobals.goalareanum, + AAS_AreaTravelTimeToGoalArea(newarea, origin, botlibglobals.goalareanum, TFL_DEFAULT|TFL_ROCKETJUMP)); + /* + VectorCopy(origin, end); + end[2] += 5; + numareas = AAS_TraceAreas(origin, end, areas, NULL, 10); + AAS_TraceClientBBox(origin, end, PRESENCE_CROUCH, -1); + botimport.Print(PRT_MESSAGE, "num areas = %d, area = %d\n", numareas, areas[0]); + */ + /* + botlibglobals.goalareanum = newarea; + VectorCopy(parm2, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", + origin[0], origin[1], origin[2], newarea); + */ + } //end if + //* + flood = LibVarGetValue("bot_flood"); + if (parm0 & 1) + { + if (flood) + { + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_FloodAreas(parm2); + } + else + { + botlibglobals.goalareanum = newarea; + VectorCopy(parm2, botlibglobals.goalorigin); + botimport.Print(PRT_MESSAGE, "new goal %2.1f %2.1f %2.1f area %d\n", + origin[0], origin[1], origin[2], newarea); + } + } //end if*/ + if (flood) + return 0; +// if (parm0 & BUTTON_USE) +// { +// botlibglobals.runai = !botlibglobals.runai; +// if (botlibglobals.runai) botimport.Print(PRT_MESSAGE, "started AI\n"); +// else botimport.Print(PRT_MESSAGE, "stopped AI\n"); + //* / + /* + goal.areanum = botlibglobals.goalareanum; + reachnum = BotGetReachabilityToGoal(parm2, newarea, 1, + ms.avoidreach, ms.avoidreachtimes, + &goal, TFL_DEFAULT); + if (!reachnum) + { + botimport.Print(PRT_MESSAGE, "goal not reachable\n"); + } //end if + else + { + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ClearShownDebugLines(); + AAS_ShowArea(area, qtrue); + AAS_ShowArea(reach.areanum, qtrue); + AAS_DrawCross(reach.start, 6, LINECOLOR_BLUE); + AAS_DrawCross(reach.end, 6, LINECOLOR_RED); + // + if ((reach.traveltype & TRAVELTYPE_MASK) == TRAVEL_ELEVATOR) + { + ElevatorBottomCenter(&reach, bottomcenter); + AAS_DrawCross(bottomcenter, 10, LINECOLOR_GREEN); + } //end if + } //end else*/ +// botimport.Print(PRT_MESSAGE, "travel time to goal = %d\n", +// AAS_AreaTravelTimeToGoalArea(area, origin, botlibglobals.goalareanum, TFL_DEFAULT)); +// botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); +// AAS_Reachability_WeaponJump(703, 716); +// } //end if*/ + +/* face = AAS_AreaGroundFace(newarea, parm2); + if (face) + { + AAS_ShowFace(face - aasworld.faces); + } //end if*/ + /* + AAS_ClearShownDebugLines(); + AAS_ShowArea(newarea, parm0 & BUTTON_USE); + AAS_ShowReachableAreas(area); + */ + AAS_ClearShownPolygons(); + AAS_ClearShownDebugLines(); + AAS_ShowAreaPolygons(newarea, 1, parm0 & 4); + if (parm0 & 2) AAS_ShowReachableAreas(area); + else + { + static int lastgoalareanum, lastareanum; + static int avoidreach[MAX_AVOIDREACH]; + static float avoidreachtimes[MAX_AVOIDREACH]; + static int avoidreachtries[MAX_AVOIDREACH]; + int reachnum, resultFlags; + bot_goal_t goal; + aas_reachability_t reach; + + /* + goal.areanum = botlibglobals.goalareanum; + VectorCopy(botlibglobals.goalorigin, goal.origin); + reachnum = BotGetReachabilityToGoal(origin, newarea, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, + NULL, 0, &resultFlags); + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ShowReachability(&reach); + */ + int curarea; + vec3_t curorigin; + + goal.areanum = botlibglobals.goalareanum; + VectorCopy(botlibglobals.goalorigin, goal.origin); + VectorCopy(origin, curorigin); + curarea = newarea; + for ( i = 0; i < 100; i++ ) { + if ( curarea == goal.areanum ) { + break; + } + reachnum = BotGetReachabilityToGoal(curorigin, curarea, + lastgoalareanum, lastareanum, + avoidreach, avoidreachtimes, avoidreachtries, + &goal, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, TFL_DEFAULT|TFL_FUNCBOB|TFL_ROCKETJUMP, + NULL, 0, &resultFlags); + AAS_ReachabilityFromNum(reachnum, &reach); + AAS_ShowReachability(&reach); + VectorCopy(reach.end, origin); + lastareanum = curarea; + curarea = reach.areanum; + } + } //end else + VectorClear(forward); + //BotGapDistance(origin, forward, 0); + /* + if (parm0 & BUTTON_USE) + { + botimport.Print(PRT_MESSAGE, "test rj from 703 to 716\n"); + AAS_Reachability_WeaponJump(703, 716); + } //end if*/ + + AngleVectors(parm3, forward, right, NULL); + //get the eye 16 units to the right of the origin + VectorMA(parm2, 8, right, eye); + //get the eye 24 units up + eye[2] += 24; + //get the end point for the line to be traced + VectorMA(eye, 800, forward, end); + +// AAS_TestMovementPrediction(1, parm2, forward); +/* + //trace the line to find the hit point + trace = AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); + if (!line[0]) line[0] = botimport.DebugLineCreate(); + botimport.DebugLineShow(line[0], eye, trace.endpos, LINECOLOR_BLUE); + // + AAS_ClearShownDebugLines(); + if (trace.ent) + { + ent = &aasworld.entities[trace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if +*/ + +/* + start_time = clock(); + for (i = 0; i < 2000; i++) + { + AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); +// AAS_TraceClientBBox(eye, end, PRESENCE_NORMAL, 1); + } //end for + end_time = clock(); + botimport.Print(PRT_MESSAGE, "me %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); + start_time = clock(); + for (i = 0; i < 2000; i++) + { + AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + } //end for + end_time = clock(); + botimport.Print(PRT_MESSAGE, "id %lu clocks, %lu CLOCKS_PER_SEC\n", end_time - start_time, CLOCKS_PER_SEC); +*/ + + // TTimo: nested comments are BAD for gcc -Werror, use #if 0 instead.. +#if 0 + AAS_ClearShownDebugLines(); + //bsptrace = AAS_Trace(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); + bsptrace = AAS_Trace(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + if (!line[0]) line[0] = botimport.DebugLineCreate(); + botimport.DebugLineShow(line[0], eye, bsptrace.endpos, LINECOLOR_YELLOW); + if (bsptrace.fraction < 1.0) + { + face = AAS_TraceEndFace(&trace); + if (face) + { + AAS_ShowFace(face - aasworld.faces); + } //end if + + AAS_DrawPlaneCross(bsptrace.endpos, + bsptrace.plane.normal, + bsptrace.plane.dist + bsptrace.exp_dist, + bsptrace.plane.type, LINECOLOR_GREEN); + if (trace.ent) + { + ent = &aasworld.entities[trace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if + } //end if + //bsptrace = AAS_Trace2(eye, NULL, NULL, end, 1, MASK_PLAYERSOLID); + bsptrace = AAS_Trace2(eye, mins, maxs, end, 1, MASK_PLAYERSOLID); + botimport.DebugLineShow(line[1], eye, bsptrace.endpos, LINECOLOR_BLUE); + if (bsptrace.fraction < 1.0) + { + AAS_DrawPlaneCross(bsptrace.endpos, + bsptrace.plane.normal, + bsptrace.plane.dist,// + bsptrace.exp_dist, + bsptrace.plane.type, LINECOLOR_RED); + if (bsptrace.ent) + { + ent = &aasworld.entities[bsptrace.ent]; + AAS_ShowBoundingBox(ent->origin, ent->mins, ent->maxs); + } //end if + } //end if +#endif +#endif + return 0; +} //end of the function BotExportTest + + +/* +============ +Init_AAS_Export +============ +*/ +static void Init_AAS_Export( aas_export_t *aas ) { + //-------------------------------------------- + // be_aas_entity.c + //-------------------------------------------- + aas->AAS_EntityInfo = AAS_EntityInfo; + //-------------------------------------------- + // be_aas_main.c + //-------------------------------------------- + aas->AAS_Initialized = AAS_Initialized; + aas->AAS_PresenceTypeBoundingBox = AAS_PresenceTypeBoundingBox; + aas->AAS_Time = AAS_Time; + //-------------------------------------------- + // be_aas_sample.c + //-------------------------------------------- + aas->AAS_PointAreaNum = AAS_PointAreaNum; + aas->AAS_PointReachabilityAreaIndex = AAS_PointReachabilityAreaIndex; + aas->AAS_TraceAreas = AAS_TraceAreas; + aas->AAS_BBoxAreas = AAS_BBoxAreas; + aas->AAS_AreaInfo = AAS_AreaInfo; + //-------------------------------------------- + // be_aas_bspq3.c + //-------------------------------------------- + aas->AAS_PointContents = AAS_PointContents; + aas->AAS_NextBSPEntity = AAS_NextBSPEntity; + aas->AAS_ValueForBSPEpairKey = AAS_ValueForBSPEpairKey; + aas->AAS_VectorForBSPEpairKey = AAS_VectorForBSPEpairKey; + aas->AAS_FloatForBSPEpairKey = AAS_FloatForBSPEpairKey; + aas->AAS_IntForBSPEpairKey = AAS_IntForBSPEpairKey; + //-------------------------------------------- + // be_aas_reach.c + //-------------------------------------------- + aas->AAS_AreaReachability = AAS_AreaReachability; + //-------------------------------------------- + // be_aas_route.c + //-------------------------------------------- + aas->AAS_AreaTravelTimeToGoalArea = AAS_AreaTravelTimeToGoalArea; + aas->AAS_EnableRoutingArea = AAS_EnableRoutingArea; + aas->AAS_PredictRoute = AAS_PredictRoute; + //-------------------------------------------- + // be_aas_altroute.c + //-------------------------------------------- + aas->AAS_AlternativeRouteGoals = AAS_AlternativeRouteGoals; + //-------------------------------------------- + // be_aas_move.c + //-------------------------------------------- + aas->AAS_Swimming = AAS_Swimming; + aas->AAS_PredictClientMovement = AAS_PredictClientMovement; +} + + +/* +============ +Init_EA_Export +============ +*/ +static void Init_EA_Export( ea_export_t *ea ) { + //ClientCommand elementary actions + ea->EA_Command = EA_Command; + ea->EA_Say = EA_Say; + ea->EA_SayTeam = EA_SayTeam; + + ea->EA_Action = EA_Action; + ea->EA_Gesture = EA_Gesture; + ea->EA_Talk = EA_Talk; + ea->EA_Attack = EA_Attack; + ea->EA_Use = EA_Use; + ea->EA_Respawn = EA_Respawn; + ea->EA_Crouch = EA_Crouch; + ea->EA_MoveUp = EA_MoveUp; + ea->EA_MoveDown = EA_MoveDown; + ea->EA_MoveForward = EA_MoveForward; + ea->EA_MoveBack = EA_MoveBack; + ea->EA_MoveLeft = EA_MoveLeft; + ea->EA_MoveRight = EA_MoveRight; + + ea->EA_SelectWeapon = EA_SelectWeapon; + ea->EA_Jump = EA_Jump; + ea->EA_DelayedJump = EA_DelayedJump; + ea->EA_Move = EA_Move; + ea->EA_View = EA_View; + ea->EA_GetInput = EA_GetInput; + ea->EA_EndRegular = EA_EndRegular; + ea->EA_ResetInput = EA_ResetInput; +} + + +/* +============ +Init_AI_Export +============ +*/ +static void Init_AI_Export( ai_export_t *ai ) { + //----------------------------------- + // be_ai_char.h + //----------------------------------- + ai->BotLoadCharacter = BotLoadCharacter; + ai->BotFreeCharacter = BotFreeCharacter; + ai->Characteristic_Float = Characteristic_Float; + ai->Characteristic_BFloat = Characteristic_BFloat; + ai->Characteristic_Integer = Characteristic_Integer; + ai->Characteristic_BInteger = Characteristic_BInteger; + ai->Characteristic_String = Characteristic_String; + //----------------------------------- + // be_ai_chat.h + //----------------------------------- + ai->BotAllocChatState = BotAllocChatState; + ai->BotFreeChatState = BotFreeChatState; + ai->BotQueueConsoleMessage = BotQueueConsoleMessage; + ai->BotRemoveConsoleMessage = BotRemoveConsoleMessage; + ai->BotNextConsoleMessage = BotNextConsoleMessage; + ai->BotNumConsoleMessages = BotNumConsoleMessages; + ai->BotInitialChat = BotInitialChat; + ai->BotNumInitialChats = BotNumInitialChats; + ai->BotReplyChat = BotReplyChat; + ai->BotChatLength = BotChatLength; + ai->BotEnterChat = BotEnterChat; + ai->BotGetChatMessage = BotGetChatMessage; + ai->StringContains = StringContains; + ai->BotFindMatch = BotFindMatch; + ai->BotMatchVariable = BotMatchVariable; + ai->UnifyWhiteSpaces = UnifyWhiteSpaces; + ai->BotReplaceSynonyms = BotReplaceSynonyms; + ai->BotLoadChatFile = BotLoadChatFile; + ai->BotSetChatGender = BotSetChatGender; + ai->BotSetChatName = BotSetChatName; + //----------------------------------- + // be_ai_goal.h + //----------------------------------- + ai->BotResetGoalState = BotResetGoalState; + ai->BotResetAvoidGoals = BotResetAvoidGoals; + ai->BotRemoveFromAvoidGoals = BotRemoveFromAvoidGoals; + ai->BotPushGoal = BotPushGoal; + ai->BotPopGoal = BotPopGoal; + ai->BotEmptyGoalStack = BotEmptyGoalStack; + ai->BotDumpAvoidGoals = BotDumpAvoidGoals; + ai->BotDumpGoalStack = BotDumpGoalStack; + ai->BotGoalName = BotGoalName; + ai->BotGetTopGoal = BotGetTopGoal; + ai->BotGetSecondGoal = BotGetSecondGoal; + ai->BotChooseLTGItem = BotChooseLTGItem; + ai->BotChooseNBGItem = BotChooseNBGItem; + ai->BotTouchingGoal = BotTouchingGoal; + ai->BotItemGoalInVisButNotVisible = BotItemGoalInVisButNotVisible; + ai->BotGetLevelItemGoal = BotGetLevelItemGoal; + ai->BotGetNextCampSpotGoal = BotGetNextCampSpotGoal; + ai->BotGetMapLocationGoal = BotGetMapLocationGoal; + ai->BotAvoidGoalTime = BotAvoidGoalTime; + ai->BotSetAvoidGoalTime = BotSetAvoidGoalTime; + ai->BotInitLevelItems = BotInitLevelItems; + ai->BotUpdateEntityItems = BotUpdateEntityItems; + ai->BotLoadItemWeights = BotLoadItemWeights; + ai->BotFreeItemWeights = BotFreeItemWeights; + ai->BotInterbreedGoalFuzzyLogic = BotInterbreedGoalFuzzyLogic; + ai->BotSaveGoalFuzzyLogic = BotSaveGoalFuzzyLogic; + ai->BotMutateGoalFuzzyLogic = BotMutateGoalFuzzyLogic; + ai->BotAllocGoalState = BotAllocGoalState; + ai->BotFreeGoalState = BotFreeGoalState; + //----------------------------------- + // be_ai_move.h + //----------------------------------- + ai->BotResetMoveState = BotResetMoveState; + ai->BotMoveToGoal = BotMoveToGoal; + ai->BotMoveInDirection = BotMoveInDirection; + ai->BotResetAvoidReach = BotResetAvoidReach; + ai->BotResetLastAvoidReach = BotResetLastAvoidReach; + ai->BotReachabilityArea = BotReachabilityArea; + ai->BotMovementViewTarget = BotMovementViewTarget; + ai->BotPredictVisiblePosition = BotPredictVisiblePosition; + ai->BotAllocMoveState = BotAllocMoveState; + ai->BotFreeMoveState = BotFreeMoveState; + ai->BotInitMoveState = BotInitMoveState; + ai->BotAddAvoidSpot = BotAddAvoidSpot; + //----------------------------------- + // be_ai_weap.h + //----------------------------------- + ai->BotChooseBestFightWeapon = BotChooseBestFightWeapon; + ai->BotGetWeaponInfo = BotGetWeaponInfo; + ai->BotLoadWeaponWeights = BotLoadWeaponWeights; + ai->BotAllocWeaponState = BotAllocWeaponState; + ai->BotFreeWeaponState = BotFreeWeaponState; + ai->BotResetWeaponState = BotResetWeaponState; + //----------------------------------- + // be_ai_gen.h + //----------------------------------- + ai->GeneticParentsAndChildSelection = GeneticParentsAndChildSelection; +} + + +/* +============ +GetBotLibAPI +============ +*/ +botlib_export_t *GetBotLibAPI(int apiVersion, botlib_import_t *import) { + assert(import); + botimport = *import; + assert(botimport.Print); + + Com_Memset( &be_botlib_export, 0, sizeof( be_botlib_export ) ); + + if ( apiVersion != BOTLIB_API_VERSION ) { + botimport.Print( PRT_ERROR, "Mismatched BOTLIB_API_VERSION: expected %i, got %i\n", BOTLIB_API_VERSION, apiVersion ); + return NULL; + } + + Init_AAS_Export(&be_botlib_export.aas); + Init_EA_Export(&be_botlib_export.ea); + Init_AI_Export(&be_botlib_export.ai); + + be_botlib_export.BotLibSetup = Export_BotLibSetup; + be_botlib_export.BotLibShutdown = Export_BotLibShutdown; + be_botlib_export.BotLibVarSet = Export_BotLibVarSet; + be_botlib_export.BotLibVarGet = Export_BotLibVarGet; + + be_botlib_export.PC_AddGlobalDefine = PC_AddGlobalDefine; + be_botlib_export.PC_LoadSourceHandle = PC_LoadSourceHandle; + be_botlib_export.PC_FreeSourceHandle = PC_FreeSourceHandle; + be_botlib_export.PC_ReadTokenHandle = PC_ReadTokenHandle; + be_botlib_export.PC_SourceFileAndLine = PC_SourceFileAndLine; + be_botlib_export.PC_LoadGlobalDefines = PC_LoadGlobalDefines; + be_botlib_export.PC_RemoveAllGlobalDefines = PC_RemoveAllGlobalDefines; + + be_botlib_export.BotLibStartFrame = Export_BotLibStartFrame; + be_botlib_export.BotLibLoadMap = Export_BotLibLoadMap; + be_botlib_export.BotLibUpdateEntity = Export_BotLibUpdateEntity; + be_botlib_export.Test = BotExportTest; + + return &be_botlib_export; +} diff --git a/code/botlib/be_interface.h b/code/botlib/be_interface.h new file mode 100644 index 0000000..c3cdf08 --- /dev/null +++ b/code/botlib/be_interface.h @@ -0,0 +1,57 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: be_interface.h + * + * desc: botlib interface + * + * $Archive: /source/code/botlib/be_interface.h $ + * + *****************************************************************************/ + +//#define DEBUG //debug code +#define RANDOMIZE //randomize bot behaviour + +//FIXME: get rid of this global structure +typedef struct botlib_globals_s +{ + int botlibsetup; //true when the bot library has been setup + int maxentities; //maximum number of entities + int maxclients; //maximum number of clients + float time; //the global time +#ifdef DEBUG + qboolean debug; //true if debug is on + int goalareanum; + vec3_t goalorigin; + int runai; +#endif +} botlib_globals_t; + + +extern botlib_globals_t botlibglobals; +extern botlib_import_t botimport; +extern int botDeveloper; //true if developer is on + +// +int Sys_MilliSeconds(void); + diff --git a/code/botlib/botlib.h b/code/botlib/botlib.h new file mode 100644 index 0000000..fc1a1c9 --- /dev/null +++ b/code/botlib/botlib.h @@ -0,0 +1,519 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +/***************************************************************************** + * 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 0x00000001 +#define ACTION_USE 0x00000002 +#define ACTION_RESPAWN 0x00000008 +#define ACTION_JUMP 0x00000010 +#define ACTION_MOVEUP 0x00000020 +#define ACTION_CROUCH 0x00000080 +#define ACTION_MOVEDOWN 0x00000100 +#define ACTION_MOVEFORWARD 0x00000200 +#define ACTION_MOVEBACK 0x00000800 +#define ACTION_MOVELEFT 0x00001000 +#define ACTION_MOVERIGHT 0x00002000 +#define ACTION_DELAYEDJUMP 0x00008000 +#define ACTION_TALK 0x00010000 +#define ACTION_GESTURE 0x00020000 +#define ACTION_WALK 0x00080000 +#define ACTION_AFFIRMATIVE 0x00100000 +#define ACTION_NEGATIVE 0x00200000 +#define ACTION_GETFLAG 0x00800000 +#define ACTION_GUARDBASE 0x01000000 +#define ACTION_PATROL 0x02000000 +#define ACTION_FOLLOWME 0x08000000 +#define ACTION_JUMPEDLASTFRAME 0x10000000 + +//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, ...) __attribute__ ((format (printf, 2, 3))); + //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); + int (*PC_LoadGlobalDefines)(const char *filename); + void (*PC_RemoveAllGlobalDefines)(void); + + //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 (it's "botDeveloper" in C to prevent symbol clash). + +"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/code/botlib/l_crc.c b/code/botlib/l_crc.c new file mode 100644 index 0000000..1fbb152 --- /dev/null +++ b/code/botlib/l_crc.c @@ -0,0 +1,152 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_crc.c + * + * desc: CRC calculation + * + * $Archive: /MissionPack/CODE/botlib/l_crc.c $ + * + *****************************************************************************/ + +#include +#include +#include + +#include "../qcommon/q_shared.h" +#include "botlib.h" +#include "be_interface.h" //for botimport.Print +#include "l_crc.h" + + +// FIXME: byte swap? + +// this is a 16 bit, non-reflected CRC using the polynomial 0x1021 +// and the initial and final xor values shown below... in other words, the +// CCITT standard CRC used by XMODEM + +#define CRC_INIT_VALUE 0xffff +#define CRC_XOR_VALUE 0x0000 + +unsigned short crctable[257] = +{ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_Init(unsigned short *crcvalue) +{ + *crcvalue = CRC_INIT_VALUE; +} //end of the function CRC_Init +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_ProcessByte(unsigned short *crcvalue, byte data) +{ + *crcvalue = (*crcvalue << 8) ^ crctable[(*crcvalue >> 8) ^ data]; +} //end of the function CRC_ProcessByte +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_Value(unsigned short crcvalue) +{ + return crcvalue ^ CRC_XOR_VALUE; +} //end of the function CRC_Value +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +unsigned short CRC_ProcessString(unsigned char *data, int length) +{ + unsigned short crcvalue; + int i, ind; + + CRC_Init(&crcvalue); + + for (i = 0; i < length; i++) + { + ind = (crcvalue >> 8) ^ data[i]; + if (ind < 0 || ind > 256) ind = 0; + crcvalue = (crcvalue << 8) ^ crctable[ind]; + } //end for + return CRC_Value(crcvalue); +} //end of the function CRC_ProcessString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void CRC_ContinueProcessString(unsigned short *crc, char *data, int length) +{ + int i; + + for (i = 0; i < length; i++) + { + *crc = (*crc << 8) ^ crctable[(*crc >> 8) ^ data[i]]; + } //end for +} //end of the function CRC_ProcessString diff --git a/code/botlib/l_crc.h b/code/botlib/l_crc.h new file mode 100644 index 0000000..f9c7e37 --- /dev/null +++ b/code/botlib/l_crc.h @@ -0,0 +1,29 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +typedef unsigned short crc_t; + +void CRC_Init(unsigned short *crcvalue); +void CRC_ProcessByte(unsigned short *crcvalue, byte data); +unsigned short CRC_Value(unsigned short crcvalue); +unsigned short CRC_ProcessString(unsigned char *data, int length); +void CRC_ContinueProcessString(unsigned short *crc, char *data, int length); diff --git a/code/botlib/l_libvar.c b/code/botlib/l_libvar.c new file mode 100644 index 0000000..0270781 --- /dev/null +++ b/code/botlib/l_libvar.c @@ -0,0 +1,295 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_libvar.c + * + * desc: bot library variables + * + * $Archive: /MissionPack/code/botlib/l_libvar.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "l_memory.h" +#include "l_libvar.h" + +//list with library variables +libvar_t *libvarlist = NULL; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarStringValue(char *string) +{ + int dotfound = 0; + float value = 0; + + while(*string) + { + if (*string < '0' || *string > '9') + { + if (dotfound || *string != '.') + { + return 0; + } //end if + else + { + dotfound = 10; + string++; + } //end if + } //end if + if (dotfound) + { + value = value + (float) (*string - '0') / (float) dotfound; + dotfound *= 10; + } //end if + else + { + value = value * 10.0 + (float) (*string - '0'); + } //end else + string++; + } //end while + return value; +} //end of the function LibVarStringValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVarAlloc(char *var_name) +{ + libvar_t *v; + + v = (libvar_t *) GetMemory(sizeof(libvar_t)); + Com_Memset(v, 0, sizeof(libvar_t)); + v->name = (char *) GetMemory(strlen(var_name)+1); + strcpy(v->name, var_name); + //add the variable in the list + v->next = libvarlist; + libvarlist = v; + return v; +} //end of the function LibVarAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarDeAlloc(libvar_t *v) +{ + if (v->string) FreeMemory(v->string); + FreeMemory(v->name); + FreeMemory(v); +} //end of the function LibVarDeAlloc +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarDeAllocAll(void) +{ + libvar_t *v; + + for (v = libvarlist; v; v = libvarlist) + { + libvarlist = libvarlist->next; + LibVarDeAlloc(v); + } //end for + libvarlist = NULL; +} //end of the function LibVarDeAllocAll +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVarGet(char *var_name) +{ + libvar_t *v; + + for (v = libvarlist; v; v = v->next) + { + if (!Q_stricmp(v->name, var_name)) + { + return v; + } //end if + } //end for + return NULL; +} //end of the function LibVarGet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *LibVarGetString(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + return v->string; + } //end if + else + { + return ""; + } //end else +} //end of the function LibVarGetString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarGetValue(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + return v->value; + } //end if + else + { + return 0; + } //end else +} //end of the function LibVarGetValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +libvar_t *LibVar(char *var_name, char *value) +{ + libvar_t *v; + v = LibVarGet(var_name); + if (v) return v; + //create new variable + v = LibVarAlloc(var_name); + //variable string + v->string = (char *) GetMemory(strlen(value) + 1); + strcpy(v->string, value); + //the value + v->value = LibVarStringValue(v->string); + //variable is modified + v->modified = qtrue; + // + return v; +} //end of the function LibVar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *LibVarString(char *var_name, char *value) +{ + libvar_t *v; + + v = LibVar(var_name, value); + return v->string; +} //end of the function LibVarString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +float LibVarValue(char *var_name, char *value) +{ + libvar_t *v; + + v = LibVar(var_name, value); + return v->value; +} //end of the function LibVarValue +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarSet(char *var_name, char *value) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + FreeMemory(v->string); + } //end if + else + { + v = LibVarAlloc(var_name); + } //end else + //variable string + v->string = (char *) GetMemory(strlen(value) + 1); + strcpy(v->string, value); + //the value + v->value = LibVarStringValue(v->string); + //variable is modified + v->modified = qtrue; +} //end of the function LibVarSet +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean LibVarChanged(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + return v->modified; + } //end if + else + { + return qfalse; + } //end else +} //end of the function LibVarChanged +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LibVarSetNotModified(char *var_name) +{ + libvar_t *v; + + v = LibVarGet(var_name); + if (v) + { + v->modified = qfalse; + } //end if +} //end of the function LibVarSetNotModified diff --git a/code/botlib/l_libvar.h b/code/botlib/l_libvar.h new file mode 100644 index 0000000..d96685f --- /dev/null +++ b/code/botlib/l_libvar.h @@ -0,0 +1,63 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_libvar.h + * + * desc: botlib vars + * + * $Archive: /source/code/botlib/l_libvar.h $ + * + *****************************************************************************/ + +//library variable +typedef struct libvar_s +{ + char *name; + char *string; + int flags; + qboolean modified; // set each time the cvar is changed + float value; + struct libvar_s *next; +} libvar_t; + +//removes all library variables +void LibVarDeAllocAll(void); +//gets the library variable with the given name +libvar_t *LibVarGet(char *var_name); +//gets the string of the library variable with the given name +char *LibVarGetString(char *var_name); +//gets the value of the library variable with the given name +float LibVarGetValue(char *var_name); +//creates the library variable if not existing already and returns it +libvar_t *LibVar(char *var_name, char *value); +//creates the library variable if not existing already and returns the value +float LibVarValue(char *var_name, char *value); +//creates the library variable if not existing already and returns the value string +char *LibVarString(char *var_name, char *value); +//sets the library variable +void LibVarSet(char *var_name, char *value); +//returns true if the library variable has been modified +qboolean LibVarChanged(char *var_name); +//sets the library variable to unmodified +void LibVarSetNotModified(char *var_name); + diff --git a/code/botlib/l_log.c b/code/botlib/l_log.c new file mode 100644 index 0000000..ee25604 --- /dev/null +++ b/code/botlib/l_log.c @@ -0,0 +1,170 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_log.c + * + * desc: log file + * + * $Archive: /MissionPack/CODE/botlib/l_log.c $ + * + *****************************************************************************/ + +#include +#include +#include + +#include "../qcommon/q_shared.h" +#include "botlib.h" +#include "be_interface.h" //for botimport.Print +#include "l_libvar.h" +#include "l_log.h" + +#define MAX_LOGFILENAMESIZE 1024 + +typedef struct logfile_s +{ + char filename[MAX_LOGFILENAMESIZE]; + FILE *fp; + int numwrites; +} logfile_t; + +static logfile_t logfile; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Open(char *filename) +{ + if (!LibVarValue("log", "0")) return; + if (!filename || !strlen(filename)) + { + botimport.Print(PRT_MESSAGE, "openlog \n"); + return; + } //end if + if (logfile.fp) + { + botimport.Print(PRT_ERROR, "log file %s is already opened\n", logfile.filename); + return; + } //end if + logfile.fp = fopen(filename, "wb"); + if (!logfile.fp) + { + botimport.Print(PRT_ERROR, "can't open the log file %s\n", filename); + return; + } //end if + strncpy(logfile.filename, filename, MAX_LOGFILENAMESIZE); + botimport.Print(PRT_MESSAGE, "Opened log %s\n", logfile.filename); +} //end of the function Log_Create +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Close(void) +{ + if (!logfile.fp) return; + if (fclose(logfile.fp)) + { + botimport.Print(PRT_ERROR, "can't close log file %s\n", logfile.filename); + return; + } //end if + logfile.fp = NULL; + botimport.Print(PRT_MESSAGE, "Closed log %s\n", logfile.filename); +} //end of the function Log_Close +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Shutdown(void) +{ + if (logfile.fp) Log_Close(); +} //end of the function Log_Shutdown +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL Log_Write(char *fmt, ...) +{ + va_list ap; + + if (!logfile.fp) return; + va_start(ap, fmt); + vfprintf(logfile.fp, fmt, ap); + va_end(ap); + //fprintf(logfile.fp, "\r\n"); + fflush(logfile.fp); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL Log_WriteTimeStamped(char *fmt, ...) +{ + va_list ap; + + if (!logfile.fp) return; + fprintf(logfile.fp, "%d %02d:%02d:%02d:%02d ", + logfile.numwrites, + (int) (botlibglobals.time / 60 / 60), + (int) (botlibglobals.time / 60), + (int) (botlibglobals.time), + (int) ((int) (botlibglobals.time * 100)) - + ((int) botlibglobals.time) * 100); + va_start(ap, fmt); + vfprintf(logfile.fp, fmt, ap); + va_end(ap); + fprintf(logfile.fp, "\r\n"); + logfile.numwrites++; + fflush(logfile.fp); +} //end of the function Log_Write +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +FILE *Log_FilePointer(void) +{ + return logfile.fp; +} //end of the function Log_FilePointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void Log_Flush(void) +{ + if (logfile.fp) fflush(logfile.fp); +} //end of the function Log_Flush + diff --git a/code/botlib/l_log.h b/code/botlib/l_log.h new file mode 100644 index 0000000..8b3153d --- /dev/null +++ b/code/botlib/l_log.h @@ -0,0 +1,46 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_log.h + * + * desc: log file + * + * $Archive: /source/code/botlib/l_log.h $ + * + *****************************************************************************/ + +//open a log file +void Log_Open(char *filename); +//close the current log file +void Log_Close(void); +//close log file if present +void Log_Shutdown(void); +//write to the current opened log file +void QDECL Log_Write(char *fmt, ...) __attribute__ ((format (printf, 1, 2))); +//write to the current opened log file with a time stamp +void QDECL Log_WriteTimeStamped(char *fmt, ...) __attribute__ ((format (printf, 1, 2))); +//returns a pointer to the log file +FILE *Log_FilePointer(void); +//flush log file +void Log_Flush(void); + diff --git a/code/botlib/l_memory.c b/code/botlib/l_memory.c new file mode 100644 index 0000000..e67c43c --- /dev/null +++ b/code/botlib/l_memory.c @@ -0,0 +1,464 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_memory.c + * + * desc: memory allocation + * + * $Archive: /MissionPack/code/botlib/l_memory.c $ + * + *****************************************************************************/ + +#include "../qcommon/q_shared.h" +#include "botlib.h" +#include "l_log.h" +#include "l_memory.h" +#include "be_interface.h" + +//#define MEMDEBUG +//#define MEMORYMANEGER + +#define MEM_ID 0x12345678l +#define HUNK_ID 0x87654321l + +int allocatedmemory; +int totalmemorysize; +int numblocks; + +#ifdef MEMORYMANEGER + +typedef struct memoryblock_s +{ + unsigned long int id; + void *ptr; + int size; +#ifdef MEMDEBUG + char *label; + char *file; + int line; +#endif //MEMDEBUG + struct memoryblock_s *prev, *next; +} memoryblock_t; + +memoryblock_t *memory; + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void LinkMemoryBlock(memoryblock_t *block) +{ + block->prev = NULL; + block->next = memory; + if (memory) memory->prev = block; + memory = block; +} //end of the function LinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void UnlinkMemoryBlock(memoryblock_t *block) +{ + if (block->prev) block->prev->next = block->next; + else memory = block->next; + if (block->next) block->next->prev = block->prev; +} //end of the function UnlinkMemoryBlock +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + assert(botimport.GetMemory); + ptr = botimport.GetMemory(size + sizeof(memoryblock_t)); + block = (memoryblock_t *) ptr; + block->id = MEM_ID; + block->ptr = (char *) ptr + sizeof(memoryblock_t); + block->size = size + sizeof(memoryblock_t); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock(block); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof(memoryblock_t); + numblocks++; + return block->ptr; +} //end of the function GetMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug(size, label, file, line); +#else + ptr = GetMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + memoryblock_t *block; + + ptr = botimport.HunkAlloc(size + sizeof(memoryblock_t)); + block = (memoryblock_t *) ptr; + block->id = HUNK_ID; + block->ptr = (char *) ptr + sizeof(memoryblock_t); + block->size = size + sizeof(memoryblock_t); +#ifdef MEMDEBUG + block->label = label; + block->file = file; + block->line = line; +#endif //MEMDEBUG + LinkMemoryBlock(block); + allocatedmemory += block->size; + totalmemorysize += block->size + sizeof(memoryblock_t); + numblocks++; + return block->ptr; +} //end of the function GetHunkMemoryDebug +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug(size, label, file, line); +#else + ptr = GetHunkMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +memoryblock_t *BlockFromPointer(void *ptr, char *str) +{ + memoryblock_t *block; + + if (!ptr) + { +#ifdef MEMDEBUG + //char *crash = (char *) NULL; + //crash[0] = 1; + botimport.Print(PRT_FATAL, "%s: NULL pointer\n", str); +#endif // MEMDEBUG + return NULL; + } //end if + block = (memoryblock_t *) ((char *) ptr - sizeof(memoryblock_t)); + if (block->id != MEM_ID && block->id != HUNK_ID) + { + botimport.Print(PRT_FATAL, "%s: invalid memory block\n", str); + return NULL; + } //end if + if (block->ptr != ptr) + { + botimport.Print(PRT_FATAL, "%s: memory block pointer invalid\n", str); + return NULL; + } //end if + return block; +} //end of the function BlockFromPointer +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory(void *ptr) +{ + memoryblock_t *block; + + block = BlockFromPointer(ptr, "FreeMemory"); + if (!block) return; + UnlinkMemoryBlock(block); + allocatedmemory -= block->size; + totalmemorysize -= block->size + sizeof(memoryblock_t); + numblocks--; + // + if (block->id == MEM_ID) + { + botimport.FreeMemory(block); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AvailableMemory(void) +{ + return botimport.AvailableMemory(); +} //end of the function AvailableMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int MemoryByteSize(void *ptr) +{ + memoryblock_t *block; + + block = BlockFromPointer(ptr, "MemoryByteSize"); + if (!block) return 0; + return block->size; +} //end of the function MemoryByteSize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize(void) +{ + botimport.Print(PRT_MESSAGE, "total allocated memory: %d KB\n", allocatedmemory >> 10); + botimport.Print(PRT_MESSAGE, "total botlib memory: %d KB\n", totalmemorysize >> 10); + botimport.Print(PRT_MESSAGE, "total memory blocks: %d\n", numblocks); +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels(void) +{ + memoryblock_t *block; + int i; + + PrintUsedMemorySize(); + i = 0; + Log_Write("============= Botlib memory log ==============\r\n"); + Log_Write("\r\n"); + for (block = memory; block; block = block->next) + { +#ifdef MEMDEBUG + if (block->id == HUNK_ID) + { + Log_Write("%6d, hunk %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); + } //end if + else + { + Log_Write("%6d, %p, %8d: %24s line %6d: %s\r\n", i, block->ptr, block->size, block->file, block->line, block->label); + } //end else +#endif //MEMDEBUG + i++; + } //end for +} //end of the function PrintMemoryLabels +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void DumpMemory(void) +{ + memoryblock_t *block; + + for (block = memory; block; block = memory) + { + FreeMemory(block->ptr); + } //end for + totalmemorysize = 0; + allocatedmemory = 0; +} //end of the function DumpMemory + +#else + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = botimport.GetMemory(size + sizeof(unsigned long int)); + if (!ptr) return NULL; + memid = (unsigned long int *) ptr; + *memid = MEM_ID; + return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); +} //end of the function GetMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetMemoryDebug(size, label, file, line); +#else + ptr = GetMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; + unsigned long int *memid; + + ptr = botimport.HunkAlloc(size + sizeof(unsigned long int)); + if (!ptr) return NULL; + memid = (unsigned long int *) ptr; + *memid = HUNK_ID; + return (unsigned long int *) ((char *) ptr + sizeof(unsigned long int)); +} //end of the function GetHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +#ifdef MEMDEBUG +void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line) +#else +void *GetClearedHunkMemory(unsigned long size) +#endif //MEMDEBUG +{ + void *ptr; +#ifdef MEMDEBUG + ptr = GetHunkMemoryDebug(size, label, file, line); +#else + ptr = GetHunkMemory(size); +#endif //MEMDEBUG + Com_Memset(ptr, 0, size); + return ptr; +} //end of the function GetClearedHunkMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void FreeMemory(void *ptr) +{ + unsigned long int *memid; + + memid = (unsigned long int *) ((char *) ptr - sizeof(unsigned long int)); + + if (*memid == MEM_ID) + { + botimport.FreeMemory(memid); + } //end if +} //end of the function FreeMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int AvailableMemory(void) +{ + return botimport.AvailableMemory(); +} //end of the function AvailableMemory +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintUsedMemorySize(void) +{ +} //end of the function PrintUsedMemorySize +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PrintMemoryLabels(void) +{ +} //end of the function PrintMemoryLabels + +#endif diff --git a/code/botlib/l_memory.h b/code/botlib/l_memory.h new file mode 100644 index 0000000..17c89d5 --- /dev/null +++ b/code/botlib/l_memory.h @@ -0,0 +1,76 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_memory.h + * + * desc: memory management + * + * $Archive: /source/code/botlib/l_memory.h $ + * + *****************************************************************************/ + +//#define MEMDEBUG + +#ifdef MEMDEBUG +#define GetMemory(size) GetMemoryDebug(size, #size, __FILE__, __LINE__); +#define GetClearedMemory(size) GetClearedMemoryDebug(size, #size, __FILE__, __LINE__); +//allocate a memory block of the given size +void *GetMemoryDebug(unsigned long size, char *label, char *file, int line); +//allocate a memory block of the given size and clear it +void *GetClearedMemoryDebug(unsigned long size, char *label, char *file, int line); +// +#define GetHunkMemory(size) GetHunkMemoryDebug(size, #size, __FILE__, __LINE__); +#define GetClearedHunkMemory(size) GetClearedHunkMemoryDebug(size, #size, __FILE__, __LINE__); +//allocate a memory block of the given size +void *GetHunkMemoryDebug(unsigned long size, char *label, char *file, int line); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemoryDebug(unsigned long size, char *label, char *file, int line); +#else +//allocate a memory block of the given size +void *GetMemory(unsigned long size); +//allocate a memory block of the given size and clear it +void *GetClearedMemory(unsigned long size); +// +#ifdef BSPC +#define GetHunkMemory GetMemory +#define GetClearedHunkMemory GetClearedMemory +#else +//allocate a memory block of the given size +void *GetHunkMemory(unsigned long size); +//allocate a memory block of the given size and clear it +void *GetClearedHunkMemory(unsigned long size); +#endif +#endif + +//free the given memory block +void FreeMemory(void *ptr); +//returns the amount available memory +int AvailableMemory(void); +//prints the total used memory size +void PrintUsedMemorySize(void); +//print all memory blocks with label +void PrintMemoryLabels(void); +//returns the size of the memory block in bytes +int MemoryByteSize(void *ptr); +//free all allocated memory +void DumpMemory(void); diff --git a/code/botlib/l_precomp.c b/code/botlib/l_precomp.c new file mode 100644 index 0000000..a0b4cc3 --- /dev/null +++ b/code/botlib/l_precomp.c @@ -0,0 +1,3329 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// + +/***************************************************************************** + * name: l_precomp.c + * + * desc: pre compiler + * + * $Archive: /MissionPack/code/botlib/l_precomp.c $ + * + *****************************************************************************/ + +//Notes: fix: PC_StringizeTokens + +//#define SCREWUP +//#define BOTLIB +//#define QUAKE +//#define QUAKEC +//#define MEQCC + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" + +typedef enum {qfalse, qtrue} qboolean; +#endif //SCREWUP + +#ifdef BOTLIB +#include "../qcommon/q_shared.h" +#include "botlib.h" +#include "be_interface.h" +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" +#endif //BOTLIB + +#ifdef MEQCC +#include "qcc.h" +#include "time.h" //time & ctime +#include "math.h" //fabs +#include "l_memory.h" +#include "l_script.h" +#include "l_precomp.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" +#include "l_precomp.h" + +#define qtrue true +#define qfalse false +#define Q_stricmp stricmp + +#endif //BSPC + +#if defined(QUAKE) && !defined(BSPC) +#include "l_utils.h" +#endif //QUAKE + +//#define DEBUG_EVAL + +#define MAX_DEFINEPARMS 128 + +#define DEFINEHASHING 1 + +//directive name with parse function +typedef struct directive_s +{ + char *name; + int (*func)(source_t *source); +} directive_t; + +#define DEFINEHASHSIZE 1024 + +#define TOKEN_HEAP_SIZE 4096 + +int numtokens; +/* +int tokenheapinitialized; //true when the token heap is initialized +token_t token_heap[TOKEN_HEAP_SIZE]; //heap with tokens +token_t *freetokens; //free tokens from the heap +*/ + +//list with global defines added to every source loaded +#if DEFINEHASHING +define_t **globaldefines = NULL; +#else +define_t *globaldefines; +#endif + +qboolean addGlobalDefine = qfalse; + +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void QDECL SourceError(source_t *source, char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + Q_vsnprintf(text, sizeof(text), str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("error: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BSPC +} //end of the function SourceError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL SourceWarning(source_t *source, char *str, ...) +{ + char text[1024]; + va_list ap; + + va_start(ap, str); + Q_vsnprintf(text, sizeof(text), str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("warning: file %s, line %d: %s\n", source->scriptstack->filename, source->scriptstack->line, text); +#endif //BSPC +} //end of the function ScriptWarning +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushIndent(source_t *source, int type, int skip) +{ + indent_t *indent; + + indent = (indent_t *) GetMemory(sizeof(indent_t)); + indent->type = type; + indent->script = source->scriptstack; + indent->skip = (skip != 0); + source->skip += indent->skip; + indent->next = source->indentstack; + source->indentstack = indent; +} //end of the function PC_PushIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PopIndent(source_t *source, int *type, int *skip) +{ + indent_t *indent; + + *type = 0; + *skip = 0; + + indent = source->indentstack; + if (!indent) return; + + //must be an indent from the current script + if (source->indentstack->script != source->scriptstack) return; + + *type = indent->type; + *skip = indent->skip; + source->indentstack = source->indentstack->next; + source->skip -= indent->skip; + FreeMemory(indent); +} //end of the function PC_PopIndent +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PushScript(source_t *source, script_t *script) +{ + script_t *s; + + for (s = source->scriptstack; s; s = s->next) + { + if (!Q_stricmp(s->filename, script->filename)) + { + SourceError(source, "%s recursively included", script->filename); + return; + } //end if + } //end for + //push the script on the script stack + script->next = source->scriptstack; + source->scriptstack = script; +} //end of the function PC_PushScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_InitTokenHeap(void) +{ + /* + int i; + + if (tokenheapinitialized) return; + freetokens = NULL; + for (i = 0; i < TOKEN_HEAP_SIZE; i++) + { + token_heap[i].next = freetokens; + freetokens = &token_heap[i]; + } //end for + tokenheapinitialized = qtrue; + */ +} //end of the function PC_InitTokenHeap +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +token_t *PC_CopyToken(token_t *token) +{ + token_t *t; + +// t = (token_t *) malloc(sizeof(token_t)); + t = (token_t *) GetMemory(sizeof(token_t)); +// t = freetokens; + if (!t) + { +#ifdef BSPC + Error("out of token space\n"); +#else + Com_Error(ERR_FATAL, "out of token space"); +#endif + return NULL; + } //end if +// freetokens = freetokens->next; + Com_Memcpy(t, token, sizeof(token_t)); + t->next = NULL; + numtokens++; + return t; +} //end of the function PC_CopyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeToken(token_t *token) +{ + //free(token); + FreeMemory(token); +// token->next = freetokens; +// freetokens = token; + numtokens--; +} //end of the function PC_FreeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + script_t *script; + int type, skip; + + //if there's no token already available + while(!source->tokens) + { + //if there's a token to read from the script + if (PS_ReadToken(source->scriptstack, token)) return qtrue; + //if at the end of the script + if (EndOfScript(source->scriptstack)) + { + //remove all indents of the script + while(source->indentstack && + source->indentstack->script == source->scriptstack) + { + SourceWarning(source, "missing #endif"); + PC_PopIndent(source, &type, &skip); + } //end if + } //end if + //if this was the initial script + if (!source->scriptstack->next) return qfalse; + //remove the script and return to the last one + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript(script); + } //end while + //copy the already available token + Com_Memcpy(token, source->tokens, sizeof(token_t)); + //free the read token + t = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken(t); + return qtrue; +} //end of the function PC_ReadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_UnreadSourceToken(source_t *source, token_t *token) +{ + token_t *t; + + t = PC_CopyToken(token); + t->next = source->tokens; + source->tokens = t; + return qtrue; +} //end of the function PC_UnreadSourceToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadDefineParms(source_t *source, define_t *define, token_t **parms, int maxparms) +{ + token_t token, *t, *last; + int i, done, lastcomma, numparms, indent; + + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "define %s missing parms", define->name); + return qfalse; + } //end if + // + if (define->numparms > maxparms) + { + SourceError(source, "define with more than %d parameters", maxparms); + return qfalse; + } //end if + // + for (i = 0; i < define->numparms; i++) parms[i] = NULL; + //if no leading "(" + if (strcmp(token.string, "(")) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "define %s missing parms", define->name); + return qfalse; + } //end if + //read the define parameters + for (done = 0, numparms = 0, indent = 0; !done;) + { + if (numparms >= maxparms) + { + SourceError(source, "define %s with too many parms", define->name); + return qfalse; + } //end if + if (numparms >= define->numparms) + { + SourceWarning(source, "define %s has too many parms", define->name); + return qfalse; + } //end if + parms[numparms] = NULL; + lastcomma = 1; + last = NULL; + while(!done) + { + // + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "define %s incomplete", define->name); + return qfalse; + } //end if + // + if (!strcmp(token.string, ",")) + { + if (indent <= 0) + { + if (lastcomma) SourceWarning(source, "too many comma's"); + lastcomma = 1; + break; + } //end if + } //end if + lastcomma = 0; + // + if (!strcmp(token.string, "(")) + { + indent++; + continue; + } //end if + else if (!strcmp(token.string, ")")) + { + if (--indent <= 0) + { + if (!parms[define->numparms-1]) + { + SourceWarning(source, "too few define parms"); + } //end if + done = 1; + break; + } //end if + } //end if + // + if (numparms < define->numparms) + { + // + t = PC_CopyToken(&token); + t->next = NULL; + if (last) last->next = t; + else parms[numparms] = t; + last = t; + } //end if + } //end while + numparms++; + } //end for + return qtrue; +} //end of the function PC_ReadDefineParms +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_StringizeTokens(token_t *tokens, token_t *token) +{ + token_t *t; + + token->type = TT_STRING; + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->string[0] = '\0'; + strcat(token->string, "\""); + for (t = tokens; t; t = t->next) + { + strncat(token->string, t->string, MAX_TOKEN - strlen(token->string) - 1); + } //end for + strncat(token->string, "\"", MAX_TOKEN - strlen(token->string) - 1); + return qtrue; +} //end of the function PC_StringizeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_MergeTokens(token_t *t1, token_t *t2) +{ + //merging of a name with a name or number + if (t1->type == TT_NAME && (t2->type == TT_NAME || t2->type == TT_NUMBER)) + { + strcat(t1->string, t2->string); + return qtrue; + } //end if + //merging of two strings + if (t1->type == TT_STRING && t2->type == TT_STRING) + { + //remove trailing double quote + t1->string[strlen(t1->string)-1] = '\0'; + //concat without leading double quote + strcat(t1->string, &t2->string[1]); + return qtrue; + } //end if + //FIXME: merging of two number of the same sub type + return qfalse; +} //end of the function PC_MergeTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +/* +void PC_PrintDefine(define_t *define) +{ + printf("define->name = %s\n", define->name); + printf("define->flags = %d\n", define->flags); + printf("define->builtin = %d\n", define->builtin); + printf("define->numparms = %d\n", define->numparms); +// token_t *parms; //define parameters +// token_t *tokens; //macro tokens (possibly containing parm tokens) +// struct define_s *next; //next defined macro in a list +} //end of the function PC_PrintDefine*/ +#if DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_PrintDefineHashTable(define_t **definehash) +{ + int i; + define_t *d; + + for (i = 0; i < DEFINEHASHSIZE; i++) + { + Log_Write("%4d:", i); + for (d = definehash[i]; d; d = d->hashnext) + { + Log_Write(" %s", d->name); + } //end for + Log_Write("\n"); + } //end for +} //end of the function PC_PrintDefineHashTable +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +//char primes[16] = {1, 3, 5, 7, 11, 13, 17, 19, 23, 27, 29, 31, 37, 41, 43, 47}; + +int PC_NameHash(char *name) +{ + int register hash, i; + + hash = 0; + for (i = 0; name[i] != '\0'; i++) + { + hash += name[i] * (119 + i); + //hash += (name[i] << 7) + i; + //hash += (name[i] << (i&15)); + } //end while + hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE-1); + return hash; +} //end of the function PC_NameHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddDefineToHash(define_t *define, define_t **definehash) +{ + int hash; + + if( addGlobalDefine ) { + definehash = globaldefines; + define->flags |= DEFINE_GLOBAL; + } + + hash = PC_NameHash(define->name); + define->hashnext = definehash[hash]; + definehash[hash] = define; + + if( addGlobalDefine ) { + define->globalnext = define->hashnext; + } +} //end of the function PC_AddDefineToHash +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindHashedDefine(define_t **definehash, char *name) +{ + define_t *d; + int hash; + + hash = PC_NameHash(name); + for (d = definehash[hash]; d; d = d->hashnext) + { + if (!strcmp(d->name, name)) return d; + } //end for + return NULL; +} //end of the function PC_FindHashedDefine +#endif //DEFINEHASHING +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_FindDefine(define_t *defines, char *name) +{ + define_t *d; + + for (d = defines; d; d = d->next) + { + if (!strcmp(d->name, name)) return d; + } //end for + return NULL; +} //end of the function PC_FindDefine +//============================================================================ +// +// Parameter: - +// Returns: number of the parm +// if no parm found with the given name -1 is returned +// Changes Globals: - +//============================================================================ +int PC_FindDefineParm(define_t *define, char *name) +{ + token_t *p; + int i; + + i = 0; + for (p = define->parms; p; p = p->next) + { + if (!strcmp(p->string, name)) return i; + i++; + } //end for + return -1; +} //end of the function PC_FindDefineParm +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_FreeDefine(define_t *define) +{ + token_t *t, *next; + + //free the define parameters + for (t = define->parms; t; t = next) + { + next = t->next; + PC_FreeToken(t); + } //end for + //free the define tokens + for (t = define->tokens; t; t = next) + { + next = t->next; + PC_FreeToken(t); + } //end for + //free the define + FreeMemory(define->name); + FreeMemory(define); +} //end of the function PC_FreeDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddBuiltinDefines(source_t *source) +{ + int i; + define_t *define; + struct builtin + { + char *string; + int builtin; + } builtin[] = { + { "__LINE__", BUILTIN_LINE }, + { "__FILE__", BUILTIN_FILE }, + { "__DATE__", BUILTIN_DATE }, + { "__TIME__", BUILTIN_TIME }, +// { "__STDC__", BUILTIN_STDC }, + { NULL, 0 } + }; + + for (i = 0; builtin[i].string; i++) + { + define = (define_t *) GetMemory(sizeof(define_t)); + Com_Memset(define, 0, sizeof(define_t)); + define->name = (char *) GetMemory(strlen(builtin[i].string) + 1); + strcpy(define->name, builtin[i].string); + define->flags |= DEFINE_FIXED; + define->builtin = builtin[i].builtin; + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash(define, source->definehash); +#else + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + } //end for +} //end of the function PC_AddBuiltinDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandBuiltinDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *token; + time_t t; + + char *curtime; + + token = PC_CopyToken(deftoken); + switch(define->builtin) + { + case BUILTIN_LINE: + { + sprintf(token->string, "%d", deftoken->line); +#ifdef NUMBERVALUE + token->intvalue = deftoken->line; + token->floatvalue = deftoken->line; +#endif //NUMBERVALUE + token->type = TT_NUMBER; + token->subtype = TT_DECIMAL | TT_INTEGER; + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_FILE: + { + strcpy(token->string, source->scriptstack->filename); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_DATE: + { + t = time(NULL); + curtime = ctime(&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+4, 7); + strncat(token->string+7, curtime+20, 4); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_TIME: + { + t = time(NULL); + curtime = ctime(&t); + strcpy(token->string, "\""); + strncat(token->string, curtime+11, 8); + strcat(token->string, "\""); + free(curtime); + token->type = TT_NAME; + token->subtype = strlen(token->string); + *firsttoken = token; + *lasttoken = token; + break; + } //end case + case BUILTIN_STDC: + default: + { + *firsttoken = NULL; + *lasttoken = NULL; + break; + } //end case + } //end switch + return qtrue; +} //end of the function PC_ExpandBuiltinDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefine(source_t *source, token_t *deftoken, define_t *define, + token_t **firsttoken, token_t **lasttoken) +{ + token_t *parms[MAX_DEFINEPARMS], *dt, *pt, *t; + token_t *t1, *t2, *first, *last, *nextpt, token; + int parmnum, i; + + //if it is a builtin define + if (define->builtin) + { + return PC_ExpandBuiltinDefine(source, deftoken, define, firsttoken, lasttoken); + } //end if + //if the define has parameters + if (define->numparms) + { + if (!PC_ReadDefineParms(source, define, parms, MAX_DEFINEPARMS)) return qfalse; +#ifdef DEBUG_EVAL + for (i = 0; i < define->numparms; i++) + { + Log_Write("define parms %d:", i); + for (pt = parms[i]; pt; pt = pt->next) + { + Log_Write("%s", pt->string); + } //end for + } //end for +#endif //DEBUG_EVAL + } //end if + //empty list at first + first = NULL; + last = NULL; + //create a list with tokens of the expanded define + for (dt = define->tokens; dt; dt = dt->next) + { + parmnum = -1; + //if the token is a name, it could be a define parameter + if (dt->type == TT_NAME) + { + parmnum = PC_FindDefineParm(define, dt->string); + } //end if + //if it is a define parameter + if (parmnum >= 0) + { + for (pt = parms[parmnum]; pt; pt = pt->next) + { + t = PC_CopyToken(pt); + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } //end for + } //end if + else + { + //if stringizing operator + if (dt->string[0] == '#' && dt->string[1] == '\0') + { + //the stringizing operator must be followed by a define parameter + if (dt->next) parmnum = PC_FindDefineParm(define, dt->next->string); + else parmnum = -1; + // + if (parmnum >= 0) + { + //step over the stringizing operator + dt = dt->next; + //stringize the define parameter tokens + if (!PC_StringizeTokens(parms[parmnum], &token)) + { + SourceError(source, "can't stringize tokens"); + return qfalse; + } //end if + t = PC_CopyToken(&token); + } //end if + else + { + SourceWarning(source, "stringizing operator without define parameter"); + continue; + } //end if + } //end if + else + { + t = PC_CopyToken(dt); + } //end else + //add the token to the list + t->next = NULL; + if (last) last->next = t; + else first = t; + last = t; + } //end else + } //end for + //check for the merging operator + for (t = first; t; ) + { + if (t->next) + { + //if the merging operator + if (t->next->string[0] == '#' && t->next->string[1] == '#') + { + t1 = t; + t2 = t->next->next; + if (t2) + { + if (!PC_MergeTokens(t1, t2)) + { + SourceError(source, "can't merge %s with %s", t1->string, t2->string); + return qfalse; + } //end if + PC_FreeToken(t1->next); + t1->next = t2->next; + if (t2 == last) last = t1; + PC_FreeToken(t2); + continue; + } //end if + } //end if + } //end if + t = t->next; + } //end for + //store the first and last token of the list + *firsttoken = first; + *lasttoken = last; + //free all the parameter tokens + for (i = 0; i < define->numparms; i++) + { + for (pt = parms[i]; pt; pt = nextpt) + { + nextpt = pt->next; + PC_FreeToken(pt); + } //end for + } //end for + // + return qtrue; +} //end of the function PC_ExpandDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpandDefineIntoSource(source_t *source, token_t *deftoken, define_t *define) +{ + token_t *firsttoken, *lasttoken; + + if (!PC_ExpandDefine(source, deftoken, define, &firsttoken, &lasttoken)) return qfalse; + + if (firsttoken && lasttoken) + { + lasttoken->next = source->tokens; + source->tokens = firsttoken; + return qtrue; + } //end if + return qfalse; +} //end of the function PC_ExpandDefineIntoSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ConvertPath(char *path) +{ + char *ptr; + + //remove double path seperators + for (ptr = path; *ptr;) + { + if ((*ptr == '\\' || *ptr == '/') && + (*(ptr+1) == '\\' || *(ptr+1) == '/')) + { + memmove(ptr, ptr+1, strlen(ptr)); + } //end if + else + { + ptr++; + } //end else + } //end while + //set OS dependent path seperators + for (ptr = path; *ptr;) + { + if (*ptr == '/' || *ptr == '\\') *ptr = PATHSEPERATOR_CHAR; + ptr++; + } //end while +} //end of the function PC_ConvertPath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_include(source_t *source) +{ + script_t *script; + token_t token; + char path[MAX_PATH]; +#ifdef QUAKE + foundfile_t file; +#endif //QUAKE + + if (source->skip > 0) return qtrue; + // + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "#include without file name"); + return qfalse; + } //end if + if (token.linescrossed > 0) + { + SourceError(source, "#include without file name"); + return qfalse; + } //end if + if (token.type == TT_STRING) + { + StripDoubleQuotes(token.string); + PC_ConvertPath(token.string); + script = LoadScriptFile(token.string); + if (!script) + { + strcpy(path, source->includepath); + strcat(path, token.string); + script = LoadScriptFile(path); + } //end if + } //end if + else if (token.type == TT_PUNCTUATION && *token.string == '<') + { + strcpy(path, source->includepath); + while(PC_ReadSourceToken(source, &token)) + { + if (token.linescrossed > 0) + { + PC_UnreadSourceToken(source, &token); + break; + } //end if + if (token.type == TT_PUNCTUATION && *token.string == '>') break; + strncat(path, token.string, MAX_PATH - 1); + } //end while + if (*token.string != '>') + { + SourceWarning(source, "#include missing trailing >"); + } //end if + if (!strlen(path)) + { + SourceError(source, "#include without file name between < >"); + return qfalse; + } //end if + PC_ConvertPath(path); + script = LoadScriptFile(path); + } //end if + else + { + SourceError(source, "#include without file name"); + return qfalse; + } //end else +#ifdef QUAKE + if (!script) + { + Com_Memset(&file, 0, sizeof(foundfile_t)); + script = LoadScriptFile(path); + if (script) strncpy(script->filename, path, MAX_PATH); + } //end if +#endif //QUAKE + if (!script) + { +#ifdef SCREWUP + SourceWarning(source, "file %s not found", path); + return qtrue; +#else + SourceError(source, "file %s not found", path); + return qfalse; +#endif //SCREWUP + } //end if + PC_PushScript(source, script); + return qtrue; +} //end of the function PC_Directive_include +//============================================================================ +// reads a token from the current line, continues reading on the next +// line only if a backslash '\' is encountered. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadLine(source_t *source, token_t *token) +{ + int crossline; + + crossline = 0; + do + { + if (!PC_ReadSourceToken(source, token)) return qfalse; + + if (token->linescrossed > crossline) + { + PC_UnreadSourceToken(source, token); + return qfalse; + } //end if + crossline = 1; + } while(!strcmp(token->string, "\\")); + return qtrue; +} //end of the function PC_ReadLine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_WhiteSpaceBeforeToken(token_t *token) +{ + return token->endwhitespace_p - token->whitespace_p > 0; +} //end of the function PC_WhiteSpaceBeforeToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_ClearTokenWhiteSpace(token_t *token) +{ + token->whitespace_p = NULL; + token->endwhitespace_p = NULL; + token->linescrossed = 0; +} //end of the function PC_ClearTokenWhiteSpace +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_undef(source_t *source) +{ + token_t token; + define_t *define, *lastdefine; + int hash; + + if (source->skip > 0) return qtrue; + // + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "undef without name"); + return qfalse; + } //end if + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "expected name, found %s", token.string); + return qfalse; + } //end if +#if DEFINEHASHING + + hash = PC_NameHash(token.string); + for (lastdefine = NULL, define = source->definehash[hash]; define; define = define->hashnext) + { + if (!strcmp(define->name, token.string)) + { + if (define->flags & DEFINE_FIXED) + { + SourceWarning(source, "can't undef %s", token.string); + } //end if + else + { + if (lastdefine) lastdefine->hashnext = define->hashnext; + else source->definehash[hash] = define->hashnext; + + if( !( define->flags & DEFINE_GLOBAL ) ) + PC_FreeDefine(define); + } //end else + break; + } //end if + lastdefine = define; + } //end for +#else //DEFINEHASHING + for (lastdefine = NULL, define = source->defines; define; define = define->next) + { + if (!strcmp(define->name, token.string)) + { + if (define->flags & DEFINE_FIXED) + { + SourceWarning(source, "can't undef %s", token.string); + } //end if + else + { + if (lastdefine) lastdefine->next = define->next; + else source->defines = define->next; + PC_FreeDefine(define); + } //end else + break; + } //end if + lastdefine = define; + } //end for +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_Directive_undef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_define(source_t *source) +{ + token_t token, *t, *last; + define_t *define; + + if (source->skip > 0) return qtrue; + // + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "#define without name"); + return qfalse; + } //end if + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "expected name after #define, found %s", token.string); + return qfalse; + } //end if + //check if the define already exists +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + if (define) + { + if (define->flags & DEFINE_FIXED) + { + SourceError(source, "can't redefine %s", token.string); + return qfalse; + } //end if + SourceWarning(source, "redefinition of %s", token.string); + //unread the define name before executing the #undef directive + PC_UnreadSourceToken(source, &token); + if (!PC_Directive_undef(source)) return qfalse; + //if the define was not removed (define->flags & DEFINE_FIXED) +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + } //end if + //allocate define + define = (define_t *) GetMemory(sizeof(define_t)); + Com_Memset(define, 0, sizeof(define_t)); + define->name = (char *) GetMemory(strlen(token.string) + 1); + strcpy(define->name, token.string); + //add the define to the source +#if DEFINEHASHING + PC_AddDefineToHash(define, source->definehash); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + //if nothing is defined, just return + if (!PC_ReadLine(source, &token)) return qtrue; + //if it is a define with parameters + if (!PC_WhiteSpaceBeforeToken(&token) && !strcmp(token.string, "(")) + { + //read the define parameters + last = NULL; + if (!PC_CheckTokenString(source, ")")) + { + while(1) + { + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "expected define parameter"); + return qfalse; + } //end if + //if it isn't a name + if (token.type != TT_NAME) + { + SourceError(source, "invalid define parameter"); + return qfalse; + } //end if + // + if (PC_FindDefineParm(define, token.string) >= 0) + { + SourceError(source, "two the same define parameters"); + return qfalse; + } //end if + //add the define parm + t = PC_CopyToken(&token); + PC_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->parms = t; + last = t; + define->numparms++; + //read next token + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "define parameters not terminated"); + return qfalse; + } //end if + // + if (!strcmp(token.string, ")")) break; + //then it must be a comma + if (strcmp(token.string, ",")) + { + SourceError(source, "define not terminated"); + return qfalse; + } //end if + } //end while + } //end if + if (!PC_ReadLine(source, &token)) return qtrue; + } //end if + //read the defined stuff + last = NULL; + do + { + t = PC_CopyToken(&token); + if (t->type == TT_NAME && !strcmp(t->string, define->name)) + { + SourceError(source, "recursive define (removed recursion)"); + continue; + } //end if + PC_ClearTokenWhiteSpace(t); + t->next = NULL; + if (last) last->next = t; + else define->tokens = t; + last = t; + } while(PC_ReadLine(source, &token)); + // + if (last) + { + //check for merge operators at the beginning or end + if (!strcmp(define->tokens->string, "##") || + !strcmp(last->string, "##")) + { + SourceError(source, "define with misplaced ##"); + return qfalse; + } //end if + } //end if + return qtrue; +} //end of the function PC_Directive_define +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_DefineFromString(char *string) +{ + script_t *script; + source_t src; + token_t *t; + int res, i; + define_t *def; + + PC_InitTokenHeap(); + + script = LoadScriptMemory(string, strlen(string), "*extern"); + //create a new source + Com_Memset(&src, 0, sizeof(source_t)); + strncpy(src.filename, "*extern", MAX_PATH); + src.scriptstack = script; +#if DEFINEHASHING + src.definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); +#endif //DEFINEHASHING + //create a define from the source + res = PC_Directive_define(&src); + //free any tokens if left + for (t = src.tokens; t; t = src.tokens) + { + src.tokens = src.tokens->next; + PC_FreeToken(t); + } //end for +#ifdef DEFINEHASHING + def = NULL; + for (i = 0; i < DEFINEHASHSIZE; i++) + { + if (src.definehash[i]) + { + def = src.definehash[i]; + break; + } //end if + } //end for +#else + def = src.defines; +#endif //DEFINEHASHING + // +#if DEFINEHASHING + FreeMemory(src.definehash); +#endif //DEFINEHASHING + // + FreeScript(script); + //if the define was created successfully + if (res > 0) return def; + //free the define is created + if (src.defines) PC_FreeDefine(def); + // + return NULL; +} //end of the function PC_DefineFromString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddDefine(source_t *source, char *string) +{ + define_t *define; + + if( addGlobalDefine ) { + return PC_AddGlobalDefine( string ); + } + + define = PC_DefineFromString(string); + if (!define) return qfalse; +#if DEFINEHASHING + PC_AddDefineToHash(define, source->definehash); +#else //DEFINEHASHING + define->next = source->defines; + source->defines = define; +#endif //DEFINEHASHING + return qtrue; +} //end of the function PC_AddDefine +//============================================================================ +// add a globals define that will be added to all opened sources +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_AddGlobalDefine(char *string) +{ +#if !DEFINEHASHING + define_t *define; + + define = PC_DefineFromString(string); + if (!define) return qfalse; + define->next = globaldefines; + globaldefines = define; +#endif + return qtrue; +} //end of the function PC_AddGlobalDefine +//============================================================================ +// remove the given global define +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_RemoveGlobalDefine(char *name) +{ +#if !DEFINEHASHING + define_t *define; + + define = PC_FindDefine(globaldefines, name); + if (define) + { + PC_FreeDefine(define); + return qtrue; + } //end if +#endif + return qfalse; +} //end of the function PC_RemoveGlobalDefine +//============================================================================ +// remove all globals defines +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_RemoveAllGlobalDefines(void) +{ + define_t *define; + +#if DEFINEHASHING + int i; + if( globaldefines ) { + for( i = 0; i < DEFINEHASHSIZE; i++ ) { + while( globaldefines[i] ) { + define = globaldefines[i]; + globaldefines[i] = globaldefines[i]->globalnext; + PC_FreeDefine( define ); + } //end while + } //end for + } //end if +#else //DEFINEHASHING + for (define = globaldefines; define; define = globaldefines) + { + globaldefines = globaldefines->next; + PC_FreeDefine(define); + } //end for +#endif +} //end of the function PC_RemoveAllGlobalDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +define_t *PC_CopyDefine(source_t *source, define_t *define) +{ + define_t *newdefine; + token_t *token, *newtoken, *lasttoken; + + newdefine = (define_t *) GetMemory(sizeof(define_t)); + //copy the define name + newdefine->name = (char *) GetMemory(strlen(define->name) + 1); + strcpy(newdefine->name, define->name); + newdefine->flags = define->flags; + newdefine->builtin = define->builtin; + newdefine->numparms = define->numparms; + //the define is not linked + newdefine->next = NULL; + newdefine->hashnext = NULL; + //copy the define tokens + newdefine->tokens = NULL; + for (lasttoken = NULL, token = define->tokens; token; token = token->next) + { + newtoken = PC_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->tokens = newtoken; + lasttoken = newtoken; + } //end for + //copy the define parameters + newdefine->parms = NULL; + for (lasttoken = NULL, token = define->parms; token; token = token->next) + { + newtoken = PC_CopyToken(token); + newtoken->next = NULL; + if (lasttoken) lasttoken->next = newtoken; + else newdefine->parms = newtoken; + lasttoken = newtoken; + } //end for + return newdefine; +} //end of the function PC_CopyDefine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_AddGlobalDefinesToSource(source_t *source) +{ + define_t *define; +#if DEFINEHASHING + int i; + for( i = 0; i < DEFINEHASHSIZE; i++ ) { + define = globaldefines[i]; + while( define ) { + define->hashnext = NULL; + PC_AddDefineToHash( define, source->definehash ); + + define = define->globalnext; + } // end while + } // end for +#else //DEFINEHASHING + define_t *newdefine; + for( define = globaldefines; define; define = define->next ) { + newdefine = PC_CopyDefine( source, define ); + + newdefine->next = source->defines; + source->defines = newdefine; + } // end for +#endif +} //end of the function PC_AddGlobalDefinesToSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if_def(source_t *source, int type) +{ + token_t token; + define_t *d; + int skip; + + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "#ifdef without name"); + return qfalse; + } //end if + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "expected name after #ifdef, found %s", token.string); + return qfalse; + } //end if +#if DEFINEHASHING + d = PC_FindHashedDefine(source->definehash, token.string); +#else + d = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + skip = (type == INDENT_IFDEF) == (d == NULL); + PC_PushIndent(source, type, skip); + return qtrue; +} //end of the function PC_Directiveif_def +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifdef(source_t *source) +{ + return PC_Directive_if_def(source, INDENT_IFDEF); +} //end of the function PC_Directive_ifdef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_ifndef(source_t *source) +{ + return PC_Directive_if_def(source, INDENT_IFNDEF); +} //end of the function PC_Directive_ifndef +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_else(source_t *source) +{ + int type, skip; + + PC_PopIndent(source, &type, &skip); + if (!type) + { + SourceError(source, "misplaced #else"); + return qfalse; + } //end if + if (type == INDENT_ELSE) + { + SourceError(source, "#else after #else"); + return qfalse; + } //end if + PC_PushIndent(source, INDENT_ELSE, !skip); + return qtrue; +} //end of the function PC_Directive_else +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_endif(source_t *source) +{ + int type, skip; + + PC_PopIndent(source, &type, &skip); + if (!type) + { + SourceError(source, "misplaced #endif"); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_Directive_endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +typedef struct operator_s +{ + int operator; + int priority; + int parentheses; + struct operator_s *prev, *next; +} operator_t; + +typedef struct value_s +{ + signed long int intvalue; + float floatvalue; + int parentheses; + struct value_s *prev, *next; +} value_t; + +int PC_OperatorPriority(int op) +{ + switch(op) + { + case P_MUL: return 15; + case P_DIV: return 15; + case P_MOD: return 15; + case P_ADD: return 14; + case P_SUB: return 14; + + case P_LOGIC_AND: return 7; + case P_LOGIC_OR: return 6; + case P_LOGIC_GEQ: return 12; + case P_LOGIC_LEQ: return 12; + case P_LOGIC_EQ: return 11; + case P_LOGIC_UNEQ: return 11; + + case P_LOGIC_NOT: return 16; + case P_LOGIC_GREATER: return 12; + case P_LOGIC_LESS: return 12; + + case P_RSHIFT: return 13; + case P_LSHIFT: return 13; + + case P_BIN_AND: return 10; + case P_BIN_OR: return 8; + case P_BIN_XOR: return 9; + case P_BIN_NOT: return 16; + + case P_COLON: return 5; + case P_QUESTIONMARK: return 5; + } //end switch + return qfalse; +} //end of the function PC_OperatorPriority + +//#define AllocValue() GetClearedMemory(sizeof(value_t)); +//#define FreeValue(val) FreeMemory(val) +//#define AllocOperator(op) op = (operator_t *) GetClearedMemory(sizeof(operator_t)); +//#define FreeOperator(op) FreeMemory(op); + +#define MAX_VALUES 64 +#define MAX_OPERATORS 64 +#define AllocValue(val) \ + if (numvalues >= MAX_VALUES) { \ + SourceError(source, "out of value space\n"); \ + error = 1; \ + break; \ + } \ + else \ + val = &value_heap[numvalues++]; +#define FreeValue(val) +// +#define AllocOperator(op) \ + if (numoperators >= MAX_OPERATORS) { \ + SourceError(source, "out of operator space\n"); \ + error = 1; \ + break; \ + } \ + else \ + op = &operator_heap[numoperators++]; +#define FreeOperator(op) + +int PC_EvaluateTokens(source_t *source, token_t *tokens, signed long int *intvalue, + float *floatvalue, int integer) +{ + operator_t *o, *firstoperator, *lastoperator; + value_t *v, *firstvalue, *lastvalue, *v1, *v2; + token_t *t; + int brace = 0; + int parentheses = 0; + int error = 0; + int lastwasvalue = 0; + int negativevalue = 0; + int questmarkintvalue = 0; + float questmarkfloatvalue = 0; + int gotquestmarkvalue = qfalse; + // + operator_t operator_heap[MAX_OPERATORS]; + int numoperators = 0; + value_t value_heap[MAX_VALUES]; + int numvalues = 0; + + firstoperator = lastoperator = NULL; + firstvalue = lastvalue = NULL; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + for (t = tokens; t; t = t->next) + { + switch(t->type) + { + case TT_NAME: + { + if (lastwasvalue || negativevalue) + { + SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } //end if + if (strcmp(t->string, "defined")) + { + SourceError(source, "undefined name %s in #if/#elif", t->string); + error = 1; + break; + } //end if + t = t->next; + if (!strcmp(t->string, "(")) + { + brace = qtrue; + t = t->next; + } //end if + if (!t || t->type != TT_NAME) + { + SourceError(source, "defined without name in #if/#elif"); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); +#if DEFINEHASHING + if (PC_FindHashedDefine(source->definehash, t->string)) +#else + if (PC_FindDefine(source->defines, t->string)) +#endif //DEFINEHASHING + { + v->intvalue = 1; + v->floatvalue = 1; + } //end if + else + { + v->intvalue = 0; + v->floatvalue = 0; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + if (brace) + { + t = t->next; + if (!t || strcmp(t->string, ")")) + { + SourceError(source, "defined without ) in #if/#elif"); + error = 1; + break; + } //end if + } //end if + brace = qfalse; + // defined() creates a value + lastwasvalue = 1; + break; + } //end case + case TT_NUMBER: + { + if (lastwasvalue) + { + SourceError(source, "syntax error in #if/#elif"); + error = 1; + break; + } //end if + //v = (value_t *) GetClearedMemory(sizeof(value_t)); + AllocValue(v); + if (negativevalue) + { + v->intvalue = - (signed int) t->intvalue; + v->floatvalue = - t->floatvalue; + } //end if + else + { + v->intvalue = t->intvalue; + v->floatvalue = t->floatvalue; + } //end else + v->parentheses = parentheses; + v->next = NULL; + v->prev = lastvalue; + if (lastvalue) lastvalue->next = v; + else firstvalue = v; + lastvalue = v; + //last token was a value + lastwasvalue = 1; + // + negativevalue = 0; + break; + } //end case + case TT_PUNCTUATION: + { + if (negativevalue) + { + SourceError(source, "misplaced minus sign in #if/#elif"); + error = 1; + break; + } //end if + if (t->subtype == P_PARENTHESESOPEN) + { + parentheses++; + break; + } //end if + else if (t->subtype == P_PARENTHESESCLOSE) + { + parentheses--; + if (parentheses < 0) + { + SourceError(source, "too many ) in #if/#elsif"); + error = 1; + } //end if + break; + } //end else if + //check for invalid operators on floating point values + if (!integer) + { + if (t->subtype == P_BIN_NOT || t->subtype == P_MOD || + t->subtype == P_RSHIFT || t->subtype == P_LSHIFT || + t->subtype == P_BIN_AND || t->subtype == P_BIN_OR || + t->subtype == P_BIN_XOR) + { + SourceError(source, "illigal operator %s on floating point operands\n", t->string); + error = 1; + break; + } //end if + } //end if + switch(t->subtype) + { + case P_LOGIC_NOT: + case P_BIN_NOT: + { + if (lastwasvalue) + { + SourceError(source, "! or ~ after value in #if/#elif"); + error = 1; + break; + } //end if + break; + } //end case + case P_INC: + case P_DEC: + { + SourceError(source, "++ or -- used in #if/#elif"); + break; + } //end case + case P_SUB: + { + if (!lastwasvalue) + { + negativevalue = 1; + break; + } //end if + } //end case + + case P_MUL: + case P_DIV: + case P_MOD: + case P_ADD: + + case P_LOGIC_AND: + case P_LOGIC_OR: + case P_LOGIC_GEQ: + case P_LOGIC_LEQ: + case P_LOGIC_EQ: + case P_LOGIC_UNEQ: + + case P_LOGIC_GREATER: + case P_LOGIC_LESS: + + case P_RSHIFT: + case P_LSHIFT: + + case P_BIN_AND: + case P_BIN_OR: + case P_BIN_XOR: + + case P_COLON: + case P_QUESTIONMARK: + { + if (!lastwasvalue) + { + SourceError(source, "operator %s after operator in #if/#elif", t->string); + error = 1; + break; + } //end if + break; + } //end case + default: + { + SourceError(source, "invalid operator %s in #if/#elif", t->string); + error = 1; + break; + } //end default + } //end switch + if (!error && !negativevalue) + { + //o = (operator_t *) GetClearedMemory(sizeof(operator_t)); + AllocOperator(o); + o->operator = t->subtype; + o->priority = PC_OperatorPriority(t->subtype); + o->parentheses = parentheses; + o->next = NULL; + o->prev = lastoperator; + if (lastoperator) lastoperator->next = o; + else firstoperator = o; + lastoperator = o; + lastwasvalue = 0; + } //end if + break; + } //end case + default: + { + SourceError(source, "unknown %s in #if/#elif", t->string); + error = 1; + break; + } //end default + } //end switch + if (error) break; + } //end for + if (!error) + { + if (!lastwasvalue) + { + SourceError(source, "trailing operator in #if/#elif"); + error = 1; + } //end if + else if (parentheses) + { + SourceError(source, "too many ( in #if/#elif"); + error = 1; + } //end else if + } //end if + // + gotquestmarkvalue = qfalse; + questmarkintvalue = 0; + questmarkfloatvalue = 0; + //while there are operators + while(!error && firstoperator) + { + v = firstvalue; + for (o = firstoperator; o->next; o = o->next) + { + //if the current operator is nested deeper in parentheses + //than the next operator + if (o->parentheses > o->next->parentheses) break; + //if the current and next operator are nested equally deep in parentheses + if (o->parentheses == o->next->parentheses) + { + //if the priority of the current operator is equal or higher + //than the priority of the next operator + if (o->priority >= o->next->priority) break; + } //end if + //if the arity of the operator isn't equal to 1 + if (o->operator != P_LOGIC_NOT + && o->operator != P_BIN_NOT) v = v->next; + //if there's no value or no next value + if (!v) + { + SourceError(source, "mising values in #if/#elif"); + error = 1; + break; + } //end if + } //end for + if (error) break; + v1 = v; + v2 = v->next; +#ifdef DEBUG_EVAL + if (integer) + { + Log_Write("operator %s, value1 = %d", PunctuationFromNum(source->scriptstack, o->operator), v1->intvalue); + if (v2) Log_Write("value2 = %d", v2->intvalue); + } //end if + else + { + Log_Write("operator %s, value1 = %f", PunctuationFromNum(source->scriptstack, o->operator), v1->floatvalue); + if (v2) Log_Write("value2 = %f", v2->floatvalue); + } //end else +#endif //DEBUG_EVAL + switch(o->operator) + { + case P_LOGIC_NOT: v1->intvalue = !v1->intvalue; + v1->floatvalue = !v1->floatvalue; break; + case P_BIN_NOT: v1->intvalue = ~v1->intvalue; + break; + case P_MUL: v1->intvalue *= v2->intvalue; + v1->floatvalue *= v2->floatvalue; break; + case P_DIV: if (!v2->intvalue || !v2->floatvalue) + { + SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue /= v2->intvalue; + v1->floatvalue /= v2->floatvalue; break; + case P_MOD: if (!v2->intvalue) + { + SourceError(source, "divide by zero in #if/#elif\n"); + error = 1; + break; + } + v1->intvalue %= v2->intvalue; break; + case P_ADD: v1->intvalue += v2->intvalue; + v1->floatvalue += v2->floatvalue; break; + case P_SUB: v1->intvalue -= v2->intvalue; + v1->floatvalue -= v2->floatvalue; break; + case P_LOGIC_AND: v1->intvalue = v1->intvalue && v2->intvalue; + v1->floatvalue = v1->floatvalue && v2->floatvalue; break; + case P_LOGIC_OR: v1->intvalue = v1->intvalue || v2->intvalue; + v1->floatvalue = v1->floatvalue || v2->floatvalue; break; + case P_LOGIC_GEQ: v1->intvalue = v1->intvalue >= v2->intvalue; + v1->floatvalue = v1->floatvalue >= v2->floatvalue; break; + case P_LOGIC_LEQ: v1->intvalue = v1->intvalue <= v2->intvalue; + v1->floatvalue = v1->floatvalue <= v2->floatvalue; break; + case P_LOGIC_EQ: v1->intvalue = v1->intvalue == v2->intvalue; + v1->floatvalue = v1->floatvalue == v2->floatvalue; break; + case P_LOGIC_UNEQ: v1->intvalue = v1->intvalue != v2->intvalue; + v1->floatvalue = v1->floatvalue != v2->floatvalue; break; + case P_LOGIC_GREATER: v1->intvalue = v1->intvalue > v2->intvalue; + v1->floatvalue = v1->floatvalue > v2->floatvalue; break; + case P_LOGIC_LESS: v1->intvalue = v1->intvalue < v2->intvalue; + v1->floatvalue = v1->floatvalue < v2->floatvalue; break; + case P_RSHIFT: v1->intvalue >>= v2->intvalue; + break; + case P_LSHIFT: v1->intvalue <<= v2->intvalue; + break; + case P_BIN_AND: v1->intvalue &= v2->intvalue; + break; + case P_BIN_OR: v1->intvalue |= v2->intvalue; + break; + case P_BIN_XOR: v1->intvalue ^= v2->intvalue; + break; + case P_COLON: + { + if (!gotquestmarkvalue) + { + SourceError(source, ": without ? in #if/#elif"); + error = 1; + break; + } //end if + if (integer) + { + if (!questmarkintvalue) v1->intvalue = v2->intvalue; + } //end if + else + { + if (!questmarkfloatvalue) v1->floatvalue = v2->floatvalue; + } //end else + gotquestmarkvalue = qfalse; + break; + } //end case + case P_QUESTIONMARK: + { + if (gotquestmarkvalue) + { + SourceError(source, "? after ? in #if/#elif"); + error = 1; + break; + } //end if + questmarkintvalue = v1->intvalue; + questmarkfloatvalue = v1->floatvalue; + gotquestmarkvalue = qtrue; + break; + } //end if + } //end switch +#ifdef DEBUG_EVAL + if (integer) Log_Write("result value = %d", v1->intvalue); + else Log_Write("result value = %f", v1->floatvalue); +#endif //DEBUG_EVAL + if (error) break; + //if not an operator with arity 1 + if (o->operator != P_LOGIC_NOT + && o->operator != P_BIN_NOT) + { + //remove the second value if not question mark operator + if (o->operator != P_QUESTIONMARK) v = v->next; + // + if (v->prev) v->prev->next = v->next; + else firstvalue = v->next; + if (v->next) v->next->prev = v->prev; + else lastvalue = v->prev; + //FreeMemory(v); + FreeValue(v); + } //end if + //remove the operator + if (o->prev) o->prev->next = o->next; + else firstoperator = o->next; + if (o->next) o->next->prev = o->prev; + else lastoperator = o->prev; + //FreeMemory(o); + FreeOperator(o); + } //end while + if (firstvalue) + { + if (intvalue) *intvalue = firstvalue->intvalue; + if (floatvalue) *floatvalue = firstvalue->floatvalue; + } //end if + for (o = firstoperator; o; o = lastoperator) + { + lastoperator = o->next; + //FreeMemory(o); + FreeOperator(o); + } //end for + for (v = firstvalue; v; v = lastvalue) + { + lastvalue = v->next; + //FreeMemory(v); + FreeValue(v); + } //end for + if (!error) return qtrue; + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + return qfalse; +} //end of the function PC_EvaluateTokens +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Evaluate(source_t *source, signed long int *intvalue, + float *floatvalue, int integer) +{ + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + int defined = qfalse; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!PC_ReadLine(source, &token)) + { + SourceError(source, "no value after #if/#elif"); + return qfalse; + } //end if + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = qfalse; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else if (!strcmp(token.string, "defined")) + { + defined = qtrue; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + if (!define) + { + SourceError(source, "can't evaluate %s, not defined", token.string); + return qfalse; + } //end if + if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; + } //end else + } //end if + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError(source, "can't evaluate %s", token.string); + return qfalse; + } //end else + } while(PC_ReadLine(source, &token)); + // + if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; + // +#ifdef DEBUG_EVAL + Log_Write("eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) + { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->string); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken(t); + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("eval result: %d", *intvalue); + else Log_Write("eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_Evaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarEvaluate(source_t *source, signed long int *intvalue, + float *floatvalue, int integer) +{ + int indent, defined = qfalse; + token_t token, *firsttoken, *lasttoken; + token_t *t, *nexttoken; + define_t *define; + + if (intvalue) *intvalue = 0; + if (floatvalue) *floatvalue = 0; + // + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "no leading ( after $evalint/$evalfloat"); + return qfalse; + } //end if + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "nothing to evaluate"); + return qfalse; + } //end if + indent = 1; + firsttoken = NULL; + lasttoken = NULL; + do + { + //if the token is a name + if (token.type == TT_NAME) + { + if (defined) + { + defined = qfalse; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else if (!strcmp(token.string, "defined")) + { + defined = qtrue; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end if + else + { + //then it must be a define +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token.string); +#else + define = PC_FindDefine(source->defines, token.string); +#endif //DEFINEHASHING + if (!define) + { + SourceError(source, "can't evaluate %s, not defined", token.string); + return qfalse; + } //end if + if (!PC_ExpandDefineIntoSource(source, &token, define)) return qfalse; + } //end else + } //end if + //if the token is a number or a punctuation + else if (token.type == TT_NUMBER || token.type == TT_PUNCTUATION) + { + if (*token.string == '(') indent++; + else if (*token.string == ')') indent--; + if (indent <= 0) break; + t = PC_CopyToken(&token); + t->next = NULL; + if (lasttoken) lasttoken->next = t; + else firsttoken = t; + lasttoken = t; + } //end else + else //can't evaluate the token + { + SourceError(source, "can't evaluate %s", token.string); + return qfalse; + } //end else + } while(PC_ReadSourceToken(source, &token)); + // + if (!PC_EvaluateTokens(source, firsttoken, intvalue, floatvalue, integer)) return qfalse; + // +#ifdef DEBUG_EVAL + Log_Write("$eval:"); +#endif //DEBUG_EVAL + for (t = firsttoken; t; t = nexttoken) + { +#ifdef DEBUG_EVAL + Log_Write(" %s", t->string); +#endif //DEBUG_EVAL + nexttoken = t->next; + PC_FreeToken(t); + } //end for +#ifdef DEBUG_EVAL + if (integer) Log_Write("$eval result: %d", *intvalue); + else Log_Write("$eval result: %f", *floatvalue); +#endif //DEBUG_EVAL + // + return qtrue; +} //end of the function PC_DollarEvaluate +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_elif(source_t *source) +{ + signed long int value; + int type, skip; + + PC_PopIndent(source, &type, &skip); + if (!type || type == INDENT_ELSE) + { + SourceError(source, "misplaced #elif"); + return qfalse; + } //end if + if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; + skip = (value == 0); + PC_PushIndent(source, INDENT_ELIF, skip); + return qtrue; +} //end of the function PC_Directive_elif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_if(source_t *source) +{ + signed long int value; + int skip; + + if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; + skip = (value == 0); + PC_PushIndent(source, INDENT_IF, skip); + return qtrue; +} //end of the function PC_Directive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_line(source_t *source) +{ + SourceError(source, "#line directive not supported"); + return qfalse; +} //end of the function PC_Directive_line +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_error(source_t *source) +{ + token_t token; + + strcpy(token.string, ""); + PC_ReadSourceToken(source, &token); + SourceError(source, "#error directive: %s", token.string); + return qfalse; +} //end of the function PC_Directive_error +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_pragma(source_t *source) +{ + token_t token; + + SourceWarning(source, "#pragma directive not supported"); + while(PC_ReadLine(source, &token)) ; + return qtrue; +} //end of the function PC_Directive_pragma +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void UnreadSignToken(source_t *source) +{ + token_t token; + + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + strcpy(token.string, "-"); + token.type = TT_PUNCTUATION; + token.subtype = P_SUB; + PC_UnreadSourceToken(source, &token); +} //end of the function UnreadSignToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_eval(source_t *source) +{ + signed long int value; + token_t token; + + if (!PC_Evaluate(source, &value, NULL, qtrue)) return qfalse; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%d", abs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_Directive_eval +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_Directive_evalfloat(source_t *source) +{ + float value; + token_t token; + + if (!PC_Evaluate(source, NULL, &value, qfalse)) return qfalse; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + PC_UnreadSourceToken(source, &token); + if (value < 0) UnreadSignToken(source); + return qtrue; +} //end of the function PC_Directive_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t directives[20] = +{ + {"if", PC_Directive_if}, + {"ifdef", PC_Directive_ifdef}, + {"ifndef", PC_Directive_ifndef}, + {"elif", PC_Directive_elif}, + {"else", PC_Directive_else}, + {"endif", PC_Directive_endif}, + {"include", PC_Directive_include}, + {"define", PC_Directive_define}, + {"undef", PC_Directive_undef}, + {"line", PC_Directive_line}, + {"error", PC_Directive_error}, + {"pragma", PC_Directive_pragma}, + {"eval", PC_Directive_eval}, + {"evalfloat", PC_Directive_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "found # without name"); + return qfalse; + } //end if + //directive name must be on the same line + if (token.linescrossed > 0) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "found # at end of line"); + return qfalse; + } //end if + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; directives[i].name; i++) + { + if (!strcmp(directives[i].name, token.string)) + { + return directives[i].func(source); + } //end if + } //end for + } //end if + SourceError(source, "unknown precompiler directive %s", token.string); + return qfalse; +} //end of the function PC_ReadDirective +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalint(source_t *source) +{ + signed long int value; + token_t token; + + if (!PC_DollarEvaluate(source, &value, NULL, qtrue)) return qfalse; + // + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%d", abs(value)); + token.type = TT_NUMBER; + token.subtype = TT_INTEGER|TT_LONG|TT_DECIMAL; + +#ifdef NUMBERVALUE + token.intvalue = abs(value); + token.floatvalue = token.intvalue; +#endif //NUMBERVALUE + + PC_UnreadSourceToken(source, &token); + if (value < 0) + UnreadSignToken(source); + + return qtrue; +} //end of the function PC_DollarDirective_evalint +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_DollarDirective_evalfloat(source_t *source) +{ + float value; + token_t token; + + if (!PC_DollarEvaluate(source, NULL, &value, qfalse)) return qfalse; + token.line = source->scriptstack->line; + token.whitespace_p = source->scriptstack->script_p; + token.endwhitespace_p = source->scriptstack->script_p; + token.linescrossed = 0; + sprintf(token.string, "%1.2f", fabs(value)); + token.type = TT_NUMBER; + token.subtype = TT_FLOAT|TT_LONG|TT_DECIMAL; + +#ifdef NUMBERVALUE + token.floatvalue = fabs(value); + token.intvalue = (unsigned long) token.floatvalue; +#endif //NUMBERVALUE + + PC_UnreadSourceToken(source, &token); + if (value < 0) + UnreadSignToken(source); + + return qtrue; +} //end of the function PC_DollarDirective_evalfloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +directive_t dollardirectives[20] = +{ + {"evalint", PC_DollarDirective_evalint}, + {"evalfloat", PC_DollarDirective_evalfloat}, + {NULL, NULL} +}; + +int PC_ReadDollarDirective(source_t *source) +{ + token_t token; + int i; + + //read the directive name + if (!PC_ReadSourceToken(source, &token)) + { + SourceError(source, "found $ without name"); + return qfalse; + } //end if + //directive name must be on the same line + if (token.linescrossed > 0) + { + PC_UnreadSourceToken(source, &token); + SourceError(source, "found $ at end of line"); + return qfalse; + } //end if + //if if is a name + if (token.type == TT_NAME) + { + //find the precompiler directive + for (i = 0; dollardirectives[i].name; i++) + { + if (!strcmp(dollardirectives[i].name, token.string)) + { + return dollardirectives[i].func(source); + } //end if + } //end for + } //end if + PC_UnreadSourceToken(source, &token); + SourceError(source, "unknown precompiler directive %s", token.string); + return qfalse; +} //end of the function PC_ReadDirective + +#ifdef QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int BuiltinFunction(source_t *source) +{ + token_t token; + + if (!PC_ReadSourceToken(source, &token)) return qfalse; + if (token.type == TT_NUMBER) + { + PC_UnreadSourceToken(source, &token); + return qtrue; + } //end if + else + { + PC_UnreadSourceToken(source, &token); + return qfalse; + } //end else +} //end of the function BuiltinFunction +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int QuakeCMacro(source_t *source) +{ + int i; + token_t token; + + if (!PC_ReadSourceToken(source, &token)) return qtrue; + if (token.type != TT_NAME) + { + PC_UnreadSourceToken(source, &token); + return qtrue; + } //end if + //find the precompiler directive + for (i = 0; dollardirectives[i].name; i++) + { + if (!strcmp(dollardirectives[i].name, token.string)) + { + PC_UnreadSourceToken(source, &token); + return qfalse; + } //end if + } //end for + PC_UnreadSourceToken(source, &token); + return qtrue; +} //end of the function QuakeCMacro +#endif //QUAKEC +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadToken(source_t *source, token_t *token) +{ + define_t *define; + + while(1) + { + if (!PC_ReadSourceToken(source, token)) return qfalse; + // check for stringed markers + if (token->type == TT_PUNCTUATION && *token->string == '@') { + char *holdString, holdString2[MAX_TOKEN]; + + PC_ReadSourceToken(source, token); + holdString = &token->string[1]; + Com_Memcpy( holdString2, token->string, sizeof(token->string)); + Com_Memcpy( holdString, holdString2, sizeof(token->string)); + token->string[0] = '@'; + return qtrue; + } + //check for precompiler directives + if (token->type == TT_PUNCTUATION && *token->string == '#') + { +#ifdef QUAKEC + if (!BuiltinFunction(source)) +#endif //QUAKC + { + //read the precompiler directive + if (!PC_ReadDirective(source)) return qfalse; + continue; + } //end if + } //end if + if (token->type == TT_PUNCTUATION && *token->string == '$') + { +#ifdef QUAKEC + if (!QuakeCMacro(source)) +#endif //QUAKEC + { + //read the precompiler directive + if (!PC_ReadDollarDirective(source)) return qfalse; + continue; + } //end if + } //end if + // recursively concatenate strings that are behind each other still resolving defines + if (token->type == TT_STRING) + { + token_t newtoken; + if (PC_ReadToken(source, &newtoken)) + { + if (newtoken.type == TT_STRING) + { + token->string[strlen(token->string)-1] = '\0'; + if (strlen(token->string) + strlen(newtoken.string+1) + 1 >= MAX_TOKEN) + { + SourceError(source, "string longer than MAX_TOKEN %d\n", MAX_TOKEN); + return qfalse; + } + strcat(token->string, newtoken.string+1); + } + else + { + PC_UnreadToken(source, &newtoken); + } + } + } //end if + //if skipping source because of conditional compilation + if (source->skip) continue; + //if the token is a name + if (token->type == TT_NAME) + { + //check if the name is a define macro +#if DEFINEHASHING + define = PC_FindHashedDefine(source->definehash, token->string); +#else + define = PC_FindDefine(source->defines, token->string); +#endif //DEFINEHASHING + //if it is a define macro + if (define) + { + //expand the defined macro + if (!PC_ExpandDefineIntoSource(source, token, define)) return qfalse; + continue; + } //end if + } //end if + //copy token for unreading + Com_Memcpy(&source->token, token, sizeof(token_t)); + //found a token + return qtrue; + } //end while +} //end of the function PC_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenString(source_t *source, char *string) +{ + token_t token; + + if (!PC_ReadToken(source, &token)) + { + SourceError(source, "couldn't find expected %s", string); + return qfalse; + } //end if + + if (strcmp(token.string, string)) + { + SourceError(source, "expected %s, found %s", string, token.string); + return qfalse; + } //end if + return qtrue; +} //end of the function PC_ExpectTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token) +{ + char str[MAX_TOKEN]; + + if (!PC_ReadToken(source, token)) + { + SourceError(source, "couldn't read expected token"); + return qfalse; + } //end if + + if (token->type != type) + { + strcpy(str, ""); + if (type == TT_STRING) strcpy(str, "string"); + if (type == TT_LITERAL) strcpy(str, "literal"); + if (type == TT_NUMBER) strcpy(str, "number"); + if (type == TT_NAME) strcpy(str, "name"); + if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); + SourceError(source, "expected a %s, found %s", str, token->string); + return qfalse; + } //end if + if (token->type == TT_NUMBER) + { + if ((token->subtype & subtype) != subtype) + { + if (subtype & TT_DECIMAL) strcpy(str, "decimal"); + if (subtype & TT_HEX) strcpy(str, "hex"); + if (subtype & TT_OCTAL) strcpy(str, "octal"); + if (subtype & TT_BINARY) strcpy(str, "binary"); + if (subtype & TT_LONG) strcat(str, " long"); + if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); + if (subtype & TT_FLOAT) strcat(str, " float"); + if (subtype & TT_INTEGER) strcat(str, " integer"); + SourceError(source, "expected %s, found %s", str, token->string); + return qfalse; + } //end if + } //end if + else if (token->type == TT_PUNCTUATION) + { + if (token->subtype != subtype) + { + SourceError(source, "found %s", token->string); + return qfalse; + } //end if + } //end else if + return qtrue; +} //end of the function PC_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ExpectAnyToken(source_t *source, token_t *token) +{ + if (!PC_ReadToken(source, token)) + { + SourceError(source, "couldn't read expected token"); + return qfalse; + } //end if + else + { + return qtrue; + } //end else +} //end of the function PC_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenString(source_t *source, char *string) +{ + token_t tok; + + if (!PC_ReadToken(source, &tok)) return qfalse; + //if the token is available + if (!strcmp(tok.string, string)) return qtrue; + // + PC_UnreadSourceToken(source, &tok); + return qfalse; +} //end of the function PC_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token) +{ + token_t tok; + + if (!PC_ReadToken(source, &tok)) return qfalse; + //if the type matches + if (tok.type == type && + (tok.subtype & subtype) == subtype) + { + Com_Memcpy(token, &tok, sizeof(token_t)); + return qtrue; + } //end if + // + PC_UnreadSourceToken(source, &tok); + return qfalse; +} //end of the function PC_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SkipUntilString(source_t *source, char *string) +{ + token_t token; + + while(PC_ReadToken(source, &token)) + { + if (!strcmp(token.string, string)) return qtrue; + } //end while + return qfalse; +} //end of the function PC_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadLastToken(source_t *source) +{ + PC_UnreadSourceToken(source, &source->token); +} //end of the function PC_UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_UnreadToken(source_t *source, token_t *token) +{ + PC_UnreadSourceToken(source, token); +} //end of the function PC_UnreadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetIncludePath(source_t *source, char *path) +{ + strncpy(source->includepath, path, MAX_PATH); + //add trailing path seperator + if (source->includepath[strlen(source->includepath)-1] != '\\' && + source->includepath[strlen(source->includepath)-1] != '/') + { + strcat(source->includepath, PATHSEPERATOR_STR); + } //end if +} //end of the function PC_SetIncludePath +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetPunctuations(source_t *source, punctuation_t *p) +{ + source->punctuations = p; +} //end of the function PC_SetPunctuations +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceFile(const char *filename) +{ + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + +#if DEFINEHASHING + if( !globaldefines ) + globaldefines = (struct define_s **)GetClearedMemory( DEFINEHASHSIZE * sizeof( define_t * ) ); +#endif + + script = LoadScriptFile(filename); + if (!script) return NULL; + + script->next = NULL; + + source = (source_t *) GetMemory(sizeof(source_t)); + Com_Memset(source, 0, sizeof(source_t)); + + strncpy(source->filename, filename, MAX_PATH); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource(source); + return source; +} //end of the function LoadSourceFile +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +source_t *LoadSourceMemory(char *ptr, int length, char *name) +{ + source_t *source; + script_t *script; + + PC_InitTokenHeap(); + + script = LoadScriptMemory(ptr, length, name); + if (!script) return NULL; + script->next = NULL; + + source = (source_t *) GetMemory(sizeof(source_t)); + Com_Memset(source, 0, sizeof(source_t)); + + strncpy(source->filename, name, MAX_PATH); + source->scriptstack = script; + source->tokens = NULL; + source->defines = NULL; + source->indentstack = NULL; + source->skip = 0; + +#if DEFINEHASHING + source->definehash = GetClearedMemory(DEFINEHASHSIZE * sizeof(define_t *)); +#endif //DEFINEHASHING + PC_AddGlobalDefinesToSource(source); + return source; +} //end of the function LoadSourceMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeSource(source_t *source) +{ + script_t *script; + token_t *token; + define_t *define; + indent_t *indent; + define_t *nextdefine; + int i; + + //PC_PrintDefineHashTable(source->definehash); + //free all the scripts + while(source->scriptstack) + { + script = source->scriptstack; + source->scriptstack = source->scriptstack->next; + FreeScript(script); + } //end for + //free all the tokens + while(source->tokens) + { + token = source->tokens; + source->tokens = source->tokens->next; + PC_FreeToken(token); + } //end for +#if DEFINEHASHING + for( i = 0; i < DEFINEHASHSIZE; i++ ) { + define = source->definehash[i]; + while( define ) { + nextdefine = define->hashnext; + + if( !( define->flags & DEFINE_GLOBAL ) ) + PC_FreeDefine( define ); + + define = nextdefine; + } //end while + + source->definehash[i] = NULL; + } // end for +#else //DEFINEHASHING + //free all defines + while(source->defines) + { + define = source->defines; + source->defines = source->defines->next; + PC_FreeDefine(define); + } //end for +#endif //DEFINEHASHING + //free all indents + while(source->indentstack) + { + indent = source->indentstack; + source->indentstack = source->indentstack->next; + FreeMemory(indent); + } //end for +#if DEFINEHASHING + // + if (source->definehash) FreeMemory(source->definehash); +#endif //DEFINEHASHING + //free the source itself + FreeMemory(source); +} //end of the function FreeSource +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ + +#define MAX_SOURCEFILES 64 + +source_t *sourceFiles[MAX_SOURCEFILES]; + +int PC_LoadSourceHandle(const char *filename) +{ + source_t *source; + int i; + + for (i = 1; i < MAX_SOURCEFILES; i++) + { + if (!sourceFiles[i]) + break; + } //end for + if (i >= MAX_SOURCEFILES) + return 0; + PS_SetBaseFolder(""); + source = LoadSourceFile(filename); + if (!source) + return 0; + sourceFiles[i] = source; + return i; +} //end of the function PC_LoadSourceHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_FreeSourceHandle(int handle) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return qfalse; + if (!sourceFiles[handle]) + return qfalse; + + FreeSource(sourceFiles[handle]); + sourceFiles[handle] = NULL; + return qtrue; +} //end of the function PC_FreeSourceHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_LoadGlobalDefines ( const char* filename ) { + int handle; + token_t token; + + handle = PC_LoadSourceHandle( filename ); + if ( handle < 1 ) + return qfalse; + + addGlobalDefine = qtrue; + + // Read all the token files which will add the defines globally + while( PC_ReadToken( sourceFiles[handle], &token ) ); + + addGlobalDefine = qfalse; + + PC_FreeSourceHandle ( handle ); + + return qtrue; +} //end of the function PC_LoadGlobalDefines +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_ReadTokenHandle(int handle, pc_token_t *pc_token) +{ + token_t token; + int ret; + + if (handle < 1 || handle >= MAX_SOURCEFILES) + return 0; + if (!sourceFiles[handle]) + return 0; + + ret = PC_ReadToken(sourceFiles[handle], &token); + strcpy(pc_token->string, token.string); + pc_token->type = token.type; + pc_token->subtype = token.subtype; + pc_token->intvalue = token.intvalue; + pc_token->floatvalue = token.floatvalue; + if( ( pc_token->type == TT_STRING ) && ( pc_token->string[0] != '@' ) ) + StripDoubleQuotes(pc_token->string); + return ret; +} //end of the function PC_ReadTokenHandle +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PC_SourceFileAndLine(int handle, char *filename, int *line) +{ + if (handle < 1 || handle >= MAX_SOURCEFILES) + return qfalse; + if (!sourceFiles[handle]) + return qfalse; + + strcpy(filename, sourceFiles[handle]->filename); + if (sourceFiles[handle]->scriptstack) + *line = sourceFiles[handle]->scriptstack->line; + else + *line = 0; + return qtrue; +} //end of the function PC_SourceFileAndLine +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_SetBaseFolder(char *path) +{ + PS_SetBaseFolder(path); +} //end of the function PC_SetBaseFolder +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PC_CheckOpenSourceHandles(void) +{ + int i; + + for (i = 1; i < MAX_SOURCEFILES; i++) + { + if (sourceFiles[i]) + { +#ifdef BOTLIB + botimport.Print(PRT_ERROR, "file %s still open in precompiler\n", sourceFiles[i]->scriptstack->filename); +#endif //BOTLIB + } //end if + } //end for +} //end of the function PC_CheckOpenSourceHandles + diff --git a/code/botlib/l_precomp.h b/code/botlib/l_precomp.h new file mode 100644 index 0000000..8e6e7a6 --- /dev/null +++ b/code/botlib/l_precomp.h @@ -0,0 +1,184 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_precomp.h + * + * desc: pre compiler + * + * $Archive: /source/code/botlib/l_precomp.h $ + * + *****************************************************************************/ + +#ifndef MAX_PATH + #define MAX_PATH MAX_QPATH +#endif + +#ifndef PATH_SEPERATORSTR + #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) + #define PATHSEPERATOR_STR "\\" + #else + #define PATHSEPERATOR_STR "/" + #endif +#endif +#ifndef PATH_SEPERATORCHAR + #if defined(WIN32)|defined(_WIN32)|defined(__NT__)|defined(__WINDOWS__)|defined(__WINDOWS_386__) + #define PATHSEPERATOR_CHAR '\\' + #else + #define PATHSEPERATOR_CHAR '/' + #endif +#endif + +#if defined(BSPC) && !defined(QDECL) +#define QDECL +#endif + + +#define DEFINE_FIXED 0x0001 +#define DEFINE_GLOBAL 0x0002 + +#define BUILTIN_LINE 1 +#define BUILTIN_FILE 2 +#define BUILTIN_DATE 3 +#define BUILTIN_TIME 4 +#define BUILTIN_STDC 5 + +#define INDENT_IF 0x0001 +#define INDENT_ELSE 0x0002 +#define INDENT_ELIF 0x0004 +#define INDENT_IFDEF 0x0008 +#define INDENT_IFNDEF 0x0010 + +//macro definitions +typedef struct define_s +{ + char *name; //define name + int flags; //define flags + int builtin; // > 0 if builtin define + int numparms; //number of define parameters + token_t *parms; //define parameters + token_t *tokens; //macro tokens (possibly containing parm tokens) + struct define_s *next; //next defined macro in a list + struct define_s *hashnext; //next define in the hash chain + struct define_s *globalnext; //used to link up the globald defines +} define_t; + +//indents +//used for conditional compilation directives: +//#if, #else, #elif, #ifdef, #ifndef +typedef struct indent_s +{ + int type; //indent type + int skip; //true if skipping current indent + script_t *script; //script the indent was in + struct indent_s *next; //next indent on the indent stack +} indent_t; + +//source file +typedef struct source_s +{ + char filename[1024]; //file name of the script + char includepath[1024]; //path to include files + punctuation_t *punctuations; //punctuations to use + script_t *scriptstack; //stack with scripts of the source + token_t *tokens; //tokens to read first + define_t *defines; //list with macro definitions + define_t **definehash; //hash chain with defines + indent_t *indentstack; //stack with indents + int skip; // > 0 if skipping conditional code + token_t token; //last read token +} source_t; + + +//read a token from the source +int PC_ReadToken(source_t *source, token_t *token); +//expect a certain token +int PC_ExpectTokenString(source_t *source, char *string); +//expect a certain token type +int PC_ExpectTokenType(source_t *source, int type, int subtype, token_t *token); +//expect a token +int PC_ExpectAnyToken(source_t *source, token_t *token); +//returns true when the token is available +int PC_CheckTokenString(source_t *source, char *string); +//returns true an reads the token when a token with the given type is available +int PC_CheckTokenType(source_t *source, int type, int subtype, token_t *token); +//skip tokens until the given token string is read +int PC_SkipUntilString(source_t *source, char *string); +//unread the last token read from the script +void PC_UnreadLastToken(source_t *source); +//unread the given token +void PC_UnreadToken(source_t *source, token_t *token); +//read a token only if on the same line, lines are concatenated with a slash +int PC_ReadLine(source_t *source, token_t *token); +//returns true if there was a white space in front of the token +int PC_WhiteSpaceBeforeToken(token_t *token); +//add a define to the source +int PC_AddDefine(source_t *source, char *string); +//add a globals define that will be added to all opened sources +int PC_AddGlobalDefine(char *string); +//remove the given global define +int PC_RemoveGlobalDefine(char *name); +//remove all globals defines +void PC_RemoveAllGlobalDefines(void); +// add global defines from a file +int PC_LoadGlobalDefines( const char *filename ); +//add builtin defines +void PC_AddBuiltinDefines(source_t *source); +//set the source include path +void PC_SetIncludePath(source_t *source, char *path); +//set the punction set +void PC_SetPunctuations(source_t *source, punctuation_t *p); +//set the base folder to load files from +void PC_SetBaseFolder(char *path); +//load a source file +source_t *LoadSourceFile(const char *filename); +//load a source from memory +source_t *LoadSourceMemory(char *ptr, int length, char *name); +//free the given source +void FreeSource(source_t *source); +//print a source error +void QDECL SourceError(source_t *source, char *str, ...) __attribute__ ((format (printf, 2, 3))); +//print a source warning +void QDECL SourceWarning(source_t *source, char *str, ...) __attribute__ ((format (printf, 2, 3))); + +#ifdef BSPC +// some of BSPC source does include game/q_shared.h and some does not +// we define pc_token_s pc_token_t if needed (yes, it's ugly) +#ifndef __Q_SHARED_H +#define MAX_TOKENLENGTH 1024 +typedef struct pc_token_s +{ + int type; + int subtype; + int intvalue; + float floatvalue; + char string[MAX_TOKENLENGTH]; +} pc_token_t; +#endif //!_Q_SHARED_H +#endif //BSPC + +// +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); +void PC_CheckOpenSourceHandles(void); diff --git a/code/botlib/l_script.c b/code/botlib/l_script.c new file mode 100644 index 0000000..6650269 --- /dev/null +++ b/code/botlib/l_script.c @@ -0,0 +1,1449 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_script.c + * + * desc: lexicographical parser + * + * $Archive: /MissionPack/code/botlib/l_script.c $ + * + *****************************************************************************/ + +//#define SCREWUP +//#define BOTLIB +//#define MEQCC +//#define BSPC + +#ifdef SCREWUP +#include +#include +#include +#include +#include +#include "l_memory.h" +#include "l_script.h" + +typedef enum {qfalse, qtrue} qboolean; + +#endif //SCREWUP + +#ifdef BOTLIB +//include files for usage in the bot library +#include "../qcommon/q_shared.h" +#include "botlib.h" +#include "be_interface.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" +#include "l_libvar.h" +#endif //BOTLIB + +#ifdef MEQCC +//include files for usage in MrElusive's QuakeC Compiler +#include "qcc.h" +#include "l_script.h" +#include "l_memory.h" +#include "l_log.h" + +#define qtrue true +#define qfalse false +#endif //MEQCC + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" + +#define qtrue true +#define qfalse false +#endif //BSPC + + +#define PUNCTABLE + +//longer punctuations first +punctuation_t default_punctuations[] = +{ + //binary operators + {">>=",P_RSHIFT_ASSIGN, NULL}, + {"<<=",P_LSHIFT_ASSIGN, NULL}, + // + {"...",P_PARMS, NULL}, + //define merge operator + {"##",P_PRECOMPMERGE, NULL}, + //logic operators + {"&&",P_LOGIC_AND, NULL}, + {"||",P_LOGIC_OR, NULL}, + {">=",P_LOGIC_GEQ, NULL}, + {"<=",P_LOGIC_LEQ, NULL}, + {"==",P_LOGIC_EQ, NULL}, + {"!=",P_LOGIC_UNEQ, NULL}, + //arithmatic operators + {"*=",P_MUL_ASSIGN, NULL}, + {"/=",P_DIV_ASSIGN, NULL}, + {"%=",P_MOD_ASSIGN, NULL}, + {"+=",P_ADD_ASSIGN, NULL}, + {"-=",P_SUB_ASSIGN, NULL}, + {"++",P_INC, NULL}, + {"--",P_DEC, NULL}, + //binary operators + {"&=",P_BIN_AND_ASSIGN, NULL}, + {"|=",P_BIN_OR_ASSIGN, NULL}, + {"^=",P_BIN_XOR_ASSIGN, NULL}, + {">>",P_RSHIFT, NULL}, + {"<<",P_LSHIFT, NULL}, + //reference operators + {"->",P_POINTERREF, NULL}, + //C++ + {"::",P_CPP1, NULL}, + {".*",P_CPP2, NULL}, + //arithmatic operators + {"*",P_MUL, NULL}, + {"/",P_DIV, NULL}, + {"%",P_MOD, NULL}, + {"+",P_ADD, NULL}, + {"-",P_SUB, NULL}, + {"=",P_ASSIGN, NULL}, + //binary operators + {"&",P_BIN_AND, NULL}, + {"|",P_BIN_OR, NULL}, + {"^",P_BIN_XOR, NULL}, + {"~",P_BIN_NOT, NULL}, + //logic operators + {"!",P_LOGIC_NOT, NULL}, + {">",P_LOGIC_GREATER, NULL}, + {"<",P_LOGIC_LESS, NULL}, + //reference operator + {".",P_REF, NULL}, + //seperators + {",",P_COMMA, NULL}, + {";",P_SEMICOLON, NULL}, + //label indication + {":",P_COLON, NULL}, + //if statement + {"?",P_QUESTIONMARK, NULL}, + //embracements + {"(",P_PARENTHESESOPEN, NULL}, + {")",P_PARENTHESESCLOSE, NULL}, + {"{",P_BRACEOPEN, NULL}, + {"}",P_BRACECLOSE, NULL}, + {"[",P_SQBRACKETOPEN, NULL}, + {"]",P_SQBRACKETCLOSE, NULL}, + // + {"\\",P_BACKSLASH, NULL}, + //precompiler operator + {"#",P_PRECOMP, NULL}, +#ifdef DOLLAR + {"$",P_DOLLAR, NULL}, +#endif //DOLLAR + // StringEd key + {"@",P_ATSIGN, NULL}, + {NULL, 0} +}; + +#ifdef BSPC +char basefolder[MAX_PATH]; +#else +char basefolder[MAX_QPATH]; +#endif + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void PS_CreatePunctuationTable(script_t *script, punctuation_t *punctuations) +{ + int i; + punctuation_t *p, *lastp, *newp; + + //get memory for the table + if (!script->punctuationtable) script->punctuationtable = (punctuation_t **) + GetMemory(256 * sizeof(punctuation_t *)); + Com_Memset(script->punctuationtable, 0, 256 * sizeof(punctuation_t *)); + //add the punctuations in the list to the punctuation table + for (i = 0; punctuations[i].p; i++) + { + newp = &punctuations[i]; + lastp = NULL; + //sort the punctuations in this table entry on length (longer punctuations first) + for (p = script->punctuationtable[(unsigned int) newp->p[0]]; p; p = p->next) + { + if (strlen(p->p) < strlen(newp->p)) + { + newp->next = p; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + break; + } //end if + lastp = p; + } //end for + if (!p) + { + newp->next = NULL; + if (lastp) lastp->next = newp; + else script->punctuationtable[(unsigned int) newp->p[0]] = newp; + } //end if + } //end for +} //end of the function PS_CreatePunctuationTable +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +char *PunctuationFromNum(script_t *script, int num) +{ + int i; + + for (i = 0; script->punctuations[i].p; i++) + { + if (script->punctuations[i].n == num) return script->punctuations[i].p; + } //end for + return "unkown punctuation"; +} //end of the function PunctuationFromNum +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptError(script_t *script, char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOERRORS) return; + + va_start(ap, str); + Q_vsnprintf(text, sizeof(text), str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_ERROR, "file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("error: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("error: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BSPC +} //end of the function ScriptError +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void QDECL ScriptWarning(script_t *script, char *str, ...) +{ + char text[1024]; + va_list ap; + + if (script->flags & SCFL_NOWARNINGS) return; + + va_start(ap, str); + Q_vsnprintf(text, sizeof(text), str, ap); + va_end(ap); +#ifdef BOTLIB + botimport.Print(PRT_WARNING, "file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BOTLIB +#ifdef MEQCC + printf("warning: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //MEQCC +#ifdef BSPC + Log_Print("warning: file %s, line %d: %s\n", script->filename, script->line, text); +#endif //BSPC +} //end of the function ScriptWarning +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +void SetScriptPunctuations(script_t *script, punctuation_t *p) +{ +#ifdef PUNCTABLE + if (p) PS_CreatePunctuationTable(script, p); + else PS_CreatePunctuationTable(script, default_punctuations); +#endif //PUNCTABLE + if (p) script->punctuations = p; + else script->punctuations = default_punctuations; +} //end of the function SetScriptPunctuations +//============================================================================ +// Reads spaces, tabs, C-like comments etc. +// When a newline character is found the scripts line counter is increased. +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadWhiteSpace(script_t *script) +{ + while(1) + { + //skip white space + while(*script->script_p <= ' ') + { + if (!*script->script_p) return 0; + if (*script->script_p == '\n') script->line++; + script->script_p++; + } //end while + //skip comments + if (*script->script_p == '/') + { + //comments // + if (*(script->script_p+1) == '/') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return 0; + } //end do + while(*script->script_p != '\n'); + script->line++; + script->script_p++; + if (!*script->script_p) return 0; + continue; + } //end if + //comments /* */ + else if (*(script->script_p+1) == '*') + { + script->script_p++; + do + { + script->script_p++; + if (!*script->script_p) return 0; + if (*script->script_p == '\n') script->line++; + } //end do + while(!(*script->script_p == '*' && *(script->script_p+1) == '/')); + script->script_p++; + if (!*script->script_p) return 0; + script->script_p++; + if (!*script->script_p) return 0; + continue; + } //end if + } //end if + break; + } //end while + return 1; +} //end of the function PS_ReadWhiteSpace +//============================================================================ +// Reads an escape character. +// +// Parameter: script : script to read from +// ch : place to store the read escape character +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadEscapeCharacter(script_t *script, char *ch) +{ + int c, val, i; + + //step over the leading '\\' + script->script_p++; + //determine the escape character + switch(*script->script_p) + { + case '\\': c = '\\'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'a': c = '\a'; break; + case '\'': c = '\''; break; + case '\"': c = '\"'; break; + case '\?': c = '\?'; break; + case 'x': + { + script->script_p++; + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else if (c >= 'A' && c <= 'Z') c = c - 'A' + 10; + else if (c >= 'a' && c <= 'z') c = c - 'a' + 10; + else break; + val = (val << 4) + c; + } //end for + script->script_p--; + if (val > 0xFF) + { + ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } //end if + c = val; + break; + } //end case + default: //NOTE: decimal ASCII code, NOT octal + { + if (*script->script_p < '0' || *script->script_p > '9') ScriptError(script, "unknown escape char"); + for (i = 0, val = 0; ; i++, script->script_p++) + { + c = *script->script_p; + if (c >= '0' && c <= '9') c = c - '0'; + else break; + val = val * 10 + c; + } //end for + script->script_p--; + if (val > 0xFF) + { + ScriptWarning(script, "too large value in escape character"); + val = 0xFF; + } //end if + c = val; + break; + } //end default + } //end switch + //step over the escape character or the last digit of the number + script->script_p++; + //store the escape character + *ch = c; + //successfully read escape character + return 1; +} //end of the function PS_ReadEscapeCharacter +//============================================================================ +// Reads C-like string. Escape characters are interpretted. +// Quotes are included with the string. +// Reads two strings with a white space between them as one string. +// +// Parameter: script : script to read from +// token : buffer to store the string +// Returns: qtrue when a string was read successfully +// Changes Globals: - +//============================================================================ +int PS_ReadString(script_t *script, token_t *token, int quote) +{ + int len, tmpline; + char *tmpscript_p; + + if (quote == '\"') token->type = TT_STRING; + else token->type = TT_LITERAL; + + len = 0; + //leading quote + token->string[len++] = *script->script_p++; + // + while(1) + { + //minus 2 because trailing double quote and zero have to be appended + if (len >= MAX_TOKEN - 2) + { + ScriptError(script, "string longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + //if there is an escape character and + //if escape characters inside a string are allowed + if (*script->script_p == '\\' && !(script->flags & SCFL_NOSTRINGESCAPECHARS)) + { + if (!PS_ReadEscapeCharacter(script, &token->string[len])) + { + token->string[len] = 0; + return 0; + } //end if + len++; + } //end if + //if a trailing quote + else if (*script->script_p == quote) + { + //step over the double quote + script->script_p++; + //if white spaces in a string are not allowed + if (script->flags & SCFL_NOSTRINGWHITESPACES) break; + // + tmpscript_p = script->script_p; + tmpline = script->line; + //read unusefull stuff between possible two following strings + if (!PS_ReadWhiteSpace(script)) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //if there's no leading double qoute + if (*script->script_p != quote) + { + script->script_p = tmpscript_p; + script->line = tmpline; + break; + } //end if + //step over the new leading double quote + script->script_p++; + } //end if + else + { + if (*script->script_p == '\0') + { + token->string[len] = 0; + ScriptError(script, "missing trailing quote"); + return 0; + } //end if + if (*script->script_p == '\n') + { + token->string[len] = 0; + ScriptError(script, "newline inside string %s", token->string); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end else + } //end while + //trailing quote + token->string[len++] = quote; + //end string with a zero + token->string[len] = '\0'; + //the sub type is the length of the string + token->subtype = len; + return 1; +} //end of the function PS_ReadString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadName(script_t *script, token_t *token) +{ + int len = 0; + char c; + + token->type = TT_NAME; + do + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN) + { + ScriptError(script, "name longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + c = *script->script_p; + } while ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '_'); + token->string[len] = '\0'; + //the sub type is the length of the name + token->subtype = len; + return 1; +} //end of the function PS_ReadName +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void NumberValue(char *string, int subtype, unsigned long int *intvalue, + float *floatvalue) +{ + unsigned long int dotfound = 0; + + *intvalue = 0; + *floatvalue = 0; + //floating point number + if (subtype & TT_FLOAT) + { + while(*string) + { + if (*string == '.') + { + if (dotfound) return; + dotfound = 10; + string++; + } //end if + if (dotfound) + { + *floatvalue = *floatvalue + (float) (*string - '0') / + (float) dotfound; + dotfound *= 10; + } //end if + else + { + *floatvalue = *floatvalue * 10.0 + (float) (*string - '0'); + } //end else + string++; + } //end while + *intvalue = (unsigned long) *floatvalue; + } //end if + else if (subtype & TT_DECIMAL) + { + while(*string) *intvalue = *intvalue * 10 + (*string++ - '0'); + *floatvalue = *intvalue; + } //end else if + else if (subtype & TT_HEX) + { + //step over the leading 0x or 0X + string += 2; + while(*string) + { + *intvalue <<= 4; + if (*string >= 'a' && *string <= 'f') *intvalue += *string - 'a' + 10; + else if (*string >= 'A' && *string <= 'F') *intvalue += *string - 'A' + 10; + else *intvalue += *string - '0'; + string++; + } //end while + *floatvalue = *intvalue; + } //end else if + else if (subtype & TT_OCTAL) + { + //step over the first zero + string += 1; + while(*string) *intvalue = (*intvalue << 3) + (*string++ - '0'); + *floatvalue = *intvalue; + } //end else if + else if (subtype & TT_BINARY) + { + //step over the leading 0b or 0B + string += 2; + while(*string) *intvalue = (*intvalue << 1) + (*string++ - '0'); + *floatvalue = *intvalue; + } //end else if +} //end of the function NumberValue +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadNumber(script_t *script, token_t *token) +{ + int len = 0, i; + int octal, dot; + char c; +// unsigned long int intvalue = 0; +// double floatvalue = 0; + + token->type = TT_NUMBER; + //check for a hexadecimal number + if (*script->script_p == '0' && + (*(script->script_p + 1) == 'x' || + *(script->script_p + 1) == 'X')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //hexadecimal + while((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || + (c >= 'A' && c <= 'A')) + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN) + { + ScriptError(script, "hexadecimal number longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_HEX; + } //end if +#ifdef BINARYNUMBERS + //check for a binary number + else if (*script->script_p == '0' && + (*(script->script_p + 1) == 'b' || + *(script->script_p + 1) == 'B')) + { + token->string[len++] = *script->script_p++; + token->string[len++] = *script->script_p++; + c = *script->script_p; + //binary + while(c == '0' || c == '1') + { + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN) + { + ScriptError(script, "binary number longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + c = *script->script_p; + } //end while + token->subtype |= TT_BINARY; + } //end if +#endif //BINARYNUMBERS + else //decimal or octal integer or floating point number + { + octal = qfalse; + dot = qfalse; + if (*script->script_p == '0') octal = qtrue; + while(1) + { + c = *script->script_p; + if (c == '.') dot = qtrue; + else if (c == '8' || c == '9') octal = qfalse; + else if (c < '0' || c > '9') break; + token->string[len++] = *script->script_p++; + if (len >= MAX_TOKEN - 1) + { + ScriptError(script, "number longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + } //end while + if (octal) token->subtype |= TT_OCTAL; + else token->subtype |= TT_DECIMAL; + if (dot) token->subtype |= TT_FLOAT; + } //end else + for (i = 0; i < 2; i++) + { + c = *script->script_p; + //check for a LONG number + if ( (c == 'l' || c == 'L') + && !(token->subtype & TT_LONG)) + { + script->script_p++; + token->subtype |= TT_LONG; + } //end if + //check for an UNSIGNED number + else if ( (c == 'u' || c == 'U') + && !(token->subtype & (TT_UNSIGNED | TT_FLOAT))) + { + script->script_p++; + token->subtype |= TT_UNSIGNED; + } //end if + } //end for + token->string[len] = '\0'; +#ifdef NUMBERVALUE + NumberValue(token->string, token->subtype, &token->intvalue, &token->floatvalue); +#endif //NUMBERVALUE + if (!(token->subtype & TT_FLOAT)) token->subtype |= TT_INTEGER; + return 1; +} //end of the function PS_ReadNumber +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadLiteral(script_t *script, token_t *token) +{ + token->type = TT_LITERAL; + //first quote + token->string[0] = *script->script_p++; + //check for end of file + if (!*script->script_p) + { + ScriptError(script, "end of file before trailing \'"); + return 0; + } //end if + //if it is an escape character + if (*script->script_p == '\\') + { + if (!PS_ReadEscapeCharacter(script, &token->string[1])) return 0; + } //end if + else + { + token->string[1] = *script->script_p++; + } //end else + //check for trailing quote + if (*script->script_p != '\'') + { + ScriptWarning(script, "too many characters in literal, ignored"); + while(*script->script_p && + *script->script_p != '\'' && + *script->script_p != '\n') + { + script->script_p++; + } //end while + if (*script->script_p == '\'') script->script_p++; + } //end if + //store the trailing quote + token->string[2] = *script->script_p++; + //store trailing zero to end the string + token->string[3] = '\0'; + //the sub type is the integer literal value + token->subtype = token->string[1]; + // + return 1; +} //end of the function PS_ReadLiteral +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPunctuation(script_t *script, token_t *token) +{ + int len; + char *p; + punctuation_t *punc; + +#ifdef PUNCTABLE + for (punc = script->punctuationtable[(unsigned int)*script->script_p]; punc; punc = punc->next) + { +#else + int i; + + for (i = 0; script->punctuations[i].p; i++) + { + punc = &script->punctuations[i]; +#endif //PUNCTABLE + p = punc->p; + len = strlen(p); + //if the script contains at least as much characters as the punctuation + if (script->script_p + len <= script->end_p) + { + //if the script contains the punctuation + if (!strncmp(script->script_p, p, len)) + { + strncpy(token->string, p, MAX_TOKEN); + script->script_p += len; + token->type = TT_PUNCTUATION; + //sub type is the number of the punctuation + token->subtype = punc->n; + return 1; + } //end if + } //end if + } //end for + return 0; +} //end of the function PS_ReadPunctuation +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadPrimitive(script_t *script, token_t *token) +{ + int len; + + len = 0; + while(*script->script_p > ' ' && *script->script_p != ';') + { + if (len >= MAX_TOKEN) + { + ScriptError(script, "primitive token longer than MAX_TOKEN = %d", MAX_TOKEN); + return 0; + } //end if + token->string[len++] = *script->script_p++; + } //end while + token->string[len] = 0; + //copy the token into the script structure + Com_Memcpy(&script->token, token, sizeof(token_t)); + //primitive reading successfull + return 1; +} //end of the function PS_ReadPrimitive +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ReadToken(script_t *script, token_t *token) +{ + //if there is a token available (from UnreadToken) + if (script->tokenavailable) + { + script->tokenavailable = 0; + Com_Memcpy(token, &script->token, sizeof(token_t)); + return 1; + } //end if + //save script pointer + script->lastscript_p = script->script_p; + //save line counter + script->lastline = script->line; + //clear the token stuff + Com_Memset(token, 0, sizeof(token_t)); + //start of the white space + script->whitespace_p = script->script_p; + token->whitespace_p = script->script_p; + //read unusefull stuff + if (!PS_ReadWhiteSpace(script)) return 0; + //end of the white space + script->endwhitespace_p = script->script_p; + token->endwhitespace_p = script->script_p; + //line the token is on + token->line = script->line; + //number of lines crossed before token + token->linescrossed = script->line - script->lastline; + //if there is a leading double quote + if (*script->script_p == '\"') + { + if (!PS_ReadString(script, token, '\"')) return 0; + } //end if + //if an literal + else if (*script->script_p == '\'') + { + //if (!PS_ReadLiteral(script, token)) return 0; + if (!PS_ReadString(script, token, '\'')) return 0; + } //end if + //if there is a number + else if ((*script->script_p >= '0' && *script->script_p <= '9') || + (*script->script_p == '.' && + (*(script->script_p + 1) >= '0' && *(script->script_p + 1) <= '9'))) + { + if (!PS_ReadNumber(script, token)) return 0; + } //end if + //if this is a primitive script + else if (script->flags & SCFL_PRIMITIVE) + { + return PS_ReadPrimitive(script, token); + } //end else if + //if there is a name + else if ((*script->script_p >= 'a' && *script->script_p <= 'z') || + (*script->script_p >= 'A' && *script->script_p <= 'Z') || + *script->script_p == '_' || *script->script_p == '@') + { + if (!PS_ReadName(script, token)) return 0; + } //end if + //check for punctuations + else if (!PS_ReadPunctuation(script, token)) + { + ScriptError(script, "can't read token"); + return 0; + } //end if + //copy the token into the script structure + Com_Memcpy(&script->token, token, sizeof(token_t)); + //successfully read a token + return 1; +} //end of the function PS_ReadToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenString(script_t *script, char *string) +{ + token_t token; + + if (!PS_ReadToken(script, &token)) + { + ScriptError(script, "couldn't find expected %s", string); + return 0; + } //end if + + if (strcmp(token.string, string)) + { + ScriptError(script, "expected %s, found %s", string, token.string); + return 0; + } //end if + return 1; +} //end of the function PS_ExpectToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token) +{ + char str[MAX_TOKEN]; + + if (!PS_ReadToken(script, token)) + { + ScriptError(script, "couldn't read expected token"); + return 0; + } //end if + + if (token->type != type) + { + if (type == TT_STRING) strcpy(str, "string"); + if (type == TT_LITERAL) strcpy(str, "literal"); + if (type == TT_NUMBER) strcpy(str, "number"); + if (type == TT_NAME) strcpy(str, "name"); + if (type == TT_PUNCTUATION) strcpy(str, "punctuation"); + ScriptError(script, "expected a %s, found %s", str, token->string); + return 0; + } //end if + if (token->type == TT_NUMBER) + { + if ((token->subtype & subtype) != subtype) + { + if (subtype & TT_DECIMAL) strcpy(str, "decimal"); + if (subtype & TT_HEX) strcpy(str, "hex"); + if (subtype & TT_OCTAL) strcpy(str, "octal"); + if (subtype & TT_BINARY) strcpy(str, "binary"); + if (subtype & TT_LONG) strcat(str, " long"); + if (subtype & TT_UNSIGNED) strcat(str, " unsigned"); + if (subtype & TT_FLOAT) strcat(str, " float"); + if (subtype & TT_INTEGER) strcat(str, " integer"); + ScriptError(script, "expected %s, found %s", str, token->string); + return 0; + } //end if + } //end if + else if (token->type == TT_PUNCTUATION) + { + if (subtype < 0) + { + ScriptError(script, "BUG: wrong punctuation subtype"); + return 0; + } //end if + if (token->subtype != subtype) + { + ScriptError(script, "expected %s, found %s", + script->punctuations[subtype].p, token->string); + return 0; + } //end if + } //end else if + return 1; +} //end of the function PS_ExpectTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_ExpectAnyToken(script_t *script, token_t *token) +{ + if (!PS_ReadToken(script, token)) + { + ScriptError(script, "couldn't read expected token"); + return 0; + } //end if + else + { + return 1; + } //end else +} //end of the function PS_ExpectAnyToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenString(script_t *script, char *string) +{ + token_t tok; + + if (!PS_ReadToken(script, &tok)) return 0; + //if the token is available + if (!strcmp(tok.string, string)) return 1; + //token not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token) +{ + token_t tok; + + if (!PS_ReadToken(script, &tok)) return 0; + //if the type matches + if (tok.type == type && + (tok.subtype & subtype) == subtype) + { + Com_Memcpy(token, &tok, sizeof(token_t)); + return 1; + } //end if + //token is not available + script->script_p = script->lastscript_p; + return 0; +} //end of the function PS_CheckTokenType +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int PS_SkipUntilString(script_t *script, char *string) +{ + token_t token; + + while(PS_ReadToken(script, &token)) + { + if (!strcmp(token.string, string)) return 1; + } //end while + return 0; +} //end of the function PS_SkipUntilString +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadLastToken(script_t *script) +{ + script->tokenavailable = 1; +} //end of the function UnreadLastToken +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_UnreadToken(script_t *script, token_t *token) +{ + Com_Memcpy(&script->token, token, sizeof(token_t)); + script->tokenavailable = 1; +} //end of the function UnreadToken +//============================================================================ +// returns the next character of the read white space, returns NULL if none +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +char PS_NextWhiteSpaceChar(script_t *script) +{ + if (script->whitespace_p != script->endwhitespace_p) + { + return *script->whitespace_p++; + } //end if + else + { + return 0; + } //end else +} //end of the function PS_NextWhiteSpaceChar +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripDoubleQuotes(char *string) +{ + if (*string == '\"') + { + memmove(string, string+1, strlen(string)); + } //end if + if (string[strlen(string)-1] == '\"') + { + string[strlen(string)-1] = '\0'; + } //end if +} //end of the function StripDoubleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void StripSingleQuotes(char *string) +{ + if (*string == '\'') + { + memmove(string, string+1, strlen(string)); + } //end if + if (string[strlen(string)-1] == '\'') + { + string[strlen(string)-1] = '\0'; + } //end if +} //end of the function StripSingleQuotes +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +float ReadSignedFloat(script_t *script) +{ + token_t token; + float sign = 1.0; + + PS_ExpectAnyToken(script, &token); + if (!strcmp(token.string, "-")) + { + if(!PS_ExpectAnyToken(script, &token)) + { + ScriptError(script, "Missing float value\n"); + return 0; + } + + sign = -1.0; + } + + if (token.type != TT_NUMBER) + { + ScriptError(script, "expected float value, found %s\n", token.string); + return 0; + } + + return sign * token.floatvalue; +} //end of the function ReadSignedFloat +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +signed long int ReadSignedInt(script_t *script) +{ + token_t token; + signed long int sign = 1; + + PS_ExpectAnyToken(script, &token); + if (!strcmp(token.string, "-")) + { + if(!PS_ExpectAnyToken(script, &token)) + { + ScriptError(script, "Missing integer value\n"); + return 0; + } + + sign = -1; + } + + if (token.type != TT_NUMBER || token.subtype == TT_FLOAT) + { + ScriptError(script, "expected integer value, found %s\n", token.string); + return 0; + } + + return sign * token.intvalue; +} //end of the function ReadSignedInt +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void SetScriptFlags(script_t *script, int flags) +{ + script->flags = flags; +} //end of the function SetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int GetScriptFlags(script_t *script) +{ + return script->flags; +} //end of the function GetScriptFlags +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void ResetScript(script_t *script) +{ + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //begin of white space + script->whitespace_p = NULL; + //end of white space + script->endwhitespace_p = NULL; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + //clear the saved token + Com_Memset(&script->token, 0, sizeof(token_t)); +} //end of the function ResetScript +//============================================================================ +// returns true if at the end of the script +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int EndOfScript(script_t *script) +{ + return script->script_p >= script->end_p; +} //end of the function EndOfScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int NumLinesCrossed(script_t *script) +{ + return script->line - script->lastline; +} //end of the function NumLinesCrossed +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int ScriptSkipTo(script_t *script, char *value) +{ + int len; + char firstchar; + + firstchar = *value; + len = strlen(value); + do + { + if (!PS_ReadWhiteSpace(script)) return 0; + if (*script->script_p == firstchar) + { + if (!strncmp(script->script_p, value, len)) + { + return 1; + } //end if + } //end if + script->script_p++; + } while(1); +} //end of the function ScriptSkipTo +#ifndef BOTLIB +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +int FileLength(FILE *fp) +{ + int pos; + int end; + + pos = ftell(fp); + fseek(fp, 0, SEEK_END); + end = ftell(fp); + fseek(fp, pos, SEEK_SET); + + return end; +} //end of the function FileLength +#endif +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptFile(const char *filename) +{ +#ifdef BOTLIB + fileHandle_t fp; + char pathname[MAX_QPATH]; +#else + FILE *fp; +#endif + int length; + void *buffer; + script_t *script; + +#ifdef BOTLIB + if (strlen(basefolder)) + Com_sprintf(pathname, sizeof(pathname), "%s/%s", basefolder, filename); + else + Com_sprintf(pathname, sizeof(pathname), "%s", filename); + length = botimport.FS_FOpenFile( pathname, &fp, FS_READ ); + if (!fp) return NULL; +#else + fp = fopen(filename, "rb"); + if (!fp) return NULL; + + length = FileLength(fp); +#endif + + buffer = GetClearedMemory(sizeof(script_t) + length + 1); + script = (script_t *) buffer; + Com_Memset(script, 0, sizeof(script_t)); + strcpy(script->filename, filename); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations(script, NULL); + // +#ifdef BOTLIB + botimport.FS_Read(script->buffer, length, fp); + botimport.FS_FCloseFile(fp); +#else + if (fread(script->buffer, length, 1, fp) != 1) + { + FreeMemory(buffer); + script = NULL; + } //end if + fclose(fp); +#endif + + return script; +} //end of the function LoadScriptFile +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +script_t *LoadScriptMemory(char *ptr, int length, char *name) +{ + void *buffer; + script_t *script; + + buffer = GetClearedMemory(sizeof(script_t) + length + 1); + script = (script_t *) buffer; + Com_Memset(script, 0, sizeof(script_t)); + strcpy(script->filename, name); + script->buffer = (char *) buffer + sizeof(script_t); + script->buffer[length] = 0; + script->length = length; + //pointer in script buffer + script->script_p = script->buffer; + //pointer in script buffer before reading token + script->lastscript_p = script->buffer; + //pointer to end of script buffer + script->end_p = &script->buffer[length]; + //set if there's a token available in script->token + script->tokenavailable = 0; + // + script->line = 1; + script->lastline = 1; + // + SetScriptPunctuations(script, NULL); + // + Com_Memcpy(script->buffer, ptr, length); + // + return script; +} //end of the function LoadScriptMemory +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void FreeScript(script_t *script) +{ +#ifdef PUNCTABLE + if (script->punctuationtable) FreeMemory(script->punctuationtable); +#endif //PUNCTABLE + FreeMemory(script); +} //end of the function FreeScript +//============================================================================ +// +// Parameter: - +// Returns: - +// Changes Globals: - +//============================================================================ +void PS_SetBaseFolder(char *path) +{ +#ifdef BSPC + sprintf(basefolder, path); +#else + Com_sprintf(basefolder, sizeof(basefolder), "%s", path); +#endif +} //end of the function PS_SetBaseFolder diff --git a/code/botlib/l_script.h b/code/botlib/l_script.h new file mode 100644 index 0000000..6e547af --- /dev/null +++ b/code/botlib/l_script.h @@ -0,0 +1,248 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_script.h + * + * desc: lexicographical parser + * + * $Archive: /source/code/botlib/l_script.h $ + * + *****************************************************************************/ + +//undef if binary numbers of the form 0b... or 0B... are not allowed +#define BINARYNUMBERS +//undef if not using the token.intvalue and token.floatvalue +#define NUMBERVALUE +//use dollar sign also as punctuation +#define DOLLAR + +//maximum token length +#define MAX_TOKEN 1024 + +#if defined(BSPC) && !defined(QDECL) +#define QDECL +#endif + + +//script flags +#define SCFL_NOERRORS 0x0001 +#define SCFL_NOWARNINGS 0x0002 +#define SCFL_NOSTRINGWHITESPACES 0x0004 +#define SCFL_NOSTRINGESCAPECHARS 0x0008 +#define SCFL_PRIMITIVE 0x0010 +#define SCFL_NOBINARYNUMBERS 0x0020 +#define SCFL_NONUMBERVALUES 0x0040 + +//token types +#define TT_STRING 1 // string +#define TT_LITERAL 2 // literal +#define TT_NUMBER 3 // number +#define TT_NAME 4 // name +#define TT_PUNCTUATION 5 // punctuation + +//string sub type +//--------------- +// the length of the string +//literal sub type +//---------------- +// the ASCII code of the literal +//number sub type +//--------------- +#define TT_DECIMAL 0x0008 // decimal number +#define TT_HEX 0x0100 // hexadecimal number +#define TT_OCTAL 0x0200 // octal number +#ifdef BINARYNUMBERS +#define TT_BINARY 0x0400 // binary number +#endif //BINARYNUMBERS +#define TT_FLOAT 0x0800 // floating point number +#define TT_INTEGER 0x1000 // integer number +#define TT_LONG 0x2000 // long number +#define TT_UNSIGNED 0x4000 // unsigned number +//punctuation sub type +//-------------------- +#define P_RSHIFT_ASSIGN 1 +#define P_LSHIFT_ASSIGN 2 +#define P_PARMS 3 +#define P_PRECOMPMERGE 4 + +#define P_LOGIC_AND 5 +#define P_LOGIC_OR 6 +#define P_LOGIC_GEQ 7 +#define P_LOGIC_LEQ 8 +#define P_LOGIC_EQ 9 +#define P_LOGIC_UNEQ 10 + +#define P_MUL_ASSIGN 11 +#define P_DIV_ASSIGN 12 +#define P_MOD_ASSIGN 13 +#define P_ADD_ASSIGN 14 +#define P_SUB_ASSIGN 15 +#define P_INC 16 +#define P_DEC 17 + +#define P_BIN_AND_ASSIGN 18 +#define P_BIN_OR_ASSIGN 19 +#define P_BIN_XOR_ASSIGN 20 +#define P_RSHIFT 21 +#define P_LSHIFT 22 + +#define P_POINTERREF 23 +#define P_CPP1 24 +#define P_CPP2 25 +#define P_MUL 26 +#define P_DIV 27 +#define P_MOD 28 +#define P_ADD 29 +#define P_SUB 30 +#define P_ASSIGN 31 + +#define P_BIN_AND 32 +#define P_BIN_OR 33 +#define P_BIN_XOR 34 +#define P_BIN_NOT 35 + +#define P_LOGIC_NOT 36 +#define P_LOGIC_GREATER 37 +#define P_LOGIC_LESS 38 + +#define P_REF 39 +#define P_COMMA 40 +#define P_SEMICOLON 41 +#define P_COLON 42 +#define P_QUESTIONMARK 43 + +#define P_PARENTHESESOPEN 44 +#define P_PARENTHESESCLOSE 45 +#define P_BRACEOPEN 46 +#define P_BRACECLOSE 47 +#define P_SQBRACKETOPEN 48 +#define P_SQBRACKETCLOSE 49 +#define P_BACKSLASH 50 + +#define P_PRECOMP 51 +#define P_DOLLAR 52 +#define P_ATSIGN 53 +//name sub type +//------------- +// the length of the name + +//punctuation +typedef struct punctuation_s +{ + char *p; //punctuation character(s) + int n; //punctuation indication + struct punctuation_s *next; //next punctuation +} punctuation_t; + +//token +typedef struct token_s +{ + char string[MAX_TOKEN]; //available token + int type; //last read token type + int subtype; //last read token sub type +#ifdef NUMBERVALUE + unsigned long int intvalue; //integer value + float floatvalue; //floating point value +#endif //NUMBERVALUE + char *whitespace_p; //start of white space before token + char *endwhitespace_p; //start of white space before token + int line; //line the token was on + int linescrossed; //lines crossed in white space + struct token_s *next; //next token in chain +} token_t; + +//script file +typedef struct script_s +{ + char filename[1024]; //file name of the script + char *buffer; //buffer containing the script + char *script_p; //current pointer in the script + char *end_p; //pointer to the end of the script + char *lastscript_p; //script pointer before reading token + char *whitespace_p; //begin of the white space + char *endwhitespace_p; //end of the white space + int length; //length of the script in bytes + int line; //current line in script + int lastline; //line before reading token + int tokenavailable; //set by UnreadLastToken + int flags; //several script flags + punctuation_t *punctuations; //the punctuations used in the script + punctuation_t **punctuationtable; + token_t token; //available token + struct script_s *next; //next script in a chain +} script_t; + +//read a token from the script +int PS_ReadToken(script_t *script, token_t *token); +//expect a certain token +int PS_ExpectTokenString(script_t *script, char *string); +//expect a certain token type +int PS_ExpectTokenType(script_t *script, int type, int subtype, token_t *token); +//expect a token +int PS_ExpectAnyToken(script_t *script, token_t *token); +//returns true when the token is available +int PS_CheckTokenString(script_t *script, char *string); +//returns true an reads the token when a token with the given type is available +int PS_CheckTokenType(script_t *script, int type, int subtype, token_t *token); +//skip tokens until the given token string is read +int PS_SkipUntilString(script_t *script, char *string); +//unread the last token read from the script +void PS_UnreadLastToken(script_t *script); +//unread the given token +void PS_UnreadToken(script_t *script, token_t *token); +//returns the next character of the read white space, returns NULL if none +char PS_NextWhiteSpaceChar(script_t *script); +//remove any leading and trailing double quotes from the token +void StripDoubleQuotes(char *string); +//remove any leading and trailing single quotes from the token +void StripSingleQuotes(char *string); +//read a possible signed integer +signed long int ReadSignedInt(script_t *script); +//read a possible signed floating point number +float ReadSignedFloat(script_t *script); +//set an array with punctuations, NULL restores default C/C++ set +void SetScriptPunctuations(script_t *script, punctuation_t *p); +//set script flags +void SetScriptFlags(script_t *script, int flags); +//get script flags +int GetScriptFlags(script_t *script); +//reset a script +void ResetScript(script_t *script); +//returns true if at the end of the script +int EndOfScript(script_t *script); +//returns a pointer to the punctuation with the given number +char *PunctuationFromNum(script_t *script, int num); +//load a script from the given file at the given offset with the given length +script_t *LoadScriptFile(const char *filename); +//load a script from the given memory with the given length +script_t *LoadScriptMemory(char *ptr, int length, char *name); +//free a script +void FreeScript(script_t *script); +//set the base folder to load files from +void PS_SetBaseFolder(char *path); +//print a script error with filename and line number +void QDECL ScriptError(script_t *script, char *str, ...) __attribute__ ((format (printf, 2, 3))); +//print a script warning with filename and line number +void QDECL ScriptWarning(script_t *script, char *str, ...) __attribute__ ((format (printf, 2, 3))); + + diff --git a/code/botlib/l_struct.c b/code/botlib/l_struct.c new file mode 100644 index 0000000..0983b4d --- /dev/null +++ b/code/botlib/l_struct.c @@ -0,0 +1,462 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_struct.c + * + * desc: structure reading / writing + * + * $Archive: /MissionPack/CODE/botlib/l_struct.c $ + * + *****************************************************************************/ + +#ifdef BOTLIB +#include "../qcommon/q_shared.h" +#include "botlib.h" //for the include of be_interface.h +#include "l_script.h" +#include "l_precomp.h" +#include "l_struct.h" +#include "l_utils.h" +#include "be_interface.h" +#endif //BOTLIB + +#ifdef BSPC +//include files for usage in the BSP Converter +#include "../bspc/qbsp.h" +#include "../bspc/l_log.h" +#include "../bspc/l_mem.h" +#include "l_precomp.h" +#include "l_struct.h" + +#define qtrue true +#define qfalse false +#endif //BSPC + +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +fielddef_t *FindField(fielddef_t *defs, char *name) +{ + int i; + + for (i = 0; defs[i].name; i++) + { + if (!strcmp(defs[i].name, name)) return &defs[i]; + } //end for + return NULL; +} //end of the function FindField +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ReadNumber(source_t *source, fielddef_t *fd, void *p) +{ + token_t token; + int negative = qfalse; + long int intval, intmin = 0, intmax = 0; + double floatval; + + if (!PC_ExpectAnyToken(source, &token)) return 0; + + //check for minus sign + if (token.type == TT_PUNCTUATION) + { + if (fd->type & FT_UNSIGNED) + { + SourceError(source, "expected unsigned value, found %s", token.string); + return 0; + } //end if + //if not a minus sign + if (strcmp(token.string, "-")) + { + SourceError(source, "unexpected punctuation %s", token.string); + return 0; + } //end if + negative = qtrue; + //read the number + if (!PC_ExpectAnyToken(source, &token)) return 0; + } //end if + //check if it is a number + if (token.type != TT_NUMBER) + { + SourceError(source, "expected number, found %s", token.string); + return 0; + } //end if + //check for a float value + if (token.subtype & TT_FLOAT) + { + if ((fd->type & FT_TYPE) != FT_FLOAT) + { + SourceError(source, "unexpected float"); + return 0; + } //end if + floatval = token.floatvalue; + if (negative) floatval = -floatval; + if (fd->type & FT_BOUNDED) + { + if (floatval < fd->floatmin || floatval > fd->floatmax) + { + SourceError(source, "float out of range [%f, %f]", fd->floatmin, fd->floatmax); + return 0; + } //end if + } //end if + *(float *) p = (float) floatval; + return 1; + } //end if + // + intval = token.intvalue; + if (negative) intval = -intval; + //check bounds + if ((fd->type & FT_TYPE) == FT_CHAR) + { + if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 255;} + else {intmin = -128; intmax = 127;} + } //end if + if ((fd->type & FT_TYPE) == FT_INT) + { + if (fd->type & FT_UNSIGNED) {intmin = 0; intmax = 65535;} + else {intmin = -32768; intmax = 32767;} + } //end else if + if ((fd->type & FT_TYPE) == FT_CHAR || (fd->type & FT_TYPE) == FT_INT) + { + if (fd->type & FT_BOUNDED) + { + intmin = Maximum(intmin, fd->floatmin); + intmax = Minimum(intmax, fd->floatmax); + } //end if + if (intval < intmin || intval > intmax) + { + SourceError(source, "value %ld out of range [%ld, %ld]", intval, intmin, intmax); + return 0; + } //end if + } //end if + else if ((fd->type & FT_TYPE) == FT_FLOAT) + { + if (fd->type & FT_BOUNDED) + { + if (intval < fd->floatmin || intval > fd->floatmax) + { + SourceError(source, "value %ld out of range [%f, %f]", intval, fd->floatmin, fd->floatmax); + return 0; + } //end if + } //end if + } //end else if + //store the value + if ((fd->type & FT_TYPE) == FT_CHAR) + { + if (fd->type & FT_UNSIGNED) *(unsigned char *) p = (unsigned char) intval; + else *(char *) p = (char) intval; + } //end if + else if ((fd->type & FT_TYPE) == FT_INT) + { + if (fd->type & FT_UNSIGNED) *(unsigned int *) p = (unsigned int) intval; + else *(int *) p = (int) intval; + } //end else + else if ((fd->type & FT_TYPE) == FT_FLOAT) + { + *(float *) p = (float) intval; + } //end else + return 1; +} //end of the function ReadNumber +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +qboolean ReadChar(source_t *source, fielddef_t *fd, void *p) +{ + token_t token; + + if (!PC_ExpectAnyToken(source, &token)) return 0; + + //take literals into account + if (token.type == TT_LITERAL) + { + StripSingleQuotes(token.string); + *(char *) p = token.string[0]; + } //end if + else + { + PC_UnreadLastToken(source); + if (!ReadNumber(source, fd, p)) return 0; + } //end if + return 1; +} //end of the function ReadChar +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadString(source_t *source, fielddef_t *fd, void *p) +{ + token_t token; + + if (!PC_ExpectTokenType(source, TT_STRING, 0, &token)) return 0; + //remove the double quotes + StripDoubleQuotes(token.string); + //copy the string + strncpy((char *) p, token.string, MAX_STRINGFIELD); + //make sure the string is closed with a zero + ((char *)p)[MAX_STRINGFIELD-1] = '\0'; + // + return 1; +} //end of the function ReadString +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int ReadStructure(source_t *source, structdef_t *def, char *structure) +{ + token_t token; + fielddef_t *fd; + void *p; + int num; + + if (!PC_ExpectTokenString(source, "{")) return 0; + while(1) + { + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + //if end of structure + if (!strcmp(token.string, "}")) break; + //find the field with the name + fd = FindField(def->fields, token.string); + if (!fd) + { + SourceError(source, "unknown structure field %s", token.string); + return qfalse; + } //end if + if (fd->type & FT_ARRAY) + { + num = fd->maxarray; + if (!PC_ExpectTokenString(source, "{")) return qfalse; + } //end if + else + { + num = 1; + } //end else + p = (void *)(structure + fd->offset); + while (num-- > 0) + { + if (fd->type & FT_ARRAY) + { + if (PC_CheckTokenString(source, "}")) break; + } //end if + switch(fd->type & FT_TYPE) + { + case FT_CHAR: + { + if (!ReadChar(source, fd, p)) return qfalse; + p = (char *) p + sizeof(char); + break; + } //end case + case FT_INT: + { + if (!ReadNumber(source, fd, p)) return qfalse; + p = (char *) p + sizeof(int); + break; + } //end case + case FT_FLOAT: + { + if (!ReadNumber(source, fd, p)) return qfalse; + p = (char *) p + sizeof(float); + break; + } //end case + case FT_STRING: + { + if (!ReadString(source, fd, p)) return qfalse; + p = (char *) p + MAX_STRINGFIELD; + break; + } //end case + case FT_STRUCT: + { + if (!fd->substruct) + { + SourceError(source, "BUG: no sub structure defined"); + return qfalse; + } //end if + ReadStructure(source, fd->substruct, (char *) p); + p = (char *) p + fd->substruct->size; + break; + } //end case + } //end switch + if (fd->type & FT_ARRAY) + { + if (!PC_ExpectAnyToken(source, &token)) return qfalse; + if (!strcmp(token.string, "}")) break; + if (strcmp(token.string, ",")) + { + SourceError(source, "expected a comma, found %s", token.string); + return qfalse; + } //end if + } //end if + } //end while + } //end while + return qtrue; +} //end of the function ReadStructure +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteIndent(FILE *fp, int indent) +{ + while(indent-- > 0) + { + if (fprintf(fp, "\t") < 0) return qfalse; + } //end while + return qtrue; +} //end of the function WriteIndent +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteFloat(FILE *fp, float value) +{ + char buf[128]; + int l; + + Com_sprintf(buf, sizeof(buf), "%f", value); + l = strlen(buf); + //strip any trailing zeros + while(l-- > 1) + { + if (buf[l] != '0' && buf[l] != '.') break; + if (buf[l] == '.') + { + buf[l] = 0; + break; + } //end if + buf[l] = 0; + } //end while + //write the float to file + if (fprintf(fp, "%s", buf) < 0) return 0; + return 1; +} //end of the function WriteFloat +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteStructWithIndent(FILE *fp, structdef_t *def, char *structure, int indent) +{ + int i, num; + void *p; + fielddef_t *fd; + + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "{\r\n") < 0) return qfalse; + + indent++; + for (i = 0; def->fields[i].name; i++) + { + fd = &def->fields[i]; + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "%s\t", fd->name) < 0) return qfalse; + p = (void *)(structure + fd->offset); + if (fd->type & FT_ARRAY) + { + num = fd->maxarray; + if (fprintf(fp, "{") < 0) return qfalse; + } //end if + else + { + num = 1; + } //end else + while(num-- > 0) + { + switch(fd->type & FT_TYPE) + { + case FT_CHAR: + { + if (fprintf(fp, "%d", *(char *) p) < 0) return qfalse; + p = (char *) p + sizeof(char); + break; + } //end case + case FT_INT: + { + if (fprintf(fp, "%d", *(int *) p) < 0) return qfalse; + p = (char *) p + sizeof(int); + break; + } //end case + case FT_FLOAT: + { + if (!WriteFloat(fp, *(float *)p)) return qfalse; + p = (char *) p + sizeof(float); + break; + } //end case + case FT_STRING: + { + if (fprintf(fp, "\"%s\"", (char *) p) < 0) return qfalse; + p = (char *) p + MAX_STRINGFIELD; + break; + } //end case + case FT_STRUCT: + { + if (!WriteStructWithIndent(fp, fd->substruct, structure, indent)) return qfalse; + p = (char *) p + fd->substruct->size; + break; + } //end case + } //end switch + if (fd->type & FT_ARRAY) + { + if (num > 0) + { + if (fprintf(fp, ",") < 0) return qfalse; + } //end if + else + { + if (fprintf(fp, "}") < 0) return qfalse; + } //end else + } //end if + } //end while + if (fprintf(fp, "\r\n") < 0) return qfalse; + } //end for + indent--; + + if (!WriteIndent(fp, indent)) return qfalse; + if (fprintf(fp, "}\r\n") < 0) return qfalse; + return qtrue; +} //end of the function WriteStructWithIndent +//=========================================================================== +// +// Parameter: - +// Returns: - +// Changes Globals: - +//=========================================================================== +int WriteStructure(FILE *fp, structdef_t *def, char *structure) +{ + return WriteStructWithIndent(fp, def, structure, 0); +} //end of the function WriteStructure + diff --git a/code/botlib/l_struct.h b/code/botlib/l_struct.h new file mode 100644 index 0000000..e2c6b03 --- /dev/null +++ b/code/botlib/l_struct.h @@ -0,0 +1,75 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_struct.h + * + * desc: structure reading/writing + * + * $Archive: /source/code/botlib/l_struct.h $ + * + *****************************************************************************/ + + +#define MAX_STRINGFIELD 80 +//field types +#define FT_CHAR 1 // char +#define FT_INT 2 // int +#define FT_FLOAT 3 // float +#define FT_STRING 4 // char [MAX_STRINGFIELD] +#define FT_STRUCT 6 // struct (sub structure) +//type only mask +#define FT_TYPE 0x00FF // only type, clear subtype +//sub types +#define FT_ARRAY 0x0100 // array of type +#define FT_BOUNDED 0x0200 // bounded value +#define FT_UNSIGNED 0x0400 + +//structure field definition +typedef struct fielddef_s +{ + char *name; //name of the field + int offset; //offset in the structure + int type; //type of the field + //type specific fields + int maxarray; //maximum array size + float floatmin, floatmax; //float min and max + struct structdef_s *substruct; //sub structure +} fielddef_t; + +//structure definition +typedef struct structdef_s +{ + int size; + fielddef_t *fields; +} structdef_t; + +//read a structure from a script +int ReadStructure(source_t *source, structdef_t *def, char *structure); +//write a structure to a file +int WriteStructure(FILE *fp, structdef_t *def, char *structure); +//writes indents +int WriteIndent(FILE *fp, int indent); +//writes a float without traling zeros +int WriteFloat(FILE *fp, float value); + + diff --git a/code/botlib/l_utils.h b/code/botlib/l_utils.h new file mode 100644 index 0000000..6944d06 --- /dev/null +++ b/code/botlib/l_utils.h @@ -0,0 +1,37 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: l_util.h + * + * desc: utils + * + * $Archive: /source/code/botlib/l_util.h $ + * + *****************************************************************************/ + +#define Vector2Angles(v,a) vectoangles(v,a) +#ifndef MAX_PATH +#define MAX_PATH MAX_QPATH +#endif +#define Maximum(x,y) (x > y ? x : y) +#define Minimum(x,y) (x < y ? x : y) diff --git a/code/botlib/lcc.mak b/code/botlib/lcc.mak new file mode 100644 index 0000000..f5567c3 --- /dev/null +++ b/code/botlib/lcc.mak @@ -0,0 +1,55 @@ +# +# Makefile for Gladiator Bot library: gladiator.dll +# Intended for LCC-Win32 +# + +CC=lcc +CFLAGS=-DC_ONLY -o +OBJS= be_aas_bspq2.obj \ + be_aas_bsphl.obj \ + be_aas_cluster.obj \ + be_aas_debug.obj \ + be_aas_entity.obj \ + be_aas_file.obj \ + be_aas_light.obj \ + be_aas_main.obj \ + be_aas_move.obj \ + be_aas_optimize.obj \ + be_aas_reach.obj \ + be_aas_route.obj \ + be_aas_routealt.obj \ + be_aas_sample.obj \ + be_aas_sound.obj \ + be_ai2_dm.obj \ + be_ai2_dmnet.obj \ + be_ai2_main.obj \ + be_ai_char.obj \ + be_ai_chat.obj \ + be_ai_goal.obj \ + be_ai_load.obj \ + be_ai_move.obj \ + be_ai_weap.obj \ + be_ai_weight.obj \ + be_ea.obj \ + be_interface.obj \ + l_crc.obj \ + l_libvar.obj \ + l_log.obj \ + l_memory.obj \ + l_precomp.obj \ + l_script.obj \ + l_struct.obj \ + l_utils.obj \ + q_shared.obj + +all: gladiator.dll + +gladiator.dll: $(OBJS) + lcclnk -dll -entry GetBotAPI *.obj botlib.def -o gladiator.dll + +clean: + del *.obj gladiator.dll + +%.obj: %.c + $(CC) $(CFLAGS) $< + diff --git a/code/botlib/linux-i386.mak b/code/botlib/linux-i386.mak new file mode 100644 index 0000000..c9607a7 --- /dev/null +++ b/code/botlib/linux-i386.mak @@ -0,0 +1,92 @@ +# +# Makefile for Gladiator Bot library: gladiator.so +# Intended for gcc/Linux +# + +ARCH=i386 +CC=gcc +BASE_CFLAGS=-Dstricmp=strcasecmp + +#use these cflags to optimize it +CFLAGS=$(BASE_CFLAGS) -m486 -O6 -ffast-math -funroll-loops \ + -fomit-frame-pointer -fexpensive-optimizations -malign-loops=2 \ + -malign-jumps=2 -malign-functions=2 +#use these when debugging +#CFLAGS=$(BASE_CFLAGS) -g + +LDFLAGS=-ldl -lm +SHLIBEXT=so +SHLIBCFLAGS=-fPIC +SHLIBLDFLAGS=-shared + +DO_CC=$(CC) $(CFLAGS) $(SHLIBCFLAGS) -o $@ -c $< + +############################################################################# +# SETUP AND BUILD +# GLADIATOR BOT +############################################################################# + +.c.o: + $(DO_CC) + +GAME_OBJS = \ + be_aas_bsphl.o\ + be_aas_bspq2.o\ + be_aas_cluster.o\ + be_aas_debug.o\ + be_aas_entity.o\ + be_aas_file.o\ + be_aas_light.o\ + be_aas_main.o\ + be_aas_move.o\ + be_aas_optimize.o\ + be_aas_reach.o\ + be_aas_route.o\ + be_aas_routealt.o\ + be_aas_sample.o\ + be_aas_sound.o\ + be_ai2_dmq2.o\ + be_ai2_dmhl.o\ + be_ai2_dmnet.o\ + be_ai2_main.o\ + be_ai_char.o\ + be_ai_chat.o\ + be_ai_goal.o\ + be_ai_load.o\ + be_ai_move.o\ + be_ai_weap.o\ + be_ai_weight.o\ + be_ea.o\ + be_interface.o\ + l_crc.o\ + l_libvar.o\ + l_log.o\ + l_memory.o\ + l_precomp.o\ + l_script.o\ + l_struct.o\ + l_utils.o\ + q_shared.o + +glad$(ARCH).$(SHLIBEXT) : $(GAME_OBJS) + $(CC) $(CFLAGS) $(SHLIBLDFLAGS) -o $@ $(GAME_OBJS) + + +############################################################################# +# MISC +############################################################################# + +clean: + -rm -f $(GAME_OBJS) + +depend: + gcc -MM $(GAME_OBJS:.o=.c) + + +install: + cp gladiator.so .. + +# +# From "make depend" +# + diff --git a/code/cgame/animtable.h b/code/cgame/animtable.h new file mode 100644 index 0000000..4772bf2 --- /dev/null +++ b/code/cgame/animtable.h @@ -0,0 +1,1792 @@ +// special file included only by cg_players.cpp & ui_players.cpp +// +// moved it from the original header file for PCH reasons... +// + +#if defined(_XBOX) && !defined(_JK2EXE) && !defined(_UI) // Linker only wants one copy +extern stringID_table_t animTable[MAX_ANIMATIONS+1]; +#else +stringID_table_t animTable [MAX_ANIMATIONS+1] = +{ + //================================================= + //HEAD ANIMS + //================================================= + //# #sep Head-only anims + ENUM2STRING(FACE_TALK0), //# silent + ENUM2STRING(FACE_TALK1), //# quiet + ENUM2STRING(FACE_TALK2), //# semi-quiet + ENUM2STRING(FACE_TALK3), //# semi-loud + ENUM2STRING(FACE_TALK4), //# loud + ENUM2STRING(FACE_ALERT), //# + ENUM2STRING(FACE_SMILE), //# + ENUM2STRING(FACE_FROWN), //# + ENUM2STRING(FACE_DEAD), //# + + //================================================= + //ANIMS IN WHICH UPPER AND LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep ENUM2STRING(BOTH_ DEATHS + ENUM2STRING(BOTH_DEATH1), //# First Death anim + ENUM2STRING(BOTH_DEATH2), //# Second Death anim + ENUM2STRING(BOTH_DEATH3), //# Third Death anim + ENUM2STRING(BOTH_DEATH4), //# Fourth Death anim + ENUM2STRING(BOTH_DEATH5), //# Fifth Death anim + ENUM2STRING(BOTH_DEATH6), //# Sixth Death anim + ENUM2STRING(BOTH_DEATH7), //# Seventh Death anim + ENUM2STRING(BOTH_DEATH8), //# + ENUM2STRING(BOTH_DEATH9), //# + ENUM2STRING(BOTH_DEATH10), //# + ENUM2STRING(BOTH_DEATH11), //# + ENUM2STRING(BOTH_DEATH12), //# + ENUM2STRING(BOTH_DEATH13), //# + ENUM2STRING(BOTH_DEATH14), //# + ENUM2STRING(BOTH_DEATH15), //# + ENUM2STRING(BOTH_DEATH16), //# + ENUM2STRING(BOTH_DEATH17), //# + ENUM2STRING(BOTH_DEATH18), //# + ENUM2STRING(BOTH_DEATH19), //# + ENUM2STRING(BOTH_DEATH20), //# + ENUM2STRING(BOTH_DEATH21), //# + ENUM2STRING(BOTH_DEATH22), //# + ENUM2STRING(BOTH_DEATH23), //# + ENUM2STRING(BOTH_DEATH24), //# + ENUM2STRING(BOTH_DEATH25), //# + + ENUM2STRING(BOTH_DEATHFORWARD1), //# First Death in which they get thrown forward + ENUM2STRING(BOTH_DEATHFORWARD2), //# Second Death in which they get thrown forward + ENUM2STRING(BOTH_DEATHFORWARD3), //# Tavion's falling in cin# 23 + ENUM2STRING(BOTH_DEATHBACKWARD1), //# First Death in which they get thrown backward + ENUM2STRING(BOTH_DEATHBACKWARD2), //# Second Death in which they get thrown backward + + ENUM2STRING(BOTH_DEATH1IDLE), //# Idle while close to death + ENUM2STRING(BOTH_LYINGDEATH1), //# Death to play when killed lying down + ENUM2STRING(BOTH_STUMBLEDEATH1), //# Stumble forward and fall face first death + ENUM2STRING(BOTH_FALLDEATH1), //# Fall forward off a high cliff and splat death - start + ENUM2STRING(BOTH_FALLDEATH1INAIR), //# Fall forward off a high cliff and splat death - loop + ENUM2STRING(BOTH_FALLDEATH1LAND), //# Fall forward off a high cliff and splat death - hit bottom + ENUM2STRING(BOTH_DEATH_ROLL), //# Death anim from a roll + ENUM2STRING(BOTH_DEATH_FLIP), //# Death anim from a flip + ENUM2STRING(BOTH_DEATH_SPIN_90_R), //# Death anim when facing 90 degrees right + ENUM2STRING(BOTH_DEATH_SPIN_90_L), //# Death anim when facing 90 degrees left + ENUM2STRING(BOTH_DEATH_SPIN_180), //# Death anim when facing backwards + ENUM2STRING(BOTH_DEATH_LYING_UP), //# Death anim when lying on back + ENUM2STRING(BOTH_DEATH_LYING_DN), //# Death anim when lying on front + ENUM2STRING(BOTH_DEATH_FALLING_DN), //# Death anim when falling on face + ENUM2STRING(BOTH_DEATH_FALLING_UP), //# Death anim when falling on back + ENUM2STRING(BOTH_DEATH_CROUCHED), //# Death anim when crouched + //# #sep ENUM2STRING(BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + ENUM2STRING(BOTH_DEAD1), //# First Death finished pose + ENUM2STRING(BOTH_DEAD2), //# Second Death finished pose + ENUM2STRING(BOTH_DEAD3), //# Third Death finished pose + ENUM2STRING(BOTH_DEAD4), //# Fourth Death finished pose + ENUM2STRING(BOTH_DEAD5), //# Fifth Death finished pose + ENUM2STRING(BOTH_DEAD6), //# Sixth Death finished pose + ENUM2STRING(BOTH_DEAD7), //# Seventh Death finished pose + ENUM2STRING(BOTH_DEAD8), //# + ENUM2STRING(BOTH_DEAD9), //# + ENUM2STRING(BOTH_DEAD10), //# + ENUM2STRING(BOTH_DEAD11), //# + ENUM2STRING(BOTH_DEAD12), //# + ENUM2STRING(BOTH_DEAD13), //# + ENUM2STRING(BOTH_DEAD14), //# + ENUM2STRING(BOTH_DEAD15), //# + ENUM2STRING(BOTH_DEAD16), //# + ENUM2STRING(BOTH_DEAD17), //# + ENUM2STRING(BOTH_DEAD18), //# + ENUM2STRING(BOTH_DEAD19), //# + ENUM2STRING(BOTH_DEAD20), //# + ENUM2STRING(BOTH_DEAD21), //# + ENUM2STRING(BOTH_DEAD22), //# + ENUM2STRING(BOTH_DEAD23), //# + ENUM2STRING(BOTH_DEAD24), //# + ENUM2STRING(BOTH_DEAD25), //# + ENUM2STRING(BOTH_DEADFORWARD1), //# First thrown forward death finished pose + ENUM2STRING(BOTH_DEADFORWARD2), //# Second thrown forward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD1), //# First thrown backward death finished pose + ENUM2STRING(BOTH_DEADBACKWARD2), //# Second thrown backward death finished pose + ENUM2STRING(BOTH_LYINGDEAD1), //# Killed lying down death finished pose + ENUM2STRING(BOTH_STUMBLEDEAD1), //# Stumble forward death finished pose + ENUM2STRING(BOTH_FALLDEAD1LAND), //# Fall forward and splat death finished pose + //# #sep ENUM2STRING(BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + ENUM2STRING(BOTH_DEADFLOP1), //# React to being shot from First Death finished pose + ENUM2STRING(BOTH_DEADFLOP2), //# React to being shot from Second Death finished pose + ENUM2STRING(BOTH_DISMEMBER_HEAD1), //# + ENUM2STRING(BOTH_DISMEMBER_TORSO1), //# + ENUM2STRING(BOTH_DISMEMBER_LLEG), //# + ENUM2STRING(BOTH_DISMEMBER_RLEG), //# + ENUM2STRING(BOTH_DISMEMBER_RARM), //# + ENUM2STRING(BOTH_DISMEMBER_LARM), //# + //# #sep ENUM2STRING(BOTH_ PAINS + ENUM2STRING(BOTH_PAIN1), //# First take pain anim + ENUM2STRING(BOTH_PAIN2), //# Second take pain anim + ENUM2STRING(BOTH_PAIN3), //# Third take pain anim + ENUM2STRING(BOTH_PAIN4), //# Fourth take pain anim + ENUM2STRING(BOTH_PAIN5), //# Fifth take pain anim - from behind + ENUM2STRING(BOTH_PAIN6), //# Sixth take pain anim - from behind + ENUM2STRING(BOTH_PAIN7), //# Seventh take pain anim - from behind + ENUM2STRING(BOTH_PAIN8), //# Eigth take pain anim - from behind + ENUM2STRING(BOTH_PAIN9), //# + ENUM2STRING(BOTH_PAIN10), //# + ENUM2STRING(BOTH_PAIN11), //# + ENUM2STRING(BOTH_PAIN12), //# + ENUM2STRING(BOTH_PAIN13), //# + ENUM2STRING(BOTH_PAIN14), //# + ENUM2STRING(BOTH_PAIN15), //# + ENUM2STRING(BOTH_PAIN16), //# + ENUM2STRING(BOTH_PAIN17), //# + ENUM2STRING(BOTH_PAIN18), //# + + //# #sep ENUM2STRING(BOTH_ ATTACKS + ENUM2STRING(BOTH_ATTACK1), //# Attack with stun baton + ENUM2STRING(BOTH_ATTACK2), //# Attack with one-handed pistol + ENUM2STRING(BOTH_ATTACK3), //# Attack with blaster rifle + ENUM2STRING(BOTH_ATTACK4), //# Attack with disruptor + ENUM2STRING(BOTH_ATTACK5), //# Another Rancor Attack + ENUM2STRING(BOTH_ATTACK6), //# Yet Another Rancor Attack + ENUM2STRING(BOTH_ATTACK7), //# Yet Another Rancor Attack + ENUM2STRING(BOTH_ATTACK10), //# Attack with thermal det + ENUM2STRING(BOTH_ATTACK11), //# "Attack" with tripmine and detpack + ENUM2STRING(BOTH_MELEE1), //# First melee attack + ENUM2STRING(BOTH_MELEE2), //# Second melee attack + ENUM2STRING(BOTH_THERMAL_READY), //# pull back with thermal + ENUM2STRING(BOTH_THERMAL_THROW), //# throw thermal + //* #sep ENUM2STRING(BOTH_ SABER ANIMS + //Saber attack anims - power level 1 + ENUM2STRING(BOTH_A1_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A1__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A1__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A1_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A1_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A1_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A1_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T1_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T1_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T1_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T1_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T1__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T1__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T1__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T1__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T1_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T1_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T1_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T1_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T1_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T1_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T1_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T1_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T1_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T1_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T1_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T1_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T1__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T1__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T1__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T1_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T1_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T1_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T1_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T1_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T1_TR_BR) + ENUM2STRING(BOTH_T1_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T1_T__BR) + ENUM2STRING(BOTH_T1__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T1_BR__R) + ENUM2STRING(BOTH_T1__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T1_T___R) + ENUM2STRING(BOTH_T1_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T1__R_TR) + ENUM2STRING(BOTH_T1_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T1_T__TR) + ENUM2STRING(BOTH_T1_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T1__R_TL) + ENUM2STRING(BOTH_T1_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T1_TR_TL) + ENUM2STRING(BOTH_T1_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T1_T__TL) + ENUM2STRING(BOTH_T1_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T1__L_TL) + ENUM2STRING(BOTH_T1__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T1_TR__L) + ENUM2STRING(BOTH_T1__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T1_T___L) + ENUM2STRING(BOTH_T1__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T1_BL__L) + ENUM2STRING(BOTH_T1_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T1_T__BL) + ENUM2STRING(BOTH_T1_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T1_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S1_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S1_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S1_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S1_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S1_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S1_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S1_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R1_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R1__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R1__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R1_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B1_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B1__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B1_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B1_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B1_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B1__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B1_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D1_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D1__R___), //# Deflection toward R + ENUM2STRING(BOTH_D1_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D1_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D1__L___), //# Deflection toward L + ENUM2STRING(BOTH_D1_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D1_B____), //# Deflection toward B + //Saber attack anims - power level 2 + ENUM2STRING(BOTH_A2_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A2__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A2__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A2_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A2_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A2_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A2_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T2_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T2_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T2_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T2_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T2__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T2__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T2__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T2__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T2_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T2_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T2_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T2_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T2_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T2_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T2_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T2_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T2_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T2_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T2_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T2_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T2__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T2__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T2__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T2_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T2_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T2_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T2_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T2_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T2_TR_BR) + ENUM2STRING(BOTH_T2_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T2_T__BR) + ENUM2STRING(BOTH_T2__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T2_BR__R) + ENUM2STRING(BOTH_T2__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T2_T___R) + ENUM2STRING(BOTH_T2_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T2__R_TR) + ENUM2STRING(BOTH_T2_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T2_T__TR) + ENUM2STRING(BOTH_T2_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T2__R_TL) + ENUM2STRING(BOTH_T2_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T2_TR_TL) + ENUM2STRING(BOTH_T2_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T2_T__TL) + ENUM2STRING(BOTH_T2_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T2__L_TL) + ENUM2STRING(BOTH_T2__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T2_TR__L) + ENUM2STRING(BOTH_T2__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T2_T___L) + ENUM2STRING(BOTH_T2__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T2_BL__L) + ENUM2STRING(BOTH_T2_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T2_T__BL) + ENUM2STRING(BOTH_T2_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T2_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S2_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S2_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S2_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S2_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S2_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S2_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S2_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R2_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R2__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R2__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R2_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B2_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B2__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B2_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B2_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B2_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B2__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B2_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D2_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D2__R___), //# Deflection toward R + ENUM2STRING(BOTH_D2_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D2_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D2__L___), //# Deflection toward L + ENUM2STRING(BOTH_D2_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D2_B____), //# Deflection toward B + //Saber attack anims - power level 3 + ENUM2STRING(BOTH_A3_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A3__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A3__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A3_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A3_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A3_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A3_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T3_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T3_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T3_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T3_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T3__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T3__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T3__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T3__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T3_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T3_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T3_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T3_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T3_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T3_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T3_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T3_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T3_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T3_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T3_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T3_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T3__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T3__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T3__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T3_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T3_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T3_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T3_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T3_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T3_TR_BR) + ENUM2STRING(BOTH_T3_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T3_T__BR) + ENUM2STRING(BOTH_T3__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T3_BR__R) + ENUM2STRING(BOTH_T3__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T3_T___R) + ENUM2STRING(BOTH_T3_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T3__R_TR) + ENUM2STRING(BOTH_T3_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T3_T__TR) + ENUM2STRING(BOTH_T3_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T3__R_TL) + ENUM2STRING(BOTH_T3_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T3_TR_TL) + ENUM2STRING(BOTH_T3_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T3_T__TL) + ENUM2STRING(BOTH_T3_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T3__L_TL) + ENUM2STRING(BOTH_T3__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T3_TR__L) + ENUM2STRING(BOTH_T3__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T3_T___L) + ENUM2STRING(BOTH_T3__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T3_BL__L) + ENUM2STRING(BOTH_T3_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T3_T__BL) + ENUM2STRING(BOTH_T3_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T3_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S3_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S3_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S3_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S3_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S3_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S3_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S3_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R3_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R3__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R3__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R3_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B3_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B3__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B3_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B3_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B3_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B3__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B3_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D3_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D3__R___), //# Deflection toward R + ENUM2STRING(BOTH_D3_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D3_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D3__L___), //# Deflection toward L + ENUM2STRING(BOTH_D3_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D3_B____), //# Deflection toward B + //Saber attack anims - power level 4 - Desann's + ENUM2STRING(BOTH_A4_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A4__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A4__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A4_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A4_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A4_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A4_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T4_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T4_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T4_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T4_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T4__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T4__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T4__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T4__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T4_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T4_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T4_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T4_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T4_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T4_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T4_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T4_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T4_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T4_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T4_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T4_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T4__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T4__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T4__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T4_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T4_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T4_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T4_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T4_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T4_TR_BR) + ENUM2STRING(BOTH_T4_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T4_T__BR) + ENUM2STRING(BOTH_T4__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T4_BR__R) + ENUM2STRING(BOTH_T4__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T4_T___R) + ENUM2STRING(BOTH_T4_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T4__R_TR) + ENUM2STRING(BOTH_T4_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T4_T__TR) + ENUM2STRING(BOTH_T4_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T4__R_TL) + ENUM2STRING(BOTH_T4_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T4_TR_TL) + ENUM2STRING(BOTH_T4_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T4_T__TL) + ENUM2STRING(BOTH_T4_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T4__L_TL) + ENUM2STRING(BOTH_T4__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T4_TR__L) + ENUM2STRING(BOTH_T4__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T4_T___L) + ENUM2STRING(BOTH_T4__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T4_BL__L) + ENUM2STRING(BOTH_T4_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T4_T__BL) + ENUM2STRING(BOTH_T4_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T4_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S4_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S4_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S4_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S4_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S4_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S4_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S4_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R4_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R4__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R4__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R4_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B4_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B4__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B4_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B4_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B4_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B4__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B4_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D4_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D4__R___), //# Deflection toward R + ENUM2STRING(BOTH_D4_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D4_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D4__L___), //# Deflection toward L + ENUM2STRING(BOTH_D4_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D4_B____), //# Deflection toward B + //Saber attack anims - power level 5 - Tavion's + ENUM2STRING(BOTH_A5_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A5__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A5__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A5_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A5_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A5_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A5_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T5_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T5_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T5_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T5_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T5__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T5__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T5__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T5__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T5_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T5_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T5_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T5_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T5_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T5_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T5_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T5_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T5_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T5_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T5_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T5_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T5__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T5__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T5__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T5_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T5_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T5_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T5_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T5_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T5_TR_BR) + ENUM2STRING(BOTH_T5_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T5_T__BR) + ENUM2STRING(BOTH_T5__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T5_BR__R) + ENUM2STRING(BOTH_T5__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T5_T___R) + ENUM2STRING(BOTH_T5_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T5__R_TR) + ENUM2STRING(BOTH_T5_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T5_T__TR) + ENUM2STRING(BOTH_T5_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T5__R_TL) + ENUM2STRING(BOTH_T5_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T5_TR_TL) + ENUM2STRING(BOTH_T5_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T5_T__TL) + ENUM2STRING(BOTH_T5_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T5__L_TL) + ENUM2STRING(BOTH_T5__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T5_TR__L) + ENUM2STRING(BOTH_T5__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T5_T___L) + ENUM2STRING(BOTH_T5__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T5_BL__L) + ENUM2STRING(BOTH_T5_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T5_T__BL) + ENUM2STRING(BOTH_T5_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T5_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S5_S1_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S5_S1__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S5_S1__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S5_S1_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S5_S1_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S5_S1_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S5_S1_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R5_B__S1), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R5__L_S1), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R5__R_S1), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_TL_S1), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_BR_S1), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_BL_S1), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R5_TR_S1), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B5_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B5__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B5_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B5_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B5_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B5__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B5_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D5_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D5__R___), //# Deflection toward R + ENUM2STRING(BOTH_D5_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D5_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D5__L___), //# Deflection toward L + ENUM2STRING(BOTH_D5_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D5_B____), //# Deflection toward B + //Saber attack anims - power level 6 + ENUM2STRING(BOTH_A6_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A6__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A6__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A6_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A6_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A6_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A6_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T6_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T6_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T6_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T6_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T6__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T6__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T6__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T6__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T6_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T6_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T6_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T6_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T6_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T6_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T6_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T6_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T6_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T6_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T6_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T6_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T6__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T6__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T6__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T6_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T6_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T6_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T6_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T6_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T6_TR_BR) + ENUM2STRING(BOTH_T6_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T6_T__BR) + ENUM2STRING(BOTH_T6__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T6_BR__R) + ENUM2STRING(BOTH_T6__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T6_T___R) + ENUM2STRING(BOTH_T6_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T6__R_TR) + ENUM2STRING(BOTH_T6_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T6_T__TR) + ENUM2STRING(BOTH_T6_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T6__R_TL) + ENUM2STRING(BOTH_T6_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T6_TR_TL) + ENUM2STRING(BOTH_T6_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T6_T__TL) + ENUM2STRING(BOTH_T6_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T6__L_TL) + ENUM2STRING(BOTH_T6__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T6_TR__L) + ENUM2STRING(BOTH_T6__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T6_T___L) + ENUM2STRING(BOTH_T6__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T6_BL__L) + ENUM2STRING(BOTH_T6_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T6_T__BL) + ENUM2STRING(BOTH_T6_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T6_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S6_S6_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S6_S6__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S6_S6__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S6_S6_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S6_S6_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S6_S6_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S6_S6_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R6_B__S6), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R6__L_S6), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R6__R_S6), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_TL_S6), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_BR_S6), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_BL_S6), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R6_TR_S6), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B6_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B6__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B6_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B6_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B6_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B6__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B6_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D6_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D6__R___), //# Deflection toward R + ENUM2STRING(BOTH_D6_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D6_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D6__L___), //# Deflection toward L + ENUM2STRING(BOTH_D6_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D6_B____), //# Deflection toward B + //Saber attack anims - power level 7 + ENUM2STRING(BOTH_A7_T__B_), //# Fast weak vertical attack top to bottom + ENUM2STRING(BOTH_A7__L__R), //# Fast weak horizontal attack left to right + ENUM2STRING(BOTH_A7__R__L), //# Fast weak horizontal attack right to left + ENUM2STRING(BOTH_A7_TL_BR), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A7_BR_TL), //# Fast weak diagonal attack top left to botom right + ENUM2STRING(BOTH_A7_BL_TR), //# Fast weak diagonal attack bottom left to top right + ENUM2STRING(BOTH_A7_TR_BL), //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + ENUM2STRING(BOTH_T7_BR__R), //# Fast arc bottom right to right + ENUM2STRING(BOTH_T7_BR_TL), //# Fast weak spin bottom right to top left + ENUM2STRING(BOTH_T7_BR__L), //# Fast weak spin bottom right to left + ENUM2STRING(BOTH_T7_BR_BL), //# Fast weak spin bottom right to bottom left + ENUM2STRING(BOTH_T7__R_TR), //# Fast arc right to top right + ENUM2STRING(BOTH_T7__R_TL), //# Fast arc right to top left + ENUM2STRING(BOTH_T7__R__L), //# Fast weak spin right to left + ENUM2STRING(BOTH_T7__R_BL), //# Fast weak spin right to bottom left + ENUM2STRING(BOTH_T7_TR_BR), //# Fast arc top right to bottom right + ENUM2STRING(BOTH_T7_TR_TL), //# Fast arc top right to top left + ENUM2STRING(BOTH_T7_TR__L), //# Fast arc top right to left + ENUM2STRING(BOTH_T7_TR_BL), //# Fast weak spin top right to bottom left + ENUM2STRING(BOTH_T7_T__BR), //# Fast arc top to bottom right + ENUM2STRING(BOTH_T7_T___R), //# Fast arc top to right + ENUM2STRING(BOTH_T7_T__TR), //# Fast arc top to top right + ENUM2STRING(BOTH_T7_T__TL), //# Fast arc top to top left + ENUM2STRING(BOTH_T7_T___L), //# Fast arc top to left + ENUM2STRING(BOTH_T7_T__BL), //# Fast arc top to bottom left + ENUM2STRING(BOTH_T7_TL_BR), //# Fast weak spin top left to bottom right + ENUM2STRING(BOTH_T7_TL_BL), //# Fast arc top left to bottom left + ENUM2STRING(BOTH_T7__L_BR), //# Fast weak spin left to bottom right + ENUM2STRING(BOTH_T7__L__R), //# Fast weak spin left to right + ENUM2STRING(BOTH_T7__L_TL), //# Fast arc left to top left + ENUM2STRING(BOTH_T7_BL_BR), //# Fast weak spin bottom left to bottom right + ENUM2STRING(BOTH_T7_BL__R), //# Fast weak spin bottom left to right + ENUM2STRING(BOTH_T7_BL_TR), //# Fast weak spin bottom left to top right + ENUM2STRING(BOTH_T7_BL__L), //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + ENUM2STRING(BOTH_T7_BR_TR), //# Fast arc bottom right to top right (use: ENUM2STRING(BOTH_T7_TR_BR) + ENUM2STRING(BOTH_T7_BR_T_), //# Fast arc bottom right to top (use: ENUM2STRING(BOTH_T7_T__BR) + ENUM2STRING(BOTH_T7__R_BR), //# Fast arc right to bottom right (use: ENUM2STRING(BOTH_T7_BR__R) + ENUM2STRING(BOTH_T7__R_T_), //# Fast ar right to top (use: ENUM2STRING(BOTH_T7_T___R) + ENUM2STRING(BOTH_T7_TR__R), //# Fast arc top right to right (use: ENUM2STRING(BOTH_T7__R_TR) + ENUM2STRING(BOTH_T7_TR_T_), //# Fast arc top right to top (use: ENUM2STRING(BOTH_T7_T__TR) + ENUM2STRING(BOTH_T7_TL__R), //# Fast arc top left to right (use: ENUM2STRING(BOTH_T7__R_TL) + ENUM2STRING(BOTH_T7_TL_TR), //# Fast arc top left to top right (use: ENUM2STRING(BOTH_T7_TR_TL) + ENUM2STRING(BOTH_T7_TL_T_), //# Fast arc top left to top (use: ENUM2STRING(BOTH_T7_T__TL) + ENUM2STRING(BOTH_T7_TL__L), //# Fast arc top left to left (use: ENUM2STRING(BOTH_T7__L_TL) + ENUM2STRING(BOTH_T7__L_TR), //# Fast arc left to top right (use: ENUM2STRING(BOTH_T7_TR__L) + ENUM2STRING(BOTH_T7__L_T_), //# Fast arc left to top (use: ENUM2STRING(BOTH_T7_T___L) + ENUM2STRING(BOTH_T7__L_BL), //# Fast arc left to bottom left (use: ENUM2STRING(BOTH_T7_BL__L) + ENUM2STRING(BOTH_T7_BL_T_), //# Fast arc bottom left to top (use: ENUM2STRING(BOTH_T7_T__BL) + ENUM2STRING(BOTH_T7_BL_TL), //# Fast arc bottom left to top left (use: ENUM2STRING(BOTH_T7_TL_BL) + //Saber Attack Start Transitions + ENUM2STRING(BOTH_S7_S7_T_), //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + ENUM2STRING(BOTH_S7_S7__L), //# Fast plain transition from stance1 to left-to-right Fast weak attack + ENUM2STRING(BOTH_S7_S7__R), //# Fast plain transition from stance1 to right-to-left Fast weak attack + ENUM2STRING(BOTH_S7_S7_TL), //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + ENUM2STRING(BOTH_S7_S7_BR), //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + ENUM2STRING(BOTH_S7_S7_BL), //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + ENUM2STRING(BOTH_S7_S7_TR), //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + ENUM2STRING(BOTH_R7_B__S7), //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + ENUM2STRING(BOTH_R7__L_S7), //# Fast plain transition from left-to-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R7__R_S7), //# Fast plain transition from right-to-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_TL_S7), //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_BR_S7), //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_BL_S7), //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + ENUM2STRING(BOTH_R7_TR_S7), //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack), played backwards) + ENUM2STRING(BOTH_B7_BR___), //# Bounce-back if attack from BR is blocked + ENUM2STRING(BOTH_B7__R___), //# Bounce-back if attack from R is blocked + ENUM2STRING(BOTH_B7_TR___), //# Bounce-back if attack from TR is blocked + ENUM2STRING(BOTH_B7_T____), //# Bounce-back if attack from T is blocked + ENUM2STRING(BOTH_B7_TL___), //# Bounce-back if attack from TL is blocked + ENUM2STRING(BOTH_B7__L___), //# Bounce-back if attack from L is blocked + ENUM2STRING(BOTH_B7_BL___), //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + ENUM2STRING(BOTH_D7_BR___), //# Deflection toward BR + ENUM2STRING(BOTH_D7__R___), //# Deflection toward R + ENUM2STRING(BOTH_D7_TR___), //# Deflection toward TR + ENUM2STRING(BOTH_D7_TL___), //# Deflection toward TL + ENUM2STRING(BOTH_D7__L___), //# Deflection toward L + ENUM2STRING(BOTH_D7_BL___), //# Deflection toward BL + ENUM2STRING(BOTH_D7_B____), //# Deflection toward B + //Saber parry anims + ENUM2STRING(BOTH_P1_S1_T_), //# Block shot/saber top + ENUM2STRING(BOTH_P1_S1_TR), //# Block shot/saber top right + ENUM2STRING(BOTH_P1_S1_TL), //# Block shot/saber top left + ENUM2STRING(BOTH_P1_S1_BL), //# Block shot/saber bottom left + ENUM2STRING(BOTH_P1_S1_BR), //# Block shot/saber bottom right + //Saber knockaway + ENUM2STRING(BOTH_K1_S1_T_), //# knockaway saber top + ENUM2STRING(BOTH_K1_S1_TR), //# knockaway saber top right + ENUM2STRING(BOTH_K1_S1_TL), //# knockaway saber top left + ENUM2STRING(BOTH_K1_S1_BL), //# knockaway saber bottom left + ENUM2STRING(BOTH_K1_S1_B_), //# knockaway saber bottom + ENUM2STRING(BOTH_K1_S1_BR), //# knockaway saber bottom right + //Saber attack knocked away + ENUM2STRING(BOTH_V1_BR_S1), //# BR attack knocked away + ENUM2STRING(BOTH_V1__R_S1), //# R attack knocked away + ENUM2STRING(BOTH_V1_TR_S1), //# TR attack knocked away + ENUM2STRING(BOTH_V1_T__S1), //# T attack knocked away + ENUM2STRING(BOTH_V1_TL_S1), //# TL attack knocked away + ENUM2STRING(BOTH_V1__L_S1), //# L attack knocked away + ENUM2STRING(BOTH_V1_BL_S1), //# BL attack knocked away + ENUM2STRING(BOTH_V1_B__S1), //# B attack knocked away + //Saber parry broken + ENUM2STRING(BOTH_H1_S1_T_), //# saber knocked down from top parry + ENUM2STRING(BOTH_H1_S1_TR), //# saber knocked down-left from TR parry + ENUM2STRING(BOTH_H1_S1_TL), //# saber knocked down-right from TL parry + ENUM2STRING(BOTH_H1_S1_BL), //# saber knocked up-right from BL parry + ENUM2STRING(BOTH_H1_S1_B_), //# saber knocked up over head from ready? + ENUM2STRING(BOTH_H1_S1_BR), //# saber knocked up-left from BR parry + //Dual Sabers parry anims + ENUM2STRING(BOTH_P6_S6_T_), //# Block shot/saber top + ENUM2STRING(BOTH_P6_S6_TR), //# Block shot/saber top right + ENUM2STRING(BOTH_P6_S6_TL), //# Block shot/saber top left + ENUM2STRING(BOTH_P6_S6_BL), //# Block shot/saber bottom left + ENUM2STRING(BOTH_P6_S6_BR), //# Block shot/saber bottom right + //Dual Sabers knockaway + ENUM2STRING(BOTH_K6_S6_T_), //# knockaway saber top + ENUM2STRING(BOTH_K6_S6_TR), //# knockaway saber top right + ENUM2STRING(BOTH_K6_S6_TL), //# knockaway saber top left + ENUM2STRING(BOTH_K6_S6_BL), //# knockaway saber bottom left + ENUM2STRING(BOTH_K6_S6_B_), //# knockaway saber bottom + ENUM2STRING(BOTH_K6_S6_BR), //# knockaway saber bottom right + //Dual Sabers attack knocked away + ENUM2STRING(BOTH_V6_BR_S6), //# BR attack knocked away + ENUM2STRING(BOTH_V6__R_S6), //# R attack knocked away + ENUM2STRING(BOTH_V6_TR_S6), //# TR attack knocked away + ENUM2STRING(BOTH_V6_T__S6), //# T attack knocked away + ENUM2STRING(BOTH_V6_TL_S6), //# TL attack knocked away + ENUM2STRING(BOTH_V6__L_S6), //# L attack knocked away + ENUM2STRING(BOTH_V6_BL_S6), //# BL attack knocked away + ENUM2STRING(BOTH_V6_B__S6), //# B attack knocked away + //Dual Sabers parry broken + ENUM2STRING(BOTH_H6_S6_T_), //# saber knocked down from top parry + ENUM2STRING(BOTH_H6_S6_TR), //# saber knocked down-left from TR parry + ENUM2STRING(BOTH_H6_S6_TL), //# saber knocked down-right from TL parry + ENUM2STRING(BOTH_H6_S6_BL), //# saber knocked up-right from BL parry + ENUM2STRING(BOTH_H6_S6_B_), //# saber knocked up over head from ready? + ENUM2STRING(BOTH_H6_S6_BR), //# saber knocked up-left from BR parry + //SaberStaff parry anims + ENUM2STRING(BOTH_P7_S7_T_), //# Block shot/saber top + ENUM2STRING(BOTH_P7_S7_TR), //# Block shot/saber top right + ENUM2STRING(BOTH_P7_S7_TL), //# Block shot/saber top left + ENUM2STRING(BOTH_P7_S7_BL), //# Block shot/saber bottom left + ENUM2STRING(BOTH_P7_S7_BR), //# Block shot/saber bottom right + //SaberStaff knockaway + ENUM2STRING(BOTH_K7_S7_T_), //# knockaway saber top + ENUM2STRING(BOTH_K7_S7_TR), //# knockaway saber top right + ENUM2STRING(BOTH_K7_S7_TL), //# knockaway saber top left + ENUM2STRING(BOTH_K7_S7_BL), //# knockaway saber bottom left + ENUM2STRING(BOTH_K7_S7_B_), //# knockaway saber bottom + ENUM2STRING(BOTH_K7_S7_BR), //# knockaway saber bottom right + //SaberStaff attack knocked away + ENUM2STRING(BOTH_V7_BR_S7), //# BR attack knocked away + ENUM2STRING(BOTH_V7__R_S7), //# R attack knocked away + ENUM2STRING(BOTH_V7_TR_S7), //# TR attack knocked away + ENUM2STRING(BOTH_V7_T__S7), //# T attack knocked away + ENUM2STRING(BOTH_V7_TL_S7), //# TL attack knocked away + ENUM2STRING(BOTH_V7__L_S7), //# L attack knocked away + ENUM2STRING(BOTH_V7_BL_S7), //# BL attack knocked away + ENUM2STRING(BOTH_V7_B__S7), //# B attack knocked away + //SaberStaff parry broken + ENUM2STRING(BOTH_H7_S7_T_), //# saber knocked down from top parry + ENUM2STRING(BOTH_H7_S7_TR), //# saber knocked down-left from TR parry + ENUM2STRING(BOTH_H7_S7_TL), //# saber knocked down-right from TL parry + ENUM2STRING(BOTH_H7_S7_BL), //# saber knocked up-right from BL parry + ENUM2STRING(BOTH_H7_S7_B_), //# saber knocked up over head from ready? + ENUM2STRING(BOTH_H7_S7_BR), //# saber knocked up-left from BR parry + //Sabers locked anims + //* #sep BOTH_ SABER LOCKED ANIMS + //BOTH_(DL, S, ST)_(DL, S, ST)_(T, S)_(L, B, SB)_1(_W, _L) +//===Single locks================================================================== +//SINGLE vs. DUAL + //side locks - I'm using a single and they're using dual + ENUM2STRING(BOTH_LK_S_DL_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_DL_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_DL_S_L_1), //lock if I'm using single vs. a dual + ENUM2STRING(BOTH_LK_S_DL_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_DL_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_S_DL_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_DL_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_DL_T_L_1), //lock if I'm using single vs. a dual + ENUM2STRING(BOTH_LK_S_DL_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_DL_T_SB_1_W), //super break I won +//SINGLE vs. STAFF + //side locks + ENUM2STRING(BOTH_LK_S_ST_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_ST_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_ST_S_L_1), //lock if I'm using single vs. a staff + ENUM2STRING(BOTH_LK_S_ST_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_ST_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_S_ST_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_ST_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_ST_T_L_1), //lock if I'm using single vs. a staff + ENUM2STRING(BOTH_LK_S_ST_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_ST_T_SB_1_W), //super break I won +//SINGLE vs. SINGLE + //side locks + ENUM2STRING(BOTH_LK_S_S_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_S_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_S_S_L_1), //lock if I'm using single vs. a single and I initiated + ENUM2STRING(BOTH_LK_S_S_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_S_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_S_S_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_S_S_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_S_S_T_L_1), //lock if I'm using single vs. a single and I initiated + ENUM2STRING(BOTH_LK_S_S_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_S_S_T_SB_1_W), //super break I won +//===Dual Saber locks================================================================== +//DUAL vs. DUAL + //side locks + ENUM2STRING(BOTH_LK_DL_DL_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_DL_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_DL_S_L_1), //lock if I'm using dual vs. dual and I initiated + ENUM2STRING(BOTH_LK_DL_DL_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_DL_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_DL_DL_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_DL_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_DL_T_L_1), //lock if I'm using dual vs. dual and I initiated + ENUM2STRING(BOTH_LK_DL_DL_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_DL_T_SB_1_W), //super break I won +//DUAL vs. STAFF + //side locks + ENUM2STRING(BOTH_LK_DL_ST_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_ST_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_ST_S_L_1), //lock if I'm using dual vs. a staff + ENUM2STRING(BOTH_LK_DL_ST_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_ST_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_DL_ST_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_ST_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_ST_T_L_1), //lock if I'm using dual vs. a staff + ENUM2STRING(BOTH_LK_DL_ST_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_ST_T_SB_1_W), //super break I won +//DUAL vs. SINGLE + //side locks + ENUM2STRING(BOTH_LK_DL_S_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_S_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_S_S_L_1), //lock if I'm using dual vs. a single + ENUM2STRING(BOTH_LK_DL_S_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_S_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_DL_S_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_DL_S_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_DL_S_T_L_1), //lock if I'm using dual vs. a single + ENUM2STRING(BOTH_LK_DL_S_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_DL_S_T_SB_1_W), //super break I won +//===Saber Staff locks================================================================== +//STAFF vs. DUAL + //side locks + ENUM2STRING(BOTH_LK_ST_DL_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_DL_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_DL_S_L_1), //lock if I'm using staff vs. dual + ENUM2STRING(BOTH_LK_ST_DL_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_DL_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_ST_DL_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_DL_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_DL_T_L_1), //lock if I'm using staff vs. dual + ENUM2STRING(BOTH_LK_ST_DL_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_DL_T_SB_1_W), //super break I won +//STAFF vs. STAFF + //side locks + ENUM2STRING(BOTH_LK_ST_ST_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_ST_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_ST_S_L_1), //lock if I'm using staff vs. a staff and I initiated + ENUM2STRING(BOTH_LK_ST_ST_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_ST_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_ST_ST_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_ST_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_ST_T_L_1), //lock if I'm using staff vs. a staff and I initiated + ENUM2STRING(BOTH_LK_ST_ST_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_ST_T_SB_1_W), //super break I won +//STAFF vs. SINGLE + //side locks + ENUM2STRING(BOTH_LK_ST_S_S_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_S_S_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_S_S_L_1), //lock if I'm using staff vs. a single + ENUM2STRING(BOTH_LK_ST_S_S_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_S_S_SB_1_W), //super break I won + //top locks + ENUM2STRING(BOTH_LK_ST_S_T_B_1_L), //normal break I lost + ENUM2STRING(BOTH_LK_ST_S_T_B_1_W), //normal break I won + ENUM2STRING(BOTH_LK_ST_S_T_L_1), //lock if I'm using staff vs. a single + ENUM2STRING(BOTH_LK_ST_S_T_SB_1_L), //super break I lost + ENUM2STRING(BOTH_LK_ST_S_T_SB_1_W), //super break I won +//Special cases for same saber style vs. each other (won't fit in nice 5-anim size lists above) + ENUM2STRING(BOTH_LK_S_S_S_L_2), //lock if I'm using single vs. a single and other intitiated + ENUM2STRING(BOTH_LK_S_S_T_L_2), //lock if I'm using single vs. a single and other initiated + ENUM2STRING(BOTH_LK_DL_DL_S_L_2), //lock if I'm using dual vs. dual and other initiated + ENUM2STRING(BOTH_LK_DL_DL_T_L_2), //lock if I'm using dual vs. dual and other initiated + ENUM2STRING(BOTH_LK_ST_ST_S_L_2), //lock if I'm using staff vs. a staff and other initiated + ENUM2STRING(BOTH_LK_ST_ST_T_L_2), //lock if I'm using staff vs. a staff and other initiated +//===End Saber locks================================================================== + ENUM2STRING(BOTH_BF2RETURN), //# + ENUM2STRING(BOTH_BF2BREAK), //# + ENUM2STRING(BOTH_BF2LOCK), //# + ENUM2STRING(BOTH_BF1RETURN), //# + ENUM2STRING(BOTH_BF1BREAK), //# + ENUM2STRING(BOTH_BF1LOCK), //# + ENUM2STRING(BOTH_CWCIRCLE_R2__R_S1), //# + ENUM2STRING(BOTH_CCWCIRCLE_R2__L_S1), //# + ENUM2STRING(BOTH_CWCIRCLE_A2__L__R), //# + ENUM2STRING(BOTH_CCWCIRCLE_A2__R__L), //# + ENUM2STRING(BOTH_CWCIRCLEBREAK), //# + ENUM2STRING(BOTH_CCWCIRCLEBREAK), //# + ENUM2STRING(BOTH_CWCIRCLELOCK), //# + ENUM2STRING(BOTH_CCWCIRCLELOCK), //# + //other saber anims/attacks + ENUM2STRING(BOTH_SABERFAST_STANCE), + ENUM2STRING(BOTH_SABERSLOW_STANCE), + ENUM2STRING(BOTH_SABERDUAL_STANCE), + ENUM2STRING(BOTH_SABERSTAFF_STANCE), + ENUM2STRING(BOTH_A2_STABBACK1), //# Stab saber backward + ENUM2STRING(BOTH_ATTACK_BACK), //# Swing around backwards and attack + ENUM2STRING(BOTH_JUMPFLIPSLASHDOWN1),//# + ENUM2STRING(BOTH_JUMPFLIPSTABDOWN),//# + ENUM2STRING(BOTH_FORCELEAP2_T__B_),//# + ENUM2STRING(BOTH_LUNGE2_B__T_),//# + ENUM2STRING(BOTH_CROUCHATTACKBACK1),//# + //New specials for JKA: + ENUM2STRING(BOTH_JUMPATTACK6),//# + ENUM2STRING(BOTH_JUMPATTACK7),//# + ENUM2STRING(BOTH_SPINATTACK6),//# + ENUM2STRING(BOTH_SPINATTACK7),//# + ENUM2STRING(BOTH_S1_S6),//# From stand1 to saberdual stance - turning on your dual sabers + ENUM2STRING(BOTH_S6_S1),//# From dualstaff stance to stand1 - turning off your dual sabers + ENUM2STRING(BOTH_S1_S7),//# From stand1 to saberstaff stance - turning on your saberstaff + ENUM2STRING(BOTH_S7_S1),//# From saberstaff stance to stand1 - turning off your saberstaff + ENUM2STRING(BOTH_FORCELONGLEAP_START), + ENUM2STRING(BOTH_FORCELONGLEAP_ATTACK), + ENUM2STRING(BOTH_FORCELONGLEAP_LAND), + ENUM2STRING(BOTH_FORCEWALLRUNFLIP_START), + ENUM2STRING(BOTH_FORCEWALLRUNFLIP_END), + ENUM2STRING(BOTH_FORCEWALLRUNFLIP_ALT), + ENUM2STRING(BOTH_FORCEWALLREBOUND_FORWARD), + ENUM2STRING(BOTH_FORCEWALLREBOUND_LEFT), + ENUM2STRING(BOTH_FORCEWALLREBOUND_BACK), + ENUM2STRING(BOTH_FORCEWALLREBOUND_RIGHT), + ENUM2STRING(BOTH_FORCEWALLHOLD_FORWARD), + ENUM2STRING(BOTH_FORCEWALLHOLD_LEFT), + ENUM2STRING(BOTH_FORCEWALLHOLD_BACK), + ENUM2STRING(BOTH_FORCEWALLHOLD_RIGHT), + ENUM2STRING(BOTH_FORCEWALLRELEASE_FORWARD), + ENUM2STRING(BOTH_FORCEWALLRELEASE_LEFT), + ENUM2STRING(BOTH_FORCEWALLRELEASE_BACK), + ENUM2STRING(BOTH_FORCEWALLRELEASE_RIGHT), + ENUM2STRING(BOTH_A7_KICK_F), + ENUM2STRING(BOTH_A7_KICK_B), + ENUM2STRING(BOTH_A7_KICK_R), + ENUM2STRING(BOTH_A7_KICK_L), + ENUM2STRING(BOTH_A7_KICK_S), + ENUM2STRING(BOTH_A7_KICK_BF), + ENUM2STRING(BOTH_A7_KICK_BF_STOP), + ENUM2STRING(BOTH_A7_KICK_RL), + ENUM2STRING(BOTH_A7_KICK_F_AIR), + ENUM2STRING(BOTH_A7_KICK_B_AIR), + ENUM2STRING(BOTH_A7_KICK_R_AIR), + ENUM2STRING(BOTH_A7_KICK_L_AIR), + ENUM2STRING(BOTH_FLIP_ATTACK7), + ENUM2STRING(BOTH_FLIP_HOLD7), + ENUM2STRING(BOTH_FLIP_LAND), + ENUM2STRING(BOTH_PULL_IMPALE_STAB), + ENUM2STRING(BOTH_PULL_IMPALE_SWING), + ENUM2STRING(BOTH_PULLED_INAIR_B), + ENUM2STRING(BOTH_PULLED_INAIR_F), + ENUM2STRING(BOTH_STABDOWN), + ENUM2STRING(BOTH_STABDOWN_STAFF), + ENUM2STRING(BOTH_STABDOWN_DUAL), + ENUM2STRING(BOTH_A6_SABERPROTECT), + ENUM2STRING(BOTH_A7_SOULCAL), + ENUM2STRING(BOTH_A1_SPECIAL), + ENUM2STRING(BOTH_A2_SPECIAL), + ENUM2STRING(BOTH_A3_SPECIAL), + ENUM2STRING(BOTH_ROLL_STAB), + + //# #sep ENUM2STRING(BOTH_ STANDING + ENUM2STRING(BOTH_STAND1), //# Standing idle, no weapon, hands down + ENUM2STRING(BOTH_STAND1IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND2), //# Standing idle with a saber + ENUM2STRING(BOTH_STAND2IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND2IDLE2), + ENUM2STRING(BOTH_STAND3), //# Standing idle with 2-handed weapon + ENUM2STRING(BOTH_STAND3IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND4), //# hands clasp behind back + ENUM2STRING(BOTH_STAND5), //# standing idle, no weapon, hand down, back straight + ENUM2STRING(BOTH_STAND5IDLE1), //# Random standing idle + ENUM2STRING(BOTH_STAND6), //# one handed), gun at side), relaxed stand + ENUM2STRING(BOTH_STAND8), //# both hands on hips (male) + ENUM2STRING(BOTH_STAND1TO2), //# Transition from stand1 to stand2 + ENUM2STRING(BOTH_STAND2TO1), //# Transition from stand2 to stand1 + ENUM2STRING(BOTH_STAND2TO4), //# Transition from stand2 to stand4 + ENUM2STRING(BOTH_STAND4TO2), //# Transition from stand4 to stand2 + ENUM2STRING(BOTH_STAND4TOATTACK2), //# relaxed stand to 1-handed pistol ready + ENUM2STRING(BOTH_STANDUP2), //# Luke standing up from his meditation platform (cin # 37) + ENUM2STRING(BOTH_STAND5TOSIT3), //# transition from stand 5 to sit 3 + ENUM2STRING(BOTH_STAND1TOSTAND5), //# Transition from stand1 to stand5 + ENUM2STRING(BOTH_STAND5TOSTAND1), //# Transition from stand5 to stand1 + ENUM2STRING(BOTH_STAND5TOAIM), //# Transition of Kye aiming his gun at Desann (cin #9) + ENUM2STRING(BOTH_STAND5STARTLEDLOOKLEFT), //# Kyle turning to watch the bridge drop (cin #9) + ENUM2STRING(BOTH_STARTLEDLOOKLEFTTOSTAND5), //# Kyle returning to stand 5 from watching the bridge drop (cin #9) + ENUM2STRING(BOTH_STAND5TOSTAND8), //# Transition from stand5 to stand8 + ENUM2STRING(BOTH_STAND7TOSTAND8), //# Tavion putting hands on back of chair (cin #11) + ENUM2STRING(BOTH_STAND8TOSTAND5), //# Transition from stand8 to stand5 + ENUM2STRING(BOTH_STAND9), //# Kyle's standing idle, no weapon, hands down + ENUM2STRING(BOTH_STAND9IDLE1), //# Kyle's random standing idle + ENUM2STRING(BOTH_STAND5SHIFTWEIGHT), //# Weightshift from stand5 to side and back to stand5 + ENUM2STRING(BOTH_STAND5SHIFTWEIGHTSTART), //# From stand5 to side + ENUM2STRING(BOTH_STAND5SHIFTWEIGHTSTOP), //# From side to stand5 + ENUM2STRING(BOTH_STAND5TURNLEFTSTART), //# Start turning left from stand5 + ENUM2STRING(BOTH_STAND5TURNLEFTSTOP), //# Stop turning left from stand5 + ENUM2STRING(BOTH_STAND5TURNRIGHTSTART), //# Start turning right from stand5 + ENUM2STRING(BOTH_STAND5TURNRIGHTSTOP), //# Stop turning right from stand5 + ENUM2STRING(BOTH_STAND5LOOK180LEFTSTART), //# Start looking over left shoulder (cin #17) + ENUM2STRING(BOTH_STAND5LOOK180LEFTSTOP), //# Stop looking over left shoulder (cin #17) + + ENUM2STRING(BOTH_CONSOLE1START), //# typing at a console + ENUM2STRING(BOTH_CONSOLE1), //# typing at a console + ENUM2STRING(BOTH_CONSOLE1STOP), //# typing at a console + ENUM2STRING(BOTH_CONSOLE2START), //# typing at a console with comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2), //# typing at a console with comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2STOP), //# typing at a console with comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2HOLDCOMSTART), //# lean in to type at console while holding comm link in hand (cin #5) + ENUM2STRING(BOTH_CONSOLE2HOLDCOMSTOP), //# lean away after typing at console while holding comm link in hand (cin #5) + + ENUM2STRING(BOTH_GUARD_LOOKAROUND1), //# Cradling weapon and looking around + ENUM2STRING(BOTH_GUARD_IDLE1), //# Cradling weapon and standing + ENUM2STRING(BOTH_GESTURE1), //# Generic gesture), non-specific + ENUM2STRING(BOTH_GESTURE2), //# Generic gesture), non-specific + ENUM2STRING(BOTH_WALK1TALKCOMM1), //# Talking into coom link while walking + ENUM2STRING(BOTH_TALK1), //# Generic talk anim + ENUM2STRING(BOTH_TALK2), //# Generic talk anim + ENUM2STRING(BOTH_TALKCOMM1START), //# Start talking into a comm link + ENUM2STRING(BOTH_TALKCOMM1), //# Talking into a comm link + ENUM2STRING(BOTH_TALKCOMM1STOP), //# Stop talking into a comm link + ENUM2STRING(BOTH_TALKGESTURE1), //# Generic talk anim + + ENUM2STRING(BOTH_HEADTILTLSTART), //# Head tilt to left + ENUM2STRING(BOTH_HEADTILTLSTOP), //# Head tilt to left + ENUM2STRING(BOTH_HEADTILTRSTART), //# Head tilt to right + ENUM2STRING(BOTH_HEADTILTRSTOP), //# Head tilt to right + ENUM2STRING(BOTH_HEADNOD), //# Head shake YES + ENUM2STRING(BOTH_HEADSHAKE), //# Head shake NO + ENUM2STRING(BOTH_SIT2HEADTILTLSTART), //# Head tilt to left from seated position 2 + ENUM2STRING(BOTH_SIT2HEADTILTLSTOP), //# Head tilt to left from seated position 2 + + ENUM2STRING(BOTH_REACH1START), //# Monmothma reaching for crystal + ENUM2STRING(BOTH_REACH1STOP), //# Monmothma reaching for crystal + + ENUM2STRING(BOTH_COME_ON1), //# Jan gesturing to Kyle (cin #32a) + ENUM2STRING(BOTH_STEADYSELF1), //# Jan trying to keep footing (cin #32a) Kyle (cin#5) + ENUM2STRING(BOTH_STEADYSELF1END), //# Return hands to side from STEADSELF1 Kyle (cin#5) + ENUM2STRING(BOTH_SILENCEGESTURE1), //# Luke silencing Kyle with a raised hand (cin #37) + ENUM2STRING(BOTH_REACHFORSABER1), //# Luke holding hand out for Kyle's saber (cin #37) + ENUM2STRING(BOTH_SABERKILLER1), //# Tavion about to strike Jan with saber (cin #9) + ENUM2STRING(BOTH_SABERKILLEE1), //# Jan about to be struck by Tavion with saber (cin #9) + ENUM2STRING(BOTH_HUGGER1), //# Kyle hugging Jan (cin #29) + ENUM2STRING(BOTH_HUGGERSTOP1), //# Kyle stop hugging Jan but don't let her go (cin #29) + ENUM2STRING(BOTH_HUGGEE1), //# Jan being hugged (cin #29) + ENUM2STRING(BOTH_HUGGEESTOP1), //# Jan stop being hugged but don't let go (cin #29) + + ENUM2STRING(BOTH_SABERTHROW1START), //# Desann throwing his light saber (cin #26) + ENUM2STRING(BOTH_SABERTHROW1STOP), //# Desann throwing his light saber (cin #26) + ENUM2STRING(BOTH_SABERTHROW2START), //# Kyle throwing his light saber (cin #32) + ENUM2STRING(BOTH_SABERTHROW2STOP), //# Kyle throwing his light saber (cin #32) + + //# #sep ENUM2STRING(BOTH_ SITTING/CROUCHING + ENUM2STRING(BOTH_SIT1), //# Normal chair sit. + ENUM2STRING(BOTH_SIT2), //# Lotus position. + ENUM2STRING(BOTH_SIT3), //# Sitting in tired position), elbows on knees + + ENUM2STRING(BOTH_SIT2TOSTAND5), //# Transition from sit 2 to stand 5 + ENUM2STRING(BOTH_STAND5TOSIT2), //# Transition from stand 5 to sit 2 + ENUM2STRING(BOTH_SIT2TOSIT4), //# Trans from sit2 to sit4 (cin #12) Luke leaning back from lotus position. + ENUM2STRING(BOTH_SIT3TOSTAND5), //# transition from sit 3 to stand 5 + + ENUM2STRING(BOTH_CROUCH1), //# Transition from standing to crouch + ENUM2STRING(BOTH_CROUCH1IDLE), //# Crouching idle + ENUM2STRING(BOTH_CROUCH1WALK), //# Walking while crouched + ENUM2STRING(BOTH_CROUCH1WALKBACK), //# Walking while crouched + ENUM2STRING(BOTH_UNCROUCH1), //# Transition from crouch to standing + ENUM2STRING(BOTH_CROUCH2TOSTAND1), //# going from crouch2 to stand1 + ENUM2STRING(BOTH_CROUCH3), //# Desann crouching down to Kyle (cin 9) + ENUM2STRING(BOTH_UNCROUCH3), //# Desann uncrouching down to Kyle (cin 9) + ENUM2STRING(BOTH_CROUCH4), //# Slower version of crouch1 for cinematics + ENUM2STRING(BOTH_UNCROUCH4), //# Slower version of uncrouch1 for cinematics + + ENUM2STRING(BOTH_GUNSIT1), //# sitting on an emplaced gun. + + // Swoop Vehicle animations. + //* #sep BOTH_ SWOOP ANIMS + ENUM2STRING(BOTH_VS_MOUNT_L), //# Mount from left + ENUM2STRING(BOTH_VS_DISMOUNT_L), //# Dismount to left + ENUM2STRING(BOTH_VS_MOUNT_R), //# Mount from right (symmetry) + ENUM2STRING(BOTH_VS_DISMOUNT_R), //# Dismount to right (symmetry) + + ENUM2STRING(BOTH_VS_MOUNTJUMP_L), //# + ENUM2STRING(BOTH_VS_MOUNTTHROW), //# Land on an occupied vehicle & throw off current pilot + ENUM2STRING(BOTH_VS_MOUNTTHROW_L), //# Land on an occupied vehicle & throw off current pilot + ENUM2STRING(BOTH_VS_MOUNTTHROW_R), //# Land on an occupied vehicle & throw off current pilot + ENUM2STRING(BOTH_VS_MOUNTTHROWEE), //# Current pilot getting thrown off by another guy + + ENUM2STRING(BOTH_VS_LOOKLEFT), //# Turn & Look behind and to the left (no weapon) + ENUM2STRING(BOTH_VS_LOOKRIGHT), //# Turn & Look behind and to the right (no weapon) + + ENUM2STRING(BOTH_VS_TURBO), //# Hit The Turbo Button + + ENUM2STRING(BOTH_VS_REV), //# Player looks back as swoop reverses + + ENUM2STRING(BOTH_VS_AIR), //# Player stands up when swoop is airborn + ENUM2STRING(BOTH_VS_AIR_G), //# "" with Gun + ENUM2STRING(BOTH_VS_AIR_SL), //# "" with Saber Left + ENUM2STRING(BOTH_VS_AIR_SR), //# "" with Saber Right + + ENUM2STRING(BOTH_VS_LAND), //# Player bounces down when swoop lands + ENUM2STRING(BOTH_VS_LAND_G), //# "" with Gun + ENUM2STRING(BOTH_VS_LAND_SL), //# "" with Saber Left + ENUM2STRING(BOTH_VS_LAND_SR), //# "" with Saber Right + + ENUM2STRING(BOTH_VS_IDLE), //# Sit + ENUM2STRING(BOTH_VS_IDLE_G), //# Sit (gun) + ENUM2STRING(BOTH_VS_IDLE_SL), //# Sit (saber left) + ENUM2STRING(BOTH_VS_IDLE_SR), //# Sit (saber right) + + ENUM2STRING(BOTH_VS_LEANL), //# Lean left + ENUM2STRING(BOTH_VS_LEANL_G), //# Lean left (gun) + ENUM2STRING(BOTH_VS_LEANL_SL), //# Lean left (saber left) + ENUM2STRING(BOTH_VS_LEANL_SR), //# Lean left (saber right) + + ENUM2STRING(BOTH_VS_LEANR), //# Lean right + ENUM2STRING(BOTH_VS_LEANR_G), //# Lean right (gun) + ENUM2STRING(BOTH_VS_LEANR_SL), //# Lean right (saber left) + ENUM2STRING(BOTH_VS_LEANR_SR), //# Lean right (saber right) + + ENUM2STRING(BOTH_VS_ATL_S), //# Attack left with saber + ENUM2STRING(BOTH_VS_ATR_S), //# Attack right with saber + ENUM2STRING(BOTH_VS_ATR_TO_L_S), //# Attack toss saber from right to left hand + ENUM2STRING(BOTH_VS_ATL_TO_R_S), //# Attack toss saber from left to right hand + ENUM2STRING(BOTH_VS_ATR_G), //# Attack right with gun (90) + ENUM2STRING(BOTH_VS_ATL_G), //# Attack left with gun (90) + ENUM2STRING(BOTH_VS_ATF_G), //# Attack forward with gun + + ENUM2STRING(BOTH_VS_PAIN1), //# Pain + + // Added 12/04/02 by Aurelio. + //* #sep BOTH_ TAUNTAUN ANIMS + ENUM2STRING(BOTH_VT_MOUNT_L), //# Mount from left + ENUM2STRING(BOTH_VT_MOUNT_R), //# Mount from right + ENUM2STRING(BOTH_VT_MOUNT_B), //# Mount from air, behind + ENUM2STRING(BOTH_VT_DISMOUNT), //# Dismount for tauntaun + ENUM2STRING(BOTH_VT_DISMOUNT_L), //# Dismount to tauntauns left + ENUM2STRING(BOTH_VT_DISMOUNT_R), //# Dismount to tauntauns right (symmetry) + + ENUM2STRING(BOTH_VT_WALK_FWD), //# Walk forward + ENUM2STRING(BOTH_VT_WALK_REV), //# Walk backward + ENUM2STRING(BOTH_VT_WALK_FWD_L), //# walk lean left + ENUM2STRING(BOTH_VT_WALK_FWD_R), //# walk lean right + ENUM2STRING(BOTH_VT_RUN_FWD), //# Run forward + ENUM2STRING(BOTH_VT_RUN_REV), //# Look backwards while running (not weapon specific) + ENUM2STRING(BOTH_VT_RUN_FWD_L), //# run lean left + ENUM2STRING(BOTH_VT_RUN_FWD_R), //# run lean right + + ENUM2STRING(BOTH_VT_SLIDEF), //# Tauntaun slides forward with abrupt stop + ENUM2STRING(BOTH_VT_AIR), //# Tauntaun jump + ENUM2STRING(BOTH_VT_ATB), //# Tauntaun tail swipe + ENUM2STRING(BOTH_VT_PAIN1), //# Pain + ENUM2STRING(BOTH_VT_DEATH1), //# Die + ENUM2STRING(BOTH_VT_STAND), //# Stand still and breath + ENUM2STRING(BOTH_VT_BUCK), //# Tauntaun bucking loop animation + + ENUM2STRING(BOTH_VT_LAND), //# Player bounces down when tauntaun lands + ENUM2STRING(BOTH_VT_TURBO), //# Hit The Turbo Button + ENUM2STRING(BOTH_VT_IDLE_SL), //# Sit (saber left) + ENUM2STRING(BOTH_VT_IDLE_SR), //# Sit (saber right) + ENUM2STRING(BOTH_VT_IDLE), //# Sit with no weapon selected + ENUM2STRING(BOTH_VT_IDLE1), //# Sit with no weapon selected + ENUM2STRING(BOTH_VT_IDLE_S), //# Sit with saber selected + ENUM2STRING(BOTH_VT_IDLE_G), //# Sit with gun selected + ENUM2STRING(BOTH_VT_IDLE_T), //# Sit with thermal grenade selected + + ENUM2STRING(BOTH_VT_ATL_S), //# Attack left with saber + ENUM2STRING(BOTH_VT_ATR_S), //# Attack right with saber + ENUM2STRING(BOTH_VT_ATR_TO_L_S), //# Attack toss saber from right to left hand + ENUM2STRING(BOTH_VT_ATL_TO_R_S), //# Attack toss saber from left to right hand + ENUM2STRING(BOTH_VT_ATR_G), //# Attack right with gun (90) + ENUM2STRING(BOTH_VT_ATL_G), //# Attack left with gun (90) + ENUM2STRING(BOTH_VT_ATF_G), //# Attack forward with gun + + + // Added 2/26/02 by Aurelio. + //* #sep BOTH_ FIGHTER ANIMS + ENUM2STRING( BOTH_GEARS_OPEN ), + ENUM2STRING( BOTH_GEARS_CLOSE ), + ENUM2STRING( BOTH_WINGS_OPEN ), + ENUM2STRING( BOTH_WINGS_CLOSE ), + + /////////////////////////////////// + + ENUM2STRING(BOTH_DEATH14_UNGRIP), //# Desann's end death (cin #35) + ENUM2STRING(BOTH_DEATH14_SITUP), //# Tavion sitting up after having been thrown (cin #23) + ENUM2STRING(BOTH_KNEES1), //# Tavion on her knees + ENUM2STRING(BOTH_KNEES2), //# Tavion on her knees looking down + ENUM2STRING(BOTH_KNEES2TO1), //# Transition of KNEES2 to KNEES1 + + //# #sep ENUM2STRING(BOTH_ MOVING + ENUM2STRING(BOTH_WALK1), //# Normal walk + ENUM2STRING(BOTH_WALK2), //# Normal walk + ENUM2STRING(BOTH_WALK_STAFF), //# Walk with saberstaff turned on + ENUM2STRING(BOTH_WALKBACK_STAFF), //# Walk backwards with saberstaff turned on + ENUM2STRING(BOTH_WALK_DUAL), //# Walk with dual turned on + ENUM2STRING(BOTH_WALKBACK_DUAL), //# Walk backwards with dual turned on + ENUM2STRING(BOTH_WALK5), //# Tavion taunting Kyle (cin 22) + ENUM2STRING(BOTH_WALK6), //# Slow walk for Luke (cin 12) + ENUM2STRING(BOTH_WALK7), //# Fast walk + ENUM2STRING(BOTH_RUN1), //# Full run + ENUM2STRING(BOTH_RUN1START), //# Start into full run1 + ENUM2STRING(BOTH_RUN1STOP), //# Stop from full run1 + ENUM2STRING(BOTH_RUN2), //# Full run + ENUM2STRING(BOTH_RUN1TORUN2), //# Wampa run anim transition + ENUM2STRING(BOTH_RUN2TORUN1), //# Wampa run anim transition + ENUM2STRING(BOTH_RUN4), //# Jawa run + ENUM2STRING(BOTH_RUN_STAFF), //# Run with saberstaff turned on + ENUM2STRING(BOTH_RUNBACK_STAFF), //# Run backwards with saberstaff turned on + ENUM2STRING(BOTH_RUN_DUAL), //# Run with dual turned on + ENUM2STRING(BOTH_RUNBACK_DUAL), //# Run backwards with dual turned on + ENUM2STRING(BOTH_STRAFE_LEFT1), //# Sidestep left), should loop + ENUM2STRING(BOTH_STRAFE_RIGHT1), //# Sidestep right), should loop + ENUM2STRING(BOTH_RUNSTRAFE_LEFT1), //# Sidestep left), should loop + ENUM2STRING(BOTH_RUNSTRAFE_RIGHT1), //# Sidestep right), should loop + ENUM2STRING(BOTH_TURN_LEFT1), //# Turn left), should loop + ENUM2STRING(BOTH_TURN_RIGHT1), //# Turn right), should loop + ENUM2STRING(BOTH_TURNSTAND1), //# Turn from STAND1 position + ENUM2STRING(BOTH_TURNSTAND2), //# Turn from STAND2 position + ENUM2STRING(BOTH_TURNSTAND3), //# Turn from STAND3 position + ENUM2STRING(BOTH_TURNSTAND4), //# Turn from STAND4 position + ENUM2STRING(BOTH_TURNSTAND5), //# Turn from STAND5 position + ENUM2STRING(BOTH_TURNCROUCH1), //# Turn from CROUCH1 position + + ENUM2STRING(BOTH_WALKBACK1), //# Walk1 backwards + ENUM2STRING(BOTH_WALKBACK2), //# Walk2 backwards + ENUM2STRING(BOTH_RUNBACK1), //# Run1 backwards + ENUM2STRING(BOTH_RUNBACK2), //# Run1 backwards + + //# #sep BOTH_ JUMPING + ENUM2STRING(BOTH_JUMP1), //# Jump - wind-up and leave ground + ENUM2STRING(BOTH_INAIR1), //# In air loop (from jump) + ENUM2STRING(BOTH_LAND1), //# Landing (from in air loop) + ENUM2STRING(BOTH_LAND2), //# Landing Hard (from a great height) + + ENUM2STRING(BOTH_JUMPBACK1), //# Jump backwards - wind-up and leave ground + ENUM2STRING(BOTH_INAIRBACK1), //# In air loop (from jump back) + ENUM2STRING(BOTH_LANDBACK1), //# Landing backwards(from in air loop) + + ENUM2STRING(BOTH_JUMPLEFT1), //# Jump left - wind-up and leave ground + ENUM2STRING(BOTH_INAIRLEFT1), //# In air loop (from jump left) + ENUM2STRING(BOTH_LANDLEFT1), //# Landing left(from in air loop) + + ENUM2STRING(BOTH_JUMPRIGHT1), //# Jump right - wind-up and leave ground + ENUM2STRING(BOTH_INAIRRIGHT1), //# In air loop (from jump right) + ENUM2STRING(BOTH_LANDRIGHT1), //# Landing right(from in air loop) + + ENUM2STRING(BOTH_FORCEJUMP1), //# Jump - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIR1), //# In air loop (from jump) + ENUM2STRING(BOTH_FORCELAND1), //# Landing (from in air loop) + + ENUM2STRING(BOTH_FORCEJUMPBACK1), //# Jump backwards - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIRBACK1), //# In air loop (from jump back) + ENUM2STRING(BOTH_FORCELANDBACK1), //# Landing backwards(from in air loop) + + ENUM2STRING(BOTH_FORCEJUMPLEFT1), //# Jump left - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIRLEFT1), //# In air loop (from jump left) + ENUM2STRING(BOTH_FORCELANDLEFT1), //# Landing left(from in air loop) + + ENUM2STRING(BOTH_FORCEJUMPRIGHT1), //# Jump right - wind-up and leave ground + ENUM2STRING(BOTH_FORCEINAIRRIGHT1), //# In air loop (from jump right) + ENUM2STRING(BOTH_FORCELANDRIGHT1), //# Landing right(from in air loop) + //# #sep BOTH_ ACROBATICS + ENUM2STRING(BOTH_FLIP_F), //# Flip forward + ENUM2STRING(BOTH_FLIP_B), //# Flip backwards + ENUM2STRING(BOTH_FLIP_L), //# Flip left + ENUM2STRING(BOTH_FLIP_R), //# Flip right + + ENUM2STRING(BOTH_ROLL_F), //# Roll forward + ENUM2STRING(BOTH_ROLL_B), //# Roll backward + ENUM2STRING(BOTH_ROLL_L), //# Roll left + ENUM2STRING(BOTH_ROLL_R), //# Roll right + + ENUM2STRING(BOTH_HOP_F), //# quickstep forward + ENUM2STRING(BOTH_HOP_B), //# quickstep backwards + ENUM2STRING(BOTH_HOP_L), //# quickstep left + ENUM2STRING(BOTH_HOP_R), //# quickstep right + + ENUM2STRING(BOTH_DODGE_FL), //# lean-dodge forward left + ENUM2STRING(BOTH_DODGE_FR), //# lean-dodge forward right + ENUM2STRING(BOTH_DODGE_BL), //# lean-dodge backwards left + ENUM2STRING(BOTH_DODGE_BR), //# lean-dodge backwards right + ENUM2STRING(BOTH_DODGE_L), //# lean-dodge left + ENUM2STRING(BOTH_DODGE_R), //# lean-dodge right + ENUM2STRING(BOTH_DODGE_HOLD_FL), //# lean-dodge pose forward left + ENUM2STRING(BOTH_DODGE_HOLD_FR), //# lean-dodge pose forward right + ENUM2STRING(BOTH_DODGE_HOLD_BL), //# lean-dodge pose backwards left + ENUM2STRING(BOTH_DODGE_HOLD_BR), //# lean-dodge pose backwards right + ENUM2STRING(BOTH_DODGE_HOLD_L), //# lean-dodge pose left + ENUM2STRING(BOTH_DODGE_HOLD_R), //# lean-dodge pose right + + //MP taunt anims + ENUM2STRING(BOTH_ENGAGETAUNT), + ENUM2STRING(BOTH_BOW), + ENUM2STRING(BOTH_MEDITATE), + ENUM2STRING(BOTH_MEDITATE_END), + ENUM2STRING(BOTH_SHOWOFF_FAST), + ENUM2STRING(BOTH_SHOWOFF_MEDIUM), + ENUM2STRING(BOTH_SHOWOFF_STRONG), + ENUM2STRING(BOTH_SHOWOFF_DUAL), + ENUM2STRING(BOTH_SHOWOFF_STAFF), + ENUM2STRING(BOTH_VICTORY_FAST), + ENUM2STRING(BOTH_VICTORY_MEDIUM), + ENUM2STRING(BOTH_VICTORY_STRONG), + ENUM2STRING(BOTH_VICTORY_DUAL), + ENUM2STRING(BOTH_VICTORY_STAFF), + //other saber/acro anims + ENUM2STRING(BOTH_ARIAL_LEFT), //# + ENUM2STRING(BOTH_ARIAL_RIGHT), //# + ENUM2STRING(BOTH_CARTWHEEL_LEFT), //# + ENUM2STRING(BOTH_CARTWHEEL_RIGHT), //# + ENUM2STRING(BOTH_FLIP_LEFT), //# + ENUM2STRING(BOTH_FLIP_BACK1), //# + ENUM2STRING(BOTH_FLIP_BACK2), //# + ENUM2STRING(BOTH_FLIP_BACK3), //# + ENUM2STRING(BOTH_BUTTERFLY_LEFT), //# + ENUM2STRING(BOTH_BUTTERFLY_RIGHT), //# + ENUM2STRING(BOTH_WALL_RUN_RIGHT), //# + ENUM2STRING(BOTH_WALL_RUN_RIGHT_FLIP),//# + ENUM2STRING(BOTH_WALL_RUN_RIGHT_STOP),//# + ENUM2STRING(BOTH_WALL_RUN_LEFT), //# + ENUM2STRING(BOTH_WALL_RUN_LEFT_FLIP),//# + ENUM2STRING(BOTH_WALL_RUN_LEFT_STOP),//# + ENUM2STRING(BOTH_WALL_FLIP_RIGHT), //# + ENUM2STRING(BOTH_WALL_FLIP_LEFT), //# + ENUM2STRING(BOTH_KNOCKDOWN1), //# knocked backwards + ENUM2STRING(BOTH_KNOCKDOWN2), //# knocked backwards hard + ENUM2STRING(BOTH_KNOCKDOWN3), //# knocked forwards + ENUM2STRING(BOTH_KNOCKDOWN4), //# knocked backwards from crouch + ENUM2STRING(BOTH_KNOCKDOWN5), //# dupe of 3 - will be removed + ENUM2STRING(BOTH_GETUP1), //# + ENUM2STRING(BOTH_GETUP2), //# + ENUM2STRING(BOTH_GETUP3), //# + ENUM2STRING(BOTH_GETUP4), //# + ENUM2STRING(BOTH_GETUP5), //# + ENUM2STRING(BOTH_GETUP_CROUCH_F1), //# + ENUM2STRING(BOTH_GETUP_CROUCH_B1), //# + ENUM2STRING(BOTH_FORCE_GETUP_F1), //# + ENUM2STRING(BOTH_FORCE_GETUP_F2), //# + ENUM2STRING(BOTH_FORCE_GETUP_B1), //# + ENUM2STRING(BOTH_FORCE_GETUP_B2), //# + ENUM2STRING(BOTH_FORCE_GETUP_B3), //# + ENUM2STRING(BOTH_FORCE_GETUP_B4), //# + ENUM2STRING(BOTH_FORCE_GETUP_B5), //# + ENUM2STRING(BOTH_FORCE_GETUP_B6), //# + ENUM2STRING(BOTH_GETUP_BROLL_B), //# + ENUM2STRING(BOTH_GETUP_BROLL_F), //# + ENUM2STRING(BOTH_GETUP_BROLL_L), //# + ENUM2STRING(BOTH_GETUP_BROLL_R), //# + ENUM2STRING(BOTH_GETUP_FROLL_B), //# + ENUM2STRING(BOTH_GETUP_FROLL_F), //# + ENUM2STRING(BOTH_GETUP_FROLL_L), //# + ENUM2STRING(BOTH_GETUP_FROLL_R), //# + ENUM2STRING(BOTH_WALL_FLIP_BACK1), //# + ENUM2STRING(BOTH_WALL_FLIP_BACK2), //# + ENUM2STRING(BOTH_SPIN1), //# + ENUM2STRING(BOTH_CEILING_CLING), //# clinging to ceiling + ENUM2STRING(BOTH_CEILING_DROP), //# dropping from ceiling cling + + //TESTING + ENUM2STRING(BOTH_FJSS_TR_BL), //# jump spin slash tr to bl + ENUM2STRING(BOTH_FJSS_TL_BR), //# jump spin slash bl to tr + ENUM2STRING(BOTH_RIGHTHANDCHOPPEDOFF),//# + ENUM2STRING(BOTH_DEFLECTSLASH__R__L_FIN),//# + ENUM2STRING(BOTH_BASHED1),//# + ENUM2STRING(BOTH_ARIAL_F1),//# + ENUM2STRING(BOTH_BUTTERFLY_FR1),//# + ENUM2STRING(BOTH_BUTTERFLY_FL1),//# + + //NEW SABER/JEDI/FORCE ANIMS + ENUM2STRING(BOTH_BACK_FLIP_UP), //# back flip up Bonus Animation!!!! + ENUM2STRING(BOTH_LOSE_SABER), //# player losing saber (pulled from hand by force pull 4 - Kyle?) + ENUM2STRING(BOTH_STAFF_TAUNT), //# taunt saberstaff + ENUM2STRING(BOTH_DUAL_TAUNT), //# taunt dual + ENUM2STRING(BOTH_A6_FB), //# dual attack front/back + ENUM2STRING(BOTH_A6_LR), //# dual attack left/right + ENUM2STRING(BOTH_A7_HILT), //# saber knock (alt + stand still) + //Alora + ENUM2STRING(BOTH_ALORA_SPIN), //#jump spin attack death ballet + ENUM2STRING(BOTH_ALORA_FLIP_1), //# gymnast move 1 + ENUM2STRING(BOTH_ALORA_FLIP_2), //# gymnast move 2 + ENUM2STRING(BOTH_ALORA_FLIP_3), //# gymnast move3 + ENUM2STRING(BOTH_ALORA_FLIP_B), //# gymnast move back + ENUM2STRING(BOTH_ALORA_SPIN_THROW), //# dual saber throw + ENUM2STRING(BOTH_ALORA_SPIN_SLASH), //# spin slash special bonus animation!! :) + ENUM2STRING(BOTH_ALORA_TAUNT), //# special taunt + //Rosh (Kothos battle) + ENUM2STRING(BOTH_ROSH_PAIN), //# hurt animation (exhausted) + ENUM2STRING(BOTH_ROSH_HEAL), //# healed/rejuvenated + //Tavion + ENUM2STRING(BOTH_TAVION_SCEPTERGROUND), //# stabbing ground with sith sword shoots electricity everywhere + ENUM2STRING(BOTH_TAVION_SWORDPOWER),//# Tavion doing the He-Man(tm) thing + ENUM2STRING(BOTH_SCEPTER_START), //#Point scepter and attack start + ENUM2STRING(BOTH_SCEPTER_HOLD), //#Point scepter and attack hold + ENUM2STRING(BOTH_SCEPTER_STOP), //#Point scepter and attack stop + //Kyle Boss + ENUM2STRING(BOTH_KYLE_GRAB), //# grab + ENUM2STRING(BOTH_KYLE_MISS), //# miss + ENUM2STRING(BOTH_KYLE_PA_1), //# hold 1 + ENUM2STRING(BOTH_PLAYER_PA_1), //# player getting held 1 + ENUM2STRING(BOTH_KYLE_PA_2), //# hold 2 + ENUM2STRING(BOTH_PLAYER_PA_2), //# player getting held 2 + ENUM2STRING(BOTH_PLAYER_PA_FLY), //# player getting knocked back from punch at end of hold 1 + ENUM2STRING(BOTH_KYLE_PA_3), //# hold 3 + ENUM2STRING(BOTH_PLAYER_PA_3), //# player getting held 3 + ENUM2STRING(BOTH_PLAYER_PA_3_FLY),//# player getting thrown at end of hold 3 + //Rancor + ENUM2STRING(BOTH_BUCK_RIDER), //# Rancor bucks when someone is on him + //WAMPA Grabbing enemy + ENUM2STRING(BOTH_HOLD_START), //# + ENUM2STRING(BOTH_HOLD_MISS), //# + ENUM2STRING(BOTH_HOLD_IDLE), //# + ENUM2STRING(BOTH_HOLD_END), //# + ENUM2STRING(BOTH_HOLD_ATTACK), //# + ENUM2STRING(BOTH_HOLD_SNIFF), //# Sniff the guy you're holding + ENUM2STRING(BOTH_HOLD_DROP), //# just drop 'em + //BEING GRABBED BY WAMPA + ENUM2STRING(BOTH_GRABBED), //# + ENUM2STRING(BOTH_RELEASED), //# + ENUM2STRING(BOTH_HANG_IDLE), //# + ENUM2STRING(BOTH_HANG_ATTACK), //# + ENUM2STRING(BOTH_HANG_PAIN), //# + + //# #sep BOTH_ MISC MOVEMENT + ENUM2STRING(BOTH_HIT1), //# Kyle hit by crate in cin #9 + ENUM2STRING(BOTH_LADDER_UP1), //# Climbing up a ladder with rungs at 16 unit intervals + ENUM2STRING(BOTH_LADDER_DWN1), //# Climbing down a ladder with rungs at 16 unit intervals + ENUM2STRING(BOTH_LADDER_IDLE), //# Just sitting on the ladder + + //# #sep ENUM2STRING(BOTH_ FLYING IDLE + ENUM2STRING(BOTH_FLY_SHIELDED), //# For sentry droid, shields in + + //# #sep BOTH_ SWIMMING + ENUM2STRING(BOTH_SWIM_IDLE1), //# Swimming Idle 1 + ENUM2STRING(BOTH_SWIMFORWARD), //# Swim forward loop + ENUM2STRING(BOTH_SWIMBACKWARD), //# Swim backward loop + + //# #sep ENUM2STRING(BOTH_ LYING + ENUM2STRING(BOTH_SLEEP1), //# laying on back-rknee up-rhand on torso + ENUM2STRING(BOTH_SLEEP6START), //# Kyle leaning back to sleep (cin 20) + ENUM2STRING(BOTH_SLEEP6STOP), //# Kyle waking up and shaking his head (cin 21) + ENUM2STRING(BOTH_SLEEP1GETUP), //# alarmed and getting up out of sleep1 pose to stand + ENUM2STRING(BOTH_SLEEP1GETUP2), //# + + ENUM2STRING(BOTH_CHOKE1START), //# tavion in force grip choke + ENUM2STRING(BOTH_CHOKE1STARTHOLD), //# loop of tavion in force grip choke + ENUM2STRING(BOTH_CHOKE1), //# tavion in force grip choke + + ENUM2STRING(BOTH_CHOKE2), //# tavion recovering from force grip choke + ENUM2STRING(BOTH_CHOKE3), //# left-handed choke (for people still holding a weapon) + + //# #sep ENUM2STRING(BOTH_ HUNTER-SEEKER BOT-SPECIFIC + ENUM2STRING(BOTH_POWERUP1), //# Wakes up + + ENUM2STRING(BOTH_TURNON), //# Protocol Droid wakes up + ENUM2STRING(BOTH_TURNOFF), //# Protocol Droid shuts off + ENUM2STRING(BOTH_BUTTON1), //# Single button push with right hand + ENUM2STRING(BOTH_BUTTON2), //# Single button push with left finger + ENUM2STRING(BOTH_BUTTON_HOLD), //# Single button hold with left hand + ENUM2STRING(BOTH_BUTTON_RELEASE), //# Single button release with left hand + + //# JEDI-SPECIFIC + //# #sep BOTH_ FORCE ANIMS + ENUM2STRING(BOTH_RESISTPUSH), //# plant yourself to resist force push/pulls. + ENUM2STRING(BOTH_FORCEPUSH), //# Use off-hand to do force power. + ENUM2STRING(BOTH_FORCEPULL), //# Use off-hand to do force power. + ENUM2STRING(BOTH_MINDTRICK1), //# Use off-hand to do mind trick + ENUM2STRING(BOTH_MINDTRICK2), //# Use off-hand to do distraction + ENUM2STRING(BOTH_FORCELIGHTNING), //# Use off-hand to do lightning + ENUM2STRING(BOTH_FORCELIGHTNING_START), //# Use off-hand to do lightning - start + ENUM2STRING(BOTH_FORCELIGHTNING_HOLD), //# Use off-hand to do lightning - hold + ENUM2STRING(BOTH_FORCELIGHTNING_RELEASE),//# Use off-hand to do lightning - release + ENUM2STRING(BOTH_FORCEHEAL_START), //# Healing meditation pose start + ENUM2STRING(BOTH_FORCEHEAL_STOP), //# Healing meditation pose end + ENUM2STRING(BOTH_FORCEHEAL_QUICK), //# Healing meditation gesture + ENUM2STRING(BOTH_SABERPULL), //# Use off-hand to do force power. + ENUM2STRING(BOTH_FORCEGRIP1), //# force-gripping (no anim?) + ENUM2STRING(BOTH_FORCEGRIP3), //# force-gripping (right-hand) + ENUM2STRING(BOTH_FORCEGRIP3THROW), //# throwing while force-gripping (right hand) + ENUM2STRING(BOTH_FORCEGRIP_HOLD), //# Use off-hand to do grip - hold + ENUM2STRING(BOTH_FORCEGRIP_RELEASE),//# Use off-hand to do grip - release + ENUM2STRING(BOTH_TOSS1), //# throwing to left after force gripping + ENUM2STRING(BOTH_TOSS2), //# throwing to right after force gripping + //NEW force anims for JKA: + ENUM2STRING(BOTH_FORCE_RAGE), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING_START), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING_HOLD), + ENUM2STRING(BOTH_FORCE_2HANDEDLIGHTNING_RELEASE), + ENUM2STRING(BOTH_FORCE_DRAIN), + ENUM2STRING(BOTH_FORCE_DRAIN_START), + ENUM2STRING(BOTH_FORCE_DRAIN_HOLD), + ENUM2STRING(BOTH_FORCE_DRAIN_RELEASE), + ENUM2STRING(BOTH_FORCE_DRAIN_GRAB_START), + ENUM2STRING(BOTH_FORCE_DRAIN_GRAB_HOLD), + ENUM2STRING(BOTH_FORCE_DRAIN_GRAB_END), + ENUM2STRING(BOTH_FORCE_DRAIN_GRABBED), + ENUM2STRING(BOTH_FORCE_ABSORB), + ENUM2STRING(BOTH_FORCE_ABSORB_START), + ENUM2STRING(BOTH_FORCE_ABSORB_END), + ENUM2STRING(BOTH_FORCE_PROTECT), + ENUM2STRING(BOTH_FORCE_PROTECT_FAST), + + ENUM2STRING(BOTH_WIND), + + ENUM2STRING(BOTH_STAND_TO_KNEEL), + ENUM2STRING(BOTH_KNEEL_TO_STAND), + + ENUM2STRING(BOTH_TUSKENATTACK1), + ENUM2STRING(BOTH_TUSKENATTACK2), + ENUM2STRING(BOTH_TUSKENATTACK3), + ENUM2STRING(BOTH_TUSKENLUNGE1), + ENUM2STRING(BOTH_TUSKENTAUNT1), + + ENUM2STRING(BOTH_COWER1_START), //# cower start + ENUM2STRING(BOTH_COWER1), //# cower loop + ENUM2STRING(BOTH_COWER1_STOP), //# cower stop + ENUM2STRING(BOTH_SONICPAIN_START), + ENUM2STRING(BOTH_SONICPAIN_HOLD), + ENUM2STRING(BOTH_SONICPAIN_END), + + //new anim slots per Jarrod's request + ENUM2STRING(BOTH_STAND10), + ENUM2STRING(BOTH_STAND10_TALK1), + ENUM2STRING(BOTH_STAND10_TALK2), + ENUM2STRING(BOTH_STAND10TOSTAND1), + + ENUM2STRING(BOTH_STAND1_TALK1), + ENUM2STRING(BOTH_STAND1_TALK2), + ENUM2STRING(BOTH_STAND1_TALK3), + + ENUM2STRING(BOTH_SIT4), + ENUM2STRING(BOTH_SIT5), + ENUM2STRING(BOTH_SIT5_TALK1), + ENUM2STRING(BOTH_SIT5_TALK2), + ENUM2STRING(BOTH_SIT5_TALK3), + + ENUM2STRING(BOTH_SIT6), + ENUM2STRING(BOTH_SIT7), + //================================================= + //ANIMS IN WHICH ONLY THE UPPER OBJECTS ARE IN MD3 + //================================================= + //# #sep ENUM2STRING(TORSO_ WEAPON-RELATED + ENUM2STRING(TORSO_DROPWEAP1), //# Put weapon away + ENUM2STRING(TORSO_DROPWEAP4), //# Put weapon away + ENUM2STRING(TORSO_RAISEWEAP1), //# Draw Weapon + ENUM2STRING(TORSO_RAISEWEAP4), //# Draw Weapon + ENUM2STRING(TORSO_WEAPONREADY1), //# Ready to fire stun baton + ENUM2STRING(TORSO_WEAPONREADY2), //# Ready to fire one-handed blaster pistol + ENUM2STRING(TORSO_WEAPONREADY3), //# Ready to fire blaster rifle + ENUM2STRING(TORSO_WEAPONREADY4), //# Ready to fire sniper rifle + ENUM2STRING(TORSO_WEAPONREADY10), //# Ready to fire thermal det + ENUM2STRING(TORSO_WEAPONIDLE2), //# Holding one-handed blaster + ENUM2STRING(TORSO_WEAPONIDLE3), //# Holding blaster rifle + ENUM2STRING(TORSO_WEAPONIDLE4), //# Holding sniper rifle + ENUM2STRING(TORSO_WEAPONIDLE10), //# Holding thermal det + + //# #sep ENUM2STRING(TORSO_ USING NON-WEAPON OBJECTS + + //# #sep ENUM2STRING(TORSO_ MISC + ENUM2STRING(TORSO_SURRENDER_START), //# arms up + ENUM2STRING(TORSO_SURRENDER_STOP), //# arms back down + ENUM2STRING(TORSO_CHOKING1), //# TEMP + + ENUM2STRING(TORSO_HANDSIGNAL1), + ENUM2STRING(TORSO_HANDSIGNAL2), + ENUM2STRING(TORSO_HANDSIGNAL3), + ENUM2STRING(TORSO_HANDSIGNAL4), + ENUM2STRING(TORSO_HANDSIGNAL5), + + //================================================= + //ANIMS IN WHICH ONLY THE LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep Legs-only anims + ENUM2STRING(LEGS_TURN1), //# What legs do when you turn your lower body to match your upper body facing + ENUM2STRING(LEGS_TURN2), //# Leg turning from stand2 + ENUM2STRING(LEGS_LEAN_LEFT1), //# Lean left + ENUM2STRING(LEGS_LEAN_RIGHT1), //# Lean Right + ENUM2STRING(LEGS_CHOKING1), //# TEMP + ENUM2STRING(LEGS_LEFTUP1), //# On a slope with left foot 4 higher than right + ENUM2STRING(LEGS_LEFTUP2), //# On a slope with left foot 8 higher than right + ENUM2STRING(LEGS_LEFTUP3), //# On a slope with left foot 12 higher than right + ENUM2STRING(LEGS_LEFTUP4), //# On a slope with left foot 16 higher than right + ENUM2STRING(LEGS_LEFTUP5), //# On a slope with left foot 20 higher than right + ENUM2STRING(LEGS_RIGHTUP1), //# On a slope with RIGHT foot 4 higher than left + ENUM2STRING(LEGS_RIGHTUP2), //# On a slope with RIGHT foot 8 higher than left + ENUM2STRING(LEGS_RIGHTUP3), //# On a slope with RIGHT foot 12 higher than left + ENUM2STRING(LEGS_RIGHTUP4), //# On a slope with RIGHT foot 16 higher than left + ENUM2STRING(LEGS_RIGHTUP5), //# On a slope with RIGHT foot 20 higher than left + ENUM2STRING(LEGS_S1_LUP1), + ENUM2STRING(LEGS_S1_LUP2), + ENUM2STRING(LEGS_S1_LUP3), + ENUM2STRING(LEGS_S1_LUP4), + ENUM2STRING(LEGS_S1_LUP5), + ENUM2STRING(LEGS_S1_RUP1), + ENUM2STRING(LEGS_S1_RUP2), + ENUM2STRING(LEGS_S1_RUP3), + ENUM2STRING(LEGS_S1_RUP4), + ENUM2STRING(LEGS_S1_RUP5), + ENUM2STRING(LEGS_S3_LUP1), + ENUM2STRING(LEGS_S3_LUP2), + ENUM2STRING(LEGS_S3_LUP3), + ENUM2STRING(LEGS_S3_LUP4), + ENUM2STRING(LEGS_S3_LUP5), + ENUM2STRING(LEGS_S3_RUP1), + ENUM2STRING(LEGS_S3_RUP2), + ENUM2STRING(LEGS_S3_RUP3), + ENUM2STRING(LEGS_S3_RUP4), + ENUM2STRING(LEGS_S3_RUP5), + ENUM2STRING(LEGS_S4_LUP1), + ENUM2STRING(LEGS_S4_LUP2), + ENUM2STRING(LEGS_S4_LUP3), + ENUM2STRING(LEGS_S4_LUP4), + ENUM2STRING(LEGS_S4_LUP5), + ENUM2STRING(LEGS_S4_RUP1), + ENUM2STRING(LEGS_S4_RUP2), + ENUM2STRING(LEGS_S4_RUP3), + ENUM2STRING(LEGS_S4_RUP4), + ENUM2STRING(LEGS_S4_RUP5), + ENUM2STRING(LEGS_S5_LUP1), + ENUM2STRING(LEGS_S5_LUP2), + ENUM2STRING(LEGS_S5_LUP3), + ENUM2STRING(LEGS_S5_LUP4), + ENUM2STRING(LEGS_S5_LUP5), + ENUM2STRING(LEGS_S5_RUP1), + ENUM2STRING(LEGS_S5_RUP2), + ENUM2STRING(LEGS_S5_RUP3), + ENUM2STRING(LEGS_S5_RUP4), + ENUM2STRING(LEGS_S5_RUP5), + ENUM2STRING(LEGS_S6_LUP1), + ENUM2STRING(LEGS_S6_LUP2), + ENUM2STRING(LEGS_S6_LUP3), + ENUM2STRING(LEGS_S6_LUP4), + ENUM2STRING(LEGS_S6_LUP5), + ENUM2STRING(LEGS_S6_RUP1), + ENUM2STRING(LEGS_S6_RUP2), + ENUM2STRING(LEGS_S6_RUP3), + ENUM2STRING(LEGS_S6_RUP4), + ENUM2STRING(LEGS_S6_RUP5), + ENUM2STRING(LEGS_S7_LUP1), + ENUM2STRING(LEGS_S7_LUP2), + ENUM2STRING(LEGS_S7_LUP3), + ENUM2STRING(LEGS_S7_LUP4), + ENUM2STRING(LEGS_S7_LUP5), + ENUM2STRING(LEGS_S7_RUP1), + ENUM2STRING(LEGS_S7_RUP2), + ENUM2STRING(LEGS_S7_RUP3), + ENUM2STRING(LEGS_S7_RUP4), + ENUM2STRING(LEGS_S7_RUP5), + + //New anim as per Jarrod's request + ENUM2STRING(LEGS_TURN180), + + //====================================================== + //cinematic anims + //====================================================== + //# #sep BOTH_ CINEMATIC-ONLY + ENUM2STRING(BOTH_CIN_1), //# Level specific cinematic 1 + ENUM2STRING(BOTH_CIN_2), //# Level specific cinematic 2 + ENUM2STRING(BOTH_CIN_3), //# Level specific cinematic 3 + ENUM2STRING(BOTH_CIN_4), //# Level specific cinematic 4 + ENUM2STRING(BOTH_CIN_5), //# Level specific cinematic 5 + ENUM2STRING(BOTH_CIN_6), //# Level specific cinematic 6 + ENUM2STRING(BOTH_CIN_7), //# Level specific cinematic 7 + ENUM2STRING(BOTH_CIN_8), //# Level specific cinematic 8 + ENUM2STRING(BOTH_CIN_9), //# Level specific cinematic 9 + ENUM2STRING(BOTH_CIN_10), //# Level specific cinematic 10 + ENUM2STRING(BOTH_CIN_11), //# Level specific cinematic 11 + ENUM2STRING(BOTH_CIN_12), //# Level specific cinematic 12 + ENUM2STRING(BOTH_CIN_13), //# Level specific cinematic 13 + ENUM2STRING(BOTH_CIN_14), //# Level specific cinematic 14 + ENUM2STRING(BOTH_CIN_15), //# Level specific cinematic 15 + ENUM2STRING(BOTH_CIN_16), //# Level specific cinematic 16 + ENUM2STRING(BOTH_CIN_17), //# Level specific cinematic 17 + ENUM2STRING(BOTH_CIN_18), //# Level specific cinematic 18 + ENUM2STRING(BOTH_CIN_19), //# Level specific cinematic 19 + ENUM2STRING(BOTH_CIN_20), //# Level specific cinematic 20 + ENUM2STRING(BOTH_CIN_21), //# Level specific cinematic 21 + ENUM2STRING(BOTH_CIN_22), //# Level specific cinematic 22 + ENUM2STRING(BOTH_CIN_23), //# Level specific cinematic 23 + ENUM2STRING(BOTH_CIN_24), //# Level specific cinematic 24 + ENUM2STRING(BOTH_CIN_25), //# Level specific cinematic 25 + + ENUM2STRING(BOTH_CIN_26), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_27), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_28), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_29), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_30), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_31), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_32), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_33), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_34), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_35), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_36), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_37), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_38), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_39), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_40), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_41), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_42), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_43), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_44), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_45), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_46), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_47), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_48), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_49), //# Level specific cinematic + ENUM2STRING(BOTH_CIN_50), //# Level specific cinematic + + //must be terminated + NULL,-1 +}; +#endif // _XBOX / _UI diff --git a/code/cgame/cg_consolecmds.c b/code/cgame/cg_consolecmds.c new file mode 100644 index 0000000..d852383 --- /dev/null +++ b/code/cgame/cg_consolecmds.c @@ -0,0 +1,391 @@ +// 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" +#include "bg_saga.h" +extern menuDef_t *menuScoreboard; + + + +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 ("%s (%i %i %i) : %i\n", cgs.mapname, (int)cg.refdef.vieworg[0], + (int)cg.refdef.vieworg[1], (int)cg.refdef.vieworg[2], + (int)cg.refdef.viewangles[YAW]); +} + + +static void CG_ScoresDown_f( void ) { + + CG_BuildSpectatorString(); + 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; + } +} + +extern menuDef_t *menuScoreboard; +void Menu_Reset(); // FIXME: add to right include file + +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); + } +} + + +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(CG_GetStringEdString("MP_INGAME", "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(CG_GetStringEdString("MP_INGAME", "YOU_LOSE"), SCREEN_HEIGHT * .30, 0); +} + + +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 ); +} + + +/* +================== +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"); + } +} + +void CG_SiegeBriefingDisplay(int team, int dontshow); +static void CG_SiegeBriefing_f(void) +{ + int team; + + if (cgs.gametype != GT_SIEGE) + { //Cannot be displayed unless in this gametype + return; + } + + team = cg.predictedPlayerState.persistant[PERS_TEAM]; + + if (team != SIEGETEAM_TEAM1 && + team != SIEGETEAM_TEAM2) + { //cannot be displayed if not on a valid team + return; + } + + CG_SiegeBriefingDisplay(team, 0); +} + +static void CG_SiegeCvarUpdate_f(void) +{ + int team; + + if (cgs.gametype != GT_SIEGE) + { //Cannot be displayed unless in this gametype + return; + } + + team = cg.predictedPlayerState.persistant[PERS_TEAM]; + + if (team != SIEGETEAM_TEAM1 && + team != SIEGETEAM_TEAM2) + { //cannot be displayed if not on a valid team + return; + } + + CG_SiegeBriefingDisplay(team, 1); +} +static void CG_SiegeCompleteCvarUpdate_f(void) +{ + + if (cgs.gametype != GT_SIEGE) + { //Cannot be displayed unless in this gametype + return; + } + + // Set up cvars for both teams + CG_SiegeBriefingDisplay(SIEGETEAM_TEAM1, 1); + CG_SiegeBriefingDisplay(SIEGETEAM_TEAM2, 1); +} +/* +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 }, + { "sizeup", CG_SizeUp_f }, + { "sizedown", CG_SizeDown_f }, + { "weapnext", CG_NextWeapon_f }, + { "weapprev", CG_PrevWeapon_f }, + { "weapon", CG_Weapon_f }, + { "weaponclean", CG_WeaponClean_f }, + { "tell_target", CG_TellTarget_f }, + { "tell_attacker", CG_TellAttacker_f }, + { "tcmd", CG_TargetCommand_f }, + { "spWin", CG_spWin_f }, + { "spLose", CG_spLose_f }, + { "scoresDown", CG_scrollScoresDown_f }, + { "scoresUp", CG_scrollScoresUp_f }, + { "startOrbit", CG_StartOrbit_f }, + //{ "camera", CG_Camera_f }, + { "loaddeferred", CG_LoadDeferredPlayers }, + { "invnext", CG_NextInventory_f }, + { "invprev", CG_PrevInventory_f }, + { "forcenext", CG_NextForcePower_f }, + { "forceprev", CG_PrevForcePower_f }, + { "briefing", CG_SiegeBriefing_f }, + { "siegeCvarUpdate", CG_SiegeCvarUpdate_f }, + { "siegeCompleteCvarUpdate", CG_SiegeCompleteCvarUpdate_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 ("forcechanged"); + trap_AddCommand ("sv_invnext"); + trap_AddCommand ("sv_invprev"); + trap_AddCommand ("sv_forcenext"); + trap_AddCommand ("sv_forceprev"); + trap_AddCommand ("sv_saberswitch"); + trap_AddCommand ("engage_duel"); + trap_AddCommand ("force_heal"); + trap_AddCommand ("force_speed"); + trap_AddCommand ("force_throw"); + trap_AddCommand ("force_pull"); + trap_AddCommand ("force_distract"); + trap_AddCommand ("force_rage"); + trap_AddCommand ("force_protect"); + trap_AddCommand ("force_absorb"); + trap_AddCommand ("force_healother"); + trap_AddCommand ("force_forcepowerother"); + trap_AddCommand ("force_seeing"); + trap_AddCommand ("use_seeker"); + trap_AddCommand ("use_field"); + trap_AddCommand ("use_bacta"); + trap_AddCommand ("use_electrobinoculars"); + trap_AddCommand ("zoom"); + trap_AddCommand ("use_sentry"); + trap_AddCommand ("bot_order"); + trap_AddCommand ("saberAttackCycle"); + trap_AddCommand ("kill"); + trap_AddCommand ("say"); + trap_AddCommand ("say_team"); + trap_AddCommand ("tell"); + 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 +} diff --git a/code/cgame/cg_draw.c b/code/cgame/cg_draw.c new file mode 100644 index 0000000..03d753d --- /dev/null +++ b/code/cgame/cg_draw.c @@ -0,0 +1,8568 @@ +// 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" + +#include "bg_saga.h" + +#include "../ui/ui_shared.h" +#include "../ui/ui_public.h" + +extern float CG_RadiusForCent( centity_t *cent ); +qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y); +qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ); +static void CG_DrawSiegeTimer(int timeRemaining, qboolean isMyTeam); +static void CG_DrawSiegeDeathTimer( int timeRemaining ); +// nmckenzie: DUEL_HEALTH +void CG_DrawDuelistHealth ( float x, float y, float w, float h, int duelist ); + +// used for scoreboard +extern displayContextDef_t cgDC; +menuDef_t *menuScoreboard = NULL; +vec4_t bluehudtint = {0.5, 0.5, 1.0, 1.0}; +vec4_t redhudtint = {1.0, 0.5, 0.5, 1.0}; +float *hudTintColor; + +int sortedTeamPlayers[TEAM_MAXOVERLAY]; +int numSortedTeamPlayers; + +int lastvalidlockdif; + +extern float zoomFov; //this has to be global client-side + +char systemChat[256]; +char teamChat1[256]; +char teamChat2[256]; + +// The time at which you died and the time it will take for you to rejoin game. +int cg_siegeDeathTime = 0; + +#define MAX_HUD_TICS 4 +const char *armorTicName[MAX_HUD_TICS] = +{ +"armor_tic1", +"armor_tic2", +"armor_tic3", +"armor_tic4", +}; + +const char *healthTicName[MAX_HUD_TICS] = +{ +"health_tic1", +"health_tic2", +"health_tic3", +"health_tic4", +}; + +const char *forceTicName[MAX_HUD_TICS] = +{ +"force_tic1", +"force_tic2", +"force_tic3", +"force_tic4", +}; + +const char *ammoTicName[MAX_HUD_TICS] = +{ +"ammo_tic1", +"ammo_tic2", +"ammo_tic3", +"ammo_tic4", +}; + +char *showPowersName[] = +{ + "HEAL2",//FP_HEAL + "JUMP2",//FP_LEVITATION + "SPEED2",//FP_SPEED + "PUSH2",//FP_PUSH + "PULL2",//FP_PULL + "MINDTRICK2",//FP_TELEPTAHY + "GRIP2",//FP_GRIP + "LIGHTNING2",//FP_LIGHTNING + "DARK_RAGE2",//FP_RAGE + "PROTECT2",//FP_PROTECT + "ABSORB2",//FP_ABSORB + "TEAM_HEAL2",//FP_TEAM_HEAL + "TEAM_REPLENISH2",//FP_TEAM_FORCE + "DRAIN2",//FP_DRAIN + "SEEING2",//FP_SEE + "SABER_OFFENSE2",//FP_SABER_OFFENSE + "SABER_DEFENSE2",//FP_SABER_DEFENSE + "SABER_THROW2",//FP_SABERTHROW + NULL +}; + +//Called from UI shared code. For now we'll just redirect to the normal anim load function. +#include "../namespace_begin.h" + + +int UI_ParseAnimationFile(const char *filename, animation_t *animset, qboolean isHumanoid) +{ + return BG_ParseAnimationFile(filename, animset, isHumanoid); +} + +int MenuFontToHandle(int iMenuFont) +{ + switch (iMenuFont) + { + case FONT_SMALL: return cgDC.Assets.qhSmallFont; + case FONT_SMALL2: return cgDC.Assets.qhSmall2Font; + case FONT_MEDIUM: return cgDC.Assets.qhMediumFont; + case FONT_LARGE: return cgDC.Assets.qhMediumFont;//cgDC.Assets.qhBigFont; + //fixme? Big fonr isn't registered...? + } + + return cgDC.Assets.qhMediumFont; +} + +#include "../namespace_end.h" + +int CG_Text_Width(const char *text, float scale, int iMenuFont) +{ + int iFontIndex = MenuFontToHandle(iMenuFont); + + return trap_R_Font_StrLenPixels(text, iFontIndex, scale); +} + +int CG_Text_Height(const char *text, float scale, int iMenuFont) +{ + int iFontIndex = MenuFontToHandle(iMenuFont); + + return trap_R_Font_HeightPixels(iFontIndex, scale); +} + +#include "../qcommon/qfiles.h" // for STYLE_BLINK etc +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style, int iMenuFont) +{ + int iStyleOR = 0; + int iFontIndex = MenuFontToHandle(iMenuFont); + + switch (style) + { + case ITEM_TEXTSTYLE_NORMAL: iStyleOR = 0;break; // JK2 normal text + case ITEM_TEXTSTYLE_BLINK: iStyleOR = STYLE_BLINK;break; // JK2 fast blinking + case ITEM_TEXTSTYLE_PULSE: iStyleOR = STYLE_BLINK;break; // JK2 slow pulsing + case ITEM_TEXTSTYLE_SHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + case ITEM_TEXTSTYLE_OUTLINED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + case ITEM_TEXTSTYLE_OUTLINESHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + case ITEM_TEXTSTYLE_SHADOWEDMORE: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) + } + + trap_R_Font_DrawString( x, // int ox + y, // int oy + text, // const char *text + color, // paletteRGBA_c c + iStyleOR | iFontIndex, // const int iFontHandle + !limit?-1:limit, // iCharLimit (-1 = none) + scale // const float scale = 1.0f + ); +} + +/* +qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, int *x, int *y) + + Take any world coord and convert it to a 2D virtual 640x480 screen coord +*/ +/* +qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) +{ + int xcenter, ycenter; + vec3_t local, transformed; + +// xcenter = cg.refdef.width / 2;//gives screen coords adjusted for resolution +// ycenter = cg.refdef.height / 2;//gives screen coords adjusted for resolution + + //NOTE: did it this way because most draw functions expect virtual 640x480 coords + // and adjust them for current resolution + xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn + ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn + + VectorSubtract (worldCoord, cg.refdef.vieworg, local); + + transformed[0] = DotProduct(local,vright); + transformed[1] = DotProduct(local,vup); + transformed[2] = DotProduct(local,vfwd); + + // Make sure Z is not negative. + if(transformed[2] < 0.01) + { + return qfalse; + } + // Simple convert to screen coords. + float xzi = xcenter / transformed[2] * (90.0/cg.refdef.fov_x); + float yzi = ycenter / transformed[2] * (90.0/cg.refdef.fov_y); + + *x = xcenter + xzi * transformed[0]; + *y = ycenter - yzi * transformed[1]; + + return qtrue; +} + +qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) +{ + float xF, yF; + qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ); + *x = (int)xF; + *y = (int)yF; + return retVal; +} +*/ + +/* +================ +CG_DrawZoomMask + +================ +*/ +static void CG_DrawZoomMask( void ) +{ + vec4_t color1; + float level; + static qboolean flip = qtrue; + +// int ammo = cg_entities[0].gent->client->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex]; + float cx, cy; +// int val[5]; + float max, fi; + + // Check for Binocular specific zooming since we'll want to render different bits in each case + if ( cg.predictedPlayerState.zoomMode == 2 ) + { + int val, i; + float off; + + // zoom level + level = (float)(80.0f - cg.predictedPlayerState.zoomFov) / 80.0f; + + // ...so we'll clamp it + if ( level < 0.0f ) + { + level = 0.0f; + } + else if ( level > 1.0f ) + { + level = 1.0f; + } + + // Using a magic number to convert the zoom level to scale amount + level *= 162.0f; + + // draw blue tinted distortion mask, trying to make it as small as is necessary to fill in the viewable area + trap_R_SetColor( colorTable[CT_WHITE] ); + CG_DrawPic( 34, 48, 570, 362, cgs.media.binocularStatic ); + + // Black out the area behind the numbers + trap_R_SetColor( colorTable[CT_BLACK]); + CG_DrawPic( 212, 367, 200, 40, cgs.media.whiteShader ); + + // Numbers should be kind of greenish + color1[0] = 0.2f; + color1[1] = 0.4f; + color1[2] = 0.2f; + color1[3] = 0.3f; + trap_R_SetColor( color1 ); + + // Draw scrolling numbers, use intervals 10 units apart--sorry, this section of code is just kind of hacked + // up with a bunch of magic numbers..... + val = ((int)((cg.refdef.viewangles[YAW] + 180) / 10)) * 10; + off = (cg.refdef.viewangles[YAW] + 180) - val; + + for ( i = -10; i < 30; i += 10 ) + { + val -= 10; + + if ( val < 0 ) + { + val += 360; + } + + // we only want to draw the very far left one some of the time, if it's too far to the left it will + // poke outside the mask. + if (( off > 3.0f && i == -10 ) || i > -10 ) + { + // draw the value, but add 200 just to bump the range up...arbitrary, so change it if you like + CG_DrawNumField( 155 + i * 10 + off * 10, 374, 3, val + 200, 24, 14, NUM_FONT_CHUNKY, qtrue ); + CG_DrawPic( 245 + (i-1) * 10 + off * 10, 376, 6, 6, cgs.media.whiteShader ); + } + } + + CG_DrawPic( 212, 367, 200, 28, cgs.media.binocularOverlay ); + + color1[0] = sin( cg.time * 0.01f ) * 0.5f + 0.5f; + color1[0] = color1[0] * color1[0]; + color1[1] = color1[0]; + color1[2] = color1[0]; + color1[3] = 1.0f; + + trap_R_SetColor( color1 ); + + CG_DrawPic( 82, 94, 16, 16, cgs.media.binocularCircle ); + + // Flickery color + color1[0] = 0.7f + crandom() * 0.1f; + color1[1] = 0.8f + crandom() * 0.1f; + color1[2] = 0.7f + crandom() * 0.1f; + color1[3] = 1.0f; + trap_R_SetColor( color1 ); + + CG_DrawPic( 0, 0, 640, 480, cgs.media.binocularMask ); + + CG_DrawPic( 4, 282 - level, 16, 16, cgs.media.binocularArrow ); + + // The top triangle bit randomly flips + if ( flip ) + { + CG_DrawPic( 330, 60, -26, -30, cgs.media.binocularTri ); + } + else + { + CG_DrawPic( 307, 40, 26, 30, cgs.media.binocularTri ); + } + + if ( random() > 0.98f && ( cg.time & 1024 )) + { + flip = !flip; + } + } + else if ( cg.predictedPlayerState.zoomMode) + { + // disruptor zoom mode + level = (float)(50.0f - zoomFov) / 50.0f;//(float)(80.0f - zoomFov) / 80.0f; + + // ...so we'll clamp it + if ( level < 0.0f ) + { + level = 0.0f; + } + else if ( level > 1.0f ) + { + level = 1.0f; + } + + // Using a magic number to convert the zoom level to a rotation amount that correlates more or less with the zoom artwork. + level *= 103.0f; + + // Draw target mask + trap_R_SetColor( colorTable[CT_WHITE] ); + CG_DrawPic( 0, 0, 640, 480, cgs.media.disruptorMask ); + + // apparently 99.0f is the full zoom level + if ( level >= 99 ) + { + // Fully zoomed, so make the rotating insert pulse + color1[0] = 1.0f; + color1[1] = 1.0f; + color1[2] = 1.0f; + color1[3] = 0.7f + sin( cg.time * 0.01f ) * 0.3f; + + trap_R_SetColor( color1 ); + } + + // Draw rotating insert + CG_DrawRotatePic2( 320, 240, 640, 480, -level, cgs.media.disruptorInsert ); + + // Increase the light levels under the center of the target +// CG_DrawPic( 198, 118, 246, 246, cgs.media.disruptorLight ); + + // weirdness.....converting ammo to a base five number scale just to be geeky. +/* val[0] = ammo % 5; + val[1] = (ammo / 5) % 5; + val[2] = (ammo / 25) % 5; + val[3] = (ammo / 125) % 5; + val[4] = (ammo / 625) % 5; + + color1[0] = 0.2f; + color1[1] = 0.55f + crandom() * 0.1f; + color1[2] = 0.5f + crandom() * 0.1f; + color1[3] = 1.0f; + trap_R_SetColor( color1 ); + + for ( int t = 0; t < 5; t++ ) + { + cx = 320 + sin( (t*10+45)/57.296f ) * 192; + cy = 240 + cos( (t*10+45)/57.296f ) * 192; + + CG_DrawRotatePic2( cx, cy, 24, 38, 45 - t * 10, trap_R_RegisterShader( va("gfx/2d/char%d",val[4-t] ))); + } +*/ + //max = ( cg_entities[0].gent->health / 100.0f ); + + + if ( (cg.snap->ps.eFlags & EF_DOUBLE_AMMO) ) + { + max = cg.snap->ps.ammo[weaponData[WP_DISRUPTOR].ammoIndex] / ((float)ammoData[weaponData[WP_DISRUPTOR].ammoIndex].max*2.0f); + } + else + { + max = cg.snap->ps.ammo[weaponData[WP_DISRUPTOR].ammoIndex] / (float)ammoData[weaponData[WP_DISRUPTOR].ammoIndex].max; + } + if ( max > 1.0f ) + { + max = 1.0f; + } + + color1[0] = (1.0f - max) * 2.0f; + color1[1] = max * 1.5f; + color1[2] = 0.0f; + color1[3] = 1.0f; + + // If we are low on health, make us flash + if ( max < 0.15f && ( cg.time & 512 )) + { + VectorClear( color1 ); + } + + if ( color1[0] > 1.0f ) + { + color1[0] = 1.0f; + } + + if ( color1[1] > 1.0f ) + { + color1[1] = 1.0f; + } + + trap_R_SetColor( color1 ); + + max *= 58.0f; + + for (fi = 18.5f; fi <= 18.5f + max; fi+= 3 ) // going from 15 to 45 degrees, with 5 degree increments + { + cx = 320 + sin( (fi+90.0f)/57.296f ) * 190; + cy = 240 + cos( (fi+90.0f)/57.296f ) * 190; + + CG_DrawRotatePic2( cx, cy, 12, 24, 90 - fi, cgs.media.disruptorInsertTick ); + } + + if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT ) + { + trap_R_SetColor( colorTable[CT_WHITE] ); + + // draw the charge level + max = ( cg.time - cg.predictedPlayerState.weaponChargeTime ) / ( 50.0f * 30.0f ); // bad hardcodedness 50 is disruptor charge unit and 30 is max charge units allowed. + + if ( max > 1.0f ) + { + max = 1.0f; + } + + trap_R_DrawStretchPic(257, 435, 134*max, 34, 0, 0, max, 1, cgs.media.disruptorChargeShader); + } +// trap_R_SetColor( colorTable[CT_WHITE] ); +// CG_DrawPic( 0, 0, 640, 480, cgs.media.disruptorMask ); + + } +} + + +/* +================ +CG_Draw3DModel + +================ +*/ +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, void *ghoul2, int g2radius, qhandle_t skin, vec3_t origin, vec3_t angles ) { + refdef_t refdef; + refEntity_t ent; + + if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { + return; + } + + memset( &refdef, 0, sizeof( refdef ) ); + + memset( &ent, 0, sizeof( ent ) ); + AnglesToAxis( angles, ent.axis ); + VectorCopy( origin, ent.origin ); + ent.hModel = model; + ent.ghoul2 = ghoul2; + ent.radius = g2radius; + 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 ) +{ + clientInfo_t *ci; + + if (clientNum >= MAX_CLIENTS) + { //npc? + return; + } + + ci = &cgs.clientinfo[ clientNum ]; + + 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 = 0;//cgs.media.neutralFlagModel; + } else { + return; + } + CG_Draw3DModel( x, y, w, h, handle, NULL, 0, 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 ); + } + } +} + +/* +================ +DrawAmmo +================ +*/ +void DrawAmmo() +{ + int x, y; + + x = SCREEN_WIDTH-80; + y = SCREEN_HEIGHT-80; + +} + + + +/* +================ +CG_DrawHealth +================ +*/ +void CG_DrawHealth( menuDef_t *menuHUD ) +{ + vec4_t calcColor; + playerState_t *ps; + int healthAmt; + int i,currValue,inc; + itemDef_t *focusItem; + float percent; + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + ps = &cg.snap->ps; + + // What's the health? + healthAmt = ps->stats[STAT_HEALTH]; + if (healthAmt > ps->stats[STAT_MAX_HEALTH]) + { + healthAmt = ps->stats[STAT_MAX_HEALTH]; + } + + + inc = (float) ps->stats[STAT_MAX_HEALTH] / MAX_HUD_TICS; + currValue = healthAmt; + + // Print the health tics, fading out the one which is partial health + for (i=(MAX_HUD_TICS-1);i>=0;i--) + { + focusItem = Menu_FindItemByName(menuHUD, healthTicName[i]); + + if (!focusItem) // This is bad + { + continue; + } + + memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + percent = (float) currValue / inc; + calcColor[3] *= percent; // Fade it out + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + + currValue -= inc; + } + + // Print the mueric amount + focusItem = Menu_FindItemByName(menuHUD, "healthamount"); + if (focusItem) + { + // Print health amount + trap_R_SetColor( focusItem->window.foreColor ); + + CG_DrawNumField ( + focusItem->window.rect.x, + focusItem->window.rect.y, + 3, + ps->stats[STAT_HEALTH], + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); + } + +} + +/* +================ +CG_DrawArmor +================ +*/ +void CG_DrawArmor( menuDef_t *menuHUD ) +{ + vec4_t calcColor; + playerState_t *ps; + int armor, maxArmor; + itemDef_t *focusItem; + float percent,quarterArmor; + int i,currValue,inc; + + //ps = &cg.snap->ps; + ps = &cg.predictedPlayerState; + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + armor = ps->stats[STAT_ARMOR]; + maxArmor = ps->stats[STAT_MAX_HEALTH]; + + if (armor> maxArmor) + { + armor = maxArmor; + } + + currValue = armor; + inc = (float) maxArmor / MAX_HUD_TICS; + + memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + for (i=(MAX_HUD_TICS-1);i>=0;i--) + { + focusItem = Menu_FindItemByName(menuHUD, armorTicName[i]); + + if (!focusItem) // This is bad + { + continue; + } + + memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + percent = (float) currValue / inc; + calcColor[3] *= percent; + } + + trap_R_SetColor( calcColor); + + if ((i==(MAX_HUD_TICS-1)) && (currValue < inc)) + { + if (cg.HUDArmorFlag) + { + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + } + else + { + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + + currValue -= inc; + } + + focusItem = Menu_FindItemByName(menuHUD, "armoramount"); + + if (focusItem) + { + // Print armor amount + trap_R_SetColor( focusItem->window.foreColor ); + + CG_DrawNumField ( + focusItem->window.rect.x, + focusItem->window.rect.y, + 3, + armor, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); + } + + // If armor is low, flash a graphic to warn the player + if (armor) // Is there armor? Draw the HUD Armor TIC + { + quarterArmor = (float) (ps->stats[STAT_MAX_HEALTH] / 4.0f); + + // Make tic flash if armor is at 25% of full armor + if (ps->stats[STAT_ARMOR] < quarterArmor) // Do whatever the flash timer says + { + if (cg.HUDTickFlashTime < cg.time) // Flip at the same time + { + cg.HUDTickFlashTime = cg.time + 400; + if (cg.HUDArmorFlag) + { + cg.HUDArmorFlag = qfalse; + } + else + { + cg.HUDArmorFlag = qtrue; + } + } + } + else + { + cg.HUDArmorFlag=qtrue; + } + } + else // No armor? Don't show it. + { + cg.HUDArmorFlag=qfalse; + } + +} + +/* +================ +CG_DrawSaberStyle + +If the weapon is a light saber (which needs no ammo) then draw a graphic showing +the saber style (fast, medium, strong) +================ +*/ +static void CG_DrawSaberStyle( centity_t *cent, menuDef_t *menuHUD) +{ + itemDef_t *focusItem; + + if (!cent->currentState.weapon ) // We don't have a weapon right now + { + return; + } + + if ( cent->currentState.weapon != WP_SABER ) + { + return; + } + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + + // draw the current saber style in this window + switch ( cg.predictedPlayerState.fd.saberDrawAnimLevel ) + { + case 1://FORCE_LEVEL_1: + case 5://FORCE_LEVEL_5://Tavion + + focusItem = Menu_FindItemByName(menuHUD, "saberstyle_fast"); + + if (focusItem) + { + trap_R_SetColor( hudTintColor ); + + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + + break; + case 2://FORCE_LEVEL_2: + case 6://SS_DUAL + case 7://SS_STAFF + focusItem = Menu_FindItemByName(menuHUD, "saberstyle_medium"); + + if (focusItem) + { + trap_R_SetColor( hudTintColor ); + + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + break; + case 3://FORCE_LEVEL_3: + case 4://FORCE_LEVEL_4://Desann + focusItem = Menu_FindItemByName(menuHUD, "saberstyle_strong"); + + if (focusItem) + { + trap_R_SetColor( hudTintColor ); + + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + break; + } + +} + +/* +================ +CG_DrawAmmo +================ +*/ +static void CG_DrawAmmo( centity_t *cent,menuDef_t *menuHUD) +{ + playerState_t *ps; + int i; + vec4_t calcColor; + float value,inc = 0.0f,percent; + itemDef_t *focusItem; + + ps = &cg.snap->ps; + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + if (!cent->currentState.weapon ) // We don't have a weapon right now + { + return; + } + + value = ps->ammo[weaponData[cent->currentState.weapon].ammoIndex]; + if (value < 0) // No ammo + { + return; + } + + focusItem = Menu_FindItemByName(menuHUD, "ammoamount"); + trap_R_SetColor( hudTintColor ); + + if (weaponData[cent->currentState.weapon].energyPerShot == 0 && + weaponData[cent->currentState.weapon].altEnergyPerShot == 0) + { //just draw "infinite" + inc = 8 / MAX_HUD_TICS; + value = 8; + + focusItem = Menu_FindItemByName(menuHUD, "ammoinfinite"); + trap_R_SetColor( hudTintColor ); + if (focusItem) + { + UI_DrawProportionalString(focusItem->window.rect.x, focusItem->window.rect.y, "--", NUM_FONT_SMALL, focusItem->window.foreColor); + } + } + else + { + focusItem = Menu_FindItemByName(menuHUD, "ammoamount"); + trap_R_SetColor( hudTintColor ); + if (focusItem) + { + + if ( (cent->currentState.eFlags & EF_DOUBLE_AMMO) ) + { + inc = (float) (ammoData[weaponData[cent->currentState.weapon].ammoIndex].max*2.0f) / MAX_HUD_TICS; + } + else + { + inc = (float) ammoData[weaponData[cent->currentState.weapon].ammoIndex].max / MAX_HUD_TICS; + } + value =ps->ammo[weaponData[cent->currentState.weapon].ammoIndex]; + + CG_DrawNumField ( + focusItem->window.rect.x, + focusItem->window.rect.y, + 3, + value, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); + } + } + + // Draw tics + for (i=MAX_HUD_TICS-1;i>=0;i--) + { + focusItem = Menu_FindItemByName(menuHUD, ammoTicName[i]); + + if (!focusItem) + { + continue; + } + + memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + + if ( value <= 0 ) // done + { + break; + } + else if (value < inc) // partial tic + { + percent = value / inc; + calcColor[3] = percent; + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + + value -= inc; + } + +} + +/* +================ +CG_DrawForcePower +================ +*/ +void CG_DrawForcePower( menuDef_t *menuHUD ) +{ + int i; + vec4_t calcColor; + float value,inc,percent; + itemDef_t *focusItem; + const int maxForcePower = 100; + qboolean flash=qfalse; + + // Can we find the menu? + if (!menuHUD) + { + return; + } + + // Make the hud flash by setting forceHUDTotalFlashTime above cg.time + if (cg.forceHUDTotalFlashTime > cg.time ) + { + flash = qtrue; + if (cg.forceHUDNextFlashTime < cg.time) + { + cg.forceHUDNextFlashTime = cg.time + 400; + trap_S_StartSound (NULL, 0, CHAN_LOCAL, cgs.media.noforceSound ); + + if (cg.forceHUDActive) + { + cg.forceHUDActive = qfalse; + } + else + { + cg.forceHUDActive = qtrue; + } + + } + } + else // turn HUD back on if it had just finished flashing time. + { + cg.forceHUDNextFlashTime = 0; + cg.forceHUDActive = qtrue; + } + +// if (!cg.forceHUDActive) +// { +// return; +// } + + inc = (float) maxForcePower / MAX_HUD_TICS; + value = cg.snap->ps.fd.forcePower; + + for (i=MAX_HUD_TICS-1;i>=0;i--) + { + focusItem = Menu_FindItemByName(menuHUD, forceTicName[i]); + + if (!focusItem) + { + continue; + } + +// memcpy(calcColor, hudTintColor, sizeof(vec4_t)); + + if ( value <= 0 ) // done + { + break; + } + else if (value < inc) // partial tic + { + if (flash) + { + memcpy(calcColor, colorTable[CT_RED], sizeof(vec4_t)); + } + else + { + memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); + } + + percent = value / inc; + calcColor[3] = percent; + } + else + { + if (flash) + { + memcpy(calcColor, colorTable[CT_RED], sizeof(vec4_t)); + } + else + { + memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); + } + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + + value -= inc; + } + + focusItem = Menu_FindItemByName(menuHUD, "forceamount"); + + if (focusItem) + { + // Print force amount + trap_R_SetColor( focusItem->window.foreColor ); + + CG_DrawNumField ( + focusItem->window.rect.x, + focusItem->window.rect.y, + 3, + cg.snap->ps.fd.forcePower, + focusItem->window.rect.w, + focusItem->window.rect.h, + NUM_FONT_SMALL, + qfalse); + } +} + +/* +================ +CG_DrawHUD +================ +*/ +void CG_DrawHUD(centity_t *cent) +{ + menuDef_t *menuHUD = NULL; + itemDef_t *focusItem = NULL; + const char *scoreStr = NULL; + int scoreBias; + char scoreBiasStr[16]; + + if (cg_hudFiles.integer) + { + int x = 0; + int y = SCREEN_HEIGHT-80; + char ammoString[64]; + int weapX = x; + + UI_DrawProportionalString( x+16, y+40, va( "%i", cg.snap->ps.stats[STAT_HEALTH] ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_RED] ); + + UI_DrawProportionalString( x+18+14, y+40+14, va( "%i", cg.snap->ps.stats[STAT_ARMOR] ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_GREEN] ); + + if (cg.snap->ps.weapon == WP_SABER) + { + if (cg.snap->ps.fd.saberDrawAnimLevel == SS_DUAL) + { + Com_sprintf(ammoString, sizeof(ammoString), "AKIMBO"); + weapX += 16; + } + else if (cg.snap->ps.fd.saberDrawAnimLevel == SS_STAFF) + { + Com_sprintf(ammoString, sizeof(ammoString), "STAFF"); + weapX += 16; + } + else if (cg.snap->ps.fd.saberDrawAnimLevel == FORCE_LEVEL_3) + { + Com_sprintf(ammoString, sizeof(ammoString), "STRONG"); + weapX += 16; + } + else if (cg.snap->ps.fd.saberDrawAnimLevel == FORCE_LEVEL_2) + { + Com_sprintf(ammoString, sizeof(ammoString), "MEDIUM"); + weapX += 16; + } + else + { + Com_sprintf(ammoString, sizeof(ammoString), "FAST"); + } + } + else + { + Com_sprintf(ammoString, sizeof(ammoString), "%i", cg.snap->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex]); + } + + UI_DrawProportionalString( SCREEN_WIDTH-(weapX+16+32), y+40, va( "%s", ammoString ), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_ORANGE] ); + + UI_DrawProportionalString( SCREEN_WIDTH-(x+18+14+32), y+40+14, va( "%i", cg.snap->ps.fd.forcePower), + UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_ICON_BLUE] ); + + return; + } + + if (cgs.gametype >= GT_TEAM && cgs.gametype != GT_SIEGE) + { // tint the hud items based on team + if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) + hudTintColor = redhudtint; + else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) + hudTintColor = bluehudtint; + else // If we're not on a team for whatever reason, leave things as they are. + hudTintColor = colorTable[CT_WHITE]; + } + else + { // tint the hud items white (dont' tint) + hudTintColor = colorTable[CT_WHITE]; + } + + // Draw the left HUD + menuHUD = Menus_FindByName("lefthud"); + if (menuHUD) + { + itemDef_t *focusItem; + + // Print scanline + focusItem = Menu_FindItemByName(menuHUD, "scanline"); + if (focusItem) + { + trap_R_SetColor( hudTintColor ); + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + + // Print frame + focusItem = Menu_FindItemByName(menuHUD, "frame"); + if (focusItem) + { + trap_R_SetColor( hudTintColor ); + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + + if (cg.predictedPlayerState.pm_type != PM_SPECTATOR) + { + CG_DrawArmor(menuHUD); + CG_DrawHealth(menuHUD); + } + } + else + { + //CG_Error("CG_ChatBox_ArrayInsert: unable to locate HUD menu file "); + } + + //scoreStr = va("Score: %i", cgs.clientinfo[cg.snap->ps.clientNum].score); + if ( cgs.gametype == GT_DUEL ) + {//A duel that requires more than one kill to knock the current enemy back to the queue + //show current kills out of how many needed + scoreStr = va("%s: %i/%i", CG_GetStringEdString("MP_INGAME", "SCORE"), cg.snap->ps.persistant[PERS_SCORE], cgs.fraglimit); + } + else if (0 && cgs.gametype < GT_TEAM ) + { // This is a teamless mode, draw the score bias. + scoreBias = cg.snap->ps.persistant[PERS_SCORE] - cgs.scores1; + if (scoreBias == 0) + { // We are the leader! + if (cgs.scores2 <= 0) + { // Nobody to be ahead of yet. + Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), ""); + } + else + { + scoreBias = cg.snap->ps.persistant[PERS_SCORE] - cgs.scores2; + if (scoreBias == 0) + { + Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (Tie)"); + } + else + { + Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (+%d)", scoreBias); + } + } + } + else // if (scoreBias < 0) + { // We are behind! + Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (%d)", scoreBias); + } + scoreStr = va("%s: %i%s", CG_GetStringEdString("MP_INGAME", "SCORE"), cg.snap->ps.persistant[PERS_SCORE], scoreBiasStr); + } + else + { // Don't draw a bias. + scoreStr = va("%s: %i", CG_GetStringEdString("MP_INGAME", "SCORE"), cg.snap->ps.persistant[PERS_SCORE]); + } + + menuHUD = Menus_FindByName("righthud"); + + if (menuHUD) + { + if (cgs.gametype != GT_POWERDUEL) + { + focusItem = Menu_FindItemByName(menuHUD, "score_line"); + if (focusItem) + { + UI_DrawScaledProportionalString( + focusItem->window.rect.x, + focusItem->window.rect.y, + scoreStr, + UI_RIGHT|UI_DROPSHADOW, + focusItem->window.foreColor, + 0.7); + } + } + + // Print scanline + focusItem = Menu_FindItemByName(menuHUD, "scanline"); + if (focusItem) + { + trap_R_SetColor( hudTintColor ); + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + + focusItem = Menu_FindItemByName(menuHUD, "frame"); + if (focusItem) + { + trap_R_SetColor( hudTintColor ); + CG_DrawPic( + focusItem->window.rect.x, + focusItem->window.rect.y, + focusItem->window.rect.w, + focusItem->window.rect.h, + focusItem->window.background + ); + } + + CG_DrawForcePower(menuHUD); + + // Draw ammo tics or saber style + if ( cent->currentState.weapon == WP_SABER ) + { + CG_DrawSaberStyle(cent,menuHUD); + } + else + { + CG_DrawAmmo(cent,menuHUD); + } + } + else + { + //CG_Error("CG_ChatBox_ArrayInsert: unable to locate HUD menu file "); + } +} + +#define MAX_SHOWPOWERS NUM_FORCE_POWERS + +qboolean ForcePower_Valid(int i) +{ + if (i == FP_LEVITATION || + i == FP_SABER_OFFENSE || + i == FP_SABER_DEFENSE || + i == FP_SABERTHROW) + { + return qfalse; + } + + if (cg.snap->ps.fd.forcePowersKnown & (1 << i)) + { + return qtrue; + } + + return qfalse; +} + +/* +=================== +CG_DrawForceSelect +=================== +*/ +#ifdef _XBOX +extern bool CL_ExtendSelectTime(void); +#endif +void CG_DrawForceSelect( void ) +{ + int i; + int count; + int smallIconSize,bigIconSize; + int holdX,x,y,x2,y2,pad,length; + int sideLeftIconCnt,sideRightIconCnt; + int sideMax,holdCount,iconCnt; + int yOffset = 0; + + + x2 = 0; + y2 = 0; + + // don't display if dead + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) + { + return; + } + + if ((cg.forceSelectTime+WEAPON_SELECT_TIME)ps.fd.forcePowerSelected; + return; + } + + if (!cg.snap->ps.fd.forcePowersKnown) + { + return; + } + +#ifdef _XBOX + if(CL_ExtendSelectTime()) { + cg.forceSelectTime = cg.time; + } + yOffset = -50; +#endif + + // count the number of powers owned + count = 0; + + for (i=0;i < NUM_FORCE_POWERS;++i) + { + if (ForcePower_Valid(i)) + { + count++; + } + } + + if (count == 0) // If no force powers, don't display + { + return; + } + + sideMax = 3; // Max number of icons on the side + + // Calculate how many icons will appear to either side of the center one + holdCount = count - 1; // -1 for the center icon + if (holdCount == 0) // No icons to either side + { + sideLeftIconCnt = 0; + sideRightIconCnt = 0; + } + else if (count > (2*sideMax)) // Go to the max on each side + { + sideLeftIconCnt = sideMax; + sideRightIconCnt = sideMax; + } + else // Less than max, so do the calc + { + sideLeftIconCnt = holdCount/2; + sideRightIconCnt = holdCount - sideLeftIconCnt; + } + + smallIconSize = 30; + bigIconSize = 60; + pad = 12; + + x = 320; + y = 425; + + // Background + length = (sideLeftIconCnt * smallIconSize) + (sideLeftIconCnt*pad) + + bigIconSize + (sideRightIconCnt * smallIconSize) + (sideRightIconCnt*pad) + 12; + + i = BG_ProperForceIndex(cg.forceSelect) - 1; + if (i < 0) + { + i = MAX_SHOWPOWERS; + } + + trap_R_SetColor(NULL); + // Work backwards from current icon + holdX = x - ((bigIconSize/2) + pad + smallIconSize); + for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--) + { + if (i < 0) + { + i = MAX_SHOWPOWERS; + } + + if (!ForcePower_Valid(forcePowerSorted[i])) // Does he have this power? + { + continue; + } + + ++iconCnt; // Good icon + + if (cgs.media.forcePowerIcons[forcePowerSorted[i]]) + { + CG_DrawPic( holdX, y + yOffset, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] ); + holdX -= (smallIconSize+pad); + } + } + + if (ForcePower_Valid(cg.forceSelect)) + { + // Current Center Icon + if (cgs.media.forcePowerIcons[cg.forceSelect]) + { + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2)) + yOffset, bigIconSize, bigIconSize, cgs.media.forcePowerIcons[cg.forceSelect] ); //only cache the icon for display + } + } + + i = BG_ProperForceIndex(cg.forceSelect) + 1; + if (i>MAX_SHOWPOWERS) + { + i = 0; + } + + // Work forwards from current icon + holdX = x + (bigIconSize/2) + pad; + for (iconCnt=1;iconCnt<(sideRightIconCnt+1);i++) + { + if (i>MAX_SHOWPOWERS) + { + i = 0; + } + + if (!ForcePower_Valid(forcePowerSorted[i])) // Does he have this power? + { + continue; + } + + ++iconCnt; // Good icon + + if (cgs.media.forcePowerIcons[forcePowerSorted[i]]) + { + CG_DrawPic( holdX, y + yOffset, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] ); //only cache the icon for display + holdX += (smallIconSize+pad); + } + } + + if ( showPowersName[cg.forceSelect] ) + { + UI_DrawProportionalString(320, y + 30 + yOffset, CG_GetStringEdString("SP_INGAME", showPowersName[cg.forceSelect]), UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]); + } +} + +/* +=================== +CG_DrawInventorySelect +=================== +*/ +void CG_DrawInvenSelect( void ) +{ + int i; + int sideMax,holdCount,iconCnt; + int smallIconSize,bigIconSize; + int sideLeftIconCnt,sideRightIconCnt; + int count; + int holdX,x,y,y2,pad; + int height; + float addX; + + // don't display if dead + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) + { + return; + } + + if ((cg.invenSelectTime+WEAPON_SELECT_TIME)ps.stats[STAT_HOLDABLE_ITEM] || !cg.snap->ps.stats[STAT_HOLDABLE_ITEMS]) + { + return; + } + +#ifdef _XBOX + if(CL_ExtendSelectTime()) { + cg.invenSelectTime = cg.time; + } +#endif + + if (cg.itemSelect == -1) + { + cg.itemSelect = bg_itemlist[cg.snap->ps.stats[STAT_HOLDABLE_ITEM]].giTag; + } + +//const int bits = cg.snap->ps.stats[ STAT_ITEMS ]; + + // count the number of items owned + count = 0; + for ( i = 0 ; i < HI_NUM_HOLDABLE ; i++ ) + { + if (/*CG_InventorySelectable(i) && inv_icons[i]*/ + (cg.snap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) ) + { + count++; + } + } + + if (!count) + { + y2 = 0; //err? + UI_DrawProportionalString(320, y2 + 22, "EMPTY INVENTORY", UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]); + return; + } + + sideMax = 3; // Max number of icons on the side + + // Calculate how many icons will appear to either side of the center one + holdCount = count - 1; // -1 for the center icon + if (holdCount == 0) // No icons to either side + { + sideLeftIconCnt = 0; + sideRightIconCnt = 0; + } + else if (count > (2*sideMax)) // Go to the max on each side + { + sideLeftIconCnt = sideMax; + sideRightIconCnt = sideMax; + } + else // Less than max, so do the calc + { + sideLeftIconCnt = holdCount/2; + sideRightIconCnt = holdCount - sideLeftIconCnt; + } + + i = cg.itemSelect - 1; + if (i<0) + { + i = HI_NUM_HOLDABLE-1; + } + + smallIconSize = 40; + bigIconSize = 80; + pad = 16; + + x = 320; + y = 410; + + // Left side ICONS + // Work backwards from current icon + holdX = x - ((bigIconSize/2) + pad + smallIconSize); + height = smallIconSize * cg.iconHUDPercent; + addX = (float) smallIconSize * .75; + + for (iconCnt=0;iconCntps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) || i == cg.itemSelect ) + { + continue; + } + + ++iconCnt; // Good icon + + if (!BG_IsItemSelectable(&cg.predictedPlayerState, i)) + { + continue; + } + + if (cgs.media.invenIcons[i]) + { + trap_R_SetColor(NULL); + CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, cgs.media.invenIcons[i] ); + + trap_R_SetColor(colorTable[CT_ICON_BLUE]); + /*CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12, + NUM_FONT_SMALL,qfalse); + */ + + holdX -= (smallIconSize+pad); + } + } + + // Current Center Icon + height = bigIconSize * cg.iconHUDPercent; + if (cgs.media.invenIcons[cg.itemSelect] && BG_IsItemSelectable(&cg.predictedPlayerState, cg.itemSelect)) + { + int itemNdex; + trap_R_SetColor(NULL); + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10, bigIconSize, bigIconSize, cgs.media.invenIcons[cg.itemSelect] ); + addX = (float) bigIconSize * .75; + trap_R_SetColor(colorTable[CT_ICON_BLUE]); + /*CG_DrawNumField ((x-(bigIconSize/2)) + addX, y, 2, cg.snap->ps.inventory[cg.inventorySelect], 6, 12, + NUM_FONT_SMALL,qfalse);*/ + + itemNdex = BG_GetItemIndexByTag(cg.itemSelect, IT_HOLDABLE); + if (bg_itemlist[itemNdex].classname) + { + vec4_t textColor = { .312f, .75f, .621f, 1.0f }; + char text[1024]; + char upperKey[1024]; + + strcpy(upperKey, bg_itemlist[itemNdex].classname); + + if ( trap_SP_GetStringTextString( va("SP_INGAME_%s",Q_strupr(upperKey)), text, sizeof( text ))) + { + UI_DrawProportionalString(320, y+45, text, UI_CENTER | UI_SMALLFONT, textColor); + } + else + { + UI_DrawProportionalString(320, y+45, bg_itemlist[itemNdex].classname, UI_CENTER | UI_SMALLFONT, textColor); + } + } + } + + i = cg.itemSelect + 1; + if (i> HI_NUM_HOLDABLE-1) + { + i = 0; + } + + // Right side ICONS + // Work forwards from current icon + holdX = x + (bigIconSize/2) + pad; + height = smallIconSize * cg.iconHUDPercent; + addX = (float) smallIconSize * .75; + for (iconCnt=0;iconCnt HI_NUM_HOLDABLE-1) + { + i = 0; + } + + if ( !(cg.snap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) || i == cg.itemSelect ) + { + continue; + } + + ++iconCnt; // Good icon + + if (!BG_IsItemSelectable(&cg.predictedPlayerState, i)) + { + continue; + } + + if (cgs.media.invenIcons[i]) + { + trap_R_SetColor(NULL); + CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, cgs.media.invenIcons[i] ); + + trap_R_SetColor(colorTable[CT_ICON_BLUE]); + /*CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12, + NUM_FONT_SMALL,qfalse);*/ + + holdX += (smallIconSize+pad); + } + } +} + +int cg_targVeh = ENTITYNUM_NONE; +int cg_targVehLastTime = 0; +qboolean CG_CheckTargetVehicle( centity_t **pTargetVeh, float *alpha ) +{ + int targetNum = ENTITYNUM_NONE; + centity_t *targetVeh = NULL; + + if ( !pTargetVeh || !alpha ) + {//hey, where are my pointers? + return qfalse; + } + + *alpha = 1.0f; + + //FIXME: need to clear all of these when you die? + if ( cg.predictedPlayerState.rocketLockIndex < ENTITYNUM_WORLD ) + { + targetNum = cg.predictedPlayerState.rocketLockIndex; + } + else if ( cg.crosshairVehNum < ENTITYNUM_WORLD + && cg.time - cg.crosshairVehTime < 3000 ) + {//crosshair was on a vehicle in the last 3 seconds + targetNum = cg.crosshairVehNum; + } + else if ( cg.crosshairClientNum < ENTITYNUM_WORLD ) + { + targetNum = cg.crosshairClientNum; + } + + if ( targetNum < MAX_CLIENTS ) + {//real client + if ( cg_entities[targetNum].currentState.m_iVehicleNum >= MAX_CLIENTS ) + {//in a vehicle + targetNum = cg_entities[targetNum].currentState.m_iVehicleNum; + } + } + if ( targetNum < ENTITYNUM_WORLD + && targetNum >= MAX_CLIENTS ) + { + centity_t *targetVeh = &cg_entities[targetNum]; + if ( targetVeh->currentState.NPC_class == CLASS_VEHICLE + && targetVeh->m_pVehicle + && targetVeh->m_pVehicle->m_pVehicleInfo + && targetVeh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + {//it's a vehicle + cg_targVeh = targetNum; + cg_targVehLastTime = cg.time; + *alpha = 1.0f; + } + else + { + targetVeh = NULL; + } + } + if ( !targetVeh ) + { + if ( cg_targVehLastTime && cg.time - cg_targVehLastTime < 3000 ) + { + targetVeh = &cg_entities[cg_targVeh];; + if ( cg.time-cg_targVehLastTime < 1000 ) + {//stay at full alpha for 1 sec after lose them from crosshair + *alpha = 1.0f; + } + else + {//fade out over 2 secs + *alpha = 1.0f-((cg.time-cg_targVehLastTime-1000)/2000.0f); + } + } + } + if ( targetVeh ) + { + *pTargetVeh = targetVeh; + return qtrue; + } + return qfalse; +} + +#define MAX_VHUD_SHIELD_TICS 12 +#define MAX_VHUD_SPEED_TICS 5 +#define MAX_VHUD_ARMOR_TICS 5 +#define MAX_VHUD_AMMO_TICS 5 + +float CG_DrawVehicleShields( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxShields; + vec4_t calcColor; + itemDef_t *item; + float percShields; + + item = Menu_FindItemByName((menuDef_t *) menuHUD, "armorbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxShields = veh->m_pVehicle->m_pVehicleInfo->shields; + currValue = cg.predictedVehicleState.stats[STAT_ARMOR]; + percShields = (float)currValue/(float)maxShields; + // Print all the tics of the shield graphic + // Look at the amount of health left and show only as much of the graphic as there is health. + // Use alpha to fade out partial section of health + inc = (float) maxShields / MAX_VHUD_ARMOR_TICS; + for (i=1;i<=MAX_VHUD_ARMOR_TICS;i++) + { + sprintf( itemName, "armor_tic%d", i ); + + item = Menu_FindItemByName((menuDef_t *) menuHUD, itemName); + + if (!item) + { + continue; + } + + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } + + return percShields; +} + +int cg_vehicleAmmoWarning = 0; +int cg_vehicleAmmoWarningTime = 0; +void CG_DrawVehicleAmmo( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxAmmo; + vec4_t calcColor; + itemDef_t *item; + + item = Menu_FindItemByName((menuDef_t *) menuHUD, "ammobackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxAmmo = veh->m_pVehicle->m_pVehicleInfo->weapon[0].ammoMax; + currValue = cg.predictedVehicleState.ammo[0]; + + inc = (float) maxAmmo / MAX_VHUD_AMMO_TICS; + for (i=1;i<=MAX_VHUD_AMMO_TICS;i++) + { + sprintf( itemName, "ammo_tic%d", i ); + + item = Menu_FindItemByName((menuDef_t *)menuHUD, itemName); + + if (!item) + { + continue; + } + + if ( cg_vehicleAmmoWarningTime > cg.time + && cg_vehicleAmmoWarning == 0 ) + { + memcpy(calcColor, g_color_table[ColorIndex(COLOR_RED)], sizeof(vec4_t)); + calcColor[3] = sin(cg.time*0.005)*0.5f+0.5f; + } + else + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + + +void CG_DrawVehicleAmmoUpper( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxAmmo; + vec4_t calcColor; + itemDef_t *item; + + item = Menu_FindItemByName((menuDef_t *)menuHUD, "ammoupperbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxAmmo = veh->m_pVehicle->m_pVehicleInfo->weapon[0].ammoMax; + currValue = cg.predictedVehicleState.ammo[0]; + + inc = (float) maxAmmo / MAX_VHUD_AMMO_TICS; + for (i=1;i cg.time + && cg_vehicleAmmoWarning == 0 ) + { + memcpy(calcColor, g_color_table[ColorIndex(COLOR_RED)], sizeof(vec4_t)); + calcColor[3] = sin(cg.time*0.005)*0.5f+0.5f; + } + else + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + + +void CG_DrawVehicleAmmoLower( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxAmmo; + vec4_t calcColor; + itemDef_t *item; + + + item = Menu_FindItemByName((menuDef_t *)menuHUD, "ammolowerbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxAmmo = veh->m_pVehicle->m_pVehicleInfo->weapon[1].ammoMax; + currValue = cg.predictedVehicleState.ammo[1]; + + inc = (float) maxAmmo / MAX_VHUD_AMMO_TICS; + for (i=1;i cg.time + && cg_vehicleAmmoWarning == 1 ) + { + memcpy(calcColor, g_color_table[ColorIndex(COLOR_RED)], sizeof(vec4_t)); + calcColor[3] = sin(cg.time*0.005)*0.5f+0.5f; + } + else + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + +// The HUD.menu file has the graphic print with a negative height, so it will print from the bottom up. +void CG_DrawVehicleTurboRecharge( const menuDef_t *menuHUD, const centity_t *veh ) +{ + itemDef_t *item; + int height; + + item = Menu_FindItemByName( (menuDef_t *) menuHUD, "turborecharge"); + + if (item) + { + float percent=0.0f; + int diff = ( cg.time - veh->m_pVehicle->m_iTurboTime ); + + height = item->window.rect.h; + + if (diff > veh->m_pVehicle->m_pVehicleInfo->turboRecharge) + { + percent = 1.0f; + trap_R_SetColor( colorTable[CT_GREEN] ); + } + else + { + percent = (float) diff / veh->m_pVehicle->m_pVehicleInfo->turboRecharge; + if (percent < 0.0f) + { + percent = 0.0f; + } + trap_R_SetColor( colorTable[CT_RED] ); + } + + height *= percent; + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + height, + cgs.media.whiteShader); + } +} + +qboolean cg_drawLink = qfalse; +void CG_DrawVehicleWeaponsLinked( const menuDef_t *menuHUD, const centity_t *veh ) +{ + qboolean drawLink = qfalse; + if ( veh->m_pVehicle + && veh->m_pVehicle->m_pVehicleInfo + && (veh->m_pVehicle->m_pVehicleInfo->weapon[0].linkable == 2|| veh->m_pVehicle->m_pVehicleInfo->weapon[1].linkable == 2) ) + {//weapon is always linked + drawLink = qtrue; + } + else + { +//MP way: + //must get sent over network + if ( cg.predictedVehicleState.vehWeaponsLinked ) + { + drawLink = qtrue; + } +//NOTE: below is SP way +/* + //just cheat it + if ( veh->gent->m_pVehicle->weaponStatus[0].linked + || veh->gent->m_pVehicle->weaponStatus[1].linked ) + { + drawLink = qtrue; + } +*/ + } + + if ( cg_drawLink != drawLink ) + {//state changed, play sound + cg_drawLink = drawLink; + trap_S_StartSound (NULL, cg.predictedPlayerState.clientNum, CHAN_LOCAL, trap_S_RegisterSound( "sound/vehicles/common/linkweaps.wav" ) ); + } + + if ( drawLink ) + { + itemDef_t *item; + + item = Menu_FindItemByName( (menuDef_t *) menuHUD, "weaponslinked"); + + if (item) + { + trap_R_SetColor( colorTable[CT_CYAN] ); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + cgs.media.whiteShader); + } + } +} + +void CG_DrawVehicleSpeed( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + char itemName[64]; + float inc, currValue,maxSpeed; + vec4_t calcColor; + itemDef_t *item; + + item = Menu_FindItemByName((menuDef_t *) menuHUD, "speedbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + maxSpeed = veh->m_pVehicle->m_pVehicleInfo->speedMax; + currValue = cg.predictedVehicleState.speed; + + + // Print all the tics of the shield graphic + // Look at the amount of health left and show only as much of the graphic as there is health. + // Use alpha to fade out partial section of health + inc = (float) maxSpeed / MAX_VHUD_SPEED_TICS; + for (i=1;i<=MAX_VHUD_SPEED_TICS;i++) + { + sprintf( itemName, "speed_tic%d", i ); + + item = Menu_FindItemByName((menuDef_t *)menuHUD, itemName); + + if (!item) + { + continue; + } + + if ( cg.time > veh->m_pVehicle->m_iTurboTime ) + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + } + else // In turbo mode + { + if (cg.VHUDFlashTime < cg.time) + { + cg.VHUDFlashTime = cg.time + 200; + if (cg.VHUDTurboFlag) + { + cg.VHUDTurboFlag = qfalse; + } + else + { + cg.VHUDTurboFlag = qtrue; + } + } + + if (cg.VHUDTurboFlag) + { + memcpy(calcColor, colorTable[CT_LTRED1], sizeof(vec4_t)); + } + else + { + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + } + } + + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + +void CG_DrawVehicleArmor( const menuDef_t *menuHUD, const centity_t *veh ) +{ + int i; + vec4_t calcColor; + char itemName[64]; + float inc, currValue,maxArmor; + itemDef_t *item; + + maxArmor = veh->m_pVehicle->m_pVehicleInfo->armor; + currValue = cg.predictedVehicleState.stats[STAT_HEALTH]; + + item = Menu_FindItemByName( (menuDef_t *) menuHUD, "shieldbackground"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + + // Print all the tics of the shield graphic + // Look at the amount of health left and show only as much of the graphic as there is health. + // Use alpha to fade out partial section of health + inc = (float) maxArmor / MAX_VHUD_SHIELD_TICS; + for (i=1;i <= MAX_VHUD_SHIELD_TICS;i++) + { + sprintf( itemName, "shield_tic%d", i ); + + item = Menu_FindItemByName((menuDef_t *) menuHUD, itemName); + + if (!item) + { + continue; + } + + + memcpy(calcColor, item->window.foreColor, sizeof(vec4_t)); + + if (currValue <= 0) // don't show tic + { + break; + } + else if (currValue < inc) // partial tic (alpha it out) + { + float percent = currValue / inc; + calcColor[3] *= percent; // Fade it out + } + + trap_R_SetColor( calcColor); + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + + currValue -= inc; + } +} + +enum +{ + VEH_DAMAGE_FRONT=0, + VEH_DAMAGE_BACK, + VEH_DAMAGE_LEFT, + VEH_DAMAGE_RIGHT, +}; + +typedef struct +{ + char *itemName; + short heavyDamage; + short lightDamage; +} veh_damage_t; + +veh_damage_t vehDamageData[4] = +{ +"vehicle_front",SHIPSURF_DAMAGE_FRONT_HEAVY,SHIPSURF_DAMAGE_FRONT_LIGHT, +"vehicle_back",SHIPSURF_DAMAGE_BACK_HEAVY,SHIPSURF_DAMAGE_BACK_LIGHT, +"vehicle_left",SHIPSURF_DAMAGE_LEFT_HEAVY,SHIPSURF_DAMAGE_LEFT_LIGHT, +"vehicle_right",SHIPSURF_DAMAGE_RIGHT_HEAVY,SHIPSURF_DAMAGE_RIGHT_LIGHT, +}; + +// Draw health graphic for given part of vehicle +void CG_DrawVehicleDamage(const centity_t *veh,int brokenLimbs,const menuDef_t *menuHUD,float alpha,int index) +{ + itemDef_t *item; + int colorI; + vec4_t color; + int graphicHandle=0; + + item = Menu_FindItemByName((menuDef_t *)menuHUD, vehDamageData[index].itemName); + if (item) + { + if (brokenLimbs & (1<m_pVehicle->m_pVehicleInfo->iconFrontHandle; + break; + case VEH_DAMAGE_BACK : + graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconBackHandle; + break; + case VEH_DAMAGE_LEFT : + graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconLeftHandle; + break; + case VEH_DAMAGE_RIGHT : + graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconRightHandle; + break; + } + + if (graphicHandle) + { + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + graphicHandle ); + } + } +} + + +// Used on both damage indicators : player vehicle and the vehicle the player is locked on +void CG_DrawVehicleDamageHUD(const centity_t *veh,int brokenLimbs,float percShields,char *menuName, float alpha) +{ + menuDef_t *menuHUD; + itemDef_t *item; + vec4_t color; + + menuHUD = Menus_FindByName(menuName); + + if ( !menuHUD ) + { + return; + } + + item = Menu_FindItemByName(menuHUD, "background"); + if (item) + { + if (veh->m_pVehicle->m_pVehicleInfo->dmgIndicBackgroundHandle) + { + if ( veh->damageTime > cg.time ) + {//ship shields currently taking damage + //NOTE: cent->damageAngle can be accessed to get the direction from the ship origin to the impact point (in 3-D space) + float perc = 1.0f - ((veh->damageTime - cg.time) / 2000.0f/*MIN_SHIELD_TIME*/); + if ( perc < 0.0f ) + { + perc = 0.0f; + } + else if ( perc > 1.0f ) + { + perc = 1.0f; + } + color[0] = item->window.foreColor[0];//flash red + color[1] = item->window.foreColor[1]*perc;//fade other colors back in over time + color[2] = item->window.foreColor[2]*perc;//fade other colors back in over time + color[3] = item->window.foreColor[3];//always normal alpha + trap_R_SetColor( color ); + } + else + { + trap_R_SetColor( item->window.foreColor ); + } + + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + veh->m_pVehicle->m_pVehicleInfo->dmgIndicBackgroundHandle ); + } + } + + item = Menu_FindItemByName(menuHUD, "outer_frame"); + if (item) + { + if (veh->m_pVehicle->m_pVehicleInfo->dmgIndicFrameHandle) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + veh->m_pVehicle->m_pVehicleInfo->dmgIndicFrameHandle ); + } + } + + item = Menu_FindItemByName(menuHUD, "shields"); + if (item) + { + if (veh->m_pVehicle->m_pVehicleInfo->dmgIndicShieldHandle) + { + VectorCopy4 ( colorTable[CT_HUD_GREEN], color ); + color[3] = percShields; + trap_R_SetColor( color ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + veh->m_pVehicle->m_pVehicleInfo->dmgIndicShieldHandle ); + } + } + + //TODO: if we check nextState.brokenLimbs & prevState.brokenLimbs, we can tell when a damage flag has been added and flash that part of the ship + //FIXME: when ship explodes, either stop drawing ship or draw all parts black + CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_FRONT); + CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_BACK); + CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_LEFT); + CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_RIGHT); +} + +qboolean CG_DrawVehicleHud( const centity_t *cent ) +{ + itemDef_t *item; + menuDef_t *menuHUD; + playerState_t *ps; + centity_t *veh; + float shieldPerc,alpha; + + menuHUD = Menus_FindByName("swoopvehiclehud"); + if (!menuHUD) + { + return qtrue; // Draw player HUD + } + + ps = &cg.predictedPlayerState; + + if (!ps || !(ps->m_iVehicleNum)) + { + return qtrue; // Draw player HUD + } + veh = &cg_entities[ps->m_iVehicleNum]; + + if ( !veh ) + { + return qtrue; // Draw player HUD + } + + CG_DrawVehicleTurboRecharge( menuHUD, veh ); + CG_DrawVehicleWeaponsLinked( menuHUD, veh ); + + item = Menu_FindItemByName(menuHUD, "leftframe"); + + // Draw frame + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + item = Menu_FindItemByName(menuHUD, "rightframe"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + + CG_DrawVehicleArmor( menuHUD, veh ); + + // Get animal hud for speed +// if (veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) +// { +// menuHUD = Menus_FindByName("tauntaunhud"); +// } + + + CG_DrawVehicleSpeed( menuHUD, veh ); + + // Revert to swoophud +// if (veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) +// { +// menuHUD = Menus_FindByName("swoopvehiclehud"); +// } + +// if (veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL) +// { + shieldPerc = CG_DrawVehicleShields( menuHUD, veh ); +// } + + if (veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID && !veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID) + { + CG_DrawVehicleAmmo( menuHUD, veh ); + } + else if (veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID && veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID) + { + CG_DrawVehicleAmmoUpper( menuHUD, veh ); + CG_DrawVehicleAmmoLower( menuHUD, veh ); + } + + // If he's hidden, he must be in a vehicle + if (veh->m_pVehicle->m_pVehicleInfo->hideRider) + { + CG_DrawVehicleDamageHUD(veh,cg.predictedVehicleState.brokenLimbs,shieldPerc,"vehicledamagehud",1.0f); + + // Has he targeted an enemy? + if (CG_CheckTargetVehicle( &veh, &alpha )) + { + CG_DrawVehicleDamageHUD(veh,veh->currentState.brokenLimbs,((float)veh->currentState.activeForcePass/10.0f),"enemyvehicledamagehud",alpha); + } + + return qfalse; // Don't draw player HUD + } + + return qtrue; // Draw player HUD + +} + +/* +================ +CG_DrawStats + +================ +*/ +static void CG_DrawStats( void ) +{ + centity_t *cent; + playerState_t *ps; + qboolean drawHUD = qtrue; +/* 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; + + VectorClear( angles ); + + // Do start + if (!cg.interfaceStartupDone) + { + CG_InterfaceStartup(); + } + + cgi_UI_MenuPaintAll();*/ + + if ( cent ) + { + ps = &cg.predictedPlayerState; + + if ( (ps->m_iVehicleNum ) ) // In a vehicle??? + { + drawHUD = CG_DrawVehicleHud( cent ); + } + } + + if (drawHUD) + { + CG_DrawHUD(cent); + } + + /*CG_DrawArmor(cent); + CG_DrawHealth(cent); + CG_DrawAmmo(cent); + + CG_DrawTalk(cent);*/ +} + +/* +=================== +CG_DrawPickupItem +=================== +*/ +static void CG_DrawPickupItem( void ) { + int value; + float *fadeColor; + + value = cg.itemPickup; + if ( value && cg_items[ value ].icon != -1 ) + { + fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); + if ( fadeColor ) + { + CG_RegisterItemVisuals( value ); + trap_R_SetColor( fadeColor ); + CG_DrawPic( 573, 320, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); + trap_R_SetColor( NULL ); + } + } +} + +/* +================ +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] = .2f; + hcolor[2] = .2f; + } else if ( team == TEAM_BLUE ) { + hcolor[0] = .2f; + hcolor[1] = .2f; + hcolor[2] = 1; + } else { + return; + } +// trap_R_SetColor( hcolor ); + + CG_FillRect ( x, y, w, h, hcolor ); +// CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); + trap_R_SetColor( NULL ); +} + + +/* +=========================================================================================== + + UPPER RIGHT CORNER + +=========================================================================================== +*/ + +/* +================ +CG_DrawMiniScoreboard +================ +*/ +static float CG_DrawMiniScoreboard ( float y ) +{ + char temp[MAX_QPATH]; + int xOffset = 0; + +#ifdef _XBOX + xOffset = -40; +#endif + + if ( !cg_drawScores.integer ) + { + return y; + } + + if (cgs.gametype == GT_SIEGE) + { //don't bother with this in siege + return y; + } + + if ( cgs.gametype >= GT_TEAM ) + { + strcpy ( temp, va("%s: ", CG_GetStringEdString("MP_INGAME", "RED"))); + Q_strcat ( temp, MAX_QPATH, cgs.scores1==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores1)) ); + Q_strcat ( temp, MAX_QPATH, va(" %s: ", CG_GetStringEdString("MP_INGAME", "BLUE")) ); + Q_strcat ( temp, MAX_QPATH, cgs.scores2==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores2)) ); + + CG_Text_Paint( 630 - CG_Text_Width ( temp, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, temp, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM ); + y += 15; + } + else + { + /* + strcpy ( temp, "1st: " ); + Q_strcat ( temp, MAX_QPATH, cgs.scores1==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores1)) ); + + Q_strcat ( temp, MAX_QPATH, " 2nd: " ); + Q_strcat ( temp, MAX_QPATH, cgs.scores2==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores2)) ); + + CG_Text_Paint( 630 - CG_Text_Width ( temp, 0.7f, FONT_SMALL ), y, 0.7f, colorWhite, temp, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM ); + y += 15; + */ + //rww - no longer doing this. Since the attacker now shows who is first, we print the score there. + } + + + return y; +} + +/* +================ +CG_DrawEnemyInfo +================ +*/ +static float CG_DrawEnemyInfo ( float y ) +{ + float size; + int clientNum; + const char *title; + clientInfo_t *ci; + int xOffset = 0; + + if (!cg.snap) + { + return y; + } + +#ifdef _XBOX + xOffset = -40; +#endif + + if ( !cg_drawEnemyInfo.integer ) + { + return y; + } + + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) + { + return y; + } + + if (cgs.gametype == GT_POWERDUEL) + { //just get out of here then + return y; + } + + if ( cgs.gametype == GT_JEDIMASTER ) + { + //title = "Jedi Master"; + title = CG_GetStringEdString("MP_INGAME", "MASTERY7"); + clientNum = cgs.jediMaster; + + if ( clientNum < 0 ) + { + //return y; +// title = "Get Saber!"; + title = CG_GetStringEdString("MP_INGAME", "GET_SABER"); + + + size = ICON_SIZE * 1.25; + y += 5; + + CG_DrawPic( 640 - size - 12 + xOffset, y, size, size, cgs.media.weaponIcons[WP_SABER] ); + + y += size; + + /* + CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 0.7f, FONT_MEDIUM ), y, 0.7f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM ); + y += 15; + */ + + CG_Text_Paint( 630 - CG_Text_Width ( title, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, title, 0, 0, 0, FONT_MEDIUM ); + + return y + BIGCHAR_HEIGHT + 2; + } + } + else if ( cg.snap->ps.duelInProgress ) + { +// title = "Dueling"; + title = CG_GetStringEdString("MP_INGAME", "DUELING"); + clientNum = cg.snap->ps.duelIndex; + } + else if ( cgs.gametype == GT_DUEL && cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR) + { + title = CG_GetStringEdString("MP_INGAME", "DUELING"); + if (cg.snap->ps.clientNum == cgs.duelist1) + { + clientNum = cgs.duelist2; //if power duel, should actually draw both duelists 2 and 3 I guess + } + else if (cg.snap->ps.clientNum == cgs.duelist2) + { + clientNum = cgs.duelist1; + } + else if (cg.snap->ps.clientNum == cgs.duelist3) + { + clientNum = cgs.duelist1; + } + else + { + return y; + } + } + else + { + /* + title = "Attacker"; + clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER]; + + if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) + { + return y; + } + + if ( cg.time - cg.attackerTime > ATTACKER_HEAD_TIME ) + { + cg.attackerTime = 0; + return y; + } + */ + //As of current, we don't want to draw the attacker. Instead, draw whoever is in first place. + if (cgs.duelWinner < 0 || cgs.duelWinner >= MAX_CLIENTS) + { + return y; + } + + + title = va("%s: %i",CG_GetStringEdString("MP_INGAME", "LEADER"), cgs.scores1); + + /* + if (cgs.scores1 == 1) + { + title = va("%i kill", cgs.scores1); + } + else + { + title = va("%i kills", cgs.scores1); + } + */ + clientNum = cgs.duelWinner; + } + + if ( clientNum >= MAX_CLIENTS || !(&cgs.clientinfo[ clientNum ]) ) + { + return y; + } + + ci = &cgs.clientinfo[ clientNum ]; + + size = ICON_SIZE * 1.25; + y += 5; + + if ( ci->modelIcon ) + { + CG_DrawPic( 640 - size - 5 + xOffset, y, size, size, ci->modelIcon ); + } + + y += size; + +// CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM ); + CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 1.0f, FONT_SMALL2 ) + xOffset, y, 1.0f, colorWhite, ci->name, 0, 0, 0, FONT_SMALL2 ); + + y += 15; +// CG_Text_Paint( 630 - CG_Text_Width ( title, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, title, 0, 0, 0, FONT_MEDIUM ); + CG_Text_Paint( 630 - CG_Text_Width ( title, 1.0f, FONT_SMALL2 ) + xOffset, y, 1.0f, colorWhite, title, 0, 0, 0, FONT_SMALL2 ); + + if ( (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) && cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR) + {//also print their score + char text[1024]; + y += 15; + Com_sprintf(text, sizeof(text), "%i/%i", cgs.clientinfo[clientNum].score, cgs.fraglimit ); + CG_Text_Paint( 630 - CG_Text_Width ( text, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, text, 0, 0, 0, FONT_MEDIUM ); + } + +// nmckenzie: DUEL_HEALTH - fixme - need checks and such here. And this is coded to duelist 1 right now, which is wrongly. + if ( cgs.showDuelHealths >= 2) + { + y += 15; + if ( cgs.duelist1 == clientNum ) + { + CG_DrawDuelistHealth ( 640 - size - 5 + xOffset, y, 64, 8, 1 ); + } + else if ( cgs.duelist2 == clientNum ) + { + CG_DrawDuelistHealth ( 640 - size - 5 + xOffset, y, 64, 8, 2 ); + } + } + + return y + BIGCHAR_HEIGHT + 2; +} + +/* +================== +CG_DrawSnapshot +================== +*/ +static float CG_DrawSnapshot( float y ) { + char *s; + int w; + int xOffset = 0; + +#ifdef _XBOX + xOffset = -40; +#endif + + 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 + xOffset, y + 2, s, 1.0F); + + return y + BIGCHAR_HEIGHT + 4; +} + +/* +================== +CG_DrawFPS +================== +*/ +#define FPS_FRAMES 16 +static float CG_DrawFPS( float y ) { + char *s; + int w; + static unsigned short previousTimes[FPS_FRAMES]; + static unsigned short index; + static int previous, lastupdate; + int t, i, fps, total; + unsigned short frameTime; +#ifdef _XBOX + const int xOffset = -40; +#else + const int xOffset = 0; +#endif + + + // 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; + if (t - lastupdate > 50) //don't sample faster than this + { + lastupdate = t; + previousTimes[index % FPS_FRAMES] = frameTime; + index++; + } + // 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 + xOffset, y + 2, s, 1.0F); + + return y + BIGCHAR_HEIGHT + 4; +} + +// nmckenzie: DUEL_HEALTH +#define MAX_HEALTH_FOR_IFACE 100 +void CG_DrawHealthBarRough (float x, float y, int width, int height, float ratio, const float *color1, const float *color2) +{ + float midpoint, remainder; + float color3[4] = {1, 0, 0, .7f}; + + midpoint = width * ratio - 1; + remainder = width - midpoint; + color3[0] = color1[0] * 0.5f; + + assert(!(height%4));//this won't line up otherwise. + CG_DrawRect(x + 1, y + height/2-1, midpoint, 1, height/4+1, color1); // creme-y filling. + CG_DrawRect(x + midpoint, y + height/2-1, remainder, 1, height/4+1, color3); // used-up-ness. + CG_DrawRect(x, y, width, height, 1, color2); // hard crispy shell +} + +void CG_DrawDuelistHealth ( float x, float y, float w, float h, int duelist ) +{ + float duelHealthColor[4] = {1, 0, 0, 0.7f}; + float healthSrc = 0.0f; + float ratio; + + if ( duelist == 1 ) + { + healthSrc = cgs.duelist1health; + } + else if (duelist == 2 ) + { + healthSrc = cgs.duelist2health; + } + + ratio = healthSrc / MAX_HEALTH_FOR_IFACE; + if ( ratio > 1.0f ) + { + ratio = 1.0f; + } + if ( ratio < 0.0f ) + { + ratio = 0.0f; + } + duelHealthColor[0] = (ratio * 0.2f) + 0.5f; + + CG_DrawHealthBarRough (x, y, w, h, ratio, duelHealthColor, colorTable[CT_WHITE]); // new art for this? I'm not crazy about how this looks. +} + +/* +===================== +CG_DrawRadar +===================== +*/ +//#define RADAR_RANGE 2500 +float cg_radarRange = 2500.0f; + +#define RADAR_RADIUS 60 +#define RADAR_X (580 - RADAR_RADIUS) +//#define RADAR_Y 10 //dynamic based on passed Y val +#define RADAR_CHAT_DURATION 6000 +static int radarLockSoundDebounceTime = 0; +static int impactSoundDebounceTime = 0; +#define RADAR_MISSILE_RANGE 3000.0f +#define RADAR_ASTEROID_RANGE 10000.0f +#define RADAR_MIN_ASTEROID_SURF_WARN_DIST 1200.0f + +float CG_DrawRadar ( float y ) +{ + vec4_t color; + vec4_t teamColor; + float arrow_w; + float arrow_h; + clientInfo_t *cl; + clientInfo_t *local; + int i; + float arrowBaseScale; + float zScale; + int xOffset = 0; + + if (!cg.snap) + { + return y; + } + +#ifdef _XBOX + xOffset = -40; +#endif + + // Make sure the radar should be showing + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) + { + return y; + } + + if ( (cg.predictedPlayerState.pm_flags & PMF_FOLLOW) || cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR ) + { + return y; + } + + local = &cgs.clientinfo[ cg.snap->ps.clientNum ]; + if ( !local->infoValid ) + { + return y; + } + + // Draw the radar background image + color[0] = color[1] = color[2] = 1.0f; + color[3] = 0.6f; + trap_R_SetColor ( color ); + CG_DrawPic( RADAR_X + xOffset, y, RADAR_RADIUS*2, RADAR_RADIUS*2, cgs.media.radarShader ); + + //Always green for your own team. + VectorCopy ( g_color_table[ColorIndex(COLOR_GREEN)], teamColor ); + teamColor[3] = 1.0f; + + // Draw all of the radar entities. Draw them backwards so players are drawn last + for ( i = cg.radarEntityCount -1 ; i >= 0 ; i-- ) + { + vec3_t dirLook; + vec3_t dirPlayer; + float angleLook; + float anglePlayer; + float angle; + float distance, actualDist; + centity_t* cent; + + cent = &cg_entities[cg.radarEntities[i]]; + + // Get the distances first + VectorSubtract ( cg.predictedPlayerState.origin, cent->lerpOrigin, dirPlayer ); + dirPlayer[2] = 0; + actualDist = distance = VectorNormalize ( dirPlayer ); + + if ( distance > cg_radarRange * 0.8f) + { + if ( (cent->currentState.eFlags & EF_RADAROBJECT)//still want to draw the direction + || ( cent->currentState.eType==ET_NPC//FIXME: draw last, with players... + && cent->currentState.NPC_class == CLASS_VEHICLE + && cent->currentState.speed > 0 ) )//always draw vehicles + { + distance = cg_radarRange*0.8f; + } + else + { + continue; + } + } + + distance = distance / cg_radarRange; + distance *= RADAR_RADIUS; + + AngleVectors ( cg.predictedPlayerState.viewangles, dirLook, NULL, NULL ); + + dirLook[2] = 0; + anglePlayer = atan2(dirPlayer[0],dirPlayer[1]); + VectorNormalize ( dirLook ); + angleLook = atan2(dirLook[0],dirLook[1]); + angle = angleLook - anglePlayer; + + switch ( cent->currentState.eType ) + { + default: + { + float x; + float ly; + qhandle_t shader; + vec4_t color; + + x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance; + + arrowBaseScale = 9.0f; + shader = 0; + zScale = 1.0f; + + //we want to scale the thing up/down based on the relative Z (up/down) positioning + if (cent->lerpOrigin[2] > cg.predictedPlayerState.origin[2]) + { //higher, scale up (between 16 and 24) + float dif = (cent->lerpOrigin[2] - cg.predictedPlayerState.origin[2]); + + //max out to 1.5x scale at 512 units above local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale += dif; + } + else if (cent->lerpOrigin[2] < cg.predictedPlayerState.origin[2]) + { //lower, scale down (between 16 and 8) + float dif = (cg.predictedPlayerState.origin[2] - cent->lerpOrigin[2]); + + //half scale at 512 units below local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale -= dif; + } + + arrowBaseScale *= zScale; + + if (cent->currentState.brokenLimbs) + { //slightly misleading to use this value, but don't want to add more to entstate. + //any ent with brokenLimbs non-0 and on radar is an objective ent. + //brokenLimbs is literal team value. + char objState[1024]; + int complete; + + //we only want to draw it if the objective for it is not complete. + //frame represents objective num. + trap_Cvar_VariableStringBuffer(va("team%i_objective%i", cent->currentState.brokenLimbs, cent->currentState.frame), objState, 1024); + + complete = atoi(objState); + + if (!complete) + { + + // generic enemy index specifies a shader to use for the radar entity. + if ( cent->currentState.genericenemyindex ) + { + color[0] = color[1] = color[2] = color[3] = 1.0f; + shader = cgs.gameIcons[cent->currentState.genericenemyindex]; + } + else + { + if (cg.snap && + cent->currentState.brokenLimbs == cg.snap->ps.persistant[PERS_TEAM]) + { + VectorCopy ( g_color_table[ColorIndex(COLOR_RED)], color ); + } + else + { + VectorCopy ( g_color_table[ColorIndex(COLOR_GREEN)], color ); + } + + shader = cgs.media.siegeItemShader; + } + } + } + else + { + color[0] = color[1] = color[2] = color[3] = 1.0f; + + // generic enemy index specifies a shader to use for the radar entity. + if ( cent->currentState.genericenemyindex ) + { + shader = cgs.gameIcons[cent->currentState.genericenemyindex]; + } + else + { + shader = cgs.media.siegeItemShader; + } + + } + + if ( shader ) + { + // Pulse the alpha if time2 is set. time2 gets set when the entity takes pain + if ( (cent->currentState.time2 && cg.time - cent->currentState.time2 < 5000) || + (cent->currentState.time2 == 0xFFFFFFFF) ) + { + if ( (cg.time / 200) & 1 ) + { + color[3] = 0.1f + 0.9f * (float) (cg.time % 200) / 200.0f; + } + else + { + color[3] = 1.0f - 0.9f * (float) (cg.time % 200) / 200.0f; + } + } + + trap_R_SetColor ( color ); + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, shader ); + } + } + break; + + case ET_NPC://FIXME: draw last, with players... + if ( cent->currentState.NPC_class == CLASS_VEHICLE + && cent->currentState.speed > 0 ) + { + if ( cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->radarIconHandle ) + { + float x; + float ly; + + x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance; + + arrowBaseScale = 9.0f; + zScale = 1.0f; + + //we want to scale the thing up/down based on the relative Z (up/down) positioning + if (cent->lerpOrigin[2] > cg.predictedPlayerState.origin[2]) + { //higher, scale up (between 16 and 24) + float dif = (cent->lerpOrigin[2] - cg.predictedPlayerState.origin[2]); + + //max out to 1.5x scale at 512 units above local player's height + dif /= 4096.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale += dif; + } + else if (cent->lerpOrigin[2] < cg.predictedPlayerState.origin[2]) + { //lower, scale down (between 16 and 8) + float dif = (cg.predictedPlayerState.origin[2] - cent->lerpOrigin[2]); + + //half scale at 512 units below local player's height + dif /= 4096.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale -= dif; + } + + arrowBaseScale *= zScale; + + if ( cent->currentState.m_iVehicleNum //vehicle has a driver + && cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].infoValid ) + { + if ( cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].team == local->team ) + { + trap_R_SetColor ( teamColor ); + } + else + { + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + } + else + { + trap_R_SetColor ( NULL ); + } + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, cent->m_pVehicle->m_pVehicleInfo->radarIconHandle ); + } + } + break; //maybe do something? + + case ET_MOVER: + if ( cent->currentState.speed//the mover's size, actually + && actualDist < (cent->currentState.speed+RADAR_ASTEROID_RANGE) + && cg.predictedPlayerState.m_iVehicleNum ) + {//a mover that's close to me and I'm in a vehicle + qboolean mayImpact = qfalse; + float surfaceDist = (actualDist-cent->currentState.speed); + if ( surfaceDist < 0.0f ) + { + surfaceDist = 0.0f; + } + if ( surfaceDist < RADAR_MIN_ASTEROID_SURF_WARN_DIST ) + {//always warn! + mayImpact = qtrue; + } + else + {//not close enough to always warn, yet, so check its direction + vec3_t asteroidPos, myPos, moveDir; + int predictTime, timeStep = 500; + float newDist; + for ( predictTime = timeStep; predictTime < 5000; predictTime+=timeStep ) + { + //asteroid dir, speed, size, + my dir & speed... + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time+predictTime, asteroidPos ); + //FIXME: I don't think it's calcing "myPos" correctly + AngleVectors( cg.predictedVehicleState.viewangles, moveDir, NULL, NULL ); + VectorMA( cg.predictedVehicleState.origin, cg.predictedVehicleState.speed*predictTime/1000.0f, moveDir, myPos ); + newDist = Distance( myPos, asteroidPos ); + if ( (newDist-cent->currentState.speed) <= RADAR_MIN_ASTEROID_SURF_WARN_DIST )//200.0f ) + {//heading for an impact within the next 5 seconds + mayImpact = qtrue; + break; + } + } + } + if ( mayImpact ) + {//possible collision + vec4_t asteroidColor = {0.5f,0.5f,0.5f,1.0f}; + float x; + float ly; + float asteroidScale = (cent->currentState.speed/2000.0f);//average asteroid radius? + if ( actualDist > RADAR_ASTEROID_RANGE ) + { + actualDist = RADAR_ASTEROID_RANGE; + } + distance = (actualDist/RADAR_ASTEROID_RANGE)*RADAR_RADIUS; + + x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance; + + if ( asteroidScale > 3.0f ) + { + asteroidScale = 3.0f; + } + else if ( asteroidScale < 0.2f ) + { + asteroidScale = 0.2f; + } + arrowBaseScale = (9.0f*asteroidScale); + if ( impactSoundDebounceTime < cg.time ) + { + vec3_t soundOrg; + if ( surfaceDist > RADAR_ASTEROID_RANGE*0.66f ) + { + impactSoundDebounceTime = cg.time + 1000; + } + else if ( surfaceDist > RADAR_ASTEROID_RANGE/3.0f ) + { + impactSoundDebounceTime = cg.time + 400; + } + else + { + impactSoundDebounceTime = cg.time + 100; + } + VectorMA( cg.refdef.vieworg, -500.0f*(surfaceDist/RADAR_ASTEROID_RANGE), dirPlayer, soundOrg ); + trap_S_StartSound( soundOrg, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound( "sound/vehicles/common/impactalarm.wav" ) ); + } + //brighten it the closer it is + if ( surfaceDist > RADAR_ASTEROID_RANGE*0.66f ) + { + asteroidColor[0] = asteroidColor[1] = asteroidColor[2] = 0.7f; + } + else if ( surfaceDist > RADAR_ASTEROID_RANGE/3.0f ) + { + asteroidColor[0] = asteroidColor[1] = asteroidColor[2] = 0.85f; + } + else + { + asteroidColor[0] = asteroidColor[1] = asteroidColor[2] = 1.0f; + } + //alpha out the longer it's been since it was considered dangerous + if ( (cg.time-impactSoundDebounceTime) > 100 ) + { + asteroidColor[3] = (float)((cg.time-impactSoundDebounceTime)-100)/900.0f; + } + + trap_R_SetColor ( asteroidColor ); + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, trap_R_RegisterShaderNoMip( "gfx/menus/radar/asteroid" ) ); + } + } + break; + + case ET_MISSILE: + if ( //cent->currentState.weapon == WP_ROCKET_LAUNCHER &&//a rocket + cent->currentState.owner > MAX_CLIENTS //belongs to an NPC + && cg_entities[cent->currentState.owner].currentState.NPC_class == CLASS_VEHICLE ) + {//a rocket belonging to an NPC, FIXME: only tracking rockets! + float x; + float ly; + + x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance; + ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance; + + arrowBaseScale = 3.0f; + if ( cg.predictedPlayerState.m_iVehicleNum ) + {//I'm in a vehicle + //if it's targetting me, then play an alarm sound if I'm in a vehicle + if ( cent->currentState.otherEntityNum == cg.predictedPlayerState.clientNum || cent->currentState.otherEntityNum == cg.predictedPlayerState.m_iVehicleNum ) + { + if ( radarLockSoundDebounceTime < cg.time ) + { + vec3_t soundOrg; + int alarmSound; + if ( actualDist > RADAR_MISSILE_RANGE * 0.66f ) + { + radarLockSoundDebounceTime = cg.time + 1000; + arrowBaseScale = 3.0f; + alarmSound = trap_S_RegisterSound( "sound/vehicles/common/lockalarm1.wav" ); + } + else if ( actualDist > RADAR_MISSILE_RANGE/3.0f ) + { + radarLockSoundDebounceTime = cg.time + 500; + arrowBaseScale = 6.0f; + alarmSound = trap_S_RegisterSound( "sound/vehicles/common/lockalarm2.wav" ); + } + else + { + radarLockSoundDebounceTime = cg.time + 250; + arrowBaseScale = 9.0f; + alarmSound = trap_S_RegisterSound( "sound/vehicles/common/lockalarm3.wav" ); + } + if ( actualDist > RADAR_MISSILE_RANGE ) + { + actualDist = RADAR_MISSILE_RANGE; + } + VectorMA( cg.refdef.vieworg, -500.0f*(actualDist/RADAR_MISSILE_RANGE), dirPlayer, soundOrg ); + trap_S_StartSound( soundOrg, ENTITYNUM_WORLD, CHAN_AUTO, alarmSound ); + } + } + } + zScale = 1.0f; + + //we want to scale the thing up/down based on the relative Z (up/down) positioning + if (cent->lerpOrigin[2] > cg.predictedPlayerState.origin[2]) + { //higher, scale up (between 16 and 24) + float dif = (cent->lerpOrigin[2] - cg.predictedPlayerState.origin[2]); + + //max out to 1.5x scale at 512 units above local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale += dif; + } + else if (cent->lerpOrigin[2] < cg.predictedPlayerState.origin[2]) + { //lower, scale down (between 16 and 8) + float dif = (cg.predictedPlayerState.origin[2] - cent->lerpOrigin[2]); + + //half scale at 512 units below local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale -= dif; + } + + arrowBaseScale *= zScale; + + if ( cent->currentState.owner >= MAX_CLIENTS//missile owned by an NPC + && cg_entities[cent->currentState.owner].currentState.NPC_class == CLASS_VEHICLE//NPC is a vehicle + && cg_entities[cent->currentState.owner].currentState.m_iVehicleNum <= MAX_CLIENTS//Vehicle has a player driver + && cgs.clientinfo[cg_entities[cent->currentState.owner].currentState.m_iVehicleNum-1].infoValid ) //player driver is valid + { + cl = &cgs.clientinfo[cg_entities[cent->currentState.owner].currentState.m_iVehicleNum-1]; + if ( cl->team == local->team ) + { + trap_R_SetColor ( teamColor ); + } + else + { + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + } + else + { + trap_R_SetColor ( NULL ); + } + CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, cgs.media.mAutomapRocketIcon ); + } + break; + + case ET_PLAYER: + { + vec4_t color; + + cl = &cgs.clientinfo[ cent->currentState.number ]; + + // not valid then dont draw it + if ( !cl->infoValid ) + { + continue; + } + + VectorCopy4 ( teamColor, color ); + + arrowBaseScale = 16.0f; + zScale = 1.0f; + + // Pulse the radar icon after a voice message + if ( cent->vChatTime + 2000 > cg.time ) + { + float f = (cent->vChatTime + 2000 - cg.time) / 3000.0f; + arrowBaseScale = 16.0f + 4.0f * f; + color[0] = teamColor[0] + (1.0f - teamColor[0]) * f; + color[1] = teamColor[1] + (1.0f - teamColor[1]) * f; + color[2] = teamColor[2] + (1.0f - teamColor[2]) * f; + } + + trap_R_SetColor ( color ); + + //we want to scale the thing up/down based on the relative Z (up/down) positioning + if (cent->lerpOrigin[2] > cg.predictedPlayerState.origin[2]) + { //higher, scale up (between 16 and 32) + float dif = (cent->lerpOrigin[2] - cg.predictedPlayerState.origin[2]); + + //max out to 2x scale at 1024 units above local player's height + dif /= 1024.0f; + if (dif > 1.0f) + { + dif = 1.0f; + } + zScale += dif; + } + else if (cent->lerpOrigin[2] < cg.predictedPlayerState.origin[2]) + { //lower, scale down (between 16 and 8) + float dif = (cg.predictedPlayerState.origin[2] - cent->lerpOrigin[2]); + + //half scale at 512 units below local player's height + dif /= 1024.0f; + if (dif > 0.5f) + { + dif = 0.5f; + } + zScale -= dif; + } + + arrowBaseScale *= zScale; + + arrow_w = arrowBaseScale * RADAR_RADIUS / 128; + arrow_h = arrowBaseScale * RADAR_RADIUS / 128; + + CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + sin (angle) * distance + xOffset, + y + RADAR_RADIUS + cos (angle) * distance, + arrow_w, arrow_h, + (360 - cent->lerpAngles[YAW]) + cg.predictedPlayerState.viewangles[YAW], cgs.media.mAutomapPlayerIcon ); + break; + } + } + } + + arrowBaseScale = 16.0f; + + arrow_w = arrowBaseScale * RADAR_RADIUS / 128; + arrow_h = arrowBaseScale * RADAR_RADIUS / 128; + + trap_R_SetColor ( colorWhite ); + CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + xOffset, y + RADAR_RADIUS, arrow_w, arrow_h, + 0, cgs.media.mAutomapPlayerIcon ); + + return y+(RADAR_RADIUS*2); +} + +/* +================= +CG_DrawTimer +================= +*/ +static float CG_DrawTimer( float y ) { + char *s; + int w; + int mins, seconds, tens; + int msec; + int xOffset = 0; + +#ifdef _XBOX + xOffset = -40; +#endif + + 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 + xOffset, y + 2, s, 1.0F); + + return y + BIGCHAR_HEIGHT + 4; +} + + +/* +================= +CG_DrawTeamOverlay +================= +*/ +extern const char *CG_GetLocationString(const char *loc); //cg_main.c +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; + int xOffset = 0; + +#ifdef _XBOX + xOffset = -40; +#endif + + 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_GetLocationString(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 + xOffset, 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 + xOffset, y, + ci->name, hcolor, qfalse, qfalse, + TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH); + + if (lwidth) { + p = CG_GetLocationString(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 + xOffset, 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 + xOffset, 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 + xOffset, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, + cg_weapons[ci->curWeapon].weaponIcon ); + } else { + CG_DrawPic( xx + xOffset, 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 + xOffset, 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 +} + + +static void CG_DrawPowerupIcons(int y) +{ + int j; + int ico_size = 64; + //int y = ico_size/2; + int xOffset = 0; + gitem_t *item; + +#ifdef _XBOX + xOffset = -40; +#endif + + if (!cg.snap) + { + return; + } + + y += 16; + + for (j = 0; j <= PW_NUM_POWERUPS; j++) + { + if (cg.snap->ps.powerups[j] > cg.time) + { + int secondsleft = (cg.snap->ps.powerups[j] - cg.time)/1000; + + item = BG_FindItemForPowerup( j ); + + if (item) + { + int icoShader = 0; + if (cgs.gametype == GT_CTY && (j == PW_REDFLAG || j == PW_BLUEFLAG)) + { + if (j == PW_REDFLAG) + { + icoShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" ); + } + else + { + icoShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" ); + } + } + else + { + icoShader = trap_R_RegisterShader( item->icon ); + } + + CG_DrawPic( (640-(ico_size*1.1)) + xOffset, y, ico_size, ico_size, icoShader ); + + y += ico_size; + + if (j != PW_REDFLAG && j != PW_BLUEFLAG && secondsleft < 999) + { + UI_DrawProportionalString((640-(ico_size*1.1))+(ico_size/2) + xOffset, y-8, va("%i", secondsleft), UI_CENTER | UI_BIGFONT | UI_DROPSHADOW, colorTable[CT_WHITE]); + } + + y += (ico_size/3); + } + } + } +} + + +/* +===================== +CG_DrawUpperRight + +===================== +*/ +static void CG_DrawUpperRight( void ) { + float y; + +#ifdef _XBOX + y = 50; +#else + y = 0; +#endif + + trap_R_SetColor( colorTable[CT_WHITE] ); + + 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 ( ( cgs.gametype >= GT_TEAM || cg.predictedPlayerState.m_iVehicleNum ) + && cg_drawRadar.integer ) + {//draw Radar in Siege mode or when in a vehicle of any kind + y = CG_DrawRadar ( y ); + } + + y = CG_DrawEnemyInfo ( y ); + + y = CG_DrawMiniScoreboard ( y ); + + CG_DrawPowerupIcons(y); +} + +/* +=================== +CG_DrawReward +=================== +*/ +#ifdef JK2AWARDS +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 ); +} +#endif + + +/* +=============================================================================== + +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]; + + if (cg.mMapChange) + { + s = CG_GetStringEdString("MP_INGAME", "SERVER_CHANGING_MAPS"); // s = "Server Changing Maps"; + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w/2, 100, s, 1.0F); + + s = CG_GetStringEdString("MP_INGAME", "PLEASE_WAIT"); // s = "Please wait..."; + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString( 320 - w/2, 200, s, 1.0F); + return; + } + + // 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 = CG_GetStringEdString("MP_INGAME", "CONNECTION_INTERRUPTED"); // 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; + } + + x = 640 - 48; + y = 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 + // + x = 640 - 48; + y = 480 - 144; + + trap_R_SetColor( NULL ); + CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); + + ax = x; + ay = y; + aw = 48; + ah = 48; + + 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(); +} + +void CG_DrawSiegeMessage( const char *str, int objectiveScreen ) +{ +// if (!( trap_Key_GetCatcher() & KEYCATCH_UI )) + { + trap_OpenUIMenu(UIMENU_CLOSEALL); + trap_Cvar_Set("cg_siegeMessage", str); + if (objectiveScreen) + { + trap_OpenUIMenu(UIMENU_SIEGEOBJECTIVES); + } + else + { + trap_OpenUIMenu(UIMENU_SIEGEMESSAGE); + } + } +} + +void CG_DrawSiegeMessageNonMenu( const char *str ) +{ + char text[1024]; + if (str[0]=='@') + { + trap_SP_GetStringTextString(str+1, text, sizeof(text)); + str = text; + } + CG_CenterPrint(str, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH); +} + +/* +=============================================================================== + +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; + int h; + float *color; + const float scale = 1.0; //0.5 + + 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; + + w = CG_Text_Width(linebuffer, scale, FONT_MEDIUM); + h = CG_Text_Height(linebuffer, scale, FONT_MEDIUM); + x = (SCREEN_WIDTH - w) / 2; + CG_Text_Paint(x, y + h, scale, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM); + y += h + 6; + + while ( *start && ( *start != '\n' ) ) { + start++; + } + if ( !*start ) { + break; + } + start++; + } + + trap_R_SetColor( NULL ); +} + + + +/* +================================================================================ + +CROSSHAIR + +================================================================================ +*/ + +#define HEALTH_WIDTH 50.0f +#define HEALTH_HEIGHT 5.0f + +//see if we can draw some extra info on this guy based on our class +void CG_DrawSiegeInfo(centity_t *cent, float chX, float chY, float chW, float chH) +{ + siegeExtended_t *se = &cg_siegeExtendedData[cent->currentState.number]; + clientInfo_t *ci; + const char *configstring, *v; + siegeClass_t *siegeClass; + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x; + float y; + float percent; + int ammoMax; + + assert(cent->currentState.number < MAX_CLIENTS); + + if (se->lastUpdated > cg.time) + { //strange, shouldn't happen + return; + } + + if ((cg.time - se->lastUpdated) > 10000) + { //if you haven't received a status update on this guy in 10 seconds, forget about it + return; + } + + if (cent->currentState.eFlags & EF_DEAD) + { //he's dead, don't display info on him + return; + } + + if (cent->currentState.weapon != se->weapon) + { //data is invalidated until it syncs back again + return; + } + + ci = &cgs.clientinfo[cent->currentState.number]; + if (ci->team != cg.predictedPlayerState.persistant[PERS_TEAM]) + { //not on the same team + return; + } + + configstring = CG_ConfigString( cg.predictedPlayerState.clientNum + CS_PLAYERS ); + v = Info_ValueForKey( configstring, "siegeclass" ); + + if (!v || !v[0]) + { //don't have siege class in info? + return; + } + + siegeClass = BG_SiegeFindClassByName(v); + + if (!siegeClass) + { //invalid + return; + } + + if (!(siegeClass->classflags & (1<health/(float)se->maxhealth)*HEALTH_WIDTH; + + //color of the bar + aColor[0] = 0.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing health" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.4f; + + //draw the background (black) + CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor); + + //then draw the other part greyed out + CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor); + + + //now draw his ammo + ammoMax = ammoData[weaponData[cent->currentState.weapon].ammoIndex].max; + if ( (cent->currentState.eFlags & EF_DOUBLE_AMMO) ) + { + ammoMax *= 2; + } + + x = chX+((chW/2)-(HEALTH_WIDTH/2)); + y = (chY+chH) + HEALTH_HEIGHT + 10.0f; + + if (!weaponData[cent->currentState.weapon].energyPerShot && + !weaponData[cent->currentState.weapon].altEnergyPerShot) + { //a weapon that takes no ammo, so show full + percent = HEALTH_WIDTH; + } + else + { + percent = ((float)se->ammo/(float)ammoMax)*HEALTH_WIDTH; + } + + //color of the bar + aColor[0] = 1.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing health" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.4f; + + //draw the background (black) + CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor); + + //then draw the other part greyed out + CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor); +} + +//draw the health bar based on current "health" and maxhealth +void CG_DrawHealthBar(centity_t *cent, float chX, float chY, float chW, float chH) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = chX+((chW/2)-(HEALTH_WIDTH/2)); + float y = (chY+chH) + 8.0f; + float percent = ((float)cent->currentState.health/(float)cent->currentState.maxhealth)*HEALTH_WIDTH; + if (percent <= 0) + { + return; + } + + //color of the bar + if (!cent->currentState.teamowner || cgs.gametype < GT_TEAM) + { //not owned by a team or teamplay + aColor[0] = 1.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + } + else if (cent->currentState.teamowner == cg.predictedPlayerState.persistant[PERS_TEAM]) + { //owned by my team + aColor[0] = 0.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + } + else + { //hostile + aColor[0] = 1.0f; + aColor[1] = 0.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + } + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing health" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.4f; + + //draw the background (black) + CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor); + + //then draw the other part greyed out + CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor); +} + +//same routine (at least for now), draw progress of a "hack" or whatever +void CG_DrawHaqrBar(float chX, float chY, float chW, float chH) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = chX+((chW/2)-(HEALTH_WIDTH/2)); + float y = (chY+chH) + 8.0f; + float percent = (((float)cg.predictedPlayerState.hackingTime-(float)cg.time)/(float)cg.predictedPlayerState.hackingBaseTime)*HEALTH_WIDTH; + + if (percent > HEALTH_WIDTH || + percent < 1.0f) + { + return; + } + + //color of the bar + aColor[0] = 1.0f; + aColor[1] = 1.0f; + aColor[2] = 0.0f; + aColor[3] = 0.4f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out done area + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor); + + //then draw the other part greyed out + CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor); + + //draw the hacker icon + CG_DrawPic(x, y-HEALTH_WIDTH, HEALTH_WIDTH, HEALTH_WIDTH, cgs.media.hackerIconShader); +} + +//generic timing bar +int cg_genericTimerBar = 0; +int cg_genericTimerDur = 0; +vec4_t cg_genericTimerColor; +#define CGTIMERBAR_H 50.0f +#define CGTIMERBAR_W 10.0f +#define CGTIMERBAR_X (SCREEN_WIDTH-CGTIMERBAR_W-120.0f) +#define CGTIMERBAR_Y (SCREEN_HEIGHT-CGTIMERBAR_H-20.0f) +void CG_DrawGenericTimerBar(void) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = CGTIMERBAR_X; + float y = CGTIMERBAR_Y; + float percent = ((float)(cg_genericTimerBar-cg.time)/(float)cg_genericTimerDur)*CGTIMERBAR_H; + + if (percent > CGTIMERBAR_H) + { + return; + } + + if (percent < 0.1f) + { + percent = 0.1f; + } + + //color of the bar + aColor[0] = cg_genericTimerColor[0]; + aColor[1] = cg_genericTimerColor[1]; + aColor[2] = cg_genericTimerColor[2]; + aColor[3] = cg_genericTimerColor[3]; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing fuel" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, CGTIMERBAR_W, CGTIMERBAR_H, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f+(CGTIMERBAR_H-percent), CGTIMERBAR_W-2.0f, CGTIMERBAR_H-1.0f-(CGTIMERBAR_H-percent), aColor); + + //then draw the other part greyed out + CG_FillRect(x+1.0f, y+1.0f, CGTIMERBAR_W-2.0f, CGTIMERBAR_H-percent, cColor); +} + +/* +================= +CG_DrawCrosshair +================= +*/ + +#ifdef _XBOX +int cg_crossHairStatus = 0; +#endif + +float cg_crosshairPrevPosX = 0; +float cg_crosshairPrevPosY = 0; +#define CRAZY_CROSSHAIR_MAX_ERROR_X (100.0f*640.0f/480.0f) +#define CRAZY_CROSSHAIR_MAX_ERROR_Y (100.0f) +void CG_LerpCrosshairPos( float *x, float *y ) +{ + if ( cg_crosshairPrevPosX ) + {//blend from old pos + float maxMove = 30.0f * ((float)cg.frametime/500.0f) * 640.0f/480.0f; + float xDiff = (*x - cg_crosshairPrevPosX); + if ( fabs(xDiff) > CRAZY_CROSSHAIR_MAX_ERROR_X ) + { + maxMove = CRAZY_CROSSHAIR_MAX_ERROR_X; + } + if ( xDiff > maxMove ) + { + *x = cg_crosshairPrevPosX + maxMove; + } + else if ( xDiff < -maxMove ) + { + *x = cg_crosshairPrevPosX - maxMove; + } + } + cg_crosshairPrevPosX = *x; + + if ( cg_crosshairPrevPosY ) + {//blend from old pos + float maxMove = 30.0f * ((float)cg.frametime/500.0f); + float yDiff = (*y - cg_crosshairPrevPosY); + if ( fabs(yDiff) > CRAZY_CROSSHAIR_MAX_ERROR_Y ) + { + maxMove = CRAZY_CROSSHAIR_MAX_ERROR_X; + } + if ( yDiff > maxMove ) + { + *y = cg_crosshairPrevPosY + maxMove; + } + else if ( yDiff < -maxMove ) + { + *y = cg_crosshairPrevPosY - maxMove; + } + } + cg_crosshairPrevPosY = *y; +} + +vec3_t cg_crosshairPos={0,0,0}; +static void CG_DrawCrosshair( vec3_t worldPoint, int chEntValid ) { + float w, h; + qhandle_t hShader = 0; + float f; + float x, y; + qboolean corona = qfalse; + vec4_t ecolor = {0,0,0,0}; + centity_t *crossEnt = NULL; + float chX, chY; + +#ifdef _XBOX + cg_crossHairStatus = 0; +#endif + + if ( worldPoint ) + { + VectorCopy( worldPoint, cg_crosshairPos ); + } + + if ( !cg_drawCrosshair.integer ) + { + return; + } + + if (cg.snap->ps.fallingToDeath) + { + return; + } + + if ( cg.predictedPlayerState.zoomMode != 0 ) + {//not while scoped + return; + } + + if ( cg_crosshairHealth.integer ) + { + vec4_t hcolor; + + CG_ColorForHealth( hcolor ); + trap_R_SetColor( hcolor ); + } + else + { + //set color based on what kind of ent is under crosshair + if ( cg.crosshairClientNum >= ENTITYNUM_WORLD ) + { + trap_R_SetColor( NULL ); + } + //rwwFIXMEFIXME: Write this a different way, it's getting a bit too sloppy looking + else if (chEntValid && + (cg_entities[cg.crosshairClientNum].currentState.number < MAX_CLIENTS || + cg_entities[cg.crosshairClientNum].currentState.eType == ET_NPC || + cg_entities[cg.crosshairClientNum].currentState.shouldtarget || + cg_entities[cg.crosshairClientNum].currentState.health || //always show ents with health data under crosshair + (cg_entities[cg.crosshairClientNum].currentState.eType == ET_MOVER && cg_entities[cg.crosshairClientNum].currentState.bolt1 && cg.predictedPlayerState.weapon == WP_SABER) || + (cg_entities[cg.crosshairClientNum].currentState.eType == ET_MOVER && cg_entities[cg.crosshairClientNum].currentState.teamowner))) + { + crossEnt = &cg_entities[cg.crosshairClientNum]; + + if ( crossEnt->currentState.powerups & (1 <currentState.number < MAX_CLIENTS ) + { + if (cgs.gametype >= GT_TEAM && + cgs.clientinfo[crossEnt->currentState.number].team == cgs.clientinfo[cg.snap->ps.clientNum].team ) + { + //Allies are green + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else + { + if (cgs.gametype == GT_POWERDUEL && + cgs.clientinfo[crossEnt->currentState.number].duelTeam == cgs.clientinfo[cg.snap->ps.clientNum].duelTeam) + { //on the same duel team in powerduel, so he's a friend + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else + { //Enemies are red + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + cg_crossHairStatus = 1; +#endif + } + } + + if (cg.snap->ps.duelInProgress) + { + if (crossEnt->currentState.number != cg.snap->ps.duelIndex) + { //grey out crosshair for everyone but your foe if you're in a duel + ecolor[0] = 0.4; + ecolor[1] = 0.4; + ecolor[2] = 0.4; + } + } + else if (crossEnt->currentState.bolt1) + { //this fellow is in a duel. We just checked if we were in a duel above, so + //this means we aren't and he is. Which of course means our crosshair greys out over him. + ecolor[0] = 0.4; + ecolor[1] = 0.4; + ecolor[2] = 0.4; + } + } + else if (crossEnt->currentState.shouldtarget || crossEnt->currentState.eType == ET_NPC) + { + //VectorCopy( crossEnt->startRGBA, ecolor ); + if ( !ecolor[0] && !ecolor[1] && !ecolor[2] ) + { + // We really don't want black, so set it to yellow + ecolor[0] = 1.0F;//R + ecolor[1] = 0.8F;//G + ecolor[2] = 0.3F;//B + } + + if (crossEnt->currentState.eType == ET_NPC) + { + int plTeam; + if (cgs.gametype == GT_SIEGE) + { + plTeam = cg.predictedPlayerState.persistant[PERS_TEAM]; + } + else + { + plTeam = NPCTEAM_PLAYER; + } + + if ( crossEnt->currentState.powerups & (1 <currentState.teamowner ) + { //not on a team + if (!crossEnt->currentState.teamowner || + crossEnt->currentState.NPC_class == CLASS_VEHICLE) + { //neutral + if (crossEnt->currentState.owner < MAX_CLIENTS) + { //base color on who is pilotting this thing + clientInfo_t *ci = &cgs.clientinfo[crossEnt->currentState.owner]; + + if (cgs.gametype >= GT_TEAM && ci->team == cg.predictedPlayerState.persistant[PERS_TEAM]) + { //friendly + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else + { //hostile + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + cg_crossHairStatus = 1; +#endif + } + } + else + { //unmanned + ecolor[0] = 1.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + } + else + { + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + cg_crossHairStatus = 1; +#endif + } + } + else if ( crossEnt->currentState.teamowner != plTeam ) + {// on enemy team + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + cg_crossHairStatus = 1; +#endif + } + else + { //a friend + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + } + else if ( crossEnt->currentState.teamowner == TEAM_RED + || crossEnt->currentState.teamowner == TEAM_BLUE ) + { + if (cgs.gametype < GT_TEAM) + { //not teamplay, just neutral then + ecolor[0] = 1.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else if ( crossEnt->currentState.teamowner != cgs.clientinfo[cg.snap->ps.clientNum].team ) + { //on the enemy team + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + cg_crossHairStatus = 1; +#endif + } + else + { //on my team + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + } + else if (crossEnt->currentState.owner == cg.snap->ps.clientNum || + (cgs.gametype >= GT_TEAM && crossEnt->currentState.teamowner == cgs.clientinfo[cg.snap->ps.clientNum].team)) + { + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else if (crossEnt->currentState.teamowner == 16 || + (cgs.gametype >= GT_TEAM && crossEnt->currentState.teamowner && crossEnt->currentState.teamowner != cgs.clientinfo[cg.snap->ps.clientNum].team)) + { + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + cg_crossHairStatus = 1; +#endif + } + } + else if (crossEnt->currentState.eType == ET_MOVER && crossEnt->currentState.bolt1 && cg.predictedPlayerState.weapon == WP_SABER) + { //can push/pull this mover. Only show it if we're using the saber. + ecolor[0] = 0.2f; + ecolor[1] = 0.5f; + ecolor[2] = 1.0f; + + corona = qtrue; + } + else if (crossEnt->currentState.eType == ET_MOVER && crossEnt->currentState.teamowner) + { //a team owns this - if it's my team green, if not red, if not teamplay then yellow + if (cgs.gametype < GT_TEAM) + { + ecolor[0] = 1.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + else if (cg.predictedPlayerState.persistant[PERS_TEAM] != crossEnt->currentState.teamowner) + { //not my team + ecolor[0] = 1.0;//R + ecolor[1] = 0.0;//G + ecolor[2] = 0.0;//B +#ifdef _XBOX + cg_crossHairStatus = 1; +#endif + } + else + { //my team + ecolor[0] = 0.0;//R + ecolor[1] = 1.0;//G + ecolor[2] = 0.0;//B + } + } + else if (crossEnt->currentState.health) + { + if (!crossEnt->currentState.teamowner || cgs.gametype < GT_TEAM) + { //not owned by a team or teamplay + ecolor[0] = 1.0f; + ecolor[1] = 1.0f; + ecolor[2] = 0.0f; + } + else if (crossEnt->currentState.teamowner == cg.predictedPlayerState.persistant[PERS_TEAM]) + { //owned by my team + ecolor[0] = 0.0f; + ecolor[1] = 1.0f; + ecolor[2] = 0.0f; + } + else + { //hostile + ecolor[0] = 1.0f; + ecolor[1] = 0.0f; + ecolor[2] = 0.0f; +#ifdef _XBOX + cg_crossHairStatus = 1; +#endif + } + } + + ecolor[3] = 1.0; + + trap_R_SetColor( ecolor ); + } + } + + if ( cg.predictedPlayerState.m_iVehicleNum ) + {//I'm in a vehicle + centity_t *vehCent = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + if ( vehCent + && vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->crosshairShaderHandle ) + { + hShader = vehCent->m_pVehicle->m_pVehicleInfo->crosshairShaderHandle; + } + //bigger by default + w = cg_crosshairSize.value*2.0f; + h = w; + } + else + { + 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 ); + } + + if ( worldPoint && VectorLength( worldPoint ) ) + { + if ( !CG_WorldCoordToScreenCoordFloat( worldPoint, &x, &y ) ) + {//off screen, don't draw it + return; + } + //CG_LerpCrosshairPos( &x, &y ); + x -= 320; + y -= 240; + } + else + { + x = cg_crosshairX.integer; + y = cg_crosshairY.integer; + } + + if ( !hShader ) + { + hShader = cgs.media.crosshairShader[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ]; + } + + chX = x + cg.refdef.x + 0.5 * (640 - w); + chY = y + cg.refdef.y + 0.5 * (480 - h); + trap_R_DrawStretchPic( chX, chY, w, h, 0, 0, 1, 1, hShader ); + + //draw a health bar directly under the crosshair if we're looking at something + //that takes damage + if (crossEnt && + crossEnt->currentState.maxhealth) + { + CG_DrawHealthBar(crossEnt, chX, chY, w, h); + chY += HEALTH_HEIGHT*2; + } + else if (crossEnt && crossEnt->currentState.number < MAX_CLIENTS) + { + if (cgs.gametype == GT_SIEGE) + { + CG_DrawSiegeInfo(crossEnt, chX, chY, w, h); + chY += HEALTH_HEIGHT*4; + } + if (cg.crosshairVehNum && cg.time == cg.crosshairVehTime) + { //it was in the crosshair this frame + centity_t *hisVeh = &cg_entities[cg.crosshairVehNum]; + + if (hisVeh->currentState.eType == ET_NPC && + hisVeh->currentState.NPC_class == CLASS_VEHICLE && + hisVeh->currentState.maxhealth && + hisVeh->m_pVehicle) + { //draw the health for this vehicle + CG_DrawHealthBar(hisVeh, chX, chY, w, h); + chY += HEALTH_HEIGHT*2; + } + } + } + + if (cg.predictedPlayerState.hackingTime) + { //hacking something + CG_DrawHaqrBar(chX, chY, w, h); + } + + if (cg_genericTimerBar > cg.time) + { //draw generic timing bar, can be used for whatever + CG_DrawGenericTimerBar(); + } + + if ( corona ) // drawing extra bits + { + ecolor[3] = 0.5f; + ecolor[0] = ecolor[1] = ecolor[2] = (1 - ecolor[3]) * ( sin( cg.time * 0.001f ) * 0.08f + 0.35f ); // don't draw full color + ecolor[3] = 1.0f; + + trap_R_SetColor( ecolor ); + + w *= 2.0f; + h *= 2.0f; + + trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (640 - w), + y + cg.refdef.y + 0.5 * (480 - h), + w, h, 0, 0, 1, 1, cgs.media.forceCoronaShader ); + } +} + +qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) +{ + float xcenter, ycenter; + vec3_t local, transformed; + vec3_t vfwd; + vec3_t vright; + vec3_t vup; + float xzi; + float yzi; + +// xcenter = cg.refdef.width / 2;//gives screen coords adjusted for resolution +// ycenter = cg.refdef.height / 2;//gives screen coords adjusted for resolution + + //NOTE: did it this way because most draw functions expect virtual 640x480 coords + // and adjust them for current resolution + xcenter = 640.0f / 2.0f;//gives screen coords in virtual 640x480, to be adjusted when drawn + ycenter = 480.0f / 2.0f;//gives screen coords in virtual 640x480, to be adjusted when drawn + + AngleVectors (cg.refdef.viewangles, vfwd, vright, vup); + + VectorSubtract (worldCoord, cg.refdef.vieworg, local); + + transformed[0] = DotProduct(local,vright); + transformed[1] = DotProduct(local,vup); + transformed[2] = DotProduct(local,vfwd); + + // Make sure Z is not negative. + if(transformed[2] < 0.01f) + { + return qfalse; + } + + xzi = xcenter / transformed[2] * (96.0f/cg.refdef.fov_x); + yzi = ycenter / transformed[2] * (102.0f/cg.refdef.fov_y); + + *x = xcenter + xzi * transformed[0]; + *y = ycenter - yzi * transformed[1]; + + return qtrue; +} + +qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) +{ + float xF, yF; + qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ); + *x = (int)xF; + *y = (int)yF; + return retVal; +} + +/* +==================== +CG_SaberClashFlare +==================== +*/ +int cg_saberFlashTime = 0; +vec3_t cg_saberFlashPos = {0, 0, 0}; +void CG_SaberClashFlare( void ) +{ + int t, maxTime = 150; + vec3_t dif; + vec3_t color; + int x,y; + float v, len; + trace_t tr; + + t = cg.time - cg_saberFlashTime; + + if ( t <= 0 || t >= maxTime ) + { + return; + } + + // Don't do clashes for things that are behind us + VectorSubtract( cg_saberFlashPos, cg.refdef.vieworg, dif ); + + if ( DotProduct( dif, cg.refdef.viewaxis[0] ) < 0.2 ) + { + return; + } + + CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, cg_saberFlashPos, -1, CONTENTS_SOLID ); + + if ( tr.fraction < 1.0f ) + { + return; + } + + len = VectorNormalize( dif ); + + // clamp to a known range + /* + if ( len > 800 ) + { + len = 800; + } + */ + if ( len > 1200 ) + { + return; + } + + v = ( 1.0f - ((float)t / maxTime )) * ((1.0f - ( len / 800.0f )) * 2.0f + 0.35f); + if (v < 0.001f) + { + v = 0.001f; + } + + CG_WorldCoordToScreenCoord( cg_saberFlashPos, &x, &y ); + + VectorSet( color, 0.8f, 0.8f, 0.8f ); + trap_R_SetColor( color ); + + CG_DrawPic( x - ( v * 300 ), y - ( v * 300 ), + v * 600, v * 600, + trap_R_RegisterShader( "gfx/effects/saberFlare" )); +} + +void CG_DottedLine( float x1, float y1, float x2, float y2, float dotSize, int numDots, vec4_t color, float alpha ) +{ + float x, y, xDiff, yDiff, xStep, yStep; + vec4_t colorRGBA; + int dotNum = 0; + + VectorCopy4( color, colorRGBA ); + colorRGBA[3] = alpha; + + trap_R_SetColor( colorRGBA ); + + xDiff = x2-x1; + yDiff = y2-y1; + xStep = xDiff/(float)numDots; + yStep = yDiff/(float)numDots; + + for ( dotNum = 0; dotNum < numDots; dotNum++ ) + { + x = x1 + (xStep*dotNum) - (dotSize*0.5f); + y = y1 + (yStep*dotNum) - (dotSize*0.5f); + + CG_DrawPic( x, y, dotSize, dotSize, cgs.media.whiteShader ); + } +} + +void CG_BracketEntity( centity_t *cent, float radius ) +{ + trace_t tr; + vec3_t dif; + float len, size, lineLength, lineWidth; + float x, y; + clientInfo_t *local; + qboolean isEnemy = qfalse; + + VectorSubtract( cent->lerpOrigin, cg.refdef.vieworg, dif ); + len = VectorNormalize( dif ); + + if ( cg.crosshairClientNum != cent->currentState.clientNum + && (!cg.snap||cg.snap->ps.rocketLockIndex!= cent->currentState.clientNum) ) + {//if they're the entity you're locking onto or under your crosshair, always draw bracket + //Hmm... for now, if they're closer than 2000, don't bracket? + if ( len < 2000.0f ) + { + return; + } + + CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, cent->lerpOrigin, -1, CONTENTS_OPAQUE ); + + //don't bracket if can't see them + if ( tr.fraction < 1.0f ) + { + return; + } + } + + if ( !CG_WorldCoordToScreenCoordFloat(cent->lerpOrigin, &x, &y) ) + {//off-screen, don't draw it + return; + } + + //just to see if it's centered + //CG_DrawPic( x-2, y-2, 4, 4, cgs.media.whiteShader ); + + local = &cgs.clientinfo[cg.snap->ps.clientNum]; + if ( cent->currentState.m_iVehicleNum //vehicle has a driver + && cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].infoValid ) + { + if ( cgs.gametype < GT_TEAM ) + {//ffa? + isEnemy = qtrue; + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + else if ( cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].team == local->team ) + { + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_GREEN)] ); + } + else + { + isEnemy = qtrue; + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + } + else if ( cent->currentState.teamowner ) + { + if ( cgs.gametype < GT_TEAM ) + {//ffa? + isEnemy = qtrue; + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + else if ( cent->currentState.teamowner != cg.predictedPlayerState.persistant[PERS_TEAM] ) + {// on enemy team + isEnemy = qtrue; + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + } + else + { //a friend + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_GREEN)] ); + } + } + else + {//FIXME: if we want to ever bracket anything besides vehicles (like siege objectives we want to blow up), we should handle the coloring here + trap_R_SetColor ( NULL ); + } + + if ( len <= 1.0f ) + {//super-close, max out at 400 times radius (which is HUGE) + size = radius*400.0f; + } + else + {//scale by dist + size = radius*(400.0f/len); + } + + if ( size < 1.0f ) + { + size = 1.0f; + } + + //length scales with dist + lineLength = (size*0.1f); + if ( lineLength < 0.5f ) + {//always visible + lineLength = 0.5f; + } + //always visible width + lineWidth = 1.0f; + + x -= (size*0.5f); + y -= (size*0.5f); + + /* + if ( x >= 0 && x <= 640 + && y >= 0 && y <= 480 ) + */ + {//brackets would be drawn on the screen, so draw them + //upper left corner + //horz + CG_DrawPic( x, y, lineLength, lineWidth, cgs.media.whiteShader ); + //vert + CG_DrawPic( x, y, lineWidth, lineLength, cgs.media.whiteShader ); + //upper right corner + //horz + CG_DrawPic( x+size-lineLength, y, lineLength, lineWidth, cgs.media.whiteShader ); + //vert + CG_DrawPic( x+size-lineWidth, y, lineWidth, lineLength, cgs.media.whiteShader ); + //lower left corner + //horz + CG_DrawPic( x, y+size-lineWidth, lineLength, lineWidth, cgs.media.whiteShader ); + //vert + CG_DrawPic( x, y+size-lineLength, lineWidth, lineLength, cgs.media.whiteShader ); + //lower right corner + //horz + CG_DrawPic( x+size-lineLength, y+size-lineWidth, lineLength, lineWidth, cgs.media.whiteShader ); + //vert + CG_DrawPic( x+size-lineWidth, y+size-lineLength, lineWidth, lineLength, cgs.media.whiteShader ); + } + //Lead Indicator... + if ( cg_drawVehLeadIndicator.integer ) + {//draw the lead indicator + if ( isEnemy ) + {//an enemy object + if ( cent->currentState.NPC_class == CLASS_VEHICLE ) + {//enemy vehicle + if ( !VectorCompare( cent->currentState.pos.trDelta, vec3_origin ) ) + {//enemy vehicle is moving + if ( cg.predictedPlayerState.m_iVehicleNum ) + {//I'm in a vehicle + centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + if ( veh //vehicle cent + && veh->m_pVehicle//vehicle + && veh->m_pVehicle->m_pVehicleInfo//vehicle stats + && veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID > VEH_WEAPON_BASE )//valid vehicle weapon + { + vehWeaponInfo_t *vehWeapon = &g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID]; + if ( vehWeapon + && vehWeapon->bIsProjectile//primary weapon's shot is a projectile + && !vehWeapon->bHasGravity//primary weapon's shot is not affected by gravity + && !vehWeapon->fHoming//primary weapon's shot is not homing + && vehWeapon->fSpeed )//primary weapon's shot has speed + {//our primary weapon's projectile has a speed + vec3_t vehDiff, vehLeadPos; + float vehDist, eta; + float leadX, leadY; + + VectorSubtract( cent->lerpOrigin, cg.predictedVehicleState.origin, vehDiff ); + vehDist = VectorNormalize( vehDiff ); + eta = (vehDist/vehWeapon->fSpeed);//how many seconds it would take for my primary weapon's projectile to get from my ship to theirs + //now extrapolate their position that number of seconds into the future based on their velocity + VectorMA( cent->lerpOrigin, eta, cent->currentState.pos.trDelta, vehLeadPos ); + //now we have where we should be aiming at, project that onto the screen at a 2D co-ord + if ( !CG_WorldCoordToScreenCoordFloat(cent->lerpOrigin, &x, &y) ) + {//off-screen, don't draw it + return; + } + if ( !CG_WorldCoordToScreenCoordFloat(vehLeadPos, &leadX, &leadY) ) + {//off-screen, don't draw it + //just draw the line + CG_DottedLine( x, y, leadX, leadY, 1, 10, g_color_table[ColorIndex(COLOR_RED)], 0.5f ); + return; + } + //draw a line from the ship's cur pos to the lead pos + CG_DottedLine( x, y, leadX, leadY, 1, 10, g_color_table[ColorIndex(COLOR_RED)], 0.5f ); + //now draw the lead indicator + trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] ); + CG_DrawPic( leadX-8, leadY-8, 16, 16, trap_R_RegisterShader( "gfx/menus/radar/lead" ) ); + } + } + } + } + } + } + } +} + +qboolean CG_InFighter( void ) +{ + if ( cg.predictedPlayerState.m_iVehicleNum ) + {//I'm in a vehicle + centity_t *vehCent = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + if ( vehCent + && vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + {//I'm in a fighter + return qtrue; + } + } + return qfalse; +} + +qboolean CG_InATST( void ) +{ + if ( cg.predictedPlayerState.m_iVehicleNum ) + {//I'm in a vehicle + centity_t *vehCent = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + if ( vehCent + && vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) + {//I'm in an atst + return qtrue; + } + } + return qfalse; +} + +void CG_DrawBracketedEntities( void ) +{ + int i; + for ( i = 0; i < cg.bracketedEntityCount; i++ ) + { + centity_t *cent = &cg_entities[cg.bracketedEntities[i]]; + CG_BracketEntity( cent, CG_RadiusForCent( cent ) ); + } +} + +//-------------------------------------------------------------- +static void CG_DrawHolocronIcons(void) +//-------------------------------------------------------------- +{ + int icon_size = 40; + int i = 0; + int startx = 10; + int starty = 10;//SCREEN_HEIGHT - icon_size*3; + + int endx = icon_size; + int endy = icon_size; + + if (cg.snap->ps.zoomMode) + { //don't display over zoom mask + return; + } + + if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR) + { + return; + } + + while (i < NUM_FORCE_POWERS) + { + if (cg.snap->ps.holocronBits & (1 << forcePowerSorted[i])) + { + CG_DrawPic( startx, starty, endx, endy, cgs.media.forcePowerIcons[forcePowerSorted[i]]); + starty += (icon_size+2); //+2 for spacing + if ((starty+icon_size) >= SCREEN_HEIGHT-80) + { + starty = 10;//SCREEN_HEIGHT - icon_size*3; + startx += (icon_size+2); + } + } + + i++; + } +} + +static qboolean CG_IsDurationPower(int power) +{ + if (power == FP_HEAL || + power == FP_SPEED || + power == FP_TELEPATHY || + power == FP_RAGE || + power == FP_PROTECT || + power == FP_ABSORB || + power == FP_SEE) + { + return qtrue; + } + + return qfalse; +} + +//-------------------------------------------------------------- +static void CG_DrawActivePowers(void) +//-------------------------------------------------------------- +{ + int icon_size = 40; + int i = 0; + int startx = icon_size*2+16; + int starty = SCREEN_HEIGHT - icon_size*2; + + int endx = icon_size; + int endy = icon_size; + + if (cg.snap->ps.zoomMode) + { //don't display over zoom mask + return; + } + + if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR) + { + return; + } + + while (i < NUM_FORCE_POWERS) + { + if ((cg.snap->ps.fd.forcePowersActive & (1 << forcePowerSorted[i])) && + CG_IsDurationPower(forcePowerSorted[i])) + { + CG_DrawPic( startx, starty, endx, endy, cgs.media.forcePowerIcons[forcePowerSorted[i]]); + startx += (icon_size+2); //+2 for spacing + if ((startx+icon_size) >= SCREEN_WIDTH-80) + { + startx = icon_size*2+16; + starty += (icon_size+2); + } + } + + i++; + } + + //additionally, draw an icon force force rage recovery + if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time) + { + CG_DrawPic( startx, starty, endx, endy, cgs.media.rageRecShader); + } +} + +//-------------------------------------------------------------- +static void CG_DrawRocketLocking( int lockEntNum, int lockTime ) +//-------------------------------------------------------------- +{ + int cx, cy; + vec3_t org; + static int oldDif = 0; + centity_t *cent = &cg_entities[lockEntNum]; + vec4_t color={0.0f,0.0f,0.0f,0.0f}; + float lockTimeInterval = ((cgs.gametype==GT_SIEGE)?2400.0f:1200.0f)/16.0f; + //FIXME: if in a vehicle, use the vehicle's lockOnTime... + int dif = (cg.time - cg.snap->ps.rocketLockTime)/lockTimeInterval; + int i; + + if (!cg.snap->ps.rocketLockTime) + { + return; + } + + if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR) + { + return; + } + + if ( cg.snap->ps.m_iVehicleNum ) + {//driving a vehicle + centity_t *veh = &cg_entities[cg.snap->ps.m_iVehicleNum]; + if ( veh->m_pVehicle ) + { + vehWeaponInfo_t *vehWeapon = NULL; + if ( cg.predictedVehicleState.weaponstate == WEAPON_CHARGING_ALT ) + { + if ( veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID > VEH_WEAPON_BASE + && veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID < MAX_VEH_WEAPONS ) + { + vehWeapon = &g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID]; + } + } + else + { + if ( veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID > VEH_WEAPON_BASE + && veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID < MAX_VEH_WEAPONS ) + { + vehWeapon = &g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID]; + } + } + if ( vehWeapon != NULL ) + {//we are trying to lock on with a valid vehicle weapon, so use *its* locktime, not the hard-coded one + if ( !vehWeapon->iLockOnTime ) + {//instant lock-on + dif = 10.0f; + } + else + {//use the custom vehicle lockOnTime + lockTimeInterval = (vehWeapon->iLockOnTime/16.0f); + dif = (cg.time - cg.snap->ps.rocketLockTime)/lockTimeInterval; + } + } + } + } + //We can't check to see in pmove if players are on the same team, so we resort + //to just not drawing the lock if a teammate is the locked on ent + if (cg.snap->ps.rocketLockIndex >= 0 && + cg.snap->ps.rocketLockIndex < ENTITYNUM_NONE) + { + clientInfo_t *ci = NULL; + + if (cg.snap->ps.rocketLockIndex < MAX_CLIENTS) + { + ci = &cgs.clientinfo[cg.snap->ps.rocketLockIndex]; + } + else + { + ci = cg_entities[cg.snap->ps.rocketLockIndex].npcClient; + } + + if (ci) + { + if (ci->team == cgs.clientinfo[cg.snap->ps.clientNum].team) + { + if (cgs.gametype >= GT_TEAM) + { + return; + } + } + else if (cgs.gametype >= GT_TEAM) + { + centity_t *hitEnt = &cg_entities[cg.snap->ps.rocketLockIndex]; + if (hitEnt->currentState.eType == ET_NPC && + hitEnt->currentState.NPC_class == CLASS_VEHICLE && + hitEnt->currentState.owner < ENTITYNUM_WORLD) + { //this is a vehicle, if it has a pilot and that pilot is on my team, then... + if (hitEnt->currentState.owner < MAX_CLIENTS) + { + ci = &cgs.clientinfo[hitEnt->currentState.owner]; + } + else + { + ci = cg_entities[hitEnt->currentState.owner].npcClient; + } + if (ci && ci->team == cgs.clientinfo[cg.snap->ps.clientNum].team) + { + return; + } + } + } + } + } + + if (cg.snap->ps.rocketLockTime != -1) + { + lastvalidlockdif = dif; + } + else + { + dif = lastvalidlockdif; + } + + if ( !cent ) + { + return; + } + + VectorCopy( cent->lerpOrigin, org ); + + if ( CG_WorldCoordToScreenCoord( org, &cx, &cy )) + { + // we care about distance from enemy to eye, so this is good enough + float sz = Distance( cent->lerpOrigin, cg.refdef.vieworg ) / 1024.0f; + + if ( sz > 1.0f ) + { + sz = 1.0f; + } + else if ( sz < 0.0f ) + { + sz = 0.0f; + } + + sz = (1.0f - sz) * (1.0f - sz) * 32 + 6; + + cy += sz * 0.5f; + + if ( dif < 0 ) + { + oldDif = 0; + return; + } + else if ( dif > 8 ) + { + dif = 8; + } + + // do sounds + if ( oldDif != dif ) + { + if ( dif == 8 ) + { + if ( cg.snap->ps.m_iVehicleNum ) + { + trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/vehicles/weapons/common/lock.wav" )); + } + else + { + trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/rocket/lock.wav" )); + } + } + else + { + if ( cg.snap->ps.m_iVehicleNum ) + { + trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/vehicles/weapons/common/tick.wav" )); + } + else + { + trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/rocket/tick.wav" )); + } + } + } + + oldDif = dif; + + for ( i = 0; i < dif; i++ ) + { + color[0] = 1.0f; + color[1] = 0.0f; + color[2] = 0.0f; + color[3] = 0.1f * i + 0.2f; + + trap_R_SetColor( color ); + + // our slices are offset by about 45 degrees. + CG_DrawRotatePic( cx - sz, cy - sz, sz, sz, i * 45.0f, trap_R_RegisterShaderNoMip( "gfx/2d/wedge" )); + } + + // we are locked and loaded baby + if ( dif == 8 ) + { + color[0] = color[1] = color[2] = sin( cg.time * 0.05f ) * 0.5f + 0.5f; + color[3] = 1.0f; // this art is additive, so the alpha value does nothing + + trap_R_SetColor( color ); + + CG_DrawPic( cx - sz, cy - sz * 2, sz * 2, sz * 2, trap_R_RegisterShaderNoMip( "gfx/2d/lock" )); + } + } +} + +extern void CG_CalcVehMuzzle(Vehicle_t *pVeh, centity_t *ent, int muzzleNum); +qboolean CG_CalcVehicleMuzzlePoint( int entityNum, vec3_t start, vec3_t d_f, vec3_t d_rt, vec3_t d_up) +{ + centity_t *vehCent = &cg_entities[entityNum]; + if ( vehCent->m_pVehicle && vehCent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) + {//draw from barrels + VectorCopy( vehCent->lerpOrigin, start ); + start[2] += vehCent->m_pVehicle->m_pVehicleInfo->height-DEFAULT_MINS_2-48; + AngleVectors( vehCent->lerpAngles, d_f, d_rt, d_up ); + /* + mdxaBone_t boltMatrix; + int bolt; + vec3_t yawOnlyAngles; + + VectorSet( yawOnlyAngles, 0, vehCent->lerpAngles[YAW], 0 ); + + bolt = trap_G2API_AddBolt( vehCent->ghoul2, 0, "*flash1"); + trap_G2API_GetBoltMatrix( vehCent->ghoul2, 0, bolt, &boltMatrix, + yawOnlyAngles, vehCent->lerpOrigin, cg.time, + NULL, vehCent->modelScale ); + + // work the matrix axis stuff into the original axis and origins used. + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, start ); + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_X, d_f ); + VectorClear( d_rt );//don't really need this, do we? + VectorClear( d_up );//don't really need this, do we? + */ + } + else + { + //check to see if we're a turret gunner on this vehicle + if ( cg.predictedPlayerState.generic1 )//as a passenger + {//passenger in a vehicle + if ( vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->maxPassengers ) + {//a vehicle capable of carrying passengers + int turretNum; + for ( turretNum = 0; turretNum < MAX_VEHICLE_TURRETS; turretNum++ ) + { + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].iAmmoMax ) + {// valid turret + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].passengerNum == cg.predictedPlayerState.generic1 ) + {//I control this turret + //Go through all muzzles, average their positions and directions and use the result for crosshair trace + int vehMuzzle, numMuzzles = 0; + vec3_t muzzlesAvgPos={0},muzzlesAvgDir={0}; + int i; + + for ( i = 0; i < MAX_VEHICLE_TURRET_MUZZLES; i++ ) + { + vehMuzzle = vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].iMuzzle[i]; + if ( vehMuzzle ) + { + vehMuzzle -= 1; + CG_CalcVehMuzzle( vehCent->m_pVehicle, vehCent, vehMuzzle ); + VectorAdd( muzzlesAvgPos, vehCent->m_pVehicle->m_vMuzzlePos[vehMuzzle], muzzlesAvgPos ); + VectorAdd( muzzlesAvgDir, vehCent->m_pVehicle->m_vMuzzleDir[vehMuzzle], muzzlesAvgDir ); + numMuzzles++; + } + if ( numMuzzles ) + { + VectorScale( muzzlesAvgPos, 1.0f/(float)numMuzzles, start ); + VectorScale( muzzlesAvgDir, 1.0f/(float)numMuzzles, d_f ); + VectorClear( d_rt ); + VectorClear( d_up ); + return qtrue; + } + } + } + } + } + } + } + VectorCopy( vehCent->lerpOrigin, start ); + AngleVectors( vehCent->lerpAngles, d_f, d_rt, d_up ); + } + return qfalse; +} + +//calc the muzzle point from the e-web itself +void CG_CalcEWebMuzzlePoint(centity_t *cent, vec3_t start, vec3_t d_f, vec3_t d_rt, vec3_t d_up) +{ + int bolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*cannonflash"); + + assert(bolt != -1); + + if (bolt != -1) + { + mdxaBone_t boltMatrix; + + trap_G2API_GetBoltMatrix_NoRecNoRot(cent->ghoul2, 0, bolt, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time, NULL, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, start); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, d_f); + + //these things start the shot a little inside the bbox to assure not starting in something solid + VectorMA(start, -16.0f, d_f, start); + + //I guess + VectorClear( d_rt );//don't really need this, do we? + VectorClear( d_up );//don't really need this, do we? + } +} + +/* +================= +CG_`Entity +================= +*/ +#define MAX_XHAIR_DIST_ACCURACY 20000.0f +static void CG_ScanForCrosshairEntity( void ) { + trace_t trace; + vec3_t start, end; + int content; + int ignore; + qboolean bVehCheckTraceFromCamPos = qfalse; + + ignore = cg.predictedPlayerState.clientNum; + + if ( cg_dynamicCrosshair.integer ) + { + vec3_t d_f, d_rt, d_up; + /* + if ( cg.snap->ps.weapon == WP_NONE || + cg.snap->ps.weapon == WP_SABER || + cg.snap->ps.weapon == WP_STUN_BATON) + { + VectorCopy( cg.refdef.vieworg, start ); + AngleVectors( cg.refdef.viewangles, d_f, d_rt, d_up ); + } + else + */ + //For now we still want to draw the crosshair in relation to the player's world coordinates + //even if we have a melee weapon/no weapon. + if ( cg.predictedPlayerState.m_iVehicleNum && (cg.predictedPlayerState.eFlags&EF_NODRAW) ) + {//we're *inside* a vehicle + //do the vehicle's crosshair instead + centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + qboolean gunner = qfalse; + + //if (veh->currentState.owner == cg.predictedPlayerState.clientNum) + { //the pilot + ignore = cg.predictedPlayerState.m_iVehicleNum; + gunner = CG_CalcVehicleMuzzlePoint(cg.predictedPlayerState.m_iVehicleNum, start, d_f, d_rt, d_up); + } + /* + else + { //a passenger + ignore = cg.predictedPlayerState.m_iVehicleNum; + VectorCopy( veh->lerpOrigin, start ); + AngleVectors( veh->lerpAngles, d_f, d_rt, d_up ); + VectorMA(start, 32.0f, d_f, start); //super hack + } + */ + if ( veh->m_pVehicle + && veh->m_pVehicle->m_pVehicleInfo + && veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER + && cg.distanceCull > MAX_XHAIR_DIST_ACCURACY + && !gunner ) + { + //NOTE: on huge maps, the crosshair gets inaccurate at close range, + // so we'll do an extra G2 trace from the cg.refdef.vieworg + // to see if we hit anything closer and auto-aim at it if so + bVehCheckTraceFromCamPos = qtrue; + } + } + else if (cg.snap && cg.snap->ps.weapon == WP_EMPLACED_GUN && cg.snap->ps.emplacedIndex && + cg_entities[cg.snap->ps.emplacedIndex].ghoul2 && cg_entities[cg.snap->ps.emplacedIndex].currentState.weapon == WP_NONE) + { //locked into our e-web, calc the muzzle from it + CG_CalcEWebMuzzlePoint(&cg_entities[cg.snap->ps.emplacedIndex], start, d_f, d_rt, d_up); + } + else + { + if (cg.snap && cg.snap->ps.weapon == WP_EMPLACED_GUN && cg.snap->ps.emplacedIndex) + { + vec3_t pitchConstraint; + + ignore = cg.snap->ps.emplacedIndex; + + VectorCopy(cg.refdef.viewangles, pitchConstraint); + + if (cg.renderingThirdPerson) + { + VectorCopy(cg.predictedPlayerState.viewangles, pitchConstraint); + } + else + { + VectorCopy(cg.refdef.viewangles, pitchConstraint); + } + + if (pitchConstraint[PITCH] > 40) + { + pitchConstraint[PITCH] = 40; + } + + AngleVectors( pitchConstraint, d_f, d_rt, d_up ); + } + else + { + vec3_t pitchConstraint; + + if (cg.renderingThirdPerson) + { + VectorCopy(cg.predictedPlayerState.viewangles, pitchConstraint); + } + else + { + VectorCopy(cg.refdef.viewangles, pitchConstraint); + } + + AngleVectors( pitchConstraint, d_f, d_rt, d_up ); + } + CG_CalcMuzzlePoint(cg.snap->ps.clientNum, start); + } + + VectorMA( start, cg.distanceCull, d_f, end ); + } + else + { + VectorCopy( cg.refdef.vieworg, start ); + VectorMA( start, 131072, cg.refdef.viewaxis[0], end ); + } + + if ( cg_dynamicCrosshair.integer && cg_dynamicCrosshairPrecision.integer ) + { //then do a trace with ghoul2 models in mind + CG_G2Trace( &trace, start, vec3_origin, vec3_origin, end, + ignore, CONTENTS_SOLID|CONTENTS_BODY ); + if ( bVehCheckTraceFromCamPos ) + { + //NOTE: this MUST stay up to date with the method used in WP_VehCheckTraceFromCamPos + centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + trace_t extraTrace; + vec3_t viewDir2End, extraEnd; + float minAutoAimDist = Distance( veh->lerpOrigin, cg.refdef.vieworg ) + (veh->m_pVehicle->m_pVehicleInfo->length/2.0f) + 200.0f; + + VectorSubtract( end, cg.refdef.vieworg, viewDir2End ); + VectorNormalize( viewDir2End ); + VectorMA( cg.refdef.vieworg, MAX_XHAIR_DIST_ACCURACY, viewDir2End, extraEnd ); + CG_G2Trace( &extraTrace, cg.refdef.vieworg, vec3_origin, vec3_origin, extraEnd, + ignore, CONTENTS_SOLID|CONTENTS_BODY ); + if ( !extraTrace.allsolid + && !extraTrace.startsolid ) + { + if ( extraTrace.fraction < 1.0f ) + { + if ( (extraTrace.fraction*MAX_XHAIR_DIST_ACCURACY) > minAutoAimDist ) + { + if ( ((extraTrace.fraction*MAX_XHAIR_DIST_ACCURACY)-Distance( veh->lerpOrigin, cg.refdef.vieworg )) < (trace.fraction*cg.distanceCull) ) + {//this trace hit *something* that's closer than the thing the main trace hit, so use this result instead + memcpy( &trace, &extraTrace, sizeof( trace_t ) ); + } + } + } + } + } + } + else + { + CG_Trace( &trace, start, vec3_origin, vec3_origin, end, + ignore, CONTENTS_SOLID|CONTENTS_BODY ); + } + + if (trace.entityNum < MAX_CLIENTS) + { + if (CG_IsMindTricked(cg_entities[trace.entityNum].currentState.trickedentindex, + cg_entities[trace.entityNum].currentState.trickedentindex2, + cg_entities[trace.entityNum].currentState.trickedentindex3, + cg_entities[trace.entityNum].currentState.trickedentindex4, + cg.snap->ps.clientNum)) + { + if (cg.crosshairClientNum == trace.entityNum) + { + cg.crosshairClientNum = ENTITYNUM_NONE; + cg.crosshairClientTime = 0; + } + + CG_DrawCrosshair(trace.endpos, 0); + + return; //this entity is mind-tricking the current client, so don't render it + } + } + + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR) + { + if (trace.entityNum < /*MAX_CLIENTS*/ENTITYNUM_WORLD) + { + cg.crosshairClientNum = trace.entityNum; + cg.crosshairClientTime = cg.time; + + if (cg.crosshairClientNum < ENTITYNUM_WORLD) + { + centity_t *veh = &cg_entities[cg.crosshairClientNum]; + + if (veh->currentState.eType == ET_NPC && + veh->currentState.NPC_class == CLASS_VEHICLE && + veh->currentState.owner < MAX_CLIENTS) + { //draw the name of the pilot then + cg.crosshairClientNum = veh->currentState.owner; + cg.crosshairVehNum = veh->currentState.number; + cg.crosshairVehTime = cg.time; + } + } + + CG_DrawCrosshair(trace.endpos, 1); + } + else + { + CG_DrawCrosshair(trace.endpos, 0); + } + } + +// 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; + } + + // update the fade timer + cg.crosshairClientNum = trace.entityNum; + cg.crosshairClientTime = cg.time; +} + +void CG_SanitizeString( char *in, char *out ) +{ + int i = 0; + int r = 0; + + while (in[i]) + { + if (i >= 128-1) + { //the ui truncates the name here.. + break; + } + + if (in[i] == '^') + { + if (in[i+1] >= 48 && //'0' + in[i+1] <= 57) //'9' + { //only skip it if there's a number after it for the color + i += 2; + continue; + } + else + { //just skip the ^ + i++; + continue; + } + } + + if (in[i] < 32) + { + i++; + continue; + } + + out[r] = in[i]; + r++; + i++; + } + out[r] = 0; +} + +/* +===================== +CG_DrawCrosshairNames +===================== +*/ +static void CG_DrawCrosshairNames( void ) { + float *color; + vec4_t tcolor; + char *name; + char sanitized[1024]; + int baseColor; + qboolean isVeh = qfalse; + + if ( !cg_drawCrosshair.integer ) { + return; + } + + // scan the known entities to see if the crosshair is sighted on one + CG_ScanForCrosshairEntity(); + + if ( !cg_drawCrosshairNames.integer ) { + return; + } + //rww - still do the trace, our dynamic crosshair depends on it + + if (cg.crosshairClientNum < ENTITYNUM_WORLD) + { + centity_t *veh = &cg_entities[cg.crosshairClientNum]; + + if (veh->currentState.eType == ET_NPC && + veh->currentState.NPC_class == CLASS_VEHICLE && + veh->currentState.owner < MAX_CLIENTS) + { //draw the name of the pilot then + cg.crosshairClientNum = veh->currentState.owner; + cg.crosshairVehNum = veh->currentState.number; + cg.crosshairVehTime = cg.time; + isVeh = qtrue; //so we know we're drawing the pilot's name + } + } + + if (cg.crosshairClientNum >= MAX_CLIENTS) + { + return; + } + + if (cg_entities[cg.crosshairClientNum].currentState.powerups & (1 << PW_CLOAKED)) + { + return; + } + + // 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; + + if (cgs.gametype >= GT_TEAM) + { + //if (cgs.gametype == GT_SIEGE) + if (1) + { //instead of team-based we'll make it oriented based on which team we're on + if (cgs.clientinfo[cg.crosshairClientNum].team == cg.predictedPlayerState.persistant[PERS_TEAM]) + { + baseColor = CT_GREEN; + } + else + { + baseColor = CT_RED; + } + } + else + { + if (cgs.clientinfo[cg.crosshairClientNum].team == TEAM_RED) + { + baseColor = CT_RED; + } + else + { + baseColor = CT_BLUE; + } + } + } + else + { + //baseColor = CT_WHITE; + if (cgs.gametype == GT_POWERDUEL && + cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR && + cgs.clientinfo[cg.crosshairClientNum].duelTeam == cgs.clientinfo[cg.predictedPlayerState.clientNum].duelTeam) + { //on the same duel team in powerduel, so he's a friend + baseColor = CT_GREEN; + } + else + { + baseColor = CT_RED; //just make it red in nonteam modes since everyone is hostile and crosshair will be red on them too + } + } + + if (cg.snap->ps.duelInProgress) + { + if (cg.crosshairClientNum != cg.snap->ps.duelIndex) + { //grey out crosshair for everyone but your foe if you're in a duel + baseColor = CT_BLACK; + } + } + else if (cg_entities[cg.crosshairClientNum].currentState.bolt1) + { //this fellow is in a duel. We just checked if we were in a duel above, so + //this means we aren't and he is. Which of course means our crosshair greys out over him. + baseColor = CT_BLACK; + } + + tcolor[0] = colorTable[baseColor][0]; + tcolor[1] = colorTable[baseColor][1]; + tcolor[2] = colorTable[baseColor][2]; + tcolor[3] = color[3]*0.5f; + + CG_SanitizeString(name, sanitized); + + if (isVeh) + { + char str[MAX_STRING_CHARS]; + Com_sprintf(str, MAX_STRING_CHARS, "%s (pilot)", sanitized); + UI_DrawProportionalString(320, 170, str, UI_CENTER, tcolor); + } + else + { + UI_DrawProportionalString(320, 170, sanitized, UI_CENTER, tcolor); + } + + trap_R_SetColor( NULL ); +} + + +//============================================================================== + +/* +================= +CG_DrawSpectator +================= +*/ +static void CG_DrawSpectator(void) +{ + const char* s; + + s = CG_GetStringEdString("MP_INGAME", "SPECTATOR"); + if ((cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) && + cgs.duelist1 != -1 && + cgs.duelist2 != -1) + { + char text[1024]; + int size = 64; + + if (cgs.gametype == GT_POWERDUEL && cgs.duelist3 != -1) + { + Com_sprintf(text, sizeof(text), "%s^7 %s %s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name, CG_GetStringEdString("MP_INGAME", "AND"), cgs.clientinfo[cgs.duelist3].name); + } + else + { + Com_sprintf(text, sizeof(text), "%s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name); + } + CG_Text_Paint ( 320 - CG_Text_Width ( text, 1.0f, 3 ) / 2, 420, 1.0f, colorWhite, text, 0, 0, 0, 3 ); + + trap_R_SetColor( colorTable[CT_WHITE] ); + if ( cgs.clientinfo[cgs.duelist1].modelIcon ) + { + CG_DrawPic( 10, SCREEN_HEIGHT-(size*1.5), size, size, cgs.clientinfo[cgs.duelist1].modelIcon ); + } + if ( cgs.clientinfo[cgs.duelist2].modelIcon ) + { + CG_DrawPic( SCREEN_WIDTH-size-10, SCREEN_HEIGHT-(size*1.5), size, size, cgs.clientinfo[cgs.duelist2].modelIcon ); + } + +// nmckenzie: DUEL_HEALTH + if (cgs.gametype == GT_DUEL) + { + if ( cgs.showDuelHealths >= 1) + { // draw the healths on the two guys - how does this interact with power duel, though? + CG_DrawDuelistHealth ( 10, SCREEN_HEIGHT-(size*1.5) - 12, 64, 8, 1 ); + CG_DrawDuelistHealth ( SCREEN_WIDTH-size-10, SCREEN_HEIGHT-(size*1.5) - 12, 64, 8, 2 ); + } + } + + if (cgs.gametype != GT_POWERDUEL) + { + Com_sprintf(text, sizeof(text), "%i/%i", cgs.clientinfo[cgs.duelist1].score, cgs.fraglimit ); + CG_Text_Paint( 42 - CG_Text_Width( text, 1.0f, 2 ) / 2, SCREEN_HEIGHT-(size*1.5) + 64, 1.0f, colorWhite, text, 0, 0, 0, 2 ); + + Com_sprintf(text, sizeof(text), "%i/%i", cgs.clientinfo[cgs.duelist2].score, cgs.fraglimit ); + CG_Text_Paint( SCREEN_WIDTH-size+22 - CG_Text_Width( text, 1.0f, 2 ) / 2, SCREEN_HEIGHT-(size*1.5) + 64, 1.0f, colorWhite, text, 0, 0, 0, 2 ); + } + + if (cgs.gametype == GT_POWERDUEL && cgs.duelist3 != -1) + { + if ( cgs.clientinfo[cgs.duelist3].modelIcon ) + { + CG_DrawPic( SCREEN_WIDTH-size-10, SCREEN_HEIGHT-(size*2.8), size, size, cgs.clientinfo[cgs.duelist3].modelIcon ); + } + } + } + else + { + CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 420, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + } + + if ( cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL ) + { + s = CG_GetStringEdString("MP_INGAME", "WAITING_TO_PLAY"); // "waiting to play"; + CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 440, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + } + else //if ( cgs.gametype >= GT_TEAM ) + { + //s = "press ESC and use the JOIN menu to play"; + s = CG_GetStringEdString("MP_INGAME", "SPEC_CHOOSEJOIN"); + CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 440, 1.0f, colorWhite, s, 0, 0, 0, 3 ); + } +} + +/* +================= +CG_DrawVote +================= +*/ +static void CG_DrawVote(void) { + const char *s; + int sec; + char sYes[20]; + char sNo[20]; + char sVote[20]; + char sCmd[100]; + const char* sParm = 0; + + 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; + } + + if (strncmp(cgs.voteString, "map_restart", 11)==0) + { + trap_SP_GetStringTextString("MENUS_RESTART_MAP", sCmd, sizeof(sCmd) ); + } + else if (strncmp(cgs.voteString, "vstr nextmap", 12)==0) + { + trap_SP_GetStringTextString("MENUS_NEXT_MAP", sCmd, sizeof(sCmd) ); + } + else if (strncmp(cgs.voteString, "g_doWarmup", 10)==0) + { + trap_SP_GetStringTextString("MENUS_WARMUP", sCmd, sizeof(sCmd) ); + } + else if (strncmp(cgs.voteString, "g_gametype", 10)==0) + { + trap_SP_GetStringTextString("MENUS_GAME_TYPE", sCmd, sizeof(sCmd) ); + if ( stricmp("Free For All", cgs.voteString+11)==0 ) + { + sParm = CG_GetStringEdString("MENUS", "FREE_FOR_ALL"); + } + else if ( stricmp("Duel", cgs.voteString+11)==0 ) + { + sParm = CG_GetStringEdString("MENUS", "DUEL"); + } + else if ( stricmp("Holocron FFA", cgs.voteString+11)==0 ) + { + sParm = CG_GetStringEdString("MENUS", "HOLOCRON_FFA"); + } + else if ( stricmp("Power Duel", cgs.voteString+11)==0 ) + { + sParm = CG_GetStringEdString("MENUS", "POWERDUEL"); + } + else if ( stricmp("Team FFA", cgs.voteString+11)==0 ) + { + sParm = CG_GetStringEdString("MENUS", "TEAM_FFA"); + } + else if ( stricmp("Siege", cgs.voteString+11)==0 ) + { + sParm = CG_GetStringEdString("MENUS", "SIEGE"); + } + else if ( stricmp("Capture the Flag", cgs.voteString+11)==0 ) + { + sParm = CG_GetStringEdString("MENUS", "CAPTURE_THE_FLAG"); + } + else if ( stricmp("Capture the Ysalamiri", cgs.voteString+11)==0 ) + { + sParm = CG_GetStringEdString("MENUS", "CAPTURE_THE_YSALIMARI"); + } + } + else if (strncmp(cgs.voteString, "map", 3)==0) + { + trap_SP_GetStringTextString("MENUS_NEW_MAP", sCmd, sizeof(sCmd) ); + sParm = cgs.voteString+4; + } + else if (strncmp(cgs.voteString, "kick", 4)==0) + { + trap_SP_GetStringTextString("MENUS_KICK_PLAYER", sCmd, sizeof(sCmd) ); + sParm = cgs.voteString+5; + } + + + + trap_SP_GetStringTextString("MENUS_VOTE", sVote, sizeof(sVote) ); + trap_SP_GetStringTextString("MENUS_YES", sYes, sizeof(sYes) ); + trap_SP_GetStringTextString("MENUS_NO", sNo, sizeof(sNo) ); + + if (sParm && sParm[0]) + { + s = va("%s(%i):<%s %s> %s:%i %s:%i", sVote, sec, sCmd, sParm, sYes, cgs.voteYes, sNo, cgs.voteNo); + } + else + { + s = va("%s(%i):<%s> %s:%i %s:%i", sVote, sec, sCmd, sYes, cgs.voteYes, sNo, cgs.voteNo); + } + CG_DrawSmallString( 4, 58, s, 1.0F ); + s = CG_GetStringEdString("MP_INGAME", "OR_PRESS_ESC_THEN_CLICK_VOTE"); // s = "or press ESC then click Vote"; + CG_DrawSmallString( 4, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F ); +} + +/* +================= +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; + } + if (strstr(cgs.teamVoteString[cs_offset], "leader")) + { + int i = 0; + + while (cgs.teamVoteString[cs_offset][i] && cgs.teamVoteString[cs_offset][i] != ' ') + { + i++; + } + + if (cgs.teamVoteString[cs_offset][i] == ' ') + { + int voteIndex = 0; + char voteIndexStr[256]; + + i++; + + while (cgs.teamVoteString[cs_offset][i]) + { + voteIndexStr[voteIndex] = cgs.teamVoteString[cs_offset][i]; + voteIndex++; + i++; + } + voteIndexStr[voteIndex] = 0; + + voteIndex = atoi(voteIndexStr); + + s = va("TEAMVOTE(%i):(Make %s the new team leader) yes:%i no:%i", sec, cgs.clientinfo[voteIndex].name, + cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); + } + else + { + s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], + cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); + } + } + else + { + s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], + cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); + } + CG_DrawSmallString( 4, 90, s, 1.0F ); +} + +static qboolean CG_DrawScoreboard() { + return CG_DrawOldScoreboard(); +#if 0 + 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; +#endif +} + +/* +================= +CG_DrawIntermission +================= +*/ +static void CG_DrawIntermission( void ) { +// int key; + //if (cg_singlePlayer.integer) { + // CG_DrawCenterString(); + // return; + //} + cg.scoreFadeTime = cg.time; + cg.scoreBoardShowing = CG_DrawScoreboard(); +} + +/* +================= +CG_DrawFollow +================= +*/ +static qboolean CG_DrawFollow( void ) +{ + const char *s; + + if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) + { + return qfalse; + } + +// s = "following"; + if (cgs.gametype == GT_POWERDUEL) + { + clientInfo_t *ci = &cgs.clientinfo[ cg.snap->ps.clientNum ]; + + if (ci->duelTeam == DUELTEAM_LONE) + { + s = CG_GetStringEdString("MP_INGAME", "FOLLOWINGLONE"); + } + else if (ci->duelTeam == DUELTEAM_DOUBLE) + { + s = CG_GetStringEdString("MP_INGAME", "FOLLOWINGDOUBLE"); + } + else + { + s = CG_GetStringEdString("MP_INGAME", "FOLLOWING"); + } + } + else + { + s = CG_GetStringEdString("MP_INGAME", "FOLLOWING"); + } + + CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, 60, 1.0f, colorWhite, s, 0, 0, 0, FONT_MEDIUM ); + + s = cgs.clientinfo[ cg.snap->ps.clientNum ].name; + CG_Text_Paint ( 320 - CG_Text_Width ( s, 2.0f, FONT_MEDIUM ) / 2, 80, 2.0f, colorWhite, s, 0, 0, 0, FONT_MEDIUM ); + + return qtrue; +} + +#if 0 +static void CG_DrawTemporaryStats() +{ //placeholder for testing (draws ammo and force power) + char s[512]; + + if (!cg.snap) + { + return; + } + + sprintf(s, "Force: %i", cg.snap->ps.fd.forcePower); + + CG_DrawBigString(SCREEN_WIDTH-164, SCREEN_HEIGHT-dmgIndicSize, s, 1.0f); + + sprintf(s, "Ammo: %i", cg.snap->ps.ammo[weaponData[cg.snap->ps.weapon].ammoIndex]); + + CG_DrawBigString(SCREEN_WIDTH-164, SCREEN_HEIGHT-112, s, 1.0f); + + sprintf(s, "Health: %i", cg.snap->ps.stats[STAT_HEALTH]); + + CG_DrawBigString(8, SCREEN_HEIGHT-dmgIndicSize, s, 1.0f); + + sprintf(s, "Armor: %i", cg.snap->ps.stats[STAT_ARMOR]); + + CG_DrawBigString(8, SCREEN_HEIGHT-112, s, 1.0f); +} +#endif + +/* +================= +CG_DrawAmmoWarning +================= +*/ +static void CG_DrawAmmoWarning( void ) { +#if 0 + const char *s; + int w; + + if (!cg_drawStatus.integer) + { + return; + } + + if ( cg_drawAmmoWarning.integer == 0 ) { + return; + } + + if ( !cg.lowAmmoWarning ) { + return; + } + + if ( cg.lowAmmoWarning == 2 ) { + s = "OUT OF AMMO"; + } else { + s = "LOW AMMO WARNING"; + } + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + CG_DrawBigString(320 - w / 2, 64, s, 1.0F); +#endif +} + + + +/* +================= +CG_DrawWarmup +================= +*/ +static void CG_DrawWarmup( void ) { + int w; + int sec; + int i; + float scale; + int cw; + const char *s; + + sec = cg.warmup; + if ( !sec ) { + return; + } + + if ( sec < 0 ) { +// s = "Waiting for players"; + s = CG_GetStringEdString("MP_INGAME", "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_DUEL || cgs.gametype == GT_POWERDUEL) + { + // find the two active players + clientInfo_t *ci1, *ci2, *ci3; + + ci1 = NULL; + ci2 = NULL; + ci3 = NULL; + + if (cgs.gametype == GT_POWERDUEL) + { + if (cgs.duelist1 != -1) + { + ci1 = &cgs.clientinfo[cgs.duelist1]; + } + if (cgs.duelist2 != -1) + { + ci2 = &cgs.clientinfo[cgs.duelist2]; + } + if (cgs.duelist3 != -1) + { + ci3 = &cgs.clientinfo[cgs.duelist3]; + } + } + else + { + 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 ) + { + if (ci3) + { + s = va( "%s vs %s and %s", ci1->name, ci2->name, ci3->name ); + } + else + { + s = va( "%s vs %s", ci1->name, ci2->name ); + } + w = CG_Text_Width(s, 0.6f, FONT_MEDIUM); + CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE,FONT_MEDIUM); + } + } else { + if ( cgs.gametype == GT_FFA ) { + s = CG_GetStringEdString("MENUS", "FREE_FOR_ALL");//"Free For All"; + } else if ( cgs.gametype == GT_HOLOCRON ) { + s = CG_GetStringEdString("MENUS", "HOLOCRON_FFA");//"Holocron FFA"; + } else if ( cgs.gametype == GT_JEDIMASTER ) { + s = CG_GetStringEdString("MENUS", "POWERDUEL");//"Jedi Master";?? + } else if ( cgs.gametype == GT_TEAM ) { + s = CG_GetStringEdString("MENUS", "TEAM_FFA");//"Team FFA"; + } else if ( cgs.gametype == GT_SIEGE ) { + s = CG_GetStringEdString("MENUS", "SIEGE");//"Siege"; + } else if ( cgs.gametype == GT_CTF ) { + s = CG_GetStringEdString("MENUS", "CAPTURE_THE_FLAG");//"Capture the Flag"; + } else if ( cgs.gametype == GT_CTY ) { + s = CG_GetStringEdString("MENUS", "CAPTURE_THE_YSALIMARI");//"Capture the Ysalamiri"; + } else { + s = ""; + } + w = CG_Text_Width(s, 1.5f, FONT_MEDIUM); + CG_Text_Paint(320 - w / 2, 90, 1.5f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE,FONT_MEDIUM); + } + + sec = ( sec - cg.time ) / 1000; + if ( sec < 0 ) { + cg.warmup = 0; + sec = 0; + } +// s = va( "Starts in: %i", sec + 1 ); + s = va( "%s: %i",CG_GetStringEdString("MP_INGAME", "STARTS_IN"), sec + 1 ); + if ( sec != cg.warmupCount ) { + cg.warmupCount = sec; + + if (cgs.gametype != GT_SIEGE) + { + 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 = 1.25f; + break; + case 1: + cw = 24; + scale = 1.15f; + break; + case 2: + cw = 20; + scale = 1.05f; + break; + default: + cw = 16; + scale = 0.9f; + break; + } + + w = CG_Text_Width(s, scale, FONT_MEDIUM); + CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM); +} + +//================================================================================== +/* +================= +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; + } + } +} + +void CG_DrawFlagStatus() +{ + int myFlagTakenShader = 0; + int theirFlagShader = 0; + int team = 0; + int startDrawPos = 2; + int ico_size = 32; + + if (!cg.snap) + { + return; + } + + if (cgs.gametype != GT_CTF && cgs.gametype != GT_CTY) + { + return; + } + + team = cg.snap->ps.persistant[PERS_TEAM]; + + if (cgs.gametype == GT_CTY) + { + if (team == TEAM_RED) + { + myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" ); + theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" ); + } + else + { + myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" ); + theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" ); + } + } + else + { + if (team == TEAM_RED) + { + myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" ); + theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag" ); + } + else + { + myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" ); + theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag" ); + } + } + + if (CG_YourTeamHasFlag()) + { + //CG_DrawPic( startDrawPos, 330, ico_size, ico_size, theirFlagShader ); + CG_DrawPic( 2, 330-startDrawPos, ico_size, ico_size, theirFlagShader ); + startDrawPos += ico_size+2; + } + + if (CG_OtherTeamHasFlag()) + { + //CG_DrawPic( startDrawPos, 330, ico_size, ico_size, myFlagTakenShader ); + CG_DrawPic( 2, 330-startDrawPos, ico_size, ico_size, myFlagTakenShader ); + } +} + +//draw meter showing jetpack fuel when it's not full +#define JPFUELBAR_H 100.0f +#define JPFUELBAR_W 20.0f +#define JPFUELBAR_X (SCREEN_WIDTH-JPFUELBAR_W-8.0f) +#define JPFUELBAR_Y 260.0f +void CG_DrawJetpackFuel(void) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = JPFUELBAR_X; + float y = JPFUELBAR_Y; + float percent = ((float)cg.snap->ps.jetpackFuel/100.0f)*JPFUELBAR_H; + + if (percent > JPFUELBAR_H) + { + return; + } + + if (percent < 0.1f) + { + percent = 0.1f; + } + + //color of the bar + aColor[0] = 0.5f; + aColor[1] = 0.0f; + aColor[2] = 0.0f; + aColor[3] = 0.8f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing fuel" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, JPFUELBAR_W, JPFUELBAR_H, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f+(JPFUELBAR_H-percent), JPFUELBAR_W-1.0f, JPFUELBAR_H-1.0f-(JPFUELBAR_H-percent), aColor); + + //then draw the other part greyed out + CG_FillRect(x+1.0f, y+1.0f, JPFUELBAR_W-1.0f, JPFUELBAR_H-percent, cColor); +} + +//draw meter showing e-web health when it is in use +#define EWEBHEALTH_H 100.0f +#define EWEBHEALTH_W 20.0f +#define EWEBHEALTH_X (SCREEN_WIDTH-EWEBHEALTH_W-8.0f) +#define EWEBHEALTH_Y 290.0f +void CG_DrawEWebHealth(void) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = EWEBHEALTH_X; + float y = EWEBHEALTH_Y; + centity_t *eweb = &cg_entities[cg.predictedPlayerState.emplacedIndex]; + float percent = ((float)eweb->currentState.health/eweb->currentState.maxhealth)*EWEBHEALTH_H; + + if (percent > EWEBHEALTH_H) + { + return; + } + + if (percent < 0.1f) + { + percent = 0.1f; + } + + //kind of hacky, need to pass a coordinate in here + if (cg.snap->ps.jetpackFuel < 100) + { + x -= (JPFUELBAR_W+8.0f); + } + if (cg.snap->ps.cloakFuel < 100) + { + x -= (JPFUELBAR_W+8.0f); + } + + //color of the bar + aColor[0] = 0.5f; + aColor[1] = 0.0f; + aColor[2] = 0.0f; + aColor[3] = 0.8f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing fuel" + cColor[0] = 0.5f; + cColor[1] = 0.5f; + cColor[2] = 0.5f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, EWEBHEALTH_W, EWEBHEALTH_H, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much health there is in the color specified + CG_FillRect(x+1.0f, y+1.0f+(EWEBHEALTH_H-percent), EWEBHEALTH_W-1.0f, EWEBHEALTH_H-1.0f-(EWEBHEALTH_H-percent), aColor); + + //then draw the other part greyed out + CG_FillRect(x+1.0f, y+1.0f, EWEBHEALTH_W-1.0f, EWEBHEALTH_H-percent, cColor); +} + +//draw meter showing cloak fuel when it's not full +#define CLFUELBAR_H 100.0f +#define CLFUELBAR_W 20.0f +#define CLFUELBAR_X (SCREEN_WIDTH-CLFUELBAR_W-8.0f) +#define CLFUELBAR_Y 260.0f +void CG_DrawCloakFuel(void) +{ + vec4_t aColor; + vec4_t bColor; + vec4_t cColor; + float x = CLFUELBAR_X; + float y = CLFUELBAR_Y; + float percent = ((float)cg.snap->ps.cloakFuel/100.0f)*CLFUELBAR_H; + + if (percent > CLFUELBAR_H) + { + return; + } + + if ( cg.snap->ps.jetpackFuel < 100 ) + {//if drawing jetpack fuel bar too, then move this over...? + x -= (JPFUELBAR_W+8.0f); + } + + if (percent < 0.1f) + { + percent = 0.1f; + } + + //color of the bar + aColor[0] = 0.0f; + aColor[1] = 0.0f; + aColor[2] = 0.6f; + aColor[3] = 0.8f; + + //color of the border + bColor[0] = 0.0f; + bColor[1] = 0.0f; + bColor[2] = 0.0f; + bColor[3] = 0.3f; + + //color of greyed out "missing fuel" + cColor[0] = 0.1f; + cColor[1] = 0.1f; + cColor[2] = 0.3f; + cColor[3] = 0.1f; + + //draw the background (black) + CG_DrawRect(x, y, CLFUELBAR_W, CLFUELBAR_H, 1.0f, colorTable[CT_BLACK]); + + //now draw the part to show how much fuel there is in the color specified + CG_FillRect(x+1.0f, y+1.0f+(CLFUELBAR_H-percent), CLFUELBAR_W-1.0f, CLFUELBAR_H-1.0f-(CLFUELBAR_H-percent), aColor); + + //then draw the other part greyed out + CG_FillRect(x+1.0f, y+1.0f, CLFUELBAR_W-1.0f, CLFUELBAR_H-percent, cColor); +} + +int cgRageTime = 0; +int cgRageFadeTime = 0; +float cgRageFadeVal = 0; + +int cgRageRecTime = 0; +int cgRageRecFadeTime = 0; +float cgRageRecFadeVal = 0; + +int cgAbsorbTime = 0; +int cgAbsorbFadeTime = 0; +float cgAbsorbFadeVal = 0; + +int cgProtectTime = 0; +int cgProtectFadeTime = 0; +float cgProtectFadeVal = 0; + +int cgYsalTime = 0; +int cgYsalFadeTime = 0; +float cgYsalFadeVal = 0; + +qboolean gCGHasFallVector = qfalse; +vec3_t gCGFallVector; + +/* +================= +CG_Draw2D +================= +*/ +extern int cgSiegeRoundState; +extern int cgSiegeRoundTime; + +extern int team1Timed; +extern int team2Timed; + +int cg_beatingSiegeTime = 0; + +int cgSiegeRoundBeganTime = 0; +int cgSiegeRoundCountTime = 0; + +static void CG_DrawSiegeTimer(int timeRemaining, qboolean isMyTeam) +{ //rwwFIXMEFIXME: Make someone make assets and use them. + //this function is pretty much totally placeholder. +// int x = 0; +// int y = SCREEN_HEIGHT-160; + int fColor = 0; + int minutes = 0; + int seconds = 0; + char timeStr[1024]; + menuDef_t *menuHUD = NULL; + itemDef_t *item = NULL; + + menuHUD = Menus_FindByName("mp_timer"); + if (!menuHUD) + { + return; + } + + item = Menu_FindItemByName(menuHUD, "frame"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + seconds = timeRemaining; + + while (seconds >= 60) + { + minutes++; + seconds -= 60; + } + + strcpy(timeStr, va( "%i:%02i", minutes, seconds )); + + if (isMyTeam) + { + fColor = CT_HUD_RED; + } + else + { + fColor = CT_HUD_GREEN; + } + +// trap_Cvar_Set("ui_siegeTimer", timeStr); + +// UI_DrawProportionalString( x+16, y+40, timeStr, +// UI_SMALLFONT|UI_DROPSHADOW, colorTable[fColor] ); + + item = Menu_FindItemByName(menuHUD, "timer"); + if (item) + { + UI_DrawProportionalString( + item->window.rect.x, + item->window.rect.y, + timeStr, + UI_SMALLFONT|UI_DROPSHADOW, + colorTable[fColor] ); + } + +} + +static void CG_DrawSiegeDeathTimer( int timeRemaining ) +{ + int minutes = 0; + int seconds = 0; + char timeStr[1024]; + menuDef_t *menuHUD = NULL; + itemDef_t *item = NULL; + + menuHUD = Menus_FindByName("mp_timer"); + if (!menuHUD) + { + return; + } + + item = Menu_FindItemByName(menuHUD, "frame"); + + if (item) + { + trap_R_SetColor( item->window.foreColor ); + CG_DrawPic( + item->window.rect.x, + item->window.rect.y, + item->window.rect.w, + item->window.rect.h, + item->window.background ); + } + + seconds = timeRemaining; + + while (seconds >= 60) + { + minutes++; + seconds -= 60; + } + + if (seconds < 10) + { + strcpy(timeStr, va( "%i:0%i", minutes, seconds )); + } + else + { + strcpy(timeStr, va( "%i:%i", minutes, seconds )); + } + + item = Menu_FindItemByName(menuHUD, "deathtimer"); + if (item) + { + UI_DrawProportionalString( + item->window.rect.x, + item->window.rect.y, + timeStr, + UI_SMALLFONT|UI_DROPSHADOW, + item->window.foreColor ); + } + +} + +int cgSiegeEntityRender = 0; + +static void CG_DrawSiegeHUDItem(void) +{ + void *g2; + qhandle_t handle; + vec3_t origin, angles; + vec3_t mins, maxs; + float len; + centity_t *cent = &cg_entities[cgSiegeEntityRender]; + + if (cent->ghoul2) + { + g2 = cent->ghoul2; + handle = 0; + } + else + { + handle = cgs.gameModels[cent->currentState.modelindex]; + g2 = NULL; + } + + if (handle) + { + trap_R_ModelBounds( handle, mins, maxs ); + } + else + { + VectorSet(mins, -16, -16, -20); + VectorSet(maxs, 16, 16, 32); + } + + origin[2] = -0.5 * ( mins[2] + maxs[2] ); + origin[1] = 0.5 * ( mins[1] + maxs[1] ); + len = 0.5 * ( maxs[2] - mins[2] ); + origin[0] = len / 0.268; + + VectorClear(angles); + angles[YAW] = cg.autoAngles[YAW]; + + CG_Draw3DModel( 8, 8, 64, 64, handle, g2, cent->currentState.g2radius, 0, origin, angles ); + + cgSiegeEntityRender = 0; //reset for next frame +} + +/*==================================== +chatbox functionality -rww +====================================*/ +#define CHATBOX_CUTOFF_LEN 550 +#define CHATBOX_FONT_HEIGHT 20 + +//utility func, insert a string into a string at the specified +//place (assuming this will not overflow the buffer) +void CG_ChatBox_StrInsert(char *buffer, int place, char *str) +{ + int insLen = strlen(str); + int i = strlen(buffer); + int k = 0; + + buffer[i+insLen+1] = 0; //terminate the string at its new length + while (i >= place) + { + buffer[i+insLen] = buffer[i]; + i--; + } + + i++; + while (k < insLen) + { + buffer[i] = str[k]; + i++; + k++; + } +} + +//add chatbox string +void CG_ChatBox_AddString(char *chatStr) +{ + chatBoxItem_t *chat = &cg.chatItems[cg.chatItemActive]; + float chatLen; + + if (cg_chatBox.integer<=0) + { //don't bother then. + return; + } + + memset(chat, 0, sizeof(chatBoxItem_t)); + + if (strlen(chatStr) > sizeof(chat->string)) + { //too long, terminate at proper len. + chatStr[sizeof(chat->string)-1] = 0; + } + + strcpy(chat->string, chatStr); + chat->time = cg.time + cg_chatBox.integer; + + chat->lines = 1; + + chatLen = CG_Text_Width(chat->string, 1.0f, FONT_SMALL); + if (chatLen > CHATBOX_CUTOFF_LEN) + { //we have to break it into segments... + int i = 0; + int lastLinePt = 0; + char s[2]; + + chatLen = 0; + while (chat->string[i]) + { + s[0] = chat->string[i]; + s[1] = 0; + chatLen += CG_Text_Width(s, 0.65f, FONT_SMALL); + + if (chatLen >= CHATBOX_CUTOFF_LEN) + { + int j = i; + while (j > 0 && j > lastLinePt) + { + if (chat->string[j] == ' ') + { + break; + } + j--; + } + if (chat->string[j] == ' ') + { + i = j; + } + + chat->lines++; + CG_ChatBox_StrInsert(chat->string, i, "\n"); + i++; + chatLen = 0; + lastLinePt = i+1; + } + i++; + } + } + + cg.chatItemActive++; + if (cg.chatItemActive >= MAX_CHATBOX_ITEMS) + { + cg.chatItemActive = 0; + } +} + +//insert item into array (rearranging the array if necessary) +void CG_ChatBox_ArrayInsert(chatBoxItem_t **array, int insPoint, int maxNum, chatBoxItem_t *item) +{ + if (array[insPoint]) + { //recursively call, to move everything up to the top + if (insPoint+1 >= maxNum) + { + CG_Error("CG_ChatBox_ArrayInsert: Exceeded array size"); + } + CG_ChatBox_ArrayInsert(array, insPoint+1, maxNum, array[insPoint]); + } + + //now that we have moved anything that would be in this slot up, insert what we want into the slot + array[insPoint] = item; +} + +//go through all the chat strings and draw them if they are not yet expired +static CGAME_INLINE void CG_ChatBox_DrawStrings(void) +{ + chatBoxItem_t *drawThese[MAX_CHATBOX_ITEMS]; + int numToDraw = 0; + int linesToDraw = 0; + int i = 0; + int x = 30; + int y = cg.scoreBoardShowing ? 475 : cg_chatBoxHeight.integer; + float fontScale = 0.65f; + + if (!cg_chatBox.integer) + { + return; + } + + memset(drawThese, 0, sizeof(drawThese)); + + while (i < MAX_CHATBOX_ITEMS) + { + if (cg.chatItems[i].time >= cg.time) + { + int check = numToDraw; + int insertionPoint = numToDraw; + + while (check >= 0) + { + if (drawThese[check] && + cg.chatItems[i].time < drawThese[check]->time) + { //insert here + insertionPoint = check; + } + check--; + } + CG_ChatBox_ArrayInsert(drawThese, insertionPoint, MAX_CHATBOX_ITEMS, &cg.chatItems[i]); + numToDraw++; + linesToDraw += cg.chatItems[i].lines; + } + i++; + } + + if (!numToDraw) + { //nothing, then, just get out of here now. + return; + } + + //move initial point up so we draw bottom-up (visually) + y -= (CHATBOX_FONT_HEIGHT*fontScale)*linesToDraw; + + //we have the items we want to draw, just quickly loop through them now + i = 0; + while (i < numToDraw) + { + CG_Text_Paint(x, y, fontScale, colorWhite, drawThese[i]->string, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + y += ((CHATBOX_FONT_HEIGHT*fontScale)*drawThese[i]->lines); + i++; + } +} + +static void CG_Draw2DScreenTints( void ) +{ + float rageTime, rageRecTime, absorbTime, protectTime, ysalTime; + vec4_t hcolor; + if (cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR) + { + if (cg.snap->ps.fd.forcePowersActive & (1 << FP_RAGE)) + { + if (!cgRageTime) + { + cgRageTime = cg.time; + } + + rageTime = (float)(cg.time - cgRageTime); + + rageTime /= 9000; + + if (rageTime < 0) + { + rageTime = 0; + } + if (rageTime > 0.15) + { + rageTime = 0.15; + } + + hcolor[3] = rageTime; + hcolor[0] = 0.7; + hcolor[1] = 0; + hcolor[2] = 0; + + if (!cg.renderingThirdPerson) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + + cgRageFadeTime = 0; + cgRageFadeVal = 0; + } + else if (cgRageTime) + { + if (!cgRageFadeTime) + { + cgRageFadeTime = cg.time; + cgRageFadeVal = 0.15; + } + + rageTime = cgRageFadeVal; + + cgRageFadeVal -= (cg.time - cgRageFadeTime)*0.000005; + + if (rageTime < 0) + { + rageTime = 0; + } + if (rageTime > 0.15) + { + rageTime = 0.15; + } + + if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time) + { + float checkRageRecTime = rageTime; + + if (checkRageRecTime < 0.15) + { + checkRageRecTime = 0.15; + } + + hcolor[3] = checkRageRecTime; + hcolor[0] = rageTime*4; + if (hcolor[0] < 0.2) + { + hcolor[0] = 0.2; + } + hcolor[1] = 0.2; + hcolor[2] = 0.2; + } + else + { + hcolor[3] = rageTime; + hcolor[0] = 0.7; + hcolor[1] = 0; + hcolor[2] = 0; + } + + if (!cg.renderingThirdPerson && rageTime) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + else + { + if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time) + { + hcolor[3] = 0.15; + hcolor[0] = 0.2; + hcolor[1] = 0.2; + hcolor[2] = 0.2; + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + cgRageTime = 0; + } + } + else if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time) + { + if (!cgRageRecTime) + { + cgRageRecTime = cg.time; + } + + rageRecTime = (float)(cg.time - cgRageRecTime); + + rageRecTime /= 9000; + + if (rageRecTime < 0.15)//0) + { + rageRecTime = 0.15;//0; + } + if (rageRecTime > 0.15) + { + rageRecTime = 0.15; + } + + hcolor[3] = rageRecTime; + hcolor[0] = 0.2; + hcolor[1] = 0.2; + hcolor[2] = 0.2; + + if (!cg.renderingThirdPerson) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + + cgRageRecFadeTime = 0; + cgRageRecFadeVal = 0; + } + else if (cgRageRecTime) + { + if (!cgRageRecFadeTime) + { + cgRageRecFadeTime = cg.time; + cgRageRecFadeVal = 0.15; + } + + rageRecTime = cgRageRecFadeVal; + + cgRageRecFadeVal -= (cg.time - cgRageRecFadeTime)*0.000005; + + if (rageRecTime < 0) + { + rageRecTime = 0; + } + if (rageRecTime > 0.15) + { + rageRecTime = 0.15; + } + + hcolor[3] = rageRecTime; + hcolor[0] = 0.2; + hcolor[1] = 0.2; + hcolor[2] = 0.2; + + if (!cg.renderingThirdPerson && rageRecTime) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + else + { + cgRageRecTime = 0; + } + } + + if (cg.snap->ps.fd.forcePowersActive & (1 << FP_ABSORB)) + { + if (!cgAbsorbTime) + { + cgAbsorbTime = cg.time; + } + + absorbTime = (float)(cg.time - cgAbsorbTime); + + absorbTime /= 9000; + + if (absorbTime < 0) + { + absorbTime = 0; + } + if (absorbTime > 0.15) + { + absorbTime = 0.15; + } + + hcolor[3] = absorbTime/2; + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7; + + if (!cg.renderingThirdPerson) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + + cgAbsorbFadeTime = 0; + cgAbsorbFadeVal = 0; + } + else if (cgAbsorbTime) + { + if (!cgAbsorbFadeTime) + { + cgAbsorbFadeTime = cg.time; + cgAbsorbFadeVal = 0.15; + } + + absorbTime = cgAbsorbFadeVal; + + cgAbsorbFadeVal -= (cg.time - cgAbsorbFadeTime)*0.000005; + + if (absorbTime < 0) + { + absorbTime = 0; + } + if (absorbTime > 0.15) + { + absorbTime = 0.15; + } + + hcolor[3] = absorbTime/2; + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0.7; + + if (!cg.renderingThirdPerson && absorbTime) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + else + { + cgAbsorbTime = 0; + } + } + + if (cg.snap->ps.fd.forcePowersActive & (1 << FP_PROTECT)) + { + if (!cgProtectTime) + { + cgProtectTime = cg.time; + } + + protectTime = (float)(cg.time - cgProtectTime); + + protectTime /= 9000; + + if (protectTime < 0) + { + protectTime = 0; + } + if (protectTime > 0.15) + { + protectTime = 0.15; + } + + hcolor[3] = protectTime/2; + hcolor[0] = 0; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg.renderingThirdPerson) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + + cgProtectFadeTime = 0; + cgProtectFadeVal = 0; + } + else if (cgProtectTime) + { + if (!cgProtectFadeTime) + { + cgProtectFadeTime = cg.time; + cgProtectFadeVal = 0.15; + } + + protectTime = cgProtectFadeVal; + + cgProtectFadeVal -= (cg.time - cgProtectFadeTime)*0.000005; + + if (protectTime < 0) + { + protectTime = 0; + } + if (protectTime > 0.15) + { + protectTime = 0.15; + } + + hcolor[3] = protectTime/2; + hcolor[0] = 0; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg.renderingThirdPerson && protectTime) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + else + { + cgProtectTime = 0; + } + } + + if (cg.snap->ps.rocketLockIndex != ENTITYNUM_NONE && (cg.time - cg.snap->ps.rocketLockTime) > 0) + { + CG_DrawRocketLocking( cg.snap->ps.rocketLockIndex, cg.snap->ps.rocketLockTime ); + } + + if (BG_HasYsalamiri(cgs.gametype, &cg.snap->ps)) + { + if (!cgYsalTime) + { + cgYsalTime = cg.time; + } + + ysalTime = (float)(cg.time - cgYsalTime); + + ysalTime /= 9000; + + if (ysalTime < 0) + { + ysalTime = 0; + } + if (ysalTime > 0.15) + { + ysalTime = 0.15; + } + + hcolor[3] = ysalTime/2; + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg.renderingThirdPerson) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + + cgYsalFadeTime = 0; + cgYsalFadeVal = 0; + } + else if (cgYsalTime) + { + if (!cgYsalFadeTime) + { + cgYsalFadeTime = cg.time; + cgYsalFadeVal = 0.15; + } + + ysalTime = cgYsalFadeVal; + + cgYsalFadeVal -= (cg.time - cgYsalFadeTime)*0.000005; + + if (ysalTime < 0) + { + ysalTime = 0; + } + if (ysalTime > 0.15) + { + ysalTime = 0.15; + } + + hcolor[3] = ysalTime/2; + hcolor[0] = 0.7; + hcolor[1] = 0.7; + hcolor[2] = 0; + + if (!cg.renderingThirdPerson && ysalTime) + { + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + } + else + { + cgYsalTime = 0; + } + } + } + + if ( (cg.refdef.viewContents&CONTENTS_LAVA) ) + {//tint screen red + float phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + hcolor[3] = 0.5 + (0.15f*sin( phase )); + hcolor[0] = 0.7f; + hcolor[1] = 0; + hcolor[2] = 0; + + CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + } + else if ( (cg.refdef.viewContents&CONTENTS_SLIME) ) + {//tint screen green + float phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + hcolor[3] = 0.4 + (0.1f*sin( phase )); + hcolor[0] = 0; + hcolor[1] = 0.7f; + hcolor[2] = 0; + + CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + } + else if ( (cg.refdef.viewContents&CONTENTS_WATER) ) + {//tint screen light blue -- FIXME: don't do this if CONTENTS_FOG? (in case someone *does* make a water shader with fog in it?) + float phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2; + hcolor[3] = 0.3 + (0.05f*sin( phase )); + hcolor[0] = 0; + hcolor[1] = 0.2f; + hcolor[2] = 0.8; + + CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor ); + } +} + +static void CG_Draw2D( void ) { + float inTime = cg.invenSelectTime+WEAPON_SELECT_TIME; + float wpTime = cg.weaponSelectTime+WEAPON_SELECT_TIME; + float fallTime; + float bestTime; + int drawSelect = 0; + + // if we are taking a levelshot for the menu, don't draw anything + if ( cg.levelShot ) { + return; + } + + if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR) + { + cgRageTime = 0; + cgRageFadeTime = 0; + cgRageFadeVal = 0; + + cgRageRecTime = 0; + cgRageRecFadeTime = 0; + cgRageRecFadeVal = 0; + + cgAbsorbTime = 0; + cgAbsorbFadeTime = 0; + cgAbsorbFadeVal = 0; + + cgProtectTime = 0; + cgProtectFadeTime = 0; + cgProtectFadeVal = 0; + + cgYsalTime = 0; + cgYsalFadeTime = 0; + cgYsalFadeVal = 0; + } + + if ( cg_draw2D.integer == 0 ) { + return; + } + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + CG_DrawIntermission(); + CG_ChatBox_DrawStrings(); + return; + } + + CG_Draw2DScreenTints(); + + if (cg.snap->ps.rocketLockIndex != ENTITYNUM_NONE && (cg.time - cg.snap->ps.rocketLockTime) > 0) + { + CG_DrawRocketLocking( cg.snap->ps.rocketLockIndex, cg.snap->ps.rocketLockTime ); + } + + if (cg.snap->ps.holocronBits) + { + CG_DrawHolocronIcons(); + } + if (cg.snap->ps.fd.forcePowersActive || cg.snap->ps.fd.forceRageRecoveryTime > cg.time) + { + CG_DrawActivePowers(); + } + + if (cg.snap->ps.jetpackFuel < 100) + { //draw it as long as it isn't full + CG_DrawJetpackFuel(); + } + if (cg.snap->ps.cloakFuel < 100) + { //draw it as long as it isn't full + CG_DrawCloakFuel(); + } + if (cg.predictedPlayerState.emplacedIndex > 0) + { + centity_t *eweb = &cg_entities[cg.predictedPlayerState.emplacedIndex]; + + if (eweb->currentState.weapon == WP_NONE) + { //using an e-web, draw its health + CG_DrawEWebHealth(); + } + } + + // Draw this before the text so that any text won't get clipped off + CG_DrawZoomMask(); + +/* + if (cg.cameraMode) { + return; + } +*/ + if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { + CG_DrawSpectator(); + CG_DrawCrosshair(NULL, 0); + CG_DrawCrosshairNames(); + CG_SaberClashFlare(); + } 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 ) { + + if ( /*cg_drawStatus.integer*/0 ) { + //Reenable if stats are drawn with menu system again + Menu_PaintAll(); + CG_DrawTimedMenus(); + } + + //CG_DrawTemporaryStats(); + + CG_DrawAmmoWarning(); + + CG_DrawCrosshairNames(); + + if (cg_drawStatus.integer) + { + CG_DrawIconBackground(); + } + + if (inTime > wpTime) + { + drawSelect = 1; + bestTime = cg.invenSelectTime; + } + else //only draw the most recent since they're drawn in the same place + { + drawSelect = 2; + bestTime = cg.weaponSelectTime; + } + + if (cg.forceSelectTime > bestTime) + { + drawSelect = 3; + } + + switch(drawSelect) + { + case 1: + CG_DrawInvenSelect(); + break; + case 2: + CG_DrawWeaponSelect(); + break; + case 3: + CG_DrawForceSelect(); + break; + default: + break; + } + + if (cg_drawStatus.integer) + { + //Powerups now done with upperright stuff + //CG_DrawPowerupIcons(); + + CG_DrawFlagStatus(); + } + + CG_SaberClashFlare(); + + if (cg_drawStatus.integer) + { + CG_DrawStats(); + } + + CG_DrawPickupItem(); + //Do we want to use this system again at some point? + //CG_DrawReward(); + } + + } + + if (cg.snap->ps.fallingToDeath) + { + vec4_t hcolor; + + fallTime = (float)(cg.time - cg.snap->ps.fallingToDeath); + + fallTime /= (FALL_FADE_TIME/2); + + if (fallTime < 0) + { + fallTime = 0; + } + if (fallTime > 1) + { + fallTime = 1; + } + + hcolor[3] = fallTime; + hcolor[0] = 0; + hcolor[1] = 0; + hcolor[2] = 0; + + CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); + + if (!gCGHasFallVector) + { + VectorCopy(cg.snap->ps.origin, gCGFallVector); + gCGHasFallVector = qtrue; + } + } + else + { + if (gCGHasFallVector) + { + gCGHasFallVector = qfalse; + VectorClear(gCGFallVector); + } + } + + CG_DrawVote(); + CG_DrawTeamVote(); + + CG_DrawLagometer(); + + + if (!cg_paused.integer) { + CG_DrawBracketedEntities(); + CG_DrawUpperRight(); + } + + if ( !CG_DrawFollow() ) { + CG_DrawWarmup(); + } + + if (cgSiegeRoundState) + { + char pStr[1024]; + int rTime = 0; + + //cgSiegeRoundBeganTime = 0; + + switch (cgSiegeRoundState) + { + case 1: + CG_CenterPrint(CG_GetStringEdString("MP_INGAME", "WAITING_FOR_PLAYERS"), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH); + break; + case 2: + rTime = (SIEGE_ROUND_BEGIN_TIME - (cg.time - cgSiegeRoundTime)); + + if (rTime < 0) + { + rTime = 0; + } + if (rTime > SIEGE_ROUND_BEGIN_TIME) + { + rTime = SIEGE_ROUND_BEGIN_TIME; + } + + rTime /= 1000; + + rTime += 1; + + if (rTime < 1) + { + rTime = 1; + } + + if (rTime <= 3 && rTime != cgSiegeRoundCountTime) + { + cgSiegeRoundCountTime = rTime; + + switch (rTime) + { + case 1: + trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); + break; + case 2: + trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); + break; + case 3: + trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); + break; + default: + break; + } + } + + strcpy(pStr, va("%s %i...", CG_GetStringEdString("MP_INGAME", "ROUNDBEGINSIN"), rTime)); + CG_CenterPrint(pStr, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH); + //same + break; + default: + break; + } + + cgSiegeEntityRender = 0; + } + else if (cgSiegeRoundTime) + { + CG_CenterPrint("", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH); + cgSiegeRoundTime = 0; + + //cgSiegeRoundBeganTime = cg.time; + cgSiegeEntityRender = 0; + } + else if (cgSiegeRoundBeganTime) + { //Draw how much time is left in the round based on local info. + int timedTeam = TEAM_FREE; + int timedValue = 0; + + if (cgSiegeEntityRender) + { //render the objective item model since this client has it + CG_DrawSiegeHUDItem(); + } + + if (team1Timed) + { + timedTeam = TEAM_RED; //team 1 + if (cg_beatingSiegeTime) + { + timedValue = cg_beatingSiegeTime; + } + else + { + timedValue = team1Timed; + } + } + else if (team2Timed) + { + timedTeam = TEAM_BLUE; //team 2 + if (cg_beatingSiegeTime) + { + timedValue = cg_beatingSiegeTime; + } + else + { + timedValue = team2Timed; + } + } + + if (timedTeam != TEAM_FREE) + { //one of the teams has a timer + int timeRemaining; + qboolean isMyTeam = qfalse; + + if (cgs.siegeTeamSwitch && !cg_beatingSiegeTime) + { //in switchy mode but not beating a time, so count up. + timeRemaining = (cg.time-cgSiegeRoundBeganTime); + if (timeRemaining < 0) + { + timeRemaining = 0; + } + } + else + { + timeRemaining = (((cgSiegeRoundBeganTime)+timedValue) - cg.time); + } + + if (timeRemaining > timedValue) + { + timeRemaining = timedValue; + } + else if (timeRemaining < 0) + { + timeRemaining = 0; + } + + if (timeRemaining) + { + timeRemaining /= 1000; + } + + if (cg.predictedPlayerState.persistant[PERS_TEAM] == timedTeam) + { //the team that's timed is the one this client is on + isMyTeam = qtrue; + } + + CG_DrawSiegeTimer(timeRemaining, isMyTeam); + } + } + else + { + cgSiegeEntityRender = 0; + } + + if ( cg_siegeDeathTime ) + { + int timeRemaining = ( cg_siegeDeathTime - cg.time ); + + if ( timeRemaining < 0 ) + { + timeRemaining = 0; + cg_siegeDeathTime = 0; + } + + if ( timeRemaining ) + { + timeRemaining /= 1000; + } + + CG_DrawSiegeDeathTimer( timeRemaining ); + } + + // don't draw center string if scoreboard is up + cg.scoreBoardShowing = CG_DrawScoreboard(); + if ( !cg.scoreBoardShowing) { + CG_DrawCenterString(); + } + + // always draw chat + CG_ChatBox_DrawStrings(); +} + + +static void CG_DrawTourneyScoreboard() { +} + +/* +===================== +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 ); + } + + cg.refdef.rdflags |= RDF_DRAWSKYBOX; + + // 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(); +} + + + diff --git a/code/cgame/cg_drawtools.c b/code/cgame/cg_drawtools.c new file mode 100644 index 0000000..cafe5a4 --- /dev/null +++ b/code/cgame/cg_drawtools.c @@ -0,0 +1,690 @@ +/* +// this line must stay at top so the whole PCH thing works... +#include "cg_headers.h" + +//#include "cg_local.h" +#include "cg_media.h" +#include "cg_text.h" +*/ + +// 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" +#include "../game/q_shared.h" + + +/* +================ +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_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_DrawSides + +Coords are virtual 640x480 +================ +*/ +void CG_DrawSides(float x, float y, float w, float h, float size) { + 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) { + 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 ); +} + +/* +------------------------- +CGC_FillRect2 +real coords +------------------------- +*/ +void CG_FillRect2( float x, float y, float width, float height, const float *color ) { + trap_R_SetColor( color ); + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader); + trap_R_SetColor( NULL ); +} + +/* +================ +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 ); + + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cgs.media.whiteShader); + + trap_R_SetColor( NULL ); +} + + +/* +================ +CG_DrawPic + +Coordinates are 640*480 virtual values +A width of 0 will draw with the original image width +================= +*/ +void CG_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + trap_R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + +/* +================ +CG_DrawRotatePic + +Coordinates are 640*480 virtual values +A width of 0 will draw with the original image width +rotates around the upper right corner of the passed in point +================= +*/ +void CG_DrawRotatePic( float x, float y, float width, float height,float angle, qhandle_t hShader ) { + trap_R_DrawRotatePic( x, y, width, height, 0, 0, 1, 1, angle, hShader ); +} + +/* +================ +CG_DrawRotatePic2 + +Coordinates are 640*480 virtual values +A width of 0 will draw with the original image width +Actually rotates around the center point of the passed in coordinates +================= +*/ +void CG_DrawRotatePic2( float x, float y, float width, float height,float angle, qhandle_t hShader ) { + trap_R_DrawRotatePic2( x, y, width, height, 0, 0, 1, 1, angle, 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; + float size2; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + ax = x; + ay = y; + aw = width; + ah = height; + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.03125; + size2 = 0.0625; + + trap_R_DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol + size, frow + size2, + 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 +================== +*/ +#include "../../ui/menudef.h" // for "ITEM_TEXTSTYLE_SHADOWED" +void CG_DrawStringExt( int x, int y, const char *string, const float *setColor, + qboolean forceColor, qboolean shadow, int charWidth, int charHeight, int maxChars ) +{ + if (trap_Language_IsAsian()) + { + // hack-a-doodle-do (post-release quick fix code)... + // + vec4_t color; + memcpy(color,setColor, sizeof(color)); // de-const it + CG_Text_Paint(x, y, 1.0f, // float scale, + color, // vec4_t color, + string, // const char *text, + 0.0f, // float adjust, + 0, // int limit, + shadow ? ITEM_TEXTSTYLE_SHADOWED : 0, // int style, + FONT_MEDIUM // iMenuFont + ) ; + } + else + { + vec4_t color; + const char *s; + int xx; + + // 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; + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + continue; + } + CG_DrawChar( xx + 2, y + 2, charWidth, charHeight, *s ); + xx += charWidth; + s++; + } + } + + // draw the colored text + s = string; + xx = x; + trap_R_SetColor( setColor ); + while ( *s ) { + 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; + 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_ColorForHealth +================= +*/ +void CG_ColorForGivenHealth( vec4_t hcolor, int health ) +{ + // set the color based on health + hcolor[0] = 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 ) +{ + int health; + int count; + int max; + + // calculate the total points of damage that can + // be sustained at the current health / armor level + health = cg.snap->ps.stats[STAT_HEALTH]; + + if ( health <= 0 ) + { + VectorClear( hcolor ); // black + hcolor[3] = 1; + return; + } + + count = cg.snap->ps.stats[STAT_ARMOR]; + max = health * ARMOR_PROTECTION / ( 1.0 - ARMOR_PROTECTION ); + if ( max < count ) + { + count = max; + } + health += count; + + hcolor[3] = 1.0; + CG_ColorForGivenHealth( hcolor, health ); +} + +/* +============== +CG_DrawNumField + +Take x,y positions as if 640 x 480 and scales them to the proper resolution + +============== +*/ +void CG_DrawNumField (int x, int y, int width, int value,int charWidth,int charHeight,int style,qboolean zeroFill) +{ + char num[16], *ptr; + int l; + int frame; + int xWidth; + int i = 0; + + 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; + + // FIXME: Might need to do something different for the chunky font?? + switch(style) + { + case NUM_FONT_SMALL: + xWidth = charWidth; + break; + case NUM_FONT_CHUNKY: + xWidth = (charWidth/1.2f) + 2; + break; + default: + case NUM_FONT_BIG: + xWidth = (charWidth/2) + 7;//(charWidth/6); + break; + } + + if ( zeroFill ) + { + for (i = 0; i < (width - l); i++ ) + { + switch(style) + { + case NUM_FONT_SMALL: + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.smallnumberShaders[0] ); + break; + case NUM_FONT_CHUNKY: + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.chunkyNumberShaders[0] ); + break; + default: + case NUM_FONT_BIG: + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[0] ); + break; + } + x += 2 + (xWidth); + } + } + else + { + x += 2 + (xWidth)*(width - l); + } + + ptr = num; + while (*ptr && l) + { + if (*ptr == '-') + frame = STAT_MINUS; + else + frame = *ptr -'0'; + + switch(style) + { + case NUM_FONT_SMALL: + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.smallnumberShaders[frame] ); + x++; // For a one line gap + break; + case NUM_FONT_CHUNKY: + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.chunkyNumberShaders[frame] ); + break; + default: + case NUM_FONT_BIG: + CG_DrawPic( x,y, charWidth, charHeight, cgs.media.numberShaders[frame] ); + break; + } + + x += (xWidth); + ptr++; + l--; + } + +} + +#include "../ui/ui_shared.h" // for some text style junk +void UI_DrawProportionalString( int x, int y, const char* str, int style, vec4_t color ) +{ + // having all these different style defines (1 for UI, one for CG, and now one for the re->font stuff) + // is dumb, but for now... + // + int iStyle = 0; + int iMenuFont = (style & UI_SMALLFONT) ? FONT_SMALL : FONT_MEDIUM; + + switch (style & (UI_LEFT|UI_CENTER|UI_RIGHT)) + { + default: + case UI_LEFT: + { + // nada... + } + break; + + case UI_CENTER: + { + x -= CG_Text_Width(str, 1.0, iMenuFont) / 2; + } + break; + + case UI_RIGHT: + { + x -= CG_Text_Width(str, 1.0, iMenuFont) / 2; + } + break; + } + + if (style & UI_DROPSHADOW) + { + iStyle = ITEM_TEXTSTYLE_SHADOWED; + } + else + if ( style & (UI_BLINK|UI_PULSE) ) + { + iStyle = ITEM_TEXTSTYLE_BLINK; + } + + CG_Text_Paint(x, y, 1.0, color, str, 0, 0, iStyle, iMenuFont); +} + +void UI_DrawScaledProportionalString( int x, int y, const char* str, int style, vec4_t color, float scale) +{ + // having all these different style defines (1 for UI, one for CG, and now one for the re->font stuff) + // is dumb, but for now... + // + int iStyle = 0; + + switch (style & (UI_LEFT|UI_CENTER|UI_RIGHT)) + { + default: + case UI_LEFT: + { + // nada... + } + break; + + case UI_CENTER: + { + x -= CG_Text_Width(str, scale, FONT_MEDIUM) / 2; + } + break; + + case UI_RIGHT: + { + x -= CG_Text_Width(str, scale, FONT_MEDIUM) / 2; + } + break; + } + + if (style & UI_DROPSHADOW) + { + iStyle = ITEM_TEXTSTYLE_SHADOWED; + } + else + if ( style & (UI_BLINK|UI_PULSE) ) + { + iStyle = ITEM_TEXTSTYLE_BLINK; + } + + CG_Text_Paint(x, y, scale, color, str, 0, 0, iStyle, FONT_MEDIUM); +} + + + + diff --git a/code/cgame/cg_effects.c b/code/cgame/cg_effects.c new file mode 100644 index 0000000..e7afcdf --- /dev/null +++ b/code/cgame/cg_effects.c @@ -0,0 +1,1546 @@ +// 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 = 0;//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; + + 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; +} + +int CGDEBUG_SaberColor( int saberColor ) +{ + switch( (int)(saberColor) ) + { + case SABER_RED: + return 0x000000ff; + break; + case SABER_ORANGE: + return 0x000088ff; + break; + case SABER_YELLOW: + return 0x0000ffff; + break; + case SABER_GREEN: + return 0x0000ff00; + break; + case SABER_BLUE: + return 0x00ff0000; + break; + case SABER_PURPLE: + return 0x00ff00ff; + break; + default: + return saberColor; + break; + } +} + +void CG_TestLine( vec3_t start, vec3_t end, int time, unsigned int color, int radius) { + localEntity_t *le; + refEntity_t *re; + + le = CG_AllocLocalEntity(); + le->leType = LE_LINE; + le->startTime = cg.time; + le->endTime = cg.time + time; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + re = &le->refEntity; + VectorCopy( start, re->origin ); + VectorCopy( end, re->oldorigin); + re->shaderTime = cg.time / 1000.0f; + + re->reType = RT_LINE; + re->radius = 0.5*radius; + re->customShader = cgs.media.whiteShader; //trap_R_RegisterShaderNoMip("textures/colombia/canvas_doublesided"); + + re->shaderTexCoord[0] = re->shaderTexCoord[1] = 1.0f; + + if (color==0) + { + re->shaderRGBA[0] = re->shaderRGBA[1] = re->shaderRGBA[2] = re->shaderRGBA[3] = 0xff; + } + else + { + color = CGDEBUG_SaberColor( color ); + re->shaderRGBA[0] = color & 0xff; + color >>= 8; + re->shaderRGBA[1] = color & 0xff; + color >>= 8; + re->shaderRGBA[2] = color & 0xff; +// color >>= 8; +// re->shaderRGBA[3] = color & 0xff; + re->shaderRGBA[3] = 0xff; + } + + le->color[3] = 1.0; + + //re->renderfx |= RF_DEPTHHACK; +} + +/* +================== +CG_ThrowChunk +================== +*/ +void CG_ThrowChunk( vec3_t origin, vec3_t velocity, qhandle_t hModel, int optionalSound, int startalpha ) { + 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; + le->angles.trType = TR_GRAVITY; + VectorCopy( origin, le->pos.trBase ); + VectorCopy( velocity, le->pos.trDelta ); + VectorSet(le->angles.trBase, 20, 20, 20); + VectorCopy( velocity, le->angles.trDelta ); + le->pos.trTime = cg.time; + le->angles.trTime = cg.time; + + le->leFlags = LEF_TUMBLE; + + le->angles.trBase[YAW] = 180; + + le->bounceFactor = 0.3f; + le->bounceSound = optionalSound; + + le->forceAlpha = startalpha; +} + +//---------------------------- +// +// Breaking Glass Technology +// +//---------------------------- + +// Since we have shared verts when we tesselate the glass sheet, it helps to have a +// random offset table set up up front. + +static float offX[20][20], + offZ[20][20]; + +#define FX_ALPHA_NONLINEAR 0x00000004 +#define FX_APPLY_PHYSICS 0x02000000 +#define FX_USE_ALPHA 0x08000000 + +static void CG_DoGlassQuad( vec3_t p[4], vec2_t uv[4], qboolean stick, int time, vec3_t dmgDir ) +{ + float bounce; + vec3_t rotDelta; + vec3_t vel, accel; + vec3_t rgb1; + addpolyArgStruct_t apArgs; + int i, i_2; + + VectorSet( vel, crandom() * 12, crandom() * 12, -1 ); + + if ( !stick ) + { + // We aren't a motion delayed chunk, so let us move quickly + VectorMA( vel, 0.3f, dmgDir, vel ); + } + + // Set up acceleration due to gravity, 800 is standard QuakeIII gravity, so let's use something close + VectorSet( accel, 0.0f, 0.0f, -(600.0f + random() * 100.0f ) ); + + // We are using an additive shader, so let's set the RGB low so we look more like transparent glass +// VectorSet( rgb1, 0.1f, 0.1f, 0.1f ); + VectorSet( rgb1, 1.0f, 1.0f, 1.0f ); + + // Being glass, we don't want to bounce much + bounce = random() * 0.2f + 0.15f; + + // Set up our random rotate, we only do PITCH and YAW, not ROLL. This is something like degrees per second + VectorSet( rotDelta, crandom() * 40.0f, crandom() * 40.0f, 0.0f ); + + //In an ideal world, this might actually work. + /* + CPoly *pol = FX_AddPoly(p, uv, 4, // verts, ST, vertCount + vel, accel, // motion + 0.15f, 0.0f, 85.0f, // alpha start, alpha end, alpha parm ( begin alpha fade when 85% of life is complete ) + rgb1, rgb1, 0.0f, // rgb start, rgb end, rgb parm ( not used ) + rotDelta, bounce, time, // rotation amount, bounce, and time to delay motion for ( zero if no delay ); + 6000, // life + cgi_R_RegisterShader( "gfx/misc/test_crackle" ), + FX_APPLY_PHYSICS | FX_ALPHA_NONLINEAR | FX_USE_ALPHA ); + + if ( random() > 0.95f && pol ) + { + pol->AddFlags( FX_IMPACT_RUNS_FX | FX_KILL_ON_IMPACT ); + pol->SetImpactFxID( theFxScheduler.RegisterEffect( "glass_impact" )); + } + */ + + //rww - this is dirty. + + i = 0; + i_2 = 0; + + while (i < 4) + { + while (i_2 < 3) + { + apArgs.p[i][i_2] = p[i][i_2]; + + i_2++; + } + + i_2 = 0; + i++; + } + + i = 0; + i_2 = 0; + + while (i < 4) + { + while (i_2 < 2) + { + apArgs.ev[i][i_2] = uv[i][i_2]; + + i_2++; + } + + i_2 = 0; + i++; + } + + apArgs.numVerts = 4; + VectorCopy(vel, apArgs.vel); + VectorCopy(accel, apArgs.accel); + + apArgs.alpha1 = 0.15f; + apArgs.alpha2 = 0.0f; + apArgs.alphaParm = 85.0f; + + VectorCopy(rgb1, apArgs.rgb1); + VectorCopy(rgb1, apArgs.rgb2); + + apArgs.rgbParm = 0.0f; + + VectorCopy(rotDelta, apArgs.rotationDelta); + + apArgs.bounce = bounce; + apArgs.motionDelay = time; + apArgs.killTime = 6000; + apArgs.shader = cgs.media.glassShardShader; + apArgs.flags = (FX_APPLY_PHYSICS | FX_ALPHA_NONLINEAR | FX_USE_ALPHA); + + trap_FX_AddPoly(&apArgs); +} + +static void CG_CalcBiLerp( vec3_t verts[4], vec3_t subVerts[4], vec2_t uv[4] ) +{ + vec3_t temp; + + // Nasty crap + VectorScale( verts[0], 1.0f - uv[0][0], subVerts[0] ); + VectorMA( subVerts[0], uv[0][0], verts[1], subVerts[0] ); + VectorScale( subVerts[0], 1.0f - uv[0][1], temp ); + VectorScale( verts[3], 1.0f - uv[0][0], subVerts[0] ); + VectorMA( subVerts[0], uv[0][0], verts[2], subVerts[0] ); + VectorMA( temp, uv[0][1], subVerts[0], subVerts[0] ); + + VectorScale( verts[0], 1.0f - uv[1][0], subVerts[1] ); + VectorMA( subVerts[1], uv[1][0], verts[1], subVerts[1] ); + VectorScale( subVerts[1], 1.0f - uv[1][1], temp ); + VectorScale( verts[3], 1.0f - uv[1][0], subVerts[1] ); + VectorMA( subVerts[1], uv[1][0], verts[2], subVerts[1] ); + VectorMA( temp, uv[1][1], subVerts[1], subVerts[1] ); + + VectorScale( verts[0], 1.0f - uv[2][0], subVerts[2] ); + VectorMA( subVerts[2], uv[2][0], verts[1], subVerts[2] ); + VectorScale( subVerts[2], 1.0f - uv[2][1], temp ); + VectorScale( verts[3], 1.0f - uv[2][0], subVerts[2] ); + VectorMA( subVerts[2], uv[2][0], verts[2], subVerts[2] ); + VectorMA( temp, uv[2][1], subVerts[2], subVerts[2] ); + + VectorScale( verts[0], 1.0f - uv[3][0], subVerts[3] ); + VectorMA( subVerts[3], uv[3][0], verts[1], subVerts[3] ); + VectorScale( subVerts[3], 1.0f - uv[3][1], temp ); + VectorScale( verts[3], 1.0f - uv[3][0], subVerts[3] ); + VectorMA( subVerts[3], uv[3][0], verts[2], subVerts[3] ); + VectorMA( temp, uv[3][1], subVerts[3], subVerts[3] ); +} +// bilinear +//f(p',q') = (1 - y) × {[(1 - x) × f(p,q)] + [x × f(p,q+1)]} + y × {[(1 - x) × f(p+1,q)] + [x × f(p+1,q+1)]}. + + +static void CG_CalcHeightWidth( vec3_t verts[4], float *height, float *width ) +{ + vec3_t dir1, dir2, cross; + + VectorSubtract( verts[3], verts[0], dir1 ); // v + VectorSubtract( verts[1], verts[0], dir2 ); // p-a + CrossProduct( dir1, dir2, cross ); + *width = VectorNormalize( cross ) / VectorNormalize( dir1 ); // v + VectorSubtract( verts[2], verts[0], dir2 ); // p-a + CrossProduct( dir1, dir2, cross ); + *width += VectorNormalize( cross ) / VectorNormalize( dir1 ); // v + *width *= 0.5f; + + VectorSubtract( verts[1], verts[0], dir1 ); // v + VectorSubtract( verts[2], verts[0], dir2 ); // p-a + CrossProduct( dir1, dir2, cross ); + *height = VectorNormalize( cross ) / VectorNormalize( dir1 ); // v + VectorSubtract( verts[3], verts[0], dir2 ); // p-a + CrossProduct( dir1, dir2, cross ); + *height += VectorNormalize( cross ) / VectorNormalize( dir1 ); // v + *height *= 0.5f; +} +//Consider a line in 3D with position vector "a" and direction vector "v" and +// let "p" be the position vector of an arbitrary point in 3D +//dist = len( crossprod(p-a,v) ) / len(v); + +void CG_InitGlass( void ) +{ + int i, t; + + // Build a table first, so that we can do a more unpredictable crack scheme + // do it once, up front to save a bit of time. + for ( i = 0; i < 20; i++ ) + { + for ( t = 0; t < 20; t++ ) + { + offX[t][i] = crandom() * 0.03f; + offZ[i][t] = crandom() * 0.03f; + } + } +} + +void Vector2Set(vec2_t a,float b,float c) +{ + a[0] = b; + a[1] = c; +} + +#define TIME_DECAY_SLOW 0.1f +#define TIME_DECAY_MED 0.04f +#define TIME_DECAY_FAST 0.009f + +void CG_DoGlass( vec3_t verts[4], vec3_t normal, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius, int maxShards ) +{ + int i, t; + int mxHeight, mxWidth; + float height, width; + float stepWidth, stepHeight; + float timeDecay; + float x, z; + float xx, zz; + float dif; + int time = 0; + int glassShards = 0; + qboolean stick = qtrue; + vec3_t subVerts[4]; + vec2_t biPoints[4]; + + // To do a smarter tesselation, we should figure out the relative height and width of the brush face, + // then use this to pick a lod value from 1-3 in each axis. This will give us 1-9 lod levels, which will + // hopefully be sufficient. + CG_CalcHeightWidth( verts, &height, &width ); + + trap_S_StartSound( dmgPt, -1, CHAN_AUTO, trap_S_RegisterSound("sound/effects/glassbreak1.wav")); + + // Pick "LOD" for height + if ( height < 100 ) + { + stepHeight = 0.2f; + mxHeight = 5; + timeDecay = TIME_DECAY_SLOW; + } + else if ( height > 220 ) + { + stepHeight = 0.05f; + mxHeight = 20; + timeDecay = TIME_DECAY_FAST; + } + else + { + stepHeight = 0.1f; + mxHeight = 10; + timeDecay = TIME_DECAY_MED; + } + + // Pick "LOD" for width + /* + if ( width < 100 ) + { + stepWidth = 0.2f; + mxWidth = 5; + timeDecay = ( timeDecay + TIME_DECAY_SLOW ) * 0.5f; + } + else if ( width > 220 ) + { + stepWidth = 0.05f; + mxWidth = 20; + timeDecay = ( timeDecay + TIME_DECAY_FAST ) * 0.5f; + } + else + { + stepWidth = 0.1f; + mxWidth = 10; + timeDecay = ( timeDecay + TIME_DECAY_MED ) * 0.5f; + } + */ + + //Attempt to scale the glass directly to the size of the window + + stepWidth = (0.25f - (width*0.0002)); //(width*0.0005)); + mxWidth = width*0.2; + timeDecay = ( timeDecay + TIME_DECAY_FAST ) * 0.5f; + + if (stepWidth < 0.01f) + { + stepWidth = 0.01f; + } + if (mxWidth < 5) + { + mxWidth = 5; + } + + for ( z = 0.0f, i = 0; z < 1.0f; z += stepHeight, i++ ) + { + for ( x = 0.0f, t = 0; x < 1.0f; x += stepWidth, t++ ) + { + // This is nasty.. + if ( t > 0 && t < mxWidth ) + { + xx = x - offX[i][t]; + } + else + { + xx = x; + } + + if ( i > 0 && i < mxHeight ) + { + zz = z - offZ[t][i]; + } + else + { + zz = z; + } + + Vector2Set( biPoints[0], xx, zz ); + + if ( t + 1 > 0 && t + 1 < mxWidth ) + { + xx = x - offX[i][t + 1]; + } + else + { + xx = x; + } + + if ( i > 0 && i < mxHeight ) + { + zz = z - offZ[t + 1][i]; + } + else + { + zz = z; + } + + Vector2Set( biPoints[1], xx + stepWidth, zz ); + + if ( t + 1 > 0 && t + 1 < mxWidth ) + { + xx = x - offX[i + 1][t + 1]; + } + else + { + xx = x; + } + + if ( i + 1 > 0 && i + 1 < mxHeight ) + { + zz = z - offZ[t + 1][i + 1]; + } + else + { + zz = z; + } + + Vector2Set( biPoints[2], xx + stepWidth, zz + stepHeight); + + if ( t > 0 && t < mxWidth ) + { + xx = x - offX[i + 1][t]; + } + else + { + xx = x; + } + + if ( i + 1 > 0 && i + 1 < mxHeight ) + { + zz = z - offZ[t][i + 1]; + } + else + { + zz = z; + } + + Vector2Set( biPoints[3], xx, zz + stepHeight ); + + CG_CalcBiLerp( verts, subVerts, biPoints ); + + dif = DistanceSquared( subVerts[0], dmgPt ) * timeDecay - random() * 32; + + // If we decrease dif, we are increasing the impact area, making it more likely to blow out large holes + dif -= dmgRadius * dmgRadius; + + if ( dif > 1 ) + { + stick = qtrue; + time = dif + random() * 200; + } + else + { + stick = qfalse; + time = 0; + } + + CG_DoGlassQuad( subVerts, biPoints, stick, time, dmgDir ); + glassShards++; + + if (maxShards && glassShards >= maxShards) + { + return; + } + } + } +} + +/* +================== +CG_GlassShatter +Break glass with fancy method +================== +*/ +void CG_GlassShatter(int entnum, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius, int maxShards) +{ + vec3_t verts[4], normal; + + if (cgs.inlineDrawModel[cg_entities[entnum].currentState.modelindex]) + { + trap_R_GetBModelVerts(cgs.inlineDrawModel[cg_entities[entnum].currentState.modelindex], verts, normal); + CG_DoGlass(verts, normal, dmgPt, dmgDir, dmgRadius, maxShards); + } + //otherwise something awful has happened. +} + +/* +================== +CG_GlassShatter_Old +Throws glass shards from within a given bounding box in the world +================== +*/ +void CG_GlassShatter_Old(int entnum, vec3_t org, vec3_t mins, vec3_t maxs) +{ + vec3_t velocity, a, shardorg, dif, difx; + float windowmass; + float shardsthrow = 0; + char chunkname[256]; + + trap_S_StartSound(org, entnum, CHAN_BODY, trap_S_RegisterSound("sound/effects/glassbreak1.wav")); + + VectorSubtract(maxs, mins, a); + + windowmass = VectorLength(a); //should give us some idea of how big the chunk of glass is + + while (shardsthrow < windowmass) + { + velocity[0] = crandom()*150; + velocity[1] = crandom()*150; + velocity[2] = 150 + crandom()*75; + + Com_sprintf(chunkname, sizeof(chunkname), "models/chunks/glass/glchunks_%i.md3", Q_irand(1, 6)); + VectorCopy(org, shardorg); + + dif[0] = (maxs[0]-mins[0])/2; + dif[1] = (maxs[1]-mins[1])/2; + dif[2] = (maxs[2]-mins[2])/2; + + if (dif[0] < 2) + { + dif[0] = 2; + } + if (dif[1] < 2) + { + dif[1] = 2; + } + if (dif[2] < 2) + { + dif[2] = 2; + } + + difx[0] = Q_irand(1, (dif[0]*0.9)*2); + difx[1] = Q_irand(1, (dif[1]*0.9)*2); + difx[2] = Q_irand(1, (dif[2]*0.9)*2); + + if (difx[0] > dif[0]) + { + shardorg[0] += difx[0]-(dif[0]); + } + else + { + shardorg[0] -= difx[0]; + } + if (difx[1] > dif[1]) + { + shardorg[1] += difx[1]-(dif[1]); + } + else + { + shardorg[1] -= difx[1]; + } + if (difx[2] > dif[2]) + { + shardorg[2] += difx[2]-(dif[2]); + } + else + { + shardorg[2] -= difx[2]; + } + + //CG_TestLine(org, shardorg, 5000, 0x0000ff, 3); + + CG_ThrowChunk( shardorg, velocity, trap_R_RegisterModel( chunkname ), 0, 254 ); + + shardsthrow += 10; + } +} + +/* +================== +CG_CreateDebris +Throws specified debris from within a given bounding box in the world +================== +*/ +#define DEBRIS_SPECIALCASE_ROCK -1 +#define DEBRIS_SPECIALCASE_CHUNKS -2 +#define DEBRIS_SPECIALCASE_WOOD -3 +#define DEBRIS_SPECIALCASE_GLASS -4 + +#define NUM_DEBRIS_MODELS_GLASS 8 +#define NUM_DEBRIS_MODELS_WOOD 8 +#define NUM_DEBRIS_MODELS_CHUNKS 3 +#define NUM_DEBRIS_MODELS_ROCKS 4 //12 + +int dbModels_Glass[NUM_DEBRIS_MODELS_GLASS]; +int dbModels_Wood[NUM_DEBRIS_MODELS_WOOD]; +int dbModels_Chunks[NUM_DEBRIS_MODELS_CHUNKS]; +int dbModels_Rocks[NUM_DEBRIS_MODELS_ROCKS]; + +void CG_CreateDebris(int entnum, vec3_t org, vec3_t mins, vec3_t maxs, int debrissound, int debrismodel) +{ + vec3_t velocity, a, shardorg, dif, difx; + float windowmass; + float shardsthrow = 0; + int omodel = debrismodel; + + if (omodel == DEBRIS_SPECIALCASE_GLASS && !dbModels_Glass[0]) + { //glass no longer exists, using it for metal. + dbModels_Glass[0] = trap_R_RegisterModel("models/chunks/metal/metal1_1.md3"); + dbModels_Glass[1] = trap_R_RegisterModel("models/chunks/metal/metal1_2.md3"); + dbModels_Glass[2] = trap_R_RegisterModel("models/chunks/metal/metal1_3.md3"); + dbModels_Glass[3] = trap_R_RegisterModel("models/chunks/metal/metal1_4.md3"); + dbModels_Glass[4] = trap_R_RegisterModel("models/chunks/metal/metal2_1.md3"); + dbModels_Glass[5] = trap_R_RegisterModel("models/chunks/metal/metal2_2.md3"); + dbModels_Glass[6] = trap_R_RegisterModel("models/chunks/metal/metal2_3.md3"); + dbModels_Glass[7] = trap_R_RegisterModel("models/chunks/metal/metal2_4.md3"); + } + if (omodel == DEBRIS_SPECIALCASE_WOOD && !dbModels_Wood[0]) + { + dbModels_Wood[0] = trap_R_RegisterModel("models/chunks/crate/crate1_1.md3"); + dbModels_Wood[1] = trap_R_RegisterModel("models/chunks/crate/crate1_2.md3"); + dbModels_Wood[2] = trap_R_RegisterModel("models/chunks/crate/crate1_3.md3"); + dbModels_Wood[3] = trap_R_RegisterModel("models/chunks/crate/crate1_4.md3"); + dbModels_Wood[4] = trap_R_RegisterModel("models/chunks/crate/crate2_1.md3"); + dbModels_Wood[5] = trap_R_RegisterModel("models/chunks/crate/crate2_2.md3"); + dbModels_Wood[6] = trap_R_RegisterModel("models/chunks/crate/crate2_3.md3"); + dbModels_Wood[7] = trap_R_RegisterModel("models/chunks/crate/crate2_4.md3"); + } + if (omodel == DEBRIS_SPECIALCASE_CHUNKS && !dbModels_Chunks[0]) + { + dbModels_Chunks[0] = trap_R_RegisterModel("models/chunks/generic/chunks_1.md3"); + dbModels_Chunks[1] = trap_R_RegisterModel("models/chunks/generic/chunks_2.md3"); + } + if (omodel == DEBRIS_SPECIALCASE_ROCK && !dbModels_Rocks[0]) + { + dbModels_Rocks[0] = trap_R_RegisterModel("models/chunks/rock/rock1_1.md3"); + dbModels_Rocks[1] = trap_R_RegisterModel("models/chunks/rock/rock1_2.md3"); + dbModels_Rocks[2] = trap_R_RegisterModel("models/chunks/rock/rock1_3.md3"); + dbModels_Rocks[3] = trap_R_RegisterModel("models/chunks/rock/rock1_4.md3"); + /* + dbModels_Rocks[4] = trap_R_RegisterModel("models/chunks/rock/rock2_1.md3"); + dbModels_Rocks[5] = trap_R_RegisterModel("models/chunks/rock/rock2_2.md3"); + dbModels_Rocks[6] = trap_R_RegisterModel("models/chunks/rock/rock2_3.md3"); + dbModels_Rocks[7] = trap_R_RegisterModel("models/chunks/rock/rock2_4.md3"); + dbModels_Rocks[8] = trap_R_RegisterModel("models/chunks/rock/rock3_1.md3"); + dbModels_Rocks[9] = trap_R_RegisterModel("models/chunks/rock/rock3_2.md3"); + dbModels_Rocks[10] = trap_R_RegisterModel("models/chunks/rock/rock3_3.md3"); + dbModels_Rocks[11] = trap_R_RegisterModel("models/chunks/rock/rock3_4.md3"); + */ + } + + VectorSubtract(maxs, mins, a); + + windowmass = VectorLength(a); //should give us some idea of how big the chunk of glass is + + while (shardsthrow < windowmass) + { + velocity[0] = crandom()*150; + velocity[1] = crandom()*150; + velocity[2] = 150 + crandom()*75; + + if (omodel == DEBRIS_SPECIALCASE_GLASS) + { + debrismodel = dbModels_Glass[Q_irand(0, NUM_DEBRIS_MODELS_GLASS-1)]; + } + else if (omodel == DEBRIS_SPECIALCASE_WOOD) + { + debrismodel = dbModels_Wood[Q_irand(0, NUM_DEBRIS_MODELS_WOOD-1)]; + } + else if (omodel == DEBRIS_SPECIALCASE_CHUNKS) + { + debrismodel = dbModels_Chunks[Q_irand(0, NUM_DEBRIS_MODELS_CHUNKS-1)]; + } + else if (omodel == DEBRIS_SPECIALCASE_ROCK) + { + debrismodel = dbModels_Rocks[Q_irand(0, NUM_DEBRIS_MODELS_ROCKS-1)]; + } + + VectorCopy(org, shardorg); + + dif[0] = (maxs[0]-mins[0])/2; + dif[1] = (maxs[1]-mins[1])/2; + dif[2] = (maxs[2]-mins[2])/2; + + if (dif[0] < 2) + { + dif[0] = 2; + } + if (dif[1] < 2) + { + dif[1] = 2; + } + if (dif[2] < 2) + { + dif[2] = 2; + } + + difx[0] = Q_irand(1, (dif[0]*0.9)*2); + difx[1] = Q_irand(1, (dif[1]*0.9)*2); + difx[2] = Q_irand(1, (dif[2]*0.9)*2); + + if (difx[0] > dif[0]) + { + shardorg[0] += difx[0]-(dif[0]); + } + else + { + shardorg[0] -= difx[0]; + } + if (difx[1] > dif[1]) + { + shardorg[1] += difx[1]-(dif[1]); + } + else + { + shardorg[1] -= difx[1]; + } + if (difx[2] > dif[2]) + { + shardorg[2] += difx[2]-(dif[2]); + } + else + { + shardorg[2] -= difx[2]; + } + + //CG_TestLine(org, shardorg, 5000, 0x0000ff, 3); + + CG_ThrowChunk( shardorg, velocity, debrismodel, debrissound, 0 ); + + shardsthrow += 10; + } +} + +//========================================================== +//SP-style chunks +//========================================================== + +/* +------------------------- +CG_ExplosionEffects + +Used to find the player and shake the camera if close enough +intensity ranges from 1 (minor tremble) to 16 (major quake) +------------------------- +*/ + +void CG_ExplosionEffects( vec3_t origin, float intensity, int radius, int time ) +{ + //FIXME: When exactly is the vieworg calculated in relation to the rest of the frame?s + + vec3_t dir; + float dist, intensityScale; + float realIntensity; + + VectorSubtract( cg.refdef.vieworg, origin, dir ); + dist = VectorNormalize( dir ); + + //Use the dir to add kick to the explosion + + if ( dist > radius ) + return; + + intensityScale = 1 - ( dist / (float) radius ); + realIntensity = intensity * intensityScale; + + CGCam_Shake( realIntensity, time ); +} + +/* +------------------------- +CG_MiscModelExplosion + +Adds an explosion to a misc model breakables +------------------------- +*/ + +void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType ) +{ + int ct = 13; + float r; + vec3_t org, mid, dir; + char *effect = NULL, *effect2 = NULL; + int eID1, eID2 = 0; + int i; + + VectorAdd( mins, maxs, mid ); + VectorScale( mid, 0.5f, mid ); + + switch( chunkType ) + { + case MAT_GLASS: + effect = "chunks/glassbreak"; + ct = 5; + break; + case MAT_GLASS_METAL: + effect = "chunks/glassbreak"; + effect2 = "chunks/metalexplode"; + ct = 5; + break; + case MAT_ELECTRICAL: + case MAT_ELEC_METAL: + effect = "chunks/sparkexplode"; + ct = 5; + break; + case MAT_METAL: + case MAT_METAL2: + case MAT_METAL3: + case MAT_CRATE1: + case MAT_CRATE2: + effect = "chunks/metalexplode"; + ct = 2; + break; + case MAT_GRATE1: + effect = "chunks/grateexplode"; + ct = 8; + break; + case MAT_ROPE: + ct = 20; + effect = "chunks/ropebreak"; + break; + case MAT_WHITE_METAL: //not sure what this crap is really supposed to be.. + case MAT_DRK_STONE: + case MAT_LT_STONE: + case MAT_GREY_STONE: + case MAT_SNOWY_ROCK: + switch( size ) + { + case 2: + effect = "chunks/rockbreaklg"; + break; + case 1: + default: + effect = "chunks/rockbreakmed"; + break; + } + } + + if ( !effect ) + { + return; + } + + ct += 7 * size; + + // FIXME: real precache .. VERify that these need to be here...don't think they would because the effects should be registered in g_breakable + //rww - No they don't.. indexed effects gameside get precached on load clientside, as server objects are setup before client asset load time. + //However, we need to index them, so.. + eID1 = trap_FX_RegisterEffect( effect ); + + if ( effect2 && effect2[0] ) + { + // FIXME: real precache + eID2 = trap_FX_RegisterEffect( effect2 ); + } + + // spawn chunk roughly in the bbox of the thing.. + for ( i = 0; i < ct; i++ ) + { + int j; + for( j = 0; j < 3; j++ ) + { + r = random() * 0.8f + 0.1f; + org[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] ); + } + + // shoot effect away from center + VectorSubtract( org, mid, dir ); + VectorNormalize( dir ); + + if ( effect2 && effect2[0] && ( rand() & 1 )) + { + trap_FX_PlayEffectID( eID2, org, dir, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( eID1, org, dir, -1, -1 ); + } + } +} + +/* +------------------------- +CG_Chunks + +Fun chunk spewer +------------------------- +*/ + +void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs, + float speed, int numChunks, material_t chunkType, int customChunk, float baseScale ) +{ + localEntity_t *le; + refEntity_t *re; + vec3_t dir; + int i, j, k; + int chunkModel = 0; + leBounceSoundType_t bounce = LEBS_NONE; + float r, speedMod = 1.0f; + qboolean chunk = qfalse; + + if ( chunkType == MAT_NONE ) + { + // Well, we should do nothing + return; + } + + // Set up our chunk sound info...breaking sounds are done here so they are done once on breaking..some return instantly because the chunks are done with effects instead of models + switch( chunkType ) + { + case MAT_GLASS: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); + return; + break; + case MAT_GRATE1: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.grateSound ); + return; + break; + case MAT_ELECTRICAL:// (sparks) + trap_S_StartSound( NULL, owner, CHAN_BODY, trap_S_RegisterSound (va("sound/ambience/spark%d.wav", Q_irand(1, 6))) ); + return; + break; + case MAT_DRK_STONE: + case MAT_LT_STONE: + case MAT_GREY_STONE: + case MAT_WHITE_METAL: // not quite sure what this stuff is supposed to be...it's for Stu + case MAT_SNOWY_ROCK: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.rockBreakSound ); + bounce = LEBS_ROCK; + speedMod = 0.5f; // rock blows up less + break; + case MAT_GLASS_METAL: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.glassChunkSound ); // FIXME: should probably have a custom sound + bounce = LEBS_METAL; + break; + case MAT_CRATE1: + case MAT_CRATE2: + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.crateBreakSound[Q_irand(0,1)] ); + break; + case MAT_METAL: + case MAT_METAL2: + case MAT_METAL3: + case MAT_ELEC_METAL:// FIXME: maybe have its own sound? + trap_S_StartSound( NULL, owner, CHAN_BODY, cgs.media.chunkSound ); + bounce = LEBS_METAL; + speedMod = 0.8f; // metal blows up a bit more + break; + case MAT_ROPE: +// trap_S_StartSound( NULL, owner, CHAN_BODY, cgi_S_RegisterSound( "" )); FIXME: needs a sound + return; + break; + } + + if ( baseScale <= 0.0f ) + { + baseScale = 1.0f; + } + + // Chunks + for( i = 0; i < numChunks; i++ ) + { + if ( customChunk > 0 ) + { + // Try to use a custom chunk. + if ( cgs.gameModels[customChunk] ) + { + chunk = qtrue; + chunkModel = cgs.gameModels[customChunk]; + } + } + + if ( !chunk ) + { + // No custom chunk. Pick a random chunk type at run-time so we don't get the same chunks + switch( chunkType ) + { + case MAT_METAL2: //bluegrey + chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)]; + break; + case MAT_GREY_STONE://gray + chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)]; + break; + case MAT_LT_STONE: //tan + chunkModel = cgs.media.chunkModels[CHUNK_ROCK2][Q_irand(0, 3)]; + break; + case MAT_DRK_STONE://brown + chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)]; + break; + case MAT_SNOWY_ROCK://gray & brown + if ( Q_irand( 0, 1 ) ) + { + chunkModel = cgs.media.chunkModels[CHUNK_ROCK1][Q_irand(0, 3)]; + } + else + { + chunkModel = cgs.media.chunkModels[CHUNK_ROCK3][Q_irand(0, 3)]; + } + break; + case MAT_WHITE_METAL: + chunkModel = cgs.media.chunkModels[CHUNK_WHITE_METAL][Q_irand(0, 3)]; + break; + case MAT_CRATE1://yellow multi-colored crate chunks + chunkModel = cgs.media.chunkModels[CHUNK_CRATE1][Q_irand(0, 3)]; + break; + case MAT_CRATE2://red multi-colored crate chunks + chunkModel = cgs.media.chunkModels[CHUNK_CRATE2][Q_irand(0, 3)]; + break; + case MAT_ELEC_METAL: + case MAT_GLASS_METAL: + case MAT_METAL://grey + chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)]; + break; + case MAT_METAL3: + if ( rand() & 1 ) + { + chunkModel = cgs.media.chunkModels[CHUNK_METAL1][Q_irand(0, 3)]; + } + else + { + chunkModel = cgs.media.chunkModels[CHUNK_METAL2][Q_irand(0, 3)]; + } + break; + } + } + + // It wouldn't look good to throw a bunch of RGB axis models...so make sure we have something to work with. + if ( chunkModel ) + { + le = CG_AllocLocalEntity(); + re = &le->refEntity; + + re->hModel = chunkModel; + le->leType = LE_FRAGMENT; + le->endTime = cg.time + 1300 + random() * 900; + + // spawn chunk roughly in the bbox of the thing...bias towards center in case thing blowing up doesn't complete fill its bbox. + for( j = 0; j < 3; j++ ) + { + r = random() * 0.8f + 0.1f; + re->origin[j] = ( r * mins[j] + ( 1 - r ) * maxs[j] ); + } + VectorCopy( re->origin, le->pos.trBase ); + + // Move out from center of thing, otherwise you can end up things moving across the brush in an undesirable direction. Visually looks wrong + VectorSubtract( re->origin, origin, dir ); + VectorNormalize( dir ); + VectorScale( dir, flrand( speed * 0.5f, speed * 1.25f ) * speedMod, le->pos.trDelta ); + + // Angular Velocity + VectorSet( le->angles.trBase, random() * 360, random() * 360, random() * 360 ); + + le->angles.trDelta[0] = crandom(); + le->angles.trDelta[1] = crandom(); + le->angles.trDelta[2] = 0; // don't do roll + + VectorScale( le->angles.trDelta, random() * 600.0f + 200.0f, le->angles.trDelta ); + + le->pos.trType = TR_GRAVITY; + le->angles.trType = TR_LINEAR; + le->pos.trTime = le->angles.trTime = cg.time; + le->bounceFactor = 0.2f + random() * 0.2f; + le->leFlags |= LEF_TUMBLE; + //le->ownerGentNum = owner; + le->leBounceSoundType = bounce; + + // Make sure that we have the desired start size set + le->radius = flrand( baseScale * 0.75f, baseScale * 1.25f ); + re->nonNormalizedAxes = qtrue; + AxisCopy( axisDefault, re->axis ); // could do an angles to axis, but this is cheaper and works ok + for( k = 0; k < 3; k++ ) + { + re->modelScale[k] = le->radius; + } + ScaleModelAxis(re); + /* + for( k = 0; k < 3; k++ ) + { + VectorScale( re->axis[k], le->radius, re->axis[k] ); + } + */ + } + } +} + +/* +================== +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, int numFrames, qhandle_t shader, + int msec, qboolean isSprite, float scale, int flags ) +{ + float ang = 0; + 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; + ex->refEntity.rotation = rand() % 360; + ex->radius = scale; + VectorScale( dir, 16, tmpVec ); + VectorAdd( tmpVec, origin, newOrigin ); + } else { + ex->leType = LE_EXPLOSION; + VectorCopy( origin, newOrigin ); + + // set axis with random rotate when necessary + if ( !dir ) + { + AxisClear( ex->refEntity.axis ); + } + else + { + if ( !(flags & LEF_NO_RANDOM_ROTATE) ) + 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; + ex->lifeRate = (float)numFrames / msec; + ex->leFlags = flags; + + //Scale the explosion + if (scale != 1) { + ex->refEntity.nonNormalizedAxes = qtrue; + + VectorScale( ex->refEntity.axis[0], scale, ex->refEntity.axis[0] ); + VectorScale( ex->refEntity.axis[1], scale, ex->refEntity.axis[1] ); + VectorScale( ex->refEntity.axis[2], scale, ex->refEntity.axis[2] ); + } + // 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_SurfaceExplosion + +Adds an explosion to a surface +------------------------- +*/ + +#define NUM_SPARKS 12 +#define NUM_PUFFS 1 +#define NUM_EXPLOSIONS 4 + +void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ) +{ + localEntity_t *le; + //FXTrail *particle; + vec3_t direction, new_org; + vec3_t velocity = { 0, 0, 0 }; + vec3_t temp_org, temp_vel; + float scale, dscale; + int i, numSparks; + + //Sparks + numSparks = 16 + (random() * 16.0f); + + for ( i = 0; i < numSparks; i++ ) + { + scale = 0.25f + (random() * 2.0f); + dscale = -scale*0.5; + +/* particle = FX_AddTrail( origin, + NULL, + NULL, + 32.0f, + -64.0f, + scale, + -scale, + 1.0f, + 0.0f, + 0.25f, + 4000.0f, + cgs.media.sparkShader, + rand() & FXF_BOUNCE); + if ( particle == NULL ) + return; + + FXE_Spray( normal, 500, 150, 1.0f, 768 + (rand() & 255), (FXPrimitive *) particle );*/ + } + + //Smoke + //Move this out a little from the impact surface + VectorMA( origin, 4, normal, new_org ); + VectorSet( velocity, 0.0f, 0.0f, 16.0f ); + + for ( i = 0; i < 4; i++ ) + { + VectorSet( temp_org, new_org[0] + (crandom() * 16.0f), new_org[1] + (crandom() * 16.0f), new_org[2] + (random() * 4.0f) ); + VectorSet( temp_vel, velocity[0] + (crandom() * 8.0f), velocity[1] + (crandom() * 8.0f), velocity[2] + (crandom() * 8.0f) ); + +/* FX_AddSprite( temp_org, + temp_vel, + NULL, + 64.0f + (random() * 32.0f), + 16.0f, + 1.0f, + 0.0f, + 20.0f + (crandom() * 90.0f), + 0.5f, + 1500.0f, + cgs.media.smokeShader, FXF_USE_ALPHA_CHAN );*/ + } + + //Core of the explosion + + //Orient the explosions to face the camera + VectorSubtract( cg.refdef.vieworg, origin, direction ); + VectorNormalize( direction ); + + //Tag the last one with a light + le = CG_MakeExplosion( origin, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 500, qfalse, radius * 0.02f + (random() * 0.3f), 0); + le->light = 150; + VectorSet( le->lightColor, 0.9f, 0.8f, 0.5f ); + + for ( i = 0; i < NUM_EXPLOSIONS-1; i ++) + { + VectorSet( new_org, (origin[0] + (16 + (crandom() * 8))*crandom()), (origin[1] + (16 + (crandom() * 8))*crandom()), (origin[2] + (16 + (crandom() * 8))*crandom()) ); + le = CG_MakeExplosion( new_org, direction, cgs.media.explosionModel, 6, cgs.media.surfaceExplosionShader, 300 + (rand() & 99), qfalse, radius * 0.05f + (crandom() *0.3f), 0); + } + + //Shake the camera + CG_ExplosionEffects( origin, shake_speed, 350, 750 ); + + // The level designers wanted to be able to turn the smoke spawners off. The rationale is that they + // want to blow up catwalks and such that fall down...when that happens, it shouldn't really leave a mark + // and a smoke spewer at the explosion point... + if ( smoke ) + { + VectorMA( origin, -8, normal, temp_org ); +// FX_AddSpawner( temp_org, normal, NULL, NULL, 100, random()*25.0f, 5000.0f, (void *) CG_SmokeSpawn ); + + //Impact mark + //FIXME: Replace mark + //CG_ImpactMark( cgs.media.burnMarkShader, origin, normal, random()*360, 1,1,1,1, qfalse, 8, qfalse ); + } +} + +/* +================= +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 = 0;//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; +} diff --git a/code/cgame/cg_ents.c b/code/cgame/cg_ents.c new file mode 100644 index 0000000..41031ed --- /dev/null +++ b/code/cgame/cg_ents.c @@ -0,0 +1,3793 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_ents.c -- present snapshot entities, happens every single frame + +#include "cg_local.h" +/* +Ghoul2 Insert Start +*/ +#include "..\game\q_shared.h" +#include "..\ghoul2\g2.h" +/* +Ghoul2 Insert end +*/ + +extern qboolean CG_InFighter( void ); +static void CG_Missile( centity_t *cent ); + +/* +====================== +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 + +========================================================================== +*/ + +//only need to use the CG_S_ system when you want a looping sound that isn't going to add itself +//via trap each frame. Such as ones generated by events. +/* +====================== +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_S_AddLoopingSound + +Set the current looping sounds on the entity. +================== +*/ +void CG_S_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + centity_t *cent = &cg_entities[entityNum]; + cgLoopSound_t *cSound = NULL; + int i = 0; + qboolean alreadyPlaying = qfalse; + + //first see if we're already looping this sound handle. + while (i < cent->numLoopingSounds) + { + cSound = ¢->loopingSound[i]; + + if (cSound->sfx == sfx) + { + alreadyPlaying = qtrue; + break; + } + } + + if (alreadyPlaying && cSound) + { //if this is the case, just update the properties of the looping sound and return. + VectorCopy(origin, cSound->origin); + VectorCopy(velocity, cSound->velocity); + } + else if (cent->numLoopingSounds >= MAX_CG_LOOPSOUNDS) + { //Just don't add it then I suppose. +#ifdef _XBOX // We decreased this number, so I'd like to know if it gets overflowed + Com_Printf( S_COLOR_YELLOW "Warning: MAX_CG_LOOPSOUNDS exceeded!!\n" ); +#endif + return; + } + + //Add a new looping sound. + cSound = ¢->loopingSound[cent->numLoopingSounds]; + + cSound->entityNum = entityNum; + VectorCopy(origin, cSound->origin); + VectorCopy(velocity, cSound->velocity); + cSound->sfx = sfx; + + cent->numLoopingSounds++; +} + +/* +================== +CG_S_AddLoopingSound + +For now just redirect, might eventually do something different. +================== +*/ +void CG_S_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + CG_S_AddLoopingSound(entityNum, origin, velocity, sfx); +} + +/* +================== +CG_S_AddLoopingSound + +Clear looping sounds. +================== +*/ +void CG_S_StopLoopingSound(int entityNum, sfxHandle_t sfx) +{ + centity_t *cent = &cg_entities[entityNum]; + cgLoopSound_t *cSound; + + if (sfx == -1) + { //clear all the looping sounds on the entity + cent->numLoopingSounds = 0; + } + else + { //otherwise, clear only the specified looping sound + int i = 0; + + while (i < cent->numLoopingSounds) + { + cSound = ¢->loopingSound[i]; + + if (cSound->sfx == sfx) + { //remove it then + int x = i+1; + + while (x < cent->numLoopingSounds) + { + memcpy(¢->loopingSound[x-1], ¢->loopingSound[x], sizeof(cent->loopingSound[x])); + x++; + } + cent->numLoopingSounds--; + } + + i++; + } + } + //trap_S_StopLoopingSound(entityNum); +} + +/* +================== +CG_S_UpdateLoopingSounds + +Update any existing looping sounds on the entity. +================== +*/ +void CG_S_UpdateLoopingSounds(int entityNum) +{ + centity_t *cent = &cg_entities[entityNum]; + cgLoopSound_t *cSound; + vec3_t lerpOrg; + float *v; + int i = 0; + + if (!cent->numLoopingSounds) + { + return; + } + + if (cent->currentState.eType == ET_MOVER) + { + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, lerpOrg ); + } + else + { + VectorCopy(cent->lerpOrigin, lerpOrg); + } + + while (i < cent->numLoopingSounds) + { + cSound = ¢->loopingSound[i]; + + //trap_S_AddLoopingSound(entityNum, cSound->origin, cSound->velocity, cSound->sfx); + //I guess just keep using lerpOrigin for now, + trap_S_AddLoopingSound(entityNum, lerpOrg, cSound->velocity, cSound->sfx); + i++; + } +} + +/* +================== +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 ); + + // add loop sound + if ( cent->currentState.loopSound || (cent->currentState.loopIsSoundset && cent->currentState.number >= MAX_CLIENTS) ) { + sfxHandle_t realSoundIndex = -1; + + if (cent->currentState.loopIsSoundset && cent->currentState.number >= MAX_CLIENTS) + { //If this is so, then first get our soundset from the index, and loopSound actually contains which part of the set to + //use rather than a sound index (BMS_START [0], BMS_MID [1], or BMS_END [2]). Typically loop sounds will be BMS_MID. + const char *soundSet; + + soundSet = CG_ConfigString( CS_AMBIENT_SET + cent->currentState.soundSetIndex ); + + if (soundSet && soundSet[0]) + { + realSoundIndex = trap_AS_GetBModelSound(soundSet, cent->currentState.loopSound); + } + } + else + { + realSoundIndex = cgs.gameSounds[ cent->currentState.loopSound ]; + } + + //rww - doors and things with looping sounds have a crazy origin (being brush models and all) + if (realSoundIndex != -1) + { + if ( cent->currentState.solid == SOLID_BMODEL ) + { + vec3_t origin; + float *v; + + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + trap_S_AddLoopingSound( cent->currentState.number, origin, vec3_origin, + realSoundIndex ); + } + else if (cent->currentState.eType != ET_SPEAKER) { + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + realSoundIndex ); + } else { + trap_S_AddRealLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + realSoundIndex ); + } + } + } + + + // 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 ); + } + +} + +localEntity_t *FX_AddOrientedLine(vec3_t start, vec3_t end, vec3_t normal, float stScale, float scale, + float dscale, float startalpha, float endalpha, float killTime, qhandle_t shader) +{ + localEntity_t *le; + +#ifdef _DEBUG + if (!shader) + { + Com_Printf("FX_AddLine: NULL shader\n"); + } +#endif + + le = CG_AllocLocalEntity(); + le->leType = LE_OLINE; + + le->startTime = cg.time; + le->endTime = le->startTime + killTime; + le->data.line.width = scale; + le->data.line.dwidth = dscale; + + le->alpha = startalpha; + le->dalpha = endalpha - startalpha; + + le->refEntity.data.line.stscale = stScale; + le->refEntity.data.line.width = scale; + + le->refEntity.customShader = shader; + + // set origin + VectorCopy ( start, le->refEntity.origin); + VectorCopy ( end, le->refEntity.oldorigin ); + + AxisClear(le->refEntity.axis); + VectorCopy( normal, le->refEntity.axis[0] ); + RotateAroundDirection( le->refEntity.axis, 0); // le->refEntity.data.sprite.rotation ); This is roll in quad land + + le->refEntity.shaderRGBA[0] = 0xff; + le->refEntity.shaderRGBA[1] = 0xff; + le->refEntity.shaderRGBA[2] = 0xff; + le->refEntity.shaderRGBA[3] = 0xff; + + le->color[0] = 1.0; + le->color[1] = 1.0; + le->color[2] = 1.0; + le->color[3] = 1.0; + le->lifeRate = 1.0 / ( le->endTime - le->startTime ); + + return(le); +} + +void FX_DrawPortableShield(centity_t *cent) +{ + //rww - this code differs a bit from the draw code in EF, I don't know why I had to do + //it this way yet it worked in EF the other way. + + int xaxis, height, posWidth, negWidth, team; + vec3_t start, end, normal; + localEntity_t *le; + qhandle_t shader; + char buf[1024]; + + trap_Cvar_VariableStringBuffer("cl_paused", buf, sizeof(buf)); + + if (atoi(buf)) + { //rww - fix to keep from rendering repeatedly while HUD menu is up + return; + } + + if (cent->currentState.eFlags & EF_NODRAW) + { + return; + } + + // decode the data stored in time2 + xaxis = ((cent->currentState.time2 >> 24) & 1); + height = ((cent->currentState.time2 >> 16) & 255); + posWidth = ((cent->currentState.time2 >> 8) & 255); + negWidth = (cent->currentState.time2 & 255); + + team = (cent->currentState.otherEntityNum2); + + VectorClear(normal); + + VectorCopy(cent->lerpOrigin, start); + VectorCopy(cent->lerpOrigin, end); + + if (xaxis) // drawing along x-axis + { + start[0] -= negWidth; + end[0] += posWidth; + } + else + { + start[1] -= negWidth; + end[1] += posWidth; + } + + normal[0] = 1; + normal[1] = 1; + + start[2] += height/2; + end[2] += height/2; + + if (team == TEAM_RED) + { + if (cent->currentState.trickedentindex) + { + shader = trap_R_RegisterShader( "gfx/misc/red_dmgshield" ); + } + else + { + shader = trap_R_RegisterShader( "gfx/misc/red_portashield" ); + } + } + else + { + if (cent->currentState.trickedentindex) + { + shader = trap_R_RegisterShader( "gfx/misc/blue_dmgshield" ); + } + else + { + shader = trap_R_RegisterShader( "gfx/misc/blue_portashield" ); + } + } + + le = FX_AddOrientedLine(start, end, normal, 1.0f, height, 0.0f, 1.0f, 1.0f, 50.0, shader); +} + +/* +================== +CG_Special +================== +*/ +void CG_Special( centity_t *cent ) { + entityState_t *s1; + + s1 = ¢->currentState; + + if (!s1) + { + return; + } + + // if set to invisible, skip + if (!s1->modelindex) { + return; + } + + if (s1->modelindex == HI_SHIELD) + { // The portable shield should go through a different rendering function. + FX_DrawPortableShield(cent); + return; + } +} + +/* +Ghoul2 Insert Start +*/ + +// Copy the ghoul2 data into the ref ent correctly +void CG_SetGhoul2Info( refEntity_t *ent, centity_t *cent) +{ + + ent->ghoul2 = cent->ghoul2; + VectorCopy( cent->modelScale, ent->modelScale); + ent->radius = cent->radius; + VectorCopy (cent->lerpAngles, ent->angles); +} + + + +// create 8 new points on screen around a model so we can see it's bounding box +void CG_CreateBBRefEnts(entityState_t *s1, vec3_t origin ) +{ +/* +//g2r +#if _DEBUG + refEntity_t point[8]; + int i; + vec3_t angles = {0,0,0}; + + for (i=0; i<8; i++) + { + memset (&point[i], 0, sizeof(refEntity_t)); + point[i].reType = RT_SPRITE; + point[i].radius = 1; + point[i].customShader = trap_R_RegisterShader("textures/tests/circle"); + point[i].shaderRGBA[0] = 255; + point[i].shaderRGBA[1] = 255; + point[i].shaderRGBA[2] = 255; + point[i].shaderRGBA[3] = 255; + + AnglesToAxis( angles, point[i].axis ); + + // now, we need to put the correct origins into each origin from the mins and max's + switch(i) + { + case 0: + VectorCopy(s1->mins, point[i].origin); + break; + case 1: + VectorCopy(s1->mins, point[i].origin); + point[i].origin[0] = s1->maxs[0]; + break; + case 2: + VectorCopy(s1->mins, point[i].origin); + point[i].origin[1] = s1->maxs[1]; + break; + case 3: + VectorCopy(s1->mins, point[i].origin); + point[i].origin[0] = s1->maxs[0]; + point[i].origin[1] = s1->maxs[1]; + break; + case 4: + VectorCopy(s1->maxs, point[i].origin); + break; + case 5: + VectorCopy(s1->maxs, point[i].origin); + point[i].origin[0] = s1->mins[0]; + break; + case 6: + VectorCopy(s1->maxs, point[i].origin); + point[i].origin[1] = s1->mins[1]; + break; + case 7: + VectorCopy(s1->maxs, point[i].origin); + point[i].origin[0] = s1->mins[0]; + point[i].origin[1] = s1->mins[1]; + break; + } + + // add the original origin to each point and then stuff them out there + VectorAdd(point[i].origin, origin, point[i].origin); + + trap_R_AddRefEntityToScene (&point[i]); + } +#endif + */ +} + +// write in the axis and stuff +void G2_BoltToGhoul2Model(centity_t *cent, refEntity_t *ent) +{ + // extract the wraith ID from the bolt info + int modelNum = cent->boltInfo >> MODEL_SHIFT; + int boltNum = cent->boltInfo >> BOLT_SHIFT; + int entNum = cent->boltInfo >> ENTITY_SHIFT; + mdxaBone_t boltMatrix; + + modelNum &= MODEL_AND; + boltNum &= BOLT_AND; + entNum &= ENTITY_AND; + + + //NOTENOTE I put this here because the cgs.gamemodels array no longer gets initialized. + assert(0); + + + // go away and get me the bolt position for this frame please + trap_G2API_GetBoltMatrix(cent->ghoul2, modelNum, boltNum, &boltMatrix, cg_entities[entNum].currentState.angles, cg_entities[entNum].currentState.origin, cg.time, cgs.gameModels, cent->modelScale); + + // set up the axis and origin we need for the actual effect spawning + ent->origin[0] = boltMatrix.matrix[0][3]; + ent->origin[1] = boltMatrix.matrix[1][3]; + ent->origin[2] = boltMatrix.matrix[2][3]; + + ent->axis[0][0] = boltMatrix.matrix[0][0]; + ent->axis[0][1] = boltMatrix.matrix[1][0]; + ent->axis[0][2] = boltMatrix.matrix[2][0]; + + ent->axis[1][0] = boltMatrix.matrix[0][1]; + ent->axis[1][1] = boltMatrix.matrix[1][1]; + ent->axis[1][2] = boltMatrix.matrix[2][1]; + + ent->axis[2][0] = boltMatrix.matrix[0][2]; + ent->axis[2][1] = boltMatrix.matrix[1][2]; + ent->axis[2][2] = boltMatrix.matrix[2][2]; +} + +void ScaleModelAxis(refEntity_t *ent) + +{ // scale the model should we need to + if (ent->modelScale[0] && ent->modelScale[0] != 1.0f) + { + VectorScale( ent->axis[0], ent->modelScale[0] , ent->axis[0] ); + ent->nonNormalizedAxes = qtrue; + } + if (ent->modelScale[1] && ent->modelScale[1] != 1.0f) + { + VectorScale( ent->axis[1], ent->modelScale[1] , ent->axis[1] ); + ent->nonNormalizedAxes = qtrue; + } + if (ent->modelScale[2] && ent->modelScale[2] != 1.0f) + { + VectorScale( ent->axis[2], ent->modelScale[2] , ent->axis[2] ); + ent->nonNormalizedAxes = qtrue; + } +} +/* +Ghoul2 Insert End +*/ + +char *forceHolocronModels[] = { + "models/map_objects/mp/lt_heal.md3", //FP_HEAL, + "models/map_objects/mp/force_jump.md3", //FP_LEVITATION, + "models/map_objects/mp/force_speed.md3", //FP_SPEED, + "models/map_objects/mp/force_push.md3", //FP_PUSH, + "models/map_objects/mp/force_pull.md3", //FP_PULL, + "models/map_objects/mp/lt_telepathy.md3", //FP_TELEPATHY, + "models/map_objects/mp/dk_grip.md3", //FP_GRIP, + "models/map_objects/mp/dk_lightning.md3", //FP_LIGHTNING, + "models/map_objects/mp/dk_rage.md3", //FP_RAGE, + "models/map_objects/mp/lt_protect.md3", //FP_PROTECT, + "models/map_objects/mp/lt_absorb.md3", //FP_ABSORB, + "models/map_objects/mp/lt_healother.md3", //FP_TEAM_HEAL, + "models/map_objects/mp/dk_powerother.md3", //FP_TEAM_FORCE, + "models/map_objects/mp/dk_drain.md3", //FP_DRAIN, + "models/map_objects/mp/force_sight.md3", //FP_SEE, + "models/map_objects/mp/saber_attack.md3", //FP_SABER_OFFENSE, + "models/map_objects/mp/saber_defend.md3", //FP_SABER_DEFENSE, + "models/map_objects/mp/saber_throw.md3" //FP_SABERTHROW +}; + +void CG_Disintegration(centity_t *cent, refEntity_t *ent) +{ + vec3_t tempAng, hitLoc; + float tempLength; + + VectorCopy(cent->currentState.origin2, hitLoc); + + VectorSubtract( hitLoc, ent->origin, ent->oldorigin ); + + tempLength = VectorNormalize( ent->oldorigin ); + vectoangles( ent->oldorigin, tempAng ); + tempAng[YAW] -= cent->lerpAngles[YAW]; + AngleVectors( tempAng, ent->oldorigin, NULL, NULL ); + VectorScale( ent->oldorigin, tempLength, ent->oldorigin ); + + ent->endTime = cent->dustTrailTime; + + ent->renderfx |= RF_DISINTEGRATE2; + ent->customShader = cgs.media.disruptorShader; + trap_R_AddRefEntityToScene( ent ); + + ent->renderfx &= ~(RF_DISINTEGRATE2); + ent->renderfx |= (RF_DISINTEGRATE1); + ent->customShader = 0; + trap_R_AddRefEntityToScene( ent ); + + if ( cg.time - ent->endTime < 1000 && (cg_timescale.value * cg_timescale.value * random()) > 0.05f ) + { + vec3_t fxOrg, fxDir; + mdxaBone_t boltMatrix; + int torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar"); + + VectorSet(fxDir, 0, 1, 0); + + trap_G2API_GetBoltMatrix( cent->ghoul2, 0, torsoBolt, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time, + cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, fxOrg ); + + VectorMA( fxOrg, -18, cg.refdef.viewaxis[0], fxOrg ); + fxOrg[2] += crandom() * 20; + trap_FX_PlayEffectID( cgs.effects.mDisruptorDeathSmoke, fxOrg, fxDir, -1, -1 ); + + if ( random() > 0.5f ) + { + trap_FX_PlayEffectID( cgs.effects.mDisruptorDeathSmoke, fxOrg, fxDir, -1, -1 ); + } + } +} + +extern int cgSiegeEntityRender; +static qboolean CG_RenderTimeEntBolt(centity_t *cent) +{ + int clientNum = cent->currentState.boltToPlayer-1; + centity_t *cl; + mdxaBone_t matrix; + vec3_t boltOrg, boltAng; + int getBolt = -1; + + if (clientNum >= MAX_CLIENTS || clientNum < 0) + { + assert(0); + return qfalse; + } + + cl = &cg_entities[clientNum]; + + if (!cl->ghoul2) + { + assert(0); + return qfalse; + } + + if (clientNum == cg.predictedPlayerState.clientNum && + !cg.renderingThirdPerson) + { //If in first person and you have it then render the thing spinning around on your hud. + cgSiegeEntityRender = cent->currentState.number; //set it to render at the end of the frame. + return qfalse; + } + + getBolt = trap_G2API_AddBolt(cl->ghoul2, 0, "lhand"); + + trap_G2API_GetBoltMatrix(cl->ghoul2, 0, getBolt, &matrix, cl->turAngles, cl->lerpOrigin, cg.time, cgs.gameModels, cl->modelScale); + + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Y, boltAng); + vectoangles(boltAng, boltAng); + boltAng[PITCH] = boltAng[ROLL] = 0; + + VectorCopy(boltOrg, cent->lerpOrigin); + VectorCopy(boltAng, cent->lerpAngles); + + return qtrue; +} + +/* +static void CG_SiegeEntRenderAboveHead(centity_t *cent) +{ + int clientNum = cent->currentState.boltToPlayer-1; + centity_t *cl; + refEntity_t ent; + vec3_t renderAngles; + + if (clientNum >= MAX_CLIENTS || clientNum < 0) + { + assert(0); + return; + } + + cl = &cg_entities[clientNum]; + + memset(&ent, 0, sizeof(ent)); + + //Set the angles to the global auto rotating ones, and the origin to slightly above the client + VectorCopy(cg.autoAngles, renderAngles); + AnglesToAxis( renderAngles, ent.axis ); + VectorCopy(cl->lerpOrigin, ent.origin); + ent.origin[2] += 50; + + //Set the model (ghoul2 or md3/other) + if (cent->ghoul2) + { + ent.ghoul2 = cent->ghoul2; + ent.hModel = 0; + } + else + { + ent.ghoul2 = NULL; + ent.hModel = cgs.gameModels[cent->currentState.modelindex]; + } + + //Scale it up + ent.modelScale[0] = 1.5f; + ent.modelScale[1] = 1.5f; + ent.modelScale[2] = 1.5f; + ScaleModelAxis(&ent); + + //Make it transparent + ent.renderfx = RF_FORCE_ENT_ALPHA; + ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 100; + + //And finally add it + trap_R_AddRefEntityToScene(&ent); +} +*/ + +void CG_AddRadarEnt(centity_t *cent) +{ + if (cg.radarEntityCount == sizeof(cg.radarEntities)/sizeof(cg.radarEntities[0])) + { +#ifdef _DEBUG + Com_Printf("^3Warning: CG_AddRadarEnt full. (%d max)\n", sizeof(cg.radarEntities)/sizeof(cg.radarEntities[0])); +#endif + return; + } + cg.radarEntities[cg.radarEntityCount++] = cent->currentState.number; +} + +void CG_AddBracketedEnt(centity_t *cent) +{ + if (cg.bracketedEntityCount == sizeof(cg.bracketedEntities)/sizeof(cg.bracketedEntities[0])) + { +#ifdef _DEBUG + Com_Printf("^3Warning: CG_AddBracketedEnt full. (%d max)\n", sizeof(cg.radarEntities)/sizeof(cg.bracketedEntities[0])); +#endif + return; + } + cg.bracketedEntities[cg.bracketedEntityCount++] = cent->currentState.number; +} +/* +================== +CG_General +================== +*/ +void CG_G2ServerBoneAngles(centity_t *cent); + +#include "../namespace_begin.h" +extern qboolean BG_GetRootSurfNameWithVariant( void *ghoul2, const char *rootSurfName, char *returnSurfName, int returnSize ); +#include "../namespace_end.h" + +static void CG_General( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + float val; + int beamID; + vec3_t beamOrg; + mdxaBone_t matrix; + qboolean doNotSetModel = qfalse; + + if (cent->currentState.modelGhoul2 == 127) + { //not ready to be drawn or initialized.. + return; + } + + if (cent->ghoul2 && !cent->currentState.modelGhoul2 && cent->currentState.eType != ET_BODY && + cent->currentState.number >= MAX_CLIENTS) + { //this is a bad thing + if (trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(&(cent->ghoul2)); + } + } + + if (cent->currentState.eFlags & EF_RADAROBJECT) + { + CG_AddRadarEnt(cent); + } + if (cent->currentState.eFlags2 & EF2_BRACKET_ENTITY) + { + if ( CG_InFighter() ) + {//only bracken when in a fighter + CG_AddBracketedEnt(cent); + } + } + + if (cent->currentState.boltToPlayer) + { //Shove it into the player's left hand then. + centity_t *pl = &cg_entities[cent->currentState.boltToPlayer-1]; + if (CG_IsMindTricked(pl->currentState.trickedentindex, + pl->currentState.trickedentindex2, + pl->currentState.trickedentindex3, + pl->currentState.trickedentindex4, + cg.predictedPlayerState.clientNum)) + { //don't show if this guy is mindtricking + return; + } + if (!CG_RenderTimeEntBolt(cent)) + { //If this function returns qfalse we shouldn't render this ent at all. + if (cent->currentState.boltToPlayer > 0 && + cent->currentState.boltToPlayer <= MAX_CLIENTS) + { + VectorCopy(pl->lerpOrigin, cent->lerpOrigin); + + if (cent->currentState.eFlags & EF_CLIENTSMOOTH) + { //if it's set to smooth keep the smoothed lerp origin updated, as we don't want to smooth while bolted. + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + } + return; + } + + if (cent->currentState.eFlags & EF_CLIENTSMOOTH) + { //if it's set to smooth keep the smoothed lerp origin updated, as we don't want to smooth while bolted. + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + +/* disabled for now + if (pl->currentState.number != cg.predictedPlayerState.clientNum) + { //don't render thing above head to self + CG_SiegeEntRenderAboveHead(cent); + } +*/ + } + else if (cent->currentState.eFlags & EF_CLIENTSMOOTH) + { + if (cent->currentState.groundEntityNum >= ENTITYNUM_WORLD) + { + float smoothFactor = 0.5f*cg_timescale.value; + int k = 0; + vec3_t posDif; + + //Use origin smoothing since dismembered limbs use ExPhys + if (DistanceSquared(cent->turAngles,cent->lerpOrigin)>18000.0f) + { + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + + VectorSubtract(cent->lerpOrigin, cent->turAngles, posDif); + + for (k=0;k<3;k++) + { + cent->turAngles[k]=(cent->turAngles[k]+posDif[k]*smoothFactor); + cent->lerpOrigin[k]=cent->turAngles[k]; + } + } + else + { //if we're sitting on an entity like a moving plat then we don't want to smooth either + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + } + + //rww - now do ragdoll stuff + if (cent->ghoul2 && + (cent->currentState.eType == ET_BODY || (cent->currentState.eFlags & EF_RAG))) + { + if (!(cent->currentState.eFlags & EF_NODRAW) && + !(cent->currentState.eFlags & EF_DISINTEGRATION) && + cent->bodyFadeTime <= cg.time) + { + vec3_t forcedAngles; + + VectorClear(forcedAngles); + forcedAngles[YAW] = cent->lerpAngles[YAW]; + + CG_RagDoll(cent, forcedAngles); + } + } + else if (cent->isRagging) + { + cent->isRagging = qfalse; + + if (cent->ghoul2 && trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { //May not be valid, in the case of a ragged entity being removed and a non-g2 ent filling its slot. + trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + } + + if (cent->currentState.boneOrient && cent->ghoul2) + { //server sent us some bone angles to use + CG_G2ServerBoneAngles(cent); + } + + if ((cent->currentState.eFlags & EF_G2ANIMATING) && cent->ghoul2) + { //mini-animation routine for general objects that want to play quick ghoul2 anims + //obviously lacks much of the functionality contained in player/npc animation. + //we actually use torsoAnim as the start frame and legsAnim as the end frame and + //always play the anim on the root bone. + if (cent->currentState.torsoAnim != cent->pe.torso.animationNumber || + cent->currentState.legsAnim != cent->pe.legs.animationNumber || + cent->currentState.torsoFlip != cent->pe.torso.lastFlip) + { + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", cent->currentState.torsoAnim, + cent->currentState.legsAnim, (BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND), 1.0f, cg.time, -1, 100); + + cent->pe.torso.animationNumber = cent->currentState.torsoAnim; + cent->pe.legs.animationNumber = cent->currentState.legsAnim; + cent->pe.torso.lastFlip = cent->currentState.torsoFlip; + } + } + + memset (&ent, 0, sizeof(ent)); + + ent.shaderRGBA[0] = cent->currentState.customRGBA[0]; + ent.shaderRGBA[1] = cent->currentState.customRGBA[1]; + ent.shaderRGBA[2] = cent->currentState.customRGBA[2]; + ent.shaderRGBA[3] = cent->currentState.customRGBA[3]; + + if (cent->currentState.modelGhoul2 >= G2_MODELPART_HEAD && + cent->currentState.modelGhoul2 <= G2_MODELPART_RLEG && + /*cent->currentState.modelindex < MAX_CLIENTS &&*/ + cent->currentState.weapon == G2_MODEL_PART) + { //special case for client limbs + centity_t *clEnt; + int dismember_settings = cg_dismember.integer; + float smoothFactor = 0.5f*cg_timescale.value; + int k = 0; + vec3_t posDif; + + doNotSetModel = qtrue; + + if (cent->currentState.modelindex >= 0) + { + clEnt = &cg_entities[cent->currentState.modelindex]; + } + else + { + clEnt = &cg_entities[cent->currentState.otherEntityNum2]; + } + + if (!dismember_settings) + { //This client does not wish to see dismemberment. + return; + } + + if (dismember_settings < 2 && (cent->currentState.modelGhoul2 == G2_MODELPART_HEAD || cent->currentState.modelGhoul2 == G2_MODELPART_WAIST)) + { //dismember settings are not high enough to display decaps and torso slashes + return; + } + + if (!cent->ghoul2) + { + const char *limbBone; + const char *rotateBone; + char limbName[MAX_QPATH]; + char stubName[MAX_QPATH]; + char limbCapName[MAX_QPATH]; + char stubCapName[MAX_QPATH]; + char *limbTagName; + char *stubTagName; + int limb_anim; + int newBolt; + int limbBit = (1 << (cent->currentState.modelGhoul2-10)); + + if (clEnt && (clEnt->torsoBolt & limbBit)) + { //already have this limb missing! + return; + } + + + if (clEnt && !(clEnt->currentState.eFlags & EF_DEAD)) + { //death flag hasn't made it through yet for the limb owner, we cannot create the limb until he's flagged as dead + return; + } + + if (clEnt && (!BG_InDeathAnim(clEnt->currentState.torsoAnim) || !BG_InDeathAnim(clEnt->pe.torso.animationNumber))) + { //don't make it unless we're in an actual death anim already + if (clEnt->currentState.torsoAnim != BOTH_RIGHTHANDCHOPPEDOFF) + { //exception + return; + } + } + + cent->bolt4 = -1; + cent->trailTime = 0; + + if (cent->currentState.modelGhoul2 == G2_MODELPART_HEAD) + { + limbBone = "cervical"; + rotateBone = "cranium"; + Q_strncpyz( limbName , "head", sizeof( limbName ) ); + Q_strncpyz( limbCapName, "head_cap_torso", sizeof( limbCapName ) ); + Q_strncpyz( stubCapName, "torso_cap_head", sizeof( stubCapName ) ); + limbTagName = "*head_cap_torso"; + stubTagName = "*torso_cap_head"; + limb_anim = BOTH_DISMEMBER_HEAD1; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_WAIST) + { + limbBone = "pelvis"; + + if (clEnt->localAnimIndex <= 1) + { //humanoid/rtrooper + rotateBone = "thoracic"; + } + else + { + rotateBone = "pelvis"; + } + Q_strncpyz( limbName, "torso", sizeof( limbName ) ); + Q_strncpyz( limbCapName, "torso_cap_hips", sizeof( limbCapName ) ); + Q_strncpyz( stubCapName, "hips_cap_torso", sizeof( stubCapName ) ); + limbTagName = "*torso_cap_hips"; + stubTagName = "*hips_cap_torso"; + limb_anim = BOTH_DISMEMBER_TORSO1; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_LARM) + { + limbBone = "lhumerus"; + rotateBone = "lradius"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "l_arm", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "torso", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_torso", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_arm", stubName ); + limbTagName = "*l_arm_cap_torso"; + stubTagName = "*torso_cap_l_arm"; + limb_anim = BOTH_DISMEMBER_LARM; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_RARM) + { + limbBone = "rhumerus"; + rotateBone = "rradius"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_arm", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "torso", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_torso", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_arm", stubName ); + limbTagName = "*r_arm_cap_torso"; + stubTagName = "*torso_cap_r_arm"; + limb_anim = BOTH_DISMEMBER_RARM; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_RHAND) + { + limbBone = "rradiusX"; + rotateBone = "rhand"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_hand", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_arm", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_r_arm", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_hand", stubName ); + limbTagName = "*r_hand_cap_r_arm"; + stubTagName = "*r_arm_cap_r_hand"; + limb_anim = BOTH_DISMEMBER_RARM; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_LLEG) + { + limbBone = "lfemurYZ"; + rotateBone = "ltibia"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "l_leg", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "hips", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_l_leg", stubName ); + limbTagName = "*l_leg_cap_hips"; + stubTagName = "*hips_cap_l_leg"; + limb_anim = BOTH_DISMEMBER_LLEG; + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_RLEG) + { + limbBone = "rfemurYZ"; + rotateBone = "rtibia"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_leg", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "hips", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName ); + limbTagName = "*r_leg_cap_hips"; + stubTagName = "*hips_cap_r_leg"; + limb_anim = BOTH_DISMEMBER_RLEG; + } + else + {//umm... just default to the right leg, I guess (same as on server) + limbBone = "rfemurYZ"; + rotateBone = "rtibia"; + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "r_leg", limbName, sizeof(limbName) ); + BG_GetRootSurfNameWithVariant( clEnt->ghoul2, "hips", stubName, sizeof(stubName) ); + Com_sprintf( limbCapName, sizeof( limbCapName ), "%s_cap_hips", limbName ); + Com_sprintf( stubCapName, sizeof( stubCapName), "%s_cap_r_leg", stubName ); + limbTagName = "*r_leg_cap_hips"; + stubTagName = "*hips_cap_r_leg"; + limb_anim = BOTH_DISMEMBER_RLEG; + } + + if (clEnt && clEnt->ghoul2) + { + if (trap_G2API_HasGhoul2ModelOnIndex(&(clEnt->ghoul2), 2)) + { //don't want to bother dealing with a second saber on limbs and stuff, just remove the thing + trap_G2API_RemoveGhoul2Model(&(clEnt->ghoul2), 2); + } + + if (trap_G2API_HasGhoul2ModelOnIndex(&(clEnt->ghoul2), 3)) + { //turn off jetpack also I suppose + trap_G2API_RemoveGhoul2Model(&(clEnt->ghoul2), 3); + } + + if (clEnt->localAnimIndex <= 0) + { //humanoid + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "model_root", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg.time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "pelvis", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg.time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg.time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, cgs.gameModels, 100, cg.time); + } + else + { + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "model_root", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg.time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "pelvis", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg.time); + trap_G2API_SetBoneAngles(clEnt->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg.time); + } + + trap_G2API_DuplicateGhoul2Instance(clEnt->ghoul2, ¢->ghoul2); + } + + if (!cent->ghoul2) + { + return; + } + + newBolt = trap_G2API_AddBolt( cent->ghoul2, 0, limbTagName ); + if ( newBolt != -1 ) + { + vec3_t boltOrg, boltAng; + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, newBolt, &matrix, cent->lerpAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Y, boltAng); + + trap_FX_PlayEffectID(cgs.effects.mBlasterSmoke, boltOrg, boltAng, -1, -1); + } + + cent->bolt4 = newBolt; + + trap_G2API_SetRootSurface(cent->ghoul2, 0, limbName); + + trap_G2API_SetNewOrigin(cent->ghoul2, trap_G2API_AddBolt(cent->ghoul2, 0, rotateBone)); + + trap_G2API_SetSurfaceOnOff(cent->ghoul2, limbCapName, 0); + + trap_G2API_SetSurfaceOnOff(clEnt->ghoul2, limbName, 0x00000100); + trap_G2API_SetSurfaceOnOff(clEnt->ghoul2, stubCapName, 0); + + newBolt = trap_G2API_AddBolt( clEnt->ghoul2, 0, stubTagName ); + if ( newBolt != -1 ) + { + vec3_t boltOrg, boltAng; + + trap_G2API_GetBoltMatrix(clEnt->ghoul2, 0, newBolt, &matrix, clEnt->lerpAngles, clEnt->lerpOrigin, cg.time, cgs.gameModels, clEnt->modelScale); + + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Y, boltAng); + + trap_FX_PlayEffectID(cgs.effects.mBlasterSmoke, boltOrg, boltAng, -1, -1); + } + + if (cent->currentState.modelGhoul2 == G2_MODELPART_RARM || cent->currentState.modelGhoul2 == G2_MODELPART_RHAND || cent->currentState.modelGhoul2 == G2_MODELPART_WAIST) + { //Cut his weapon holding arm off, so remove the weapon + if (trap_G2API_HasGhoul2ModelOnIndex(&(clEnt->ghoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(clEnt->ghoul2), 1); + } + } + + clEnt->torsoBolt |= limbBit; //reinit model after copying limbless one to queue + //This causes issues after respawning.. just keep track of limbs cut/made on server or something. + /* + if (cent->currentState.modelGhoul2 == G2_MODELPART_WAIST) + { + clEnt->torsoBolt |= (1 << (G2_MODELPART_HEAD-10)); + clEnt->torsoBolt |= (1 << (G2_MODELPART_RARM-10)); + clEnt->torsoBolt |= (1 << (G2_MODELPART_LARM-10)); + clEnt->torsoBolt |= (1 << (G2_MODELPART_RHAND-10)); + } + else if (cent->currentState.modelGhoul2 == G2_MODELPART_RARM) + { + clEnt->torsoBolt |= (1 << (G2_MODELPART_RHAND-10)); + } + */ + + VectorCopy(cent->lerpOrigin, cent->turAngles); + // return; + } + + //Use origin smoothing since dismembered limbs use ExPhys + if (DistanceSquared(cent->turAngles,cent->lerpOrigin)>18000.0f) + { + VectorCopy(cent->lerpOrigin, cent->turAngles); + } + + VectorSubtract(cent->lerpOrigin, cent->turAngles, posDif); + + for (k=0;k<3;k++) + { + cent->turAngles[k]=(cent->turAngles[k]+posDif[k]*smoothFactor); + cent->lerpOrigin[k]=cent->turAngles[k]; + } + + if (cent->ghoul2 && cent->bolt4 != -1 && cent->trailTime < cg.time) + { + if ( cent->bolt4 != -1 && + (cent->currentState.pos.trDelta[0] || cent->currentState.pos.trDelta[1] || cent->currentState.pos.trDelta[2]) ) + { + vec3_t boltOrg, boltAng; + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cent->bolt4, &matrix, cent->lerpAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Y, boltAng); + + if (!boltAng[0] && !boltAng[1] && !boltAng[2]) + { + boltAng[1] = 1; + } + trap_FX_PlayEffectID(cgs.effects.mBlasterSmoke, boltOrg, boltAng, -1, -1); + + cent->trailTime = cg.time + 400; + } + } + + ent.radius = cent->currentState.g2radius; + ent.hModel = 0; + } + + if (cent->currentState.number >= MAX_CLIENTS && + cent->currentState.activeForcePass == NUM_FORCE_POWERS+1&& + cent->currentState.NPC_class != CLASS_VEHICLE ) + { + vec3_t empAngles; + centity_t *empOwn; + + empOwn = &cg_entities[cent->currentState.emplacedOwner]; + + if (cg.snap->ps.clientNum == empOwn->currentState.number && + !cg.renderingThirdPerson) + { + VectorCopy(cg.refdef.viewangles, empAngles); + } + else + { + VectorCopy(empOwn->lerpAngles, empAngles); + } + + if (empAngles[PITCH] > 40) + { + empAngles[PITCH] = 40; + } + empAngles[YAW] -= cent->currentState.angles[YAW]; + + trap_G2API_SetBoneAngles( cent->ghoul2, 0, "Bone02", empAngles, BONE_ANGLES_REPLACE, NEGATIVE_Y, NEGATIVE_X, POSITIVE_Z, NULL, 0, cg.time); + } + + s1 = ¢->currentState; + + // if set to invisible, skip + if ((!s1->modelindex) && !(trap_G2_HaveWeGhoul2Models(cent->ghoul2))) + { + return; + } + + if ( ( s1->eFlags & EF_NODRAW ) ) + { + return; + } + + // set frame + if ( s1->eFlags & EF_SHADER_ANIM ) + { + // Deliberately setting it up so that shader anim will completely override any kind of model animation frame setting. + ent.renderfx|=RF_SETANIMINDEX; + ent.skinNum = s1->frame; + } + else + { + ent.frame = s1->frame; + } + ent.oldframe = ent.frame; + ent.backlerp = 0; + +/* +Ghoul2 Insert Start +*/ + CG_SetGhoul2Info(&ent, cent); + +/* +Ghoul2 Insert End +*/ + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + + if (cent->currentState.modelGhoul2) + { //If the game says this guy uses a ghoul2 model and the g2 instance handle is null, then initialize it + if (!cent->ghoul2 && !cent->currentState.bolt1) + { + char skinName[MAX_QPATH]; + const char *modelName = CG_ConfigString( CS_MODELS+cent->currentState.modelindex ); + int l; + int skin = 0; + + trap_G2API_InitGhoul2Model(¢->ghoul2, modelName, 0, 0, 0, 0, 0); + if (cent->ghoul2 && trap_G2API_SkinlessModel(cent->ghoul2, 0)) + { //well, you'd never want a skinless model, so try to get his skin... + Q_strncpyz(skinName, modelName, MAX_QPATH); + l = strlen(skinName); + while (l > 0 && skinName[l] != '/') + { //parse back to first / + l--; + } + if (skinName[l] == '/') + { //got it + l++; + skinName[l] = 0; + Q_strcat(skinName, MAX_QPATH, "model_default.skin"); + + skin = trap_R_RegisterSkin(skinName); + } + trap_G2API_SetSkin(cent->ghoul2, 0, skin, skin); + } + } + else if (cent->currentState.bolt1) + { + TurretClientRun(cent); + } + + if (cent->ghoul2) + { //give us a proper radius + ent.radius = cent->currentState.g2radius; + } + } + + if (s1->eType == ET_BODY) + { //bodies should have a radius as well + ent.radius = cent->currentState.g2radius; + + if (cent->ghoul2) + { //all bodies should already have a ghoul2 instance. Use it to set the torso/head angles to 0. + cent->lerpAngles[PITCH] = 0; + cent->lerpAngles[ROLL] = 0; + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "pelvis", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg.time); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 100, cg.time); + trap_G2API_SetBoneAngles(cent->ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, cgs.gameModels, 100, cg.time); + } + } + + if (s1->eType == ET_HOLOCRON && s1->modelindex < -100) + { //special render, it's a holocron + //Using actual models now: + ent.hModel = trap_R_RegisterModel(forceHolocronModels[s1->modelindex+128]); + + //Rotate them + VectorCopy( cg.autoAngles, cent->lerpAngles ); + AxisCopy( cg.autoAxis, ent.axis ); + } + else if (!doNotSetModel) + { + 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 ); + + if (cent->currentState.iModelScale) + { //if the server says we have a custom scale then set it now. + cent->modelScale[0] = cent->modelScale[1] = cent->modelScale[2] = cent->currentState.iModelScale/100.0f; + VectorCopy(cent->modelScale, ent.modelScale); + ScaleModelAxis(&ent); + } + else + { + VectorClear(cent->modelScale); + } + + if ( cent->currentState.time > cg.time && cent->currentState.weapon == WP_EMPLACED_GUN ) + { + // make the gun pulse red to warn about it exploding + val = (1.0f - (float)(cent->currentState.time - cg.time) / 3200.0f ) * 0.3f; + + ent.customShader = trap_R_RegisterShader( "gfx/effects/turretflashdie" ); + ent.shaderRGBA[0] = (sin( cg.time * 0.04f ) * val * 0.4f + val) * 255; + ent.shaderRGBA[1] = ent.shaderRGBA[2] = 0; + + ent.shaderRGBA[3] = 100; + trap_R_AddRefEntityToScene( &ent ); + ent.customShader = 0; + } + else if ( cent->currentState.time == -1 && cent->currentState.weapon == WP_EMPLACED_GUN) + { + ent.customShader = trap_R_RegisterShader( "models/map_objects/imp_mine/turret_chair_dmg.tga" ); + //trap_R_AddRefEntityToScene( &ent ); + } + + if ((cent->currentState.eFlags & EF_DISINTEGRATION) && cent->currentState.eType == ET_BODY) + { + if (!cent->dustTrailTime) + { + cent->dustTrailTime = cg.time; + } + + CG_Disintegration(cent, &ent); + return; + } + else if (cent->currentState.eType == ET_BODY) + { + if (cent->bodyFadeTime > cg.time) + { + qboolean lightSide = cent->teamPowerType; + vec3_t hitLoc, tempAng; + float tempLength; + int curTimeDif = ((cg.time + 60000) - cent->bodyFadeTime); + int tMult = curTimeDif*0.08; + + ent.renderfx |= RF_FORCE_ENT_ALPHA; + + /* + if (!cent->bodyHeight) + { + cent->bodyHeight = ent.origin[2]; + } + */ + + if (curTimeDif*0.1 > 254) + { + ent.shaderRGBA[3] = 0; + } + else + { + ent.shaderRGBA[3] = (254 - tMult); + } + + if (ent.shaderRGBA[3] >= 1) + { //add the transparent body section + trap_R_AddRefEntityToScene (&ent); + } + + ent.renderfx &= ~RF_FORCE_ENT_ALPHA; + ent.renderfx |= RF_RGB_TINT; + + if (tMult > 200) + { //begin the disintegration effect + ent.shaderRGBA[3] = 200; + if (!cent->dustTrailTime) + { + cent->dustTrailTime = cg.time; + if (lightSide) + { + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/see.wav") ); + } + else + { + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/lightning") ); + } + } + ent.endTime = cent->dustTrailTime; + ent.renderfx |= RF_DISINTEGRATE2; + } + else + { //set the alpha on the to-be-disintegrated layer + ent.shaderRGBA[3] = tMult; + if (ent.shaderRGBA[3] < 1) + { + ent.shaderRGBA[3] = 1; + } + } + //Set everything up on the disint ref + ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = ent.shaderRGBA[3]; + VectorCopy(cent->lerpOrigin, hitLoc); + + VectorSubtract( hitLoc, ent.origin, ent.oldorigin ); + + tempLength = VectorNormalize( ent.oldorigin ); + vectoangles( ent.oldorigin, tempAng ); + tempAng[YAW] -= cent->lerpAngles[YAW]; + AngleVectors( tempAng, ent.oldorigin, NULL, NULL ); + VectorScale( ent.oldorigin, tempLength, ent.oldorigin ); + + if (lightSide) + { //might be temporary, dunno. + ent.customShader = cgs.media.playerShieldDamage; + } + else + { + ent.customShader = cgs.media.redSaberGlowShader; + } + + //slowly move the glowing part upward, out of the fading body + /* + cent->bodyHeight += 0.4f; + ent.origin[2] = cent->bodyHeight; + */ + + trap_R_AddRefEntityToScene( &ent ); + ent.renderfx &= ~RF_DISINTEGRATE2; + ent.customShader = 0; + + if (curTimeDif < 3400) + { + if (lightSide) + { + if (curTimeDif < 2200) + { //probably temporary + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) ); + } + } + else + { //probably temporary as well + ent.renderfx |= RF_RGB_TINT; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = ent.shaderRGBA[2] = 0; + ent.shaderRGBA[3] = 255; + if ( rand() & 1 ) + { + ent.customShader = cgs.media.electricBodyShader; + } + else + { + ent.customShader = cgs.media.electricBody2Shader; + } + if ( random() > 0.9f ) + { + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, cgs.media.crackleSound ); + } + trap_R_AddRefEntityToScene( &ent ); + } + } + + return; + } + else + { + cent->dustTrailTime = 0; + } + } + + if (cent->currentState.modelGhoul2 && + !ent.ghoul2 && + !ent.hModel) + { + return; + } + + // add to refresh list + trap_R_AddRefEntityToScene (&ent); + + if (cent->bolt3 == 999) + { //this is an in-flight saber being rendered manually + vec3_t org; + float wv; + int i; + addspriteArgStruct_t fxSArgs; + //refEntity_t sRef; + //memcpy( &sRef, &ent, sizeof( sRef ) ); + + ent.customShader = cgs.media.solidWhite; + ent.renderfx = RF_RGB_TINT; + wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f; + ent.shaderRGBA[0] = wv * 255; + ent.shaderRGBA[1] = wv * 255; + ent.shaderRGBA[2] = wv * 0; + trap_R_AddRefEntityToScene (&ent); + + for ( i = -4; i < 10; i += 1 ) + { + VectorMA( ent.origin, -i, ent.axis[2], org ); + + VectorCopy(org, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = 5.5f; + fxSArgs.dscale = 5.5f; + fxSArgs.sAlpha = wv; + fxSArgs.eAlpha = wv; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + fxSArgs.shader = cgs.media.yellowDroppedSaberShader; + fxSArgs.flags = 0x08000000; + + //trap_FX_AddSprite( org, NULL, NULL, 5.5f, 5.5f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowSaberGlowShader, 0x08000000 ); + trap_FX_AddSprite(&fxSArgs); + } + } + else if (cent->currentState.trickedentindex3) + { //holocron special effects + vec3_t org; + float wv; + addspriteArgStruct_t fxSArgs; + //refEntity_t sRef; + //memcpy( &sRef, &ent, sizeof( sRef ) ); + + ent.customShader = cgs.media.solidWhite; + ent.renderfx = RF_RGB_TINT; + wv = sin( cg.time * 0.005f ) * 0.08f + 0.1f; //* 0.08f + 0.1f; + + if (cent->currentState.trickedentindex3 == 1) + { //dark + ent.shaderRGBA[0] = wv*255; + ent.shaderRGBA[1] = 0; + ent.shaderRGBA[2] = 0; + } + else if (cent->currentState.trickedentindex3 == 2) + { //light + ent.shaderRGBA[0] = wv*255; + ent.shaderRGBA[1] = wv*255; + ent.shaderRGBA[2] = wv*255; + } + else + { //neutral + if ((s1->modelindex+128) == FP_SABER_OFFENSE || + (s1->modelindex+128) == FP_SABER_DEFENSE || + (s1->modelindex+128) == FP_SABERTHROW) + { //saber power + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = wv*255; + ent.shaderRGBA[2] = 0; + } + else + { + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = wv*255; + ent.shaderRGBA[2] = wv*255; + } + } + + ent.modelScale[0] = 1.1; + ent.modelScale[1] = 1.1; + ent.modelScale[2] = 1.1; + + ent.origin[2] -= 2; + ScaleModelAxis(&ent); + + trap_R_AddRefEntityToScene (&ent); + + VectorMA( ent.origin, 1, ent.axis[2], org ); + + org[2] += 18; + + wv = sin( cg.time * 0.002f ) * 0.08f + 0.1f; //* 0.08f + 0.1f; + + VectorCopy(org, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = wv*120;//16.0f; + fxSArgs.dscale = wv*120;//16.0f; + fxSArgs.sAlpha = wv*12; + fxSArgs.eAlpha = wv*12; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + + fxSArgs.flags = 0x08000000|0x00000001; + + if (cent->currentState.trickedentindex3 == 1) + { //dark + fxSArgs.sAlpha *= 3; + fxSArgs.eAlpha *= 3; + fxSArgs.shader = cgs.media.redSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else if (cent->currentState.trickedentindex3 == 2) + { //light + fxSArgs.sAlpha *= 1.5; + fxSArgs.eAlpha *= 1.5; + fxSArgs.shader = cgs.media.redSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.blueSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else + { //neutral + if ((s1->modelindex+128) == FP_SABER_OFFENSE || + (s1->modelindex+128) == FP_SABER_DEFENSE || + (s1->modelindex+128) == FP_SABERTHROW) + { //saber power + fxSArgs.sAlpha *= 1.5; + fxSArgs.eAlpha *= 1.5; + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else + { + fxSArgs.sAlpha *= 0.5; + fxSArgs.eAlpha *= 0.5; + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.blueSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + } + } + + if ( cent->currentState.time == -1 && cent->currentState.weapon == WP_TRIP_MINE && (cent->currentState.eFlags & EF_FIRING) ) + { //if force sight is active, render the laser multiple times up to the force sight level to increase visibility + if (cent->currentState.bolt2 == 1) + { + VectorMA( ent.origin, 6.6f, ent.axis[0], beamOrg );// forward + beamID = cgs.effects.tripmineGlowFX; + trap_FX_PlayEffectID( beamID, beamOrg, cent->currentState.pos.trDelta, -1, -1 ); + } + else + { + int i = 0; + + VectorMA( ent.origin, 6.6f, ent.axis[0], beamOrg );// forward + beamID = cgs.effects.tripmineLaserFX; + + if (cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) + { + i = cg.snap->ps.fd.forcePowerLevel[FP_SEE]; + + while (i > 0) + { + trap_FX_PlayEffectID( beamID, beamOrg, cent->currentState.pos.trDelta, -1, -1 ); + trap_FX_PlayEffectID( beamID, beamOrg, cent->currentState.pos.trDelta, -1, -1 ); + i--; + } + } + + trap_FX_PlayEffectID( beamID, beamOrg, cent->currentState.pos.trDelta, -1, -1 ); + } + } +/* +Ghoul2 Insert Start +*/ + + if (cg_debugBB.integer) + { + CG_CreateBBRefEnts(s1, cent->lerpOrigin); + } +/* +Ghoul2 Insert End +*/ +} + +/* +================== +CG_Speaker + +Speaker entities can automatically play sounds +================== +*/ +static void CG_Speaker( centity_t *cent ) { + if (cent->currentState.trickedentindex) + { + CG_S_StopLoopingSound(cent->currentState.number, -1); + } + + 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(); +} + +qboolean CG_GreyItem(int type, int tag, int plSide) +{ + if (type == IT_POWERUP && + (tag == PW_FORCE_ENLIGHTENED_LIGHT || tag == PW_FORCE_ENLIGHTENED_DARK)) + { + if (plSide == FORCE_LIGHTSIDE) + { + if (tag == PW_FORCE_ENLIGHTENED_DARK) + { + return qtrue; + } + } + else if (plSide == FORCE_DARKSIDE) + { + if (tag == PW_FORCE_ENLIGHTENED_LIGHT) + { + return qtrue; + } + } + } + + return qfalse; +} + +/* +================== +CG_Item +================== +*/ +static void CG_Item( centity_t *cent ) { + refEntity_t ent; + entityState_t *es; + gitem_t *item; + int msec; + float scale; + weaponInfo_t *wi; + + es = ¢->currentState; + if ( es->modelindex >= bg_numItems ) { + CG_Error( "Bad item index %i on entity", es->modelindex ); + } + +/* +Ghoul2 Insert Start +*/ + + if ((es->eFlags & EF_NODRAW) && (es->eFlags & EF_ITEMPLACEHOLDER)) + { + es->eFlags &= ~EF_NODRAW; + } + + if ( !es->modelindex ) + { + return; + } + + item = &bg_itemlist[ es->modelindex ]; + + if ((item->giType == IT_WEAPON || item->giType == IT_POWERUP) && + !(cent->currentState.eFlags & EF_DROPPEDWEAPON) && + !cg_simpleItems.integer) + { + vec3_t uNorm; + qboolean doGrey; + + VectorClear(uNorm); + + uNorm[2] = 1; + + memset( &ent, 0, sizeof( ent ) ); + + ent.customShader = 0; + VectorCopy(cent->lerpOrigin, ent.origin); + VectorCopy( cent->currentState.angles, cent->lerpAngles ); + AnglesToAxis(cent->lerpAngles, ent.axis); + ent.hModel = cgs.media.itemHoloModel; + + doGrey = CG_GreyItem(item->giType, item->giTag, cg.snap->ps.fd.forceSide); + + if (doGrey) + { + ent.renderfx |= RF_RGB_TINT; + + ent.shaderRGBA[0] = 150; + ent.shaderRGBA[1] = 150; + ent.shaderRGBA[2] = 150; + } + + trap_R_AddRefEntityToScene(&ent); + + if (!doGrey) + { + trap_FX_PlayEffectID(cgs.effects.itemCone, ent.origin, uNorm, -1, -1); + } + } + + // if set to invisible, skip + if ( ( es->eFlags & EF_NODRAW ) ) + { + return; + } +/* +Ghoul2 Insert End +*/ + + 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; + ent.customShader = cg_items[es->modelindex].icon; + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + + ent.origin[2] += 16; + + if (item->giType != IT_POWERUP || item->giTag != PW_FORCE_BOON) + { + ent.renderfx |= RF_FORCE_ENT_ALPHA; + } + + if ( es->eFlags & EF_ITEMPLACEHOLDER ) + { + if (item->giType == IT_POWERUP && item->giTag == PW_FORCE_BOON) + { + return; + } + ent.shaderRGBA[0] = 200; + ent.shaderRGBA[1] = 200; + ent.shaderRGBA[2] = 200; + ent.shaderRGBA[3] = 150 + sin(cg.time*0.01)*30; + } + else + { + ent.shaderRGBA[3] = 255; + } + + if (CG_GreyItem(item->giType, item->giTag, cg.snap->ps.fd.forceSide)) + { + ent.shaderRGBA[0] = 100; + ent.shaderRGBA[1] = 100; + ent.shaderRGBA[2] = 100; + + ent.shaderRGBA[3] = 200; + + if (item->giTag == PW_FORCE_ENLIGHTENED_LIGHT) + { + ent.customShader = trap_R_RegisterShader("gfx/misc/mp_light_enlight_disable"); + } + else + { + ent.customShader = trap_R_RegisterShader("gfx/misc/mp_dark_enlight_disable"); + } + } + trap_R_AddRefEntityToScene(&ent); + return; + } + + if ((item->giType == IT_WEAPON || item->giType == IT_POWERUP) && + !(cent->currentState.eFlags & EF_DROPPEDWEAPON)) + { + cent->lerpOrigin[2] += 16; + } + + if ((!(cent->currentState.eFlags & EF_DROPPEDWEAPON) || item->giType == IT_POWERUP) && + (item->giType == IT_WEAPON || item->giType == IT_POWERUP)) + { + // items bob up and down continuously + scale = 0.005 + cent->currentState.number * 0.00001; + cent->lerpOrigin[2] += 4 + cos( ( cg.time + 1000 ) * scale ) * 4; + } + else + { + if (item->giType == IT_HOLDABLE) + { + if (item->giTag == HI_SEEKER) + { + cent->lerpOrigin[2] += 5; + } + if (item->giTag == HI_SHIELD) + { + cent->lerpOrigin[2] += 2; + } + if (item->giTag == HI_BINOCULARS) + { + cent->lerpOrigin[2] += 2; + } + } + if (item->giType == IT_HEALTH) + { + cent->lerpOrigin[2] += 2; + } + if (item->giType == IT_ARMOR) + { + if (item->quantity == 100) + { + cent->lerpOrigin[2] += 7; + } + } + } + + memset (&ent, 0, sizeof(ent)); + + if ( (!(cent->currentState.eFlags & EF_DROPPEDWEAPON) || item->giType == IT_POWERUP) && + (item->giType == IT_WEAPON || item->giType == IT_POWERUP) ) + { //only weapons and powerups rotate now + // autorotate at one of two speeds + VectorCopy( cg.autoAngles, cent->lerpAngles ); + AxisCopy( cg.autoAxis, ent.axis ); + } + else + { + VectorCopy( cent->currentState.angles, cent->lerpAngles ); + AnglesToAxis(cent->lerpAngles, 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 (!(cent->currentState.eFlags & EF_DROPPEDWEAPON)) + { + 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 + } + } + else + { + wi = &cg_weapons[item->giTag]; + + switch(item->giTag) + { + case WP_BLASTER: + cent->lerpOrigin[2] -= 12; + break; + case WP_DISRUPTOR: + cent->lerpOrigin[2] -= 13; + break; + case WP_BOWCASTER: + cent->lerpOrigin[2] -= 16; + break; + case WP_REPEATER: + cent->lerpOrigin[2] -= 12; + break; + case WP_DEMP2: + cent->lerpOrigin[2] -= 10; + break; + case WP_FLECHETTE: + cent->lerpOrigin[2] -= 6; + break; + case WP_ROCKET_LAUNCHER: + cent->lerpOrigin[2] -= 11; + break; + case WP_THERMAL: + cent->lerpOrigin[2] -= 12; + break; + case WP_TRIP_MINE: + cent->lerpOrigin[2] -= 16; + break; + case WP_DET_PACK: + cent->lerpOrigin[2] -= 16; + break; + default: + cent->lerpOrigin[2] -= 8; + break; + } + } + + ent.hModel = cg_items[es->modelindex].models[0]; +/* +Ghoul2 Insert Start +*/ + ent.ghoul2 = cg_items[es->modelindex].g2Models[0]; + ent.radius = cg_items[es->modelindex].radius[0]; + VectorCopy (cent->lerpAngles, ent.angles); +/* +Ghoul2 Insert End +*/ + 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 (CG_GreyItem(item->giType, item->giTag, cg.snap->ps.fd.forceSide)) + { + ent.renderfx |= RF_RGB_TINT; + + ent.shaderRGBA[0] = 150; + ent.shaderRGBA[1] = 150; + ent.shaderRGBA[2] = 150; + + ent.renderfx |= RF_FORCE_ENT_ALPHA; + + ent.shaderRGBA[3] = 200; + + if (item->giTag == PW_FORCE_ENLIGHTENED_LIGHT) + { + ent.customShader = trap_R_RegisterShader("gfx/misc/mp_light_enlight_disable"); + } + else + { + ent.customShader = trap_R_RegisterShader("gfx/misc/mp_dark_enlight_disable"); + } + + trap_R_AddRefEntityToScene( &ent ); + return; + } + + if ( es->eFlags & EF_ITEMPLACEHOLDER ) // item has been picked up + { + if ( es->eFlags & EF_DEAD ) // if item had been droped, don't show at all + return; + + ent.renderfx |= RF_RGB_TINT; + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = 200; + ent.shaderRGBA[2] = 85; + ent.customShader = cgs.media.itemRespawningPlaceholder; + } + + // 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; + //trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.weaponHoverSound ); + } + + if (!(cent->currentState.eFlags & EF_DROPPEDWEAPON) && + (item->giType == IT_WEAPON || item->giType == IT_POWERUP)) + { + ent.renderfx |= RF_MINLIGHT; + } + + if (item->giType != IT_TEAM && msec >= 0 && msec < ITEM_SCALEUP_TIME && !(es->eFlags & EF_ITEMPLACEHOLDER) && !(es->eFlags & EF_DROPPEDWEAPON)) + { // if just respawned, fade in, but don't do this for flags. + float alpha; + int a; + + alpha = (float)msec / ITEM_SCALEUP_TIME; + a = alpha * 255.0; + if (a <= 0) + a=1; + + ent.shaderRGBA[3] = a; + if (item->giType != IT_POWERUP || item->giTag != PW_FORCE_BOON) + { //boon model uses a different blending mode for the sprite inside and doesn't look proper with this method + ent.renderfx |= RF_FORCE_ENT_ALPHA; + } + trap_R_AddRefEntityToScene(&ent); + + ent.renderfx &= ~RF_FORCE_ENT_ALPHA; + + // Now draw the static shader over it. + // Alpha in over half the time, out over half. + + //alpha = sin(M_PI*alpha); + a = alpha * 255.0; + + a = 255 - a; + + if (a <= 0) + a=1; + if (a > 255) + a=255; + + ent.customShader = cgs.media.itemRespawningRezOut; + + /* + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = a; + ent.shaderRGBA[2] = a-100; + + if (ent.shaderRGBA[2] < 0) + { + ent.shaderRGBA[2] = 0; + } + */ + + /* + ent.shaderRGBA[0] = + ent.shaderRGBA[1] = + ent.shaderRGBA[2] = a; + */ + + ent.renderfx |= RF_RGB_TINT; + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = 200; + ent.shaderRGBA[2] = 85; + + trap_R_AddRefEntityToScene( &ent ); + } + else + { // add to refresh list -- normal item + if (item->giType == IT_TEAM && + (item->giTag == PW_REDFLAG || item->giTag == PW_BLUEFLAG)) + { + ent.modelScale[0] = 0.7; + ent.modelScale[1] = 0.7; + ent.modelScale[2] = 0.7; + ScaleModelAxis(&ent); + } + trap_R_AddRefEntityToScene(&ent); + } + + //rww - As far as I can see, this is useless. + /* + 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; + + barrel.customShader = ent.customShader; + + CG_PositionRotatedEntityOnTag( &barrel, &ent, wi->weaponModel, "tag_barrel" ); + + AxisCopy( ent.axis, barrel.axis ); + barrel.nonNormalizedAxes = ent.nonNormalizedAxes; + + trap_R_AddRefEntityToScene( &barrel ); + } + */ + + // 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 ); + + trap_R_AddRefEntityToScene( &ent ); + } + } + } +} + +//============================================================================ + +void CG_CreateDistortionTrailPart(centity_t *cent, float scale, vec3_t pos) +{ + refEntity_t ent; + vec3_t ang; + float vLen; + + if (!cg_renderToTextureFX.integer) + { + return; + } + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( pos, ent.origin ); + + VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (VectorNormalize(ent.axis[0]) <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + VectorCopy(cent->lerpAngles, ang); + ang[PITCH] += 90.0f; + AnglesToAxis(ang, ent.axis); + + //radius must be a power of 2, and is the actual captured texture size + if (vLen < 512) + { + ent.radius = 256; + } + else if (vLen < 1024) + { + ent.radius = 128; + } + else if (vLen < 2048) + { + ent.radius = 64; + } + else + { + ent.radius = 32; + } + + ent.modelScale[0] = scale; + ent.modelScale[1] = scale; + ent.modelScale[2] = scale*16.0f; + ScaleModelAxis(&ent); + + ent.hModel = trap_R_RegisterModel("models/weapons2/merr_sonn/trailmodel.md3"); + ent.customShader = cgs.media.itemRespawningRezOut;//cgs.media.cloakedShader;//cgs.media.halfShieldShader; + +#if 1 + ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); + ent.shaderRGBA[0] = 255.0f; + ent.shaderRGBA[1] = 255.0f; + ent.shaderRGBA[2] = 255.0f; + ent.shaderRGBA[3] = 100.0f; +#else //no alpha + ent.renderfx = RF_DISTORTION; +#endif + + trap_R_AddRefEntityToScene( &ent ); +} + +//distortion trail effect for rockets -rww +/* +static void CG_DistortionTrail( centity_t *cent ) +{ + vec3_t fwd; + vec3_t pos; + float overallScale = 4.0f; + + VectorCopy(cent->currentState.pos.trDelta, fwd); + VectorNormalize(fwd); + + VectorMA(cent->lerpOrigin, -8.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.5f*overallScale, pos); + VectorMA(cent->lerpOrigin, -12.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.6f*overallScale, pos); + VectorMA(cent->lerpOrigin, -16.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.7f*overallScale, pos); + VectorMA(cent->lerpOrigin, -20.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.8f*overallScale, pos); + VectorMA(cent->lerpOrigin, -30.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 0.9f*overallScale, pos); + VectorMA(cent->lerpOrigin, -40.0f*overallScale, fwd, pos); + CG_CreateDistortionTrailPart(cent, 1.0f*overallScale, pos); +} +*/ + +/* +=============== +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 != G2_MODEL_PART ) { + s1->weapon = 0; + } + + if (cent->ghoul2 && s1->weapon == G2_MODEL_PART) + { + weapon = &cg_weapons[WP_SABER]; + } + else + { + weapon = &cg_weapons[s1->weapon]; + } + + if (cent->currentState.eFlags & EF_RADAROBJECT) + { + CG_AddRadarEnt(cent); + } + + if (s1->weapon == WP_SABER) + { + if ((cent->currentState.modelindex != cent->serverSaberHitIndex || !cent->ghoul2) && !(s1->eFlags & EF_NODRAW)) + { //no g2, or server changed the model we are using + const char *saberModel = CG_ConfigString( CS_MODELS+cent->currentState.modelindex ); + + cent->serverSaberHitIndex = cent->currentState.modelindex; + + if (cent->ghoul2) + { //clean if we already have one (because server changed model string index) + trap_G2API_CleanGhoul2Models(&(cent->ghoul2)); + cent->ghoul2 = 0; + } + + if (saberModel && saberModel[0]) + { + trap_G2API_InitGhoul2Model(¢->ghoul2, saberModel, 0, 0, 0, 0, 0); + } + else + { + trap_G2API_InitGhoul2Model(¢->ghoul2, "models/weapons2/saber/saber_w.glm", 0, 0, 0, 0, 0); + } + return; + } + else if (s1->eFlags & EF_NODRAW) + { + return; + } + } + + if (cent->ghoul2) + { //give us a proper radius + ent.radius = cent->currentState.g2radius; + } + + // calculate the axis + VectorCopy( s1->angles, cent->lerpAngles); + + if ( s1->otherEntityNum2 && s1->weapon != WP_SABER ) + {//using an over-ridden trail effect! + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + if ((s1->eFlags&EF_JETPACK_ACTIVE)//hack so we know we're a vehicle Weapon shot + && (g_vehWeaponInfo[s1->otherEntityNum2].iShotFX + || g_vehWeaponInfo[s1->otherEntityNum2].iModel != NULL_HANDLE) ) + { //a vehicle with an override for the weapon trail fx or model + trap_FX_PlayEffectID( g_vehWeaponInfo[s1->otherEntityNum2].iShotFX, cent->lerpOrigin, forward, -1, -1 ); + if ( g_vehWeaponInfo[s1->otherEntityNum2].iLoopSound ) + { + vec3_t velocity; + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, g_vehWeaponInfo[s1->otherEntityNum2].iLoopSound ); + } + //add custom model + if ( g_vehWeaponInfo[s1->otherEntityNum2].iModel == NULL_HANDLE ) + { + return; + } + } + else + {//a regular missile + trap_FX_PlayEffectID( cgs.gameEffects[s1->otherEntityNum2], cent->lerpOrigin, forward, -1, -1 ); + if ( s1->loopSound ) + { + vec3_t velocity; + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, s1->loopSound ); + } + //FIXME: if has a custom model, too, then set it and do rest of code below? + return; + } + } + else if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + // add trails + if ( weapon->altMissileTrailFunc ) + { + weapon->altMissileTrailFunc( cent, weapon ); + } + + // add dynamic light + if ( weapon->altMissileDlight ) + { + trap_R_AddLightToScene(cent->lerpOrigin, weapon->altMissileDlight, + weapon->altMissileDlightColor[0], weapon->altMissileDlightColor[1], weapon->altMissileDlightColor[2] ); + } + + // add missile sound + if ( weapon->altMissileSound ) { + vec3_t velocity; + + BG_EvaluateTrajectoryDelta( ¢->currentState.pos, cg.time, velocity ); + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, velocity, weapon->altMissileSound ); + } + + //Don't draw something without a model + if ( weapon->altMissileModel == NULL_HANDLE ) + return; + } + else + { + // 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 ); + } + + //Don't draw something without a model + if ( weapon->missileModel == NULL_HANDLE && s1->weapon != WP_SABER && s1->weapon != G2_MODEL_PART ) //saber uses ghoul2 model, doesn't matter + return; + } + + // create the render entity + memset (&ent, 0, sizeof(ent)); + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); +/* +Ghoul2 Insert Start +*/ + CG_SetGhoul2Info(&ent, cent); + +/* +Ghoul2 Insert End +*/ + + // flicker between two skins + ent.skinNum = cg.clientFrame & 1; + ent.renderfx = /*weapon->missileRenderfx | */RF_NOSHADOW; + + if ( !(s1->eFlags&EF_JETPACK_ACTIVE) ) + { + if (s1->weapon != WP_SABER && s1->weapon != G2_MODEL_PART) + { + //if ( cent->currentState.eFlags | EF_ALT_FIRING ) + //rww - why was this like this? + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { + ent.hModel = weapon->altMissileModel; + } + else + { + ent.hModel = weapon->missileModel; + } + } + } + //add custom model + else + { + if ( g_vehWeaponInfo[s1->otherEntityNum2].iModel != NULL_HANDLE ) + { + ent.hModel = g_vehWeaponInfo[s1->otherEntityNum2].iModel; + } + else + {//wtf? how did we get here? + return; + } + } + + // spin as it moves + if ( s1->apos.trType != TR_INTERPOLATE ) + { + // convert direction of travel into axis + if ( VectorNormalize2( s1->pos.trDelta, ent.axis[0] ) == 0 ) { + ent.axis[0][2] = 1; + } + + // spin as it moves + if ( s1->pos.trType != TR_STATIONARY ) + { + if ( s1->eFlags & EF_MISSILE_STICK ) + { + RotateAroundDirection( ent.axis, cg.time * 0.5f );//Did this so regular missiles don't get broken + } + else + { + RotateAroundDirection( ent.axis, cg.time * 0.25f );//JFM:FLOAT FIX + } + } + else + { + if ( s1->eFlags & EF_MISSILE_STICK ) + { + RotateAroundDirection( ent.axis, (float)s1->pos.trTime * 0.5f ); + } + else + { + RotateAroundDirection( ent.axis, (float)s1->time ); + } + } + } + else + { + AnglesToAxis( cent->lerpAngles, ent.axis ); + } + + if (s1->weapon == WP_SABER) + { + ent.radius = s1->g2radius; + } + + // add to refresh list, possibly with quad glow + CG_AddRefEntityWithPowerups( &ent, s1, TEAM_FREE ); + + if (s1->weapon == WP_SABER && cgs.gametype == GT_JEDIMASTER) + { //in jedimaster always make the saber glow when on the ground + vec3_t org; + float wv; + int i; + addspriteArgStruct_t fxSArgs; + //refEntity_t sRef; + //memcpy( &sRef, &ent, sizeof( sRef ) ); + + ent.customShader = cgs.media.solidWhite; + ent.renderfx = RF_RGB_TINT; + wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f; + ent.shaderRGBA[0] = wv * 255; + ent.shaderRGBA[1] = wv * 255; + ent.shaderRGBA[2] = wv * 0; + trap_R_AddRefEntityToScene (&ent); + + for ( i = -4; i < 10; i += 1 ) + { + VectorMA( ent.origin, -i, ent.axis[2], org ); + + VectorCopy(org, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = 5.5f; + fxSArgs.dscale = 5.5f; + fxSArgs.sAlpha = wv; + fxSArgs.eAlpha = wv; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + fxSArgs.shader = cgs.media.yellowDroppedSaberShader; + fxSArgs.flags = 0x08000000; + + //trap_FX_AddSprite( org, NULL, NULL, 5.5f, 5.5f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowSaberGlowShader, 0x08000000 ); + trap_FX_AddSprite(&fxSArgs); + } + + if (cgs.gametype == GT_JEDIMASTER) + { + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 0; + + ent.renderfx |= RF_DEPTHHACK; + ent.customShader = cgs.media.forceSightBubble; + + trap_R_AddRefEntityToScene( &ent ); + } + } + + if ( s1->eFlags & EF_FIRING ) + {//special code for adding the beam to the attached tripwire mine + vec3_t beamOrg; + + VectorMA( ent.origin, 8, ent.axis[0], beamOrg );// forward + trap_FX_PlayEffectID( cgs.effects.mTripMineLaster, beamOrg, ent.axis[0], -1, -1 ); + } +} + +int CG_BMS_START = 0; +int CG_BMS_MID = 1; +int CG_BMS_END = 2; + +/* +------------------------- +CG_PlayDoorLoopSound +------------------------- +*/ + +void CG_PlayDoorLoopSound( centity_t *cent ) +{ + sfxHandle_t sfx; + const char *soundSet; + vec3_t origin; + float *v; + + if ( !cent->currentState.soundSetIndex ) + { + return; + } + + soundSet = CG_ConfigString( CS_AMBIENT_SET + cent->currentState.soundSetIndex ); + + if (!soundSet || !soundSet[0]) + { + return; + } + + sfx = trap_AS_GetBModelSound( soundSet, CG_BMS_MID ); + + if ( sfx == -1 ) + { + return; + } + + if (cent->currentState.eType == ET_MOVER) //shouldn't be in here otherwise, but just in case. + { + v = cgs.inlineModelMidpoints[ cent->currentState.modelindex ]; + VectorAdd( cent->lerpOrigin, v, origin ); + } + else + { + VectorCopy(cent->lerpOrigin, origin); + } + + //ent->s.loopSound = sfx; + CG_S_AddRealLoopingSound(cent->currentState.number, origin, vec3_origin, sfx); +} + +/* +------------------------- +CG_PlayDoorSound +------------------------- +*/ + +void CG_PlayDoorSound( centity_t *cent, int type ) +{ + sfxHandle_t sfx; + const char *soundSet; + + if ( !cent->currentState.soundSetIndex ) + { + return; + } + + soundSet = CG_ConfigString( CS_AMBIENT_SET + cent->currentState.soundSetIndex ); + + if (!soundSet || !soundSet[0]) + { + return; + } + + sfx = trap_AS_GetBModelSound( soundSet, type ); + + if ( sfx == -1 ) + { + return; + } + + trap_S_StartSound( NULL, cent->currentState.number, CHAN_AUTO, sfx ); +} + +/* +=============== +CG_Mover +=============== +*/ +static void CG_Mover( centity_t *cent ) { + refEntity_t ent; + entityState_t *s1; + + s1 = ¢->currentState; + + // create the render entity + memset (&ent, 0, sizeof(ent)); + + if ( (cent->currentState.eFlags2&EF2_HYPERSPACE) ) + {//I'm the hyperspace brush + qboolean drawMe = qfalse; + if ( cg.predictedPlayerState.m_iVehicleNum + && cg.predictedVehicleState.hyperSpaceTime + && (cg.time-cg.predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME + && (cg.time-cg.predictedVehicleState.hyperSpaceTime) > 1000 ) + { + if ( cg.snap + && cg.snap->ps.pm_type == PM_INTERMISSION ) + {//in the intermission, stop drawing hyperspace ent + } + else if ( (cg.predictedVehicleState.eFlags2&EF2_HYPERSPACE) ) + {//actually hyperspacing now + float timeFrac = ((float)(cg.time-cg.predictedVehicleState.hyperSpaceTime-1000))/(HYPERSPACE_TIME-1000); + if ( timeFrac < (HYPERSPACE_TELEPORT_FRAC+0.1f) ) + {//still in hyperspace or just popped out + const float alpha = timeFrac<0.5f?timeFrac/0.5f:1.0f; + drawMe = qtrue; + VectorMA( cg.refdef.vieworg, 1000.0f+((1.0f-timeFrac)*1000.0f), cg.refdef.viewaxis[0], cent->lerpOrigin ); + VectorSet( cent->lerpAngles, cg.refdef.viewangles[PITCH], cg.refdef.viewangles[YAW]-90.0f, 0 );//cos( ( cg.time + 1000 ) * scale ) * 4 ); + ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = alpha*255; + } + } + } + if ( !drawMe ) + {//else, never draw + return; + } + } + + if (cent->currentState.eFlags & EF_RADAROBJECT) + { + CG_AddRadarEnt(cent); + } + + VectorCopy( cent->lerpOrigin, ent.origin); + VectorCopy( cent->lerpOrigin, ent.oldorigin); + AnglesToAxis( cent->lerpAngles, ent.axis ); + + ent.renderfx = RF_NOSHADOW; +/* +Ghoul2 Insert Start +*/ + + CG_SetGhoul2Info(&ent, cent); +/* +Ghoul2 Insert End +*/ + // 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]; + } + + if ( s1->eFlags & EF_SHADER_ANIM ) + { + ent.renderfx|=RF_SETANIMINDEX; + ent.skinNum = s1->frame; + //ent.shaderTime = cg.time*0.001f - s1->frame/s1->time;//NOTE: s1->time is number of frames + } + + // add to refresh list + trap_R_AddRefEntityToScene(&ent); + + // add the secondary model + if ( s1->modelindex2 ) + { + ent.skinNum = 0; + ent.hModel = cgs.gameModels[s1->modelindex2]; + if (s1->iModelScale) + { //custom model2 scale + ent.modelScale[0] = ent.modelScale[1] = ent.modelScale[2] = s1->iModelScale/100.0f; + ScaleModelAxis(&ent); + } + 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; +/* +Ghoul2 Insert Start +*/ + CG_SetGhoul2Info(&ent, cent); + +/* +Ghoul2 Insert End +*/ + // 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 +/* +Ghoul2 Insert Start +*/ + CG_SetGhoul2Info(&ent, cent); +/* +Ghoul2 Insert End +*/ + // add to refresh list + trap_R_AddRefEntityToScene(&ent); +} + + +/* +========================= +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 ]; + if ( cent->currentState.eType != ET_MOVER ) { + 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 + +=============== +*/ +void CG_CalcEntityLerpPositions( centity_t *cent ) { + qboolean goAway = qfalse; + + // 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 (cg.predictedPlayerState.m_iVehicleNum && + cg.predictedPlayerState.m_iVehicleNum == cent->currentState.number && + cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE) + { //special case for vehicle we are riding + centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + + if (veh->currentState.owner == cg.predictedPlayerState.clientNum) + { //only do this if the vehicle is pilotted by this client and predicting properly + BG_EvaluateTrajectory( ¢->currentState.pos, cg.time, cent->lerpOrigin ); + BG_EvaluateTrajectory( ¢->currentState.apos, cg.time, cent->lerpAngles ); + return; + } + } + + 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 ); + goAway = qtrue; + } + else if (cent->interpolate && + cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE) + { + CG_InterpolateEntityPosition( cent ); + goAway = qtrue; + } + 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 ); + } + +#if 0 + if (cent->hasRagOffset && cent->ragOffsetTime < cg.time) + { //take all of the offsets from last frame and normalize the total direction and add it in + vec3_t slideDir; + vec3_t preOffset; + vec3_t addedOffset; + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + trace_t tr; + + //VectorSubtract(cent->lerpOrigin, callData->bonePos, slideDir); + VectorCopy(cent->ragOffsets, slideDir); + VectorNormalize(slideDir); + + //Store it in case we want to go back + VectorCopy(cent->lerpOriginOffset, preOffset); + + //just add a little at a time + VectorMA(cent->lerpOriginOffset, 0.4f, slideDir, cent->lerpOriginOffset); + + if (VectorLength(cent->lerpOriginOffset) > 10.0f) + { //don't go too far away + VectorCopy(preOffset, cent->lerpOriginOffset); + } + else + { + //Let's trace there to make sure we can make it + VectorAdd(cent->lerpOrigin, cent->lerpOriginOffset, addedOffset); + CG_Trace(&tr, cent->lerpOrigin, playerMins, playerMaxs, addedOffset, cent->currentState.number, MASK_PLAYERSOLID); + + if (tr.startsolid || tr.allsolid || tr.fraction != 1.0f) + { //can't get there + VectorCopy(preOffset, cent->lerpOriginOffset); + } + else + { + /* + if (cent->lerpOriginOffset[2] > 4.0f) + { //don't go too far off the ground + cent->lerpOriginOffset[2] = 4.0f; + } + */ + //I guess I just don't want this happening. + cent->lerpOriginOffset[2] = 0.0f; + } + } + + //done with this bit + cent->hasRagOffset = qfalse; + VectorClear(cent->ragOffsets); + cent->ragOffsetTime = cg.time + 50; + } + + //See if we should add in the offset for ragdoll + if (cent->isRagging && ((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG))) + { + VectorAdd(cent->lerpOrigin, cent->lerpOriginOffset, cent->lerpOrigin); + } +#endif + + if (goAway) + { + return; + } + + // adjust for riding a mover if it wasn't rolled into the predicted + // player state + if ( cent->currentState.number != cg.predictedPlayerState.clientNum ) { + 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; + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { + // 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; + } + + if (cent->currentState.eType != ET_NPC) + { //do not do this for g2animents + trap_R_AddRefEntityToScene( &model ); + } + } +} + +void CG_G2Animated( centity_t *cent ); + +static void CG_FX( centity_t *cent ) +{ + vec3_t fxDir; + int efxIndex = 0; + entityState_t *s1; + const char *s; + + if (cent->miscTime > cg.time) + { + return; + } + + s1 = ¢->currentState; + + if (!s1) + { + return; + } + + if (s1->modelindex2 == FX_STATE_OFF) + { // fx not active + return; + } + + if (s1->modelindex2 < FX_STATE_ONE_SHOT_LIMIT) + { // fx is single shot + if (cent->muzzleFlashTime == s1->modelindex2) + { + return; + } + + cent->muzzleFlashTime = s1->modelindex2; + } + + cent->miscTime = cg.time + s1->speed + random() * s1->time; + + AngleVectors(s1->angles, fxDir, 0, 0); + + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + + if ( cgs.gameEffects[ s1->modelindex ] ) + { + efxIndex = cgs.gameEffects[s1->modelindex]; + } + else + { + s = CG_ConfigString( CS_EFFECTS + s1->modelindex ); + if (s && s[0]) + { + efxIndex = trap_FX_RegisterEffect(s); + cgs.gameEffects[s1->modelindex] = efxIndex; + } + } + + if (efxIndex) + { + if (s1->isPortalEnt) + { + trap_FX_PlayPortalEffectID(efxIndex, cent->lerpOrigin, fxDir, -1, -1 ); + } + else + { + trap_FX_PlayEffectID(efxIndex, cent->lerpOrigin, fxDir, -1, -1 ); + } + } + + +} + + +/* +=============== +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; + } + + if (cg.predictedPlayerState.pm_type == PM_INTERMISSION) + { //don't render anything then + if (cent->currentState.eType == ET_GENERAL || + cent->currentState.eType == ET_PLAYER || + cent->currentState.eType == ET_INVISIBLE) + { + return; + } + if ( cent->currentState.eType == ET_NPC ) + {//NPC in intermission + if ( cent->currentState.NPC_class == CLASS_VEHICLE ) + {//don't render vehicles in intermissions, allow other NPCs for scripts + return; + } + } + } + + // calculate the current origin + CG_CalcEntityLerpPositions( cent ); + + // add automatic effects + CG_EntityEffects( cent ); +/* +Ghoul2 Insert Start +*/ + + // add local sound set if any + if ( cent->currentState.soundSetIndex && cent->currentState.eType != ET_MOVER ) + { + const char *soundSet = CG_ConfigString( CS_AMBIENT_SET + cent->currentState.soundSetIndex ); + + if (soundSet && soundSet[0]) + { + trap_S_AddLocalSet(soundSet, cg.refdef.vieworg, cent->lerpOrigin, cent->currentState.number, cg.time); + } + } +/* +Ghoul2 Insert End +*/ + switch ( cent->currentState.eType ) { + default: + CG_Error( "Bad entity type: %i\n", cent->currentState.eType ); + break; + + case ET_FX: + CG_FX( cent ); + break; + + case ET_INVISIBLE: + case ET_PUSH_TRIGGER: + case ET_TELEPORT_TRIGGER: + case ET_TERRAIN: + break; + case ET_GENERAL: + 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_SPECIAL: + CG_Special( cent ); + break; + case ET_HOLOCRON: + CG_General( 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_NPC: //An entity that wants to be able to use ghoul2 humanoid (and other) anims. Like a player, but not. + CG_G2Animated( cent ); + break; + case ET_TEAM: + CG_TeamBase( cent ); + break; + case ET_BODY: + CG_General( cent ); + break; + } +} + +void CG_ManualEntityRender(centity_t *cent) +{ + CG_AddCEntity(cent); +} + +/* +=============== +CG_AddPacketEntities + +=============== +*/ +void CG_AddPacketEntities( qboolean isPortal ) { + int num; + centity_t *cent; + playerState_t *ps; + + if (isPortal) + { + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) + { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + + if (cent->currentState.isPortalEnt) + { + CG_AddCEntity( cent ); + } + } + return; + } + + // 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; + + AnglesToAxis( cg.autoAngles, cg.autoAxis ); + AnglesToAxis( cg.autoAnglesFast, cg.autoAxisFast ); + + // Reset radar entities + cg.radarEntityCount = 0; + cg.bracketedEntityCount = 0; + + // generate and add the entity from the playerstate + ps = &cg.predictedPlayerState; + + CG_CheckPlayerG2Weapons(ps, &cg_entities[cg.predictedPlayerState.clientNum]); + BG_PlayerStateToEntityState( ps, &cg_entities[cg.predictedPlayerState.clientNum].currentState, qfalse ); + + if (cg.predictedPlayerState.m_iVehicleNum) + { //add the vehicle I'm riding first + //BG_PlayerStateToEntityState( &cg.predictedVehicleState, &cg_entities[cg.predictedPlayerState.m_iVehicleNum].currentState, qfalse ); + //cg_entities[cg.predictedPlayerState.m_iVehicleNum].currentState.eType = ET_NPC; + centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + + if (veh->currentState.owner == cg.predictedPlayerState.clientNum) + { + BG_PlayerStateToEntityState( &cg.predictedVehicleState, &veh->currentState, qfalse ); + veh->currentState.eType = ET_NPC; + + veh->currentState.pos.trType = TR_INTERPOLATE; + } + CG_AddCEntity(veh); + veh->bodyHeight = cg.time; //indicate we have already been added + } + + CG_AddCEntity( &cg_entities[cg.predictedPlayerState.clientNum] ); + + /* + // lerp the non-predicted value for lightning gun origins + CG_CalcEntityLerpPositions( &cg_entities[ cg.snap->ps.clientNum ] ); + */ + //No longer have to do this. + + // add each entity sent over by the server + for ( num = 0 ; num < cg.snap->numEntities ; num++ ) { + // Don't re-add ents that have been predicted. + if (cg.snap->entities[ num ].number != cg.snap->ps.clientNum) + { + cent = &cg_entities[ cg.snap->entities[ num ].number ]; + if (cent->currentState.eType == ET_PLAYER && + cent->currentState.m_iVehicleNum) + { //add his veh first + int j = 0; + + while (j < cg.snap->numEntities) + { + if (cg.snap->entities[j].number == cent->currentState.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg.snap->entities[j].number]; + + CG_AddCEntity(veh); + veh->bodyHeight = cg.time; //indicate we have already been added + break; + } + + j++; + } + } + else if (cent->currentState.eType == ET_NPC && + cent->currentState.m_iVehicleNum && + cent->bodyHeight == cg.time) + { //never add a vehicle with a pilot, his pilot entity will get him added first. + //if we were to add the vehicle after the pilot, the pilot's bolt would lag a frame behind. + continue; + } + CG_AddCEntity( cent ); + } + } + + for(num=0;numcurrentValid) + { + CG_AddCEntity( cent ); + } + } +} + +void CG_ROFF_NotetrackCallback( centity_t *cent, const char *notetrack) +{ + int i = 0, r = 0, objectID = 0, anglesGathered = 0, posoffsetGathered = 0; + char type[256]; + char argument[512]; + char addlArg[512]; + char errMsg[256]; + char t[64]; + int addlArgs = 0; + vec3_t parsedAngles, parsedOffset, useAngles, useOrigin, forward, right, up; + + if (!cent || !notetrack) + { + return; + } + + //notetrack = "effect effects/explosion1.efx 0+0+64 0-0-1"; + + while (notetrack[i] && notetrack[i] != ' ') + { + type[i] = notetrack[i]; + i++; + } + + type[i] = '\0'; + + if (notetrack[i] != ' ') + { //didn't pass in a valid notetrack type, or forgot the argument for it + return; + } + + i++; + + while (notetrack[i] && notetrack[i] != ' ') + { + argument[r] = notetrack[i]; + r++; + i++; + } + argument[r] = '\0'; + + if (!r) + { + return; + } + + if (notetrack[i] == ' ') + { //additional arguments... + addlArgs = 1; + + i++; + r = 0; + while (notetrack[i]) + { + addlArg[r] = notetrack[i]; + r++; + i++; + } + addlArg[r] = '\0'; + } + + if (strcmp(type, "effect") == 0) + { + if (!addlArgs) + { + //sprintf(errMsg, "Offset position argument for 'effect' type is invalid."); + //goto functionend; + VectorClear(parsedOffset); + goto defaultoffsetposition; + } + + i = 0; + + while (posoffsetGathered < 3) + { + r = 0; + while (addlArg[i] && addlArg[i] != '+' && addlArg[i] != ' ') + { + t[r] = addlArg[i]; + r++; + i++; + } + t[r] = '\0'; + i++; + if (!r) + { //failure.. + //sprintf(errMsg, "Offset position argument for 'effect' type is invalid."); + //goto functionend; + VectorClear(parsedOffset); + i = 0; + goto defaultoffsetposition; + } + parsedOffset[posoffsetGathered] = atof(t); + posoffsetGathered++; + } + + if (posoffsetGathered < 3) + { + Com_sprintf(errMsg, sizeof(errMsg), "Offset position argument for 'effect' type is invalid."); + goto functionend; + } + + i--; + + if (addlArg[i] != ' ') + { + addlArgs = 0; + } + +defaultoffsetposition: + + objectID = trap_FX_RegisterEffect(argument); + + if (objectID) + { + if (addlArgs) + { //if there is an additional argument for an effect it is expected to be XANGLE-YANGLE-ZANGLE + i++; + while (anglesGathered < 3) + { + r = 0; + while (addlArg[i] && addlArg[i] != '-') + { + t[r] = addlArg[i]; + r++; + i++; + } + t[r] = '\0'; + i++; + + if (!r) + { //failed to get a new part of the vector + anglesGathered = 0; + break; + } + + parsedAngles[anglesGathered] = atof(t); + anglesGathered++; + } + + if (anglesGathered) + { + VectorCopy(parsedAngles, useAngles); + } + else + { //failed to parse angles from the extra argument provided.. + VectorCopy(cent->lerpAngles, useAngles); + } + } + else + { //if no constant angles, play in direction entity is facing + VectorCopy(cent->lerpAngles, useAngles); + } + + AngleVectors(useAngles, forward, right, up); + + VectorCopy(cent->lerpOrigin, useOrigin); + + //forward + useOrigin[0] += forward[0]*parsedOffset[0]; + useOrigin[1] += forward[1]*parsedOffset[0]; + useOrigin[2] += forward[2]*parsedOffset[0]; + + //right + useOrigin[0] += right[0]*parsedOffset[1]; + useOrigin[1] += right[1]*parsedOffset[1]; + useOrigin[2] += right[2]*parsedOffset[1]; + + //up + useOrigin[0] += up[0]*parsedOffset[2]; + useOrigin[1] += up[1]*parsedOffset[2]; + useOrigin[2] += up[2]*parsedOffset[2]; + + trap_FX_PlayEffectID(objectID, useOrigin, useAngles, -1, -1); + } + } + else if (strcmp(type, "sound") == 0) + { + objectID = trap_S_RegisterSound(argument); + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_BODY, objectID); + } + else if (strcmp(type, "loop") == 0) + { //handled server-side + return; + } + //else if ... + else + { + if (type[0]) + { + Com_Printf("^3Warning: \"%s\" is an invalid ROFF notetrack function\n", type); + } + else + { + Com_Printf("^3Warning: Notetrack is missing function and/or arguments\n"); + } + } + + return; + +functionend: + Com_Printf("^3Type-specific notetrack error: %s\n", errMsg); + return; +} + +void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ) +{ + vec3_t rot={0,0,0}; + int vec[3]; + int axis, i; + addpolyArgStruct_t apArgs; + + memset (&apArgs, 0, sizeof(apArgs)); + + for ( axis = 0, vec[0] = 0, vec[1] = 1, vec[2] = 2; axis < 3; axis++, vec[0]++, vec[1]++, vec[2]++ ) + { + for ( i = 0; i < 3; i++ ) + { + if ( vec[i] > 2 ) + { + vec[i] = 0; + } + } + + apArgs.p[0][vec[1]] = mins[vec[1]]; + apArgs.p[0][vec[2]] = mins[vec[2]]; + + apArgs.p[1][vec[1]] = mins[vec[1]]; + apArgs.p[1][vec[2]] = maxs[vec[2]]; + + apArgs.p[2][vec[1]] = maxs[vec[1]]; + apArgs.p[2][vec[2]] = maxs[vec[2]]; + + apArgs.p[3][vec[1]] = maxs[vec[1]]; + apArgs.p[3][vec[2]] = mins[vec[2]]; + + //- face + apArgs.p[0][vec[0]] = apArgs.p[1][vec[0]] = apArgs.p[2][vec[0]] = apArgs.p[3][vec[0]] = mins[vec[0]]; + + apArgs.numVerts = 4; + apArgs.alpha1 = apArgs.alpha2 = alpha; + VectorCopy( color, apArgs.rgb1 ); + VectorCopy( color, apArgs.rgb2 ); + VectorCopy( rot, apArgs.rotationDelta ); + apArgs.killTime = cg.frametime; + apArgs.shader = cgs.media.solidWhite; + + trap_FX_AddPoly( &apArgs ); + + //+ face + apArgs.p[0][vec[0]] = apArgs.p[1][vec[0]] = apArgs.p[2][vec[0]] = apArgs.p[3][vec[0]] = maxs[vec[0]]; + + trap_FX_AddPoly( &apArgs ); + } +} diff --git a/code/cgame/cg_event.c b/code/cgame/cg_event.c new file mode 100644 index 0000000..91f7997 --- /dev/null +++ b/code/cgame/cg_event.c @@ -0,0 +1,3557 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_event.c -- handle entity events at snapshot or playerstate transitions + +#include "cg_local.h" +#include "fx_local.h" +#include "../ui/ui_shared.h" +#include "../ui/ui_public.h" + +// for the voice chats +#include "../../ui/menudef.h" + +#include "../ghoul2/G2.h" +//========================================================================== + +extern qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum ); +extern qboolean CG_VehicleWeaponImpact( centity_t *cent ); +extern qboolean CG_InFighter( void ); +extern qboolean CG_InATST( void ); +extern int cg_saberFlashTime; +extern vec3_t cg_saberFlashPos; +extern char *showPowersName[]; + +extern int cg_siegeDeathTime; +extern int cg_siegeDeathDelay; +extern int cg_vehicleAmmoWarning; +extern int cg_vehicleAmmoWarningTime; + +//I know, not siege, but... +typedef enum +{ + TAUNT_TAUNT = 0, + TAUNT_BOW, + TAUNT_MEDITATE, + TAUNT_FLOURISH, + TAUNT_GLOAT +}; +/* +=================== +CG_PlaceString + +Also called by scoreboard drawing +=================== +*/ +const char *CG_PlaceString( int rank ) { + static char str[64]; + char *s, *t; + // number extenstions, eg 1st, 2nd, 3rd, 4th etc. + // note that the rules are different for french, but by changing the required strip strings they seem to work + char sST[10]; + char sND[10]; + char sRD[10]; + char sTH[10]; + char sTiedFor[64]; // german is much longer, super safe... + + trap_SP_GetStringTextString("MP_INGAME_NUMBER_ST",sST, sizeof(sST) ); + trap_SP_GetStringTextString("MP_INGAME_NUMBER_ND",sND, sizeof(sND) ); + trap_SP_GetStringTextString("MP_INGAME_NUMBER_RD",sRD, sizeof(sRD) ); + trap_SP_GetStringTextString("MP_INGAME_NUMBER_TH",sTH, sizeof(sTH) ); + trap_SP_GetStringTextString("MP_INGAME_TIED_FOR" ,sTiedFor,sizeof(sTiedFor) ); + strcat(sTiedFor," "); // save worrying about translators adding spaces or not + + if ( rank & RANK_TIED_FLAG ) { + rank &= ~RANK_TIED_FLAG; + t = sTiedFor;//"Tied for "; + } else { + t = ""; + } + + if ( rank == 1 ) { + s = va("1%s",sST);//S_COLOR_BLUE "1st" S_COLOR_WHITE; // draw in blue + } else if ( rank == 2 ) { + s = va("2%s",sND);//S_COLOR_RED "2nd" S_COLOR_WHITE; // draw in red + } else if ( rank == 3 ) { + s = va("3%s",sRD);//S_COLOR_YELLOW "3rd" S_COLOR_WHITE; // draw in yellow + } else if ( rank == 11 ) { + s = va("11%s",sTH); + } else if ( rank == 12 ) { + s = va("12%s",sTH); + } else if ( rank == 13 ) { + s = va("13%s",sTH); + } else if ( rank % 10 == 1 ) { + s = va("%i%s", rank,sST); + } else if ( rank % 10 == 2 ) { + s = va("%i%s", rank,sND); + } else if ( rank % 10 == 3 ) { + s = va("%i%s", rank,sRD); + } else { + s = va("%i%s", rank,sTH); + } + + Com_sprintf( str, sizeof( str ), "%s%s", t, s ); + return str; +} + +qboolean CG_ThereIsAMaster(void); + +/* +============= +CG_Obituary +============= +*/ +static void CG_Obituary( entityState_t *ent ) { + int mod; + int target, attacker; + char *message; + const char *targetInfo; + const char *attackerInfo; + char targetName[32]; + char attackerName[32]; + gender_t gender; + clientInfo_t *ci; + + + 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); + strcat( targetName, S_COLOR_WHITE ); + + // check for single client messages + + switch( mod ) { + case MOD_SUICIDE: + case MOD_FALLING: + case MOD_CRUSH: + case MOD_WATER: + case MOD_SLIME: + case MOD_LAVA: + case MOD_TRIGGER_HURT: + message = "DIED_GENERIC"; + break; + case MOD_TARGET_LASER: + message = "DIED_LASER"; + break; + default: + message = NULL; + break; + } + + // Attacker killed themselves. Ridicule them for it. + if (attacker == target) { + gender = ci->gender; + switch (mod) { + case MOD_BRYAR_PISTOL: + case MOD_BRYAR_PISTOL_ALT: + case MOD_BLASTER: + case MOD_TURBLAST: + case MOD_DISRUPTOR: + case MOD_DISRUPTOR_SPLASH: + case MOD_DISRUPTOR_SNIPER: + case MOD_BOWCASTER: + case MOD_REPEATER: + case MOD_REPEATER_ALT: + case MOD_FLECHETTE: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_SHOT_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_SHOT_GENDERLESS"; + else + message = "SUICIDE_SHOT_MALE"; + break; + case MOD_REPEATER_ALT_SPLASH: + case MOD_FLECHETTE_ALT_SPLASH: + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: + case MOD_ROCKET_HOMING: + case MOD_ROCKET_HOMING_SPLASH: + case MOD_THERMAL: + case MOD_THERMAL_SPLASH: + case MOD_TRIP_MINE_SPLASH: + case MOD_TIMED_MINE_SPLASH: + case MOD_DET_PACK_SPLASH: + case MOD_VEHICLE: + case MOD_CONC: + case MOD_CONC_ALT: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_EXPLOSIVES_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_EXPLOSIVES_GENDERLESS"; + else + message = "SUICIDE_EXPLOSIVES_MALE"; + break; + case MOD_DEMP2: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_ELECTROCUTED_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_ELECTROCUTED_GENDERLESS"; + else + message = "SUICIDE_ELECTROCUTED_MALE"; + break; + case MOD_FALLING: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_FALLDEATH_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_FALLDEATH_GENDERLESS"; + else + message = "SUICIDE_FALLDEATH_MALE"; + break; + default: + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_GENERICDEATH_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_GENERICDEATH_GENDERLESS"; + else + message = "SUICIDE_GENERICDEATH_MALE"; + break; + } + } + + if (target != attacker && target < MAX_CLIENTS && attacker < MAX_CLIENTS) + { + goto clientkilled; + } + + if (message) { + gender = ci->gender; + + if (!message[0]) + { + if ( gender == GENDER_FEMALE ) + message = "SUICIDE_GENERICDEATH_FEMALE"; + else if ( gender == GENDER_NEUTER ) + message = "SUICIDE_GENERICDEATH_GENDERLESS"; + else + message = "SUICIDE_GENERICDEATH_MALE"; + } + message = (char *)CG_GetStringEdString("MP_INGAME", message); + + CG_Printf( "%s %s\n", targetName, message); + return; + } + +clientkilled: + + // check for kill messages from the current clientNum + if ( attacker == cg.snap->ps.clientNum ) { + char *s; + + if ( cgs.gametype < GT_TEAM && cgs.gametype != GT_DUEL && cgs.gametype != GT_POWERDUEL ) { + if (cgs.gametype == GT_JEDIMASTER && + attacker < MAX_CLIENTS && + !ent->isJediMaster && + !cg.snap->ps.isJediMaster && + CG_ThereIsAMaster()) + { + char part1[512]; + char part2[512]; + trap_SP_GetStringTextString("MP_INGAME_KILLED_MESSAGE", part1, sizeof(part1)); + trap_SP_GetStringTextString("MP_INGAME_JMKILLED_NOTJM", part2, sizeof(part2)); + s = va("%s %s\n%s\n", part1, targetName, part2); + } + else if (cgs.gametype == GT_JEDIMASTER && + attacker < MAX_CLIENTS && + !ent->isJediMaster && + !cg.snap->ps.isJediMaster) + { //no JM, saber must be out + char part1[512]; + trap_SP_GetStringTextString("MP_INGAME_KILLED_MESSAGE", part1, sizeof(part1)); + /* + kmsg1 = "for 0 points.\nGo for the saber!"; + strcpy(part2, kmsg1); + + s = va("%s %s %s\n", part1, targetName, part2); + */ + s = va("%s %s\n", part1, targetName); + } + else if (cgs.gametype == GT_POWERDUEL) + { + s = ""; + } + else + { + char sPlaceWith[256]; + char sKilledStr[256]; + trap_SP_GetStringTextString("MP_INGAME_PLACE_WITH", sPlaceWith, sizeof(sPlaceWith)); + trap_SP_GetStringTextString("MP_INGAME_KILLED_MESSAGE", sKilledStr, sizeof(sKilledStr)); + + s = va("%s %s.\n%s %s %i.", sKilledStr, targetName, + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + sPlaceWith, + cg.snap->ps.persistant[PERS_SCORE] ); + } + } else { + char sKilledStr[256]; + trap_SP_GetStringTextString("MP_INGAME_KILLED_MESSAGE", sKilledStr, sizeof(sKilledStr)); + s = va("%s %s", sKilledStr, targetName ); + } + //if (!(cg_singlePlayerActive.integer && cg_cameraOrbit.integer)) { + CG_CenterPrint( s, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + //} + // 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 ) ); + } + } + + if ( attacker != ENTITYNUM_WORLD ) { + switch (mod) { + case MOD_STUN_BATON: + message = "KILLED_STUN"; + break; + case MOD_MELEE: + message = "KILLED_MELEE"; + break; + case MOD_SABER: + message = "KILLED_SABER"; + break; + case MOD_BRYAR_PISTOL: + case MOD_BRYAR_PISTOL_ALT: + message = "KILLED_BRYAR"; + break; + case MOD_BLASTER: + message = "KILLED_BLASTER"; + break; + case MOD_TURBLAST: + message = "KILLED_BLASTER"; + break; + case MOD_DISRUPTOR: + case MOD_DISRUPTOR_SPLASH: + message = "KILLED_DISRUPTOR"; + break; + case MOD_DISRUPTOR_SNIPER: + message = "KILLED_DISRUPTORSNIPE"; + break; + case MOD_BOWCASTER: + message = "KILLED_BOWCASTER"; + break; + case MOD_REPEATER: + message = "KILLED_REPEATER"; + break; + case MOD_REPEATER_ALT: + case MOD_REPEATER_ALT_SPLASH: + message = "KILLED_REPEATERALT"; + break; + case MOD_DEMP2: + case MOD_DEMP2_ALT: + message = "KILLED_DEMP2"; + break; + case MOD_FLECHETTE: + message = "KILLED_FLECHETTE"; + break; + case MOD_FLECHETTE_ALT_SPLASH: + message = "KILLED_FLECHETTE_MINE"; + break; + case MOD_ROCKET: + case MOD_ROCKET_SPLASH: + message = "KILLED_ROCKET"; + break; + case MOD_ROCKET_HOMING: + case MOD_ROCKET_HOMING_SPLASH: + message = "KILLED_ROCKET_HOMING"; + break; + case MOD_THERMAL: + case MOD_THERMAL_SPLASH: + message = "KILLED_THERMAL"; + break; + case MOD_TRIP_MINE_SPLASH: + message = "KILLED_TRIPMINE"; + break; + case MOD_TIMED_MINE_SPLASH: + message = "KILLED_TRIPMINE_TIMED"; + break; + case MOD_DET_PACK_SPLASH: + message = "KILLED_DETPACK"; + break; + case MOD_VEHICLE: + case MOD_CONC: + case MOD_CONC_ALT: + message = "KILLED_GENERIC"; + break; + case MOD_FORCE_DARK: + message = "KILLED_DARKFORCE"; + break; + case MOD_SENTRY: + message = "KILLED_SENTRY"; + break; + case MOD_TELEFRAG: + message = "KILLED_TELEFRAG"; + break; + case MOD_CRUSH: + message = "KILLED_GENERIC";//"KILLED_FORCETOSS"; + break; + case MOD_FALLING: + message = "KILLED_FORCETOSS"; + break; + case MOD_TRIGGER_HURT: + message = "KILLED_GENERIC";//"KILLED_FORCETOSS"; + break; + default: + message = "KILLED_GENERIC"; + break; + } + + if (message) { + message = (char *)CG_GetStringEdString("MP_INGAME", message); + + CG_Printf( "%s %s %s\n", + targetName, message, attackerName); + return; + } + } + + // we don't know what it was + CG_Printf( "%s %s\n", targetName, (char *)CG_GetStringEdString("MP_INGAME", "DIED_GENERIC") ); +} + +//========================================================================== + +void CG_ToggleBinoculars(centity_t *cent, int forceZoom) +{ + if (cent->currentState.number != cg.snap->ps.clientNum) + { + return; + } + + if (cg.snap->ps.weaponstate != WEAPON_READY) + { //So we can't fool it and reactivate while switching to the saber or something. + return; + } + + /* + if (cg.snap->ps.weapon == WP_SABER) + { //No. + return; + } + */ + + if (forceZoom) + { + if (forceZoom == 2) + { + cg.snap->ps.zoomMode = 0; + } + else if (forceZoom == 1) + { + cg.snap->ps.zoomMode = 2; + } + } + + if (cg.snap->ps.zoomMode == 0) + { + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.media.zoomStart ); + } + else if (cg.snap->ps.zoomMode == 2) + { + trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.media.zoomEnd ); + } +} + +//set the local timing bar +extern int cg_genericTimerBar; +extern int cg_genericTimerDur; +extern vec4_t cg_genericTimerColor; +void CG_LocalTimingBar(int startTime, int duration) +{ + cg_genericTimerBar = startTime + duration; + cg_genericTimerDur = duration; + + cg_genericTimerColor[0] = 1.0f; + cg_genericTimerColor[1] = 1.0f; + cg_genericTimerColor[2] = 0.0f; + cg_genericTimerColor[3] = 1.0f; +} + +/* +=============== +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 { + item = BG_FindItemForHoldable( itemNum ); + } + } + + switch ( itemNum ) { + default: + case HI_NONE: + //trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.useNothingSound ); + break; + + case HI_BINOCULARS: + CG_ToggleBinoculars(cent, es->eventParm); + break; + + case HI_SEEKER: + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.deploySeeker ); + break; + + case HI_SHIELD: + case HI_SENTRY_GUN: + break; + +// case HI_MEDKIT: + case HI_MEDPAC: + case HI_MEDPAC_BIG: + clientNum = cent->currentState.clientNum; + if ( clientNum >= 0 && clientNum < MAX_CLIENTS ) { + ci = &cgs.clientinfo[ clientNum ]; + ci->medkitUsageTime = cg.time; + } + //Different sound for big bacta? + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.medkitSound ); + break; + case HI_JETPACK: + break; //Do something? + case HI_HEALTHDISP: + //CG_LocalTimingBar(cg.time, TOSS_DEBOUNCE_TIME); + break; + case HI_AMMODISP: + //CG_LocalTimingBar(cg.time, TOSS_DEBOUNCE_TIME); + break; + case HI_EWEB: + break; + case HI_CLOAK: + break; //Do something? + } + + if (cg.snap && cg.snap->ps.clientNum == cent->currentState.number && itemNum != HI_BINOCULARS && + itemNum != HI_JETPACK && itemNum != HI_HEALTHDISP && itemNum != HI_AMMODISP && itemNum != HI_CLOAK && itemNum != HI_EWEB) + { //if not using binoculars/jetpack/dispensers/cloak, we just used that item up, so switch + BG_CycleInven(&cg.snap->ps, 1); + cg.itemSelect = -1; //update the client-side selection display + } +} + + +/* +================ +CG_ItemPickup + +A new item was picked up this frame +================ +*/ +static void CG_ItemPickup( int itemNum ) { + cg.itemPickup = itemNum; + cg.itemPickupTime = cg.time; + cg.itemPickupBlendTime = cg.time; + // see if it should be the grabbed weapon + if ( cg.snap && bg_itemlist[itemNum].giType == IT_WEAPON ) { + + // 0 == no switching + // 1 == automatically switch to best SAFE weapon + // 2 == automatically switch to best weapon, safe or otherwise + // 3 == if not saber, automatically switch to best weapon, safe or otherwise + + if (0 == cg_autoswitch.integer) + { + // don't switch + } + else if ( cg_autoswitch.integer == 1) + { //only autoselect if not explosive ("safe") + if (bg_itemlist[itemNum].giTag != WP_TRIP_MINE && + bg_itemlist[itemNum].giTag != WP_DET_PACK && + bg_itemlist[itemNum].giTag != WP_THERMAL && + bg_itemlist[itemNum].giTag != WP_ROCKET_LAUNCHER && + bg_itemlist[itemNum].giTag > cg.snap->ps.weapon && + cg.snap->ps.weapon != WP_SABER) + { + if (!cg.snap->ps.emplacedIndex) + { + cg.weaponSelectTime = cg.time; + } + cg.weaponSelect = bg_itemlist[itemNum].giTag; + } + } + else if ( cg_autoswitch.integer == 2) + { //autoselect if better + if (bg_itemlist[itemNum].giTag > cg.snap->ps.weapon && + cg.snap->ps.weapon != WP_SABER) + { + if (!cg.snap->ps.emplacedIndex) + { + cg.weaponSelectTime = cg.time; + } + cg.weaponSelect = bg_itemlist[itemNum].giTag; + } + } + /* + else if ( cg_autoswitch.integer == 3) + { //autoselect if better and not using the saber as a weapon + if (bg_itemlist[itemNum].giTag > cg.snap->ps.weapon && + cg.snap->ps.weapon != WP_SABER) + { + if (!cg.snap->ps.emplacedIndex) + { + cg.weaponSelectTime = cg.time; + } + cg.weaponSelect = bg_itemlist[itemNum].giTag; + } + } + */ + //No longer required - just not switching ever if using saber + } + + //rww - print pickup messages + if (bg_itemlist[itemNum].classname && bg_itemlist[itemNum].classname[0] && + (bg_itemlist[itemNum].giType != IT_TEAM || (bg_itemlist[itemNum].giTag != PW_REDFLAG && bg_itemlist[itemNum].giTag != PW_BLUEFLAG)) ) + { //don't print messages for flags, they have their own pickup event broadcasts + char text[1024]; + char upperKey[1024]; + + strcpy(upperKey, bg_itemlist[itemNum].classname); + + if ( trap_SP_GetStringTextString( va("SP_INGAME_%s",Q_strupr(upperKey)), text, sizeof( text ))) + { + Com_Printf("%s %s\n", CG_GetStringEdString("MP_INGAME", "PICKUPLINE"), text); + } + else + { + Com_Printf("%s %s\n", CG_GetStringEdString("MP_INGAME", "PICKUPLINE"), bg_itemlist[itemNum].classname); + } + } +} + + +/* +================ +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.wav"; + } else if ( health < 50 ) { + snd = "*pain50.wav"; + } else if ( health < 75 ) { + snd = "*pain75.wav"; + } else { + snd = "*pain100.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; +} + +extern qboolean BG_GetRootSurfNameWithVariant( void *ghoul2, const char *rootSurfName, char *returnSurfName, int returnSize ); +void CG_ReattachLimb(centity_t *source) +{ + clientInfo_t *ci = NULL; + + if ( source->currentState.number >= MAX_CLIENTS ) + { + ci = source->npcClient; + } + else + { + ci = &cgs.clientinfo[source->currentState.number]; + } + if ( ci ) + {//re-apply the skin + if ( ci->torsoSkin > 0 ) + { + trap_G2API_SetSkin(source->ghoul2,0,ci->torsoSkin,ci->torsoSkin); + } + } + + /* + char *limbName; + char *stubCapName; + int i = G2_MODELPART_HEAD; + + //rww NOTE: Assumes G2_MODELPART_HEAD is first and G2_MODELPART_RLEG is last + while (i <= G2_MODELPART_RLEG) + { + if (source->torsoBolt & (1 << (i-10))) + { + switch (i) + { + case G2_MODELPART_HEAD: + limbName = "head"; + stubCapName = "torso_cap_head"; + break; + case G2_MODELPART_WAIST: + limbName = "torso"; + stubCapName = "hips_cap_torso"; + break; + case G2_MODELPART_LARM: + limbName = "l_arm"; + stubCapName = "torso_cap_l_arm"; + break; + case G2_MODELPART_RARM: + limbName = "r_arm"; + stubCapName = "torso_cap_r_arm"; + break; + case G2_MODELPART_RHAND: + limbName = "r_hand"; + stubCapName = "r_arm_cap_r_hand"; + break; + case G2_MODELPART_LLEG: + limbName = "l_leg"; + stubCapName = "hips_cap_l_leg"; + break; + case G2_MODELPART_RLEG: + limbName = "r_leg"; + stubCapName = "hips_cap_r_leg"; + break; + default: + source->torsoBolt = 0; + source->ghoul2weapon = NULL; + return; + } + + trap_G2API_SetSurfaceOnOff(source->ghoul2, limbName, 0); + trap_G2API_SetSurfaceOnOff(source->ghoul2, stubCapName, 0x00000100); + } + i++; + } + */ + source->torsoBolt = 0; + + source->ghoul2weapon = NULL; +} + +const char *CG_TeamName(int team) +{ + if (team==TEAM_RED) + return "RED"; + else if (team==TEAM_BLUE) + return "BLUE"; + else if (team==TEAM_SPECTATOR) + return "SPECTATOR"; + return "FREE"; +} + +void CG_PrintCTFMessage(clientInfo_t *ci, const char *teamName, int ctfMessage) +{ + char printMsg[1024]; + char *refName = NULL; + const char *psStringEDString = NULL; + + switch (ctfMessage) + { + case CTFMESSAGE_FRAGGED_FLAG_CARRIER: + refName = "FRAGGED_FLAG_CARRIER"; + break; + case CTFMESSAGE_FLAG_RETURNED: + refName = "FLAG_RETURNED"; + break; + case CTFMESSAGE_PLAYER_RETURNED_FLAG: + refName = "PLAYER_RETURNED_FLAG"; + break; + case CTFMESSAGE_PLAYER_CAPTURED_FLAG: + refName = "PLAYER_CAPTURED_FLAG"; + break; + case CTFMESSAGE_PLAYER_GOT_FLAG: + refName = "PLAYER_GOT_FLAG"; + break; + default: + return; + } + + psStringEDString = CG_GetStringEdString("MP_INGAME", refName); + + if (!psStringEDString || !psStringEDString[0]) + { + return; + } + + if (teamName && teamName[0]) + { + const char *f = strstr(psStringEDString, "%s"); + + if (f) + { + int strLen = 0; + int i = 0; + + if (ci) + { + Com_sprintf(printMsg, sizeof(printMsg), "%s ", ci->name); + strLen = strlen(printMsg); + } + + while (psStringEDString[i] && i < 512) + { + if (psStringEDString[i] == '%' && + psStringEDString[i+1] == 's') + { + printMsg[strLen] = '\0'; + Q_strcat(printMsg, sizeof(printMsg), teamName); + strLen = strlen(printMsg); + + i++; + } + else + { + printMsg[strLen] = psStringEDString[i]; + strLen++; + } + + i++; + } + + printMsg[strLen] = '\0'; + + goto doPrint; + } + } + + if (ci) + { + Com_sprintf(printMsg, sizeof(printMsg), "%s %s", ci->name, psStringEDString); + } + else + { + Com_sprintf(printMsg, sizeof(printMsg), "%s", psStringEDString); + } + +doPrint: + Com_Printf("%s\n", printMsg); +} + +void CG_GetCTFMessageEvent(entityState_t *es) +{ + int clIndex = es->trickedentindex; + int teamIndex = es->trickedentindex2; + clientInfo_t *ci = NULL; + const char *teamName = NULL; + + if (clIndex < MAX_CLIENTS) + { + ci = &cgs.clientinfo[clIndex]; + } + + if (teamIndex < 50) + { + teamName = CG_TeamName(teamIndex); + } + + if (!ci) + { + return; + } + + CG_PrintCTFMessage(ci, teamName, es->eventParm); +} + +#include "../namespace_begin.h" +qboolean BG_InKnockDownOnly( int anim ); +#include "../namespace_end.h" + +//JLFRUMBLE +#ifdef _XBOX +extern void FF_XboxDamage(int damage, float xpos); +#endif + +void DoFall(centity_t *cent, entityState_t *es, int clientNum) +{ + int delta = es->eventParm; + + if (cent->currentState.eFlags & EF_DEAD) + { //corpses crack into the ground ^_^ + if (delta > 25) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.fallSound ); + } + else + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( "sound/movers/objects/objectHit.wav" ) ); + } + } + else if (BG_InKnockDownOnly(es->legsAnim)) + { + if (delta > 14) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.fallSound ); + } + else + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( "sound/movers/objects/objectHit.wav" ) ); + } + } + else if (delta > 50) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.fallSound ); + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, "*land1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + } + else if (delta > 44) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.fallSound ); + trap_S_StartSound( NULL, cent->currentState.number, CHAN_VOICE, + CG_CustomSound( cent->currentState.number, "*land1.wav" ) ); + cent->pe.painTime = cg.time; // don't play a pain sound right after this + } + else + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.landSound ); + } + + if ( clientNum == cg.predictedPlayerState.clientNum ) + { + // smooth landing z changes + cg.landChange = -delta; + if (cg.landChange > 32) + { + cg.landChange = 32; + } + if (cg.landChange < -32) + { + cg.landChange = -32; + } + cg.landTime = cg.time; + } +//JLFRUMBLE +#ifdef _XBOX + if ( cent->playerState) + { + + if (BG_InKnockDownOnly(es->legsAnim)) + { + if (delta > 14) + { + FF_XboxDamage(20, 0); + } + else + { + FF_XboxDamage(14, 0); + } + return; + } + if ( delta > 50) + FF_XboxDamage(50, 0); + else + FF_XboxDamage(delta, 0); +/* else if (delta > 44) + FF_XboxDamage(44, 0); + else + FF_XboxDamage(20, 0); +*/ + } +#endif + + + +} + +int CG_InClientBitflags(entityState_t *ent, int client) +{ + int checkIn; + int sub = 0; + + if (client > 47) + { + checkIn = ent->trickedentindex4; + sub = 48; + } + else if (client > 31) + { + checkIn = ent->trickedentindex3; + sub = 32; + } + else if (client > 15) + { + checkIn = ent->trickedentindex2; + sub = 16; + } + else + { + checkIn = ent->trickedentindex; + } + + if (checkIn & (1 << (client-sub))) + { + return 1; + } + + return 0; +} + +void CG_PlayDoorLoopSound( centity_t *cent ); +void CG_PlayDoorSound( centity_t *cent, int type ); + +void CG_TryPlayCustomSound( vec3_t origin, int entityNum, int channel, const char *soundName ) +{ + sfxHandle_t cSound = CG_CustomSound(entityNum, soundName); + + if (cSound <= 0) + { + return; + } + + trap_S_StartSound(origin, entityNum, channel, cSound); +} + +void CG_G2MarkEvent(entityState_t *es) +{ + //es->origin should be the hit location of the projectile, + //whereas es->origin2 is the predicted position of the + //projectile. (based on the trajectory upon impact) -rww + centity_t *pOwner = &cg_entities[es->otherEntityNum]; + vec3_t startPoint; + float size = 0.0f; + qhandle_t shader = 0; + + if (!pOwner->ghoul2) + { //can't do anything then... + return; + } + + //es->eventParm being non-0 means to do a special trace check + //first. This will give us an impact right at the surface to + //project the mark on. Typically this is used for radius + //explosions and such, where the source position could be + //way outside of model space. + if (es->eventParm) + { + trace_t tr; + int ignore = ENTITYNUM_NONE; + + CG_G2Trace(&tr, es->origin, NULL, NULL, es->origin2, ignore, MASK_PLAYERSOLID); + + if (tr.entityNum != es->otherEntityNum) + { //try again if we hit an ent but not the one we wanted. + //CG_TestLine(es->origin, es->origin2, 2000, 0x0000ff, 1); + if (tr.entityNum < ENTITYNUM_WORLD) + { + ignore = tr.entityNum; + CG_G2Trace(&tr, es->origin, NULL, NULL, es->origin2, ignore, MASK_PLAYERSOLID); + if (tr.entityNum != es->otherEntityNum) + { //try extending the trace a bit.. or not + /* + vec3_t v; + + VectorSubtract(es->origin2, es->origin, v); + VectorScale(v, 64.0f, v); + VectorAdd(es->origin2, v, es->origin2); + + CG_G2Trace(&tr, es->origin, NULL, NULL, es->origin2, ignore, MASK_PLAYERSOLID); + if (tr.entityNum != es->otherEntityNum) + { + return; + } + */ + //didn't manage to collide with the desired person. No mark will be placed then. + return; + } + } + } + + //otherwise we now have a valid starting point. + VectorCopy(tr.endpos, startPoint); + } + else + { + VectorCopy(es->origin, startPoint); + } + + if ( (es->eFlags&EF_JETPACK_ACTIVE) ) + {// a vehicle weapon, make it a larger size mark + //OR base this on the size of the thing you hit? + if ( g_vehWeaponInfo[es->otherEntityNum2].fG2MarkSize ) + { + size = flrand( 0.6f, 1.4f )*g_vehWeaponInfo[es->otherEntityNum2].fG2MarkSize; + } + else + { + size = flrand( 32.0f, 72.0f ); + } + //specify mark shader in vehWeapon file + if ( g_vehWeaponInfo[es->otherEntityNum2].iG2MarkShaderHandle ) + {//have one we want to use instead of defaults + shader = g_vehWeaponInfo[es->otherEntityNum2].iG2MarkShaderHandle; + } + } + switch(es->weapon) + { + case WP_BRYAR_PISTOL: + case WP_CONCUSSION: + case WP_BRYAR_OLD: + case WP_BLASTER: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_TURRET: + if ( !size ) + { + size = 4.0f; + } + if ( !shader ) + { + shader = cgs.media.bdecal_bodyburn1; + } + CG_AddGhoul2Mark(shader, size, + startPoint, es->origin2, es->owner, pOwner->lerpOrigin, + pOwner->lerpAngles[YAW], pOwner->ghoul2, + pOwner->modelScale, Q_irand(10000, 20000)); + break; + case WP_ROCKET_LAUNCHER: + case WP_THERMAL: + if ( !size ) + { + size = 24.0f; + } + if ( !shader ) + { + shader = cgs.media.bdecal_burn1; + } + CG_AddGhoul2Mark(shader, size, + startPoint, es->origin2, es->owner, pOwner->lerpOrigin, + pOwner->lerpAngles[YAW], pOwner->ghoul2, + pOwner->modelScale, Q_irand(10000, 20000)); + break; + /* + case WP_FLECHETTE: + CG_AddGhoul2Mark(cgs.media.bdecal_bodyburn1, flrand(0.5f, 1.0f), + startPoint, es->origin2, es->owner, pOwner->lerpOrigin, + pOwner->lerpAngles[YAW], pOwner->ghoul2, + pOwner->modelScale); + break; + */ + //Issues with small scale? + default: + break; + } +} + +void CG_CalcVehMuzzle(Vehicle_t *pVeh, centity_t *ent, int muzzleNum) +{ + mdxaBone_t boltMatrix; + vec3_t vehAngles; + + assert(pVeh); + + if (pVeh->m_iMuzzleTime[muzzleNum] == cg.time) + { //already done for this frame, don't need to do it again + return; + } + //Uh... how about we set this, hunh...? :) + pVeh->m_iMuzzleTime[muzzleNum] = cg.time; + + VectorCopy( ent->lerpAngles, vehAngles ); + if ( pVeh->m_pVehicleInfo ) + { + if (pVeh->m_pVehicleInfo->type == VH_ANIMAL + ||pVeh->m_pVehicleInfo->type == VH_WALKER) + { + vehAngles[PITCH] = vehAngles[ROLL] = 0.0f; + } + else if (pVeh->m_pVehicleInfo->type == VH_SPEEDER) + { + vehAngles[PITCH] = 0.0f; + } + } + trap_G2API_GetBoltMatrix_NoRecNoRot(ent->ghoul2, 0, pVeh->m_iMuzzleTag[muzzleNum], &boltMatrix, vehAngles, + ent->lerpOrigin, cg.time, NULL, ent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, pVeh->m_vMuzzlePos[muzzleNum]); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, pVeh->m_vMuzzleDir[muzzleNum]); +} + +//corresponds to G_VehMuzzleFireFX -rww +void CG_VehMuzzleFireFX(centity_t *veh, entityState_t *broadcaster) +{ + Vehicle_t *pVeh = veh->m_pVehicle; + int curMuz = 0, muzFX = 0; + + if (!pVeh || !veh->ghoul2) + { + return; + } + + for ( curMuz = 0; curMuz < MAX_VEHICLE_MUZZLES; curMuz++ ) + {//go through all muzzles and + if ( pVeh->m_iMuzzleTag[curMuz] != -1//valid muzzle bolt + && (broadcaster->trickedentindex&(1<m_pVehicleInfo->weapMuzzle[curMuz] == 0 ) + {//no weaopon for this muzzle? check turrets + int i, j; + for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) + { + for ( j = 0; j < MAX_VEHICLE_TURRETS; j++ ) + { + if ( pVeh->m_pVehicleInfo->turret[i].iMuzzle[j]-1 == curMuz ) + {//this muzzle belongs to this turret + muzFX = g_vehWeaponInfo[pVeh->m_pVehicleInfo->turret[i].iWeapon].iMuzzleFX; + break; + } + } + } + } + else + { + muzFX = g_vehWeaponInfo[pVeh->m_pVehicleInfo->weapMuzzle[curMuz]].iMuzzleFX; + } + if ( muzFX ) + { + //CG_CalcVehMuzzle(pVeh, veh, curMuz); + //trap_FX_PlayEffectID(muzFX, pVeh->m_vMuzzlePos[curMuz], pVeh->m_vMuzzleDir[curMuz], -1, -1); + trap_FX_PlayBoltedEffectID(muzFX, veh->currentState.origin, veh->ghoul2, pVeh->m_iMuzzleTag[curMuz], veh->currentState.number, 0, 0, qtrue); + } + } + } +} + +const char *cg_stringEdVoiceChatTable[MAX_CUSTOM_SIEGE_SOUNDS] = +{ + "VC_ATT",//"*att_attack", + "VC_ATT_PRIMARY",//"*att_primary", + "VC_ATT_SECONDARY",//"*att_second", + "VC_DEF_GUNS",//"*def_guns", + "VC_DEF_POSITION",//"*def_position", + "VC_DEF_PRIMARY",//"*def_primary", + "VC_DEF_SECONDARY",//"*def_second", + "VC_REPLY_COMING",//"*reply_coming", + "VC_REPLY_GO",//"*reply_go", + "VC_REPLY_NO",//"*reply_no", + "VC_REPLY_STAY",//"*reply_stay", + "VC_REPLY_YES",//"*reply_yes", + "VC_REQ_ASSIST",//"*req_assist", + "VC_REQ_DEMO",//"*req_demo", + "VC_REQ_HVY",//"*req_hvy", + "VC_REQ_MEDIC",//"*req_medic", + "VC_REQ_SUPPLY",//"*req_sup", + "VC_REQ_TECH",//"*req_tech", + "VC_SPOT_AIR",//"*spot_air", + "VC_SPOT_DEF",//"*spot_defenses", + "VC_SPOT_EMPLACED",//"*spot_emplaced", + "VC_SPOT_SNIPER",//"*spot_sniper", + "VC_SPOT_TROOP",//"*spot_troops", + "VC_TAC_COVER",//"*tac_cover", + "VC_TAC_FALLBACK",//"*tac_fallback", + "VC_TAC_FOLLOW",//"*tac_follow", + "VC_TAC_HOLD",//"*tac_hold", + "VC_TAC_SPLIT",//"*tac_split", + "VC_TAC_TOGETHER",//"*tac_together", + NULL +}; + +//stupid way of figuring out what string to use for voice chats +const char *CG_GetStringForVoiceSound(const char *s) +{ + int i = 0; + while (i < MAX_CUSTOM_SIEGE_SOUNDS) + { + if (bg_customSiegeSoundNames[i] && + !Q_stricmp(bg_customSiegeSoundNames[i], s)) + { //get the matching reference name + assert(cg_stringEdVoiceChatTable[i]); + return CG_GetStringEdString("MENUS", (char *)cg_stringEdVoiceChatTable[i]); + } + i++; + } + + return "voice chat"; +} + +/* +============== +CG_EntityEvent + +An entity has an event value +also called by CG_CheckPlayerstateEvents +============== +*/ +#define DEBUGNAME(x) if(cg_debugEvents.integer){CG_Printf(x"\n");} +extern void CG_ChatBox_AddString(char *chatStr); //cg_draw.c +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; + int eID = 0; + int isnd = 0; + centity_t *cl_ent; + + 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; + } + + if (es->eType == ET_NPC) + { + clientNum = es->number; + + if (!cent->npcClient) + { + CG_CreateNPCClient(¢->npcClient); //allocate memory for it + + if (!cent->npcClient) + { + assert(0); + return; + } + + memset(cent->npcClient, 0, sizeof(clientInfo_t)); + cent->npcClient->ghoul2Model = NULL; + } + + ci = cent->npcClient; + + assert(ci); + } + else + { + ci = &cgs.clientinfo[ clientNum ]; + } + + switch ( event ) { + // + // movement generated events + // + case EV_CLIENTJOIN: + DEBUGNAME("EV_CLIENTJOIN"); + + //Slight hack to force a local reinit of client entity on join. + cl_ent = &cg_entities[es->eventParm]; + + if (cl_ent) + { + //cl_ent->torsoBolt = 0; + cl_ent->bolt1 = 0; + cl_ent->bolt2 = 0; + cl_ent->bolt3 = 0; + cl_ent->bolt4 = 0; + cl_ent->bodyHeight = 0;//SABER_LENGTH_MAX; + //cl_ent->saberExtendTime = 0; + cl_ent->boltInfo = 0; + cl_ent->frame_minus1_refreshed = 0; + cl_ent->frame_minus2_refreshed = 0; + cl_ent->frame_hold_time = 0; + cl_ent->frame_hold_refreshed = 0; + cl_ent->trickAlpha = 0; + cl_ent->trickAlphaTime = 0; + cl_ent->ghoul2weapon = NULL; + cl_ent->weapon = WP_NONE; + cl_ent->teamPowerEffectTime = 0; + cl_ent->teamPowerType = 0; + cl_ent->numLoopingSounds = 0; + //cl_ent->localAnimIndex = 0; + } + break; + + case EV_FOOTSTEP: + DEBUGNAME("EV_FOOTSTEP"); + if (cg_footsteps.integer) { + footstep_t soundType; + switch( es->eventParm ) + { + case MATERIAL_MUD: + soundType = FOOTSTEP_MUDWALK; + break; + case MATERIAL_DIRT: + soundType = FOOTSTEP_DIRTWALK; + break; + case MATERIAL_SAND: + soundType = FOOTSTEP_SANDWALK; + break; + case MATERIAL_SNOW: + soundType = FOOTSTEP_SNOWWALK; + break; + case MATERIAL_SHORTGRASS: + case MATERIAL_LONGGRASS: + soundType = FOOTSTEP_GRASSWALK; + break; + case MATERIAL_SOLIDMETAL: + soundType = FOOTSTEP_METALWALK; + break; + case MATERIAL_HOLLOWMETAL: + soundType = FOOTSTEP_PIPEWALK; + break; + case MATERIAL_GRAVEL: + soundType = FOOTSTEP_GRAVELWALK; + break; + case MATERIAL_CARPET: + case MATERIAL_FABRIC: + case MATERIAL_CANVAS: + case MATERIAL_RUBBER: + case MATERIAL_PLASTIC: + soundType = FOOTSTEP_RUGWALK; + break; + case MATERIAL_SOLIDWOOD: + case MATERIAL_HOLLOWWOOD: + soundType = FOOTSTEP_WOODWALK; + break; + + default: + soundType = FOOTSTEP_STONEWALK; + break; + } + + trap_S_StartSound (NULL, es->number, CHAN_BODY, cgs.media.footsteps[ soundType ][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_METALWALK ][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: + DEBUGNAME("EV_FALL"); + if (es->number == cg.snap->ps.clientNum && cg.snap->ps.fallingToDeath) + { + break; + } + DoFall(cent, es, clientNum); + 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"); + break; + + case EV_GHOUL2_MARK: + DEBUGNAME("EV_GHOUL2_MARK"); + + if (cg_ghoul2Marks.integer) + { //Can we put a burn mark on him? + CG_G2MarkEvent(es); + } + break; + + case EV_GLOBAL_DUEL: + DEBUGNAME("EV_GLOBAL_DUEL"); + //used for beginning of power duels + //if (cg.predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR) + if (es->otherEntityNum == cg.predictedPlayerState.clientNum || + es->otherEntityNum2 == cg.predictedPlayerState.clientNum || + es->groundEntityNum == cg.predictedPlayerState.clientNum) + { + CG_CenterPrint( CG_GetStringEdString("MP_SVGAME", "BEGIN_DUEL"), 120, GIANTCHAR_WIDTH*2 ); + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + } + break; + + case EV_PRIVATE_DUEL: + DEBUGNAME("EV_PRIVATE_DUEL"); + + if (cg.snap->ps.clientNum != es->number) + { + break; + } + + if (es->eventParm) + { //starting the duel + if (es->eventParm == 2) + { + CG_CenterPrint( CG_GetStringEdString("MP_SVGAME", "BEGIN_DUEL"), 120, GIANTCHAR_WIDTH*2 ); + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + } + else + { + trap_S_StartBackgroundTrack( "music/mp/duel.mp3", "music/mp/duel.mp3", qfalse ); + } + } + else + { //ending the duel + CG_StartMusic(qtrue); + } + break; + + case EV_JUMP: + DEBUGNAME("EV_JUMP"); + if (cg_jumpSounds.integer) + { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + } + break; + case EV_ROLL: + DEBUGNAME("EV_ROLL"); + if (es->number == cg.snap->ps.clientNum && cg.snap->ps.fallingToDeath) + { + break; + } + if (es->eventParm) + { //fall-roll-in-one event + DoFall(cent, es, clientNum); + } + + trap_S_StartSound (NULL, es->number, CHAN_VOICE, CG_CustomSound( es->number, "*jump1.wav" ) ); + trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.rollSound ); + + //FIXME: need some sort of body impact on ground sound and maybe kick up some dust? + break; + + case EV_TAUNT: + DEBUGNAME("EV_TAUNT"); + { + int soundIndex = 0; + if ( cgs.gametype != GT_DUEL + && cgs.gametype != GT_POWERDUEL + && es->eventParm == TAUNT_TAUNT ) + {//normal taunt + soundIndex = CG_CustomSound( es->number, "*taunt.wav" ); + } + else + { + switch ( es->eventParm ) + { + case TAUNT_TAUNT: + default: + if ( Q_irand( 0, 1 ) ) + { + soundIndex = CG_CustomSound( es->number, va("*anger%d.wav", Q_irand(1,3)) ); + } + else + { + soundIndex = CG_CustomSound( es->number, va("*taunt%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*anger%d.wav", Q_irand(1,3)) ); + } + } + break; + case TAUNT_BOW: + //soundIndex = CG_CustomSound( es->number, va("*respect%d.wav", Q_irand(1,3)) ); + break; + case TAUNT_MEDITATE: + //soundIndex = CG_CustomSound( es->number, va("*meditate%d.wav", Q_irand(1,3)) ); + break; + case TAUNT_FLOURISH: + if ( Q_irand( 0, 1 ) ) + { + soundIndex = CG_CustomSound( es->number, va("*deflect%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*gloat%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*anger%d.wav", Q_irand(1,3)) ); + } + } + } + else + { + soundIndex = CG_CustomSound( es->number, va("*gloat%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*deflect%d.wav", Q_irand(1,3)) ); + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, va("*anger%d.wav", Q_irand(1,3)) ); + } + } + } + break; + case TAUNT_GLOAT: + soundIndex = CG_CustomSound( es->number, va("*victory%d.wav", Q_irand(1,3)) ); + break; + } + } + if ( !soundIndex ) + { + soundIndex = CG_CustomSound( es->number, "*taunt.wav" ); + } + if ( soundIndex ) + { + trap_S_StartSound (NULL, es->number, CHAN_VOICE, soundIndex ); + } + } + break; + + //Begin NPC sounds + case EV_ANGER1: //Say when acquire an enemy when didn't have one before + case EV_ANGER2: + case EV_ANGER3: + DEBUGNAME("EV_ANGERx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*anger%i.wav", event - EV_ANGER1 + 1) ); + break; + + case EV_VICTORY1: //Say when killed an enemy + case EV_VICTORY2: + case EV_VICTORY3: + DEBUGNAME("EV_VICTORYx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*victory%i.wav", event - EV_VICTORY1 + 1) ); + break; + + case EV_CONFUSE1: //Say when confused + case EV_CONFUSE2: + case EV_CONFUSE3: + DEBUGNAME("EV_CONFUSEDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*confuse%i.wav", event - EV_CONFUSE1 + 1) ); + break; + + case EV_PUSHED1: //Say when pushed + case EV_PUSHED2: + case EV_PUSHED3: + DEBUGNAME("EV_PUSHEDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*pushed%i.wav", event - EV_PUSHED1 + 1) ); + break; + + case EV_CHOKE1: //Say when choking + case EV_CHOKE2: + case EV_CHOKE3: + DEBUGNAME("EV_CHOKEx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*choke%i.wav", event - EV_CHOKE1 + 1) ); + break; + + case EV_FFWARN: //Warn ally to stop shooting you + DEBUGNAME("EV_FFWARN"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*ffwarn.wav" ); + break; + + case EV_FFTURN: //Turn on ally after being shot by them + DEBUGNAME("EV_FFTURN"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*ffturn.wav" ); + break; + + //extra sounds for ST + case EV_CHASE1: + case EV_CHASE2: + case EV_CHASE3: + DEBUGNAME("EV_CHASEx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*chase%i.wav", event - EV_CHASE1 + 1) ); + break; + case EV_COVER1: + case EV_COVER2: + case EV_COVER3: + case EV_COVER4: + case EV_COVER5: + DEBUGNAME("EV_COVERx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*cover%i.wav", event - EV_COVER1 + 1) ); + break; + case EV_DETECTED1: + case EV_DETECTED2: + case EV_DETECTED3: + case EV_DETECTED4: + case EV_DETECTED5: + DEBUGNAME("EV_DETECTEDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*detected%i.wav", event - EV_DETECTED1 + 1) ); + break; + case EV_GIVEUP1: + case EV_GIVEUP2: + case EV_GIVEUP3: + case EV_GIVEUP4: + DEBUGNAME("EV_GIVEUPx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*giveup%i.wav", event - EV_GIVEUP1 + 1) ); + break; + case EV_LOOK1: + case EV_LOOK2: + DEBUGNAME("EV_LOOKx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*look%i.wav", event - EV_LOOK1 + 1) ); + break; + case EV_LOST1: + DEBUGNAME("EV_LOST1"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*lost1.wav" ); + break; + case EV_OUTFLANK1: + case EV_OUTFLANK2: + DEBUGNAME("EV_OUTFLANKx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*outflank%i.wav", event - EV_OUTFLANK1 + 1) ); + break; + case EV_ESCAPING1: + case EV_ESCAPING2: + case EV_ESCAPING3: + DEBUGNAME("EV_ESCAPINGx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*escaping%i.wav", event - EV_ESCAPING1 + 1) ); + break; + case EV_SIGHT1: + case EV_SIGHT2: + case EV_SIGHT3: + DEBUGNAME("EV_SIGHTx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*sight%i.wav", event - EV_SIGHT1 + 1) ); + break; + case EV_SOUND1: + case EV_SOUND2: + case EV_SOUND3: + DEBUGNAME("EV_SOUNDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*sound%i.wav", event - EV_SOUND1 + 1) ); + break; + case EV_SUSPICIOUS1: + case EV_SUSPICIOUS2: + case EV_SUSPICIOUS3: + case EV_SUSPICIOUS4: + case EV_SUSPICIOUS5: + DEBUGNAME("EV_SUSPICIOUSx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*suspicious%i.wav", event - EV_SUSPICIOUS1 + 1) ); + break; + //extra sounds for Jedi + case EV_COMBAT1: + case EV_COMBAT2: + case EV_COMBAT3: + DEBUGNAME("EV_COMBATx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*combat%i.wav", event - EV_COMBAT1 + 1) ); + break; + case EV_JDETECTED1: + case EV_JDETECTED2: + case EV_JDETECTED3: + DEBUGNAME("EV_JDETECTEDx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*jdetected%i.wav", event - EV_JDETECTED1 + 1) ); + break; + case EV_TAUNT1: + case EV_TAUNT2: + case EV_TAUNT3: + DEBUGNAME("EV_TAUNTx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*taunt%i.wav", event - EV_TAUNT1 + 1) ); + break; + case EV_JCHASE1: + case EV_JCHASE2: + case EV_JCHASE3: + DEBUGNAME("EV_JCHASEx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*jchase%i.wav", event - EV_JCHASE1 + 1) ); + break; + case EV_JLOST1: + case EV_JLOST2: + case EV_JLOST3: + DEBUGNAME("EV_JLOSTx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*jlost%i.wav", event - EV_JLOST1 + 1) ); + break; + case EV_DEFLECT1: + case EV_DEFLECT2: + case EV_DEFLECT3: + DEBUGNAME("EV_DEFLECTx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*deflect%i.wav", event - EV_DEFLECT1 + 1) ); + break; + case EV_GLOAT1: + case EV_GLOAT2: + case EV_GLOAT3: + DEBUGNAME("EV_GLOATx"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, va("*gloat%i.wav", event - EV_GLOAT1 + 1) ); + break; + case EV_PUSHFAIL: + DEBUGNAME("EV_PUSHFAIL"); + CG_TryPlayCustomSound( NULL, es->number, CHAN_VOICE, "*pushfail.wav" ); + break; + //End NPC sounds + + case EV_SIEGESPEC: + DEBUGNAME("EV_SIEGESPEC"); + if ( es->owner == cg.predictedPlayerState.clientNum ) + { + cg_siegeDeathTime = es->time; + } + + break; + + 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; + qboolean newindex = qfalse; + + index = cg_entities[es->eventParm].currentState.modelindex; // player predicted + + if (index < 1 && cg_entities[es->eventParm].currentState.isJediMaster) + { //a holocron most likely + index = cg_entities[es->eventParm].currentState.trickedentindex4; + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.holocronPickup ); + + if (es->number == cg.snap->ps.clientNum && showPowersName[index]) + { + const char *strText = CG_GetStringEdString("MP_INGAME", "PICKUPLINE"); + + //Com_Printf("%s %s\n", strText, showPowersName[index]); + CG_CenterPrint( va("%s %s\n", strText, CG_GetStringEdString("SP_INGAME",showPowersName[index])), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + } + + //Show the player their force selection bar in case picking the holocron up changed the current selection + if (index != FP_SABER_OFFENSE && index != FP_SABER_DEFENSE && index != FP_SABERTHROW && + index != FP_LEVITATION && + es->number == cg.snap->ps.clientNum && + (index == cg.snap->ps.fd.forcePowerSelected || !(cg.snap->ps.fd.forcePowersActive & (1 << cg.snap->ps.fd.forcePowerSelected)))) + { + if (cg.forceSelect != index) + { + cg.forceSelect = index; + newindex = qtrue; + } + } + + if (es->number == cg.snap->ps.clientNum && newindex) + { + if (cg.forceSelectTime < cg.time) + { + cg.forceSelectTime = cg.time; + } + } + + break; + } + + if (cg_entities[es->eventParm].weapon >= cg.time) + { //rww - an unfortunately necessary hack to prevent double item pickups + break; + } + + //Hopefully even if this entity is somehow removed and replaced with, say, another + //item, this time will have expired by the time that item needs to be picked up. + //Of course, it's quite possible this will fail miserably, so if you've got a better + //solution then please do use it. + cg_entities[es->eventParm].weapon = cg.time+500; + + if ( index < 1 || index >= bg_numItems ) { + break; + } + item = &bg_itemlist[ index ]; + + if ( /*item->giType != IT_POWERUP && */item->giType != IT_TEAM) { + if (item->pickup_sound && item->pickup_sound[0]) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound ) ); + } + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + 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 && item->pickup_sound[0] ) { + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( item->pickup_sound) ); + } + + // show icon and name on status bar + if ( es->number == cg.snap->ps.clientNum ) { + CG_ItemPickup( index ); + } + } + break; + + case EV_VEH_FIRE: + DEBUGNAME("EV_VEH_FIRE"); + { + centity_t *veh = &cg_entities[es->owner]; + CG_VehMuzzleFireFX(veh, es); + } + 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 ) + { + if ( CG_InFighter() || CG_InATST() || cg.snap->ps.weapon == WP_NONE ) + {//just letting us know our vehicle is out of ammo + //FIXME: flash something on HUD or give some message so we know we have no ammo + centity_t *localCent = &cg_entities[cg.snap->ps.clientNum]; + if ( localCent->m_pVehicle + && localCent->m_pVehicle->m_pVehicleInfo + && localCent->m_pVehicle->m_pVehicleInfo->weapon[es->eventParm].soundNoAmmo ) + {//play the "no Ammo" sound for this weapon + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, localCent->m_pVehicle->m_pVehicleInfo->weapon[es->eventParm].soundNoAmmo ); + } + else + {//play the default "no ammo" sound + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, cgs.media.noAmmoSound ); + } + //flash the HUD so they associate the sound with the visual indicator that they don't have enough ammo + if ( cg_vehicleAmmoWarningTime < cg.time + || cg_vehicleAmmoWarning != es->eventParm ) + {//if there's already one going, don't interrupt it (unless they tried to fire another weapon that's out of ammo) + cg_vehicleAmmoWarning = es->eventParm; + cg_vehicleAmmoWarningTime = cg.time+500; + } + } + else if ( cg.snap->ps.weapon == WP_SABER ) + { + cg.forceHUDTotalFlashTime = cg.time + 1000; + } + else + { + int weap = 0; + + if (es->eventParm && es->eventParm < WP_NUM_WEAPONS) + { + cg.snap->ps.stats[STAT_WEAPONS] &= ~(1 << es->eventParm); + weap = cg.snap->ps.weapon; + } + else if (es->eventParm) + { + weap = (es->eventParm-WP_NUM_WEAPONS); + } + CG_OutOfAmmoChange(weap); + } + } + break; + case EV_CHANGE_WEAPON: + DEBUGNAME("EV_CHANGE_WEAPON"); + { + int weapon = es->eventParm; + weaponInfo_t *weaponInfo; + + assert(weapon >= 0 && weapon < MAX_WEAPONS); + + weaponInfo = &cg_weapons[weapon]; + + assert(weaponInfo); + + if (weaponInfo->selectSound) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, weaponInfo->selectSound ); + } + else if (weapon != WP_SABER) + { //not sure what SP is doing for this but I don't want a select sound for saber (it has the saber-turn-on) + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.selectSound ); + } + } + break; + case EV_FIRE_WEAPON: + DEBUGNAME("EV_FIRE_WEAPON"); + if (cent->currentState.number >= MAX_CLIENTS && cent->currentState.eType != ET_NPC) + { //special case for turret firing + vec3_t gunpoint, gunangle; + mdxaBone_t matrix; + + weaponInfo_t *weaponInfo = &cg_weapons[WP_TURRET]; + + if ( !weaponInfo->registered ) + { + CG_RegisterWeapon(WP_TURRET); + } + + if (cent->ghoul2) + { + if (!cent->bolt1) + { + cent->bolt1 = trap_G2API_AddBolt(cent->ghoul2, 0, "*flash01"); + } + if (!cent->bolt2) + { + cent->bolt2 = trap_G2API_AddBolt(cent->ghoul2, 0, "*flash02"); + } + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Bone02", 1, 4, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, + 1.0f, cg.time, -1, 300); + } + else + { + break; + } + + if (cent->currentState.eventParm) + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cent->bolt2, &matrix, cent->currentState.angles, cent->currentState.origin, cg.time, cgs.gameModels, cent->modelScale); + } + else + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, cent->bolt1, &matrix, cent->currentState.angles, cent->currentState.origin, cg.time, cgs.gameModels, cent->modelScale); + } + + gunpoint[0] = matrix.matrix[0][3]; + gunpoint[1] = matrix.matrix[1][3]; + gunpoint[2] = matrix.matrix[2][3]; + + gunangle[0] = -matrix.matrix[0][0]; + gunangle[1] = -matrix.matrix[1][0]; + gunangle[2] = -matrix.matrix[2][0]; + + trap_FX_PlayEffectID(cgs.effects.mEmplacedMuzzleFlash, gunpoint, gunangle, -1, -1); + } + else if (cent->currentState.weapon != WP_EMPLACED_GUN || cent->currentState.eType == ET_NPC) + { + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle) + { //vehicles do nothing for clientside weapon fire events.. at least for now. + break; + } + CG_FireWeapon( cent, qfalse ); + } + break; + + case EV_ALT_FIRE: + DEBUGNAME("EV_ALT_FIRE"); + + if (cent->currentState.weapon == WP_EMPLACED_GUN) + { //don't do anything for emplaced stuff + break; + } + + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle) + { //vehicles do nothing for clientside weapon fire events.. at least for now. + break; + } + + CG_FireWeapon( cent, qtrue ); + + //if you just exploded your detpacks and you have no ammo left for them, autoswitch + if ( cg.snap->ps.clientNum == cent->currentState.number && + cg.snap->ps.weapon == WP_DET_PACK ) + { + if (cg.snap->ps.ammo[weaponData[WP_DET_PACK].ammoIndex] == 0) + { + CG_OutOfAmmoChange(WP_DET_PACK); + } + } + + break; + + case EV_SABER_ATTACK: + DEBUGNAME("EV_SABER_ATTACK"); + { + qhandle_t swingSound = trap_S_RegisterSound(va("sound/weapons/saber/saberhup%i.wav", Q_irand(1, 8))); + clientInfo_t *client = NULL; + if ( cg_entities[es->number].currentState.eType == ET_NPC ) + { + client = cg_entities[es->number].npcClient; + } + else if ( es->number < MAX_CLIENTS ) + { + client = &cgs.clientinfo[es->number]; + } + if ( client && client->infoValid && client->saber[0].swingSound[0] ) + {//custom swing sound + swingSound = client->saber[0].swingSound[Q_irand(0,2)]; + } + trap_S_StartSound(es->pos.trBase, es->number, CHAN_WEAPON, swingSound ); + } + break; + + case EV_SABER_HIT: + DEBUGNAME("EV_SABER_HIT"); + { + int hitPersonFxID = cgs.effects.mSaberBloodSparks; + int hitPersonSmallFxID = cgs.effects.mSaberBloodSparksSmall; + int hitPersonMidFxID = cgs.effects.mSaberBloodSparksMid; + int hitOtherFxID = cgs.effects.mSaberCut; + int hitSound = trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3))); + + if ( es->otherEntityNum2 >= 0 + && es->otherEntityNum2 < ENTITYNUM_NONE ) + {//we have a specific person who is causing this effect, see if we should override it with any custom saber effects/sounds + clientInfo_t *client = NULL; + if ( cg_entities[es->otherEntityNum2].currentState.eType == ET_NPC ) + { + client = cg_entities[es->otherEntityNum2].npcClient; + } + else if ( es->otherEntityNum2 < MAX_CLIENTS ) + { + client = &cgs.clientinfo[es->otherEntityNum2]; + } + if ( client && client->infoValid ) + { + int saberNum = es->weapon; + int bladeNum = es->legsAnim; + if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) ) + {//use second blade style values + if ( client->saber[saberNum].hitPersonEffect2 ) + {//custom hit person effect + hitPersonFxID = hitPersonSmallFxID = hitPersonMidFxID = client->saber[saberNum].hitPersonEffect2; + } + if ( client->saber[saberNum].hitOtherEffect2 ) + {//custom hit other effect + hitOtherFxID = client->saber[saberNum].hitOtherEffect2; + } + if ( client->saber[saberNum].hit2Sound[0] ) + {//custom hit sound + hitSound = client->saber[saberNum].hit2Sound[Q_irand(0,2)]; + } + } + else + {//use first blade style values + if ( client->saber[saberNum].hitPersonEffect ) + {//custom hit person effect + hitPersonFxID = hitPersonSmallFxID = hitPersonMidFxID = client->saber[saberNum].hitPersonEffect; + } + if ( client->saber[saberNum].hitOtherEffect ) + {//custom hit other effect + hitOtherFxID = client->saber[0].hitOtherEffect; + } + if ( client->saber[saberNum].hitSound[0] ) + {//custom hit sound + hitSound = client->saber[saberNum].hitSound[Q_irand(0,2)]; + } + } + } + } + + if (es->eventParm == 16) + { //Make lots of sparks, something special happened + vec3_t fxDir; + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, hitSound ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + } + else if (es->eventParm) + { //hit a person + vec3_t fxDir; + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, hitSound ); + if ( es->eventParm == 3 ) + { // moderate or big hits. + trap_FX_PlayEffectID( hitPersonSmallFxID, es->origin, fxDir, -1, -1 ); + } + else if ( es->eventParm == 2 ) + { // this is for really big hits. + trap_FX_PlayEffectID( hitPersonMidFxID, es->origin, fxDir, -1, -1 ); + } + else + { // this should really just be done in the effect itself, no? + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + trap_FX_PlayEffectID( hitPersonFxID, es->origin, fxDir, -1, -1 ); + } + } + else + { //hit something else + vec3_t fxDir; + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + //old jk2mp method + /* + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/saber/saberhit.wav")); + trap_FX_PlayEffectID( trap_FX_RegisterEffect("saber/spark.efx"), es->origin, fxDir, -1, -1 ); + */ + + trap_FX_PlayEffectID( hitOtherFxID, es->origin, fxDir, -1, -1 ); + } + + //rww - this means we have the number of the ent being hit and the ent that owns the saber doing + //the hit. This being the case, we can store these indecies and the current time in order to do + //some visual tricks on the client between frames to make it look like we're actually continuing + //to hit between server frames. + if (es->otherEntityNum != ENTITYNUM_NONE && es->otherEntityNum2 != ENTITYNUM_NONE) + { + centity_t *saberOwner; + + saberOwner = &cg_entities[es->otherEntityNum2]; + + saberOwner->serverSaberHitIndex = es->otherEntityNum; + saberOwner->serverSaberHitTime = cg.time; + + if (es->eventParm) + { + saberOwner->serverSaberFleshImpact = qtrue; + } + else + { + saberOwner->serverSaberFleshImpact = qfalse; + } + } + } + break; + + case EV_SABER_BLOCK: + DEBUGNAME("EV_SABER_BLOCK"); + { + if (es->eventParm) + { //saber block + qboolean cullPass = qfalse; + int blockFXID = cgs.effects.mSaberBlock; + qhandle_t blockSound = trap_S_RegisterSound(va( "sound/weapons/saber/saberblock%d.wav", Q_irand(1, 9) )); + qboolean noFlare = qfalse; + + if ( es->otherEntityNum2 >= 0 + && es->otherEntityNum2 < ENTITYNUM_NONE ) + {//we have a specific person who is causing this effect, see if we should override it with any custom saber effects/sounds + clientInfo_t *client = NULL; + if ( cg_entities[es->otherEntityNum2].currentState.eType == ET_NPC ) + { + client = cg_entities[es->otherEntityNum2].npcClient; + } + else if ( es->otherEntityNum2 < MAX_CLIENTS ) + { + client = &cgs.clientinfo[es->otherEntityNum2]; + } + if ( client && client->infoValid ) + { + int saberNum = es->weapon; + int bladeNum = es->legsAnim; + if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) ) + {//use second blade style values + if ( client->saber[saberNum].blockEffect2 ) + {//custom saber block effect + blockFXID = client->saber[saberNum].blockEffect2; + } + if ( client->saber[saberNum].block2Sound[0] ) + {//custom hit sound + blockSound = client->saber[saberNum].block2Sound[Q_irand(0,2)]; + } + } + else + { + if ( client->saber[saberNum].blockEffect ) + {//custom saber block effect + blockFXID = client->saber[saberNum].blockEffect; + } + if ( client->saber[saberNum].blockSound[0] ) + {//custom hit sound + blockSound = client->saber[saberNum].blockSound[Q_irand(0,2)]; + } + } + if ( (client->saber[saberNum].saberFlags2&SFL2_NO_CLASH_FLARE) ) + { + noFlare = qtrue; + } + } + } + if (cg.mInRMG) + { + trace_t tr; + vec3_t vecSub; + + VectorSubtract(cg.refdef.vieworg, es->origin, vecSub); + + if (VectorLength(vecSub) < 5000) + { + CG_Trace(&tr, cg.refdef.vieworg, NULL, NULL, es->origin, ENTITYNUM_NONE, CONTENTS_TERRAIN|CONTENTS_SOLID); + + if (tr.fraction == 1.0 || tr.entityNum < MAX_CLIENTS) + { + cullPass = qtrue; + } + } + } + else + { + cullPass = qtrue; + } + + if (cullPass) + { + vec3_t fxDir; + + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, blockSound ); + trap_FX_PlayEffectID( blockFXID, es->origin, fxDir, -1, -1 ); + + if ( !noFlare ) + { + cg_saberFlashTime = cg.time-50; + VectorCopy( es->origin, cg_saberFlashPos ); + } + } + } + else + { //projectile block + vec3_t fxDir; + VectorCopy(es->angles, fxDir); + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + trap_FX_PlayEffectID(cgs.effects.mBlasterDeflect, es->origin, fxDir, -1, -1); + } + } + break; + + case EV_SABER_CLASHFLARE: + DEBUGNAME("EV_SABER_CLASHFLARE"); + { + qboolean cullPass = qfalse; + + if (cg.mInRMG) + { + trace_t tr; + vec3_t vecSub; + + VectorSubtract(cg.refdef.vieworg, es->origin, vecSub); + + if (VectorLength(vecSub) < 5000) + { + CG_Trace(&tr, cg.refdef.vieworg, NULL, NULL, es->origin, ENTITYNUM_NONE, CONTENTS_TERRAIN|CONTENTS_SOLID); + + if (tr.fraction == 1.0 || tr.entityNum < MAX_CLIENTS) + { + cullPass = qtrue; + } + } + } + else + { + cullPass = qtrue; + } + + if (cullPass) + { + cg_saberFlashTime = cg.time-50; + VectorCopy( es->origin, cg_saberFlashPos ); + } + trap_S_StartSound ( es->origin, -1, CHAN_WEAPON, trap_S_RegisterSound( va("sound/weapons/saber/saberhitwall%i", Q_irand(1, 3)) ) ); + } + break; + + case EV_SABER_UNHOLSTER: + DEBUGNAME("EV_SABER_UNHOLSTER"); + { + clientInfo_t *ci = NULL; + + if (es->eType == ET_NPC) + { + ci = cg_entities[es->number].npcClient; + } + else if (es->number < MAX_CLIENTS) + { + ci = &cgs.clientinfo[es->number]; + } + + if (ci) + { + if (ci->saber[0].soundOn) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, ci->saber[0].soundOn ); + } + if (ci->saber[1].soundOn) + { + trap_S_StartSound (NULL, es->number, CHAN_AUTO, ci->saber[1].soundOn ); + } + } + } + break; + + case EV_BECOME_JEDIMASTER: + DEBUGNAME("EV_SABER_UNHOLSTER"); + { + trace_t tr; + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2+8}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + vec3_t ang, pos, dpos; + + VectorClear(ang); + ang[ROLL] = 1; + + VectorCopy(position, dpos); + dpos[2] -= 4096; + + CG_Trace(&tr, position, playerMins, playerMaxs, dpos, es->number, MASK_SOLID); + VectorCopy(tr.endpos, pos); + + if (tr.fraction == 1) + { + break; + } + trap_FX_PlayEffectID(cgs.effects.mJediSpawn, pos, ang, -1, -1); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" ) ); + + if (cg.snap->ps.clientNum == es->number) + { + trap_S_StartLocalSound(cgs.media.happyMusic, CHAN_LOCAL); + CGCam_SetMusicMult(0.3, 5000); + } + } + break; + + case EV_DISRUPTOR_MAIN_SHOT: + DEBUGNAME("EV_DISRUPTOR_MAIN_SHOT"); + if (cent->currentState.eventParm != cg.snap->ps.clientNum || + cg.renderingThirdPerson) + { //h4q3ry + CG_GetClientWeaponMuzzleBoltPoint(cent->currentState.eventParm, cent->currentState.origin2); + } + else + { + if (cg.lastFPFlashPoint[0] ||cg.lastFPFlashPoint[1] || cg.lastFPFlashPoint[2]) + { //get the position of the muzzle flash for the first person weapon model from the last frame + VectorCopy(cg.lastFPFlashPoint, cent->currentState.origin2); + } + } + FX_DisruptorMainShot( cent->currentState.origin2, cent->lerpOrigin ); + break; + + case EV_DISRUPTOR_SNIPER_SHOT: + DEBUGNAME("EV_DISRUPTOR_SNIPER_SHOT"); + if (cent->currentState.eventParm != cg.snap->ps.clientNum || + cg.renderingThirdPerson) + { //h4q3ry + CG_GetClientWeaponMuzzleBoltPoint(cent->currentState.eventParm, cent->currentState.origin2); + } + else + { + if (cg.lastFPFlashPoint[0] ||cg.lastFPFlashPoint[1] || cg.lastFPFlashPoint[2]) + { //get the position of the muzzle flash for the first person weapon model from the last frame + VectorCopy(cg.lastFPFlashPoint, cent->currentState.origin2); + } + } + FX_DisruptorAltShot( cent->currentState.origin2, cent->lerpOrigin, cent->currentState.shouldtarget ); + break; + + case EV_DISRUPTOR_SNIPER_MISS: + DEBUGNAME("EV_DISRUPTOR_SNIPER_MISS"); + ByteToDir( es->eventParm, dir ); + if (es->weapon) + { //primary + FX_DisruptorHitWall( cent->lerpOrigin, dir ); + } + else + { //secondary + FX_DisruptorAltMiss( cent->lerpOrigin, dir ); + } + break; + + case EV_DISRUPTOR_HIT: + DEBUGNAME("EV_DISRUPTOR_HIT"); + ByteToDir( es->eventParm, dir ); + if (es->weapon) + { //client + FX_DisruptorHitPlayer( cent->lerpOrigin, dir, qtrue ); + } + else + { //non-client + FX_DisruptorHitWall( cent->lerpOrigin, dir ); + } + break; + + case EV_DISRUPTOR_ZOOMSOUND: + DEBUGNAME("EV_DISRUPTOR_ZOOMSOUND"); + if (es->number == cg.snap->ps.clientNum) + { + if (cg.snap->ps.zoomMode) + { + trap_S_StartLocalSound(trap_S_RegisterSound("sound/weapons/disruptor/zoomstart.wav"), CHAN_AUTO); + } + else + { + trap_S_StartLocalSound(trap_S_RegisterSound("sound/weapons/disruptor/zoomend.wav"), CHAN_AUTO); + } + } + break; + case EV_PREDEFSOUND: + DEBUGNAME("EV_PREDEFSOUND"); + { + int sID = -1; + + switch (es->eventParm) + { + case PDSOUND_PROTECTHIT: + sID = trap_S_RegisterSound("sound/weapons/force/protecthit.mp3"); + break; + case PDSOUND_PROTECT: + sID = trap_S_RegisterSound("sound/weapons/force/protect.mp3"); + break; + case PDSOUND_ABSORBHIT: + sID = trap_S_RegisterSound("sound/weapons/force/absorbhit.mp3"); + if (es->trickedentindex >= 0 && es->trickedentindex < MAX_CLIENTS) + { + int clnum = es->trickedentindex; + + cg_entities[clnum].teamPowerEffectTime = cg.time + 1000; + cg_entities[clnum].teamPowerType = 3; + } + break; + case PDSOUND_ABSORB: + sID = trap_S_RegisterSound("sound/weapons/force/absorb.mp3"); + break; + case PDSOUND_FORCEJUMP: + sID = trap_S_RegisterSound("sound/weapons/force/jump.mp3"); + break; + case PDSOUND_FORCEGRIP: + sID = trap_S_RegisterSound("sound/weapons/force/grip.mp3"); + break; + default: + break; + } + + if (sID != 1) + { + trap_S_StartSound(es->origin, es->number, CHAN_AUTO, sID); + } + } + break; + + case EV_TEAM_POWER: + DEBUGNAME("EV_TEAM_POWER"); + { + int clnum = 0; + + while (clnum < MAX_CLIENTS) + { + if (CG_InClientBitflags(es, clnum)) + { + if (es->eventParm == 1) + { //eventParm 1 is heal + trap_S_StartSound (NULL, clnum, CHAN_AUTO, cgs.media.teamHealSound ); + cg_entities[clnum].teamPowerEffectTime = cg.time + 1000; + cg_entities[clnum].teamPowerType = 1; + } + else + { //eventParm 2 is force regen + trap_S_StartSound (NULL, clnum, CHAN_AUTO, cgs.media.teamRegenSound ); + cg_entities[clnum].teamPowerEffectTime = cg.time + 1000; + cg_entities[clnum].teamPowerType = 0; + } + } + clnum++; + } + } + break; + + case EV_SCREENSHAKE: + DEBUGNAME("EV_SCREENSHAKE"); + if (!es->modelindex || cg.predictedPlayerState.clientNum == es->modelindex-1) + { + CGCam_Shake(es->angles[0], es->time); + } + break; + case EV_LOCALTIMER: + DEBUGNAME("EV_LOCALTIMER"); + if (es->owner == cg.predictedPlayerState.clientNum) + { + CG_LocalTimingBar(es->time, es->time2); + } + 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; + + case EV_ITEMUSEFAIL: + DEBUGNAME("EV_ITEMUSEFAIL"); + if (cg.snap->ps.clientNum == es->number) + { + char *psStringEDRef = NULL; + + switch(es->eventParm) + { + case SENTRY_NOROOM: + psStringEDRef = (char *)CG_GetStringEdString("MP_INGAME", "SENTRY_NOROOM"); + break; + case SENTRY_ALREADYPLACED: + psStringEDRef = (char *)CG_GetStringEdString("MP_INGAME", "SENTRY_ALREADYPLACED"); + break; + case SHIELD_NOROOM: + psStringEDRef = (char *)CG_GetStringEdString("MP_INGAME", "SHIELD_NOROOM"); + break; + case SEEKER_ALREADYDEPLOYED: + psStringEDRef = (char *)CG_GetStringEdString("MP_INGAME", "SEEKER_ALREADYDEPLOYED"); + break; + default: + break; + } + + if (!psStringEDRef) + { + break; + } + + Com_Printf("%s\n", psStringEDRef); + } + break; + + //================================================================= + + // + // other events + // + case EV_PLAYER_TELEPORT_IN: + DEBUGNAME("EV_PLAYER_TELEPORT_IN"); + { + trace_t tr; + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2+8}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + vec3_t ang, pos, dpos; + + VectorClear(ang); + ang[ROLL] = 1; + + VectorCopy(position, dpos); + dpos[2] -= 4096; + + CG_Trace(&tr, position, playerMins, playerMaxs, dpos, es->number, MASK_SOLID); + VectorCopy(tr.endpos, pos); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleInSound ); + + if (tr.fraction == 1) + { + break; + } + trap_FX_PlayEffectID(cgs.effects.mSpawn, pos, ang, -1, -1); + } + break; + + case EV_PLAYER_TELEPORT_OUT: + DEBUGNAME("EV_PLAYER_TELEPORT_OUT"); + { + trace_t tr; + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2+8}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + vec3_t ang, pos, dpos; + + VectorClear(ang); + ang[ROLL] = 1; + + VectorCopy(position, dpos); + dpos[2] -= 4096; + + CG_Trace(&tr, position, playerMins, playerMaxs, dpos, es->number, MASK_SOLID); + VectorCopy(tr.endpos, pos); + + trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.teleOutSound ); + + if (tr.fraction == 1) + { + break; + } + trap_FX_PlayEffectID(cgs.effects.mSpawn, pos, ang, -1, -1); + } + 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"); + //Do something here? + break; + + case EV_SCOREPLUM: + DEBUGNAME("EV_SCOREPLUM"); + CG_ScorePlum( cent->currentState.otherEntityNum, cent->lerpOrigin, cent->currentState.time ); + break; + + case EV_CTFMESSAGE: + DEBUGNAME("EV_CTFMESSAGE"); + CG_GetCTFMessageEvent(es); + break; + + case EV_BODYFADE: + if (es->eType != ET_BODY) + { + assert(!"EV_BODYFADE event from a non-corpse"); + break; + } + + if (cent->ghoul2 && trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { + //turn the inside of the face off, to avoid showing the mouth when we start alpha fading the corpse + trap_G2API_SetSurfaceOnOff( cent->ghoul2, "head_eyes_mouth", 0x00000002/*G2SURFACEFLAG_OFF*/ ); + } + + cent->bodyFadeTime = cg.time + 60000; + break; + + // + // siege gameplay events + // + case EV_SIEGE_ROUNDOVER: + DEBUGNAME("EV_SIEGE_ROUNDOVER"); + CG_SiegeRoundOver(&cg_entities[cent->currentState.weapon], cent->currentState.eventParm); + break; + case EV_SIEGE_OBJECTIVECOMPLETE: + DEBUGNAME("EV_SIEGE_OBJECTIVECOMPLETE"); + CG_SiegeObjectiveCompleted(&cg_entities[cent->currentState.weapon], cent->currentState.eventParm, cent->currentState.trickedentindex); + break; + + case EV_DESTROY_GHOUL2_INSTANCE: + DEBUGNAME("EV_DESTROY_GHOUL2_INSTANCE"); + if (cg_entities[es->eventParm].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[es->eventParm].ghoul2)) + { + if (es->eventParm < MAX_CLIENTS) + { //You try to do very bad thing! +#ifdef _DEBUG + Com_Printf("WARNING: Tried to kill a client ghoul2 instance with a server event!\n"); +#endif + break; + } + trap_G2API_CleanGhoul2Models(&(cg_entities[es->eventParm].ghoul2)); + } + break; + + case EV_DESTROY_WEAPON_MODEL: + DEBUGNAME("EV_DESTROY_WEAPON_MODEL"); + if (cg_entities[es->eventParm].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[es->eventParm].ghoul2) && + trap_G2API_HasGhoul2ModelOnIndex(&(cg_entities[es->eventParm].ghoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(cg_entities[es->eventParm].ghoul2), 1); + } + break; + + case EV_GIVE_NEW_RANK: + DEBUGNAME("EV_GIVE_NEW_RANK"); + if (es->trickedentindex == cg.snap->ps.clientNum) + { + trap_Cvar_Set("ui_rankChange", va("%i", es->eventParm)); + + trap_Cvar_Set("ui_myteam", va("%i", es->bolt2)); + + if (!( trap_Key_GetCatcher() & KEYCATCH_UI ) && !es->bolt1) + { + trap_OpenUIMenu(UIMENU_PLAYERCONFIG); + } + } + break; + + case EV_SET_FREE_SABER: + DEBUGNAME("EV_SET_FREE_SABER"); + + trap_Cvar_Set("ui_freeSaber", va("%i", es->eventParm)); + break; + + case EV_SET_FORCE_DISABLE: + DEBUGNAME("EV_SET_FORCE_DISABLE"); + + trap_Cvar_Set("ui_forcePowerDisable", va("%i", es->eventParm)); + break; + + // + // missile impacts + // + case EV_CONC_ALT_IMPACT: + DEBUGNAME("EV_CONC_ALT_IMPACT"); + { + float dist; + float shotDist = VectorNormalize(es->angles); + vec3_t spot; + + for (dist = 0.0f; dist < shotDist; dist += 64.0f) + { //one effect would be.. a whole lot better + VectorMA( es->origin2, dist, es->angles, spot ); + trap_FX_PlayEffectID(cgs.effects.mConcussionAltRing, spot, es->angles2, -1, -1); + } + + ByteToDir( es->eventParm, dir ); + CG_MissileHitWall(WP_CONCUSSION, es->owner, position, dir, IMPACTSOUND_DEFAULT, qtrue, 0); + + FX_ConcAltShot(es->origin2, spot); + + //steal the bezier effect from the disruptor + FX_DisruptorAltMiss(position, dir); + } + break; + + case EV_MISSILE_STICK: + DEBUGNAME("EV_MISSILE_STICK"); +// trap_S_StartSound (NULL, es->number, CHAN_AUTO, cgs.media.missileStick ); + break; + + case EV_MISSILE_HIT: + DEBUGNAME("EV_MISSILE_HIT"); + ByteToDir( es->eventParm, dir ); + if ( es->emplacedOwner ) + {//hack: this is an index to a custom effect to use + trap_FX_PlayEffectID(cgs.gameEffects[es->emplacedOwner], position, dir, -1, -1); + } + else if ( CG_VehicleWeaponImpact( cent ) ) + {//a vehicle missile that uses an overridden impact effect... + } + else if (cent->currentState.eFlags & EF_ALT_FIRING) + { + CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum, qtrue); + } + else + { + CG_MissileHitPlayer( es->weapon, position, dir, es->otherEntityNum, qfalse); + } + + if (cg_ghoul2Marks.integer && + es->trickedentindex) + { //flag to place a ghoul2 mark + CG_G2MarkEvent(es); + } + break; + + case EV_MISSILE_MISS: + DEBUGNAME("EV_MISSILE_MISS"); + ByteToDir( es->eventParm, dir ); + if ( es->emplacedOwner ) + {//hack: this is an index to a custom effect to use + trap_FX_PlayEffectID(cgs.gameEffects[es->emplacedOwner], position, dir, -1, -1); + } + else if ( CG_VehicleWeaponImpact( cent ) ) + {//a vehicle missile that used an overridden impact effect... + } + else if (cent->currentState.eFlags & EF_ALT_FIRING) + { + CG_MissileHitWall(es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT, qtrue, es->generic1); + } + else + { + CG_MissileHitWall(es->weapon, 0, position, dir, IMPACTSOUND_DEFAULT, qfalse, 0); + } + + if (cg_ghoul2Marks.integer && + es->trickedentindex) + { //flag to place a ghoul2 mark + CG_G2MarkEvent(es); + } + break; + + case EV_MISSILE_MISS_METAL: + DEBUGNAME("EV_MISSILE_MISS_METAL"); + ByteToDir( es->eventParm, dir ); + if ( es->emplacedOwner ) + {//hack: this is an index to a custom effect to use + trap_FX_PlayEffectID(cgs.gameEffects[es->emplacedOwner], position, dir, -1, -1); + } + else if ( CG_VehicleWeaponImpact( cent ) ) + {//a vehicle missile that used an overridden impact effect... + } + else if (cent->currentState.eFlags & EF_ALT_FIRING) + { + CG_MissileHitWall(es->weapon, 0, position, dir, IMPACTSOUND_METAL, qtrue, es->generic1); + } + else + { + CG_MissileHitWall(es->weapon, 0, position, dir, IMPACTSOUND_METAL, qfalse, 0); + } + break; + + case EV_PLAY_EFFECT: + DEBUGNAME("EV_PLAY_EFFECT"); + switch(es->eventParm) + { //it isn't a hack, it's ingenuity! + case EFFECT_SMOKE: + eID = cgs.effects.mEmplacedDeadSmoke; + break; + case EFFECT_EXPLOSION: + eID = cgs.effects.mEmplacedExplode; + break; + case EFFECT_EXPLOSION_PAS: + eID = cgs.effects.mTurretExplode; + break; + case EFFECT_SPARK_EXPLOSION: + eID = cgs.effects.mSparkExplosion; + break; + case EFFECT_EXPLOSION_TRIPMINE: + eID = cgs.effects.mTripmineExplosion; + break; + case EFFECT_EXPLOSION_DETPACK: + eID = cgs.effects.mDetpackExplosion; + break; + case EFFECT_EXPLOSION_FLECHETTE: + eID = cgs.effects.mFlechetteAltBlow; + break; + case EFFECT_STUNHIT: + eID = cgs.effects.mStunBatonFleshImpact; + break; + case EFFECT_EXPLOSION_DEMP2ALT: + FX_DEMP2_AltDetonate( cent->lerpOrigin, es->weapon ); + eID = cgs.effects.mAltDetonate; + break; + case EFFECT_EXPLOSION_TURRET: + eID = cgs.effects.mTurretExplode; + break; + case EFFECT_SPARKS: + eID = cgs.effects.mSparksExplodeNoSound; + break; + case EFFECT_WATER_SPLASH: + eID = cgs.effects.waterSplash; + break; + case EFFECT_ACID_SPLASH: + eID = cgs.effects.acidSplash; + break; + case EFFECT_LAVA_SPLASH: + eID = cgs.effects.lavaSplash; + break; + case EFFECT_LANDING_MUD: + eID = cgs.effects.landingMud; + break; + case EFFECT_LANDING_SAND: + eID = cgs.effects.landingSand; + break; + case EFFECT_LANDING_DIRT: + eID = cgs.effects.landingDirt; + break; + case EFFECT_LANDING_SNOW: + eID = cgs.effects.landingSnow; + break; + case EFFECT_LANDING_GRAVEL: + eID = cgs.effects.landingGravel; + break; + default: + eID = -1; + break; + } + + if (eID != -1) + { + vec3_t fxDir; + + VectorCopy(es->angles, fxDir); + + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + + trap_FX_PlayEffectID(eID, es->origin, fxDir, -1, -1); + } + break; + + case EV_PLAY_EFFECT_ID: + case EV_PLAY_PORTAL_EFFECT_ID: + DEBUGNAME("EV_PLAY_EFFECT_ID"); + { + vec3_t fxDir; + qboolean portalEffect = qfalse; + int efxIndex = 0; + + if (event == EV_PLAY_PORTAL_EFFECT_ID) + { //This effect should only be played inside sky portals. + portalEffect = qtrue; + } + + AngleVectors(es->angles, fxDir, 0, 0); + + if (!fxDir[0] && !fxDir[1] && !fxDir[2]) + { + fxDir[1] = 1; + } + + if ( cgs.gameEffects[ es->eventParm ] ) + { + efxIndex = cgs.gameEffects[es->eventParm]; + } + else + { + s = CG_ConfigString( CS_EFFECTS + es->eventParm ); + if (s && s[0]) + { + efxIndex = trap_FX_RegisterEffect(s); + } + } + + if (efxIndex) + { + if (portalEffect) + { + trap_FX_PlayPortalEffectID(efxIndex, position, fxDir, -1, -1 ); + } + else + { + trap_FX_PlayEffectID(efxIndex, position, fxDir, -1, -1 ); + } + } + } + break; + + case EV_PLAYDOORSOUND: + CG_PlayDoorSound(cent, es->eventParm); + break; + case EV_PLAYDOORLOOPSOUND: + CG_PlayDoorLoopSound(cent); + break; + case EV_BMODEL_SOUND: + DEBUGNAME("EV_BMODEL_SOUND"); + { + sfxHandle_t sfx; + const char *soundSet; + + soundSet = CG_ConfigString( CS_AMBIENT_SET + es->soundSetIndex ); + + if (!soundSet || !soundSet[0]) + { + break; + } + + sfx = trap_AS_GetBModelSound(soundSet, es->eventParm); + + if (sfx == -1) + { + break; + } + + trap_S_StartSound( NULL, es->number, CHAN_AUTO, sfx ); + } + break; + + + case EV_MUTE_SOUND: + DEBUGNAME("EV_MUTE_SOUND"); + if (cg_entities[es->trickedentindex2].currentState.eFlags & EF_SOUNDTRACKER) + { + cg_entities[es->trickedentindex2].currentState.eFlags -= EF_SOUNDTRACKER; + } + trap_S_MuteSound(es->trickedentindex2, es->trickedentindex); + CG_S_StopLoopingSound(es->trickedentindex2, -1); + break; + + case EV_VOICECMD_SOUND: + DEBUGNAME("EV_VOICECMD_SOUND"); + if (es->groundEntityNum >= MAX_CLIENTS) + { //don't ever use this unless it is being used on a real client + break; + } + { + sfxHandle_t sfx = cgs.gameSounds[ es->eventParm ]; + clientInfo_t *ci = &cgs.clientinfo[es->groundEntityNum]; + centity_t *vChatEnt = &cg_entities[es->groundEntityNum]; + char descr[1024]; + + strcpy(descr, CG_GetStringForVoiceSound(CG_ConfigString( CS_SOUNDS + es->eventParm ))); + + if (!sfx) + { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + sfx = CG_CustomSound( es->groundEntityNum, s ); + } + + if (sfx) + { + if (es->groundEntityNum != cg.predictedPlayerState.clientNum) + { //play on the head as well to simulate hearing in radio and in world + if (ci->team == cg.predictedPlayerState.persistant[PERS_TEAM]) + { //don't hear it if this person is on the other team, but they can still + //hear it in the world spot. + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_MENU1, sfx); + } + } + if (ci->team == cg.predictedPlayerState.persistant[PERS_TEAM]) + { //add to the chat box + //hear it in the world spot. + char vchatstr[1024]; + strcpy(vchatstr, va("<%s: %s>\n", ci->name, descr)); + CG_Printf(vchatstr); + CG_ChatBox_AddString(vchatstr); + } + + //and play in world for everyone + trap_S_StartSound (NULL, es->groundEntityNum, CHAN_VOICE, sfx); + vChatEnt->vChatTime = cg.time + 1000; + } + } + break; + + case EV_GENERAL_SOUND: + DEBUGNAME("EV_GENERAL_SOUND"); + if (es->saberEntityNum == TRACK_CHANNEL_2 || es->saberEntityNum == TRACK_CHANNEL_3 || + es->saberEntityNum == TRACK_CHANNEL_5) + { //channels 2 and 3 are for speed and rage, 5 for sight + if ( cgs.gameSounds[ es->eventParm ] ) + { + CG_S_AddRealLoopingSound(es->number, es->pos.trBase, vec3_origin, cgs.gameSounds[ es->eventParm ] ); + } + } + else + { + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->number, es->saberEntityNum, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->number, es->saberEntityNum, 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_MENU1, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_MENU1, 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 + //CG_AddBufferedSound( cgs.media.redScoredSound ); + break; + case GTS_BLUE_CAPTURE: // CTF: blue team captured the red flag, 1FCTF: blue team captured the neutral flag + //CG_AddBufferedSound( cgs.media.blueScoredSound ); + break; + case GTS_RED_RETURN: // CTF: blue flag returned, 1FCTF: never used + if (cgs.gametype == GT_CTY) + { + CG_AddBufferedSound( cgs.media.blueYsalReturnedSound ); + } + else + { + CG_AddBufferedSound( cgs.media.blueFlagReturnedSound ); + } + break; + case GTS_BLUE_RETURN: // CTF red flag returned, 1FCTF: neutral flag returned + if (cgs.gametype == GT_CTY) + { + CG_AddBufferedSound( cgs.media.redYsalReturnedSound ); + } + else + { + 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 (cgs.gametype == GT_CTY) + { + CG_AddBufferedSound( cgs.media.redTookYsalSound ); + } + else + { + CG_AddBufferedSound( cgs.media.redTookFlagSound ); + } + 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 (cgs.gametype == GT_CTY) + { + CG_AddBufferedSound( cgs.media.blueTookYsalSound ); + } + else + { + CG_AddBufferedSound( cgs.media.blueTookFlagSound ); + } + 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; + default: + break; + } + break; + } + + case EV_ENTITY_SOUND: + DEBUGNAME("EV_ENTITY_SOUND"); + //somewhat of a hack - weapon is the caller entity's index, trickedentindex is the proper sound channel + if ( cgs.gameSounds[ es->eventParm ] ) { + trap_S_StartSound (NULL, es->clientNum, es->trickedentindex, cgs.gameSounds[ es->eventParm ] ); + } else { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + trap_S_StartSound (NULL, es->clientNum, es->trickedentindex, CG_CustomSound( es->clientNum, s ) ); + } + break; + + case EV_PLAY_ROFF: + DEBUGNAME("EV_PLAY_ROFF"); + trap_ROFF_Play(es->weapon, es->eventParm, es->trickedentindex); + break; + + case EV_GLASS_SHATTER: + DEBUGNAME("EV_GLASS_SHATTER"); + CG_GlassShatter(es->genericenemyindex, es->origin, es->angles, es->trickedentindex, es->pos.trTime); + break; + + case EV_DEBRIS: + DEBUGNAME("EV_DEBRIS"); + CG_Chunks(es->owner, es->origin, es->angles, es->origin2, es->angles2, es->speed, + es->eventParm, es->trickedentindex, es->modelindex, es->apos.trBase[0]); + break; + + case EV_MISC_MODEL_EXP: + DEBUGNAME("EV_MISC_MODEL_EXP"); + CG_MiscModelExplosion(es->origin2, es->angles2, es->time, es->eventParm); + break; + + case EV_PAIN: + // local player sounds are triggered in CG_CheckLocalSounds, + // so ignore events on the player + DEBUGNAME("EV_PAIN"); + + if ( !cg_oldPainSounds.integer || (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) ) ); + if (es->eventParm && es->number == cg.snap->ps.clientNum) + { + trap_S_StartLocalSound(cgs.media.dramaticFailure, CHAN_LOCAL); + CGCam_SetMusicMult(0.3, 5000); + } + 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_FORCE_DRAINED: + DEBUGNAME("EV_FORCE_DRAINED"); + ByteToDir( es->eventParm, dir ); + //FX_ForceDrained(position, dir); + trap_S_StartSound (NULL, es->owner, CHAN_AUTO, cgs.media.drainSound ); + cg_entities[es->owner].teamPowerEffectTime = cg.time + 1000; + cg_entities[es->owner].teamPowerType = 2; + break; + + case EV_GIB_PLAYER: + DEBUGNAME("EV_GIB_PLAYER"); + //trap_S_StartSound( NULL, es->number, CHAN_BODY, cgs.media.gibSound ); + //CG_GibPlayer( cent->lerpOrigin ); + break; + + case EV_STARTLOOPINGSOUND: + DEBUGNAME("EV_STARTLOOPINGSOUND"); + if ( cgs.gameSounds[ es->eventParm ] ) + { + isnd = cgs.gameSounds[es->eventParm]; + } + else + { + s = CG_ConfigString( CS_SOUNDS + es->eventParm ); + isnd = CG_CustomSound(es->number, s); + } + + CG_S_AddRealLoopingSound( es->number, es->pos.trBase, vec3_origin, isnd ); + es->loopSound = isnd; + break; + + case EV_STOPLOOPINGSOUND: + DEBUGNAME("EV_STOPLOOPINGSOUND"); + CG_S_StopLoopingSound( es->number, -1 ); + es->loopSound = 0; + break; + + case EV_WEAPON_CHARGE: + DEBUGNAME("EV_WEAPON_CHARGE"); + assert(es->eventParm > WP_NONE && es->eventParm < WP_NUM_WEAPONS); + if (cg_weapons[es->eventParm].chargeSound) + { + trap_S_StartSound(NULL, es->number, CHAN_WEAPON, cg_weapons[es->eventParm].chargeSound); + } + else if (es->eventParm == WP_DISRUPTOR) + { + trap_S_StartSound(NULL, es->number, CHAN_WEAPON, cgs.media.disruptorZoomLoop); + } + break; + + case EV_WEAPON_CHARGE_ALT: + DEBUGNAME("EV_WEAPON_CHARGE_ALT"); + assert(es->eventParm > WP_NONE && es->eventParm < WP_NUM_WEAPONS); + if (cg_weapons[es->eventParm].altChargeSound) + { + trap_S_StartSound(NULL, es->number, CHAN_WEAPON, cg_weapons[es->eventParm].altChargeSound); + } + break; + + case EV_SHIELD_HIT: + DEBUGNAME("EV_SHIELD_HIT"); + ByteToDir(es->eventParm, dir); + CG_PlayerShieldHit(es->otherEntityNum, dir, es->time2); + break; + + case EV_DEBUG_LINE: + DEBUGNAME("EV_DEBUG_LINE"); + CG_Beam( cent ); + break; + + case EV_TESTLINE: + DEBUGNAME("EV_TESTLINE"); + CG_TestLine(es->origin, es->origin2, es->time2, es->weapon, 1); + break; + + default: + DEBUGNAME("UNKNOWN"); + CG_Error( "Unknown event: %i", event ); + break; + } + +} + + +/* +============== +CG_CheckEvents + +============== +*/ +void CG_CheckEvents( centity_t *cent ) { + // 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/code/cgame/cg_info.c b/code/cgame/cg_info.c new file mode 100644 index 0000000..d9ffa3a --- /dev/null +++ b/code/cgame/cg_info.c @@ -0,0 +1,461 @@ +// 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 +#define MAX_LOADING_ITEM_ICONS 26 + +//static int loadingPlayerIconCount; +//static qhandle_t loadingPlayerIcons[MAX_LOADING_PLAYER_ICONS]; + +void CG_LoadBar(void); + +/* +====================== +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; + char upperKey[1024]; + + item = &bg_itemlist[itemNum]; + + if (!item->classname || !item->classname[0]) + { + // CG_LoadingString( "Unknown item" ); + return; + } + + strcpy(upperKey, item->classname); + CG_LoadingString( CG_GetStringEdString("SP_INGAME",Q_strupr(upperKey)) ); +} + +/* +=================== +CG_LoadingClient +=================== +*/ +void CG_LoadingClient( int clientNum ) { + const char *info; + char personality[MAX_QPATH]; + + info = CG_ConfigString( CS_PLAYERS + clientNum ); + +/* + char model[MAX_QPATH]; + char iconName[MAX_QPATH]; + char *skin; + 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 )); + } + */ + + CG_LoadingString( personality ); +} + + +/* +==================== +CG_DrawInformation + +Draw all the status / pacifier stuff during level loading +==================== +overlays UI_DrawConnectScreen +*/ +#define UI_INFOFONT (UI_BIGFONT) +void CG_DrawInformation( void ) { + const char *s; + const char *info; + const char *sysInfo; + int y; + int value, valueNOFP; + qhandle_t levelshot; + char buf[1024]; + int iPropHeight = 18; // I know, this is total crap, but as a post release asian-hack.... -Ste + + info = CG_ConfigString( CS_SERVERINFO ); + sysInfo = CG_ConfigString( CS_SYSTEMINFO ); + + s = Info_ValueForKey( info, "mapname" ); + levelshot = trap_R_RegisterShaderNoMip( va( "levelshots/%s", s ) ); + if ( !levelshot ) { + levelshot = trap_R_RegisterShaderNoMip( "menu/art/unknownmap_mp" ); + } + trap_R_SetColor( NULL ); + CG_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, levelshot ); + + CG_LoadBar(); + + // 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] ) { + const char *psLoading = CG_GetStringEdString("MENUS", "LOADING_MAPNAME"); + UI_DrawProportionalString( 320, 128-32, va(/*"Loading... %s"*/ psLoading, cg.infoScreenText), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + } else { + const char *psAwaitingSnapshot = CG_GetStringEdString("MENUS", "AWAITING_SNAPSHOT"); + UI_DrawProportionalString( 320, 128-32, /*"Awaiting snapshot..."*/psAwaitingSnapshot, + UI_CENTER|UI_INFOFONT|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_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + + // pure server + s = Info_ValueForKey( sysInfo, "sv_pure" ); + if ( s[0] == '1' ) { + const char *psPure = CG_GetStringEdString("MP_INGAME", "PURE_SERVER"); + UI_DrawProportionalString( 320, y, psPure, + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + // server-specific message of the day + s = CG_ConfigString( CS_MOTD ); + if ( s[0] ) { + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + { // display global MOTD at bottom (mirrors ui_main UI_DrawConnectScreen + char motdString[1024]; + trap_Cvar_VariableStringBuffer( "cl_motdString", motdString, sizeof( motdString ) ); + + if (motdString[0]) + { + UI_DrawProportionalString( 320, 425, motdString, + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + } + } + + // 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_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + // cheats warning + s = Info_ValueForKey( sysInfo, "sv_cheats" ); + if ( s[0] == '1' ) { + UI_DrawProportionalString( 320, y, CG_GetStringEdString("MP_INGAME", "CHEATSAREENABLED"), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + // game type + switch ( cgs.gametype ) { + case GT_FFA: + s = CG_GetStringEdString("MENUS", "FREE_FOR_ALL");//"Free For All"; +// s = "Free For All"; + break; + case GT_HOLOCRON: + s = CG_GetStringEdString("MENUS", "HOLOCRON_FFA");//"Holocron FFA"; +// s = "Holocron FFA"; + break; + case GT_JEDIMASTER: + s = CG_GetStringEdString("MENUS", "SAGA");//"Jedi Master";?? + +// s = "Jedi Master"; + break; + case GT_SINGLE_PLAYER: + s = CG_GetStringEdString("MENUS", "SAGA");//"Team FFA"; + + //s = "Single Player"; + break; + case GT_DUEL: + s = CG_GetStringEdString("MENUS", "DUEL");//"Team FFA"; + //s = "Duel"; + break; + case GT_POWERDUEL: + s = CG_GetStringEdString("MENUS", "POWERDUEL");//"Team FFA"; + //s = "Power Duel"; + break; + case GT_TEAM: + s = CG_GetStringEdString("MENUS", "TEAM_FFA");//"Team FFA"; + + //s = "Team FFA"; + break; + case GT_SIEGE: + s = CG_GetStringEdString("MENUS", "SIEGE");//"Siege"; + + //s = "Siege"; + break; + case GT_CTF: + s = CG_GetStringEdString("MENUS", "CAPTURE_THE_FLAG");//"Capture the Flag"; + + //s = "Capture The Flag"; + break; + case GT_CTY: + s = CG_GetStringEdString("MENUS", "CAPTURE_THE_YSALIMARI");//"Capture the Ysalamiri"; + + //s = "Capture The Ysalamiri"; + break; + default: + s = CG_GetStringEdString("MENUS", "SAGA");//"Team FFA"; + + //s = "Unknown Gametype"; + break; + } + UI_DrawProportionalString( 320, y, s, + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + + if (cgs.gametype != GT_SIEGE) + { + value = atoi( Info_ValueForKey( info, "timelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "%s %i", CG_GetStringEdString("MP_INGAME", "TIMELIMIT"), value ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + if (cgs.gametype < GT_CTF ) { + value = atoi( Info_ValueForKey( info, "fraglimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "%s %i", CG_GetStringEdString("MP_INGAME", "FRAGLIMIT"), value ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) + { + value = atoi( Info_ValueForKey( info, "duel_fraglimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "%s %i", CG_GetStringEdString("MP_INGAME", "WINLIMIT"), value ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + } + } + } + + if (cgs.gametype >= GT_CTF) { + value = atoi( Info_ValueForKey( info, "capturelimit" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, va( "%s %i", CG_GetStringEdString("MP_INGAME", "CAPTURELIMIT"), value ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + } + + if (cgs.gametype >= GT_TEAM) + { + value = atoi( Info_ValueForKey( info, "g_forceBasedTeams" ) ); + if ( value ) { + UI_DrawProportionalString( 320, y, CG_GetStringEdString("MP_INGAME", "FORCEBASEDTEAMS"), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + } + + if (cgs.gametype != GT_SIEGE) + { + valueNOFP = atoi( Info_ValueForKey( info, "g_forcePowerDisable" ) ); + + value = atoi( Info_ValueForKey( info, "g_maxForceRank" ) ); + if ( value && !valueNOFP && (value < NUM_FORCE_MASTERY_LEVELS) ) { + char fmStr[1024]; + + trap_SP_GetStringTextString("MP_INGAME_MAXFORCERANK",fmStr, sizeof(fmStr)); + + UI_DrawProportionalString( 320, y, va( "%s %s", fmStr, CG_GetStringEdString("MP_INGAME", forceMasteryLevels[value]) ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + else if (!valueNOFP) + { + char fmStr[1024]; + trap_SP_GetStringTextString("MP_INGAME_MAXFORCERANK",fmStr, sizeof(fmStr)); + + UI_DrawProportionalString( 320, y, va( "%s %s", fmStr, (char *)CG_GetStringEdString("MP_INGAME", forceMasteryLevels[7]) ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) + { + value = atoi( Info_ValueForKey( info, "g_duelWeaponDisable" ) ); + } + else + { + value = atoi( Info_ValueForKey( info, "g_weaponDisable" ) ); + } + if ( cgs.gametype != GT_JEDIMASTER && value ) { + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "SABERONLYSET") ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + + if ( valueNOFP ) { + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "NOFPSET") ), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + } + } + + // Display the rules based on type + y += iPropHeight; + switch ( cgs.gametype ) { + case GT_FFA: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_FFA_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_HOLOCRON: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_HOLO_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_HOLO_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_JEDIMASTER: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_JEDI_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_JEDI_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_SINGLE_PLAYER: + break; + case GT_DUEL: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_DUEL_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_DUEL_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_POWERDUEL: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_POWERDUEL_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_POWERDUEL_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_TEAM: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_TEAM_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_TEAM_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_SIEGE: + break; + case GT_CTF: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_CTF_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_CTF_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + case GT_CTY: + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_CTY_1")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + UI_DrawProportionalString( 320, y, va( "%s", (char *)CG_GetStringEdString("MP_INGAME", "RULES_CTY_2")), + UI_CENTER|UI_INFOFONT|UI_DROPSHADOW, colorWhite ); + y += iPropHeight; + break; + default: + break; + } +} + +/* +=================== +CG_LoadBar +=================== +*/ +void CG_LoadBar(void) +{ + const int numticks = 9, tickwidth = 40, tickheight = 8; + const int tickpadx = 20, tickpady = 12; + const int capwidth = 8; + const int barwidth = numticks*tickwidth+tickpadx*2+capwidth*2, barleft = ((640-barwidth)/2); + const int barheight = tickheight + tickpady*2, bartop = 480-barheight; + const int capleft = barleft+tickpadx, tickleft = capleft+capwidth, ticktop = bartop+tickpady; + + trap_R_SetColor( colorWhite ); + // Draw background + CG_DrawPic(barleft, bartop, barwidth, barheight, cgs.media.loadBarLEDSurround); + + // Draw left cap (backwards) + CG_DrawPic(tickleft, ticktop, -capwidth, tickheight, cgs.media.loadBarLEDCap); + + // Draw bar + CG_DrawPic(tickleft, ticktop, tickwidth*cg.loadLCARSStage, tickheight, cgs.media.loadBarLED); + + // Draw right cap + CG_DrawPic(tickleft+tickwidth*cg.loadLCARSStage, ticktop, capwidth, tickheight, cgs.media.loadBarLEDCap); +} + diff --git a/code/cgame/cg_light.c b/code/cgame/cg_light.c new file mode 100644 index 0000000..05e2224 --- /dev/null +++ b/code/cgame/cg_light.c @@ -0,0 +1,85 @@ +#include "cg_local.h" + +#if !defined(CG_LIGHTS_H_INC) + #include "cg_lights.h" +#endif + +static clightstyle_t cl_lightstyle[MAX_LIGHT_STYLES]; +static int lastofs; + +/* +================ +FX_ClearLightStyles +================ +*/ +void CG_ClearLightStyles (void) +{ + int i; + + memset (cl_lightstyle, 0, sizeof(cl_lightstyle)); + lastofs = -1; + + for(i=0;ilength) + { + ls->value[0] = ls->value[1] = ls->value[2] = ls->value[3] = 255; + } + else if (ls->length == 1) + { + ls->value[0] = ls->map[0][0]; + ls->value[1] = ls->map[0][1]; + ls->value[2] = ls->map[0][2]; + ls->value[3] = 255; //ls->map[0][3]; + } + else + { + ls->value[0] = ls->map[ofs%ls->length][0]; + ls->value[1] = ls->map[ofs%ls->length][1]; + ls->value[2] = ls->map[ofs%ls->length][2]; + ls->value[3] = 255; //ls->map[ofs%ls->length][3]; + } + trap_R_SetLightStyle(i, *(int*)ls->value); + } +} + +void CG_SetLightstyle (int i) +{ + const char *s; + int j, k; + + s = CG_ConfigString( i+CS_LIGHT_STYLES ); + j = strlen (s); + if (j >= MAX_QPATH) + { + Com_Error (ERR_DROP, "svc_lightstyle length=%i", j); + } + + cl_lightstyle[(i/3)].length = j; + for (k=0 ; koldFrame was exactly on + + int frame; + int frameTime; // time when ->frame will be exactly on + + float backlerp; + + qboolean lastFlip; //if does not match torsoFlip/legsFlip, restart the anim. + + int lastForcedFrame; + + float yawAngle; + qboolean yawing; + float pitchAngle; + qboolean pitching; + + float yawSwingDif; + + int animationNumber; + animation_t *animation; + int animationTime; // time when the first frame of the animation will be exact + + float animationSpeed; // scale the animation speed + float animationTorsoSpeed; + + qboolean torsoYawing; +} lerpFrame_t; + + +typedef struct { + lerpFrame_t legs, torso, flag; + int painTime; + int painDirection; // flip from 0 to 1 + int lightningFiring; + + // machinegun spinning + float barrelAngle; + int barrelTime; + qboolean barrelSpinning; +} playerEntity_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_COMBAT_SOUNDS 40 +#define MAX_CUSTOM_EXTRA_SOUNDS 40 +#define MAX_CUSTOM_JEDI_SOUNDS 40 +//#define MAX_CUSTOM_SIEGE_SOUNDS..defined in bg_public.h +#define MAX_CUSTOM_DUEL_SOUNDS 40 + +#define MAX_CUSTOM_SOUNDS 40 //rww - Note that for now these must all be the same, because of the way I am + //cycling through them and comparing for custom sounds. + +typedef struct { + qboolean infoValid; + + float colorOverride[3]; + + saberInfo_t saber[MAX_SABERS]; + void *ghoul2Weapons[MAX_SABERS]; + + char saberName[64]; + char saber2Name[64]; + + char name[MAX_QPATH]; + team_t team; + + int duelTeam; + + int botSkill; // 0 = not bot, 1-5 = bot + + int frame; + + vec3_t color1; + vec3_t color2; + + int icolor1; + int icolor2; + + 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 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 forcePowers[MAX_QPATH]; +// char redTeam[MAX_TEAMNAME]; +// char blueTeam[MAX_TEAMNAME]; + + char teamName[MAX_TEAMNAME]; + + int corrTime; + + vec3_t lastHeadAngles; + int lookTime; + + int brokenLimbs; + + 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; + + void *ghoul2Model; + + qhandle_t modelIcon; + + qhandle_t bolt_rhand; + qhandle_t bolt_lhand; + + qhandle_t bolt_head; + + qhandle_t bolt_motion; + + qhandle_t bolt_llumbar; + + int siegeIndex; + int siegeDesiredTeam; + + sfxHandle_t sounds[MAX_CUSTOM_SOUNDS]; + sfxHandle_t combatSounds[MAX_CUSTOM_COMBAT_SOUNDS]; + sfxHandle_t extraSounds[MAX_CUSTOM_EXTRA_SOUNDS]; + sfxHandle_t jediSounds[MAX_CUSTOM_JEDI_SOUNDS]; + sfxHandle_t siegeSounds[MAX_CUSTOM_SIEGE_SOUNDS]; + sfxHandle_t duelSounds[MAX_CUSTOM_DUEL_SOUNDS]; + + int legsAnim; + int torsoAnim; + + float facial_blink; // time before next blink. If a minus value, we are in blink mode + float facial_frown; // time before next frown. If a minus value, we are in frown mode + float facial_aux; // time before next aux. If a minus value, we are in aux mode + + int superSmoothTime; //do crazy amount of smoothing + +#ifdef _XBOX + int friendshipStatus; +#endif + +} clientInfo_t; + +//rww - cheap looping sound struct +#ifdef _XBOX +#define MAX_CG_LOOPSOUNDS 2 +#else +#define MAX_CG_LOOPSOUNDS 8 +#endif + +typedef struct cgLoopSound_s { + int entityNum; + vec3_t origin; + vec3_t velocity; + sfxHandle_t sfx; +} cgLoopSound_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 { + // This comment below is correct, but now m_pVehicle is the first thing in bg shared entity, so it goes first. - AReis + //rww - entstate must be first, to correspond with the bg shared entity structure + entityState_t currentState; // from cg.frame + playerState_t *playerState; //ptr to playerstate if applicable (for bg ents) + Vehicle_t *m_pVehicle; //vehicle data + void *ghoul2; //g2 instance + int localAnimIndex; //index locally (game/cgame) to anim data for this skel + vec3_t modelScale; //needed for g2 collision + + //from here up must be unified with bgEntity_t -rww + + 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; + + vec3_t damageAngles; + int damageTime; + + int snapShotTime; // last time this entity was found in a snapshot + + 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; + +#if 0 + //add up bone offsets until next client frame before adding them in + qboolean hasRagOffset; + vec3_t ragOffsets; + int ragOffsetTime; +#endif + + vec3_t ragLastOrigin; + int ragLastOriginTime; + + qboolean noLumbar; //if true only do anims and things on model_root instead of lower_lumbar, this will be the case for some NPCs. + qboolean noFace; + + //For keeping track of the current surface status in relation to the entitystate surface fields. + int npcLocalSurfOn; + int npcLocalSurfOff; + + int eventAnimIndex; + + clientInfo_t *npcClient; //dynamically allocated - always free it, and never stomp over it. + + int weapon; + + void *ghoul2weapon; //rww - pointer to ghoul2 instance of the current 3rd person weapon + + float radius; + int boltInfo; + + //sometimes used as a bolt index, but these values are also used as generic values for clientside entities + //at times + int bolt1; + int bolt2; + int bolt3; + int bolt4; + + float bodyHeight; + + int torsoBolt; + + vec3_t turAngles; + + vec3_t frame_minus1; + vec3_t frame_minus2; + + int frame_minus1_refreshed; + int frame_minus2_refreshed; + + void *frame_hold; //pointer to a ghoul2 instance + + int frame_hold_time; + int frame_hold_refreshed; + + void *grip_arm; //pointer to a ghoul2 instance + + int trickAlpha; + int trickAlphaTime; + + int teamPowerEffectTime; + qboolean teamPowerType; //0 regen, 1 heal, 2 drain, 3 absorb + + qboolean isRagging; + qboolean ownerRagging; + int overridingBones; + + int bodyFadeTime; + vec3_t pushEffectOrigin; + + cgLoopSound_t loopingSound[MAX_CG_LOOPSOUNDS]; + int numLoopingSounds; + + int serverSaberHitIndex; + int serverSaberHitTime; + qboolean serverSaberFleshImpact; //true if flesh, false if anything else. + + qboolean ikStatus; + + qboolean saberWasInFlight; + + float smoothYaw; + + int uncloaking; + qboolean cloaked; + + int vChatTime; +} centity_t; + + +//====================================================================== + +// local entities are created as a result of events or predicted actions, +// and live independently 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_FADE_SCALE_MODEL, // currently only for Demp2 shock sphere + LE_FRAGMENT, + LE_PUFF, + LE_MOVE_SCALE_FADE, + LE_FALL_SCALE_FADE, + LE_FADE_RGB, + LE_SCALE_FADE, + LE_SCOREPLUM, + LE_OLINE, + LE_SHOWREFENTITY, + LE_LINE +} 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_FADE_RGB = 0x0004, // explicitly fade + LEF_NO_RANDOM_ROTATE= 0x0008 // MakeExplosion adds random rotate which could be bad in some cases +} 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, + LEBS_METAL, + LEBS_ROCK +} 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 + int bounceSound; // optional sound index to play upon bounce + + float alpha; + float dalpha; + + int forceAlpha; + + float color[4]; + + float radius; + + float light; + vec3_t lightColor; + + leMarkType_t leMarkType; // mark to leave on fragment impact + leBounceSoundType_t leBounceSoundType; + + union { + struct { + float radius; + float dradius; + vec3_t startRGB; + vec3_t dRGB; + } sprite; + struct { + float width; + float dwidth; + float length; + float dlength; + vec3_t startRGB; + vec3_t dRGB; + } trail; + struct { + float width; + float dwidth; + // Below are bezier specific. + vec3_t control1; // initial position of control points + vec3_t control2; + vec3_t control1_velocity; // initial velocity of control points + vec3_t control2_velocity; + vec3_t control1_acceleration; // constant acceleration of control points + vec3_t control2_acceleration; + } line; + struct { + float width; + float dwidth; + float width2; + float dwidth2; + vec3_t startRGB; + vec3_t dRGB; + } line2; + struct { + float width; + float dwidth; + float width2; + float dwidth2; + float height; + float dheight; + } cylinder; + struct { + float width; + float dwidth; + } electricity; + struct + { + // fight the power! open and close brackets in the same column! + float radius; + float dradius; + qboolean (*thinkFn)(struct localEntity_s *le); + vec3_t dir; // magnitude is 1, but this is oldpos - newpos right before the + //particle is sent to the renderer + // may want to add something like particle::localEntity_s *le (for the particle's think fn) + } particle; + struct + { + qboolean dontDie; + vec3_t dir; + float variance; + int delay; + int nextthink; + qboolean (*thinkFn)(struct localEntity_s *le); + int data1; + int data2; + } spawner; + struct + { + float radius; + } fragment; + } data; + + 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; +} score_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; // this is the pickup model + qhandle_t viewModel; // this is the in-view model used by the player + qhandle_t barrelModel; + qhandle_t flashModel; + + vec3_t weaponMidpoint; // so it will rotate centered instead of by tag + + float flashDlight; + vec3_t flashDlightColor; + + qhandle_t weaponIcon; + qhandle_t ammoIcon; + + qhandle_t ammoModel; + + sfxHandle_t flashSound[4]; // fast firing weapons randomly choose + sfxHandle_t firingSound; + sfxHandle_t chargeSound; + fxHandle_t muzzleEffect; + qhandle_t missileModel; + sfxHandle_t missileSound; + void (*missileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + float missileDlight; + vec3_t missileDlightColor; + int missileRenderfx; + sfxHandle_t missileHitSound; + + sfxHandle_t altFlashSound[4]; + sfxHandle_t altFiringSound; + sfxHandle_t altChargeSound; + fxHandle_t altMuzzleEffect; + qhandle_t altMissileModel; + sfxHandle_t altMissileSound; + void (*altMissileTrailFunc)( centity_t *, const struct weaponInfo_s *wi ); + float altMissileDlight; + vec3_t altMissileDlightColor; + int altMissileRenderfx; + sfxHandle_t altMissileHitSound; + + sfxHandle_t selectSound; + + sfxHandle_t readySound; + float trailRadius; + float wiTrailTime; + +} 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; +/* +Ghoul2 Insert Start +*/ + void *g2Models[MAX_ITEM_MODELS]; + float radius[MAX_ITEM_MODELS]; +/* +Ghoul2 Insert End +*/ +} itemInfo_t; + + +typedef struct { + int itemNum; +} powerupInfo_t; + + +#define MAX_SKULLTRAIL 10 + +typedef struct { + vec3_t positions[MAX_SKULLTRAIL]; + int numpositions; +} skulltrail_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 + + +#define MAX_CHATBOX_ITEMS 5 +typedef struct chatBoxItem_s +{ + char string[MAX_SAY_TEXT]; + int time; + int lines; +} chatBoxItem_t; + +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 mMapChange; + + 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 mInRMG; //rwwRMG - added + qboolean mRMGWeather; //rwwRMG - added + + qboolean renderingThirdPerson; // during deaths, chasecams, etc + + // prediction state + qboolean hyperspace; // true if prediction has hit a trigger_teleport + playerState_t predictedPlayerState; + playerState_t predictedVehicleState; + + //centity_t predictedPlayerEntity; + //rww - I removed this and made it use cg_entities[clnum] directly. + + 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; + + int forceSelect; + int itemSelect; + + // auto rotating items + vec3_t autoAngles; + vec3_t autoAxis[3]; + vec3_t autoAnglesFast; + vec3_t autoAxisFast[3]; + + // view rendering + refdef_t refdef; + +#ifdef _XBOX + qboolean widescreen; +#endif + + // 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; + + int crosshairVehNum; + int crosshairVehTime; + + // 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 + int kick_time; + 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 + int loadLCARSStage; + + int forceHUDTotalFlashTime; + int forceHUDNextFlashTime; + qboolean forceHUDActive; // Flag to show force hud is off/on + + // development tool + refEntity_t testModelEntity; + char testModelName[MAX_QPATH]; + qboolean testGun; + + int VHUDFlashTime; + qboolean VHUDTurboFlag; + + // HUD stuff + float HUDTickFlashTime; + qboolean HUDArmorFlag; + qboolean HUDHealthFlag; + qboolean iconHUDActive; + float iconHUDPercent; + float iconSelectTime; + float invenSelectTime; + float forceSelectTime; + + vec3_t lastFPFlashPoint; + +/* +Ghoul2 Insert Start +*/ + int testModel; + // had to be moved so we wouldn't wipe these out with the memset - these have STL in them and shouldn't be cleared that way + snapshot_t activeSnapshots[2]; +/* +Ghoul2 Insert End +*/ + + char sharedBuffer[MAX_CG_SHARED_BUFFER_SIZE]; + + short radarEntityCount; + short radarEntities[MAX_CLIENTS+16]; + + short bracketedEntityCount; + short bracketedEntities[MAX_CLIENTS+16]; + + float distanceCull; + + chatBoxItem_t chatItems[MAX_CHATBOX_ITEMS]; + int chatItemActive; + +#if 0 + int snapshotTimeoutTime; +#endif + +} cg_t; + +#define MAX_TICS 14 + +typedef struct forceTicPos_s +{ + int x; + int y; + int width; + int height; + char *file; + qhandle_t tic; +} forceTicPos_t; +extern forceTicPos_t forceTicPos[]; +extern forceTicPos_t ammoTicPos[]; + +typedef struct cgscreffects_s +{ + float FOV; + float FOV2; + + float shake_intensity; + int shake_duration; + int shake_start; + + float music_volume_multiplier; + int music_volume_time; + qboolean music_volume_set; +} cgscreffects_t; + +extern cgscreffects_t cgScreenEffects; + +void CGCam_Shake( float intensity, int duration ); +void CGCam_SetMusicMult( float multiplier, int duration ); + +typedef enum +{ + CHUNK_METAL1 = 0, + CHUNK_METAL2, + CHUNK_ROCK1, + CHUNK_ROCK2, + CHUNK_ROCK3, + CHUNK_CRATE1, + CHUNK_CRATE2, + CHUNK_WHITE_METAL, + NUM_CHUNK_TYPES +}; +#define NUM_CHUNK_MODELS 4 + +// 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 whiteShader; + + qhandle_t loadBarLED; + qhandle_t loadBarLEDCap; + qhandle_t loadBarLEDSurround; + + qhandle_t bryarFrontFlash; + qhandle_t greenFrontFlash; + qhandle_t lightningFlash; + + qhandle_t itemHoloModel; + qhandle_t redFlagModel; + qhandle_t blueFlagModel; + + qhandle_t flagPoleModel; + qhandle_t flagFlapModel; + + qhandle_t redFlagBaseModel; + qhandle_t blueFlagBaseModel; + qhandle_t neutralFlagBaseModel; + + qhandle_t teamStatusBar; + + qhandle_t deferShader; + + qhandle_t radarShader; + qhandle_t siegeItemShader; + qhandle_t mAutomapPlayerIcon; + qhandle_t mAutomapRocketIcon; + + qhandle_t wireframeAutomapFrame_left; + qhandle_t wireframeAutomapFrame_right; + qhandle_t wireframeAutomapFrame_top; + qhandle_t wireframeAutomapFrame_bottom; + +//Chunks + qhandle_t chunkModels[NUM_CHUNK_TYPES][4]; + sfxHandle_t chunkSound; + sfxHandle_t grateSound; + sfxHandle_t rockBreakSound; + sfxHandle_t rockBounceSound[2]; + sfxHandle_t metalBounceSound[2]; + sfxHandle_t glassChunkSound; + sfxHandle_t crateBreakSound[2]; + + qhandle_t hackerIconShader; + + // Saber shaders + //----------------------------- + qhandle_t forceCoronaShader; + + qhandle_t redSaberGlowShader; + qhandle_t redSaberCoreShader; + qhandle_t orangeSaberGlowShader; + qhandle_t orangeSaberCoreShader; + qhandle_t yellowSaberGlowShader; + qhandle_t yellowSaberCoreShader; + qhandle_t greenSaberGlowShader; + qhandle_t greenSaberCoreShader; + qhandle_t blueSaberGlowShader; + qhandle_t blueSaberCoreShader; + qhandle_t purpleSaberGlowShader; + qhandle_t purpleSaberCoreShader; + qhandle_t saberBlurShader; + qhandle_t swordTrailShader; + + qhandle_t yellowDroppedSaberShader; + + qhandle_t rivetMarkShader; + + qhandle_t teamRedShader; + qhandle_t teamBlueShader; + + qhandle_t powerDuelAllyShader; + + qhandle_t balloonShader; + qhandle_t vchatShader; + qhandle_t connectionShader; + + qhandle_t crosshairShader[NUM_CROSSHAIRS]; + qhandle_t lagometerShader; + qhandle_t backTileShader; + + qhandle_t numberShaders[11]; + qhandle_t smallnumberShaders[11]; + qhandle_t chunkyNumberShaders[11]; + + qhandle_t electricBodyShader; + qhandle_t electricBody2Shader; + + qhandle_t fsrMarkShader; + qhandle_t fslMarkShader; + qhandle_t fshrMarkShader; + qhandle_t fshlMarkShader; + + qhandle_t refractionShader; + + qhandle_t cloakedShader; + + qhandle_t boltShader; + + qhandle_t shadowMarkShader; + + //glass shard shader + qhandle_t glassShardShader; + + // wall mark shaders + qhandle_t wakeMarkShader; + + // Pain view shader + qhandle_t viewPainShader; + qhandle_t viewPainShader_Shields; + qhandle_t viewPainShader_ShieldsAndHealth; + + qhandle_t itemRespawningPlaceholder; + qhandle_t itemRespawningRezOut; + + qhandle_t playerShieldDamage; + qhandle_t protectShader; + qhandle_t forceSightBubble; + qhandle_t forceShell; + qhandle_t sightShell; + + // Disruptor zoom graphics + qhandle_t disruptorMask; + qhandle_t disruptorInsert; + qhandle_t disruptorLight; + qhandle_t disruptorInsertTick; + qhandle_t disruptorChargeShader; + + // Binocular graphics + qhandle_t binocularCircle; + qhandle_t binocularMask; + qhandle_t binocularArrow; + qhandle_t binocularTri; + qhandle_t binocularStatic; + qhandle_t binocularOverlay; + + // weapon effect models + qhandle_t lightningExplosionModel; + + // explosion assets + qhandle_t explosionModel; + qhandle_t surfaceExplosionShader; + + qhandle_t disruptorShader; + + qhandle_t solidWhite; + + qhandle_t heartShader; + + // All the player shells + qhandle_t ysaliredShader; + qhandle_t ysaliblueShader; + qhandle_t ysalimariShader; + qhandle_t boonShader; + qhandle_t endarkenmentShader; + qhandle_t enlightenmentShader; + qhandle_t invulnerabilityShader; + +#ifdef JK2AWARDS + // medals shown during gameplay + qhandle_t medalImpressive; + qhandle_t medalExcellent; + qhandle_t medalGauntlet; + qhandle_t medalDefend; + qhandle_t medalAssist; + qhandle_t medalCapture; +#endif + + // sounds + sfxHandle_t selectSound; + sfxHandle_t footsteps[FOOTSTEP_TOTAL][4]; + + sfxHandle_t winnerSound; + sfxHandle_t loserSound; + + sfxHandle_t crackleSound; + + sfxHandle_t grenadeBounce1; + sfxHandle_t grenadeBounce2; + + sfxHandle_t teamHealSound; + sfxHandle_t teamRegenSound; + + sfxHandle_t teleInSound; + sfxHandle_t teleOutSound; + sfxHandle_t respawnSound; + sfxHandle_t talkSound; + sfxHandle_t landSound; + sfxHandle_t fallSound; + + sfxHandle_t oneMinuteSound; + sfxHandle_t fiveMinuteSound; + + sfxHandle_t threeFragSound; + sfxHandle_t twoFragSound; + sfxHandle_t oneFragSound; + +#ifdef JK2AWARDS + sfxHandle_t impressiveSound; + sfxHandle_t excellentSound; + sfxHandle_t deniedSound; + sfxHandle_t humiliationSound; + sfxHandle_t defendSound; +#endif + + /* + sfxHandle_t takenLeadSound; + sfxHandle_t tiedLeadSound; + sfxHandle_t lostLeadSound; + */ + + sfxHandle_t rollSound; + + sfxHandle_t watrInSound; + sfxHandle_t watrOutSound; + sfxHandle_t watrUnSound; + + sfxHandle_t noforceSound; + + sfxHandle_t deploySeeker; + sfxHandle_t medkitSound; + + // teamplay sounds +#ifdef JK2AWARDS + sfxHandle_t captureAwardSound; +#endif + sfxHandle_t redScoredSound; + sfxHandle_t blueScoredSound; + sfxHandle_t redLeadsSound; + sfxHandle_t blueLeadsSound; + sfxHandle_t teamsTiedSound; + + sfxHandle_t redFlagReturnedSound; + sfxHandle_t blueFlagReturnedSound; + sfxHandle_t redTookFlagSound; + sfxHandle_t blueTookFlagSound; + + sfxHandle_t redYsalReturnedSound; + sfxHandle_t blueYsalReturnedSound; + sfxHandle_t redTookYsalSound; + sfxHandle_t blueTookYsalSound; + + sfxHandle_t drainSound; + + //music blips + sfxHandle_t happyMusic; + sfxHandle_t dramaticFailure; + + // tournament sounds + sfxHandle_t count3Sound; + sfxHandle_t count2Sound; + sfxHandle_t count1Sound; + sfxHandle_t countFightSound; + + // 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]; + + qhandle_t halfShieldModel; + qhandle_t halfShieldShader; + + qhandle_t demp2Shell; + qhandle_t demp2ShellShader; + + qhandle_t cursor; + qhandle_t selectCursor; + qhandle_t sizeCursor; + + //weapon icons + qhandle_t weaponIcons[WP_NUM_WEAPONS]; + qhandle_t weaponIcons_NA[WP_NUM_WEAPONS]; + + //holdable inventory item icons + qhandle_t invenIcons[HI_NUM_HOLDABLE]; + + //force power icons + qhandle_t forcePowerIcons[NUM_FORCE_POWERS]; + + qhandle_t rageRecShader; + + //other HUD parts + int currentBackground; + qhandle_t weaponIconBackground; + qhandle_t forceIconBackground; + qhandle_t inventoryIconBackground; + + sfxHandle_t holocronPickup; + + // Zoom + sfxHandle_t zoomStart; + sfxHandle_t zoomLoop; + sfxHandle_t zoomEnd; + sfxHandle_t disruptorZoomLoop; + + qhandle_t bdecal_bodyburn1; + qhandle_t bdecal_saberglow; + qhandle_t bdecal_burn1; + qhandle_t mSaberDamageGlow; + + // For vehicles only now + sfxHandle_t noAmmoSound; + +} cgMedia_t; + + +// Stored FX handles +//-------------------- +typedef struct +{ + //concussion + fxHandle_t concussionShotEffect; + fxHandle_t concussionImpactEffect; + + // BRYAR PISTOL + fxHandle_t bryarShotEffect; + fxHandle_t bryarPowerupShotEffect; + fxHandle_t bryarWallImpactEffect; + fxHandle_t bryarWallImpactEffect2; + fxHandle_t bryarWallImpactEffect3; + fxHandle_t bryarFleshImpactEffect; + fxHandle_t bryarDroidImpactEffect; + + // BLASTER + fxHandle_t blasterShotEffect; + fxHandle_t blasterWallImpactEffect; + fxHandle_t blasterFleshImpactEffect; + fxHandle_t blasterDroidImpactEffect; + + // DISRUPTOR + fxHandle_t disruptorRingsEffect; + fxHandle_t disruptorProjectileEffect; + fxHandle_t disruptorWallImpactEffect; + fxHandle_t disruptorFleshImpactEffect; + fxHandle_t disruptorAltMissEffect; + fxHandle_t disruptorAltHitEffect; + + // BOWCASTER + fxHandle_t bowcasterShotEffect; + fxHandle_t bowcasterImpactEffect; + + // REPEATER + fxHandle_t repeaterProjectileEffect; + fxHandle_t repeaterAltProjectileEffect; + fxHandle_t repeaterWallImpactEffect; + fxHandle_t repeaterFleshImpactEffect; + fxHandle_t repeaterAltWallImpactEffect; + + // DEMP2 + fxHandle_t demp2ProjectileEffect; + fxHandle_t demp2WallImpactEffect; + fxHandle_t demp2FleshImpactEffect; + + // FLECHETTE + fxHandle_t flechetteShotEffect; + fxHandle_t flechetteAltShotEffect; + fxHandle_t flechetteWallImpactEffect; + fxHandle_t flechetteFleshImpactEffect; + + // ROCKET + fxHandle_t rocketShotEffect; + fxHandle_t rocketExplosionEffect; + + // THERMAL + fxHandle_t thermalExplosionEffect; + fxHandle_t thermalShockwaveEffect; + + // TRIPMINE + fxHandle_t tripmineLaserFX; + fxHandle_t tripmineGlowFX; + + //FORCE + fxHandle_t forceLightning; + fxHandle_t forceLightningWide; + + fxHandle_t forceDrain; + fxHandle_t forceDrainWide; + fxHandle_t forceDrained; + + //TURRET + fxHandle_t turretShotEffect; + + //Whatever + fxHandle_t itemCone; + + fxHandle_t mSparks; + fxHandle_t mSaberCut; + fxHandle_t mTurretMuzzleFlash; + fxHandle_t mSaberBlock; + fxHandle_t mSaberBloodSparks; + fxHandle_t mSaberBloodSparksSmall; + fxHandle_t mSaberBloodSparksMid; + fxHandle_t mSpawn; + fxHandle_t mJediSpawn; + fxHandle_t mBlasterDeflect; + fxHandle_t mBlasterSmoke; + fxHandle_t mForceConfustionOld; + fxHandle_t mDisruptorDeathSmoke; + fxHandle_t mSparkExplosion; + fxHandle_t mTurretExplode; + fxHandle_t mEmplacedExplode; + fxHandle_t mEmplacedDeadSmoke; + fxHandle_t mTripmineExplosion; + fxHandle_t mDetpackExplosion; + fxHandle_t mFlechetteAltBlow; + fxHandle_t mStunBatonFleshImpact; + fxHandle_t mAltDetonate; + fxHandle_t mSparksExplodeNoSound; + fxHandle_t mTripMineLaster; + fxHandle_t mEmplacedMuzzleFlash; + fxHandle_t mConcussionAltRing; + fxHandle_t mHyperspaceStars; + fxHandle_t mBlackSmoke; + fxHandle_t mShipDestDestroyed; + fxHandle_t mShipDestBurning; + fxHandle_t mBobaJet; + + //footstep effects + fxHandle_t footstepMud; + fxHandle_t footstepSand; + fxHandle_t footstepSnow; + fxHandle_t footstepGravel; + //landing effects + fxHandle_t landingMud; + fxHandle_t landingSand; + fxHandle_t landingDirt; + fxHandle_t landingSnow; + fxHandle_t landingGravel; + //splashes + fxHandle_t waterSplash; + fxHandle_t lavaSplash; + fxHandle_t acidSplash; +} cgEffects_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 + int siegeTeamSwitch; + int showDuelHealths; + gametype_t gametype; + int debugMelee; + int stepSlideFix; + int noSpecMove; + int dmflags; + int teamflags; + int fraglimit; + int duel_fraglimit; + int capturelimit; + int timelimit; + int maxclients; + qboolean needpass; + qboolean jediVmerc; + int wDisable; + int fDisable; + + 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 jediMaster; + int duelWinner; + int duelist1; + int duelist2; + int duelist3; +// nmckenzie: DUEL_HEALTH. hmm. + int duelist1health; + int duelist2health; + int duelist3health; + + 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]; + fxHandle_t gameEffects[MAX_FX]; + qhandle_t gameIcons[MAX_ICONS]; + + int numInlineModels; + qhandle_t inlineDrawModel[MAX_MODELS]; + vec3_t inlineModelMidpoints[MAX_MODELS]; + + clientInfo_t clientinfo[MAX_CLIENTS]; + + int cursorX; + int cursorY; + qboolean eventHandling; + qboolean mouseCaptured; + qboolean sizingHud; + void *capturedItem; + qhandle_t activeCursor; + + // media + cgMedia_t media; + + // effects + cgEffects_t effects; + +} cgs_t; + +typedef struct siegeExtended_s +{ + int health; + int maxhealth; + int ammo; + int weapon; + int lastUpdated; +} siegeExtended_t; + +//keep an entry available for each client +extern siegeExtended_t cg_siegeExtendedData[MAX_CLIENTS]; + +//============================================================================== + +extern cgs_t cgs; +extern cg_t cg; +extern centity_t cg_entities[MAX_GENTITIES]; + +extern centity_t *cg_permanents[MAX_GENTITIES]; +extern int cg_numpermanents; + +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_renderToTextureFX; +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_drawRadar; +extern vmCvar_t cg_drawVehLeadIndicator; +extern vmCvar_t cg_drawAutomap; +extern vmCvar_t cg_drawScores; +extern vmCvar_t cg_dynamicCrosshair; +extern vmCvar_t cg_dynamicCrosshairPrecision; +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_errorDecay; +extern vmCvar_t cg_nopredict; +extern vmCvar_t cg_noPlayerAnims; +extern vmCvar_t cg_showmiss; +extern vmCvar_t cg_showVehMiss; +extern vmCvar_t cg_footsteps; +extern vmCvar_t cg_addMarks; +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_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_swingAngles; + +extern vmCvar_t cg_oldPainSounds; + +extern vmCvar_t cg_ragDoll; + +extern vmCvar_t cg_jumpSounds; + +extern vmCvar_t cg_autoMap; +extern vmCvar_t cg_autoMapX; +extern vmCvar_t cg_autoMapY; +extern vmCvar_t cg_autoMapW; +extern vmCvar_t cg_autoMapH; + +extern vmCvar_t bg_fighterAltControl; + +extern vmCvar_t cg_chatBox; +extern vmCvar_t cg_chatBoxHeight; + +extern vmCvar_t cg_saberModelTraceEffect; + +extern vmCvar_t cg_saberClientVisualCompensation; + +extern vmCvar_t cg_g2TraceLod; + +extern vmCvar_t cg_fpls; + +extern vmCvar_t cg_ghoul2Marks; + +extern vmCvar_t cg_optvehtrace; + +extern vmCvar_t cg_saberDynamicMarks; +extern vmCvar_t cg_saberDynamicMarkTime; + +extern vmCvar_t cg_saberContact; +extern vmCvar_t cg_saberTrail; + +extern vmCvar_t cg_duelHeadAngles; + +extern vmCvar_t cg_speedTrail; +extern vmCvar_t cg_auraShell; + +extern vmCvar_t cg_repeaterOrb; + +extern vmCvar_t cg_animBlend; + +extern vmCvar_t cg_dismember; + +extern vmCvar_t cg_thirdPersonSpecialCam; + +extern vmCvar_t cg_thirdPerson; +extern vmCvar_t cg_thirdPersonRange; +extern vmCvar_t cg_thirdPersonAngle; +extern vmCvar_t cg_thirdPersonPitchOffset; +extern vmCvar_t cg_thirdPersonVertOffset; +extern vmCvar_t cg_thirdPersonCameraDamp; +extern vmCvar_t cg_thirdPersonTargetDamp; + +extern vmCvar_t cg_thirdPersonAlpha; +extern vmCvar_t cg_thirdPersonHorzOffset; + +extern vmCvar_t cg_stereoSeparation; +extern vmCvar_t cg_lagometer; +extern vmCvar_t cg_drawEnemyInfo; +extern vmCvar_t cg_synchronousClients; +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_hudFiles; +extern vmCvar_t cg_smoothClients; + +#include "../namespace_begin.h" +extern vmCvar_t pmove_fixed; +extern vmCvar_t pmove_msec; +#include "../namespace_end.h" + +//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_trueLightning; + +//extern vmCvar_t cg_redTeamName; +//extern vmCvar_t cg_blueTeamName; +extern vmCvar_t cg_currentSelectedPlayer; +extern vmCvar_t cg_currentSelectedPlayerName; +//extern vmCvar_t cg_singlePlayerActive; +extern vmCvar_t cg_recordSPDemo; +extern vmCvar_t cg_recordSPDemoName; + +extern vmCvar_t ui_myteam; +extern vmCvar_t cg_snapshotTimeout; +/* +Ghoul2 Insert Start +*/ + +extern vmCvar_t cg_debugBB; + +/* +Ghoul2 Insert End +*/ + +// +// cg_main.c +// +void CG_DrawMiscEnts(void); + +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( qboolean bForceStart ); + +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); +void CG_BuildSpectatorString(void); +void CG_NextInventory_f(void); +void CG_PrevInventory_f(void); +void CG_NextForcePower_f(void); +void CG_PrevForcePower_f(void); + +// +// 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 ); +/* +Ghoul2 Insert Start +*/ + +void CG_TestG2Model_f (void); +void CG_TestModelSurfaceOnOff_f(void); +void CG_ListModelSurfaces_f (void); +void CG_ListModelBones_f (void); +void CG_TestModelSetAnglespre_f(void); +void CG_TestModelSetAnglespost_f(void); +void CG_TestModelAnimate_f(void); +/* +Ghoul2 Insert End +*/ + +// +// cg_drawtools.c +// +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_DrawRotatePic( float x, float y, float width, float height,float angle, qhandle_t hShader ); +void CG_DrawRotatePic2( float x, float y, float width, float height,float angle, qhandle_t hShader ); +void CG_DrawString( float x, float y, const char *string, + float charWidth, float charHeight, const float *modulate ); + +void CG_DrawNumField (int x, int y, int width, int value,int charWidth,int charHeight,int style,qboolean zeroFill); + +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 UI_DrawScaledProportionalString( int x, int y, const char* str, int style, vec4_t color, float scale); +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 char systemChat[256]; + +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,int font); +void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style, int iMenuFont); +int CG_Text_Width(const char *text, float scale, int iMenuFont); +int CG_Text_Height(const char *text, float scale, int iMenuFont); +float CG_GetValue(int ownerDraw); +qboolean CG_OwnerDrawVisible(int flags); +void CG_RunMenuScript(char **args); +qboolean CG_DeferMenuScript(char **args); +void CG_ShowResponseHead(void); +void CG_GetTeamColor(vec4_t *color); +const char *CG_GetGameStatusText(void); +const char *CG_GetKillerText(void); +void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, void *ghoul2, int g2radius, 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); +const char *CG_GameTypeString(void); +qboolean CG_YourTeamHasFlag(void); +qboolean CG_OtherTeamHasFlag(void); +qhandle_t CG_StatusHandle(int task); + + + +// +// cg_player.c +// +qboolean CG_RagDoll(centity_t *cent, vec3_t forcedAngles); +qboolean CG_G2TraceCollide(trace_t *tr, const vec3_t mins, const vec3_t maxs, const vec3_t lastValidStart, const vec3_t lastValidEnd); +void CG_AddGhoul2Mark(int shader, float size, vec3_t start, vec3_t end, int entnum, + vec3_t entposition, float entangle, void *ghoul2, vec3_t scale, int lifeTime); + +void CG_CreateNPCClient(clientInfo_t **ci); +void CG_DestroyNPCClient(clientInfo_t **ci); + +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, qboolean entitiesInitialized ); +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ); +void CG_PlayerShieldHit(int entitynum, vec3_t angles, int amount); + + +// +// 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_G2Trace( 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 ); +void CG_ReattachLimb(centity_t *source); + + +// +// cg_ents.c +// + +void CG_S_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx); +void CG_S_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx); +void CG_S_StopLoopingSound(int entityNum, sfxHandle_t sfx); +void CG_S_UpdateLoopingSounds(int entityNum); + +void CG_SetEntitySoundPosition( centity_t *cent ); +void CG_AddPacketEntities( qboolean isPortal ); +void CG_ManualEntityRender(centity_t *cent); +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 ); + +/* +Ghoul2 Insert Start +*/ +void ScaleModelAxis(refEntity_t *ent); +/* +Ghoul2 Insert End +*/ + +// +// cg_turret.c +// +void TurretClientRun(centity_t *ent); + +// +// cg_weapons.c +// +void CG_GetClientWeaponMuzzleBoltPoint(int clIndex, vec3_t to); + +void CG_NextWeapon_f( void ); +void CG_PrevWeapon_f( void ); +void CG_Weapon_f( void ); +void CG_WeaponClean_f( void ); + +void CG_RegisterWeapon( int weaponNum); +void CG_RegisterItemVisuals( int itemNum ); + +void CG_FireWeapon( centity_t *cent, qboolean alt_fire ); +void CG_MissileHitWall(int weapon, int clientNum, vec3_t origin, vec3_t dir, impactSound_t soundType, qboolean alt_fire, int charge); +void CG_MissileHitPlayer( int weapon, vec3_t origin, vec3_t dir, int entityNum, qboolean alt_fire); + +void CG_AddViewWeapon (playerState_t *ps); +void CG_AddPlayerWeapon( refEntity_t *parent, playerState_t *ps, centity_t *cent, int team, vec3_t newAngles, qboolean thirdPerson ); +void CG_DrawWeaponSelect( void ); +void CG_DrawIconBackground(void); + +void CG_OutOfAmmoChange( int oldWeapon ); // should this be in pmove? + +// +// 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_GlassShatter(int entnum, vec3_t dmgPt, vec3_t dmgDir, float dmgRadius, int maxShards); +void CG_ScorePlum( int client, vec3_t org, int score ); + +void CG_Chunks( int owner, vec3_t origin, const vec3_t normal, const vec3_t mins, const vec3_t maxs, + float speed, int numChunks, material_t chunkType, int customChunk, float baseScale ); +void CG_MiscModelExplosion( vec3_t mins, vec3_t maxs, int size, material_t chunkType ); + +void CG_Bleed( vec3_t origin, int entityNum ); + +localEntity_t *CG_MakeExplosion( vec3_t origin, vec3_t dir, + qhandle_t hModel, int numframes, qhandle_t shader, int msec, + qboolean isSprite, float scale, int flags );// Overloaded in single player + +void CG_SurfaceExplosion( vec3_t origin, vec3_t normal, float radius, float shake_speed, qboolean smoke ); + +void CG_TestLine( vec3_t start, vec3_t end, int time, unsigned int color, int radius); + +void CG_InitGlass( void ); + +// +// 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_ShaderStateChanged(void); + +// +// cg_playerstate.c +// +int CG_IsMindTricked(int trickIndex1, int trickIndex2, int trickIndex3, int trickIndex4, int client); +void CG_Respawn( void ); +void CG_TransitionPlayerState( playerState_t *ps, playerState_t *ops ); +void CG_CheckChangedPredictableEvents( playerState_t *ps ); + + +// +// cg_siege.c +// +void CG_InitSiegeMode(void); +void CG_SiegeRoundOver(centity_t *ent, int won); +void CG_SiegeObjectiveCompleted(centity_t *ent, int won, int objectivenum); + + + +//=============================================== + +// +// system traps +// These functions are how the cgame communicates with the main game system +// + +#include "../namespace_begin.h" + +// 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 ); + +//rww - precision timer funcs... -ALWAYS- call end after start with supplied ptr, or you'll get a nasty memory leak. +//not that you should be using these outside of debug anyway.. because you shouldn't be. So don't. +void trap_PrecisionTimer_Start(void **theNewTimer); +int trap_PrecisionTimer_End(void *theTimer); + +// 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 ); +int trap_Cvar_GetHiddenVarValue(const char *name); + +// 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_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); + +// 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, qboolean SubBSP ); +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 +int trap_S_GetVoiceVolume( int entityNum ); +void trap_S_MuteSound( int entityNum, int entchannel ); +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( void ); +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 ); + +// repatialize 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 ); +void trap_S_ShutUp(qboolean shutUpFactor); +sfxHandle_t trap_S_RegisterSound( const char *sample); // returns buzz if not found +void trap_S_StartBackgroundTrack( const char *intro, const char *loop, qboolean bReturnWithoutStarting); // empty name stops music +void trap_S_StopBackgroundTrack( void ); + +void trap_S_UpdateAmbientSet( const char *name, vec3_t origin ); +void trap_AS_ParseSets( void ); +void trap_AS_AddPrecacheEntry( const char *name ); +int trap_S_AddLocalSet( const char *name, vec3_t listener_origin, vec3_t origin, int entID, int time ); +sfxHandle_t trap_AS_GetBModelSound( const char *name, int stage ); + +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 +qhandle_t trap_R_RegisterFont( const char *name ); +int trap_R_Font_StrLenPixels(const char *text, const int iFontIndex, const float scale); +int trap_R_Font_StrLenChars(const char *text); +int trap_R_Font_HeightPixels(const int iFontIndex, const float scale); +void trap_R_Font_DrawString(int ox, int oy, const char *text, const float *rgba, const int setIndex, int iCharLimit, const float scale); +qboolean trap_Language_IsAsian(void); +qboolean trap_Language_UsesSpaces(void); +unsigned trap_AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation/* = NULL*/ ); + + +// 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_ClearDecals ( 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_AddDecalToScene ( qhandle_t shader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ); +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 ); +// Does weird, barely controllable rotation behaviour +void trap_R_DrawRotatePic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); +// rotates image around exact center point of passed in coords +void trap_R_DrawRotatePic2( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ); + +void trap_R_SetRangeFog(float range); + +void trap_R_SetRefractProp(float alpha, float stretch, qboolean prepost, qboolean negate); + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ); + +void trap_R_GetLightStyle(int style, color4ub_t color); +void trap_R_SetLightStyle(int style, int color); + +void trap_R_GetBModelVerts(int bmodelIndex, vec3_t *verts, vec3_t normal ); + +void trap_R_GetDistanceCull(float *f); + +void trap_R_GetRealRes(int *w, int *h); //get screen resolution -rww +void trap_R_AutomapElevAdj(float newHeight); //automap elevation setting -rww +qboolean trap_R_InitWireframeAutomap(void); //initialize automap -rww + + +void trap_FX_AddLine( const vec3_t start, const vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t sRGB, const vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags); + +// 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 ); + + +qboolean trap_GetDefaultState(int entityIndex, entityState_t *state ); + +// 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, float mPitchOverride, float mYawOverride, float mSensitivityOverride, int fpSel, int invenSel, qboolean fighterControls ); + +void trap_SetClientForceAngle(int time, vec3_t angle); +void trap_SetClientTurnExtent(float turnAdd, float turnSub, int turnTime); + +void trap_OpenUIMenu(int menuID); + +// aids for VM testing +void testPrintInt( char *string, int i ); +void testPrintFloat( char *string, float f ); + +int trap_MemoryRemaining( void ); +qboolean trap_Key_IsDown( int keynum ); +int trap_Key_GetCatcher( void ); +void trap_Key_SetCatcher( int catcher ); +int trap_Key_GetKey( const char *binding ); + +void BG_CycleInven(playerState_t *ps, int direction); +int BG_ProperForceIndex(int power); +void BG_CycleForce(playerState_t *ps, int direction); + +#include "../namespace_end.h" + + +typedef enum { + SYSTEM_PRINT, + CHAT_PRINT, + TEAMCHAT_PRINT +} q3print_t; // bk001201 - warning: useless keyword or type name in empty declaration + +#include "../namespace_begin.h" + +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 ); +qboolean trap_R_inPVS( const vec3_t p1, const vec3_t p2, byte *mask ); + +int trap_FX_InitSystem ( refdef_t* ); +void trap_FX_SetRefDef ( refdef_t* refdef ); +int trap_FX_RegisterEffect ( const char *file); +void trap_FX_PlayEffect ( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ); // builds arbitrary perp. right vector, does a cross product to define up +void trap_FX_PlayEntityEffect ( const char *file, vec3_t org, vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ); +void trap_FX_PlayEffectID ( int id, vec3_t org, vec3_t fwd, int vol, int rad ); // builds arbitrary perp. right vector, does a cross product to define up +void trap_FX_PlayPortalEffectID ( int id, vec3_t org, vec3_t fwd, int vol, int rad ); // builds arbitrary perp. right vector, does a cross product to define up +void trap_FX_PlayEntityEffectID ( int id, vec3_t org, vec3_t axis[3], const int boltInfo, const int pGhoul2, int vol, int rad ); +void trap_FX_PlayBoltedEffectID ( int id, vec3_t org, void *pGhoul2, const int boltNum, const int entNum, const int modelNum, int iLooptime, qboolean isRelative ); +void trap_FX_AddScheduledEffects ( qboolean skyPortal ); +void trap_FX_Draw2DEffects ( float screenXScale, float screenYScale ); +qboolean trap_FX_FreeSystem ( void ); +void trap_FX_AdjustTime ( int time ); +void trap_FX_Reset ( void ); + +//rww - additional funcs for adding custom incode stuff +void trap_FX_AddPoly( addpolyArgStruct_t *p ); +void trap_FX_AddBezier( addbezierArgStruct_t *p ); +void trap_FX_AddPrimitive( effectTrailArgStruct_t *p ); +void trap_FX_AddSprite( addspriteArgStruct_t *p ); +void trap_FX_AddElectricity( addElectricityArgStruct_t *p ); + +//void trap_SP_Print(const unsigned ID, byte *Data); +int trap_SP_GetStringTextString(const char *text, char *buffer, int bufferLength); + +void trap_CG_RegisterSharedMemory(char *memory); + +int trap_CM_RegisterTerrain(const char *config); +void trap_RMG_Init(int terrainID, const char *terrainInfo); +void trap_RE_InitRendererTerrain( const char *info ); +void trap_R_WeatherContentsOverride( int contents ); +void trap_R_WorldEffectCommand(const char *cmd); +void trap_WE_AddWeatherZone( const vec3_t mins, const vec3_t maxs ); + +qboolean trap_ROFF_Clean( void ); +void trap_ROFF_UpdateEntities( void ); + int trap_ROFF_Cache( char *file ); +qboolean trap_ROFF_Play( int entID, int roffID, qboolean doTranslation ); +qboolean trap_ROFF_Purge_Ent( int entID ); + +//rww - dynamic vm memory allocation! +void trap_TrueMalloc(void **ptr, int size); +void trap_TrueFree(void **ptr); + +#include "../namespace_end.h" + +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); +const char *CG_GetStringEdString(char *refSection, char *refName); +extern qboolean initparticles; +int CG_NewParticleArea ( int num ); + +void FX_TurretProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_TurretHitWall( vec3_t origin, vec3_t normal ); +void FX_TurretHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +void FX_ConcussionHitWall( vec3_t origin, vec3_t normal ); +void FX_ConcussionHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_ConcussionProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_ConcAltShot( vec3_t start, vec3_t end ); + +//----------------------------- +// Effects related prototypes +//----------------------------- + +// Environmental effects +void CG_Spark( vec3_t origin, vec3_t dir ); + +// Weapon prototypes +void FX_BryarHitWall( vec3_t origin, vec3_t normal ); +void FX_BryarAltHitWall( vec3_t origin, vec3_t normal, int power ); +void FX_BryarHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_BryarAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterWeaponHitWall( vec3_t origin, vec3_t normal ); +void FX_BlasterWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + + +void FX_ForceDrained(vec3_t origin, vec3_t dir); + + +//----------------------------- +// Effects related prototypes +//----------------------------- + +// Environmental effects +void CG_Spark( vec3_t origin, vec3_t dir ); + +// Weapon prototypes +void FX_BryarHitWall( vec3_t origin, vec3_t normal ); +void FX_BryarAltHitWall( vec3_t origin, vec3_t normal, int power ); +void FX_BryarHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_BryarAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterWeaponHitWall( vec3_t origin, vec3_t normal ); +void FX_BlasterWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +#include "../namespace_begin.h" + +void trap_G2API_CollisionDetect ( CollisionRecord_t *collRecMap, void* ghoul2, const vec3_t angles, const vec3_t position,int frameNumber, int entNum, const vec3_t rayStart, const vec3_t rayEnd, const vec3_t scale, int traceFlags, int useLod, float fRadius ); +void trap_G2API_CollisionDetectCache ( CollisionRecord_t *collRecMap, void* ghoul2, const vec3_t angles, const vec3_t position,int frameNumber, int entNum, const vec3_t rayStart, const vec3_t rayEnd, const vec3_t scale, int traceFlags, int useLod, float fRadius ); + +/* +Ghoul2 Insert Start +*/ +// CG specific API access +void trap_G2_ListModelSurfaces(void *ghlInfo); +void trap_G2_ListModelBones(void *ghlInfo, int frame); +void trap_G2_SetGhoul2ModelIndexes(void *ghoul2, qhandle_t *modelList, qhandle_t *skinList); +qboolean trap_G2_HaveWeGhoul2Models(void *ghoul2); +qboolean trap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +qboolean trap_G2API_GetBoltMatrix_NoReconstruct(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +qboolean trap_G2API_GetBoltMatrix_NoRecNoRot(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +int trap_G2API_InitGhoul2Model(void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias); +qboolean trap_G2API_SetSkin(void *ghoul2, int modelIndex, qhandle_t customSkin, qhandle_t renderSkin); + +int trap_G2API_CopyGhoul2Instance(void *g2From, void *g2To, int modelIndex); +void trap_G2API_CopySpecificGhoul2Model(void *g2From, int modelFrom, void *g2To, int modelTo); +void trap_G2API_DuplicateGhoul2Instance(void *g2From, void **g2To); +qboolean trap_G2API_HasGhoul2ModelOnIndex(void *ghlInfo, int modelIndex); +qboolean trap_G2API_RemoveGhoul2Model(void *ghlInfo, int modelIndex); + +qboolean trap_G2API_SkinlessModel(void *ghlInfo, int modelIndex); + +//rww - for adding gore (or whatever) shaders to the g2 model +int trap_G2API_GetNumGoreMarks(void *ghlInfo, int modelIndex); +void trap_G2API_AddSkinGore(void *ghlInfo,SSkinGoreData *gore); +void trap_G2API_ClearSkinGore ( void* ghlInfo ); + +int trap_G2API_Ghoul2Size ( void* ghlInfo ); + +int trap_G2API_AddBolt(void *ghoul2, int modelIndex, const char *boneName); +void trap_G2API_SetBoltInfo(void *ghoul2, int modelIndex, int boltInfo); +qboolean trap_G2API_AttachEnt(int *boltInfo, void *ghlInfoTo, int toBoltIndex, int entNum, int toModelNum); + +void trap_G2API_CleanGhoul2Models(void **ghoul2Ptr); +qboolean trap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ); +void trap_G2API_GetGLAName(void *ghoul2, int modelIndex, char *fillBuf); +qboolean trap_G2API_SetBoneAnim(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ); +qboolean trap_G2API_GetBoneAnim(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *startFrame, + int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex); +qboolean trap_G2API_GetBoneFrame(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *modelList, const int modelIndex); + +qboolean trap_G2API_SetRootSurface(void *ghoul2, const int modelIndex, const char *surfaceName); +qboolean trap_G2API_SetSurfaceOnOff(void *ghoul2, const char *surfaceName, const int flags); +qboolean trap_G2API_SetNewOrigin(void *ghoul2, const int boltIndex); +qboolean trap_G2API_DoesBoneExist(void *ghoul2, int modelIndex, const char *boneName); +int trap_G2API_GetSurfaceRenderStatus(void *ghoul2, const int modelIndex, const char *surfaceName); + +int trap_G2API_GetTime(void); +void trap_G2API_SetTime(int time, int clock); + +void trap_G2API_AbsurdSmoothing(void *ghoul2, qboolean status); + +void trap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params); +void trap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params); + +//additional ragdoll options -rww +qboolean trap_G2API_RagPCJConstraint(void *ghoul2, const char *boneName, vec3_t min, vec3_t max); //override default pcj bonee constraints +qboolean trap_G2API_RagPCJGradientSpeed(void *ghoul2, const char *boneName, const float speed); //override the default gradient movespeed for a pcj bone +qboolean trap_G2API_RagEffectorGoal(void *ghoul2, const char *boneName, vec3_t pos); //override an effector bone's goal position (world coordinates) +qboolean trap_G2API_GetRagBonePos(void *ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale); //current position of said bone is put into pos (world coordinates) +qboolean trap_G2API_RagEffectorKick(void *ghoul2, const char *boneName, vec3_t velocity); //add velocity to a rag bone +qboolean trap_G2API_RagForceSolve(void *ghoul2, qboolean force); //make sure we are actively performing solve/settle routines, if desired + +qboolean trap_G2API_SetBoneIKState(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params); +qboolean trap_G2API_IKMove(void *ghoul2, int time, sharedIKMoveParams_t *params); + +//for removing bones so they no longer have their own seperate animation hierarchy. Or whatever reason you may have. -rww +qboolean trap_G2API_RemoveBone(void *ghoul2, const char *boneName, int modelIndex); + +void trap_G2API_AttachInstanceToEntNum(void *ghoul2, int entityNum, qboolean server); +void trap_G2API_ClearAttachedInstance(int entityNum); +void trap_G2API_CleanEntAttachments(void); +qboolean trap_G2API_OverrideServer(void *serverInstance); + +void trap_G2API_GetSurfaceName(void *ghoul2, int surfNumber, int modelIndex, char *fillBuf); + +#include "../namespace_end.h" + +void CG_Init_CG(void); +void CG_Init_CGents(void); + + +void CG_SetGhoul2Info( refEntity_t *ent, centity_t *cent); +void CG_CreateBBRefEnts(entityState_t *s1, vec3_t origin ); + +void CG_InitG2Weapons(void); +void CG_ShutDownG2Weapons(void); +void CG_CopyG2WeaponInstance(centity_t *cent, int weaponNum, void *toGhoul2); +void *CG_G2WeaponInstance(centity_t *cent, int weapon); +void CG_CheckPlayerG2Weapons(playerState_t *ps, centity_t *cent); + +void CG_SetSiegeTimerCvar( int msec ); + +/* +Ghoul2 Insert End +*/ diff --git a/code/cgame/cg_localents.c b/code/cgame/cg_localents.c new file mode 100644 index 0000000..12fd070 --- /dev/null +++ b/code/cgame/cg_localents.c @@ -0,0 +1,869 @@ +// 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*/0 ); + // 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 ) { + // half the fragments will make a bounce sounds + if ( rand() & 1 ) + { + sfxHandle_t s = 0; + + switch( le->leBounceSoundType ) + { + case LEBS_ROCK: + s = cgs.media.rockBounceSound[Q_irand(0,1)]; + break; + case LEBS_METAL: + s = cgs.media.metalBounceSound[Q_irand(0,1)];// FIXME: make sure that this sound is registered properly...might still be rock bounce sound.... + break; + default: + return; + } + + if ( s ) + { + trap_S_StartSound( trace->endpos, ENTITYNUM_WORLD, CHAN_AUTO, s ); + } + + // bouncers only make the sound once... + // FIXME: arbitrary...change if it bugs you + le->leBounceSoundType = LEBS_NONE; + } + else if ( rand() & 1 ) + { + // we may end up bouncing again, but each bounce reduces the chance of playing the sound again or they may make a lot of noise when they settle + // FIXME: maybe just always do this?? + 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->forceAlpha) + { + le->refEntity.renderfx |= RF_FORCE_ENT_ALPHA; + le->refEntity.shaderRGBA[3] = le->forceAlpha; + } + + if ( le->pos.trType == TR_STATIONARY ) { + // sink into the ground if near the removal time + int t; + float t_e; + + t = le->endTime - cg.time; + if ( t < (SINK_TIME*2) ) { + le->refEntity.renderfx |= RF_FORCE_ENT_ALPHA; + t_e = (float)((float)(le->endTime - cg.time)/(SINK_TIME*2)); + t_e = (int)((t_e)*255); + + if (t_e > 255) + { + t_e = 255; + } + if (t_e < 1) + { + t_e = 1; + } + + if (le->refEntity.shaderRGBA[3] && t_e > le->refEntity.shaderRGBA[3]) + { + t_e = le->refEntity.shaderRGBA[3]; + } + + le->refEntity.shaderRGBA[3] = t_e; + + trap_R_AddRefEntityToScene( &le->refEntity ); + } 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 ); + ScaleModelAxis(&le->refEntity); + } + + 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; + } + + if (!trace.startsolid) + { + // leave a mark + CG_FragmentBounceMark( le, &trace ); + + // do a bouncy sound + CG_FragmentBounceSound( le, &trace ); + + if (le->bounceSound) + { //specified bounce sound (debris) + trap_S_StartSound(le->pos.trBase, ENTITYNUM_WORLD, CHAN_AUTO, le->bounceSound); + } + + // 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 ); +} + +static void CG_AddFadeScaleModel( localEntity_t *le ) +{ + refEntity_t *ent = &le->refEntity; + + float frac = ( cg.time - le->startTime )/((float)( le->endTime - le->startTime )); + + frac *= frac * frac; // yes, this is completely ridiculous...but it causes the shell to grow slowly then "explode" at the end + + ent->nonNormalizedAxes = qtrue; + + AxisCopy( axisDefault, ent->axis ); + + VectorScale( ent->axis[0], le->radius * frac, ent->axis[0] ); + VectorScale( ent->axis[1], le->radius * frac, ent->axis[1] ); + VectorScale( ent->axis[2], le->radius * 0.5f * frac, ent->axis[2] ); + + frac = 1.0f - frac; + + ent->shaderRGBA[0] = le->color[0] * frac; + ent->shaderRGBA[1] = le->color[1] * frac; + ent->shaderRGBA[2] = le->color[2] * frac; + ent->shaderRGBA[3] = le->color[3] * frac; + + // add the entity + trap_R_AddRefEntityToScene( ent ); +} + +/* +================== +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_AddPuff +================== +*/ +static void CG_AddPuff( localEntity_t *le ) { + refEntity_t *re; + float c; + vec3_t delta; + float len; + + re = &le->refEntity; + + // fade / grow time + c = ( le->endTime - cg.time ) / (float)( le->endTime - le->startTime ); + + re->shaderRGBA[0] = le->color[0] * c; + re->shaderRGBA[1] = le->color[1] * c; + re->shaderRGBA[2] = le->color[2] * c; + + 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; + + // 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] ); + } +} + + +/* +=================== +CG_AddRefEntity +=================== +*/ +void CG_AddRefEntity( localEntity_t *le ) { + if (le->endTime < cg.time) { + CG_FreeLocalEntity( le ); + return; + } + trap_R_AddRefEntityToScene( &le->refEntity ); +} + +/* +=================== +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_AddOLine + +For forcefields/other rectangular things +=================== +*/ +void CG_AddOLine( localEntity_t *le ) +{ + refEntity_t *re; + float frac, alpha; + + re = &le->refEntity; + + frac = (cg.time - le->startTime) / ( float ) ( le->endTime - le->startTime ); + if ( frac > 1 ) + frac = 1.0; // can happen during connection problems + else if (frac < 0) + frac = 0.0; + + // Use the liferate to set the scale over time. + re->data.line.width = le->data.line.width + (le->data.line.dwidth * frac); + if (re->data.line.width <= 0) + { + CG_FreeLocalEntity( le ); + return; + } + + // We will assume here that we want additive transparency effects. + alpha = le->alpha + (le->dalpha * frac); + re->shaderRGBA[0] = 0xff * alpha; + re->shaderRGBA[1] = 0xff * alpha; + re->shaderRGBA[2] = 0xff * alpha; + re->shaderRGBA[3] = 0xff * alpha; // Yes, we could apply c to this too, but fading the color is better for lines. + + re->shaderTexCoord[0] = 1; + re->shaderTexCoord[1] = 1; + + re->rotation = 90; + + re->reType = RT_ORIENTEDLINE; + + trap_R_AddRefEntityToScene( re ); +} + +/* +=================== +CG_AddLine + +for beams and the like. +=================== +*/ +void CG_AddLine( localEntity_t *le ) +{ + refEntity_t *re; + + re = &le->refEntity; + + re->reType = RT_LINE; + + 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_FADE_SCALE_MODEL: + CG_AddFadeScaleModel( le ); + break; + + case LE_FRAGMENT: // gibs and brass + CG_AddFragment( le ); + break; + + case LE_PUFF: + CG_AddPuff( 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; + + case LE_OLINE: + CG_AddOLine( le ); + break; + + case LE_SHOWREFENTITY: + CG_AddRefEntity( le ); + break; + + case LE_LINE: // oriented lines for FX + CG_AddLine( le ); + break; + } + } +} + + + + diff --git a/code/cgame/cg_main.c b/code/cgame/cg_main.c new file mode 100644 index 0000000..21ee618 --- /dev/null +++ b/code/cgame/cg_main.c @@ -0,0 +1,4170 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_main.c -- initialization and primary entry point for cgame +#include "cg_local.h" + +#include "../ui/ui_shared.h" +// display context for new ui stuff +displayContextDef_t cgDC; + +#if !defined(CL_LIGHT_H_INC) + #include "cg_lights.h" +#endif + +extern int cgSiegeRoundState; +extern int cgSiegeRoundTime; +/* +Ghoul2 Insert Start +*/ +void CG_InitItems(void); +/* +Ghoul2 Insert End +*/ + +void CG_InitJetpackGhoul2(void); +void CG_CleanJetpackGhoul2(void); + +vec4_t colorTable[CT_MAX] = +{ +{0, 0, 0, 0}, // CT_NONE +{0, 0, 0, 1}, // CT_BLACK +{1, 0, 0, 1}, // CT_RED +{0, 1, 0, 1}, // CT_GREEN +{0, 0, 1, 1}, // CT_BLUE +{1, 1, 0, 1}, // CT_YELLOW +{1, 0, 1, 1}, // CT_MAGENTA +{0, 1, 1, 1}, // CT_CYAN +{1, 1, 1, 1}, // CT_WHITE +{0.75f, 0.75f, 0.75f, 1}, // CT_LTGREY +{0.50f, 0.50f, 0.50f, 1}, // CT_MDGREY +{0.25f, 0.25f, 0.25f, 1}, // CT_DKGREY +{0.15f, 0.15f, 0.15f, 1}, // CT_DKGREY2 + +{0.810f, 0.530f, 0.0f, 1}, // CT_VLTORANGE -- needs values +{0.810f, 0.530f, 0.0f, 1}, // CT_LTORANGE +{0.610f, 0.330f, 0.0f, 1}, // CT_DKORANGE +{0.402f, 0.265f, 0.0f, 1}, // CT_VDKORANGE + +{0.503f, 0.375f, 0.996f, 1}, // CT_VLTBLUE1 +{0.367f, 0.261f, 0.722f, 1}, // CT_LTBLUE1 +{0.199f, 0.0f, 0.398f, 1}, // CT_DKBLUE1 +{0.160f, 0.117f, 0.324f, 1}, // CT_VDKBLUE1 + +{0.300f, 0.628f, 0.816f, 1}, // CT_VLTBLUE2 -- needs values +{0.300f, 0.628f, 0.816f, 1}, // CT_LTBLUE2 +{0.191f, 0.289f, 0.457f, 1}, // CT_DKBLUE2 +{0.125f, 0.250f, 0.324f, 1}, // CT_VDKBLUE2 + +{0.796f, 0.398f, 0.199f, 1}, // CT_VLTBROWN1 -- needs values +{0.796f, 0.398f, 0.199f, 1}, // CT_LTBROWN1 +{0.558f, 0.207f, 0.027f, 1}, // CT_DKBROWN1 +{0.328f, 0.125f, 0.035f, 1}, // CT_VDKBROWN1 + +{0.996f, 0.796f, 0.398f, 1}, // CT_VLTGOLD1 -- needs values +{0.996f, 0.796f, 0.398f, 1}, // CT_LTGOLD1 +{0.605f, 0.441f, 0.113f, 1}, // CT_DKGOLD1 +{0.386f, 0.308f, 0.148f, 1}, // CT_VDKGOLD1 + +{0.648f, 0.562f, 0.784f, 1}, // CT_VLTPURPLE1 -- needs values +{0.648f, 0.562f, 0.784f, 1}, // CT_LTPURPLE1 +{0.437f, 0.335f, 0.597f, 1}, // CT_DKPURPLE1 +{0.308f, 0.269f, 0.375f, 1}, // CT_VDKPURPLE1 + +{0.816f, 0.531f, 0.710f, 1}, // CT_VLTPURPLE2 -- needs values +{0.816f, 0.531f, 0.710f, 1}, // CT_LTPURPLE2 +{0.566f, 0.269f, 0.457f, 1}, // CT_DKPURPLE2 +{0.343f, 0.226f, 0.316f, 1}, // CT_VDKPURPLE2 + +{0.929f, 0.597f, 0.929f, 1}, // CT_VLTPURPLE3 +{0.570f, 0.371f, 0.570f, 1}, // CT_LTPURPLE3 +{0.355f, 0.199f, 0.355f, 1}, // CT_DKPURPLE3 +{0.285f, 0.136f, 0.230f, 1}, // CT_VDKPURPLE3 + +{0.953f, 0.378f, 0.250f, 1}, // CT_VLTRED1 +{0.953f, 0.378f, 0.250f, 1}, // CT_LTRED1 +{0.593f, 0.121f, 0.109f, 1}, // CT_DKRED1 +{0.429f, 0.171f, 0.113f, 1}, // CT_VDKRED1 +{.25f, 0, 0, 1}, // CT_VDKRED +{.70f, 0, 0, 1}, // CT_DKRED + +{0.717f, 0.902f, 1.0f, 1}, // CT_VLTAQUA +{0.574f, 0.722f, 0.804f, 1}, // CT_LTAQUA +{0.287f, 0.361f, 0.402f, 1}, // CT_DKAQUA +{0.143f, 0.180f, 0.201f, 1}, // CT_VDKAQUA + +{0.871f, 0.386f, 0.375f, 1}, // CT_LTPINK +{0.435f, 0.193f, 0.187f, 1}, // CT_DKPINK +{ 0, .5f, .5f, 1}, // CT_LTCYAN +{ 0, .25f, .25f, 1}, // CT_DKCYAN +{ .179f, .51f, .92f, 1}, // CT_LTBLUE3 +{ .199f, .71f, .92f, 1}, // CT_LTBLUE3 +{ .5f, .05f, .4f, 1}, // CT_DKBLUE3 + +{ 0.0f, .613f, .097f, 1}, // CT_HUD_GREEN +{ 0.835f, .015f, .015f, 1}, // CT_HUD_RED +{ .567f, .685f, 1.0f, .75f}, // CT_ICON_BLUE +{ .515f, .406f, .507f, 1}, // CT_NO_AMMO_RED +{ 1.0f, .658f, .062f, 1}, // CT_HUD_ORANGE + +}; + +#include "holocronicons.h" + +int cgWeatherOverride = 0; + +int forceModelModificationCount = -1; + +void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ); +void CG_Shutdown( void ); + +void CG_CalcEntityLerpPositions( centity_t *cent ); +void CG_ROFF_NotetrackCallback( centity_t *cent, const char *notetrack); + +#include "../namespace_begin.h" +void UI_CleanupGhoul2(void); +#include "../namespace_end.h" + +static int C_PointContents(void); +static void C_GetLerpOrigin(void); +static void C_GetLerpData(void); +static void C_Trace(void); +static void C_G2Trace(void); +static void C_G2Mark(void); +static int CG_RagCallback(int callType); +static void C_GetBoltPos(void); +static void C_ImpactMark(void); + +#ifdef _XBOX +#define MAX_MISC_ENTS 500 +#else +#define MAX_MISC_ENTS 4000 +#endif + +//static refEntity_t *MiscEnts = 0; +//static float *Radius = 0; +static refEntity_t MiscEnts[MAX_MISC_ENTS]; //statically allocated for now. +static float Radius[MAX_MISC_ENTS]; +static float zOffset[MAX_MISC_ENTS]; //some models need a z offset for culling, because of stupid wrong model origins + +static int NumMiscEnts = 0; + +extern autoMapInput_t cg_autoMapInput; //cg_view.c +extern int cg_autoMapInputTime; +extern vec3_t cg_autoMapAngle; + +void CG_MiscEnt(void); +void CG_DoCameraShake( vec3_t origin, float intensity, int radius, int time ); + +//do we have any force powers that we would normally need to cycle to? +qboolean CG_NoUseableForce(void) +{ + int i = FP_HEAL; + while (i < NUM_FORCE_POWERS) + { + if (i != FP_SABERTHROW && + i != FP_SABER_OFFENSE && + i != FP_SABER_DEFENSE && + i != FP_LEVITATION) + { //valid selectable power + if (cg.predictedPlayerState.fd.forcePowersKnown & (1 << i)) + { //we have it + return qfalse; + } + } + i++; + } + + //no useable force powers, I guess. + return qtrue; +} + +/* +================ +vmMain + +This is the only way control passes into the module. +This must be the very first function compiled into the .q3vm file +================ +*/ +#include "../namespace_begin.h" +Q_EXPORT intptr_t 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: + 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: + 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: + cgDC.cursorx = cgs.cursorX; + cgDC.cursory = cgs.cursorY; + CG_MouseEvent(arg0, arg1); + return 0; + case CG_EVENT_HANDLING: + CG_EventHandling(arg0); + return 0; + + case CG_POINT_CONTENTS: + return C_PointContents(); + + case CG_GET_LERP_ORIGIN: + C_GetLerpOrigin(); + return 0; + + case CG_GET_LERP_DATA: + C_GetLerpData(); + return 0; + + case CG_GET_GHOUL2: + return (int)cg_entities[arg0].ghoul2; //NOTE: This is used by the effect bolting which is actually not used at all. + //I'm fairly sure if you try to use it with vm's it will just give you total + //garbage. In other words, use at your own risk. + + case CG_GET_MODEL_LIST: + return (int)cgs.gameModels; + + case CG_CALC_LERP_POSITIONS: + CG_CalcEntityLerpPositions( &cg_entities[arg0] ); + return 0; + + case CG_TRACE: + C_Trace(); + return 0; + case CG_GET_SORTED_FORCE_POWER: + return forcePowerSorted[arg0]; + case CG_G2TRACE: + C_G2Trace(); + return 0; + + case CG_G2MARK: + C_G2Mark(); + return 0; + + case CG_RAG_CALLBACK: + return CG_RagCallback(arg0); + + case CG_INCOMING_CONSOLE_COMMAND: + //rww - let mod authors filter client console messages so they can cut them off if they want. + //return 1 if the command is ok. Otherwise, you can set char 0 on the command str to 0 and return + //0 to not execute anything, or you can fill conCommand in with something valid and return 0 + //in order to have that string executed in place. Some example code: +#if 0 + { + TCGIncomingConsoleCommand *icc = (TCGIncomingConsoleCommand *)cg.sharedBuffer; + if (strstr(icc->conCommand, "wait")) + { //filter out commands contaning wait + Com_Printf("You can't use commands containing the string wait with MyMod v1.0\n"); + icc->conCommand[0] = 0; + return 0; + } + else if (strstr(icc->conCommand, "blah")) + { //any command containing the string "blah" is redirected to "quit" + strcpy(icc->conCommand, "quit"); + return 0; + } + } +#endif + return 1; + + case CG_GET_USEABLE_FORCE: + return CG_NoUseableForce(); + + case CG_GET_ORIGIN: + VectorCopy(cg_entities[arg0].currentState.pos.trBase, (float *)arg1); + return 0; + + case CG_GET_ANGLES: + VectorCopy(cg_entities[arg0].currentState.apos.trBase, (float *)arg1); + return 0; + + case CG_GET_ORIGIN_TRAJECTORY: + return (int)&cg_entities[arg0].nextState.pos; + + case CG_GET_ANGLE_TRAJECTORY: + return (int)&cg_entities[arg0].nextState.apos; + + case CG_ROFF_NOTETRACK_CALLBACK: + CG_ROFF_NotetrackCallback( &cg_entities[arg0], (const char *)arg1 ); + return 0; + + case CG_IMPACT_MARK: + C_ImpactMark(); + return 0; + + case CG_MAP_CHANGE: + // this trap map be called more than once for a given map change, as the + // server is going to attempt to send out multiple broadcasts in hopes that + // the client will receive one of them + cg.mMapChange = qtrue; + return 0; + + case CG_AUTOMAP_INPUT: + //special input during automap mode -rww + { + autoMapInput_t *autoInput = (autoMapInput_t *)cg.sharedBuffer; + + memcpy(&cg_autoMapInput, autoInput, sizeof(autoMapInput_t)); + + if (!arg0) + { //if this is non-0, it's actually a one-frame mouse event + cg_autoMapInputTime = cg.time + 1000; + } + else + { + if (cg_autoMapInput.yaw) + { + cg_autoMapAngle[YAW] += cg_autoMapInput.yaw; + } + + if (cg_autoMapInput.pitch) + { + cg_autoMapAngle[PITCH] += cg_autoMapInput.pitch; + } + cg_autoMapInput.yaw = 0.0f; + cg_autoMapInput.pitch = 0.0f; + } + } + return 0; + + case CG_MISC_ENT: + CG_MiscEnt(); + return 0; + + case CG_FX_CAMERASHAKE: + { + TCGCameraShake *data = (TCGCameraShake *)cg.sharedBuffer; + + CG_DoCameraShake( data->mOrigin, data->mIntensity, data->mRadius, data->mTime ); + } + return 0; + + default: + CG_Error( "vmMain: unknown command %i", command ); + break; + } + return -1; +} +#include "../namespace_end.h" + +static int C_PointContents(void) +{ + TCGPointContents *data = (TCGPointContents *)cg.sharedBuffer; + + return CG_PointContents( data->mPoint, data->mPassEntityNum ); +} + +static void C_GetLerpOrigin(void) +{ + TCGVectorData *data = (TCGVectorData *)cg.sharedBuffer; + + VectorCopy(cg_entities[data->mEntityNum].lerpOrigin, data->mPoint); +} + +static void C_GetLerpData(void) +{//only used by FX system to pass to getboltmat + TCGGetBoltData *data = (TCGGetBoltData *)cg.sharedBuffer; + + VectorCopy(cg_entities[data->mEntityNum].lerpOrigin, data->mOrigin); + VectorCopy(cg_entities[data->mEntityNum].modelScale, data->mScale); + VectorCopy(cg_entities[data->mEntityNum].lerpAngles, data->mAngles); + if (cg_entities[data->mEntityNum].currentState.eType == ET_PLAYER) + { //normal player + data->mAngles[PITCH] = 0.0f; + data->mAngles[ROLL] = 0.0f; + } + else if (cg_entities[data->mEntityNum].currentState.eType == ET_NPC) + { //an NPC + Vehicle_t *pVeh = cg_entities[data->mEntityNum].m_pVehicle; + if (!pVeh) + { //for vehicles, we may or may not want to 0 out pitch and roll + data->mAngles[PITCH] = 0.0f; + data->mAngles[ROLL] = 0.0f; + } + else if (pVeh->m_pVehicleInfo->type == VH_SPEEDER) + { //speeder wants no pitch but a roll + data->mAngles[PITCH] = 0.0f; + } + else if (pVeh->m_pVehicleInfo->type != VH_FIGHTER) + { //fighters want all angles + data->mAngles[PITCH] = 0.0f; + data->mAngles[ROLL] = 0.0f; + } + } +} + +static void C_Trace(void) +{ + TCGTrace *td = (TCGTrace *)cg.sharedBuffer; + + CG_Trace(&td->mResult, td->mStart, td->mMins, td->mMaxs, td->mEnd, td->mSkipNumber, td->mMask); +} + +static void C_G2Trace(void) +{ + TCGTrace *td = (TCGTrace *)cg.sharedBuffer; + + CG_G2Trace(&td->mResult, td->mStart, td->mMins, td->mMaxs, td->mEnd, td->mSkipNumber, td->mMask); +} + +static void C_G2Mark(void) +{ + TCGG2Mark *td = (TCGG2Mark *)cg.sharedBuffer; + trace_t tr; + vec3_t end; + + VectorMA(td->start, 64, td->dir, end); + CG_G2Trace(&tr, td->start, NULL, NULL, end, ENTITYNUM_NONE, MASK_PLAYERSOLID); + + if (tr.entityNum < ENTITYNUM_WORLD && + cg_entities[tr.entityNum].ghoul2) + { //hit someone with a ghoul2 instance, let's project the decal on them then. + centity_t *cent = &cg_entities[tr.entityNum]; + + //CG_TestLine(tr.endpos, end, 2000, 0x0000ff, 1); + + CG_AddGhoul2Mark(td->shader, td->size, tr.endpos, end, tr.entityNum, + cent->lerpOrigin, cent->lerpAngles[YAW], cent->ghoul2, cent->modelScale, + Q_irand(2000, 4000)); + //I'm making fx system decals have a very short lifetime. + } +} + +static void CG_DebugBoxLines(vec3_t mins, vec3_t maxs, int duration) +{ + vec3_t start; + vec3_t end; + vec3_t vert; + + float x = maxs[0] - mins[0]; + float y = maxs[1] - mins[1]; + + start[2] = maxs[2]; + vert[2] = mins[2]; + + vert[0] = mins[0]; + vert[1] = mins[1]; + start[0] = vert[0]; + start[1] = vert[1]; + CG_TestLine(start, vert, duration, 0x00000ff, 1); + + vert[0] = mins[0]; + vert[1] = maxs[1]; + start[0] = vert[0]; + start[1] = vert[1]; + CG_TestLine(start, vert, duration, 0x00000ff, 1); + + vert[0] = maxs[0]; + vert[1] = mins[1]; + start[0] = vert[0]; + start[1] = vert[1]; + CG_TestLine(start, vert, duration, 0x00000ff, 1); + + vert[0] = maxs[0]; + vert[1] = maxs[1]; + start[0] = vert[0]; + start[1] = vert[1]; + CG_TestLine(start, vert, duration, 0x00000ff, 1); + + // top of box + VectorCopy(maxs, start); + VectorCopy(maxs, end); + start[0] -= x; + CG_TestLine(start, end, duration, 0x00000ff, 1); + end[0] = start[0]; + end[1] -= y; + CG_TestLine(start, end, duration, 0x00000ff, 1); + start[1] = end[1]; + start[0] += x; + CG_TestLine(start, end, duration, 0x00000ff, 1); + CG_TestLine(start, maxs, duration, 0x00000ff, 1); + // bottom of box + VectorCopy(mins, start); + VectorCopy(mins, end); + start[0] += x; + CG_TestLine(start, end, duration, 0x00000ff, 1); + end[0] = start[0]; + end[1] += y; + CG_TestLine(start, end, duration, 0x00000ff, 1); + start[1] = end[1]; + start[0] -= x; + CG_TestLine(start, end, duration, 0x00000ff, 1); + CG_TestLine(start, mins, duration, 0x00000ff, 1); +} + +//handle ragdoll callbacks, for events and debugging -rww +static int CG_RagCallback(int callType) +{ + switch(callType) + { + case RAG_CALLBACK_DEBUGBOX: + { + ragCallbackDebugBox_t *callData = (ragCallbackDebugBox_t *)cg.sharedBuffer; + + CG_DebugBoxLines(callData->mins, callData->maxs, callData->duration); + } + break; + case RAG_CALLBACK_DEBUGLINE: + { + ragCallbackDebugLine_t *callData = (ragCallbackDebugLine_t *)cg.sharedBuffer; + + CG_TestLine(callData->start, callData->end, callData->time, callData->color, callData->radius); + } + break; + case RAG_CALLBACK_BONESNAP: + { + ragCallbackBoneSnap_t *callData = (ragCallbackBoneSnap_t *)cg.sharedBuffer; + centity_t *cent = &cg_entities[callData->entNum]; + int snapSound = trap_S_RegisterSound(va("sound/player/bodyfall_human%i.wav", Q_irand(1, 3))); + + trap_S_StartSound(cent->lerpOrigin, callData->entNum, CHAN_AUTO, snapSound); + } + case RAG_CALLBACK_BONEIMPACT: + break; + case RAG_CALLBACK_BONEINSOLID: +#if 0 + { + ragCallbackBoneInSolid_t *callData = (ragCallbackBoneInSolid_t *)cg.sharedBuffer; + + if (callData->solidCount > 16) + { //don't bother if we're just tapping into solidity, we'll probably recover on our own + centity_t *cent = &cg_entities[callData->entNum]; + vec3_t slideDir; + + VectorSubtract(cent->lerpOrigin, callData->bonePos, slideDir); + VectorAdd(cent->ragOffsets, slideDir, cent->ragOffsets); + + cent->hasRagOffset = qtrue; + } + } +#endif + break; + case RAG_CALLBACK_TRACELINE: + { + ragCallbackTraceLine_t *callData = (ragCallbackTraceLine_t *)cg.sharedBuffer; + + CG_Trace(&callData->tr, callData->start, callData->mins, callData->maxs, + callData->end, callData->ignore, callData->mask); + } + break; + default: + Com_Error(ERR_DROP, "Invalid callType in CG_RagCallback"); + break; + } + + return 0; +} + +static void C_ImpactMark(void) +{ + TCGImpactMark *data = (TCGImpactMark *)cg.sharedBuffer; + + /* + CG_ImpactMark((int)arg0, (const float *)arg1, (const float *)arg2, (float)arg3, + (float)arg4, (float)arg5, (float)arg6, (float)arg7, qtrue, (float)arg8, qfalse); + */ + CG_ImpactMark(data->mHandle, data->mPoint, data->mAngle, data->mRotation, + data->mRed, data->mGreen, data->mBlue, data->mAlphaStart, qtrue, data->mSizeStart, qfalse); +} + +void CG_MiscEnt(void) +{ + int modelIndex; + refEntity_t *RefEnt; + TCGMiscEnt *data = (TCGMiscEnt *)cg.sharedBuffer; + vec3_t mins, maxs; + float *radius, *zOff; + + if (NumMiscEnts >= MAX_MISC_ENTS) + { + return; + } + + radius = &Radius[NumMiscEnts]; + zOff = &zOffset[NumMiscEnts]; + RefEnt = &MiscEnts[NumMiscEnts++]; + + modelIndex = trap_R_RegisterModel(data->mModel); + if (modelIndex == 0) + { + Com_Error(ERR_DROP, "client_model has invalid model definition"); + return; + } + + *zOff = 0; + + memset(RefEnt, 0, sizeof(refEntity_t)); + RefEnt->reType = RT_MODEL; + RefEnt->hModel = modelIndex; + RefEnt->frame = 0; + trap_R_ModelBounds(modelIndex, mins, maxs); + VectorCopy(data->mScale, RefEnt->modelScale); + VectorCopy(data->mOrigin, RefEnt->origin); + + VectorScaleVector(mins, data->mScale, mins); + VectorScaleVector(maxs, data->mScale, maxs); + *radius = Distance(mins, maxs); + + AnglesToAxis( data->mAngles, RefEnt->axis ); + ScaleModelAxis(RefEnt); +} + +void CG_DrawMiscEnts(void) +{ + int i; + refEntity_t *RefEnt; + float *radius, *zOff; + vec3_t difference; + vec3_t cullOrigin; + + RefEnt = MiscEnts; + radius = Radius; + zOff = zOffset; + for(i=0;iorigin, cullOrigin); + cullOrigin[2] += 1.0f; + + if (*zOff) + { + cullOrigin[2] += *zOff; + } + + if (cg.snap && trap_R_inPVS(cg.refdef.vieworg, cullOrigin, cg.snap->areamask)) + { + VectorSubtract(RefEnt->origin, cg.refdef.vieworg, difference); + if (VectorLength(difference)-(*radius) <= cg.distanceCull) + { + trap_R_AddRefEntityToScene(RefEnt); + } + } + RefEnt++; + radius++; + zOff++; + } +} + +/* +Ghoul2 Insert Start +*/ +/* +void CG_ResizeG2Bolt(boltInfo_v *bolt, int newCount) +{ + bolt->resize(newCount); +} + +void CG_ResizeG2Surface(surfaceInfo_v *surface, int newCount) +{ + surface->resize(newCount); +} + +void CG_ResizeG2Bone(boneInfo_v *bone, int newCount) +{ + bone->resize(newCount); +} + +void CG_ResizeG2(CGhoul2Info_v *ghoul2, int newCount) +{ + ghoul2->resize(newCount); +} + +void CG_ResizeG2TempBone(mdxaBone_v *tempBone, int newCount) +{ + tempBone->resize(newCount); +} +*/ +/* +Ghoul2 Insert End +*/ +cg_t cg; +cgs_t cgs; +centity_t cg_entities[MAX_GENTITIES]; + +centity_t *cg_permanents[MAX_GENTITIES]; //rwwRMG - added +int cg_numpermanents = 0; + +weaponInfo_t cg_weapons[MAX_WEAPONS]; +itemInfo_t cg_items[MAX_ITEMS]; + + +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_renderToTextureFX; +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_drawRadar; +vmCvar_t cg_drawVehLeadIndicator; +vmCvar_t cg_dynamicCrosshair; +vmCvar_t cg_dynamicCrosshairPrecision; +vmCvar_t cg_drawRewards; +vmCvar_t cg_drawScores; +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_debugSaber; +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_showVehMiss; +vmCvar_t cg_footsteps; +vmCvar_t cg_addMarks; +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_autoswitch; +vmCvar_t cg_ignore; +vmCvar_t cg_simpleItems; +vmCvar_t cg_fov; +vmCvar_t cg_zoomFov; + +vmCvar_t cg_swingAngles; + +vmCvar_t cg_oldPainSounds; + +vmCvar_t cg_ragDoll; + +vmCvar_t cg_jumpSounds; + +vmCvar_t cg_autoMap; +vmCvar_t cg_autoMapX; +vmCvar_t cg_autoMapY; +vmCvar_t cg_autoMapW; +vmCvar_t cg_autoMapH; + +#ifndef _XBOX // Hmmm. This is also in game. I think this is safe. +vmCvar_t bg_fighterAltControl; +#endif + +vmCvar_t cg_chatBox; +vmCvar_t cg_chatBoxHeight; + +vmCvar_t cg_saberModelTraceEffect; + +vmCvar_t cg_saberClientVisualCompensation; + +vmCvar_t cg_g2TraceLod; + +vmCvar_t cg_fpls; + +vmCvar_t cg_ghoul2Marks; + +vmCvar_t cg_optvehtrace; + +vmCvar_t cg_saberDynamicMarks; +vmCvar_t cg_saberDynamicMarkTime; + +vmCvar_t cg_saberContact; +vmCvar_t cg_saberTrail; + +vmCvar_t cg_duelHeadAngles; + +vmCvar_t cg_speedTrail; +vmCvar_t cg_auraShell; + +vmCvar_t cg_repeaterOrb; + +vmCvar_t cg_animBlend; + +vmCvar_t cg_dismember; + +vmCvar_t cg_thirdPersonSpecialCam; + +vmCvar_t cg_thirdPerson; +vmCvar_t cg_thirdPersonRange; +vmCvar_t cg_thirdPersonAngle; +vmCvar_t cg_thirdPersonPitchOffset; +vmCvar_t cg_thirdPersonVertOffset; +vmCvar_t cg_thirdPersonCameraDamp; +vmCvar_t cg_thirdPersonTargetDamp; + +vmCvar_t cg_thirdPersonAlpha; +vmCvar_t cg_thirdPersonHorzOffset; + +vmCvar_t cg_stereoSeparation; +vmCvar_t cg_lagometer; +vmCvar_t cg_drawEnemyInfo; +vmCvar_t cg_synchronousClients; +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_hudFiles; +vmCvar_t cg_scorePlum; +vmCvar_t cg_smoothClients; + +#include "../namespace_begin.h" +vmCvar_t pmove_fixed; +//vmCvar_t cg_pmove_fixed; +vmCvar_t pmove_msec; +// nmckenzie: DUEL_HEALTH +vmCvar_t g_showDuelHealths; +#include "../namespace_end.h" + +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_noTaunt; +vmCvar_t cg_noProjectileTrail; +//vmCvar_t cg_trueLightning; +/* +Ghoul2 Insert Start +*/ +vmCvar_t cg_debugBB; +/* +Ghoul2 Insert End +*/ +//vmCvar_t cg_redTeamName; +//vmCvar_t cg_blueTeamName; +vmCvar_t cg_currentSelectedPlayer; +vmCvar_t cg_currentSelectedPlayerName; +//vmCvar_t cg_singlePlayerActive; +vmCvar_t cg_recordSPDemo; +vmCvar_t cg_recordSPDemoName; +vmCvar_t cg_showVehBounds; + +vmCvar_t ui_myteam; + +vmCvar_t cg_snapshotTimeout; + +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", "40.0", CVAR_ARCHIVE }, + { &cg_fov, "cg_fov", "80", 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_renderToTextureFX, "cg_renderToTextureFX", "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", "0", CVAR_ARCHIVE }, + { &cg_drawEnemyInfo, "cg_drawEnemyInfo", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshair, "cg_drawCrosshair", "1", CVAR_ARCHIVE }, + { &cg_drawCrosshairNames, "cg_drawCrosshairNames", "1", CVAR_ARCHIVE }, + { &cg_drawRadar, "cg_drawRadar", "1", CVAR_ARCHIVE }, + { &cg_drawVehLeadIndicator, "cg_drawVehLeadIndicator", "1", CVAR_ARCHIVE }, + { &cg_drawScores, "cg_drawScores", "1", CVAR_ARCHIVE }, + { &cg_dynamicCrosshair, "cg_dynamicCrosshair", "1", CVAR_ARCHIVE }, + //Enables ghoul2 traces for crosshair traces.. more precise when pointing at others, but slower. + //And if the server doesn't have g2 col enabled, it won't match up the same. + { &cg_dynamicCrosshairPrecision, "cg_dynamicCrosshairPrecision", "1", CVAR_ARCHIVE }, + { &cg_drawRewards, "cg_drawRewards", "1", CVAR_ARCHIVE }, + { &cg_crosshairSize, "cg_crosshairSize", "24", CVAR_ARCHIVE }, + { &cg_crosshairHealth, "cg_crosshairHealth", "0", CVAR_ARCHIVE }, + { &cg_crosshairX, "cg_crosshairX", "0", CVAR_ARCHIVE }, + { &cg_crosshairY, "cg_crosshairY", "0", CVAR_ARCHIVE }, + { &cg_simpleItems, "cg_simpleItems", "0", CVAR_ARCHIVE }, + { &cg_addMarks, "cg_marks", "1", CVAR_ARCHIVE }, + { &cg_lagometer, "cg_lagometer", "0", 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_ARCHIVE }, + { &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_debugSaber, "cg_debugsaber", "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_showVehMiss, "cg_showVehMiss", "0", 0 }, + { &cg_footsteps, "cg_footsteps", "3", CVAR_ARCHIVE }, + { &cg_swingAngles, "cg_swingAngles", "1", 0 }, + + { &cg_oldPainSounds, "cg_oldPainSounds", "0", 0 }, + + { &cg_ragDoll, "broadsword", "0", 0 }, + + { &cg_jumpSounds, "cg_jumpSounds", "0", 0 }, + + { &cg_autoMap, "r_autoMap", "0", CVAR_ARCHIVE }, + { &cg_autoMapX, "r_autoMapX", "496", CVAR_ARCHIVE }, + { &cg_autoMapY, "r_autoMapY", "32", CVAR_ARCHIVE }, + { &cg_autoMapW, "r_autoMapW", "128", CVAR_ARCHIVE }, + { &cg_autoMapH, "r_autoMapH", "128", CVAR_ARCHIVE }, + + { &bg_fighterAltControl, "bg_fighterAltControl", "0", CVAR_SERVERINFO }, + + { &cg_chatBox, "cg_chatBox", "10000", CVAR_ARCHIVE }, + { &cg_chatBoxHeight, "cg_chatBoxHeight", "350", CVAR_ARCHIVE }, + + { &cg_saberModelTraceEffect, "cg_saberModelTraceEffect", "0", 0 }, + + //allows us to trace between server frames on the client to see if we're visually + //hitting the last entity we detected a hit on from the server. + { &cg_saberClientVisualCompensation, "cg_saberClientVisualCompensation", "1", 0 }, + + { &cg_g2TraceLod, "cg_g2TraceLod", "2", 0 }, + + { &cg_fpls, "cg_fpls", "0", 0 }, + + { &cg_ghoul2Marks, "cg_ghoul2Marks", "16", 0 }, + + { &cg_optvehtrace, "com_optvehtrace", "0", 0 }, + + { &cg_saberDynamicMarks, "cg_saberDynamicMarks", "0", 0 }, + { &cg_saberDynamicMarkTime, "cg_saberDynamicMarkTime", "60000", 0 }, + + { &cg_saberContact, "cg_saberContact", "1", 0 }, + { &cg_saberTrail, "cg_saberTrail", "1", 0 }, + + { &cg_duelHeadAngles, "cg_duelHeadAngles", "0", 0 }, + + { &cg_speedTrail, "cg_speedTrail", "1", 0 }, + { &cg_auraShell, "cg_auraShell", "1", 0 }, + + { &cg_repeaterOrb, "cg_repeaterOrb", "0", 0 }, + + { &cg_animBlend, "cg_animBlend", "1", 0 }, + + { &cg_dismember, "cg_dismember", "0", CVAR_ARCHIVE }, + + { &cg_thirdPersonSpecialCam, "cg_thirdPersonSpecialCam", "0", 0 }, + + { &cg_thirdPerson, "cg_thirdPerson", "0", CVAR_ARCHIVE }, + { &cg_thirdPersonRange, "cg_thirdPersonRange", "80", CVAR_CHEAT }, + { &cg_thirdPersonAngle, "cg_thirdPersonAngle", "0", CVAR_CHEAT }, + { &cg_thirdPersonPitchOffset, "cg_thirdPersonPitchOffset", "0", CVAR_CHEAT }, + { &cg_thirdPersonVertOffset, "cg_thirdPersonVertOffset", "16", CVAR_CHEAT }, + { &cg_thirdPersonCameraDamp, "cg_thirdPersonCameraDamp", "0.3", 0 }, + { &cg_thirdPersonTargetDamp, "cg_thirdPersonTargetDamp", "0.5", CVAR_CHEAT }, + + { &cg_thirdPersonHorzOffset, "cg_thirdPersonHorzOffset", "0", CVAR_CHEAT }, + { &cg_thirdPersonAlpha, "cg_thirdPersonAlpha", "1.0", CVAR_CHEAT }, + + { &cg_forceModel, "cg_forceModel", "0", CVAR_ARCHIVE }, + { &cg_predictItems, "cg_predictItems", "1", CVAR_ARCHIVE }, + { &cg_deferPlayers, "cg_deferPlayers", "1", CVAR_ARCHIVE }, + { &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 }, + // 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 + +// { &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_singlePlayerActive, "ui_singlePlayerActive", "0", CVAR_USERINFO}, + { &cg_recordSPDemo, "ui_recordSPDemo", "0", CVAR_ARCHIVE}, + { &cg_recordSPDemoName, "ui_recordSPDemoName", "", 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_ARCHIVE}, + { &cg_hudFiles, "cg_hudFiles", "ui/jahud.txt", CVAR_ARCHIVE}, + { &cg_smoothClients, "cg_smoothClients", "1", 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_trueLightning, "cg_trueLightning", "0.0", CVAR_ARCHIVE}, + { &cg_showVehBounds, "cg_showVehBounds", "0", 0}, + + { &ui_myteam, "ui_myteam", "0", CVAR_ROM|CVAR_INTERNAL}, + { &cg_snapshotTimeout, "cg_snapshotTimeout", "10", CVAR_ARCHIVE }, + +// { &cg_pmove_fixed, "cg_pmove_fixed", "0", CVAR_USERINFO | CVAR_ARCHIVE } +/* +Ghoul2 Insert Start +*/ + { &cg_debugBB, "debugBB", "0", 0}, +/* +Ghoul2 Insert End +*/ +}; + +static int cvarTableSize = sizeof( cvarTable ) / sizeof( cvarTable[0] ); + +/* +================= +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, "forcepowers", DEFAULT_FORCEPOWERS, CVAR_USERINFO | CVAR_ARCHIVE ); + + // Cvars uses for transferring data between client and server + trap_Cvar_Register(NULL, "ui_about_gametype", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_fraglimit", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_capturelimit", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_duellimit", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_timelimit", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_maxclients", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_dmflags", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_mapname", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_hostname", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_needpass", "0", CVAR_ROM|CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_about_botminplayers", "0", CVAR_ROM|CVAR_INTERNAL ); + + trap_Cvar_Register(NULL, "ui_tm1_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm3_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + + trap_Cvar_Register(NULL, "ui_tm1_c0_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c1_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c2_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c3_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c4_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm1_c5_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + + trap_Cvar_Register(NULL, "ui_tm2_c0_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c1_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c2_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c3_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c4_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + trap_Cvar_Register(NULL, "ui_tm2_c5_cnt", "0", CVAR_ROM | CVAR_INTERNAL ); + +} + +/* +=================== +CG_SetWeatherOverride +=================== +*/ +#if 0 +void CG_SetWeatherOverride(int contents) +{ + if (contents != cgWeatherOverride) + { //only do the trap call if we aren't already set to this + trap_R_WeatherContentsOverride(contents); + } + cgWeatherOverride = contents; //keep track of it +} +#endif + +/* +=================== +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; + } + + if (cg.crosshairClientNum >= MAX_CLIENTS) + { + 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; +} + + +//======================================================================== + +//so shared code can get the local time depending on the side it's executed on +#include "../namespace_begin.h" +int BG_GetTime(void) +{ + return cg.time; +} +#include "../namespace_end.h" + +/* +================= +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 ); + } + + // 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++; + } + + trap_S_RegisterSound( data ); + } + + // parse the space seperated precache string for other media + s = item->precaches; + 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, "efx" )) { + trap_FX_RegisterEffect( data ); + } + } +} + +static void CG_AS_Register(void) +{ + const char *soundName; + int i; + +// CG_LoadingString( "ambient sound sets" ); + + //Load the ambient sets +#if 0 //as_preCacheMap was game-side.. that is evil. + trap_AS_AddPrecacheEntry( "#clear" ); // ;-) + //FIXME: Don't ask... I had to get around a really nasty MS error in the templates with this... + namePrecache_m::iterator pi; + STL_ITERATE( pi, as_preCacheMap ) + { + cgi_AS_AddPrecacheEntry( ((*pi).first).c_str() ); + } +#else + trap_AS_AddPrecacheEntry( "#clear" ); + + for ( i = 1 ; i < MAX_AMBIENT_SETS ; i++ ) { + soundName = CG_ConfigString( CS_AMBIENT_SET+i ); + if ( !soundName || !soundName[0] ) + { + break; + } + + trap_AS_AddPrecacheEntry(soundName); + } + soundName = CG_ConfigString( CS_GLOBAL_AMBIENT_SET ); + if (soundName && soundName[0] && Q_stricmp(soundName, "default")) + { //global soundset + trap_AS_AddPrecacheEntry(soundName); + } +#endif + + trap_AS_ParseSets(); +} + +//a global weather effect (rain, snow, etc) +void CG_ParseWeatherEffect(const char *str) +{ + char *sptr = (char *)str; + sptr++; //pass the '*' + trap_R_WorldEffectCommand(sptr); +} + +extern int cgSiegeRoundBeganTime; +void CG_ParseSiegeState(const char *str) +{ + int i = 0; + int j = 0; +// int prevState = cgSiegeRoundState; + char b[1024]; + + while (str[i] && str[i] != '|') + { + b[j] = str[i]; + i++; + j++; + } + b[j] = 0; + cgSiegeRoundState = atoi(b); + + if (str[i] == '|') + { + j = 0; + i++; + while (str[i]) + { + b[j] = str[i]; + i++; + j++; + } + b[j] = 0; +// if (cgSiegeRoundState != prevState) + { //it changed + cgSiegeRoundTime = atoi(b); + if (cgSiegeRoundState == 0 || cgSiegeRoundState == 2) + { + cgSiegeRoundBeganTime = cgSiegeRoundTime; + } + } + } + else + { + cgSiegeRoundTime = cg.time; + } +} + +/* +================= +CG_RegisterSounds + +called during a precache command +================= +*/ +void CG_PrecacheNPCSounds(const char *str); +void CG_ParseSiegeObjectiveStatus(const char *str); +extern int cg_beatingSiegeTime; +extern int cg_siegeWinTeam; +static void CG_RegisterSounds( void ) { + int i; + char items[MAX_ITEMS+1]; + char name[MAX_QPATH]; + const char *soundName; + + CG_AS_Register(); + +// CG_LoadingString( "sounds" ); + + trap_S_RegisterSound( "sound/weapons/melee/punch1.mp3" ); + trap_S_RegisterSound( "sound/weapons/melee/punch2.mp3" ); + trap_S_RegisterSound( "sound/weapons/melee/punch3.mp3" ); + trap_S_RegisterSound( "sound/weapons/melee/punch4.mp3" ); + trap_S_RegisterSound("sound/movers/objects/saber_slam"); + + trap_S_RegisterSound("sound/player/bodyfall_human1.wav"); + trap_S_RegisterSound("sound/player/bodyfall_human2.wav"); + trap_S_RegisterSound("sound/player/bodyfall_human3.wav"); + + //test effects + trap_FX_RegisterEffect("effects/mp/test_sparks.efx"); + trap_FX_RegisterEffect("effects/mp/test_wall_impact.efx"); + + cgs.media.oneMinuteSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM004" ); + cgs.media.fiveMinuteSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM005" ); + cgs.media.oneFragSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM001" ); + cgs.media.twoFragSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM002" ); + cgs.media.threeFragSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM003"); + cgs.media.count3Sound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM035" ); + cgs.media.count2Sound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM036" ); + cgs.media.count1Sound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM037" ); + cgs.media.countFightSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM038" ); + + cgs.media.hackerIconShader = trap_R_RegisterShaderNoMip("gfx/mp/c_icon_tech"); + + cgs.media.redSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/red_glow" ); + cgs.media.redSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/red_line" ); + cgs.media.orangeSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/orange_glow" ); + cgs.media.orangeSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/orange_line" ); + cgs.media.yellowSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/yellow_glow" ); + cgs.media.yellowSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/yellow_line" ); + cgs.media.greenSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/green_glow" ); + cgs.media.greenSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/green_line" ); + cgs.media.blueSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/blue_glow" ); + cgs.media.blueSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/blue_line" ); + cgs.media.purpleSaberGlowShader = trap_R_RegisterShader( "gfx/effects/sabers/purple_glow" ); + cgs.media.purpleSaberCoreShader = trap_R_RegisterShader( "gfx/effects/sabers/purple_line" ); + cgs.media.saberBlurShader = trap_R_RegisterShader( "gfx/effects/sabers/saberBlur" ); + cgs.media.swordTrailShader = trap_R_RegisterShader( "gfx/effects/sabers/swordTrail" ); + + cgs.media.forceCoronaShader = trap_R_RegisterShaderNoMip( "gfx/hud/force_swirl" ); + + cgs.media.yellowDroppedSaberShader = trap_R_RegisterShader("gfx/effects/yellow_glow"); + + cgs.media.rivetMarkShader = trap_R_RegisterShader( "gfx/damage/rivetmark" ); + + trap_R_RegisterShader( "gfx/effects/saberFlare" ); + + trap_R_RegisterShader( "powerups/ysalimarishell" ); + + trap_R_RegisterShader( "gfx/effects/forcePush" ); + + trap_R_RegisterShader( "gfx/misc/red_dmgshield" ); + trap_R_RegisterShader( "gfx/misc/red_portashield" ); + trap_R_RegisterShader( "gfx/misc/blue_dmgshield" ); + trap_R_RegisterShader( "gfx/misc/blue_portashield" ); + + trap_R_RegisterShader( "models/map_objects/imp_mine/turret_chair_dmg.tga" ); + + for (i=1 ; i<9 ; i++) + { + trap_S_RegisterSound(va("sound/weapons/saber/saberhup%i.wav", i)); + } + + for (i=1 ; i<10 ; i++) + { + trap_S_RegisterSound(va("sound/weapons/saber/saberblock%i.wav", i)); + } + + for (i=1 ; i<4 ; i++) + { + trap_S_RegisterSound(va("sound/weapons/saber/bounce%i.wav", i)); + } + + trap_S_RegisterSound( "sound/weapons/saber/enemy_saber_on.wav" ); + trap_S_RegisterSound( "sound/weapons/saber/enemy_saber_off.wav" ); + + trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ); + trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" ); + trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" ); + trap_S_RegisterSound( "sound/weapons/saber/saberhitwall1" ); + trap_S_RegisterSound( "sound/weapons/saber/saberhitwall2" ); + trap_S_RegisterSound( "sound/weapons/saber/saberhitwall3" ); + trap_S_RegisterSound("sound/weapons/saber/saberhit.wav"); + trap_S_RegisterSound("sound/weapons/saber/saberhit1.wav"); + trap_S_RegisterSound("sound/weapons/saber/saberhit2.wav"); + trap_S_RegisterSound("sound/weapons/saber/saberhit3.wav"); + + trap_S_RegisterSound("sound/weapons/saber/saber_catch.wav"); + + cgs.media.teamHealSound = trap_S_RegisterSound("sound/weapons/force/teamheal.wav"); + cgs.media.teamRegenSound = trap_S_RegisterSound("sound/weapons/force/teamforce.wav"); + + trap_S_RegisterSound("sound/weapons/force/heal.wav"); + trap_S_RegisterSound("sound/weapons/force/speed.wav"); + trap_S_RegisterSound("sound/weapons/force/see.wav"); + trap_S_RegisterSound("sound/weapons/force/rage.wav"); + trap_S_RegisterSound("sound/weapons/force/lightning"); + trap_S_RegisterSound("sound/weapons/force/lightninghit1"); + trap_S_RegisterSound("sound/weapons/force/lightninghit2"); + trap_S_RegisterSound("sound/weapons/force/lightninghit3"); + trap_S_RegisterSound("sound/weapons/force/drain.wav"); + trap_S_RegisterSound("sound/weapons/force/jumpbuild.wav"); + trap_S_RegisterSound("sound/weapons/force/distract.wav"); + trap_S_RegisterSound("sound/weapons/force/distractstop.wav"); + trap_S_RegisterSound("sound/weapons/force/pull.wav"); + trap_S_RegisterSound("sound/weapons/force/push.wav"); + + for (i=1 ; i<3 ; i++) + { + trap_S_RegisterSound(va("sound/weapons/thermal/bounce%i.wav", i)); + } + + trap_S_RegisterSound("sound/movers/switches/switch2.wav"); + trap_S_RegisterSound("sound/movers/switches/switch3.wav"); + trap_S_RegisterSound("sound/ambience/spark5.wav"); + trap_S_RegisterSound("sound/chars/turret/ping.wav"); + trap_S_RegisterSound("sound/chars/turret/startup.wav"); + trap_S_RegisterSound("sound/chars/turret/shutdown.wav"); + trap_S_RegisterSound("sound/chars/turret/move.wav"); + trap_S_RegisterSound("sound/player/pickuphealth.wav"); + trap_S_RegisterSound("sound/player/pickupshield.wav"); + + trap_S_RegisterSound("sound/effects/glassbreak1.wav"); + + trap_S_RegisterSound( "sound/weapons/rocket/tick.wav" ); + trap_S_RegisterSound( "sound/weapons/rocket/lock.wav" ); + + trap_S_RegisterSound("sound/weapons/force/speedloop.wav"); + + trap_S_RegisterSound("sound/weapons/force/protecthit.mp3"); //PDSOUND_PROTECTHIT + trap_S_RegisterSound("sound/weapons/force/protect.mp3"); //PDSOUND_PROTECT + trap_S_RegisterSound("sound/weapons/force/absorbhit.mp3"); //PDSOUND_ABSORBHIT + trap_S_RegisterSound("sound/weapons/force/absorb.mp3"); //PDSOUND_ABSORB + trap_S_RegisterSound("sound/weapons/force/jump.mp3"); //PDSOUND_FORCEJUMP + trap_S_RegisterSound("sound/weapons/force/grip.mp3"); //PDSOUND_FORCEGRIP + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + +#ifdef JK2AWARDS + cgs.media.captureAwardSound = trap_S_RegisterSound( "sound/teamplay/flagcapture_yourteam.wav" ); +#endif + cgs.media.redLeadsSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM046"); + cgs.media.blueLeadsSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM045"); + cgs.media.teamsTiedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM032" ); + + cgs.media.redScoredSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM044"); + cgs.media.blueScoredSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM043" ); + + if ( cgs.gametype == GT_CTF || cg_buildScript.integer ) { + cgs.media.redFlagReturnedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM042" ); + cgs.media.blueFlagReturnedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM041" ); + cgs.media.redTookFlagSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM040" ); + cgs.media.blueTookFlagSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM039" ); + } + if ( cgs.gametype == GT_CTY /*|| cg_buildScript.integer*/ ) { + cgs.media.redYsalReturnedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM050" ); + cgs.media.blueYsalReturnedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM049" ); + cgs.media.redTookYsalSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM048" ); + cgs.media.blueTookYsalSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM047" ); + } + } + + cgs.media.drainSound = trap_S_RegisterSound("sound/weapons/force/drained.mp3"); + + cgs.media.happyMusic = trap_S_RegisterSound("music/goodsmall.mp3"); + cgs.media.dramaticFailure = trap_S_RegisterSound("music/badsmall.mp3"); + + //PRECACHE ALL MUSIC HERE (don't need to precache normally because it's streamed off the disk) + if (cg_buildScript.integer) + { + trap_S_StartBackgroundTrack( "music/mp/duel.mp3", "music/mp/duel.mp3", qfalse ); + } + + cg.loadLCARSStage = 1; + + cgs.media.selectSound = trap_S_RegisterSound( "sound/weapons/change.wav" ); + + cgs.media.teleInSound = trap_S_RegisterSound( "sound/player/telein.wav" ); + cgs.media.teleOutSound = trap_S_RegisterSound( "sound/player/teleout.wav" ); + cgs.media.respawnSound = trap_S_RegisterSound( "sound/items/respawn1.wav" ); + + trap_S_RegisterSound( "sound/movers/objects/objectHit.wav" ); + + cgs.media.talkSound = trap_S_RegisterSound( "sound/player/talk.wav" ); + cgs.media.landSound = trap_S_RegisterSound( "sound/player/land1.wav"); + cgs.media.fallSound = trap_S_RegisterSound( "sound/player/fallsplat.wav"); + + cgs.media.crackleSound = trap_S_RegisterSound( "sound/effects/energy_crackle.wav" ); +#ifdef JK2AWARDS + cgs.media.impressiveSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM025" ); + cgs.media.excellentSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM053" ); + cgs.media.deniedSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM017" ); + cgs.media.humiliationSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM019" ); + cgs.media.defendSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM024" ); +#endif + + /* + cgs.media.takenLeadSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM051"); + cgs.media.tiedLeadSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM032"); + cgs.media.lostLeadSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM052"); + */ + + cgs.media.rollSound = trap_S_RegisterSound( "sound/player/roll1.wav"); + + cgs.media.noforceSound = trap_S_RegisterSound( "sound/weapons/force/noforce" ); + + cgs.media.watrInSound = trap_S_RegisterSound( "sound/player/watr_in.wav"); + cgs.media.watrOutSound = trap_S_RegisterSound( "sound/player/watr_out.wav"); + cgs.media.watrUnSound = trap_S_RegisterSound( "sound/player/watr_un.wav"); + + cgs.media.explosionModel = trap_R_RegisterModel ( "models/map_objects/mp/sphere.md3" ); + cgs.media.surfaceExplosionShader = trap_R_RegisterShader( "surfaceExplosion" ); + + cgs.media.disruptorShader = trap_R_RegisterShader( "gfx/effects/burn"); + + if (cg_buildScript.integer) + { + trap_R_RegisterShader( "gfx/effects/turretflashdie" ); + } + + cgs.media.solidWhite = trap_R_RegisterShader( "gfx/effects/solidWhite_cull" ); + + trap_R_RegisterShader("gfx/misc/mp_light_enlight_disable"); + trap_R_RegisterShader("gfx/misc/mp_dark_enlight_disable"); + + trap_R_RegisterModel ( "models/map_objects/mp/sphere.md3" ); + trap_R_RegisterModel("models/items/remote.md3"); + + cgs.media.holocronPickup = trap_S_RegisterSound( "sound/player/holocron.wav" ); + + // Zoom + cgs.media.zoomStart = trap_S_RegisterSound( "sound/interface/zoomstart.wav" ); + cgs.media.zoomLoop = trap_S_RegisterSound( "sound/interface/zoomloop.wav" ); + cgs.media.zoomEnd = trap_S_RegisterSound( "sound/interface/zoomend.wav" ); + + for (i=0 ; i<4 ; i++) { + Com_sprintf (name, sizeof(name), "sound/player/footsteps/stone_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_STONEWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/stone_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_STONERUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/metal_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_METALWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/metal_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_METALRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/pipe_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_PIPEWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/pipe_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_PIPERUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/water_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SPLASH][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/water_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_WADE][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/water_wade_0%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SWIM][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/snow_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SNOWWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/snow_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SNOWRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/sand_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SANDWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/sand_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_SANDRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/grass_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_GRASSWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/grass_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_GRASSRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/dirt_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_DIRTWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/dirt_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_DIRTRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/mud_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_MUDWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/mud_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_MUDRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/gravel_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_GRAVELWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/gravel_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_GRAVELRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/rug_step%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_RUGWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/rug_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_RUGRUN][i] = trap_S_RegisterSound (name); + + Com_sprintf (name, sizeof(name), "sound/player/footsteps/wood_walk%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_WOODWALK][i] = trap_S_RegisterSound (name); + Com_sprintf (name, sizeof(name), "sound/player/footsteps/wood_run%i.wav", i+1); + cgs.media.footsteps[FOOTSTEP_WOODRUN][i] = trap_S_RegisterSound (name); + } + + // 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_RegisterItemSounds( i ); + } + } + + for ( i = 1 ; i < MAX_SOUNDS ; i++ ) { + soundName = CG_ConfigString( CS_SOUNDS+i ); + if ( !soundName[0] ) { + break; + } + if ( soundName[0] == '*' ) + { + if (soundName[1] == '$') + { //an NPC soundset + CG_PrecacheNPCSounds(soundName); + } + continue; // custom sound + } + cgs.gameSounds[i] = trap_S_RegisterSound( soundName ); + } + + for ( i = 1 ; i < MAX_FX ; i++ ) { + soundName = CG_ConfigString( CS_EFFECTS+i ); + if ( !soundName[0] ) { + break; + } + + if (soundName[0] == '*') + { //it's a special global weather effect + CG_ParseWeatherEffect(soundName); + cgs.gameEffects[i] = 0; + } + else + { + cgs.gameEffects[i] = trap_FX_RegisterEffect( soundName ); + } + } + + // register all the server specified icons + for ( i = 1; i < MAX_ICONS; i ++ ) + { + const char* iconName; + + iconName = CG_ConfigString ( CS_ICONS + i ); + if ( !iconName[0] ) + { + break; + } + + cgs.gameIcons[i] = trap_R_RegisterShaderNoMip ( iconName ); + } + + soundName = CG_ConfigString(CS_SIEGE_STATE); + + if (soundName[0]) + { + CG_ParseSiegeState(soundName); + } + + soundName = CG_ConfigString(CS_SIEGE_WINTEAM); + + if (soundName[0]) + { + cg_siegeWinTeam = atoi(soundName); + } + + if (cgs.gametype == GT_SIEGE) + { + CG_ParseSiegeObjectiveStatus(CG_ConfigString(CS_SIEGE_OBJECTIVES)); + cg_beatingSiegeTime = atoi(CG_ConfigString(CS_SIEGE_TIMEOVERRIDE)); + if ( cg_beatingSiegeTime ) + { + CG_SetSiegeTimerCvar ( cg_beatingSiegeTime ); + } + } + + cg.loadLCARSStage = 2; + + // FIXME: only needed with item + cgs.media.deploySeeker = trap_S_RegisterSound ("sound/chars/seeker/misc/hiss"); + cgs.media.medkitSound = trap_S_RegisterSound ("sound/items/use_bacta.wav"); + + cgs.media.winnerSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM006" ); + cgs.media.loserSound = trap_S_RegisterSound( "sound/chars/protocol/misc/40MOM010" ); +} + + +//------------------------------------- +// CG_RegisterEffects +// +// Handles precaching all effect files +// and any shader, model, or sound +// files an effect may use. +//------------------------------------- +static void CG_RegisterEffects( void ) +{ + /* + const char *effectName; + int i; + + for ( i = 1 ; i < MAX_FX ; i++ ) + { + effectName = CG_ConfigString( CS_EFFECTS + i ); + + if ( !effectName[0] ) + { + break; + } + + trap_FX_RegisterEffect( effectName ); + } + */ + //the above was redundant as it's being done in CG_RegisterSounds + + // Set up the glass effects mini-system. + CG_InitGlass(); + + //footstep effects + cgs.effects.footstepMud = trap_FX_RegisterEffect( "materials/mud" ); + cgs.effects.footstepSand = trap_FX_RegisterEffect( "materials/sand" ); + cgs.effects.footstepSnow = trap_FX_RegisterEffect( "materials/snow" ); + cgs.effects.footstepGravel = trap_FX_RegisterEffect( "materials/gravel" ); + //landing effects + cgs.effects.landingMud = trap_FX_RegisterEffect( "materials/mud_large" ); + cgs.effects.landingSand = trap_FX_RegisterEffect( "materials/sand_large" ); + cgs.effects.landingDirt = trap_FX_RegisterEffect( "materials/dirt_large" ); + cgs.effects.landingSnow = trap_FX_RegisterEffect( "materials/snow_large" ); + cgs.effects.landingGravel = trap_FX_RegisterEffect( "materials/gravel_large" ); + //splashes + cgs.effects.waterSplash = trap_FX_RegisterEffect( "env/water_impact" ); + cgs.effects.lavaSplash = trap_FX_RegisterEffect( "env/lava_splash" ); + cgs.effects.acidSplash = trap_FX_RegisterEffect( "env/acid_splash" ); +} + +//=================================================================================== + +extern char *forceHolocronModels[]; +int CG_HandleAppendedSkin(char *modelName); +void CG_CacheG2AnimInfo(char *modelName); +/* +================= +CG_RegisterGraphics + +This function may execute for a couple of minutes with a slow disk. +================= +*/ +static void CG_RegisterGraphics( void ) { + int i; + int breakPoint; + char items[MAX_ITEMS+1]; + const char *terrainInfo; + int terrainID; + + static char *sb_nums[11] = { + "gfx/2d/numbers/zero", + "gfx/2d/numbers/one", + "gfx/2d/numbers/two", + "gfx/2d/numbers/three", + "gfx/2d/numbers/four", + "gfx/2d/numbers/five", + "gfx/2d/numbers/six", + "gfx/2d/numbers/seven", + "gfx/2d/numbers/eight", + "gfx/2d/numbers/nine", + "gfx/2d/numbers/minus", + }; + + static char *sb_t_nums[11] = { + "gfx/2d/numbers/t_zero", + "gfx/2d/numbers/t_one", + "gfx/2d/numbers/t_two", + "gfx/2d/numbers/t_three", + "gfx/2d/numbers/t_four", + "gfx/2d/numbers/t_five", + "gfx/2d/numbers/t_six", + "gfx/2d/numbers/t_seven", + "gfx/2d/numbers/t_eight", + "gfx/2d/numbers/t_nine", + "gfx/2d/numbers/t_minus", + }; + + static char *sb_c_nums[11] = { + "gfx/2d/numbers/c_zero", + "gfx/2d/numbers/c_one", + "gfx/2d/numbers/c_two", + "gfx/2d/numbers/c_three", + "gfx/2d/numbers/c_four", + "gfx/2d/numbers/c_five", + "gfx/2d/numbers/c_six", + "gfx/2d/numbers/c_seven", + "gfx/2d/numbers/c_eight", + "gfx/2d/numbers/c_nine", + "gfx/2d/numbers/t_minus", //????? + }; + + // clear any references to old media + memset( &cg.refdef, 0, sizeof( cg.refdef ) ); + trap_R_ClearScene(); + + CG_LoadingString( cgs.mapname ); + +//#ifndef _XBOX + trap_R_LoadWorldMap( cgs.mapname ); +//#endif + + // precache status bar pics +// CG_LoadingString( "game media" ); + + for ( i=0 ; i<11 ; i++) { + cgs.media.numberShaders[i] = trap_R_RegisterShader( sb_nums[i] ); + } + + cg.loadLCARSStage = 3; + + for ( i=0; i < 11; i++ ) + { + cgs.media.numberShaders[i] = trap_R_RegisterShaderNoMip( sb_nums[i] ); + cgs.media.smallnumberShaders[i] = trap_R_RegisterShaderNoMip( sb_t_nums[i] ); + cgs.media.chunkyNumberShaders[i] = trap_R_RegisterShaderNoMip( sb_c_nums[i] ); + } + + trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_lone" ); + trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_double" ); + + cgs.media.balloonShader = trap_R_RegisterShader( "gfx/mp/chat_icon" ); + cgs.media.vchatShader = trap_R_RegisterShader( "gfx/mp/vchat_icon" ); + + cgs.media.deferShader = trap_R_RegisterShaderNoMip( "gfx/2d/defer.tga" ); + + cgs.media.radarShader = trap_R_RegisterShaderNoMip ( "gfx/menus/radar/radar.png" ); + cgs.media.siegeItemShader = trap_R_RegisterShaderNoMip ( "gfx/menus/radar/goalitem" ); + cgs.media.mAutomapPlayerIcon = trap_R_RegisterShader( "gfx/menus/radar/arrow_w" ); + cgs.media.mAutomapRocketIcon = trap_R_RegisterShader( "gfx/menus/radar/rocket" ); + + cgs.media.wireframeAutomapFrame_left = trap_R_RegisterShader( "gfx/mp_automap/mpauto_frame_left" ); + cgs.media.wireframeAutomapFrame_right = trap_R_RegisterShader( "gfx/mp_automap/mpauto_frame_right" ); + cgs.media.wireframeAutomapFrame_top = trap_R_RegisterShader( "gfx/mp_automap/mpauto_frame_top" ); + cgs.media.wireframeAutomapFrame_bottom = trap_R_RegisterShader( "gfx/mp_automap/mpauto_frame_bottom" ); + + cgs.media.lagometerShader = trap_R_RegisterShaderNoMip("gfx/2d/lag" ); + cgs.media.connectionShader = trap_R_RegisterShaderNoMip( "gfx/2d/net" ); + + trap_FX_InitSystem(&cg.refdef); + CG_RegisterEffects(); + + cgs.media.boltShader = trap_R_RegisterShader( "gfx/misc/blueLine" ); + + cgs.effects.turretShotEffect = trap_FX_RegisterEffect( "turret/shot" ); + cgs.effects.mEmplacedDeadSmoke = trap_FX_RegisterEffect("emplaced/dead_smoke.efx"); + cgs.effects.mEmplacedExplode = trap_FX_RegisterEffect("emplaced/explode.efx"); + cgs.effects.mTurretExplode = trap_FX_RegisterEffect("turret/explode.efx"); + cgs.effects.mSparkExplosion = trap_FX_RegisterEffect("sparks/spark_explosion.efx"); + cgs.effects.mTripmineExplosion = trap_FX_RegisterEffect("tripMine/explosion.efx"); + cgs.effects.mDetpackExplosion = trap_FX_RegisterEffect("detpack/explosion.efx"); + cgs.effects.mFlechetteAltBlow = trap_FX_RegisterEffect("flechette/alt_blow.efx"); + cgs.effects.mStunBatonFleshImpact = trap_FX_RegisterEffect("stunBaton/flesh_impact.efx"); + cgs.effects.mAltDetonate = trap_FX_RegisterEffect("demp2/altDetonate.efx"); + cgs.effects.mSparksExplodeNoSound = trap_FX_RegisterEffect("sparks/spark_exp_nosnd"); + cgs.effects.mTripMineLaster = trap_FX_RegisterEffect("tripMine/laser.efx"); + cgs.effects.mEmplacedMuzzleFlash = trap_FX_RegisterEffect( "effects/emplaced/muzzle_flash" ); + cgs.effects.mConcussionAltRing = trap_FX_RegisterEffect("concussion/alt_ring"); + + cgs.effects.mHyperspaceStars = trap_FX_RegisterEffect("ships/hyperspace_stars"); + cgs.effects.mBlackSmoke = trap_FX_RegisterEffect( "volumetric/black_smoke" ); + cgs.effects.mShipDestDestroyed = trap_FX_RegisterEffect("effects/ships/dest_destroyed.efx"); + cgs.effects.mShipDestBurning = trap_FX_RegisterEffect("effects/ships/dest_burning.efx"); + cgs.effects.mBobaJet = trap_FX_RegisterEffect("effects/boba/jet.efx"); + + + cgs.effects.itemCone = trap_FX_RegisterEffect("mp/itemcone.efx"); + cgs.effects.mTurretMuzzleFlash = trap_FX_RegisterEffect("effects/turret/muzzle_flash.efx"); + cgs.effects.mSparks = trap_FX_RegisterEffect("sparks/spark_nosnd.efx"); //sparks/spark.efx + cgs.effects.mSaberCut = trap_FX_RegisterEffect("saber/saber_cut.efx"); + cgs.effects.mSaberBlock = trap_FX_RegisterEffect("saber/saber_block.efx"); + cgs.effects.mSaberBloodSparks = trap_FX_RegisterEffect("saber/blood_sparks_mp.efx"); + cgs.effects.mSaberBloodSparksSmall = trap_FX_RegisterEffect("saber/blood_sparks_25_mp.efx"); + cgs.effects.mSaberBloodSparksMid = trap_FX_RegisterEffect("saber/blood_sparks_50_mp.efx"); + cgs.effects.mSpawn = trap_FX_RegisterEffect("mp/spawn.efx"); + cgs.effects.mJediSpawn = trap_FX_RegisterEffect("mp/jedispawn.efx"); + cgs.effects.mBlasterDeflect = trap_FX_RegisterEffect("blaster/deflect.efx"); + cgs.effects.mBlasterSmoke = trap_FX_RegisterEffect("blaster/smoke_bolton"); + cgs.effects.mForceConfustionOld = trap_FX_RegisterEffect("force/confusion_old.efx"); + + cgs.effects.forceLightning = trap_FX_RegisterEffect( "effects/force/lightning.efx" ); + cgs.effects.forceLightningWide = trap_FX_RegisterEffect( "effects/force/lightningwide.efx" ); + cgs.effects.forceDrain = trap_FX_RegisterEffect( "effects/mp/drain.efx" ); + cgs.effects.forceDrainWide = trap_FX_RegisterEffect( "effects/mp/drainwide.efx" ); + cgs.effects.forceDrained = trap_FX_RegisterEffect( "effects/mp/drainhit.efx"); + + cgs.effects.mDisruptorDeathSmoke = trap_FX_RegisterEffect("disruptor/death_smoke"); + + for ( i = 0 ; i < NUM_CROSSHAIRS ; i++ ) { + cgs.media.crosshairShader[i] = trap_R_RegisterShaderNoMip( va("gfx/2d/crosshair%c", 'a'+i) ); + } + + cg.loadLCARSStage = 4; + + cgs.media.backTileShader = trap_R_RegisterShader( "gfx/2d/backtile" ); + + //precache the fpls skin + //trap_R_RegisterSkin("models/players/kyle/model_fpls2.skin"); + + cgs.media.itemRespawningPlaceholder = trap_R_RegisterShader("powerups/placeholder"); + cgs.media.itemRespawningRezOut = trap_R_RegisterShader("powerups/rezout"); + + cgs.media.playerShieldDamage = trap_R_RegisterShader("gfx/misc/personalshield"); + cgs.media.protectShader = trap_R_RegisterShader("gfx/misc/forceprotect"); + cgs.media.forceSightBubble = trap_R_RegisterShader("gfx/misc/sightbubble"); + cgs.media.forceShell = trap_R_RegisterShader("powerups/forceshell"); + cgs.media.sightShell = trap_R_RegisterShader("powerups/sightshell"); + + cgs.media.itemHoloModel = trap_R_RegisterModel("models/map_objects/mp/holo.md3"); + + if (cgs.gametype == GT_HOLOCRON || cg_buildScript.integer) + { + for ( i=0; i < NUM_FORCE_POWERS; i++ ) + { + if (forceHolocronModels[i] && + forceHolocronModels[i][0]) + { + trap_R_RegisterModel(forceHolocronModels[i]); + } + } + } + + if ( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY || cg_buildScript.integer ) { + if (cg_buildScript.integer) + { + trap_R_RegisterModel( "models/flags/r_flag.md3" ); + trap_R_RegisterModel( "models/flags/b_flag.md3" ); + trap_R_RegisterModel( "models/flags/r_flag_ysal.md3" ); + trap_R_RegisterModel( "models/flags/b_flag_ysal.md3" ); + } + + if (cgs.gametype == GT_CTF) + { + cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag.md3" ); + cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag.md3" ); + } + else + { + cgs.media.redFlagModel = trap_R_RegisterModel( "models/flags/r_flag_ysal.md3" ); + cgs.media.blueFlagModel = trap_R_RegisterModel( "models/flags/b_flag_ysal.md3" ); + } + + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" ); + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" ); + + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" ); + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" ); + + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag" ); + trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag" ); + + trap_R_RegisterShaderNoMip("gfx/2d/net.tga"); + + cgs.media.flagPoleModel = trap_R_RegisterModel( "models/flag2/flagpole.md3" ); + cgs.media.flagFlapModel = trap_R_RegisterModel( "models/flag2/flagflap3.md3" ); + + 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" ); + } + + if ( cgs.gametype >= GT_TEAM || cg_buildScript.integer ) { + cgs.media.teamRedShader = trap_R_RegisterShader( "sprites/team_red" ); + cgs.media.teamBlueShader = trap_R_RegisterShader( "sprites/team_blue" ); + //cgs.media.redQuadShader = trap_R_RegisterShader("powerups/blueflag" ); + cgs.media.teamStatusBar = trap_R_RegisterShader( "gfx/2d/colorbar.tga" ); + } + else if ( cgs.gametype == GT_JEDIMASTER ) + { + cgs.media.teamRedShader = trap_R_RegisterShader( "sprites/team_red" ); + } + + if (cgs.gametype == GT_POWERDUEL || cg_buildScript.integer) + { + cgs.media.powerDuelAllyShader = trap_R_RegisterShader("gfx/mp/pduel_icon_double");//trap_R_RegisterShader("gfx/mp/pduel_gameicon_ally"); + } + + cgs.media.heartShader = trap_R_RegisterShaderNoMip( "ui/assets/statusbar/selectedhealth.tga" ); + + cgs.media.ysaliredShader = trap_R_RegisterShader( "powerups/ysaliredshell"); + cgs.media.ysaliblueShader = trap_R_RegisterShader( "powerups/ysaliblueshell"); + cgs.media.ysalimariShader = trap_R_RegisterShader( "powerups/ysalimarishell"); + cgs.media.boonShader = trap_R_RegisterShader( "powerups/boonshell"); + cgs.media.endarkenmentShader = trap_R_RegisterShader( "powerups/endarkenmentshell"); + cgs.media.enlightenmentShader = trap_R_RegisterShader( "powerups/enlightenmentshell"); + cgs.media.invulnerabilityShader = trap_R_RegisterShader( "powerups/invulnerabilityshell"); + +#ifdef JK2AWARDS + 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" ); +#endif + + // Binocular interface + cgs.media.binocularCircle = trap_R_RegisterShader( "gfx/2d/binCircle" ); + cgs.media.binocularMask = trap_R_RegisterShader( "gfx/2d/binMask" ); + cgs.media.binocularArrow = trap_R_RegisterShader( "gfx/2d/binSideArrow" ); + cgs.media.binocularTri = trap_R_RegisterShader( "gfx/2d/binTopTri" ); + cgs.media.binocularStatic = trap_R_RegisterShader( "gfx/2d/binocularWindow" ); + cgs.media.binocularOverlay = trap_R_RegisterShader( "gfx/2d/binocularNumOverlay" ); + + cg.loadLCARSStage = 5; + + // Chunk models + //FIXME: jfm:? bother to conditionally load these if an ent has this material type? + for ( i = 0; i < NUM_CHUNK_MODELS; i++ ) + { + cgs.media.chunkModels[CHUNK_METAL2][i] = trap_R_RegisterModel( va( "models/chunks/metal/metal1_%i.md3", i+1 ) ); //_ /switched\ _ + cgs.media.chunkModels[CHUNK_METAL1][i] = trap_R_RegisterModel( va( "models/chunks/metal/metal2_%i.md3", i+1 ) ); // \switched/ + cgs.media.chunkModels[CHUNK_ROCK1][i] = trap_R_RegisterModel( va( "models/chunks/rock/rock1_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_ROCK2][i] = trap_R_RegisterModel( va( "models/chunks/rock/rock2_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_ROCK3][i] = trap_R_RegisterModel( va( "models/chunks/rock/rock3_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_CRATE1][i] = trap_R_RegisterModel( va( "models/chunks/crate/crate1_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_CRATE2][i] = trap_R_RegisterModel( va( "models/chunks/crate/crate2_%i.md3", i+1 ) ); + cgs.media.chunkModels[CHUNK_WHITE_METAL][i] = trap_R_RegisterModel( va( "models/chunks/metal/wmetal1_%i.md3", i+1 ) ); + } + + cgs.media.chunkSound = trap_S_RegisterSound("sound/weapons/explosions/glasslcar"); + cgs.media.grateSound = trap_S_RegisterSound( "sound/effects/grate_destroy" ); + cgs.media.rockBreakSound = trap_S_RegisterSound("sound/effects/wall_smash"); + cgs.media.rockBounceSound[0] = trap_S_RegisterSound("sound/effects/stone_bounce"); + cgs.media.rockBounceSound[1] = trap_S_RegisterSound("sound/effects/stone_bounce2"); + cgs.media.metalBounceSound[0] = trap_S_RegisterSound("sound/effects/metal_bounce"); + cgs.media.metalBounceSound[1] = trap_S_RegisterSound("sound/effects/metal_bounce2"); + cgs.media.glassChunkSound = trap_S_RegisterSound("sound/weapons/explosions/glassbreak1"); + cgs.media.crateBreakSound[0] = trap_S_RegisterSound("sound/weapons/explosions/crateBust1" ); + cgs.media.crateBreakSound[1] = trap_S_RegisterSound("sound/weapons/explosions/crateBust2" ); + +/* +Ghoul2 Insert Start +*/ + CG_InitItems(); +/* +Ghoul2 Insert End +*/ + 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 ); + } + } + + cg.loadLCARSStage = 6; + + cgs.media.glassShardShader = trap_R_RegisterShader( "gfx/misc/test_crackle" ); + + // doing one shader just makes it look like a shell. By using two shaders with different bulge offsets and different texture scales, it has a much more chaotic look + cgs.media.electricBodyShader = trap_R_RegisterShader( "gfx/misc/electric" ); + cgs.media.electricBody2Shader = trap_R_RegisterShader( "gfx/misc/fullbodyelectric2" ); + + cgs.media.fsrMarkShader = trap_R_RegisterShader( "footstep_r" ); + cgs.media.fslMarkShader = trap_R_RegisterShader( "footstep_l" ); + cgs.media.fshrMarkShader = trap_R_RegisterShader( "footstep_heavy_r" ); + cgs.media.fshlMarkShader = trap_R_RegisterShader( "footstep_heavy_l" ); + + cgs.media.refractionShader = trap_R_RegisterShader("effects/refraction"); + + cgs.media.cloakedShader = trap_R_RegisterShader( "gfx/effects/cloakedShader" ); + + // wall marks + cgs.media.shadowMarkShader = trap_R_RegisterShader( "markShadow" ); + cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); + + cgs.media.viewPainShader = trap_R_RegisterShader( "gfx/misc/borgeyeflare" ); + cgs.media.viewPainShader_Shields = trap_R_RegisterShader( "gfx/mp/dmgshader_shields" ); + cgs.media.viewPainShader_ShieldsAndHealth = trap_R_RegisterShader( "gfx/mp/dmgshader_shieldsandhealth" ); + + // register the inline models + breakPoint = 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 ); + if (!cgs.inlineDrawModel[i]) + { + breakPoint = i; + break; + } + + 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] ); + } + } + + cg.loadLCARSStage = 7; + + // 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 ]; +} + +//================================================================== + +/* +====================== +CG_StartMusic + +====================== +*/ +void CG_StartMusic( qboolean bForceStart ) { + char *s; + char parm1[MAX_QPATH], parm2[MAX_QPATH]; + + // start the background music + s = (char *)CG_ConfigString( CS_MUSIC ); + Q_strncpyz( parm1, COM_Parse( (const char **)&s ), sizeof( parm1 ) ); + Q_strncpyz( parm2, COM_Parse( (const char **)&s ), sizeof( parm2 ) ); + + trap_S_StartBackgroundTrack( parm1, parm2, !bForceStart ); +} + +#ifndef _XBOX +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; +} +#endif + +// +// ============================== +// new hud stuff ( mission pack ) +// ============================== +// +qboolean CG_Asset_Parse(int handle) { + pc_token_t token; + + 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 (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } + +// cgDC.registerFont(token.string, pointSize, &cgDC.Assets.textFont); + cgDC.Assets.qhMediumFont = cgDC.RegisterFont(token.string); + continue; + } + + // smallFont + if (Q_stricmp(token.string, "smallFont") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } +// cgDC.registerFont(token.string, pointSize, &cgDC.Assets.smallFont); + cgDC.Assets.qhSmallFont = cgDC.RegisterFont(token.string); + continue; + } + + // smallFont + if (Q_stricmp(token.string, "small2Font") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } +// cgDC.registerFont(token.string, pointSize, &cgDC.Assets.smallFont); + cgDC.Assets.qhSmall2Font = cgDC.RegisterFont(token.string); + continue; + } + + // font + if (Q_stricmp(token.string, "bigfont") == 0) { + int pointSize; + if (!trap_PC_ReadToken(handle, &token) || !PC_Int_Parse(handle, &pointSize)) { + return qfalse; + } +// cgDC.registerFont(token.string, pointSize, &cgDC.Assets.bigFont); + cgDC.Assets.qhBigFont = cgDC.RegisterFont(token.string); + continue; + } + + // gradientbar + if (Q_stricmp(token.string, "gradientbar") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + cgDC.Assets.gradientBar = trap_R_RegisterShaderNoMip(token.string); + continue; + } + + // enterMenuSound + if (Q_stricmp(token.string, "menuEnterSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + cgDC.Assets.menuEnterSound = trap_S_RegisterSound( token.string ); + continue; + } + + // exitMenuSound + if (Q_stricmp(token.string, "menuExitSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + cgDC.Assets.menuExitSound = trap_S_RegisterSound( token.string ); + continue; + } + + // itemFocusSound + if (Q_stricmp(token.string, "itemFocusSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + cgDC.Assets.itemFocusSound = trap_S_RegisterSound( token.string ); + continue; + } + + // menuBuzzSound + if (Q_stricmp(token.string, "menuBuzzSound") == 0) { + if (!trap_PC_ReadToken(handle, &token)) { + return qfalse; + } + cgDC.Assets.menuBuzzSound = trap_S_RegisterSound( token.string ); + 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(const char **p) +{ + + char *token; + + token = COM_ParseExt((const char **)p, qtrue); + + if (token[0] != '{') { + return qfalse; + } + + while ( 1 ) { + + token = COM_ParseExt((const char **)p, qtrue); + + if (Q_stricmp(token, "}") == 0) { + return qtrue; + } + + if ( !token || token[0] == 0 ) { + return qfalse; + } + + CG_ParseMenu(token); + } + return qfalse; +} + + +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 *handle1, qhandle_t *handle2, qhandle_t *handle3) { + gitem_t *item; + int scoreIndex = 0; + clientInfo_t *info = NULL; + int team = -1; + score_t *sp = NULL; + + *handle1 = *handle2 = *handle3 = -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 ); + *handle1 = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_REDFLAG ) ) { + item = BG_FindItemForPowerup( PW_REDFLAG ); + *handle1 = cg_items[ ITEM_INDEX(item) ].icon; + } else if ( info->powerups & ( 1 << PW_BLUEFLAG ) ) { + item = BG_FindItemForPowerup( PW_BLUEFLAG ); + *handle1 = cg_items[ ITEM_INDEX(item) ].icon; + } else { + /* + if ( info->botSkill > 0 && info->botSkill <= 5 ) { + *handle1 = cgs.media.botSkillShaders[ info->botSkill - 1 ]; + } else if ( info->handicap < 100 ) { + return va("%i", info->handicap ); + } + */ + } + break; + case 1: + if (team == -1) { + return ""; + } else { + *handle1 = 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_DUEL || cgs.gametype == GT_POWERDUEL) { + 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 qboolean CG_FeederSelection(float feederID, int index, itemDef_t *item) { + 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; + } + + return qtrue; +} + +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); +} + +void CG_Text_PaintWithCursor(float x, float y, float scale, vec4_t color, const char *text, int cursorPos, char cursor, int limit, int style, int iMenuFont) { + CG_Text_Paint(x, y, scale, color, text, 0, limit, style, iMenuFont); +} + +static int CG_OwnerDrawWidth(int ownerDraw, float scale) { + switch (ownerDraw) { + case CG_GAME_TYPE: + return CG_Text_Width(CG_GameTypeString(), scale, FONT_MEDIUM); + case CG_GAME_STATUS: + return CG_Text_Width(CG_GetGameStatusText(), scale, FONT_MEDIUM); + break; + case CG_KILLER: + return CG_Text_Width(CG_GetKillerText(), scale, FONT_MEDIUM); + break; + case CG_RED_NAME: + return CG_Text_Width(DEFAULT_REDTEAM_NAME/*cg_redTeamName.string*/, scale, FONT_MEDIUM); + break; + case CG_BLUE_NAME: + return CG_Text_Width(DEFAULT_BLUETEAM_NAME/*cg_blueTeamName.string*/, scale, FONT_MEDIUM); + 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_LoadMenus(); + +================= +*/ +void CG_LoadMenus(const char *menuFile) +{ + const char *token; + const char *p; + int len; + fileHandle_t f; + static char buf[MAX_MENUDEFFILE]; + + len = trap_FS_FOpenFile( menuFile, &f, FS_READ ); + + if ( !f ) + { + trap_Print( va( S_COLOR_RED "menu file not found: %s, using default\n", menuFile ) ); + + len = trap_FS_FOpenFile( "ui/jahud.txt", &f, FS_READ ); + if (!f) + { + trap_Print( va( S_COLOR_RED "default menu file not found: ui/hud.txt, unable to continue!\n", menuFile ) ); + } + } + + if ( len >= MAX_MENUDEFFILE ) + { + trap_Print( 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 ); + + p = buf; + + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if( !token || token[0] == 0 || token[0] == '}') + { + 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", cgi_Milliseconds() - start); +} + +/* +================= +CG_LoadHudMenu(); + +================= +*/ +void CG_LoadHudMenu() +{ + 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.Font_StrLenPixels = &trap_R_Font_StrLenPixels; + cgDC.Font_StrLenChars = &trap_R_Font_StrLenChars; + cgDC.Font_HeightPixels = &trap_R_Font_HeightPixels; + cgDC.Font_DrawString = &trap_R_Font_DrawString; + cgDC.Language_IsAsian = &trap_Language_IsAsian; + cgDC.Language_UsesSpaces = &trap_Language_UsesSpaces; + cgDC.AnyLanguage_ReadCharFromString = &trap_AnyLanguage_ReadCharFromString; + cgDC.ownerDrawItem = &CG_OwnerDraw; + cgDC.getValue = &CG_GetValue; + cgDC.ownerDrawVisible = &CG_OwnerDrawVisible; + cgDC.runScript = &CG_RunMenuScript; + cgDC.deferScript = &CG_DeferMenuScript; + 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(); + + hudSet = cg_hudFiles.string; + if (hudSet[0] == '\0') + { + hudSet = "ui/jahud.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 ); +} + +/* + + +/* +Ghoul2 Insert Start +*/ + +// initialise the cg_entities structure - take into account the ghoul2 stl stuff in the active snap shots +void CG_Init_CG(void) +{ +#ifdef _XBOX + qboolean widescreen = cg.widescreen; +#endif + memset( &cg, 0, sizeof(cg)); +#ifdef _XBOX + cg.widescreen = widescreen; +#endif +} + +#ifdef _XBOX +void CG_SetWidescreen(qboolean widescreen) +{ + cg.widescreen = widescreen; +} +#endif + + +// initialise the cg_entities structure - take into account the ghoul2 stl stuff +void CG_Init_CGents(void) +{ + + memset(&cg_entities, 0, sizeof(cg_entities)); +} + + +void CG_InitItems(void) +{ + memset( cg_items, 0, sizeof( cg_items ) ); +} + +void CG_TransitionPermanent(void) +{ + centity_t *cent = cg_entities; + int i; + + cg_numpermanents = 0; + for(i=0;icurrentState)) + { + cent->nextState = cent->currentState; + VectorCopy (cent->currentState.origin, cent->lerpOrigin); + VectorCopy (cent->currentState.angles, cent->lerpAngles); + cent->currentValid = qtrue; + + cg_permanents[cg_numpermanents++] = cent; + } + } +} + + +//this is a 32k custom pool for parsing ents, it can get reset between ent parsing +//so we don't need a whole lot of memory -rww +#define MAX_CGSTRPOOL_SIZE 32768 +static int cg_strPoolSize = 0; +static byte cg_strPool[MAX_CGSTRPOOL_SIZE]; + +char *CG_StrPool_Alloc(int size) +{ + char *giveThemThis; + + if (cg_strPoolSize+size >= MAX_CGSTRPOOL_SIZE) + { + Com_Error(ERR_DROP, "You exceeded the cgame string pool size. Bad programmer!\n"); + } + + giveThemThis = (char *) &cg_strPool[cg_strPoolSize]; + cg_strPoolSize += size; + + //memset it for them, just to be nice. + memset(giveThemThis, 0, size); + + return giveThemThis; +} + +void CG_StrPool_Reset(void) +{ + cg_strPoolSize = 0; +} + +/* +============= +CG_NewString + +Builds a copy of the string, translating \n to real linefeeds +so message texts can be multi-line +============= +*/ +char *CG_NewString( const char *string ) +{ + char *newb, *new_p; + int i,l; + + l = strlen(string) + 1; + + newb = CG_StrPool_Alloc( l ); + + new_p = newb; + + // turn \n into a real linefeed + for ( i=0 ; i< l ; i++ ) { + if (string[i] == '\\' && i < l-1) { + i++; + if (string[i] == 'n') { + *new_p++ = '\n'; + } else { + *new_p++ = '\\'; + } + } else { + *new_p++ = string[i]; + } + } + + return newb; +} + +//data to grab our spawn info into +typedef struct cgSpawnEnt_s +{ + char *classname; + vec3_t origin; + vec3_t angles; + float angle; + vec3_t scale; + float fScale; + vec3_t mins; + vec3_t maxs; + char *model; + float zoffset; + int onlyFogHere; + float fogstart; + float radarrange; +} cgSpawnEnt_t; + +#define CGFOFS(x) ((int)&(((cgSpawnEnt_t *)0)->x)) + +//spawn fields for our cgame "entity" +BG_field_t cg_spawnFields[] = +{ + {"classname", CGFOFS(classname), F_LSTRING}, + {"origin", CGFOFS(origin), F_VECTOR}, + {"angles", CGFOFS(angles), F_VECTOR}, + {"angle", CGFOFS(angle), F_FLOAT}, + {"modelscale", CGFOFS(fScale), F_FLOAT}, + {"modelscale_vec", CGFOFS(scale), F_VECTOR}, + {"model", CGFOFS(model), F_LSTRING}, + {"mins", CGFOFS(mins), F_VECTOR}, + {"maxs", CGFOFS(maxs), F_VECTOR}, + {"zoffset", CGFOFS(zoffset), F_FLOAT}, + {"onlyfoghere", CGFOFS(onlyFogHere), F_INT}, + {"fogstart", CGFOFS(fogstart), F_FLOAT}, + {"radarrange", CGFOFS(radarrange), F_FLOAT}, + {NULL} +}; + +static int cg_numSpawnVars; +static int cg_numSpawnVarChars; +static char *cg_spawnVars[MAX_SPAWN_VARS][2]; +static char cg_spawnVarChars[MAX_SPAWN_VARS_CHARS]; + +//get some info from the skyportal ent on the map +qboolean cg_noFogOutsidePortal = qfalse; +void CG_CreateSkyPortalFromSpawnEnt(cgSpawnEnt_t *ent) +{ + if (ent->onlyFogHere) + { //only globally fog INSIDE the sky portal + cg_noFogOutsidePortal = qtrue; + } +} + +//create a skybox portal orientation entity. there -should- only +//be one of these things per level. if there's more than one the +//next will just stomp over the last. -rww +qboolean cg_skyOri = qfalse; +vec3_t cg_skyOriPos; +float cg_skyOriScale = 0.0f; +void CG_CreateSkyOriFromSpawnEnt(cgSpawnEnt_t *ent) +{ + cg_skyOri = qtrue; + VectorCopy(ent->origin, cg_skyOriPos); + cg_skyOriScale = ent->fScale; +} + +//get brush box extents, note this does not care about bsp instances. +void CG_CreateBrushEntData(cgSpawnEnt_t *ent) +{ + trap_R_ModelBounds(trap_R_RegisterModel(ent->model), ent->mins, ent->maxs); +} + +void CG_CreateWeatherZoneFromSpawnEnt(cgSpawnEnt_t *ent) +{ + CG_CreateBrushEntData(ent); + trap_WE_AddWeatherZone(ent->mins, ent->maxs); +} + +//create a new cgame-only model +void CG_CreateModelFromSpawnEnt(cgSpawnEnt_t *ent) +{ + int modelIndex; + refEntity_t *RefEnt; + vec3_t mins, maxs; + float *radius; + float *zOff; + + if (NumMiscEnts >= MAX_MISC_ENTS) + { + Com_Error(ERR_DROP, "Too many misc_model_static's on level, ask a programmer to raise the limit (currently %i), or take some out.", MAX_MISC_ENTS); + return; + } + + if (!ent || !ent->model || !ent->model[0]) + { + Com_Error(ERR_DROP, "misc_model_static with no model."); + return; + } + + radius = &Radius[NumMiscEnts]; + zOff = &zOffset[NumMiscEnts]; + RefEnt = &MiscEnts[NumMiscEnts++]; + + modelIndex = trap_R_RegisterModel(ent->model); + if (modelIndex == 0) + { + Com_Error(ERR_DROP, "misc_model_static failed to load model '%s'",ent->model); + return; + } + + memset(RefEnt, 0, sizeof(refEntity_t)); + RefEnt->reType = RT_MODEL; + RefEnt->hModel = modelIndex; + RefEnt->frame = 0; + trap_R_ModelBounds(modelIndex, mins, maxs); + VectorCopy(ent->scale, RefEnt->modelScale); + if (ent->fScale) + { //use same scale on each axis then + RefEnt->modelScale[0] = RefEnt->modelScale[1] = RefEnt->modelScale[2] = ent->fScale; + } + VectorCopy(ent->origin, RefEnt->origin); + VectorCopy(ent->origin, RefEnt->lightingOrigin); + + VectorScaleVector(mins, ent->scale, mins); + VectorScaleVector(maxs, ent->scale, maxs); + *radius = Distance(mins, maxs); + *zOff = ent->zoffset; + + if (ent->angle) + { //only yaw supplied... + ent->angles[YAW] = ent->angle; + } + + AnglesToAxis( ent->angles, RefEnt->axis ); + ScaleModelAxis(RefEnt); +} + +/* +==================== +CG_AddSpawnVarToken +==================== +*/ +char *CG_AddSpawnVarToken( const char *string ) +{ + int l; + char *dest; + + l = strlen( string ); + if ( cg_numSpawnVarChars + l + 1 > MAX_SPAWN_VARS_CHARS ) { + CG_Error( "CG_AddSpawnVarToken: MAX_SPAWN_VARS" ); + } + + dest = cg_spawnVarChars + cg_numSpawnVarChars; + memcpy( dest, string, l+1 ); + + cg_numSpawnVarChars += l + 1; + + return dest; +} + +/* +==================== +CG_ParseSpawnVars + +cgame version of G_ParseSpawnVars, for ents that don't really +need to take up an entity slot (e.g. static models) -rww +==================== +*/ +qboolean CG_ParseSpawnVars( void ) +{ + char keyname[MAX_TOKEN_CHARS]; + char com_token[MAX_TOKEN_CHARS]; + + cg_numSpawnVars = 0; + cg_numSpawnVarChars = 0; + + // parse the opening brace + if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) { + // end of spawn string + return qfalse; + } + if ( com_token[0] != '{' ) { + CG_Error( "CG_ParseSpawnVars: found %s when expecting {",com_token ); + } + + // go through all the key / value pairs + while ( 1 ) + { + // parse key + if ( !trap_GetEntityToken( keyname, sizeof( keyname ) ) ) + { + CG_Error( "CG_ParseSpawnVars: EOF without closing brace" ); + } + + if ( keyname[0] == '}' ) + { + break; + } + + // parse value + if ( !trap_GetEntityToken( com_token, sizeof( com_token ) ) ) + { //this happens on mike's test level, I don't know why. Fixme? + //CG_Error( "CG_ParseSpawnVars: EOF without closing brace" ); + break; + } + + if ( com_token[0] == '}' ) + { + CG_Error( "CG_ParseSpawnVars: closing brace without data" ); + } + if ( cg_numSpawnVars == MAX_SPAWN_VARS ) + { + CG_Error( "CG_ParseSpawnVars: MAX_SPAWN_VARS" ); + } + cg_spawnVars[ cg_numSpawnVars ][0] = CG_AddSpawnVarToken( keyname ); + cg_spawnVars[ cg_numSpawnVars ][1] = CG_AddSpawnVarToken( com_token ); + cg_numSpawnVars++; + } + + return qtrue; +} + +/* +============== +CG_SpawnCGameEntFromVars + +See if we should do something for this ent cgame-side -rww +============== +*/ +#include "../namespace_begin.h" +void BG_ParseField( BG_field_t *l_fields, const char *key, const char *value, byte *ent ); +#include "../namespace_end.h" + +extern float cg_linearFogOverride; //cg_view.c +extern float cg_radarRange;//cg_draw.c +void CG_SpawnCGameEntFromVars(void) +{ + int i; + cgSpawnEnt_t ent; + + memset(&ent, 0, sizeof(cgSpawnEnt_t)); + + for (i = 0; i < cg_numSpawnVars; i++) + { //shove all this stuff into our data structure used specifically for getting spawn info + BG_ParseField( cg_spawnFields, cg_spawnVars[i][0], cg_spawnVars[i][1], (byte *)&ent ); + } + + if (ent.classname && ent.classname[0]) + { //we'll just stricmp this bastard, since there aren't all that many cgame-only things, and they all have special handling + if (!Q_stricmp(ent.classname, "worldspawn")) + { //I'd like some info off this guy + if (ent.fogstart) + { //linear fog method + cg_linearFogOverride = ent.fogstart; + } + //get radarRange off of worldspawn + if (ent.radarrange) + { //linear fog method + cg_radarRange = ent.radarrange; + } + } + else if (!Q_stricmp(ent.classname, "misc_model_static")) + { //we've got us a static model + CG_CreateModelFromSpawnEnt(&ent); + } + else if (!Q_stricmp(ent.classname, "misc_skyportal_orient")) + { //a sky portal orientation point + CG_CreateSkyOriFromSpawnEnt(&ent); + } + else if (!Q_stricmp(ent.classname, "misc_skyportal")) + { //might as well parse this thing cgame side for the extra info I want out of it + CG_CreateSkyPortalFromSpawnEnt(&ent); + } + else if (!Q_stricmp(ent.classname, "misc_weather_zone")) + { //might as well parse this thing cgame side for the extra info I want out of it + CG_CreateWeatherZoneFromSpawnEnt(&ent); + } + } + + //reset the string pool for the next entity, if there is one + CG_StrPool_Reset(); +} + +/* +============== +CG_SpawnCGameOnlyEnts + +Parses entity string data for cgame-only entities, that we can throw away on +the server and never even bother sending. -rww +============== +*/ +void CG_SpawnCGameOnlyEnts(void) +{ + //make sure it is reset + trap_GetEntityToken(NULL, -1); + + if (!CG_ParseSpawnVars()) + { //first one is gonna be the world spawn + CG_Error("no entities for cgame parse"); + } + else + { //parse the world spawn info we want + CG_SpawnCGameEntFromVars(); + } + + while(CG_ParseSpawnVars()) + { //now run through the whole list, and look for things we care about cgame-side + CG_SpawnCGameEntFromVars(); + } +} + +/* +Ghoul2 Insert End +*/ + +extern playerState_t *cgSendPS[MAX_GENTITIES]; //is not MAX_CLIENTS because NPCs exceed MAX_CLIENTS +void CG_PmoveClientPointerUpdate(); + +#include "../namespace_begin.h" +void WP_SaberLoadParms( void ); +void BG_VehicleLoadParms( void ); +#include "../namespace_end.h" + +/* +================= +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 ) +{ + static gitem_t *item; + char buf[64]; + const char *s; + int i = 0; + + BG_InitAnimsets(); //clear it out + + trap_CG_RegisterSharedMemory(cg.sharedBuffer); + + //Load external vehicle data + BG_VehicleLoadParms(); + + // clear everything +/* +Ghoul2 Insert Start +*/ + +// memset( cg_entities, 0, sizeof( cg_entities ) ); + CG_Init_CGents(); +// this is a No-No now we have stl vector classes in here. +// memset( &cg, 0, sizeof( cg ) ); + CG_Init_CG(); + CG_InitItems(); + + //create the global jetpack instance + CG_InitJetpackGhoul2(); + + CG_PmoveClientPointerUpdate(); + +/* +Ghoul2 Insert End +*/ + + //Load sabers.cfg data + WP_SaberLoadParms(); + + // this is kinda dumb as well, but I need to pre-load some fonts in order to have the text available + // to say I'm loading the assets.... which includes loading the fonts. So I'll set these up as reasonable + // defaults, then let the menu asset parser (which actually specifies the ingame fonts) load over them + // if desired during parse. Dunno how legal it is to store in these cgDC things, but it causes no harm + // and even if/when they get overwritten they'll be legalised by the menu asset parser :-) +// CG_LoadFonts(); + cgDC.Assets.qhSmallFont = trap_R_RegisterFont("ocr_a"); + cgDC.Assets.qhMediumFont = trap_R_RegisterFont("ergoec"); + cgDC.Assets.qhBigFont = cgDC.Assets.qhMediumFont; + + memset( &cgs, 0, sizeof( cgs ) ); + memset( cg_weapons, 0, sizeof(cg_weapons) ); + + cg.clientNum = clientNum; + + cgs.processedSnapshotNum = serverMessageNum; + cgs.serverCommandSequence = serverCommandSequence; + + cg.loadLCARSStage = 0; + + cg.itemSelect = -1; + cg.forceSelect = -1; + + // load a few needed things before we do any screen updates + cgs.media.charsetShader = trap_R_RegisterShaderNoMip( "gfx/2d/charsgrid_med" ); + cgs.media.whiteShader = trap_R_RegisterShader( "white" ); + + cgs.media.loadBarLED = trap_R_RegisterShaderNoMip( "gfx/hud/load_tick" ); + cgs.media.loadBarLEDCap = trap_R_RegisterShaderNoMip( "gfx/hud/load_tick_cap" ); + cgs.media.loadBarLEDSurround= trap_R_RegisterShaderNoMip( "gfx/hud/mp_levelload" ); + + // Force HUD set up + cg.forceHUDActive = qtrue; + cg.forceHUDTotalFlashTime = 0; + cg.forceHUDNextFlashTime = 0; + + i = WP_NONE+1; + while (i <= LAST_USEABLE_WEAPON) + { + item = BG_FindItemForWeapon(i); + + if (item && item->icon && item->icon[0]) + { + cgs.media.weaponIcons[i] = trap_R_RegisterShaderNoMip(item->icon); + cgs.media.weaponIcons_NA[i] = trap_R_RegisterShaderNoMip(va("%s_na", item->icon)); + } + else + { //make sure it is zero'd (default shader) + cgs.media.weaponIcons[i] = 0; + cgs.media.weaponIcons_NA[i] = 0; + } + i++; + } + trap_Cvar_VariableStringBuffer("com_buildscript", buf, sizeof(buf)); + if (atoi(buf)) + { + trap_R_RegisterShaderNoMip("gfx/hud/w_icon_saberstaff"); + trap_R_RegisterShaderNoMip("gfx/hud/w_icon_duallightsaber"); + } + i = 0; + + // HUD artwork for cycling inventory,weapons and force powers + cgs.media.weaponIconBackground = trap_R_RegisterShaderNoMip( "gfx/hud/background"); + cgs.media.forceIconBackground = trap_R_RegisterShaderNoMip( "gfx/hud/background_f"); + cgs.media.inventoryIconBackground = trap_R_RegisterShaderNoMip( "gfx/hud/background_i"); + + //rww - precache holdable item icons here + while (i < bg_numItems) + { + if (bg_itemlist[i].giType == IT_HOLDABLE) + { + if (bg_itemlist[i].icon) + { + cgs.media.invenIcons[bg_itemlist[i].giTag] = trap_R_RegisterShaderNoMip(bg_itemlist[i].icon); + } + else + { + cgs.media.invenIcons[bg_itemlist[i].giTag] = 0; + } + } + + i++; + } + + //rww - precache force power icons here + i = 0; + + while (i < NUM_FORCE_POWERS) + { + cgs.media.forcePowerIcons[i] = trap_R_RegisterShaderNoMip(HolocronIcons[i]); + + i++; + } + cgs.media.rageRecShader = trap_R_RegisterShaderNoMip("gfx/mp/f_icon_ragerec"); + + + //body decal shaders -rww + cgs.media.bdecal_bodyburn1 = trap_R_RegisterShader("gfx/damage/bodyburnmark1"); + cgs.media.bdecal_saberglow = trap_R_RegisterShader("gfx/damage/saberglowmark"); + cgs.media.bdecal_burn1 = trap_R_RegisterShader("gfx/damage/bodybigburnmark1"); + cgs.media.mSaberDamageGlow = trap_R_RegisterShader("gfx/effects/saberDamageGlow"); + + CG_RegisterCvars(); + + CG_InitConsoleCommands(); + + cg.weaponSelect = WP_BRYAR_PISTOL; + + 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 ); + + CG_TransitionPermanent(); //rwwRMG - added + + // check version + s = CG_ConfigString( CS_GAME_VERSION ); + if ( strcmp( s, GAME_VERSION ) ) { + CG_Error( "Client/Server game mismatch: %s/%s", GAME_VERSION, s ); + } + + 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, qfalse ); + + String_Init(); + + cg.loading = qtrue; // force players to load instead of defer + + //make sure saber data is loaded before this! (so we can precache the appropriate hilts) + CG_InitSiegeMode(); + + CG_RegisterSounds(); + +// CG_LoadingString( "graphics" ); + + CG_RegisterGraphics(); + +// CG_LoadingString( "clients" ); + + CG_RegisterClients(); // if low on memory, some clients will be deferred + + CG_AssetCache(); + 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(qfalse); + +// CG_LoadingString( "Clearing light styles" ); + CG_ClearLightStyles(); + +// CG_LoadingString( "Creating automap data" ); + //init automap +#ifndef _XBOX + trap_R_InitWireframeAutomap(); +#endif + + CG_LoadingString( "" ); + + CG_ShaderStateChanged(); + + trap_S_ClearLoopingSounds(); + + trap_R_GetDistanceCull(&cg.distanceCull); + + //now get all the cgame only cents + CG_SpawnCGameOnlyEnts(); +} + +//makes sure returned string is in localized format +const char *CG_GetLocationString(const char *loc) +{ + static char text[1024]={0}; + + if (!loc || loc[0] != '@') + { //just a raw string + return loc; + } + + trap_SP_GetStringTextString(loc+1, text, sizeof(text)); + return text; +} + +//clean up all the ghoul2 allocations, the nice and non-hackly way -rww +void CG_KillCEntityG2(int entNum); +void CG_DestroyAllGhoul2(void) +{ + int i = 0; + int j; + +// Com_Printf("... CGameside GHOUL2 Cleanup\n"); + while (i < MAX_GENTITIES) + { //free all dynamically allocated npc client info structs and ghoul2 instances + CG_KillCEntityG2(i); + i++; + } + + //Clean the weapon instances + CG_ShutDownG2Weapons(); + + i = 0; + while (i < MAX_ITEMS) + { //and now for items + j = 0; + while (j < MAX_ITEM_MODELS) + { + if (cg_items[i].g2Models[j] && trap_G2_HaveWeGhoul2Models(cg_items[i].g2Models[j])) + { + trap_G2API_CleanGhoul2Models(&cg_items[i].g2Models[j]); + cg_items[i].g2Models[j] = NULL; + } + j++; + } + i++; + } + + //Clean the global jetpack instance + CG_CleanJetpackGhoul2(); +} + +/* +================= +CG_Shutdown + +Called before every level change or subsystem restart +================= +*/ +void CG_Shutdown( void ) +{ + BG_ClearAnimsets(); //free all dynamic allocations made through the engine + + CG_DestroyAllGhoul2(); + +// Com_Printf("... FX System Cleanup\n"); + trap_FX_FreeSystem(); + trap_ROFF_Clean(); + + if (cgWeatherOverride) + { + trap_R_WeatherContentsOverride(0); //rwwRMG - reset it engine-side + } + + //reset weather + trap_R_WorldEffectCommand("die"); + + UI_CleanupGhoul2(); + //If there was any ghoul2 stuff in our side of the shared ui code, then remove it now. + + // some mods may need to do cleanup work here, + // like closing files or archiving session data +} + +/* +=============== +CG_NextForcePower_f +=============== +*/ +void CG_NextForcePower_f( void ) +{ + int current; + usercmd_t cmd; + if ( !cg.snap ) + { + return; + } + + if (cg.predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + current = trap_GetCurrentCmdNumber(); + trap_GetUserCmd(current, &cmd); + if ((cmd.buttons & BUTTON_USE) || CG_NoUseableForce()) + { + CG_NextInventory_f(); + return; + } + + if (cg.snap->ps.pm_flags & PMF_FOLLOW) + { + return; + } + +// BG_CycleForce(&cg.snap->ps, 1); + if (cg.forceSelect != -1) + { + cg.snap->ps.fd.forcePowerSelected = cg.forceSelect; + } + + BG_CycleForce(&cg.snap->ps, 1); + + if (cg.snap->ps.fd.forcePowersKnown & (1 << cg.snap->ps.fd.forcePowerSelected)) + { + cg.forceSelect = cg.snap->ps.fd.forcePowerSelected; + cg.forceSelectTime = cg.time; + } +} + +/* +=============== +CG_PrevForcePower_f +=============== +*/ +void CG_PrevForcePower_f( void ) +{ + int current; + usercmd_t cmd; + if ( !cg.snap ) + { + return; + } + + if (cg.predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + current = trap_GetCurrentCmdNumber(); + trap_GetUserCmd(current, &cmd); + if ((cmd.buttons & BUTTON_USE) || CG_NoUseableForce()) + { + CG_PrevInventory_f(); + return; + } + + if (cg.snap->ps.pm_flags & PMF_FOLLOW) + { + return; + } + +// BG_CycleForce(&cg.snap->ps, -1); + if (cg.forceSelect != -1) + { + cg.snap->ps.fd.forcePowerSelected = cg.forceSelect; + } + + BG_CycleForce(&cg.snap->ps, -1); + + if (cg.snap->ps.fd.forcePowersKnown & (1 << cg.snap->ps.fd.forcePowerSelected)) + { + cg.forceSelect = cg.snap->ps.fd.forcePowerSelected; + cg.forceSelectTime = cg.time; + } +} + +void CG_NextInventory_f(void) +{ + if ( !cg.snap ) + { + return; + } + + if (cg.snap->ps.pm_flags & PMF_FOLLOW) + { + return; + } + + if (cg.predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + if (cg.itemSelect != -1) + { + cg.snap->ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(cg.itemSelect, IT_HOLDABLE); + } + BG_CycleInven(&cg.snap->ps, 1); + + if (cg.snap->ps.stats[STAT_HOLDABLE_ITEM]) + { + cg.itemSelect = bg_itemlist[cg.snap->ps.stats[STAT_HOLDABLE_ITEM]].giTag; + cg.invenSelectTime = cg.time; + } +} + +void CG_PrevInventory_f(void) +{ + if ( !cg.snap ) + { + return; + } + + if (cg.snap->ps.pm_flags & PMF_FOLLOW) + { + return; + } + + if (cg.predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + if (cg.itemSelect != -1) + { + cg.snap->ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(cg.itemSelect, IT_HOLDABLE); + } + BG_CycleInven(&cg.snap->ps, -1); + + if (cg.snap->ps.stats[STAT_HOLDABLE_ITEM]) + { + cg.itemSelect = bg_itemlist[cg.snap->ps.stats[STAT_HOLDABLE_ITEM]].giTag; + cg.invenSelectTime = cg.time; + } +} diff --git a/code/cgame/cg_marks.c b/code/cgame/cg_marks.c new file mode 100644 index 0000000..5f32111 --- /dev/null +++ b/code/cgame/cg_marks.c @@ -0,0 +1,2272 @@ +// 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; + + assert(markShader); + + if ( !cg_addMarks.integer ) { + return; + } + else if (cg_addMarks.integer == 2) + { + trap_R_AddDecalToScene(markShader, origin, dir, orientation, red, green, blue, alpha, + alphaFade, radius, temporary); + 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, (const vec3_t *) 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 ) { + if (0) { + + 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 + { + float f = (float)t / MARK_FADE_TIME; + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0] * f; + mp->verts[j].modulate[1] = mp->color[1] * f; + mp->verts[j].modulate[2] = mp->color[2] * f; + } + } + } + else + { + for ( j = 0 ; j < mp->poly.numVerts ; j++ ) { + mp->verts[j].modulate[0] = mp->color[0]; + mp->verts[j].modulate[1] = mp->color[1]; + mp->verts[j].modulate[2] = mp->color[2]; + } + } + + 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 +#define MAX_PARTICLES 1024 + +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 (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 (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.0f); + cosR = width * cos(DEG2RAD(p->roll)) * sqrt(2.0f); + + 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 = 0;//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 ((const char **)&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 ((const char **)&str); + origin[i] = atof (token); + } + + for (i=0; i<3; i++) + { + token = COM_Parse ((const char **)&str); + origin2[i] = atof (token); + } + + token = COM_Parse ((const char **)&str); + numparticles = atoi (token); + + token = COM_Parse ((const char **)&str); + turb = atoi (token); + + token = COM_Parse ((const char **)&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 = 0;//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 = 0;//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 = 0;//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/code/cgame/cg_newDraw.c b/code/cgame/cg_newDraw.c new file mode 100644 index 0000000..1e2fe11 --- /dev/null +++ b/code/cgame/cg_newDraw.c @@ -0,0 +1,899 @@ +#include "cg_local.h" +#include "../ui/ui_shared.h" + +extern displayContextDef_t cgDC; + + +int CG_GetSelectedPlayer() { + if (cg_currentSelectedPlayer.integer < 0 || cg_currentSelectedPlayer.integer >= numSortedTeamPlayers) { + cg_currentSelectedPlayer.integer = 0; + } + return cg_currentSelectedPlayer.integer; +} + +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; +} + + +float CG_GetValue(int ownerDraw) { + centity_t *cent; + clientInfo_t *ci; + playerState_t *ps; + + 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[weaponData[cent->currentState.weapon].ammoIndex]; + } + 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; + case CG_PLAYER_FORCE_VALUE: + return ps->fd.forcePower; + break; + default: + break; + } + return -1; +} + +qboolean CG_OtherTeamHasFlag(void) { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_CTY) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + 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(void) { + if (cgs.gametype == GT_CTF || cgs.gametype == GT_CTY) { + int team = cg.snap->ps.persistant[PERS_TEAM]; + 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_CTF) { + if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { + return qtrue; + } + } + + 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_DUEL || cgs.gametype == GT_POWERDUEL ) { + 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; +} + + +const char *CG_GetKillerText(void) { + static const char *s = ""; + if ( cg.killerName[0] ) { + s = va("%s %s", CG_GetStringEdString("MP_INGAME", "KILLEDBY"), cg.killerName ); + } + return s; +} + + +const char *CG_GetGameStatusText(void) { + static const char *s = ""; + if (cgs.gametype == GT_POWERDUEL) + { + s = ""; + } + else if ( cgs.gametype < GT_TEAM) + { + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) + { + char sPlaceWith[256]; + trap_SP_GetStringTextString("MP_INGAME_PLACE_WITH", sPlaceWith, sizeof(sPlaceWith)); + + s = va("%s %s %i",CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), sPlaceWith, cg.snap->ps.persistant[PERS_SCORE] ); + } + } + else + { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va("%s %i", CG_GetStringEdString("MP_INGAME", "TIEDAT"), cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va("%s, %i / %i", CG_GetStringEdString("MP_INGAME", "RED_LEADS"), cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va("%s, %i / %i", CG_GetStringEdString("MP_INGAME", "BLUE_LEADS"), cg.teamScores[1], cg.teamScores[0] ); + } + } + return s; +} + +const char *CG_GameTypeString(void) { + if ( cgs.gametype == GT_FFA ) { + return "Free For All"; + } else if ( cgs.gametype == GT_HOLOCRON ) { + return "Holocron FFA"; + } else if ( cgs.gametype == GT_JEDIMASTER ) { + return "Jedi Master"; + } else if ( cgs.gametype == GT_TEAM ) { + return "Team FFA"; + } else if ( cgs.gametype == GT_SIEGE ) { + return "Siege"; + } else if ( cgs.gametype == GT_CTF ) { + return "Capture the Flag"; + } else if ( cgs.gametype == GT_CTY ) { + return "Capture the Ysalamiri"; + } + return ""; +} + +#include "../namespace_begin.h" +extern int MenuFontToHandle(int iMenuFont); +#include "../namespace_end.h" + +// maxX param is initially an X limit, but is also used as feedback. 0 = text was clipped to fit within, else maxX = next pos +// +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 iMenuFont) +{ + qboolean bIsTrailingPunctuation; + + // this is kinda dirty, but... + // + int iFontIndex = MenuFontToHandle(iMenuFont); + + //float fMax = *maxX; + int iPixelLen = trap_R_Font_StrLenPixels(text, iFontIndex, scale); + if (x + iPixelLen > *maxX) + { + // whole text won't fit, so we need to print just the amount that does... + // Ok, this is slow and tacky, but only called occasionally, and it works... + // + char sTemp[4096]={0}; // lazy assumption + const char *psText = text; + char *psOut = &sTemp[0]; + char *psOutLastGood = psOut; + unsigned int uiLetter; + + while (*psText && (x + trap_R_Font_StrLenPixels(sTemp, iFontIndex, scale)<=*maxX) + && psOut < &sTemp[sizeof(sTemp)-1] // sanity + ) + { + int iAdvanceCount; + psOutLastGood = psOut; + + uiLetter = trap_AnyLanguage_ReadCharFromString(psText, &iAdvanceCount, &bIsTrailingPunctuation); + psText += iAdvanceCount; + + if (uiLetter > 255) + { + *psOut++ = uiLetter>>8; + *psOut++ = uiLetter&0xFF; + } + else + { + *psOut++ = uiLetter&0xFF; + } + } + *psOutLastGood = '\0'; + + *maxX = 0; // feedback + CG_Text_Paint(x, y, scale, color, sTemp, adjust, limit, ITEM_TEXTSTYLE_NORMAL, iMenuFont); + } + else + { + // whole text fits fine, so print it all... + // + *maxX = x + iPixelLen; // feedback the next position, as the caller expects + CG_Text_Paint(x, y, scale, color, text, adjust, limit, ITEM_TEXTSTYLE_NORMAL, iMenuFont); + } +} + + + +#define PIC_WIDTH 12 + +extern const char *CG_GetLocationString(const char *loc); //cg_main.c +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_GetLocationString(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); + 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, FONT_MEDIUM); + + p = CG_GetLocationString(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, FONT_MEDIUM); + 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, FONT_MEDIUM); + 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, FONT_MEDIUM); + } + 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; + } + + 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, FONT_MEDIUM); + } + 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,int font) { + +//Ignore all this, at least for now. May put some stat stuff back in menu files later. +#if 0 + 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_FORCE_VALUE: + CG_DrawPlayerForceValue(&rect, scale, color, shader, textStyle); + return ; + 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; + 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_PLAYER_LOCATION: + CG_DrawPlayerLocation(&rect, scale, color, textStyle); + break; + case CG_TEAM_COLOR: + CG_DrawTeamColor(&rect, color); + break; + case CG_CTF_POWERUP: + 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: + 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; + } +#endif +} + +void CG_MouseEvent(int x, int y) { + int n; + + if ( (cg.predictedPlayerState.pm_type == PM_NORMAL || cg.predictedPlayerState.pm_type == PM_JETPACK || cg.predictedPlayerState.pm_type == PM_FLOAT || 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_JETPACK || 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 == A_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(void) { + Menus_OpenByName("voiceMenu"); + trap_Cvar_Set("cl_conXOffset", "72"); + cg.voiceTime = cg.time; +} + +void CG_RunMenuScript(char **args) { +} + +qboolean CG_DeferMenuScript (char **args) +{ + return qfalse; +} + +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/code/cgame/cg_players.c b/code/cgame/cg_players.c new file mode 100644 index 0000000..e3f970c --- /dev/null +++ b/code/cgame/cg_players.c @@ -0,0 +1,11259 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_players.c -- handle the media and animation for player entities +#include "cg_local.h" +#include "..\ghoul2\g2.h" +#include "bg_saga.h" + +extern vmCvar_t cg_thirdPersonAlpha; + +extern int cgSiegeTeam1PlShader; +extern int cgSiegeTeam2PlShader; + +extern void CG_AddRadarEnt(centity_t *cent); //cg_ents.c +extern void CG_AddBracketedEnt(centity_t *cent); //cg_ents.c +extern qboolean CG_InFighter( void ); +extern qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum ); + + +//for g2 surface routines +#define TURN_ON 0x00000000 +#define TURN_OFF 0x00000100 + +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = { + "*death1", + "*death2", + "*death3", + "*jump1", + "*pain25", + "*pain50", + "*pain75", + "*pain100", + "*falling1", + "*choke1", + "*choke2", + "*choke3", + "*gasp", + "*land1", + "*taunt", + NULL +}; + +//NPC sounds: +//Used as a supplement to the basic set for enemies and hazard team +// (keep numbers in ascending order in order for variant-capping to work) +const char *cg_customCombatSoundNames[MAX_CUSTOM_COMBAT_SOUNDS] = +{ + "*anger1", //Say when acquire an enemy when didn't have one before + "*anger2", + "*anger3", + "*victory1", //Say when killed an enemy + "*victory2", + "*victory3", + "*confuse1", //Say when confused + "*confuse2", + "*confuse3", + "*pushed1", //Say when force-pushed + "*pushed2", + "*pushed3", + "*choke1", + "*choke2", + "*choke3", + "*ffwarn", + "*ffturn", + NULL +}; + +//Used as a supplement to the basic set for stormtroopers +// (keep numbers in ascending order in order for variant-capping to work) +const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS] = +{ + "*chase1", + "*chase2", + "*chase3", + "*cover1", + "*cover2", + "*cover3", + "*cover4", + "*cover5", + "*detected1", + "*detected2", + "*detected3", + "*detected4", + "*detected5", + "*lost1", + "*outflank1", + "*outflank2", + "*escaping1", + "*escaping2", + "*escaping3", + "*giveup1", + "*giveup2", + "*giveup3", + "*giveup4", + "*look1", + "*look2", + "*sight1", + "*sight2", + "*sight3", + "*sound1", + "*sound2", + "*sound3", + "*suspicious1", + "*suspicious2", + "*suspicious3", + "*suspicious4", + "*suspicious5", + NULL +}; + +//Used as a supplement to the basic set for jedi +// (keep numbers in ascending order in order for variant-capping to work) +const char *cg_customJediSoundNames[MAX_CUSTOM_JEDI_SOUNDS] = +{ + "*combat1", + "*combat2", + "*combat3", + "*jdetected1", + "*jdetected2", + "*jdetected3", + "*taunt1", + "*taunt2", + "*taunt3", + "*jchase1", + "*jchase2", + "*jchase3", + "*jlost1", + "*jlost2", + "*jlost3", + "*deflect1", + "*deflect2", + "*deflect3", + "*gloat1", + "*gloat2", + "*gloat3", + "*pushfail", + NULL +}; + +//Used for DUEL taunts +const char *cg_customDuelSoundNames[MAX_CUSTOM_DUEL_SOUNDS] = +{ + "*anger1", //Say when acquire an enemy when didn't have one before + "*anger2", + "*anger3", + "*victory1", //Say when killed an enemy + "*victory2", + "*victory3", + "*taunt1", + "*taunt2", + "*taunt3", + "*deflect1", + "*deflect2", + "*deflect3", + "*gloat1", + "*gloat2", + "*gloat3", + NULL +}; + +void CG_Disintegration(centity_t *cent, refEntity_t *ent); + +/* +================ +CG_CustomSound + +================ +*/ +sfxHandle_t CG_CustomSound( int clientNum, const char *soundName ) { + clientInfo_t *ci; + int i; + int numCSounds = 0; + int numCComSounds = 0; + int numCExSounds = 0; + int numCJediSounds = 0; + int numCSiegeSounds = 0; + int numCDuelSounds = 0; + char lSoundName[MAX_QPATH]; + + if ( soundName[0] != '*' ) { + return trap_S_RegisterSound( soundName ); + } + + COM_StripExtension(soundName, lSoundName); + + if ( clientNum < 0 ) + { + clientNum = 0; + } + + if (clientNum >= MAX_CLIENTS) + { + ci = cg_entities[clientNum].npcClient; + } + else + { + ci = &cgs.clientinfo[ clientNum ]; + } + + if (!ci) + { + return 0; + } + + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customSoundNames[i]) + { + numCSounds = i; + break; + } + } + + if (clientNum >= MAX_CLIENTS) + { //these are only for npc's + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customCombatSoundNames[i]) + { + numCComSounds = i; + break; + } + } + + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customExtraSoundNames[i]) + { + numCExSounds = i; + break; + } + } + + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customJediSoundNames[i]) + { + numCJediSounds = i; + break; + } + } + } + + if (cgs.gametype >= GT_TEAM || cg_buildScript.integer) + { //siege only + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!bg_customSiegeSoundNames[i]) + { + numCSiegeSounds = i; + break; + } + } + } + + if (cgs.gametype == GT_DUEL + || cgs.gametype == GT_POWERDUEL + || cg_buildScript.integer) + { //Duel only + for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) + { + if (!cg_customDuelSoundNames[i]) + { + numCDuelSounds = i; + break; + } + } + } + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) + { + if ( i < numCSounds && !strcmp( lSoundName, cg_customSoundNames[i] ) ) + { + return ci->sounds[i]; + } + else if ( (cgs.gametype >= GT_TEAM || cg_buildScript.integer) && i < numCSiegeSounds && !strcmp( lSoundName, bg_customSiegeSoundNames[i] ) ) + { //siege only + return ci->siegeSounds[i]; + } + else if ( (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL || cg_buildScript.integer) && i < numCDuelSounds && !strcmp( lSoundName, cg_customDuelSoundNames[i] ) ) + { //siege only + return ci->duelSounds[i]; + } + else if ( clientNum >= MAX_CLIENTS && i < numCComSounds && !strcmp( lSoundName, cg_customCombatSoundNames[i] ) ) + { //npc only + return ci->combatSounds[i]; + } + else if ( clientNum >= MAX_CLIENTS && i < numCExSounds && !strcmp( lSoundName, cg_customExtraSoundNames[i] ) ) + { //npc only + return ci->extraSounds[i]; + } + else if ( clientNum >= MAX_CLIENTS && i < numCJediSounds && !strcmp( lSoundName, cg_customJediSoundNames[i] ) ) + { //npc only + return ci->jediSounds[i]; + } + } + + //CG_Error( "Unknown custom sound: %s", lSoundName ); +#ifndef FINAL_BUILD + Com_Printf( "Unknown custom sound: %s", lSoundName ); +#endif + return 0; +} + +/* +============================================================================= + +CLIENT INFO + +============================================================================= +*/ +#define MAX_SURF_LIST_SIZE 1024 +qboolean CG_ParseSurfsFile( const char *modelName, const char *skinName, char *surfOff, char *surfOn ) +{ + const char *text_p; + int len; + const char *token; + const char *value; + char text[20000]; + char sfilename[MAX_QPATH]; + fileHandle_t f; + int i = 0; + + while (skinName && skinName[i]) + { + if (skinName[i] == '|') + { //this is a multi-part skin, said skins do not support .surf files + return qfalse; + } + + i++; + } + + + // Load and parse .surf file + Com_sprintf( sfilename, sizeof( sfilename ), "models/players/%s/model_%s.surf", modelName, skinName ); + + // load the file + len = trap_FS_FOpenFile( sfilename, &f, FS_READ ); + if ( len <= 0 ) + {//no file + return qfalse; + } + if ( len >= sizeof( text ) - 1 ) + { + Com_Printf( "File %s too long\n", sfilename ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + memset( (char *)surfOff, 0, sizeof(surfOff) ); + memset( (char *)surfOn, 0, sizeof(surfOn) ); + + // read information for surfOff and surfOn + while ( 1 ) + { + token = COM_ParseExt( &text_p, qtrue ); + if ( !token || !token[0] ) + { + break; + } + + // surfOff + if ( !Q_stricmp( token, "surfOff" ) ) + { + if ( COM_ParseString( &text_p, &value ) ) + { + continue; + } + if ( surfOff && surfOff[0] ) + { + Q_strcat( surfOff, MAX_SURF_LIST_SIZE, "," ); + Q_strcat( surfOff, MAX_SURF_LIST_SIZE, value ); + } + else + { + Q_strncpyz( surfOff, value, MAX_SURF_LIST_SIZE ); + } + continue; + } + + // surfOn + if ( !Q_stricmp( token, "surfOn" ) ) + { + if ( COM_ParseString( &text_p, &value ) ) + { + continue; + } + if ( surfOn && surfOn[0] ) + { + Q_strcat( surfOn, MAX_SURF_LIST_SIZE, ","); + Q_strcat( surfOn, MAX_SURF_LIST_SIZE, value ); + } + else + { + Q_strncpyz( surfOn, value, MAX_SURF_LIST_SIZE ); + } + continue; + } + } + return qtrue; +} + +/* +========================== +CG_RegisterClientModelname +========================== +*/ +#include "../namespace_begin.h" +qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName); +qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors ); +#include "../namespace_end.h" + +static qboolean CG_RegisterClientModelname( clientInfo_t *ci, const char *modelName, const char *skinName, const char *teamName, int clientNum ) { + int handle; + char afilename[MAX_QPATH]; + char /**GLAName,*/ *slash; + char GLAName[MAX_QPATH]; + vec3_t tempVec = {0,0,0}; + qboolean badModel = qfalse; + char surfOff[MAX_SURF_LIST_SIZE]; + char surfOn[MAX_SURF_LIST_SIZE]; + int checkSkin; + char *useSkinName; + +retryModel: + if (badModel) + { + if (modelName && modelName[0]) + { + Com_Printf("WARNING: Attempted to load an unsupported multiplayer model %s! (bad or missing bone, or missing animation sequence)\n", modelName); + } + + modelName = "kyle"; + skinName = "default"; + + badModel = qfalse; + } + + // First things first. If this is a ghoul2 model, then let's make sure we demolish this first. + if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + trap_G2API_CleanGhoul2Models(&(ci->ghoul2Model)); + } + + if (!BG_IsValidCharacterModel(modelName, skinName)) + { + modelName = "kyle"; + skinName = "default"; + } + + if ( cgs.gametype >= GT_TEAM && !cgs.jediVmerc && cgs.gametype != GT_SIEGE ) + { //We won't force colors for siege. + BG_ValidateSkinForTeam( ci->modelName, ci->skinName, ci->team, ci->colorOverride ); + skinName = ci->skinName; + } + else + { + ci->colorOverride[0] = ci->colorOverride[1] = ci->colorOverride[2] = 0.0f; + } + + if (strchr(skinName, '|')) + {//three part skin + useSkinName = va("models/players/%s/|%s", modelName, skinName); + } + else + { + useSkinName = va("models/players/%s/model_%s.skin", modelName, skinName); + } + + checkSkin = trap_R_RegisterSkin(useSkinName); + + if (checkSkin) + { + ci->torsoSkin = checkSkin; + } + else + { //fallback to the default skin + ci->torsoSkin = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelName, skinName)); + } + Com_sprintf( afilename, sizeof( afilename ), "models/players/%s/model.glm", modelName ); + handle = trap_G2API_InitGhoul2Model(&ci->ghoul2Model, afilename, 0, ci->torsoSkin, 0, 0, 0); + + if (handle<0) + { + return qfalse; + } + + // The model is now loaded. + + trap_G2API_SetSkin(ci->ghoul2Model, 0, ci->torsoSkin, ci->torsoSkin); + + GLAName[0] = 0; + + trap_G2API_GetGLAName( ci->ghoul2Model, 0, GLAName); + if (GLAName[0] != 0) + { + if (!strstr(GLAName, "players/_humanoid/") /*&& + (!strstr(GLAName, "players/rockettrooper/") || cgs.gametype != GT_SIEGE)*/) //only allow rockettrooper in siege + { //Bad! + badModel = qtrue; + goto retryModel; + } + } + + if (!BGPAFtextLoaded) + { + if (GLAName[0] == 0/*GLAName == NULL*/) + { + badModel = qtrue; + goto retryModel; + } + Q_strncpyz( afilename, GLAName, sizeof( afilename )); + slash = Q_strrchr( afilename, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + } // Now afilename holds just the path to the animation.cfg + else + { // Didn't find any slashes, this is a raw filename right in base (whish isn't a good thing) + return qfalse; + } + + //rww - All player models must use humanoid, no matter what. + if (Q_stricmp(afilename, "models/players/_humanoid/animation.cfg") /*&& + Q_stricmp(afilename, "models/players/rockettrooper/animation.cfg")*/) + { + Com_Printf( "Model does not use supported animation config.\n"); + return qfalse; + } + else if (BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", bgHumanoidAnimations, qtrue) == -1) + { + Com_Printf( "Failed to load animation file models/players/_humanoid/animation.cfg\n" ); + return qfalse; + } + + BG_ParseAnimationEvtFile( "models/players/_humanoid/", 0, -1 ); //get the sounds for the humanoid anims +// if (cgs.gametype == GT_SIEGE) +// { +// BG_ParseAnimationEvtFile( "models/players/rockettrooper/", 1, 1 ); //parse rockettrooper too +// } + //For the time being, we're going to have all real players use the generic humanoid soundset and that's it. + //Only npc's will use model-specific soundsets. + + // BG_ParseAnimationSndFile(va("models/players/%s/", modelName), 0, -1); + } + else if (!bgAllEvents[0].eventsParsed) + { //make sure the player anim sounds are loaded even if the anims already are + BG_ParseAnimationEvtFile( "models/players/_humanoid/", 0, -1 ); +// if (cgs.gametype == GT_SIEGE) +// { +// BG_ParseAnimationEvtFile( "models/players/rockettrooper/", 1, 1 ); +// } + } + + if ( CG_ParseSurfsFile( modelName, skinName, surfOff, surfOn ) ) + {//turn on/off any surfs + const char *token; + const char *p; + + //Now turn on/off any surfaces + if ( surfOff && surfOff[0] ) + { + p = surfOff; + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + {//reached end of list + break; + } + //turn off this surf + trap_G2API_SetSurfaceOnOff( ci->ghoul2Model, token, 0x00000002/*G2SURFACEFLAG_OFF*/ ); + } + } + if ( surfOn && surfOn[0] ) + { + p = surfOn; + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + {//reached end of list + break; + } + //turn on this surf + trap_G2API_SetSurfaceOnOff( ci->ghoul2Model, token, 0 ); + } + } + } + + + ci->bolt_rhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand"); + + if (!trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, cg.time, -1, -1)) + { + badModel = qtrue; + } + + if (!trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "upper_lumbar", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, cg.time)) + { + badModel = qtrue; + } + + if (!trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "cranium", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, cg.time)) + { + badModel = qtrue; + } + + ci->bolt_lhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(ci->ghoul2Model, 0, "*chestg"); + + //claw bolts + trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand_cap_r_arm"); + trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand_cap_l_arm"); + + ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*head_top"); + if (ci->bolt_head == -1) + { + ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "ceyebrow"); + } + + ci->bolt_motion = trap_G2API_AddBolt(ci->ghoul2Model, 0, "Motion"); + + //We need a lower lumbar bolt for footsteps + ci->bolt_llumbar = trap_G2API_AddBolt(ci->ghoul2Model, 0, "lower_lumbar"); + + if (ci->bolt_rhand == -1 || ci->bolt_lhand == -1 || ci->bolt_head == -1 || ci->bolt_motion == -1 || ci->bolt_llumbar == -1) + { + badModel = qtrue; + } + + if (badModel) + { + goto retryModel; + } + + if (!Q_stricmp(modelName, "boba_fett")) + { //special case, turn off the jetpack surfs + trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_rjet", TURN_OFF); + trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_cjet", TURN_OFF); + trap_G2API_SetSurfaceOnOff(ci->ghoul2Model, "torso_ljet", TURN_OFF); + } + +// ent->s.radius = 90; + + if (clientNum != -1) + { + /* + if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2)) + { + trap_G2API_CleanGhoul2Models(&(cg_entities[clientNum].ghoul2)); + } + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2); + */ + + cg_entities[clientNum].ghoul2weapon = NULL; + } + + Q_strncpyz (ci->teamName, teamName, sizeof(ci->teamName)); + + // Model icon for drawing the portrait on screen + ci->modelIcon = trap_R_RegisterShaderNoMip ( va ( "models/players/%s/icon_%s", modelName, skinName ) ); + if (!ci->modelIcon) + { + int i = 0; + int j; + char iconName[1024]; + strcpy(iconName, "icon_"); + j = strlen(iconName); + while (skinName[i] && skinName[i] != '|' && j < 1024) + { + iconName[j] = skinName[i]; + j++; + i++; + } + iconName[j] = 0; + if (skinName[i] == '|') + { //looks like it actually may be a custom model skin, let's try getting the icon... + ci->modelIcon = trap_R_RegisterShaderNoMip ( va ( "models/players/%s/%s", modelName, iconName ) ); + } + } + 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_ColorFromInt +==================== +*/ +static void CG_ColorFromInt( int val, vec3_t color ) { + VectorClear( color ); + + 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; + } +} + +//load anim info +int CG_G2SkelForModel(void *g2) +{ + int animIndex = -1; + char GLAName[MAX_QPATH]; + char *slash; + + GLAName[0] = 0; + trap_G2API_GetGLAName(g2, 0, GLAName); + + slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + animIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + + return animIndex; +} + +//get the appropriate anim events file index +int CG_G2EvIndexForModel(void *g2, int animIndex) +{ + int evtIndex = -1; + char GLAName[MAX_QPATH]; + char *slash; + + if (animIndex == -1) + { + assert(!"shouldn't happen, bad animIndex"); + return -1; + } + + GLAName[0] = 0; + trap_G2API_GetGLAName(g2, 0, GLAName); + + slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + slash++; + *slash = 0; + + evtIndex = BG_ParseAnimationEvtFile(GLAName, animIndex, bgNumAnimEvents); + } + + return evtIndex; +} + +#define DEFAULT_FEMALE_SOUNDPATH "chars/mp_generic_female/misc"//"chars/tavion/misc" +#define DEFAULT_MALE_SOUNDPATH "chars/mp_generic_male/misc"//"chars/kyle/misc" +void CG_LoadCISounds(clientInfo_t *ci, qboolean modelloaded) +{ + fileHandle_t f; + qboolean isFemale = qfalse; + int i = 0; + int fLen = 0; + const char *dir; + char soundpath[MAX_QPATH]; + char soundName[1024]; + const char *s; + + dir = ci->modelName; + + if ( !ci->skinName || !Q_stricmp( "default", ci->skinName ) ) + {//try default sounds.cfg first + fLen = trap_FS_FOpenFile(va("models/players/%s/sounds.cfg", dir), &f, FS_READ); + if ( !f ) + {//no? Look for _default sounds.cfg + fLen = trap_FS_FOpenFile(va("models/players/%s/sounds_default.cfg", dir), &f, FS_READ); + } + } + else + {//use the .skin associated with this skin + fLen = trap_FS_FOpenFile(va("models/players/%s/sounds_%s.cfg", dir, ci->skinName), &f, FS_READ); + if ( !f ) + {//fall back to default sounds + fLen = trap_FS_FOpenFile(va("models/players/%s/sounds.cfg", dir), &f, FS_READ); + } + } + + soundpath[0] = 0; + + if (f) + { + trap_FS_Read(soundpath, fLen, f); + soundpath[fLen] = 0; + + i = fLen; + + while (i >= 0 && soundpath[i] != '\n') + { + if (soundpath[i] == 'f') + { + isFemale = qtrue; + soundpath[i] = 0; + } + + i--; + } + + i = 0; + + while (soundpath[i] && soundpath[i] != '\r' && soundpath[i] != '\n') + { + i++; + } + soundpath[i] = 0; + + trap_FS_FCloseFile(f); + } + + if (isFemale) + { + ci->gender = GENDER_FEMALE; + } + else + { + ci->gender = GENDER_MALE; + } + + trap_S_ShutUp(qtrue); + + for ( i = 0 ; i < MAX_CUSTOM_SOUNDS ; i++ ) + { + s = cg_customSoundNames[i]; + if ( !s ) { + break; + } + + Com_sprintf(soundName, sizeof(soundName), "%s", s+1); + COM_StripExtension(soundName, soundName); + //strip the extension because we might want .mp3's + + ci->sounds[i] = 0; + // if the model didn't load use the sounds of the default model + if (soundpath[0]) + { + ci->sounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", soundpath, soundName) ); + } + else + { + if (modelloaded) + { + ci->sounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) ); + } + } + + if (!ci->sounds[i]) + { //failed the load, try one out of the generic path + if (isFemale) + { + ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) ); + } + else + { + ci->sounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) ); + } + } + } + + if (cgs.gametype >= GT_TEAM || cg_buildScript.integer) + { //load the siege sounds then + for ( i = 0 ; i < MAX_CUSTOM_SIEGE_SOUNDS; i++ ) + { + s = bg_customSiegeSoundNames[i]; + if ( !s ) + { + break; + } + + Com_sprintf(soundName, sizeof(soundName), "%s", s+1); + COM_StripExtension(soundName, soundName); + //strip the extension because we might want .mp3's + + ci->siegeSounds[i] = 0; + // if the model didn't load use the sounds of the default model + if (soundpath[0]) + { + ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", soundpath, soundName) ); + } + else + { + if (modelloaded) + { + ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) ); + } + } + + if (!ci->siegeSounds[i]) + { //failed the load, try one out of the generic path + if (isFemale) + { + ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) ); + } + else + { + ci->siegeSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) ); + } + } + } + } + + if (cgs.gametype == GT_DUEL + ||cgs.gametype == GT_POWERDUEL + || cg_buildScript.integer) + { //load the Duel sounds then + for ( i = 0 ; i < MAX_CUSTOM_DUEL_SOUNDS; i++ ) + { + s = cg_customDuelSoundNames[i]; + if ( !s ) + { + break; + } + + Com_sprintf(soundName, sizeof(soundName), "%s", s+1); + COM_StripExtension(soundName, soundName); + //strip the extension because we might want .mp3's + + ci->duelSounds[i] = 0; + // if the model didn't load use the sounds of the default model + if (soundpath[0]) + { + ci->duelSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", soundpath, soundName) ); + } + else + { + if (modelloaded) + { + ci->duelSounds[i] = trap_S_RegisterSound( va("sound/chars/%s/misc/%s", dir, soundName) ); + } + } + + if (!ci->duelSounds[i]) + { //failed the load, try one out of the generic path + if (isFemale) + { + ci->duelSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_FEMALE_SOUNDPATH, soundName) ); + } + else + { + ci->duelSounds[i] = trap_S_RegisterSound( va("sound/%s/%s", DEFAULT_MALE_SOUNDPATH, soundName) ); + } + } + } + } + + trap_S_ShutUp(qfalse); +} + +/* +=================== +CG_LoadClientInfo + +Load it now, taking the disk hits. +This will usually be deferred to a safe time +=================== +*/ +void CG_LoadClientInfo( clientInfo_t *ci ) { + qboolean modelloaded; + int clientNum; + int i; + char teamname[MAX_QPATH]; + + clientNum = ci - cgs.clientinfo; + + if (clientNum < 0 || clientNum >= MAX_CLIENTS) + { + clientNum = -1; + } + + ci->deferred = qfalse; + + /* + if (ci->team == TEAM_SPECTATOR) + { + // 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] ); + } + } + + if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + } + + return; + } + */ + + teamname[0] = 0; + if( cgs.gametype >= GT_TEAM) { + if( ci->team == TEAM_BLUE ) { + Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME/*cg_blueTeamName.string*/, sizeof(teamname) ); + } else { + Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME/*cg_redTeamName.string*/, sizeof(teamname) ); + } + } + if( teamname[0] ) { + strcat( teamname, "/" ); + } + modelloaded = qtrue; + if (cgs.gametype == GT_SIEGE && + (ci->team == TEAM_SPECTATOR || ci->siegeIndex == -1)) + { //yeah.. kind of a hack I guess. Don't care until they are actually ingame with a valid class. + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", teamname, -1 ) ) + { + CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); + } + } + else + { + if ( !CG_RegisterClientModelname( ci, ci->modelName, ci->skinName, teamname, clientNum ) ) { + //CG_Error( "CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname ); + //rww - DO NOT error out here! Someone could just type in a nonsense model name and crash everyone's client. + //Give it a chance to load default model for this client instead. + + // 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_MODEL, ci->skinName, teamname, -1 ) ) { + CG_Error( "DEFAULT_MODEL / skin (%s/%s) failed to register", DEFAULT_MODEL, ci->skinName ); + } + } else { + if ( !CG_RegisterClientModelname( ci, DEFAULT_MODEL, "default", teamname, -1 ) ) { + CG_Error( "DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL ); + } + } + modelloaded = qfalse; + } + } + + if (clientNum != -1) + { + trap_G2API_ClearAttachedInstance(clientNum); + } + + if (clientNum != -1 && ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2)) + { + trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2); + } + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2); + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cg_entities[clientNum].ghoul2, clientNum, qfalse); + + + if (trap_G2API_AddBolt(cg_entities[clientNum].ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cg_entities[clientNum].noFace = qtrue; + } + + cg_entities[clientNum].localAnimIndex = CG_G2SkelForModel(cg_entities[clientNum].ghoul2); + cg_entities[clientNum].eventAnimIndex = CG_G2EvIndexForModel(cg_entities[clientNum].ghoul2, cg_entities[clientNum].localAnimIndex); + } + + 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 + if (cgs.gametype == GT_SIEGE && + (ci->team == TEAM_SPECTATOR || ci->siegeIndex == -1)) + { //don't need to load sounds + } + else + { + CG_LoadCISounds(ci, modelloaded); + } + + 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] ); + } + } +} + + +//Take care of initializing all the ghoul2 saber stuff based on clientinfo data. -rww +static void CG_InitG2SaberData(int saberNum, clientInfo_t *ci) +{ + trap_G2API_InitGhoul2Model(&ci->ghoul2Weapons[saberNum], ci->saber[saberNum].model, 0, ci->saber[saberNum].skin, 0, 0, 0); + + if (ci->ghoul2Weapons[saberNum]) + { + int k = 0; + int tagBolt; + char *tagName; + + if (ci->saber[saberNum].skin) + { + trap_G2API_SetSkin(ci->ghoul2Weapons[saberNum], 0, ci->saber[saberNum].skin, ci->saber[saberNum].skin); + } + + if (ci->saber[saberNum].saberFlags & SFL_BOLT_TO_WRIST) + { + trap_G2API_SetBoltInfo(ci->ghoul2Weapons[saberNum], 0, 3+saberNum); + } + else + { + trap_G2API_SetBoltInfo(ci->ghoul2Weapons[saberNum], 0, saberNum); + } + + while (k < ci->saber[saberNum].numBlades) + { + tagName = va("*blade%i", k+1); + tagBolt = trap_G2API_AddBolt(ci->ghoul2Weapons[saberNum], 0, tagName); + + if (tagBolt == -1) + { + if (k == 0) + { //guess this is an 0ldsk3wl saber + tagBolt = trap_G2API_AddBolt(ci->ghoul2Weapons[saberNum], 0, "*flash"); + + if (tagBolt == -1) + { + assert(0); + } + break; + } + + if (tagBolt == -1) + { + assert(0); + break; + } + } + + k++; + } + } +} + + +/* +====================== +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; + + //to->ghoul2Model = from->ghoul2Model; + //rww - Trying to use the same ghoul2 pointer for two seperate clients == DISASTER + assert(to->ghoul2Model != from->ghoul2Model); + + if (to->ghoul2Model && trap_G2_HaveWeGhoul2Models(to->ghoul2Model)) + { + trap_G2API_CleanGhoul2Models(&to->ghoul2Model); + } + if (from->ghoul2Model && trap_G2_HaveWeGhoul2Models(from->ghoul2Model)) + { + trap_G2API_DuplicateGhoul2Instance(from->ghoul2Model, &to->ghoul2Model); + } + + //Don't do this, I guess. Just leave the saber info in the original, so it will be + //properly initialized. + /* + strcpy(to->saberName, from->saberName); + strcpy(to->saber2Name, from->saber2Name); + + while (i < MAX_SABERS) + { + if (to->ghoul2Weapons[i] && trap_G2_HaveWeGhoul2Models(to->ghoul2Weapons[i])) + { + trap_G2API_CleanGhoul2Models(&to->ghoul2Weapons[i]); + } + + WP_SetSaber(to->saber, 0, to->saberName); + WP_SetSaber(to->saber, 1, to->saber2Name); + + j = 0; + + while (j < MAX_SABERS) + { + if (to->saber[j].model[0]) + { + CG_InitG2SaberData(j, to); + } + j++; + } + i++; + } + */ + + to->bolt_head = from->bolt_head; + to->bolt_lhand = from->bolt_lhand; + to->bolt_rhand = from->bolt_rhand; + to->bolt_motion = from->bolt_motion; + to->bolt_llumbar = from->bolt_llumbar; + + to->siegeIndex = from->siegeIndex; + + memcpy( to->sounds, from->sounds, sizeof( to->sounds ) ); + memcpy( to->siegeSounds, from->siegeSounds, sizeof( to->siegeSounds ) ); + memcpy( to->duelSounds, from->duelSounds, sizeof( to->duelSounds ) ); +} + +/* +====================== +CG_ScanForExistingClientInfo +====================== +*/ +static qboolean CG_ScanForExistingClientInfo( clientInfo_t *ci, int clientNum ) { + 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->saberName, match->saberName) + && !Q_stricmp( ci->saber2Name, match->saber2Name) +// && !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) + && ci->siegeIndex == match->siegeIndex + && match->ghoul2Model + && match->bolt_head) //if the bolts haven't been initialized, this "match" is useless to us + { + // this clientinfo is identical, so use it's handles + + ci->deferred = qfalse; + + //rww - Filthy hack. If this is actually the info already belonging to us, just reassign the pointer. + //Switching instances when not necessary produces small animation glitches. + //Actually, before, were we even freeing the instance attached to the old clientinfo before copying + //this new clientinfo over it? Could be a nasty leak possibility. (though this should remedy it in theory) + if (clientNum == i) + { + if (match->ghoul2Model && trap_G2_HaveWeGhoul2Models(match->ghoul2Model)) + { //The match has a valid instance (if it didn't, we'd probably already be fudged (^_^) at this state) + if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { //First kill the copy we have if we have one. (but it should be null) + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + } + + VectorCopy( match->headOffset, ci->headOffset ); +// ci->footsteps = match->footsteps; + ci->gender = match->gender; + + ci->legsModel = match->legsModel; + ci->legsSkin = match->legsSkin; + ci->torsoModel = match->torsoModel; + ci->torsoSkin = match->torsoSkin; + ci->modelIcon = match->modelIcon; + + ci->newAnims = match->newAnims; + + ci->bolt_head = match->bolt_head; + ci->bolt_lhand = match->bolt_lhand; + ci->bolt_rhand = match->bolt_rhand; + ci->bolt_motion = match->bolt_motion; + ci->bolt_llumbar = match->bolt_llumbar; + ci->siegeIndex = match->siegeIndex; + + memcpy( ci->sounds, match->sounds, sizeof( ci->sounds ) ); + memcpy( ci->siegeSounds, match->siegeSounds, sizeof( ci->siegeSounds ) ); + memcpy( ci->duelSounds, match->duelSounds, sizeof( ci->duelSounds ) ); + + //We can share this pointer, because it already belongs to this client. + //The pointer itself and the ghoul2 instance is never actually changed, just passed between + //clientinfo structures. + ci->ghoul2Model = match->ghoul2Model; + + //Don't need to do this I guess, whenever this function is called the saber stuff should + //already be taken care of in the new info. + /* + while (k < MAX_SABERS) + { + if (match->ghoul2Weapons[k] && match->ghoul2Weapons[k] != ci->ghoul2Weapons[k]) + { + if (ci->ghoul2Weapons[k]) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[k]); + } + ci->ghoul2Weapons[k] = match->ghoul2Weapons[k]; + } + k++; + } + */ + } + } + else + { + 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 && ci->team != TEAM_SPECTATOR) ) { + continue; + } + + /* + if (Q_stricmp(ci->saberName, match->saberName) || + Q_stricmp(ci->saber2Name, match->saber2Name)) + { + 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 ( ci->team != TEAM_SPECTATOR && + (Q_stricmp( ci->skinName, match->skinName ) || + (cgs.gametype >= GT_TEAM && ci->team != match->team)) ) { + continue; + } + + /* + if (Q_stricmp(ci->saberName, match->saberName) || + Q_stricmp(ci->saber2Name, match->saber2Name)) + { + 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; + } + + /* + if (Q_stricmp(ci->saberName, match->saberName) || + Q_stricmp(ci->saber2Name, match->saber2Name)) + { + continue; + } + */ + + if (match->deferred) + { //no deferring off of deferred info. Because I said so. + continue; + } + + ci->deferred = qtrue; + CG_CopyClientInfoModel( match, ci ); + return; + } + + // we should never get here... + //CG_Printf( "CG_SetDeferredClientInfo: no valid clients!\n" ); + //Actually it is possible now because of the unique sabers. + + CG_LoadClientInfo( ci ); +} + +/* +====================== +CG_NewClientInfo +====================== +*/ +#include "../namespace_begin.h" +void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ); +#include "../namespace_end.h" + +void CG_NewClientInfo( int clientNum, qboolean entitiesInitialized ) { + clientInfo_t *ci; + clientInfo_t newInfo; + const char *configstring; + const char *v; + char *slash; + void *oldGhoul2; + void *oldG2Weapons[MAX_SABERS]; + int i = 0; + int k = 0; + qboolean saberUpdate[MAX_SABERS]; + + ci = &cgs.clientinfo[clientNum]; + + oldGhoul2 = ci->ghoul2Model; + + while (k < MAX_SABERS) + { + oldG2Weapons[k] = ci->ghoul2Weapons[k]; + k++; + } + + configstring = CG_ConfigString( clientNum + CS_PLAYERS ); + if ( !configstring[0] ) { + if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { //clean this stuff up first + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + } + k = 0; + while (k < MAX_SABERS) + { + if (ci->ghoul2Weapons[k] && trap_G2_HaveWeGhoul2Models(ci->ghoul2Weapons[k])) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[k]); + } + k++; + } + + memset( ci, 0, sizeof( *ci ) ); + return; // player just left + } + + // 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 ); + + newInfo.icolor1 = atoi(v); + + v = Info_ValueForKey( configstring, "c2" ); + CG_ColorFromString( v, newInfo.color2 ); + + newInfo.icolor2 = atoi(v); + + // bot skill + v = Info_ValueForKey( configstring, "skill" ); + newInfo.botSkill = atoi( v ); + + // 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 ); + +//copy team info out to menu + if ( clientNum == cg.clientNum) //this is me + { + trap_Cvar_Set("ui_team", 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; + + 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; + } + } + + if (cgs.gametype == GT_SIEGE) + { //entries only sent in siege mode + //siege desired team + v = Info_ValueForKey( configstring, "sdt" ); + if (v && v[0]) + { + newInfo.siegeDesiredTeam = atoi(v); + } + else + { + newInfo.siegeDesiredTeam = 0; + } + + //siege classname + v = Info_ValueForKey( configstring, "siegeclass" ); + newInfo.siegeIndex = -1; + + if (v) + { + siegeClass_t *siegeClass = BG_SiegeFindClassByName(v); + + if (siegeClass) + { //See if this class forces a model, if so, then use it. Same for skin. + newInfo.siegeIndex = BG_SiegeFindClassIndexByName(v); + + if (siegeClass->forcedModel[0]) + { + Q_strncpyz( newInfo.modelName, siegeClass->forcedModel, sizeof( newInfo.modelName ) ); + } + + if (siegeClass->forcedSkin[0]) + { + Q_strncpyz( newInfo.skinName, siegeClass->forcedSkin, sizeof( newInfo.skinName ) ); + } + + if (siegeClass->hasForcedSaberColor) + { + newInfo.icolor1 = siegeClass->forcedSaberColor; + + CG_ColorFromInt( newInfo.icolor1, newInfo.color1 ); + } + if (siegeClass->hasForcedSaber2Color) + { + newInfo.icolor2 = siegeClass->forcedSaber2Color; + + CG_ColorFromInt( newInfo.icolor2, newInfo.color2 ); + } + } + } + } + + saberUpdate[0] = qfalse; + saberUpdate[1] = qfalse; + + //saber being used + v = Info_ValueForKey( configstring, "st" ); + + if (v && Q_stricmp(v, ci->saberName)) + { + Q_strncpyz( newInfo.saberName, v, 64 ); + WP_SetSaber(clientNum, newInfo.saber, 0, newInfo.saberName); + saberUpdate[0] = qtrue; + } + else + { + Q_strncpyz( newInfo.saberName, ci->saberName, 64 ); + memcpy(&newInfo.saber[0], &ci->saber[0], sizeof(newInfo.saber[0])); + newInfo.ghoul2Weapons[0] = ci->ghoul2Weapons[0]; + } + + v = Info_ValueForKey( configstring, "st2" ); + + if (v && Q_stricmp(v, ci->saber2Name)) + { + Q_strncpyz( newInfo.saber2Name, v, 64 ); + WP_SetSaber(clientNum, newInfo.saber, 1, newInfo.saber2Name); + saberUpdate[1] = qtrue; + } + else + { + Q_strncpyz( newInfo.saber2Name, ci->saber2Name, 64 ); + memcpy(&newInfo.saber[1], &ci->saber[1], sizeof(newInfo.saber[1])); + newInfo.ghoul2Weapons[1] = ci->ghoul2Weapons[1]; + } + + if (saberUpdate[0] || saberUpdate[1]) + { + int j = 0; + + while (j < MAX_SABERS) + { + if (saberUpdate[j]) + { + if (newInfo.saber[j].model[0]) + { + if (oldG2Weapons[j]) + { //free the old instance(s) + trap_G2API_CleanGhoul2Models(&oldG2Weapons[j]); + oldG2Weapons[j] = 0; + } + + CG_InitG2SaberData(j, &newInfo); + } + else + { + if (oldG2Weapons[j]) + { //free the old instance(s) + trap_G2API_CleanGhoul2Models(&oldG2Weapons[j]); + oldG2Weapons[j] = 0; + } + } + + cg_entities[clientNum].weapon = 0; + cg_entities[clientNum].ghoul2weapon = NULL; //force a refresh + } + j++; + } + } + + //Check for any sabers that didn't get set again, if they didn't, then reassign the pointers for the new ci + k = 0; + while (k < MAX_SABERS) + { + if (oldG2Weapons[k]) + { + newInfo.ghoul2Weapons[k] = oldG2Weapons[k]; + } + k++; + } + + //duel team + v = Info_ValueForKey( configstring, "dt" ); + + if (v) + { + newInfo.duelTeam = atoi(v); + } + else + { + newInfo.duelTeam = 0; + } + + // force powers + v = Info_ValueForKey( configstring, "forcepowers" ); + Q_strncpyz( newInfo.forcePowers, v, sizeof( newInfo.forcePowers ) ); + + if (cgs.gametype >= GT_TEAM && !cgs.jediVmerc && cgs.gametype != GT_SIEGE ) + { //We won't force colors for siege. + BG_ValidateSkinForTeam( newInfo.modelName, newInfo.skinName, newInfo.team, newInfo.colorOverride ); + } + else + { + newInfo.colorOverride[0] = newInfo.colorOverride[1] = newInfo.colorOverride[2] = 0.0f; + } + + // scan for an existing clientinfo that matches this modelname + // so we can avoid loading checks if possible + if ( !CG_ScanForExistingClientInfo( &newInfo, clientNum ) ) { + // if we are defering loads, just have it pick the first valid + if (cg.snap && cg.snap->ps.clientNum == clientNum) + { //rww - don't defer your own client info ever + CG_LoadClientInfo( &newInfo ); + } + else if ( cg_deferPlayers.integer && cgs.gametype != GT_SIEGE && !cg_buildScript.integer && !cg.loading ) { + // keep whatever they had if it won't violate team skins + CG_SetDeferredClientInfo( &newInfo ); + } else { + CG_LoadClientInfo( &newInfo ); + } + } + + // replace whatever was there with the new one + newInfo.infoValid = qtrue; + if (ci->ghoul2Model && + ci->ghoul2Model != newInfo.ghoul2Model && + trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { //We must kill this instance before we remove our only pointer to it from the cgame. + //Otherwise we will end up with extra instances all over the place, I think. + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + } + *ci = newInfo; + + //force a weapon change anyway, for all clients being rendered to the current client + while (i < MAX_CLIENTS) + { + cg_entities[i].ghoul2weapon = NULL; + i++; + } + + if (clientNum != -1) + { //don't want it using an invalid pointer to share + trap_G2API_ClearAttachedInstance(clientNum); + } + + // Check if the ghoul2 model changed in any way. This is safer than assuming we have a legal cent shile loading info. + if (entitiesInitialized && ci->ghoul2Model && (oldGhoul2 != ci->ghoul2Model)) + { // Copy the new ghoul2 model to the centity. + animation_t *anim; + centity_t *cent = &cg_entities[clientNum]; + + anim = &bgHumanoidAnimations[ (cg_entities[clientNum].currentState.legsAnim) ]; + + if (anim) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE; + int firstFrame = anim->firstFrame; + int setFrame = -1; + float animSpeed = 50.0f / anim->frameLerp; + + if (anim->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cent->pe.legs.frame >= anim->firstFrame && cent->pe.legs.frame <= (anim->firstFrame + anim->numFrames)) + { + setFrame = cent->pe.legs.frame; + } + + //rww - Set the animation again because it just got reset due to the model change + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150); + + cg_entities[clientNum].currentState.legsAnim = 0; + } + + anim = &bgHumanoidAnimations[ (cg_entities[clientNum].currentState.torsoAnim) ]; + + if (anim) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE; + int firstFrame = anim->firstFrame; + int setFrame = -1; + float animSpeed = 50.0f / anim->frameLerp; + + if (anim->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cent->pe.torso.frame >= anim->firstFrame && cent->pe.torso.frame <= (anim->firstFrame + anim->numFrames)) + { + setFrame = cent->pe.torso.frame; + } + + //rww - Set the animation again because it just got reset due to the model change + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "lower_lumbar", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150); + + cg_entities[clientNum].currentState.torsoAnim = 0; + } + + if (cg_entities[clientNum].ghoul2 && trap_G2_HaveWeGhoul2Models(cg_entities[clientNum].ghoul2)) + { + trap_G2API_CleanGhoul2Models(&cg_entities[clientNum].ghoul2); + } + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, &cg_entities[clientNum].ghoul2); + + if (clientNum != -1) + { + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cg_entities[clientNum].ghoul2, clientNum, qfalse); + } + + if (trap_G2API_AddBolt(cg_entities[clientNum].ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cg_entities[clientNum].noFace = qtrue; + } + + cg_entities[clientNum].localAnimIndex = CG_G2SkelForModel(cg_entities[clientNum].ghoul2); + cg_entities[clientNum].eventAnimIndex = CG_G2EvIndexForModel(cg_entities[clientNum].ghoul2, cg_entities[clientNum].localAnimIndex); + + if (cg_entities[clientNum].currentState.number != cg.predictedPlayerState.clientNum && + cg_entities[clientNum].currentState.weapon == WP_SABER) + { + cg_entities[clientNum].weapon = cg_entities[clientNum].currentState.weapon; + if (cg_entities[clientNum].ghoul2 && ci->ghoul2Model) + { + CG_CopyG2WeaponInstance(&cg_entities[clientNum], cg_entities[clientNum].currentState.weapon, cg_entities[clientNum].ghoul2); + cg_entities[clientNum].ghoul2weapon = CG_G2WeaponInstance(&cg_entities[clientNum], cg_entities[clientNum].currentState.weapon); + } + if (!cg_entities[clientNum].currentState.saberHolstered) + { //if not holstered set length and desired length for both blades to full right now. + int j; + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + i = 0; + while (i < MAX_SABERS) + { + j = 0; + while (j < ci->saber[i].numBlades) + { + ci->saber[i].blade[j].length = ci->saber[i].blade[j].lengthMax; + j++; + } + i++; + } + } + } + } +} + + +qboolean cgQueueLoad = qfalse; +/* +====================== +CG_ActualLoadDeferredPlayers + +Called at the beginning of CG_Player if cgQueueLoad is set. +====================== +*/ +void CG_ActualLoadDeferredPlayers( 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 ) { + CG_LoadClientInfo( ci ); +// break; + } + } +} + +/* +====================== +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 ) { + cgQueueLoad = qtrue; +} + +/* +============================================================================= + +PLAYER ANIMATION + +============================================================================= +*/ + +#define FOOTSTEP_DISTANCE 32 +static void _PlayerFootStep( const vec3_t origin, + const float orientation, + const float radius, + centity_t *const cent, footstepType_t footStepType ) +{ + vec3_t end, mins = {-7, -7, 0}, maxs = {7, 7, 2}; + trace_t trace; + footstep_t soundType = FOOTSTEP_TOTAL; + qboolean bMark = qfalse; + qhandle_t footMarkShader; + int effectID = -1; + //float alpha; + + // send a trace down from the player to the ground + VectorCopy( origin, end ); + end[2] -= FOOTSTEP_DISTANCE; + + trap_CM_BoxTrace( &trace, origin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + // no shadow if too high + if ( trace.fraction >= 1.0f ) + { + return; + } + + //check for foot-steppable surface flag + switch( trace.surfaceFlags & MATERIAL_MASK ) + { + case MATERIAL_MUD: + bMark = qtrue; + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_MUDRUN; + } else { + soundType = FOOTSTEP_MUDWALK; + } + effectID = cgs.effects.footstepMud; + break; + case MATERIAL_DIRT: + bMark = qtrue; + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_DIRTRUN; + } else { + soundType = FOOTSTEP_DIRTWALK; + } + effectID = cgs.effects.footstepSand; + break; + case MATERIAL_SAND: + bMark = qtrue; + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_SANDRUN; + } else { + soundType = FOOTSTEP_SANDWALK; + } + effectID = cgs.effects.footstepSand; + break; + case MATERIAL_SNOW: + bMark = qtrue; + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_SNOWRUN; + } else { + soundType = FOOTSTEP_SNOWWALK; + } + effectID = cgs.effects.footstepSnow; + break; + case MATERIAL_SHORTGRASS: + case MATERIAL_LONGGRASS: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_GRASSRUN; + } else { + soundType = FOOTSTEP_GRASSWALK; + } + break; + case MATERIAL_SOLIDMETAL: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_METALRUN; + } else { + soundType = FOOTSTEP_METALWALK; + } + break; + case MATERIAL_HOLLOWMETAL: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_PIPERUN; + } else { + soundType = FOOTSTEP_PIPEWALK; + } + break; + case MATERIAL_GRAVEL: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_GRAVELRUN; + } else { + soundType = FOOTSTEP_GRAVELWALK; + } + effectID = cgs.effects.footstepGravel; + break; + case MATERIAL_CARPET: + case MATERIAL_FABRIC: + case MATERIAL_CANVAS: + case MATERIAL_RUBBER: + case MATERIAL_PLASTIC: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_RUGRUN; + } else { + soundType = FOOTSTEP_RUGWALK; + } + break; + case MATERIAL_SOLIDWOOD: + case MATERIAL_HOLLOWWOOD: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_WOODRUN; + } else { + soundType = FOOTSTEP_WOODWALK; + } + break; + + default: + //fall through + case MATERIAL_GLASS: + case MATERIAL_WATER: + case MATERIAL_FLESH: + case MATERIAL_BPGLASS: + case MATERIAL_DRYLEAVES: + case MATERIAL_GREENLEAVES: + case MATERIAL_TILES: + case MATERIAL_PLASTER: + case MATERIAL_SHATTERGLASS: + case MATERIAL_ARMOR: + case MATERIAL_COMPUTER: + + case MATERIAL_CONCRETE: + case MATERIAL_ROCK: + case MATERIAL_ICE: + case MATERIAL_MARBLE: + if ( footStepType == FOOTSTEP_HEAVY_R || footStepType == FOOTSTEP_HEAVY_L) { + soundType = FOOTSTEP_STONERUN; + } else { + soundType = FOOTSTEP_STONEWALK; + } + break; + } + if (soundType < FOOTSTEP_TOTAL) + { + trap_S_StartSound( NULL, cent->currentState.clientNum, CHAN_BODY, cgs.media.footsteps[soundType][rand()&3] ); + } + + if ( cg_footsteps.integer < 4 ) + {//debugging - 4 always does footstep effect + if ( cg_footsteps.integer < 2 ) //1 for sounds, 2 for effects, 3 for marks + { + return; + } + } + + if ( effectID != -1 ) + { + trap_FX_PlayEffectID( effectID, trace.endpos, trace.plane.normal, -1, -1 ); + } + + if ( cg_footsteps.integer < 4 ) + {//debugging - 4 always does footstep effect + if ( !bMark || cg_footsteps.integer < 3 ) //1 for sounds, 2 for effects, 3 for marks + { + return; + } + } + + switch ( footStepType ) + { + case FOOTSTEP_HEAVY_R: + footMarkShader = cgs.media.fshrMarkShader; + break; + case FOOTSTEP_HEAVY_L: + footMarkShader = cgs.media.fshlMarkShader; + break; + case FOOTSTEP_R: + footMarkShader = cgs.media.fsrMarkShader; + break; + default: + case FOOTSTEP_L: + footMarkShader = cgs.media.fslMarkShader; + break; + } + + // fade the shadow out with height +// alpha = 1.0 - trace.fraction; + + // add the mark as a temporary, so it goes directly to the renderer + // without taking a spot in the cg_marks array + if (trace.plane.normal[0] || trace.plane.normal[1] || trace.plane.normal[2]) + { + CG_ImpactMark( footMarkShader, trace.endpos, trace.plane.normal, + orientation, 1,1,1, 1.0f, qfalse, radius, qfalse ); + } +} + +static void CG_PlayerFootsteps( centity_t *cent, footstepType_t footStepType ) +{ + if ( !cg_footsteps.integer ) + { + return; + } + + //FIXME: make this a feature of NPCs in the NPCs.cfg? Specify a footstep shader, if any? + if ( cent->currentState.NPC_class != CLASS_ATST + && cent->currentState.NPC_class != CLASS_CLAW + && cent->currentState.NPC_class != CLASS_FISH + && cent->currentState.NPC_class != CLASS_FLIER2 + && cent->currentState.NPC_class != CLASS_GLIDER + && cent->currentState.NPC_class != CLASS_INTERROGATOR + && cent->currentState.NPC_class != CLASS_MURJJ + && cent->currentState.NPC_class != CLASS_PROBE + && cent->currentState.NPC_class != CLASS_R2D2 + && cent->currentState.NPC_class != CLASS_R5D2 + && cent->currentState.NPC_class != CLASS_REMOTE + && cent->currentState.NPC_class != CLASS_SEEKER + && cent->currentState.NPC_class != CLASS_SENTRY + && cent->currentState.NPC_class != CLASS_SWAMP ) + { + mdxaBone_t boltMatrix; + vec3_t tempAngles, sideOrigin; + int footBolt = -1; + + tempAngles[PITCH] = 0; + tempAngles[YAW] = cent->pe.legs.yawAngle; + tempAngles[ROLL] = 0; + + switch ( footStepType ) + { + case FOOTSTEP_R: + case FOOTSTEP_HEAVY_R: + footBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_leg_foot");//cent->gent->footRBolt; + break; + case FOOTSTEP_L: + case FOOTSTEP_HEAVY_L: + default: + footBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_leg_foot");//cent->gent->footLBolt; + break; + } + + + //FIXME: get yaw orientation of the foot and use on decal + trap_G2API_GetBoltMatrix( cent->ghoul2, 0, footBolt, &boltMatrix, tempAngles, cent->lerpOrigin, + cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, sideOrigin ); + sideOrigin[2] += 15; //fudge up a bit for coplanar + _PlayerFootStep( sideOrigin, cent->pe.legs.yawAngle, 6, cent, footStepType ); + } +} + +void CG_PlayerAnimEventDo( centity_t *cent, animevent_t *animEvent ) +{ + soundChannel_t channel = CHAN_AUTO; + clientInfo_t *client = NULL; + qhandle_t swingSound = 0; + qhandle_t spinSound = 0; + + if ( !cent || !animEvent ) + { + return; + } + + switch ( animEvent->eventType ) + { + case AEV_SOUNDCHAN: + channel = (soundChannel_t)animEvent->eventData[AED_SOUNDCHANNEL]; + case AEV_SOUND: + { // are there variations on the sound? + const int holdSnd = animEvent->eventData[ AED_SOUNDINDEX_START+Q_irand( 0, animEvent->eventData[AED_SOUND_NUMRANDOMSNDS] ) ]; + if ( holdSnd > 0 ) + { + trap_S_StartSound( NULL, cent->currentState.number, channel, holdSnd ); + } + } + break; + case AEV_SABER_SWING: + if (cent->currentState.eType == ET_NPC) + { + client = cent->npcClient; + assert(client); + } + else + { + client = &cgs.clientinfo[cent->currentState.clientNum]; + } + if ( client && client->infoValid && client->saber[animEvent->eventData[AED_SABER_SWING_SABERNUM]].swingSound[0] ) + {//custom swing sound + swingSound = client->saber[0].swingSound[Q_irand(0,2)]; + } + else + { + int randomSwing = 1; + switch ( animEvent->eventData[AED_SABER_SWING_TYPE] ) + { + default: + case 0://SWING_FAST + randomSwing = Q_irand( 1, 3 ); + break; + case 1://SWING_MEDIUM + randomSwing = Q_irand( 4, 6 ); + break; + case 2://SWING_STRONG + randomSwing = Q_irand( 7, 9 ); + break; + } + swingSound = trap_S_RegisterSound(va("sound/weapons/saber/saberhup%i.wav", randomSwing)); + } + trap_S_StartSound(cent->currentState.pos.trBase, cent->currentState.number, CHAN_AUTO, swingSound ); + break; + case AEV_SABER_SPIN: + if (cent->currentState.eType == ET_NPC) + { + client = cent->npcClient; + assert(client); + } + else + { + client = &cgs.clientinfo[cent->currentState.clientNum]; + } + if ( client + && client->infoValid + && client->saber[AED_SABER_SPIN_SABERNUM].spinSound ) + {//use override + spinSound = client->saber[AED_SABER_SPIN_SABERNUM].spinSound; + } + else + { + switch ( animEvent->eventData[AED_SABER_SPIN_TYPE] ) + { + case 0://saberspinoff + spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspinoff.wav" ); + break; + case 1://saberspin + spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin.wav" ); + break; + case 2://saberspin1 + spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin1.wav" ); + break; + case 3://saberspin2 + spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin2.wav" ); + break; + case 4://saberspin3 + spinSound = trap_S_RegisterSound( "sound/weapons/saber/saberspin3.wav" ); + break; + default://random saberspin1-3 + spinSound = trap_S_RegisterSound( va( "sound/weapons/saber/saberspin%d.wav", Q_irand(1,3)) ); + break; + } + } + if ( spinSound ) + { + trap_S_StartSound( NULL, cent->currentState.clientNum, CHAN_AUTO, spinSound ); + } + break; + case AEV_FOOTSTEP: + CG_PlayerFootsteps( cent, (footstepType_t)animEvent->eventData[AED_FOOTSTEP_TYPE] ); + break; + case AEV_EFFECT: +#if 0 //SP method + //add bolt, play effect + if ( animEvent->stringData != NULL && cent && cent->gent && cent->gent->ghoul2.size() ) + {//have a bolt name we want to use + animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], animEvent->stringData ); + animEvent->stringData = NULL;//so we don't try to do this again + } + if ( animEvent->eventData[AED_BOLTINDEX] != -1 ) + {//have a bolt we want to play the effect on + G_PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->gent->playerModel, animEvent->eventData[AED_BOLTINDEX], cent->currentState.clientNum ); + } + else + {//play at origin? FIXME: maybe allow a fwd/rt/up offset? + theFxScheduler.PlayEffect( animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, qfalse ); + } +#else //my method + if (animEvent->stringData && animEvent->stringData[0] && cent && cent->ghoul2) + { + animEvent->eventData[AED_MODELINDEX] = 0; + if ( ( Q_stricmpn( "*blade", animEvent->stringData, 6 ) == 0 + || Q_stricmp( "*flash", animEvent->stringData ) == 0 ) ) + {//must be a weapon, try weapon 0? + animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 1, animEvent->stringData ); + if ( animEvent->eventData[AED_BOLTINDEX] != -1 ) + {//found it! + animEvent->eventData[AED_MODELINDEX] = 1; + } + else + {//hmm, just try on the player model, then? + animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 0, animEvent->stringData ); + } + } + else + { + animEvent->eventData[AED_BOLTINDEX] = trap_G2API_AddBolt( cent->ghoul2, 0, animEvent->stringData ); + } + animEvent->stringData[0] = 0; + } + if ( animEvent->eventData[AED_BOLTINDEX] != -1 ) + { + vec3_t lAngles; + vec3_t bPoint, bAngle; + mdxaBone_t matrix; + + VectorSet(lAngles, 0, cent->lerpAngles[YAW], 0); + + trap_G2API_GetBoltMatrix(cent->ghoul2, animEvent->eventData[AED_MODELINDEX], animEvent->eventData[AED_BOLTINDEX], &matrix, lAngles, + cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bPoint); + VectorSet(bAngle, 0, 1, 0); + + trap_FX_PlayEffectID(animEvent->eventData[AED_EFFECTINDEX], bPoint, bAngle, -1, -1); + } + else + { + vec3_t bAngle; + + VectorSet(bAngle, 0, 1, 0); + trap_FX_PlayEffectID(animEvent->eventData[AED_EFFECTINDEX], cent->lerpOrigin, bAngle, -1, -1); + } +#endif + break; + //Would have to keep track of this on server to for these, it's not worth it. + case AEV_FIRE: + case AEV_MOVE: + break; + /* + case AEV_FIRE: + //add fire event + if ( animEvent->eventData[AED_FIRE_ALT] ) + { + G_AddEvent( cent->gent, EV_ALT_FIRE, 0 ); + } + else + { + G_AddEvent( cent->gent, EV_FIRE_WEAPON, 0 ); + } + break; + case AEV_MOVE: + //make him jump + if ( cent && cent->gent && cent->gent->client ) + { + if ( cent->gent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//on something + vec3_t fwd, rt, up, angles = {0, cent->gent->client->ps.viewangles[YAW], 0}; + AngleVectors( angles, fwd, rt, up ); + //FIXME: set or add to velocity? + VectorScale( fwd, animEvent->eventData[AED_MOVE_FWD], cent->gent->client->ps.velocity ); + VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_RT], rt, cent->gent->client->ps.velocity ); + VectorMA( cent->gent->client->ps.velocity, animEvent->eventData[AED_MOVE_UP], up, cent->gent->client->ps.velocity ); + + if ( animEvent->eventData[AED_MOVE_UP] > 0 ) + {//a jump + cent->gent->client->ps.pm_flags |= PMF_JUMPING; + + G_AddEvent( cent->gent, EV_JUMP, 0 ); + //FIXME: if have force jump, do this? or specify sound in the event data? + //cent->gent->client->ps.forceJumpZStart = cent->gent->client->ps.origin[2];//so we don't take damage if we land at same height + //G_SoundOnEnt( cent->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + } + } + break; + */ + default: + return; + break; + } +} + +/* +void CG_PlayerAnimEvents( int animFileIndex, int eventFileIndex, qboolean torso, int oldFrame, int frame, const vec3_t org, int entNum ) + +play any keyframed sounds - only when start a new frame +This func is called once for legs and once for torso +*/ +void CG_PlayerAnimEvents( int animFileIndex, int eventFileIndex, qboolean torso, int oldFrame, int frame, int entNum ) +{ + int i; + int firstFrame = 0, lastFrame = 0; + qboolean doEvent = qfalse, inSameAnim = qfalse, loopAnim = qfalse, match = qfalse, animBackward = qfalse; + animevent_t *animEvents = NULL; + + if ( torso ) + { + animEvents = bgAllEvents[eventFileIndex].torsoAnimEvents; + } + else + { + animEvents = bgAllEvents[eventFileIndex].legsAnimEvents; + } + if ( fabs((float)(oldFrame-frame)) > 1 ) + {//given a range, see if keyFrame falls in that range + int oldAnim, anim; + if ( torso ) + { + /* + if ( cg_reliableAnimSounds.integer > 1 ) + {//more precise, slower + oldAnim = PM_TorsoAnimForFrame( &g_entities[entNum], oldFrame ); + anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame ); + } + else + */ + {//less precise, but faster + oldAnim = cg_entities[entNum].currentState.torsoAnim; + anim = cg_entities[entNum].nextState.torsoAnim; + } + } + else + { + /* + if ( cg_reliableAnimSounds.integer > 1 ) + {//more precise, slower + oldAnim = PM_LegsAnimForFrame( &g_entities[entNum], oldFrame ); + anim = PM_TorsoAnimForFrame( &g_entities[entNum], frame ); + } + else + */ + {//less precise, but faster + oldAnim = cg_entities[entNum].currentState.legsAnim; + anim = cg_entities[entNum].nextState.legsAnim; + } + } + if ( anim != oldAnim ) + {//not in same anim + inSameAnim = qfalse; + //FIXME: we *could* see if the oldFrame was *just about* to play the keyframed sound... + } + else + {//still in same anim, check for looping anim + animation_t *animation; + + inSameAnim = qtrue; + animation = &bgAllAnims[animFileIndex].anims[anim]; + animBackward = (animation->frameLerp<0); + if ( animation->loopFrames != -1 ) + {//a looping anim! + loopAnim = qtrue; + firstFrame = animation->firstFrame; + lastFrame = animation->firstFrame+animation->numFrames; + } + } + } + + // Check for anim sound + for (i=0;i 1 /*&& cg_reliableAnimSounds.integer*/ ) + {//given a range, see if keyFrame falls in that range + if ( inSameAnim ) + {//if changed anims altogether, sorry, the sound is lost + if ( fabs((float)(oldFrame-animEvents[i].keyFrame)) <= 3 + || fabs((float)(frame-animEvents[i].keyFrame)) <= 3 ) + {//must be at least close to the keyframe + if ( animBackward ) + {//animation plays backwards + if ( oldFrame > animEvents[i].keyFrame && frame < animEvents[i].keyFrame ) + {//old to new passed through keyframe + match = qtrue; + } + else if ( loopAnim ) + {//hmm, didn't pass through it linearally, see if we looped + if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame ) + {//keyframe is in this anim + if ( oldFrame > animEvents[i].keyFrame + && frame > oldFrame ) + {//old to new passed through keyframe + match = qtrue; + } + } + } + } + else + {//anim plays forwards + if ( oldFrame < animEvents[i].keyFrame && frame > animEvents[i].keyFrame ) + {//old to new passed through keyframe + match = qtrue; + } + else if ( loopAnim ) + {//hmm, didn't pass through it linearally, see if we looped + if ( animEvents[i].keyFrame >= firstFrame && animEvents[i].keyFrame < lastFrame ) + {//keyframe is in this anim + if ( oldFrame < animEvents[i].keyFrame + && frame < oldFrame ) + {//old to new passed through keyframe + match = qtrue; + } + } + } + } + } + } + } + if ( match ) + { + switch ( animEvents[i].eventType ) + { + case AEV_SOUND: + case AEV_SOUNDCHAN: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_SOUND_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_SOUND_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_SABER_SWING: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_SABER_SWING_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_SABER_SWING_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_SABER_SPIN: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_SABER_SPIN_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_SABER_SPIN_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_FOOTSTEP: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_FOOTSTEP_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_EFFECT: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_EFFECT_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_EFFECT_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_FIRE: + // Determine probability of playing sound + if (!animEvents[i].eventData[AED_FIRE_PROBABILITY]) // 100% + { + doEvent = qtrue; + } + else if (animEvents[i].eventData[AED_FIRE_PROBABILITY] > Q_irand(0, 99) ) + { + doEvent = qtrue; + } + break; + case AEV_MOVE: + doEvent = qtrue; + break; + default: + //doEvent = qfalse;//implicit + break; + } + // do event + if ( doEvent ) + { + CG_PlayerAnimEventDo( &cg_entities[entNum], &animEvents[i] ); + } + } + } +} + +void CG_TriggerAnimSounds( centity_t *cent ) +{ //this also sets the lerp frames, so I suggest you keep calling it regardless of if you want anim sounds. + int curFrame = 0; + float currentFrame = 0; + int sFileIndex; + + assert(cent->localAnimIndex >= 0); + + sFileIndex = cent->eventAnimIndex; + + if (trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg.time, ¤tFrame, cgs.gameModels, 0)) + { + // the above may have failed, not sure what to do about it, current frame will be zero in that case + curFrame = floor( currentFrame ); + } + if ( curFrame != cent->pe.legs.frame ) + { + CG_PlayerAnimEvents( cent->localAnimIndex, sFileIndex, qfalse, cent->pe.legs.frame, curFrame, cent->currentState.number ); + } + cent->pe.legs.oldFrame = cent->pe.torso.frame; + cent->pe.legs.frame = curFrame; + + if (cent->noLumbar) + { //probably a droid or something. + cent->pe.torso.oldFrame = cent->pe.legs.oldFrame; + cent->pe.torso.frame = cent->pe.legs.frame; + return; + } + + if (trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg.time, ¤tFrame, cgs.gameModels, 0)) + { + curFrame = floor( currentFrame ); + } + if ( curFrame != cent->pe.torso.frame ) + { + CG_PlayerAnimEvents( cent->localAnimIndex, sFileIndex, qtrue, cent->pe.torso.frame, curFrame, cent->currentState.number ); + } + cent->pe.torso.oldFrame = cent->pe.torso.frame; + cent->pe.torso.frame = curFrame; + cent->pe.torso.backlerp = 1.0f - (currentFrame - (float)curFrame); +} + + +static qboolean CG_FirstAnimFrame(lerpFrame_t *lf, qboolean torsoOnly, float speedScale); + +qboolean CG_InRoll( centity_t *cent ) +{ + switch ( (cent->currentState.legsAnim) ) + { + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + if ( cent->pe.legs.animationTime > cg.time ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean CG_InRollAnim( centity_t *cent ) +{ + switch ( (cent->currentState.legsAnim) ) + { + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + return qtrue; + } + return qfalse; +} + +/* +=============== +CG_SetLerpFrameAnimation +=============== +*/ +#include "../namespace_begin.h" +qboolean BG_SaberStanceAnim( int anim ); +qboolean PM_RunningAnim( int anim ); +#include "../namespace_end.h" +static void CG_SetLerpFrameAnimation( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float animSpeedMult, qboolean torsoOnly, qboolean flipState) { + animation_t *anim; + float animSpeed; + int flags=BONE_ANIM_OVERRIDE_FREEZE; + int oldAnim = -1; + int blendTime = 100; + float oldSpeed = lf->animationSpeed; + + if (cent->localAnimIndex > 0) + { //rockettroopers can't have broken arms, nor can anything else but humanoids + ci->brokenLimbs = cent->currentState.brokenLimbs; + } + + oldAnim = lf->animationNumber; + + lf->animationNumber = newAnimation; + + if ( newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS ) { + CG_Error( "Bad animation number: %i", newAnimation ); + } + + anim = &bgAllAnims[cent->localAnimIndex].anims[ newAnimation ]; + + lf->animation = anim; + lf->animationTime = lf->frameTime + abs(anim->frameLerp); + + if (cent->localAnimIndex > 1 && + anim->firstFrame == 0 && + anim->numFrames == 0) + { //We'll allow this for non-humanoids. + return; + } + + if ( cg_debugAnim.integer && (cg_debugAnim.integer < 0 || cg_debugAnim.integer == cent->currentState.clientNum) ) { + if (lf == ¢->pe.legs) + { + CG_Printf( "%d: %d TORSO Anim: %i, '%s'\n", cg.time, cent->currentState.clientNum, newAnimation, GetStringForID(animTable, newAnimation)); + } + else + { + CG_Printf( "%d: %d LEGS Anim: %i, '%s'\n", cg.time, cent->currentState.clientNum, newAnimation, GetStringForID(animTable, newAnimation)); + } + } + + if (cent->ghoul2) + { + qboolean resumeFrame = qfalse; + int beginFrame = -1; + int firstFrame; + int lastFrame; +#if 0 //disabled for now + float unused; +#endif + + animSpeed = 50.0f / anim->frameLerp; + if (lf->animation->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (animSpeed < 0) + { + lastFrame = anim->firstFrame; + firstFrame = anim->firstFrame + anim->numFrames; + } + else + { + firstFrame = anim->firstFrame; + lastFrame = anim->firstFrame + anim->numFrames; + } + + if (cg_animBlend.integer) + { + flags |= BONE_ANIM_BLEND; + } + + if (BG_InDeathAnim(newAnimation)) + { + flags &= ~BONE_ANIM_BLEND; + } + else if ( oldAnim != -1 && + BG_InDeathAnim(oldAnim)) + { + flags &= ~BONE_ANIM_BLEND; + } + + if (flags & BONE_ANIM_BLEND) + { + if (BG_FlippingAnim(newAnimation)) + { + blendTime = 200; + } + else if ( oldAnim != -1 && + (BG_FlippingAnim(oldAnim)) ) + { + blendTime = 200; + } + } + + animSpeed *= animSpeedMult; + + BG_SaberStartTransAnim(cent->currentState.number, cent->currentState.fireflag, cent->currentState.weapon, newAnimation, &animSpeed, cent->currentState.brokenLimbs); + + if (torsoOnly) + { + if (lf->animationTorsoSpeed != animSpeedMult && newAnimation == oldAnim && + flipState == lf->lastFlip) + { //same animation, but changing speed, so we will want to resume off the frame we're on. + resumeFrame = qtrue; + } + lf->animationTorsoSpeed = animSpeedMult; + } + else + { + if (lf->animationSpeed != animSpeedMult && newAnimation == oldAnim && + flipState == lf->lastFlip) + { //same animation, but changing speed, so we will want to resume off the frame we're on. + resumeFrame = qtrue; + } + lf->animationSpeed = animSpeedMult; + } + + //vehicles may have torso etc but we only want to animate the root bone + if ( cent->currentState.NPC_class == CLASS_VEHICLE ) + { + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", firstFrame, lastFrame, flags, animSpeed,cg.time, beginFrame, blendTime); + return; + } + + if (torsoOnly && !cent->noLumbar) + { //rww - The guesswork based on the lerp frame figures is usually BS, so I've resorted to a call to get the frame of the bone directly. + float GBAcFrame = 0; + if (resumeFrame) + { //we already checked, and this is the same anim, same flip state, but different speed, so we want to resume with the new speed off of the same frame. + trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg.time, &GBAcFrame, NULL, 0); + beginFrame = GBAcFrame; + } + + //even if resuming, also be sure to check if we are running the same frame on the legs. If so, we want to use their frame no matter what. + trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg.time, &GBAcFrame, NULL, 0); + + if ((cent->currentState.torsoAnim) == (cent->currentState.legsAnim) && GBAcFrame >= anim->firstFrame && GBAcFrame <= (anim->firstFrame + anim->numFrames)) + { //if the legs are already running this anim, pick up on the exact same frame to avoid the "wobbly spine" problem. + beginFrame = GBAcFrame; + } + + if (firstFrame > lastFrame || ci->torsoAnim == newAnimation) + { //don't resume on backwards playing animations.. I guess. + beginFrame = -1; + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", firstFrame, lastFrame, flags, animSpeed,cg.time, beginFrame, blendTime); + + // Update the torso frame with the new animation + cent->pe.torso.frame = firstFrame; + + if (ci) + { + ci->torsoAnim = newAnimation; + } + } + else + { + if (resumeFrame) + { //we already checked, and this is the same anim, same flip state, but different speed, so we want to resume with the new speed off of the same frame. + float GBAcFrame = 0; + trap_G2API_GetBoneFrame(cent->ghoul2, "model_root", cg.time, &GBAcFrame, NULL, 0); + beginFrame = GBAcFrame; + } + + if ((beginFrame < firstFrame) || (beginFrame > lastFrame)) + { //out of range, don't use it then. + beginFrame = -1; + } + + if (cent->currentState.torsoAnim == cent->currentState.legsAnim && + (ci->legsAnim != newAnimation || oldSpeed != animSpeed)) + { //alright, we are starting an anim on the legs, and that same anim is already playing on the toro, so pick up the frame. + float GBAcFrame = 0; + int oldBeginFrame = beginFrame; + + trap_G2API_GetBoneFrame(cent->ghoul2, "lower_lumbar", cg.time, &GBAcFrame, NULL, 0); + beginFrame = GBAcFrame; + if ((beginFrame < firstFrame) || (beginFrame > lastFrame)) + { //out of range, don't use it then. + beginFrame = oldBeginFrame; + } + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); + + if (ci) + { + ci->legsAnim = newAnimation; + } + } + + if (cent->localAnimIndex <= 1 && (cent->currentState.torsoAnim) == newAnimation && !cent->noLumbar) + { //make sure we're humanoid before we access the motion bone + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); + } + +#if 0 //disabled for now + if (cent->localAnimIndex <= 1 && cent->currentState.brokenLimbs && + (cent->currentState.brokenLimbs & (1 << BROKENLIMB_LARM))) + { //broken left arm + char *brokenBone = "lhumerus"; + animation_t *armAnim; + int armFirstFrame; + int armLastFrame; + int armFlags = 0; + float armAnimSpeed; + + armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_DEAD21 ]; + ci->brokenLimbs = cent->currentState.brokenLimbs; + + armFirstFrame = armAnim->firstFrame; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = BONE_ANIM_OVERRIDE_LOOP; + + if (cg_animBlend.integer) + { + armFlags |= BONE_ANIM_BLEND; + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg.time, -1, blendTime); + } + else if (cent->localAnimIndex <= 1 && cent->currentState.brokenLimbs && + (cent->currentState.brokenLimbs & (1 << BROKENLIMB_RARM))) + { //broken right arm + char *brokenBone = "rhumerus"; + char *supportBone = "lhumerus"; + + ci->brokenLimbs = cent->currentState.brokenLimbs; + + //Only put the arm in a broken pose if the anim is such that we + //want to allow it. + if ((//cent->currentState.weapon == WP_MELEE || + cent->currentState.weapon != WP_SABER || + BG_SaberStanceAnim(newAnimation) || + PM_RunningAnim(newAnimation)) && + cent->currentState.torsoAnim == newAnimation && + (!ci->saber[1].model[0] || cent->currentState.weapon != WP_SABER)) + { + int armFirstFrame; + int armLastFrame; + int armFlags = 0; + float armAnimSpeed; + animation_t *armAnim; + + if (cent->currentState.weapon == WP_MELEE || + cent->currentState.weapon == WP_SABER || + cent->currentState.weapon == WP_BRYAR_PISTOL) + { //don't affect this arm if holding a gun, just make the other arm support it + armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_ATTACK2 ]; + + //armFirstFrame = armAnim->firstFrame; + armFirstFrame = armAnim->firstFrame+armAnim->numFrames; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = BONE_ANIM_OVERRIDE_LOOP; + + /* + if (cg_animBlend.integer) + { + armFlags |= BONE_ANIM_BLEND; + } + */ + //No blend on the broken arm + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg.time, -1, 0); + } + else + { //we want to keep the broken bone updated for some cases + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); + } + + if (newAnimation != BOTH_MELEE1 && + newAnimation != BOTH_MELEE2 && + (newAnimation == TORSO_WEAPONREADY2 || newAnimation == BOTH_ATTACK2 || cent->currentState.weapon < WP_BRYAR_PISTOL)) + { + //Now set the left arm to "support" the right one + armAnim = &bgAllAnims[cent->localAnimIndex].anims[ BOTH_STAND2 ]; + armFirstFrame = armAnim->firstFrame; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = BONE_ANIM_OVERRIDE_LOOP; + + if (cg_animBlend.integer) + { + armFlags |= BONE_ANIM_BLEND; + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, cg.time, -1, 150); + } + else + { //we want to keep the support bone updated for some cases + trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); + } + } + else if (cent->currentState.torsoAnim == newAnimation) + { //otherwise, keep it set to the same as the torso + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, supportBone, firstFrame, lastFrame, flags, animSpeed, cg.time, beginFrame, blendTime); + } + } + else if (ci && + (ci->brokenLimbs || + trap_G2API_GetBoneFrame(cent->ghoul2, "lhumerus", cg.time, &unused, cgs.gameModels, 0) || + trap_G2API_GetBoneFrame(cent->ghoul2, "rhumerus", cg.time, &unused, cgs.gameModels, 0))) + //rwwFIXMEFIXME: brokenLimbs gets stomped sometimes, but it shouldn't. + { //remove the bone now so it can be set again + char *brokenBone = NULL; + int broken = 0; + + //Warning: Don't remove bones that you've added as bolts unless you want to invalidate your bolt index + //(well, in theory, I haven't actually run into the problem) + if (ci->brokenLimbs & (1<brokenLimbs & (1<ghoul2, 0, "lhumerus", 0, 1, 0, 0, cg.time, -1, 0); + if (!trap_G2API_RemoveBone(cent->ghoul2, "lhumerus", 0)) + { + assert(0); + Com_Printf("WARNING: Failed to remove lhumerus\n"); + } + } + + if (!brokenBone) + { + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lhumerus", 0, 1, 0, 0, cg.time, -1, 0); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "rhumerus", 0, 1, 0, 0, cg.time, -1, 0); + trap_G2API_RemoveBone(cent->ghoul2, "lhumerus", 0); + trap_G2API_RemoveBone(cent->ghoul2, "rhumerus", 0); + ci->brokenLimbs = 0; + } + else + { + //Set the flags and stuff to 0, so that the remove will succeed + trap_G2API_SetBoneAnim(cent->ghoul2, 0, brokenBone, 0, 1, 0, 0, cg.time, -1, 0); + + //Now remove it + if (!trap_G2API_RemoveBone(cent->ghoul2, brokenBone, 0)) + { + assert(0); + Com_Printf("WARNING: Failed to remove %s\n", brokenBone); + } + ci->brokenLimbs &= ~broken; + } + } +#endif + } +} + + +/* +=============== +CG_FirstAnimFrame + +Returns true if the lerpframe is on its first frame of animation. +Otherwise false. + +This is used to scale an animation into higher-speed without restarting +the animation before it completes at normal speed, in the case of a looping +animation (such as the leg running anim). +=============== +*/ +static qboolean CG_FirstAnimFrame(lerpFrame_t *lf, qboolean torsoOnly, float speedScale) +{ + if (torsoOnly) + { + if (lf->animationTorsoSpeed == speedScale) + { + return qfalse; + } + } + else + { + if (lf->animationSpeed == speedScale) + { + return qfalse; + } + } + + //I don't care where it is in the anim now, I am going to pick up from the same bone frame. +/* + if (lf->animation->numFrames < 2) + { + return qtrue; + } + + if (lf->animation->firstFrame == lf->frame) + { + return qtrue; + } +*/ + + return qtrue; +} + +/* +=============== +CG_RunLerpFrame + +Sets cg.snap, cg.oldFrame, and cg.backlerp +cg.time should be between oldFrameTime and frameTime after exit +=============== +*/ +static void CG_RunLerpFrame( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, qboolean flipState, int newAnimation, float speedScale, qboolean torsoOnly) +{ + // 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 (cent->currentState.forceFrame) + { + if (lf->lastForcedFrame != cent->currentState.forceFrame) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND; + float animSpeed = 1.0f; + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg.time, -1, 150); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg.time, -1, 150); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", cent->currentState.forceFrame, cent->currentState.forceFrame+1, flags, animSpeed, cg.time, -1, 150); + } + + lf->lastForcedFrame = cent->currentState.forceFrame; + + lf->animationNumber = 0; + } + else + { + lf->lastForcedFrame = -1; + + if ( (newAnimation != lf->animationNumber || cent->currentState.brokenLimbs != ci->brokenLimbs || lf->lastFlip != flipState || !lf->animation) || (CG_FirstAnimFrame(lf, torsoOnly, speedScale)) ) + { + CG_SetLerpFrameAnimation( cent, ci, lf, newAnimation, speedScale, torsoOnly, flipState); + } + } + + lf->lastFlip = flipState; + + 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( centity_t *cent, clientInfo_t *ci, lerpFrame_t *lf, int animationNumber, qboolean torsoOnly) { + lf->frameTime = lf->oldFrameTime = cg.time; + CG_SetLerpFrameAnimation( cent, ci, lf, animationNumber, 1, torsoOnly, qfalse ); + + if ( lf->animation->frameLerp < 0 ) + {//Plays backwards + lf->oldFrame = lf->frame = (lf->animation->firstFrame + lf->animation->numFrames); + } + else + { + lf->oldFrame = lf->frame = lf->animation->firstFrame; + } +} + + +/* +=============== +CG_PlayerAnimation +=============== +*/ +#include "../namespace_begin.h" +qboolean PM_WalkingAnim( int anim ); +#include "../namespace_end.h" + +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 (!PM_RunningAnim(cent->currentState.legsAnim) && + !PM_WalkingAnim(cent->currentState.legsAnim)) + { //if legs are not in a walking/running anim then just animate at standard speed + speedScale = 1.0f; + } + else if (cent->currentState.forcePowersActive & (1 << FP_RAGE)) + { + speedScale = 1.3f; + } + else if (cent->currentState.forcePowersActive & (1 << FP_SPEED)) + { + speedScale = 1.7f; + } + else + { + speedScale = 1.0f; + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + assert(ci); + } + else + { + ci = &cgs.clientinfo[ clientNum ]; + } + + CG_RunLerpFrame( cent, ci, ¢->pe.legs, cent->currentState.legsFlip, cent->currentState.legsAnim, speedScale, qfalse); + + if (!(cent->currentState.forcePowersActive & (1 << FP_RAGE))) + { //don't affect torso anim speed unless raged + speedScale = 1.0f; + } + else + { + speedScale = 1.7f; + } + + *legsOld = cent->pe.legs.oldFrame; + *legs = cent->pe.legs.frame; + *legsBackLerp = cent->pe.legs.backlerp; + + // If this is not a vehicle, you may lerm the frame (since vehicles never have a torso anim). -AReis + if ( cent->currentState.NPC_class != CLASS_VEHICLE ) + { + CG_RunLerpFrame( cent, ci, ¢->pe.torso, cent->currentState.torsoFlip, cent->currentState.torsoAnim, speedScale, qtrue ); + + *torsoOld = cent->pe.torso.oldFrame; + *torso = cent->pe.torso.frame; + *torsoBackLerp = cent->pe.torso.backlerp; + } +} + + + + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +#if 0 +typedef struct boneAngleParms_s { + void *ghoul2; + int modelIndex; + char *boneName; + vec3_t angles; + int flags; + int up; + int right; + int forward; + qhandle_t *modelList; + int blendTime; + int currentTime; + + qboolean refreshSet; +} boneAngleParms_t; + +boneAngleParms_t cgBoneAnglePostSet; +#endif + +void CG_G2SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ) +{ //we want to hold off on setting the bone angles until the end of the frame, because every time we set + //them the entire skeleton has to be reconstructed. +#if 0 + //This function should ONLY be called from CG_Player() or a function that is called only within CG_Player(). + //At the end of the frame we will check to use this information to call SetBoneAngles + memset(&cgBoneAnglePostSet, 0, sizeof(cgBoneAnglePostSet)); + cgBoneAnglePostSet.ghoul2 = ghoul2; + cgBoneAnglePostSet.modelIndex = modelIndex; + cgBoneAnglePostSet.boneName = (char *)boneName; + + cgBoneAnglePostSet.angles[0] = angles[0]; + cgBoneAnglePostSet.angles[1] = angles[1]; + cgBoneAnglePostSet.angles[2] = angles[2]; + + cgBoneAnglePostSet.flags = flags; + cgBoneAnglePostSet.up = up; + cgBoneAnglePostSet.right = right; + cgBoneAnglePostSet.forward = forward; + cgBoneAnglePostSet.modelList = modelList; + cgBoneAnglePostSet.blendTime = blendTime; + cgBoneAnglePostSet.currentTime = currentTime; + + cgBoneAnglePostSet.refreshSet = qtrue; +#endif + //We don't want to go with the delayed approach, we want out bolt points and everything to be updated in realtime. + //We'll just take the reconstructs and live with them. + trap_G2API_SetBoneAngles(ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList, + blendTime, currentTime); +} + +/* +================ +CG_Rag_Trace + +Variant on CG_Trace. Doesn't trace for ents because ragdoll engine trace code has no entity +trace access. Maybe correct this sometime, so bmodel col. at least works with ragdoll. +But I don't want to slow it down.. +================ +*/ +void CG_Rag_Trace( trace_t *result, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, + int skipNumber, int mask ) { + trap_CM_BoxTrace ( result, start, end, mins, maxs, 0, mask); + result->entityNum = result->fraction != 1.0 ? ENTITYNUM_WORLD : ENTITYNUM_NONE; +} + +//#define _RAG_BOLT_TESTING + +#ifdef _RAG_BOLT_TESTING +void CG_TempTestFunction(centity_t *cent, vec3_t forcedAngles) +{ + mdxaBone_t boltMatrix; + vec3_t tAngles; + vec3_t bOrg; + vec3_t bDir; + vec3_t uOrg; + + VectorSet(tAngles, 0, cent->lerpAngles[YAW], 0); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, tAngles, cent->lerpOrigin, + cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, bOrg); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, bDir); + + VectorMA(bOrg, 40, bDir, uOrg); + + CG_TestLine(bOrg, uOrg, 50, 0x0000ff, 1); + + cent->turAngles[YAW] = forcedAngles[YAW]; +} +#endif + +//list of valid ragdoll effectors +static const char *cg_effectorStringTable[] = +{ //commented out the ones I don't want dragging to affect +// "thoracic", +// "rhand", + "lhand", + "rtibia", + "ltibia", + "rtalus", + "ltalus", +// "rradiusX", + "lradiusX", + "rfemurX", + "lfemurX", +// "ceyebrow", + NULL //always terminate +}; + +//we want to see which way the pelvis is facing to get a relatively oriented base settling frame +//this is to avoid the arms stretching in opposite directions on the body trying to reach the base +//pose if the pelvis is flipped opposite of the base pose or something -rww +static int CG_RagAnimForPositioning(centity_t *cent) +{ + int bolt; + vec3_t dir; + mdxaBone_t matrix; + + assert(cent->ghoul2); + bolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis"); + assert(bolt > -1); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &matrix, cent->turAngles, cent->lerpOrigin, + cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, NEGATIVE_Z, dir); + + if (dir[2] > 0.0f) + { //facing up + return BOTH_DEADFLOP2; + } + else + { //facing down + return BOTH_DEADFLOP1; + } +} + +//rww - cgame interface for the ragdoll stuff. +//Returns qtrue if the entity is now in a ragdoll state, otherwise qfalse. +qboolean CG_RagDoll(centity_t *cent, vec3_t forcedAngles) +{ + vec3_t usedOrg; + qboolean inSomething = qfalse; + int ragAnim;//BOTH_DEAD1; //BOTH_DEATH1; + + if (!cg_ragDoll.integer) + { + return qfalse; + } + + if (cent->localAnimIndex) + { //don't rag non-humanoids + return qfalse; + } + + VectorCopy(cent->lerpOrigin, usedOrg); + + if (!cent->isRagging) + { //If we're not in a ragdoll state, perform the checks. + if (cent->currentState.eFlags & EF_RAG) + { //want to go into it no matter what then + inSomething = qtrue; + } + else if (cent->currentState.groundEntityNum == ENTITYNUM_NONE) + { + vec3_t cVel; + + VectorCopy(cent->currentState.pos.trDelta, cVel); + + if (VectorNormalize(cVel) > 400) + { //if he's flying through the air at a good enough speed, switch into ragdoll + inSomething = qtrue; + } + } + + if (cent->currentState.eType == ET_BODY) + { //just rag bodies immediately if their own was ragging on respawn + if (cent->ownerRagging) + { + cent->isRagging = qtrue; + return qfalse; + } + } + + if (cg_ragDoll.integer > 1) + { + inSomething = qtrue; + } + + if (!inSomething) + { + int anim = (cent->currentState.legsAnim); + int dur = (bgAllAnims[cent->localAnimIndex].anims[anim].numFrames-1) * fabs((float)(bgAllAnims[cent->localAnimIndex].anims[anim].frameLerp)); + int i = 0; + int boltChecks[5]; + vec3_t boltPoints[5]; + vec3_t trStart, trEnd; + vec3_t tAng; + qboolean deathDone = qfalse; + trace_t tr; + mdxaBone_t boltMatrix; + + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + if (cent->pe.legs.animationTime > 50 && (cg.time - cent->pe.legs.animationTime) > dur) + { //Looks like the death anim is done playing + deathDone = qtrue; + } + + if (deathDone) + { //only trace from the hands if the death anim is already done. + boltChecks[0] = trap_G2API_AddBolt(cent->ghoul2, 0, "rhand"); + boltChecks[1] = trap_G2API_AddBolt(cent->ghoul2, 0, "lhand"); + } + else + { //otherwise start the trace loop at the cranium. + i = 2; + } + boltChecks[2] = trap_G2API_AddBolt(cent->ghoul2, 0, "cranium"); + //boltChecks[3] = trap_G2API_AddBolt(cent->ghoul2, 0, "rtarsal"); + //boltChecks[4] = trap_G2API_AddBolt(cent->ghoul2, 0, "ltarsal"); + boltChecks[3] = trap_G2API_AddBolt(cent->ghoul2, 0, "rtalus"); + boltChecks[4] = trap_G2API_AddBolt(cent->ghoul2, 0, "ltalus"); + + //This may seem bad, but since we have a bone cache now it should manage to not be too disgustingly slow. + //Do the head first, because the hands reference it anyway. + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[2], &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[2]); + + while (i < 5) + { + if (i < 2) + { //when doing hands, trace to the head instead of origin + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[i], &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[i]); + VectorCopy(boltPoints[i], trStart); + VectorCopy(boltPoints[2], trEnd); + } + else + { + if (i > 2) + { //2 is the head, which already has the bolt point. + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, boltChecks[i], &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltPoints[i]); + } + VectorCopy(boltPoints[i], trStart); + VectorCopy(cent->lerpOrigin, trEnd); + } + + //Now that we have all that sorted out, trace between the two points we desire. + CG_Rag_Trace(&tr, trStart, NULL, NULL, trEnd, cent->currentState.number, MASK_SOLID); + + if (tr.fraction != 1.0 || tr.startsolid || tr.allsolid) + { //Hit something or start in solid, so flag it and break. + //This is a slight hack, but if we aren't done with the death anim, we don't really want to + //go into ragdoll unless our body has a relatively "flat" pitch. +#if 0 + vec3_t vSub; + + //Check the pitch from the head to the right foot (should be reasonable) + VectorSubtract(boltPoints[2], boltPoints[3], vSub); + VectorNormalize(vSub); + vectoangles(vSub, vSub); + + if (deathDone || (vSub[PITCH] < 50 && vSub[PITCH] > -50)) + { + inSomething = qtrue; + } +#else + inSomething = qtrue; +#endif + break; + } + + i++; + } + } + + if (inSomething) + { + cent->isRagging = qtrue; +#if 0 + VectorClear(cent->lerpOriginOffset); +#endif + } + } + + if (cent->isRagging) + { //We're in a ragdoll state, so make the call to keep our positions updated and whatnot. + sharedRagDollParams_t tParms; + sharedRagDollUpdateParams_t tuParms; + + ragAnim = CG_RagAnimForPositioning(cent); + + if (cent->ikStatus) + { //ik must be reset before ragdoll is started, or you'll get some interesting results. + trap_G2API_SetBoneIKState(cent->ghoul2, cg.time, NULL, IKS_NONE, NULL); + cent->ikStatus = qfalse; + } + + //these will be used as "base" frames for the ragoll settling. + tParms.startFrame = bgAllAnims[cent->localAnimIndex].anims[ragAnim].firstFrame;// + bgAllAnims[cent->localAnimIndex].anims[ragAnim].numFrames; + tParms.endFrame = bgAllAnims[cent->localAnimIndex].anims[ragAnim].firstFrame + bgAllAnims[cent->localAnimIndex].anims[ragAnim].numFrames; +#if 0 + { + float animSpeed = 0; + int blendTime = 600; + int flags = 0;//BONE_ANIM_OVERRIDE_FREEZE; + + if (bgAllAnims[cent->localAnimIndex].anims[ragAnim].loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + /* + if (cg_animBlend.integer) + { + flags |= BONE_ANIM_BLEND; + } + */ + + animSpeed = 50.0f / bgAllAnims[cent->localAnimIndex].anims[ragAnim].frameLerp; + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", tParms.startFrame, tParms.endFrame, flags, animSpeed,cg.time, -1, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", tParms.startFrame, tParms.endFrame, flags, animSpeed, cg.time, -1, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", tParms.startFrame, tParms.endFrame, flags, animSpeed, cg.time, -1, blendTime); + } +#elif 1 //with my new method of doing things I want it to continue the anim + { + float currentFrame; + int startFrame, endFrame; + int flags; + float animSpeed; + + if (trap_G2API_GetBoneAnim(cent->ghoul2, "model_root", cg.time, ¤tFrame, &startFrame, &endFrame, &flags, &animSpeed, cgs.gameModels, 0)) + { //lock the anim on the current frame. + int blendTime = 500; + animation_t *curAnim = &bgAllAnims[cent->localAnimIndex].anims[cent->currentState.legsAnim]; + + if (currentFrame >= (curAnim->firstFrame + curAnim->numFrames-1)) + { //this is sort of silly but it works for now. + currentFrame = (curAnim->firstFrame + curAnim->numFrames-2); + } + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "lower_lumbar", currentFrame, currentFrame+1, flags, animSpeed,cg.time, currentFrame, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", currentFrame, currentFrame+1, flags, animSpeed, cg.time, currentFrame, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", currentFrame, currentFrame+1, flags, animSpeed, cg.time, currentFrame, blendTime); + } + } +#endif + CG_G2SetBoneAngles(cent->ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); + CG_G2SetBoneAngles(cent->ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); + CG_G2SetBoneAngles(cent->ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); + CG_G2SetBoneAngles(cent->ghoul2, 0, "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, cgs.gameModels, 0, cg.time); + + VectorCopy(forcedAngles, tParms.angles); + VectorCopy(usedOrg, tParms.position); + VectorCopy(cent->modelScale, tParms.scale); + tParms.me = cent->currentState.number; + + tParms.collisionType = 1; + tParms.RagPhase = RP_DEATH_COLLISION; + tParms.fShotStrength = 4; + + trap_G2API_SetRagDoll(cent->ghoul2, &tParms); + + VectorCopy(forcedAngles, tuParms.angles); + VectorCopy(usedOrg, tuParms.position); + VectorCopy(cent->modelScale, tuParms.scale); + tuParms.me = cent->currentState.number; + tuParms.settleFrame = tParms.endFrame-1; + + if (cent->currentState.groundEntityNum != ENTITYNUM_NONE) + { + VectorClear(tuParms.velocity); + } + else + { + VectorScale(cent->currentState.pos.trDelta, 2.0f, tuParms.velocity); + } + + trap_G2API_AnimateG2Models(cent->ghoul2, cg.time, &tuParms); + + //So if we try to get a bolt point it's still correct + cent->turAngles[YAW] = + cent->lerpAngles[YAW] = + cent->pe.torso.yawAngle = + cent->pe.legs.yawAngle = forcedAngles[YAW]; + + if (cent->currentState.ragAttach && + (cent->currentState.eType != ET_NPC || cent->currentState.NPC_class != CLASS_VEHICLE)) + { + centity_t *grabEnt; + + if (cent->currentState.ragAttach == ENTITYNUM_NONE) + { //switch cl 0 and entitynum_none, so we can operate on the "if non-0" concept + grabEnt = &cg_entities[0]; + } + else + { + grabEnt = &cg_entities[cent->currentState.ragAttach]; + } + + if (grabEnt->ghoul2) + { + mdxaBone_t matrix; + vec3_t bOrg; + vec3_t thisHand; + vec3_t hands; + vec3_t pcjMin, pcjMax; + vec3_t pDif; + vec3_t thorPoint; + float difLen; + int thorBolt; + + //Get the person who is holding our hand's hand location + trap_G2API_GetBoltMatrix(grabEnt->ghoul2, 0, 0, &matrix, grabEnt->turAngles, grabEnt->lerpOrigin, + cg.time, cgs.gameModels, grabEnt->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, bOrg); + + //Get our hand's location + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, 0, &matrix, cent->turAngles, cent->lerpOrigin, + cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thisHand); + + //Get the position of the thoracic bone for hinting its velocity later on + thorBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "thoracic"); + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, thorBolt, &matrix, cent->turAngles, cent->lerpOrigin, + cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&matrix, ORIGIN, thorPoint); + + VectorSubtract(bOrg, thisHand, hands); + + if (VectorLength(hands) < 3.0f) + { + trap_G2API_RagForceSolve(cent->ghoul2, qfalse); + } + else + { + trap_G2API_RagForceSolve(cent->ghoul2, qtrue); + } + + //got the hand pos of him, now we want to make our hand go to it + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhand", bOrg); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rradius", bOrg); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rradiusX", bOrg); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerusX", bOrg); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerus", bOrg); + + //Make these two solve quickly so we can update decently + trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rhumerus", 1.5f); + trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rradius", 1.5f); + + //Break the constraints on them I suppose + VectorSet(pcjMin, -999, -999, -999); + VectorSet(pcjMax, 999, 999, 999); + trap_G2API_RagPCJConstraint(cent->ghoul2, "rhumerus", pcjMin, pcjMax); + trap_G2API_RagPCJConstraint(cent->ghoul2, "rradius", pcjMin, pcjMax); + + cent->overridingBones = cg.time + 2000; + + //hit the thoracic velocity to the hand point + VectorSubtract(bOrg, thorPoint, hands); + VectorNormalize(hands); + VectorScale(hands, 2048.0f, hands); + trap_G2API_RagEffectorKick(cent->ghoul2, "thoracic", hands); + trap_G2API_RagEffectorKick(cent->ghoul2, "ceyebrow", hands); + + VectorSubtract(cent->ragLastOrigin, cent->lerpOrigin, pDif); + VectorCopy(cent->lerpOrigin, cent->ragLastOrigin); + + if (cent->ragLastOriginTime >= cg.time && cent->currentState.groundEntityNum != ENTITYNUM_NONE) + { //make sure it's reasonably updated + difLen = VectorLength(pDif); + if (difLen > 0.0f) + { //if we're being dragged, then kick all the bones around a bit + vec3_t dVel; + vec3_t rVel; + int i = 0; + + if (difLen < 12.0f) + { + VectorScale(pDif, 12.0f/difLen, pDif); + difLen = 12.0f; + } + + while (cg_effectorStringTable[i]) + { + VectorCopy(pDif, dVel); + dVel[2] = 0; + + //Factor in a random velocity + VectorSet(rVel, flrand(-0.1f, 0.1f), flrand(-0.1f, 0.1f), flrand(0.1f, 0.5)); + VectorScale(rVel, 8.0f, rVel); + + VectorAdd(dVel, rVel, dVel); + VectorScale(dVel, 10.0f, dVel); + + trap_G2API_RagEffectorKick(cent->ghoul2, cg_effectorStringTable[i], dVel); + +#if 0 + { + mdxaBone_t bm; + vec3_t borg; + vec3_t vorg; + int b = trap_G2API_AddBolt(cent->ghoul2, 0, cg_effectorStringTable[i]); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &bm, cent->turAngles, cent->lerpOrigin, cg.time, + cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&bm, ORIGIN, borg); + + VectorMA(borg, 1.0f, dVel, vorg); + + CG_TestLine(borg, vorg, 50, 0x0000ff, 1); + } +#endif + + i++; + } + } + } + cent->ragLastOriginTime = cg.time + 1000; + } + } + else if (cent->overridingBones) + { //reset things to their normal rag state + vec3_t pcjMin, pcjMax; + vec3_t dVel; + + //got the hand pos of him, now we want to make our hand go to it + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhand", NULL); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rradius", NULL); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rradiusX", NULL); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerusX", NULL); + trap_G2API_RagEffectorGoal(cent->ghoul2, "rhumerus", NULL); + + VectorSet(dVel, 0.0f, 0.0f, -64.0f); + trap_G2API_RagEffectorKick(cent->ghoul2, "rhand", dVel); + + trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rhumerus", 0.0f); + trap_G2API_RagPCJGradientSpeed(cent->ghoul2, "rradius", 0.0f); + + VectorSet(pcjMin,-100.0f,-40.0f,-15.0f); + VectorSet(pcjMax,-15.0f,80.0f,15.0f); + trap_G2API_RagPCJConstraint(cent->ghoul2, "rhumerus", pcjMin, pcjMax); + + VectorSet(pcjMin,-25.0f,-20.0f,-20.0f); + VectorSet(pcjMax,90.0f,20.0f,-20.0f); + trap_G2API_RagPCJConstraint(cent->ghoul2, "rradius", pcjMin, pcjMax); + + if (cent->overridingBones < cg.time) + { + trap_G2API_RagForceSolve(cent->ghoul2, qfalse); + cent->overridingBones = 0; + } + else + { + trap_G2API_RagForceSolve(cent->ghoul2, qtrue); + } + } + + return qtrue; + } + + return qfalse; +} + +//set the bone angles of this client entity based on data from the server -rww +void CG_G2ServerBoneAngles(centity_t *cent) +{ + int i = 0; + int bone = cent->currentState.boneIndex1; + int flags, up, right, forward; + vec3_t boneAngles; + + VectorCopy(cent->currentState.boneAngles1, boneAngles); + + while (i < 4) + { //cycle through the 4 bone index values on the entstate + if (bone) + { //if it's non-0 then it could have something in it. + const char *boneName = CG_ConfigString(CS_G2BONES+bone); + + if (boneName && boneName[0]) + { //got the bone, now set the angles from the corresponding entitystate boneangles value. + flags = BONE_ANGLES_POSTMULT; + + //get the orientation out of our bit field + forward = (cent->currentState.boneOrient)&7; //3 bits from bit 0 + right = (cent->currentState.boneOrient>>3)&7; //3 bits from bit 3 + up = (cent->currentState.boneOrient>>6)&7; //3 bits from bit 6 + + trap_G2API_SetBoneAngles(cent->ghoul2, 0, boneName, boneAngles, flags, up, right, forward, cgs.gameModels, 100, cg.time); + } + } + + switch (i) + { + case 0: + bone = cent->currentState.boneIndex2; + VectorCopy(cent->currentState.boneAngles2, boneAngles); + break; + case 1: + bone = cent->currentState.boneIndex3; + VectorCopy(cent->currentState.boneAngles3, boneAngles); + break; + case 2: + bone = cent->currentState.boneIndex4; + VectorCopy(cent->currentState.boneAngles4, boneAngles); + break; + default: + break; + } + + i++; + } +} + +/* +------------------------- +CG_G2SetHeadBlink +------------------------- +*/ +static void CG_G2SetHeadBlink( centity_t *cent, qboolean bStart ) +{ + vec3_t desiredAngles; + int blendTime = 80; + qboolean bWink = qfalse; + const int hReye = trap_G2API_AddBolt( cent->ghoul2, 0, "reye" ); + const int hLeye = trap_G2API_AddBolt( cent->ghoul2, 0, "leye" ); + + if (hLeye == -1) + { + return; + } + + VectorClear(desiredAngles); + + if (bStart) + { + desiredAngles[YAW] = -50; + if ( random() > 0.95f ) + { + bWink = qtrue; + blendTime /=3; + } + } + trap_G2API_SetBoneAngles( cent->ghoul2, 0, "leye", desiredAngles, + BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time ); + + if (hReye == -1) + { + return; + } + + if (!bWink) + { + trap_G2API_SetBoneAngles( cent->ghoul2, 0, "reye", desiredAngles, + BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, blendTime, cg.time ); + } +} + +/* +------------------------- +CG_G2SetHeadAnims +------------------------- +*/ +static void CG_G2SetHeadAnim( centity_t *cent, int anim ) +{ + const int blendTime = 50; + const animation_t *animations = bgAllAnims[cent->localAnimIndex].anims; + int animFlags = BONE_ANIM_OVERRIDE ;//| BONE_ANIM_BLEND; + // animSpeed is 1.0 if the frameLerp (ms/frame) is 50 (20 fps). +// float timeScaleMod = (cg_timescale.value&&gent&&gent->s.clientNum==0&&!player_locked&&!MatrixMode&&gent->client->ps.forcePowersActive&(1<ghoul2[gent->playerModel], cent->gent->faceBone, &startFrame, &endFrame); + +// if (!animatingHead || ( animations[anim].firstFrame != startFrame ) )// only set the anim if we aren't going to do the same animation again + { + // gi.G2API_SetBoneAnimIndex(&gent->ghoul2[gent->playerModel], cent->gent->faceBone, + // firstFrame, lastFrame, animFlags, animSpeed, cg.time, -1, blendTime); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "face", firstFrame, lastFrame, animFlags, animSpeed, + cg.time, -1, blendTime); + } +} + +qboolean CG_G2PlayerHeadAnims( centity_t *cent ) +{ + clientInfo_t *ci = NULL; + int anim = -1; + int voiceVolume = 0; + + if(cent->localAnimIndex > 1) + { //only do this for humanoids + return qfalse; + } + + if (cent->noFace) + { // i don't have a face + return qfalse; + } + + if (cent->currentState.number < MAX_CLIENTS) + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + else + { + ci = cent->npcClient; + } + + if (!ci) + { + return qfalse; + } + + if ( cent->currentState.eFlags & EF_DEAD ) + {//Dead people close their eyes and don't make faces! + anim = FACE_DEAD; + ci->facial_blink = -1; + } + else + { + if (!ci->facial_blink) + { // set the timers + ci->facial_blink = cg.time + flrand(4000.0, 8000.0); + ci->facial_frown = cg.time + flrand(6000.0, 10000.0); + ci->facial_aux = cg.time + flrand(6000.0, 10000.0); + } + + //are we blinking? + if (ci->facial_blink < 0) + { // yes, check if we are we done blinking ? + if (-(ci->facial_blink) < cg.time) + { // yes, so reset blink timer + ci->facial_blink = cg.time + flrand(4000.0, 8000.0); + CG_G2SetHeadBlink( cent, qfalse ); //stop the blink + } + } + else // no we aren't blinking + { + if (ci->facial_blink < cg.time)// but should we start ? + { + CG_G2SetHeadBlink( cent, qtrue ); + if (ci->facial_blink == 1) + {//requested to stay shut by SET_FACEEYESCLOSED + ci->facial_blink = -(cg.time + 99999999.0f);// set blink timer + } + else + { + ci->facial_blink = -(cg.time + 300.0f);// set blink timer + } + } + } + + voiceVolume = trap_S_GetVoiceVolume(cent->currentState.number); + + if (voiceVolume > 0) // if we aren't talking, then it will be 0, -1 for talking but paused + { + anim = FACE_TALK1 + voiceVolume -1; + } + else if (voiceVolume == 0) //don't do aux if in a slient part of speech + {//not talking + if (ci->facial_aux < 0) // are we auxing ? + { //yes + if (-(ci->facial_aux) < cg.time)// are we done auxing ? + { // yes, reset aux timer + ci->facial_aux = cg.time + flrand(7000.0, 10000.0); + } + else + { // not yet, so choose aux + anim = FACE_ALERT; + } + } + else // no we aren't auxing + { // but should we start ? + if (ci->facial_aux < cg.time) + {//yes + anim = FACE_ALERT; + // set aux timer + ci->facial_aux = -(cg.time + 2000.0); + } + } + + if (anim != -1) //we we are auxing, see if we should override with a frown + { + if (ci->facial_frown < 0)// are we frowning ? + { // yes, + if (-(ci->facial_frown) < cg.time)//are we done frowning ? + { // yes, reset frown timer + ci->facial_frown = cg.time + flrand(7000.0, 10000.0); + } + else + { // not yet, so choose frown + anim = FACE_FROWN; + } + } + else// no we aren't frowning + { // but should we start ? + if (ci->facial_frown < cg.time) + { + anim = FACE_FROWN; + // set frown timer + ci->facial_frown = -(cg.time + 2000.0); + } + } + } + + }//talking + }//dead + if (anim != -1) + { + CG_G2SetHeadAnim( cent, anim ); + return qtrue; + } + return qfalse; +} + + +static void CG_G2PlayerAngles( centity_t *cent, vec3_t legs[3], vec3_t legsAngles) +{ + clientInfo_t *ci; + + //rww - now do ragdoll stuff + if ((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG)) + { + vec3_t forcedAngles; + + VectorClear(forcedAngles); + forcedAngles[YAW] = cent->lerpAngles[YAW]; + + if (CG_RagDoll(cent, forcedAngles)) + { //if we managed to go into the rag state, give our ent axis the forced angles and return. + AnglesToAxis( forcedAngles, legs ); + VectorCopy(forcedAngles, legsAngles); + return; + } + } + else if (cent->isRagging) + { + cent->isRagging = qfalse; + trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + assert(ci); + } + else + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + + //rww - Quite possibly the most arguments for a function ever. + if (cent->localAnimIndex <= 1) + { //don't do these things on non-humanoids + vec3_t lookAngles; + entityState_t *emplaced = NULL; + + if (cent->currentState.hasLookTarget) + { + VectorSubtract(cg_entities[cent->currentState.lookTarget].lerpOrigin, cent->lerpOrigin, lookAngles); + vectoangles(lookAngles, lookAngles); + ci->lookTime = cg.time + 1000; + } + else + { + VectorCopy(cent->lerpAngles, lookAngles); + } + lookAngles[PITCH] = 0; + + if (cent->currentState.otherEntityNum2) + { + emplaced = &cg_entities[cent->currentState.otherEntityNum2].currentState; + } + + BG_G2PlayerAngles(cent->ghoul2, ci->bolt_motion, ¢->currentState, cg.time, + cent->lerpOrigin, cent->lerpAngles, legs, legsAngles, ¢->pe.torso.yawing, ¢->pe.torso.pitching, + ¢->pe.legs.yawing, ¢->pe.torso.yawAngle, ¢->pe.torso.pitchAngle, ¢->pe.legs.yawAngle, + cg.frametime, cent->turAngles, cent->modelScale, ci->legsAnim, ci->torsoAnim, &ci->corrTime, + lookAngles, ci->lastHeadAngles, ci->lookTime, emplaced, &ci->superSmoothTime); + + if (cent->currentState.heldByClient && cent->currentState.heldByClient <= MAX_CLIENTS) + { //then put our arm in this client's hand + //is index+1 because index 0 is valid. + int heldByIndex = cent->currentState.heldByClient-1; + centity_t *other = &cg_entities[heldByIndex]; + + if (other && other->ghoul2 && ci->bolt_lhand) + { + mdxaBone_t boltMatrix; + vec3_t boltOrg; + + trap_G2API_GetBoltMatrix(other->ghoul2, 0, ci->bolt_lhand, &boltMatrix, other->turAngles, other->lerpOrigin, cg.time, cgs.gameModels, other->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + + BG_IK_MoveArm(cent->ghoul2, ci->bolt_lhand, cg.time, ¢->currentState, + cent->currentState.torsoAnim/*BOTH_DEAD1*/, boltOrg, ¢->ikStatus, cent->lerpOrigin, cent->lerpAngles, cent->modelScale, 500, qfalse); + } + } + else if (cent->ikStatus) + { //make sure we aren't IKing if we don't have anyone to hold onto us. + BG_IK_MoveArm(cent->ghoul2, ci->bolt_lhand, cg.time, ¢->currentState, + cent->currentState.torsoAnim/*BOTH_DEAD1*/, vec3_origin, ¢->ikStatus, cent->lerpOrigin, cent->lerpAngles, cent->modelScale, 500, qtrue); + } + } + else if ( cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) + { + vec3_t lookAngles; + + VectorCopy(cent->lerpAngles, legsAngles); + legsAngles[PITCH] = 0; + AnglesToAxis( legsAngles, legs ); + + VectorCopy(cent->lerpAngles, lookAngles); + lookAngles[YAW] = lookAngles[ROLL] = 0; + + BG_G2ATSTAngles( cent->ghoul2, cg.time, lookAngles ); + } + else + { + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle && + cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { //fighters actually want to take pitch and roll into account for the axial angles + VectorCopy(cent->lerpAngles, legsAngles); + AnglesToAxis( legsAngles, legs ); + } + else if (cent->currentState.eType == ET_NPC && + cent->currentState.m_iVehicleNum && + cent->currentState.NPC_class != CLASS_VEHICLE ) + { //an NPC bolted to a vehicle should use the full angles + VectorCopy(cent->lerpAngles, legsAngles); + AnglesToAxis( legsAngles, legs ); + } + else + { + vec3_t nhAngles; + + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle && + cent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER) + { //yeah, a hack, sorry. + VectorSet(nhAngles, 0, cent->lerpAngles[YAW], cent->lerpAngles[ROLL]); + } + else + { + VectorSet(nhAngles, 0, cent->lerpAngles[YAW], 0); + } + AnglesToAxis( nhAngles, legs ); + } + } + + //See if we have any bone angles sent from the server + CG_G2ServerBoneAngles(cent); +} +//========================================================================== + +/* +=============== +CG_TrailItem +=============== +*/ +#if 0 +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 ); +} +#endif + + +/* +=============== +CG_PlayerFlag +=============== +*/ +static void CG_PlayerFlag( centity_t *cent, qhandle_t hModel ) { + refEntity_t ent; + vec3_t angles; + vec3_t axis[3]; + vec3_t boltOrg, tAng, getAng, right; + mdxaBone_t boltMatrix; + clientInfo_t *ci; + + if (cent->currentState.number == cg.snap->ps.clientNum && + !cg.renderingThirdPerson) + { + return; + } + + if (!cent->ghoul2) + { + return; + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + assert(ci); + } + else + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_llumbar, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, tAng); + vectoangles(tAng, tAng); + + VectorCopy(cent->lerpAngles, angles); + + boltOrg[2] -= 12; + VectorSet(getAng, 0, cent->lerpAngles[1], 0); + AngleVectors(getAng, 0, right, 0); + boltOrg[0] += right[0]*8; + boltOrg[1] += right[1]*8; + boltOrg[2] += right[2]*8; + + angles[PITCH] = -cent->lerpAngles[PITCH]/2-30; + angles[YAW] = tAng[YAW]+270; + + AnglesToAxis(angles, axis); + + memset( &ent, 0, sizeof( ent ) ); + VectorMA( boltOrg, 24, axis[0], ent.origin ); + + angles[ROLL] += 20; + AnglesToAxis( angles, ent.axis ); + + ent.hModel = hModel; + + ent.modelScale[0] = 0.5; + ent.modelScale[1] = 0.5; + ent.modelScale[2] = 0.5; + ScaleModelAxis(&ent); + + /* + if (cent->currentState.number == cg.snap->ps.clientNum) + { //If we're the current client (in third person), render the flag on our back transparently + ent.renderfx |= RF_FORCE_ENT_ALPHA; + ent.shaderRGBA[3] = 100; + } + */ + //FIXME: Not doing this at the moment because sorting totally messes up + + trap_R_AddRefEntityToScene( &ent ); +} + + +/* +=============== +CG_PlayerPowerups +=============== +*/ +static void CG_PlayerPowerups( centity_t *cent, refEntity_t *torso ) { + int powerups; + clientInfo_t *ci; + + powerups = cent->currentState.powerups; + 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 ); + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + assert(ci); + } + else + { + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + } + // redflag + if ( powerups & ( 1 << PW_REDFLAG ) ) { + CG_PlayerFlag( cent, cgs.media.redFlagModel ); + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f ); + } + + // blueflag + if ( powerups & ( 1 << PW_BLUEFLAG ) ) { + CG_PlayerFlag( cent, cgs.media.blueFlagModel ); + trap_R_AddLightToScene( cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0 ); + } + + // neutralflag + if ( powerups & ( 1 << PW_NEUTRALFLAG ) ) { + 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_PlayerFloatSprite + +Same as above but allows custom RGBA values +=============== +*/ +#if 0 +static void CG_PlayerFloatSpriteRGBA( centity_t *cent, qhandle_t shader, vec4_t rgba ) { + 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] = rgba[0]; + ent.shaderRGBA[1] = rgba[1]; + ent.shaderRGBA[2] = rgba[2]; + ent.shaderRGBA[3] = rgba[3]; + trap_R_AddRefEntityToScene( &ent ); +} +#endif + + +/* +=============== +CG_PlayerSprites + +Float sprites over the player's head +=============== +*/ +static void CG_PlayerSprites( centity_t *cent ) { +// int team; + + if (cg.snap && + CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg.snap->ps.clientNum)) + { + return; //this entity is mind-tricking the current client, so don't render it + } + + if ( cent->currentState.eFlags & EF_CONNECTION ) { + CG_PlayerFloatSprite( cent, cgs.media.connectionShader ); + return; + } + + if (cent->vChatTime > cg.time) + { + CG_PlayerFloatSprite( cent, cgs.media.vchatShader ); + } + else if ( cent->currentState.eType != ET_NPC && //don't draw talk balloons on NPCs + (cent->currentState.eFlags & EF_TALK) ) + { + CG_PlayerFloatSprite( cent, cgs.media.balloonShader ); + 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; + float radius = 24.0f; + + *shadowPlane = 0; + + if ( cg_shadows.integer == 0 ) { + return qfalse; + } + + // no shadows when cloaked + if ( cent->currentState.powerups & ( 1 << PW_CLOAKED )) + { + return qfalse; + } + + if (cent->currentState.eFlags & EF_DEAD) + { + return qfalse; + } + + if (CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg.snap->ps.clientNum)) + { + return qfalse; //this entity is mind-tricking the current client, so don't render it + } + + if ( cg_shadows.integer == 1 ) + {//dropshadow + if (cent->currentState.m_iVehicleNum && + cent->currentState.NPC_class != CLASS_VEHICLE ) + {//riding a vehicle, no dropshadow + return qfalse; + } + } + // send a trace down from the player to the ground + VectorCopy( cent->lerpOrigin, end ); + if (cg_shadows.integer == 2) + { //stencil + end[2] -= 4096.0f; + + trap_CM_BoxTrace( &trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID ); + + if ( trace.fraction == 1.0 || trace.startsolid || trace.allsolid ) + { + trace.endpos[2] = cent->lerpOrigin[2]-25.0f; + } + } + else + { + 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; + } + } + + if (cg_shadows.integer == 2) + { //stencil shadows need plane to be on ground + *shadowPlane = trace.endpos[2]; + } + else + { + *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 + if ( cent->currentState.NPC_class == CLASS_REMOTE + || cent->currentState.NPC_class == CLASS_SEEKER ) + { + radius = 8.0f; + } + CG_ImpactMark( cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal, + cent->pe.legs.yawAngle, alpha,alpha,alpha,1, qfalse, radius, 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 ); +} + +#define REFRACT_EFFECT_DURATION 500 +static void CG_ForcePushBlur( vec3_t org, centity_t *cent ) +{ + if (!cent || !cg_renderToTextureFX.integer) + { + localEntity_t *ex; + + ex = CG_AllocLocalEntity(); + ex->leType = LE_PUFF; + ex->refEntity.reType = RT_SPRITE; + ex->radius = 2.0f; + ex->startTime = cg.time; + ex->endTime = ex->startTime + 120; + VectorCopy( org, ex->pos.trBase ); + ex->pos.trTime = cg.time; + ex->pos.trType = TR_LINEAR; + VectorScale( cg.refdef.viewaxis[1], 55, ex->pos.trDelta ); + + ex->color[0] = 24; + ex->color[1] = 32; + ex->color[2] = 40; + ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" ); + + ex = CG_AllocLocalEntity(); + ex->leType = LE_PUFF; + ex->refEntity.reType = RT_SPRITE; + ex->refEntity.rotation = 180.0f; + ex->radius = 2.0f; + ex->startTime = cg.time; + ex->endTime = ex->startTime + 120; + VectorCopy( org, ex->pos.trBase ); + ex->pos.trTime = cg.time; + ex->pos.trType = TR_LINEAR; + VectorScale( cg.refdef.viewaxis[1], -55, ex->pos.trDelta ); + + ex->color[0] = 24; + ex->color[1] = 32; + ex->color[2] = 40; + ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" ); + } + else + { //superkewl "refraction" (well sort of) effect -rww + refEntity_t ent; + vec3_t ang; + float scale; + float vLen; + float alpha; + int tDif; + + if (!cent->bodyFadeTime) + { //the duration for the expansion and fade + cent->bodyFadeTime = cg.time + REFRACT_EFFECT_DURATION; + } + + //closer tDif is to 0, the closer we are to + //being "done" + tDif = (cent->bodyFadeTime - cg.time); + + if ((REFRACT_EFFECT_DURATION-tDif) < 200) + { //stop following the hand after a little and stay in a fixed spot + //save the initial spot of the effect + VectorCopy(org, cent->pushEffectOrigin); + } + + //scale from 1.0f to 0.1f then hold at 0.1 for the rest of the duration + if (cent->currentState.powerups & (1 << PW_PULL)) + { + scale = (float)(REFRACT_EFFECT_DURATION-tDif)*0.003f; + } + else + { + scale = (float)(tDif)*0.003f; + } + + if (scale > 1.0f) + { + scale = 1.0f; + } + else if (scale < 0.2f) + { + scale = 0.2f; + } + + //start alpha at 244, fade to 10 + alpha = (float)tDif*0.488f; + + if (alpha > 244.0f) + { + alpha = 244.0f; + } + else if (alpha < 10.0f) + { + alpha = 10.0f; + } + + memset( &ent, 0, sizeof( ent ) ); + ent.shaderTime = (cent->bodyFadeTime-REFRACT_EFFECT_DURATION) / 1000.0f; + + VectorCopy( cent->pushEffectOrigin, ent.origin ); + + VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (vLen <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + vectoangles(ent.axis[0], ang); + ang[ROLL] += 180.0f; + AnglesToAxis(ang, ent.axis); + + //radius must be a power of 2, and is the actual captured texture size + if (vLen < 128) + { + ent.radius = 256; + } + else if (vLen < 256) + { + ent.radius = 128; + } + else if (vLen < 512) + { + ent.radius = 64; + } + else + { + ent.radius = 32; + } + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], scale, ent.axis[2]); + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = cgs.media.refractionShader; //cgs.media.cloakedShader; + ent.nonNormalizedAxes = qtrue; + + //make it partially transparent so it blends with the background + ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); + ent.shaderRGBA[0] = 255.0f; + ent.shaderRGBA[1] = 255.0f; + ent.shaderRGBA[2] = 255.0f; + ent.shaderRGBA[3] = alpha; + + trap_R_AddRefEntityToScene( &ent ); + } +} + +static const char *cg_pushBoneNames[] = +{ + "cranium", + "lower_lumbar", + "rhand", + "lhand", + "ltibia", + "rtibia", + "lradius", + "rradius", + NULL +}; + +static void CG_ForcePushBodyBlur( centity_t *cent ) +{ + vec3_t fxOrg; + mdxaBone_t boltMatrix; + int bolt; + int i; + + if (cent->localAnimIndex > 1) + { //Sorry, the humanoid IS IN ANOTHER CASTLE. + return; + } + + if (cg.snap && + CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg.snap->ps.clientNum)) + { + return; //this entity is mind-tricking the current client, so don't render it + } + + assert(cent->ghoul2); + + for (i = 0; cg_pushBoneNames[i]; i++) + { //go through all the bones we want to put a blur effect on + bolt = trap_G2API_AddBolt(cent->ghoul2, 0, cg_pushBoneNames[i]); + + if (bolt == -1) + { + assert(!"You've got an invalid bone/bolt name in cg_pushBoneNames"); + continue; + } + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, bolt, &boltMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, fxOrg); + + //standard effect, don't be refractive (for now) + CG_ForcePushBlur(fxOrg, NULL); + } +} + +static void CG_ForceGripEffect( vec3_t org ) +{ + localEntity_t *ex; + float wv = sin( cg.time * 0.004f ) * 0.08f + 0.1f; + + ex = CG_AllocLocalEntity(); + ex->leType = LE_PUFF; + ex->refEntity.reType = RT_SPRITE; + ex->radius = 2.0f; + ex->startTime = cg.time; + ex->endTime = ex->startTime + 120; + VectorCopy( org, ex->pos.trBase ); + ex->pos.trTime = cg.time; + ex->pos.trType = TR_LINEAR; + VectorScale( cg.refdef.viewaxis[1], 55, ex->pos.trDelta ); + + ex->color[0] = 200+((wv*255)); + if (ex->color[0] > 255) + { + ex->color[0] = 255; + } + ex->color[1] = 0; + ex->color[2] = 0; + ex->refEntity.customShader = trap_R_RegisterShader( "gfx/effects/forcePush" ); + + ex = CG_AllocLocalEntity(); + ex->leType = LE_PUFF; + ex->refEntity.reType = RT_SPRITE; + ex->refEntity.rotation = 180.0f; + ex->radius = 2.0f; + ex->startTime = cg.time; + ex->endTime = ex->startTime + 120; + VectorCopy( org, ex->pos.trBase ); + ex->pos.trTime = cg.time; + ex->pos.trType = TR_LINEAR; + VectorScale( cg.refdef.viewaxis[1], -55, ex->pos.trDelta ); + + /* + ex->color[0] = 200+((wv*255)); + if (ex->color[0] > 255) + { + ex->color[0] = 255; + } + */ + ex->color[0] = 255; + ex->color[1] = 255; + ex->color[2] = 255; + ex->refEntity.customShader = cgs.media.redSaberGlowShader;//trap_R_RegisterShader( "gfx/effects/forcePush" ); +} + + +/* +=============== +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 ) { + + if (CG_IsMindTricked(state->trickedentindex, + state->trickedentindex2, + state->trickedentindex3, + state->trickedentindex4, + cg.snap->ps.clientNum)) + { + return; //this entity is mind-tricking the current client, so don't render it + } + + trap_R_AddRefEntityToScene( ent ); +} + +#define MAX_SHIELD_TIME 2000.0 +#define MIN_SHIELD_TIME 2000.0 + + +void CG_PlayerShieldHit(int entitynum, vec3_t dir, int amount) +{ + centity_t *cent; + int time; + + if (entitynum<0 || entitynum >= MAX_ENTITIES) + { + return; + } + + cent = &cg_entities[entitynum]; + + if (amount > 100) + { + time = cg.time + MAX_SHIELD_TIME; // 2 sec. + } + else + { + time = cg.time + 500 + amount*15; + } + + if (time > cent->damageTime) + { + cent->damageTime = time; + VectorScale(dir, -1, dir); + vectoangles(dir, cent->damageAngles); + } +} + + +void CG_DrawPlayerShield(centity_t *cent, vec3_t origin) +{ + refEntity_t ent; + int alpha; + float scale; + + // Don't draw the shield when the player is dead. + if (cent->currentState.eFlags & EF_DEAD) + { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( origin, ent.origin ); + ent.origin[2] += 10.0; + AnglesToAxis( cent->damageAngles, ent.axis ); + + alpha = 255.0 * ((cent->damageTime - cg.time) / MIN_SHIELD_TIME) + random()*16; + if (alpha>255) + alpha=255; + + // Make it bigger, but tighter if more solid + scale = 1.4 - ((float)alpha*(0.4/255.0)); // Range from 1.0 to 1.4 + VectorScale( ent.axis[0], scale, ent.axis[0] ); + VectorScale( ent.axis[1], scale, ent.axis[1] ); + VectorScale( ent.axis[2], scale, ent.axis[2] ); + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = cgs.media.halfShieldShader; + ent.shaderRGBA[0] = alpha; + ent.shaderRGBA[1] = alpha; + ent.shaderRGBA[2] = alpha; + ent.shaderRGBA[3] = 255; + trap_R_AddRefEntityToScene( &ent ); +} + + +void CG_PlayerHitFX(centity_t *cent) +{ + // only do the below fx if the cent in question is...uh...me, and it's first person. + if (cent->currentState.clientNum != cg.predictedPlayerState.clientNum || cg.renderingThirdPerson) + { + if (cent->damageTime > cg.time + && cent->currentState.NPC_class != CLASS_VEHICLE ) + { + CG_DrawPlayerShield(cent, cent->lerpOrigin); + } + + return; + } +} + + + +/* +================= +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; +} + +static void CG_RGBForSaberColor( saber_colors_t color, vec3_t rgb ) +{ + switch( color ) + { + case SABER_RED: + VectorSet( rgb, 1.0f, 0.2f, 0.2f ); + break; + case SABER_ORANGE: + VectorSet( rgb, 1.0f, 0.5f, 0.1f ); + break; + case SABER_YELLOW: + VectorSet( rgb, 1.0f, 1.0f, 0.2f ); + break; + case SABER_GREEN: + VectorSet( rgb, 0.2f, 1.0f, 0.2f ); + break; + case SABER_BLUE: + VectorSet( rgb, 0.2f, 0.4f, 1.0f ); + break; + case SABER_PURPLE: + VectorSet( rgb, 0.9f, 0.2f, 1.0f ); + break; + } +} + +static void CG_DoSaberLight( saberInfo_t *saber ) +{ + vec3_t positions[MAX_BLADES*2], mid={0}, rgbs[MAX_BLADES*2], rgb={0}; + float lengths[MAX_BLADES*2]={0}, totallength = 0, numpositions = 0, dist, diameter = 0; + int i, j; + + //RGB combine all the colors of the sabers you're using into one averaged color! + if ( !saber ) + { + return; + } + + if ( (saber->saberFlags2&SFL2_NO_DLIGHT) ) + {//no dlight! + return; + } + + for ( i = 0; i < saber->numBlades; i++ ) + { + if ( saber->blade[i].length >= 0.5f ) + { + //FIXME: make RGB sabers + CG_RGBForSaberColor( saber->blade[i].color, rgbs[i] ); + lengths[i] = saber->blade[i].length; + if ( saber->blade[i].length*2.0f > diameter ) + { + diameter = saber->blade[i].length*2.0f; + } + totallength += saber->blade[i].length; + VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length, saber->blade[i].muzzleDir, positions[i] ); + if ( !numpositions ) + {//first blade, store middle of that as midpoint + VectorMA( saber->blade[i].muzzlePoint, saber->blade[i].length*0.5, saber->blade[i].muzzleDir, mid ); + VectorCopy( rgbs[i], rgb ); + } + numpositions++; + } + } + + if ( totallength ) + {//actually have something to do + if ( numpositions == 1 ) + {//only 1 blade, midpoint is already set (halfway between the start and end of that blade), rgb is already set, so it diameter + } + else + {//multiple blades, calc averages + VectorClear( mid ); + VectorClear( rgb ); + //now go through all the data and get the average RGB and middle position and the radius + for ( i = 0; i < MAX_BLADES*2; i++ ) + { + if ( lengths[i] ) + { + VectorMA( rgb, lengths[i], rgbs[i], rgb ); + VectorAdd( mid, positions[i], mid ); + } + } + + //get middle rgb + VectorScale( rgb, 1/totallength, rgb );//get the average, normalized RGB + //get mid position + VectorScale( mid, 1/numpositions, mid ); + //find the farthest distance between the blade tips, this will be our diameter + for ( i = 0; i < MAX_BLADES*2; i++ ) + { + if ( lengths[i] ) + { + for ( j = 0; j < MAX_BLADES*2; j++ ) + { + if ( lengths[j] ) + { + dist = Distance( positions[i], positions[j] ); + if ( dist > diameter ) + { + diameter = dist; + } + } + } + } + } + } + + trap_R_AddLightToScene( mid, diameter + (random()*8.0f), rgb[0], rgb[1], rgb[2] ); + } +} + +void CG_DoSaber( vec3_t origin, vec3_t dir, float length, float lengthMax, float radius, saber_colors_t color, int rfx, qboolean doLight ) +{ + vec3_t mid; + qhandle_t blade = 0, glow = 0; + refEntity_t saber; + float radiusmult; + float radiusRange; + float radiusStart; + + if ( length < 0.5f ) + { + // if the thing is so short, just forget even adding me. + return; + } + + // Find the midpoint of the saber for lighting purposes + VectorMA( origin, length * 0.5f, dir, mid ); + + switch( color ) + { + case SABER_RED: + glow = cgs.media.redSaberGlowShader; + blade = cgs.media.redSaberCoreShader; + break; + case SABER_ORANGE: + glow = cgs.media.orangeSaberGlowShader; + blade = cgs.media.orangeSaberCoreShader; + break; + case SABER_YELLOW: + glow = cgs.media.yellowSaberGlowShader; + blade = cgs.media.yellowSaberCoreShader; + break; + case SABER_GREEN: + glow = cgs.media.greenSaberGlowShader; + blade = cgs.media.greenSaberCoreShader; + break; + case SABER_BLUE: + glow = cgs.media.blueSaberGlowShader; + blade = cgs.media.blueSaberCoreShader; + break; + case SABER_PURPLE: + glow = cgs.media.purpleSaberGlowShader; + blade = cgs.media.purpleSaberCoreShader; + break; + default: + glow = cgs.media.blueSaberGlowShader; + blade = cgs.media.blueSaberCoreShader; + break; + } + + if (doLight) + { // always add a light because sabers cast a nice glow before they slice you in half!! or something... + vec3_t rgb={1,1,1}; + CG_RGBForSaberColor( color, rgb ); + trap_R_AddLightToScene( mid, (length*1.4f) + (random()*3.0f), rgb[0], rgb[1], rgb[2] ); + } + + memset( &saber, 0, sizeof( refEntity_t )); + + // Saber glow is it's own ref type because it uses a ton of sprites, otherwise it would eat up too many + // refEnts to do each glow blob individually + saber.saberLength = length; + + // Jeff, I did this because I foolishly wished to have a bright halo as the saber is unleashed. + // It's not quite what I'd hoped tho. If you have any ideas, go for it! --Pat + if (length < lengthMax) + { + radiusmult = 1.0 + (2.0 / length); // Note this creates a curve, and length cannot be < 0.5. + } + else + { + radiusmult = 1.0; + } + + if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) + { //draw the blade as a post-render so it doesn't get in the cap... + rfx |= RF_FORCEPOST; + } + + radiusRange = radius * 0.075f; + radiusStart = radius-radiusRange; + + saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult; + //saber.radius = (2.8f + crandom() * 0.2f)*radiusmult; + + VectorCopy( origin, saber.origin ); + VectorCopy( dir, saber.axis[0] ); + saber.reType = RT_SABER_GLOW; + saber.customShader = glow; + saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff; + saber.renderfx = rfx; + + trap_R_AddRefEntityToScene( &saber ); + + // Do the hot core + VectorMA( origin, length, dir, saber.origin ); + VectorMA( origin, -1, dir, saber.oldorigin ); + + +// CG_TestLine(saber.origin, saber.oldorigin, 50, 0x000000ff, 3); + saber.customShader = blade; + saber.reType = RT_LINE; + radiusStart = radius/3.0f; + saber.radius = (radiusStart + crandom() * radiusRange)*radiusmult; +// saber.radius = (1.0 + crandom() * 0.2f)*radiusmult; + + saber.shaderTexCoord[0] = saber.shaderTexCoord[1] = 1.0f; + saber.shaderRGBA[0] = saber.shaderRGBA[1] = saber.shaderRGBA[2] = saber.shaderRGBA[3] = 0xff; + + trap_R_AddRefEntityToScene( &saber ); +} + +//-------------------------------------------------------------- +// CG_GetTagWorldPosition +// +// Can pass in NULL for the axis +//-------------------------------------------------------------- +void CG_GetTagWorldPosition( refEntity_t *model, char *tag, vec3_t pos, vec3_t axis[3] ) +{ + orientation_t orientation; + int i = 0; + + // Get the requested tag + trap_R_LerpTag( &orientation, model->hModel, model->oldframe, model->frame, + 1.0f - model->backlerp, tag ); + + VectorCopy( model->origin, pos ); + for ( i = 0 ; i < 3 ; i++ ) + { + VectorMA( pos, orientation.origin[i], model->axis[i], pos ); + } + + if ( axis ) + { + MatrixMultiply( orientation.axis, model->axis, axis ); + } +} + +#define MAX_MARK_FRAGMENTS 128 +#define MAX_MARK_POINTS 384 +extern markPoly_t *CG_AllocMark(); + +void CG_CreateSaberMarks( vec3_t start, vec3_t end, vec3_t normal ) +{ +// byte colors[4]; + int i, j; + int numFragments; + vec3_t axis[3], originalPoints[4], mid; + vec3_t markPoints[MAX_MARK_POINTS], projection; + polyVert_t *v, verts[MAX_VERTS_ON_POLY]; + markPoly_t *mark; + markFragment_t markFragments[MAX_MARK_FRAGMENTS], *mf; + + float radius = 0.65f; + + if ( !cg_addMarks.integer ) + { + return; + } + + VectorSubtract( end, start, axis[1] ); + VectorNormalize( axis[1] ); + + // create the texture axis + VectorCopy( normal, axis[0] ); + CrossProduct( axis[1], axis[0], axis[2] ); + + // create the full polygon that we'll project + for ( i = 0 ; i < 3 ; i++ ) + { // stretch a bit more in the direction that we are traveling in... debateable as to whether this makes things better or worse + originalPoints[0][i] = start[i] - radius * axis[1][i] - radius * axis[2][i]; + originalPoints[1][i] = end[i] + radius * axis[1][i] - radius * axis[2][i]; + originalPoints[2][i] = end[i] + radius * axis[1][i] + radius * axis[2][i]; + originalPoints[3][i] = start[i] - radius * axis[1][i] + radius * axis[2][i]; + } + + VectorScale( normal, -1, projection ); + + // get the fragments + numFragments = trap_CM_MarkFragments( 4, (const float (*)[3])originalPoints, + projection, MAX_MARK_POINTS, markPoints[0], MAX_MARK_FRAGMENTS, markFragments ); + + for ( i = 0, mf = markFragments ; i < numFragments ; i++, mf++ ) + { + // 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; + + // Set up our texture coords, this may need some work + VectorCopy( markPoints[mf->firstPoint + j], v->xyz ); + VectorAdd( end, start, mid ); + VectorScale( mid, 0.5f, mid ); + VectorSubtract( v->xyz, mid, delta ); + + v->st[0] = 0.5 + DotProduct( delta, axis[1] ) * (0.05f + random() * 0.03f); + v->st[1] = 0.5 + DotProduct( delta, axis[2] ) * (0.15f + random() * 0.05f); + } + + if (cg_saberDynamicMarks.integer) + { + int i = 0; + int i_2 = 0; + addpolyArgStruct_t apArgs; + vec3_t x; + + memset (&apArgs, 0, sizeof(apArgs)); + + while (i < 4) + { + while (i_2 < 3) + { + apArgs.p[i][i_2] = verts[i].xyz[i_2]; + + i_2++; + } + + i_2 = 0; + i++; + } + + i = 0; + i_2 = 0; + + while (i < 4) + { + while (i_2 < 2) + { + apArgs.ev[i][i_2] = verts[i].st[i_2]; + + i_2++; + } + + i_2 = 0; + i++; + } + + //When using addpoly, having a situation like this tends to cause bad results. + //(I assume it doesn't like trying to draw a polygon over two planes and extends + //the vertex out to some odd value) + VectorSubtract(apArgs.p[0], apArgs.p[3], x); + if (VectorLength(x) > 3.0f) + { + return; + } + + apArgs.numVerts = mf->numPoints; + VectorCopy(vec3_origin, apArgs.vel); + VectorCopy(vec3_origin, apArgs.accel); + + apArgs.alpha1 = 1.0f; + apArgs.alpha2 = 0.0f; + apArgs.alphaParm = 255.0f; + + VectorSet(apArgs.rgb1, 0.0f, 0.0f, 0.0f); + VectorSet(apArgs.rgb2, 0.0f, 0.0f, 0.0f); + + apArgs.rgbParm = 0.0f; + + apArgs.bounce = 0; + apArgs.motionDelay = 0; + apArgs.killTime = cg_saberDynamicMarkTime.integer; + apArgs.shader = cgs.media.rivetMarkShader; + apArgs.flags = 0x08000000|0x00000004; + + trap_FX_AddPoly(&apArgs); + + apArgs.shader = cgs.media.mSaberDamageGlow; + apArgs.rgb1[0] = 215 + random() * 40.0f; + apArgs.rgb1[1] = 96 + random() * 32.0f; + apArgs.rgb1[2] = apArgs.alphaParm = random()*15.0f; + + apArgs.rgb1[0] /= 255; + apArgs.rgb1[1] /= 255; + apArgs.rgb1[2] /= 255; + VectorCopy(apArgs.rgb1, apArgs.rgb2); + + apArgs.killTime = 100; + + trap_FX_AddPoly(&apArgs); + } + else + { + // save it persistantly, do burn first + mark = CG_AllocMark(); + mark->time = cg.time; + mark->alphaFade = qtrue; + mark->markShader = cgs.media.rivetMarkShader; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = mark->color[1] = mark->color[2] = mark->color[3] = 255; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + + // And now do a glow pass + // by moving the start time back, we can hack it to fade out way before the burn does + mark = CG_AllocMark(); + mark->time = cg.time - 8500; + mark->alphaFade = qfalse; + mark->markShader = cgs.media.mSaberDamageGlow; + mark->poly.numVerts = mf->numPoints; + mark->color[0] = 215 + random() * 40.0f; + mark->color[1] = 96 + random() * 32.0f; + mark->color[2] = mark->color[3] = random()*15.0f; + memcpy( mark->verts, verts, mf->numPoints * sizeof( verts[0] ) ); + } + } +} + +qboolean CG_G2TraceCollide(trace_t *tr, vec3_t const mins, vec3_t const maxs, const vec3_t lastValidStart, const vec3_t lastValidEnd) +{ + G2Trace_t G2Trace; + centity_t *g2Hit; + vec3_t angles; + int tN = 0; + float fRadius = 0.0f; + + if (mins && maxs && + (mins[0] || maxs[0])) + { + fRadius=(maxs[0]-mins[0])/2.0f; + } + + memset (&G2Trace, 0, sizeof(G2Trace)); + + while (tN < MAX_G2_COLLISIONS) + { + G2Trace[tN].mEntityNum = -1; + tN++; + } + g2Hit = &cg_entities[tr->entityNum]; + + if (g2Hit && g2Hit->ghoul2) + { + angles[ROLL] = angles[PITCH] = 0; + angles[YAW] = g2Hit->lerpAngles[YAW]; + + if (cg_optvehtrace.integer && + g2Hit->currentState.eType == ET_NPC && + g2Hit->currentState.NPC_class == CLASS_VEHICLE && + g2Hit->m_pVehicle) + { + trap_G2API_CollisionDetectCache ( G2Trace, g2Hit->ghoul2, angles, g2Hit->lerpOrigin, cg.time, g2Hit->currentState.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, cg_g2TraceLod.integer, fRadius ); + } + else + { + trap_G2API_CollisionDetect ( G2Trace, g2Hit->ghoul2, angles, g2Hit->lerpOrigin, cg.time, g2Hit->currentState.number, lastValidStart, lastValidEnd, g2Hit->modelScale, 0, cg_g2TraceLod.integer, fRadius ); + } + + if (G2Trace[0].mEntityNum != g2Hit->currentState.number) + { + tr->fraction = 1.0f; + tr->entityNum = ENTITYNUM_NONE; + tr->startsolid = 0; + tr->allsolid = 0; + return qfalse; + } + else + { //Yay! + VectorCopy(G2Trace[0].mCollisionPosition, tr->endpos); + VectorCopy(G2Trace[0].mCollisionNormal, tr->plane.normal); + return qtrue; + } + } + + return qfalse; +} + +void CG_G2SaberEffects(vec3_t start, vec3_t end, centity_t *owner) +{ + trace_t trace; + vec3_t startTr; + vec3_t endTr; + qboolean backWards = qfalse; + qboolean doneWithTraces = qfalse; + + while (!doneWithTraces) + { + if (!backWards) + { + VectorCopy(start, startTr); + VectorCopy(end, endTr); + } + else + { + VectorCopy(end, startTr); + VectorCopy(start, endTr); + } + + CG_Trace( &trace, startTr, NULL, NULL, endTr, owner->currentState.number, MASK_PLAYERSOLID ); + + if (trace.entityNum < MAX_CLIENTS) + { //hit a client.. + CG_G2TraceCollide(&trace, NULL, NULL, startTr, endTr); + + if (trace.entityNum != ENTITYNUM_NONE) + { //it succeeded with the ghoul2 trace + trap_FX_PlayEffectID( cgs.effects.mSaberBloodSparks, trace.endpos, trace.plane.normal, -1, -1 ); + trap_S_StartSound(trace.endpos, trace.entityNum, CHAN_AUTO, trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3)))); + } + } + + if (!backWards) + { + backWards = qtrue; + } + else + { + doneWithTraces = qtrue; + } + } +} + +#define CG_MAX_SABER_COMP_TIME 400 //last registered saber entity hit must match within this many ms for the client effect to take place. + +void CG_AddGhoul2Mark(int shader, float size, vec3_t start, vec3_t end, int entnum, + vec3_t entposition, float entangle, void *ghoul2, vec3_t scale, int lifeTime) +{ + SSkinGoreData goreSkin; + + assert(ghoul2); + + memset ( &goreSkin, 0, sizeof(goreSkin) ); + + if (trap_G2API_GetNumGoreMarks(ghoul2, 0) >= cg_ghoul2Marks.integer) + { //you've got too many marks already + return; + } + + goreSkin.growDuration = -1; // default expandy time + goreSkin.goreScaleStartFraction = 1.0; // default start scale + goreSkin.frontFaces = qtrue; + goreSkin.backFaces = qtrue; + goreSkin.lifeTime = lifeTime; //last randomly 10-20 seconds + /* + if (lifeTime) + { + goreSkin.fadeOutTime = lifeTime*0.1; //default fade duration is relative to lifetime. + } + goreSkin.fadeRGB = qtrue; //fade on RGB instead of alpha (this depends on the shader really, modify if needed) + */ + //rwwFIXMEFIXME: fade has sorting issues with other non-fading decals, disabled until fixed + + goreSkin.baseModelOnly = qfalse; + + goreSkin.currentTime = cg.time; + goreSkin.entNum = entnum; + goreSkin.SSize = size; + goreSkin.TSize = size; + goreSkin.theta = flrand(0.0f,6.28f); + goreSkin.shader = shader; + + if (!scale[0] && !scale[1] && !scale[2]) + { + VectorSet(goreSkin.scale, 1.0f, 1.0f, 1.0f); + } + else + { + VectorCopy(goreSkin.scale, scale); + } + + VectorCopy (start, goreSkin.hitLocation); + + VectorSubtract(end, start, goreSkin.rayDirection); + if (VectorNormalize(goreSkin.rayDirection)<.1f) + { + return; + } + + VectorCopy ( entposition, goreSkin.position ); + goreSkin.angles[YAW] = entangle; + + trap_G2API_AddSkinGore(ghoul2, &goreSkin); +} + +void CG_SaberCompWork(vec3_t start, vec3_t end, centity_t *owner, int saberNum, int bladeNum) +{ + trace_t trace; + vec3_t startTr; + vec3_t endTr; + qboolean backWards = qfalse; + qboolean doneWithTraces = qfalse; + qboolean doEffect = qfalse; + clientInfo_t *client = NULL; + + if ((cg.time - owner->serverSaberHitTime) > CG_MAX_SABER_COMP_TIME) + { + return; + } + + if (cg.time == owner->serverSaberHitTime) + { //don't want to do it the same frame as the server hit, to avoid burst effect concentrations every x ms. + return; + } + + while (!doneWithTraces) + { + if (!backWards) + { + VectorCopy(start, startTr); + VectorCopy(end, endTr); + } + else + { + VectorCopy(end, startTr); + VectorCopy(start, endTr); + } + + CG_Trace( &trace, startTr, NULL, NULL, endTr, owner->currentState.number, MASK_PLAYERSOLID ); + + if (trace.entityNum == owner->serverSaberHitIndex) + { //this is the guy the server says we last hit, so continue. + if (cg_entities[trace.entityNum].ghoul2) + { //If it has a g2 instance, do the proper ghoul2 checks + CG_G2TraceCollide(&trace, NULL, NULL, startTr, endTr); + + if (trace.entityNum != ENTITYNUM_NONE) + { //it succeeded with the ghoul2 trace + doEffect = qtrue; + + if (cg_ghoul2Marks.integer) + { + vec3_t ePos; + centity_t *trEnt = &cg_entities[trace.entityNum]; + + if (trEnt->ghoul2) + { + if (trEnt->currentState.eType != ET_NPC || + trEnt->currentState.NPC_class != CLASS_VEHICLE || + !trEnt->m_pVehicle || + trEnt->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) + { //don't do on fighters cause they have crazy full axial angles + int weaponMarkShader = 0, markShader = cgs.media.bdecal_saberglow; + + VectorSubtract(endTr, trace.endpos, ePos); + VectorNormalize(ePos); + VectorMA(trace.endpos, 4.0f, ePos, ePos); + + if (owner->currentState.eType == ET_NPC) + { + client = owner->npcClient; + } + else + { + client = &cgs.clientinfo[owner->currentState.clientNum]; + } + if ( client + && client->infoValid ) + { + if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) ) + { + if ( client->saber[saberNum].g2MarksShader2 ) + {//we have a shader to use instead of the standard mark shader + markShader = client->saber[saberNum].g2MarksShader2; + } + if ( client->saber[saberNum].g2WeaponMarkShader2 ) + {//we have a shader to use as a splashback onto the weapon model + weaponMarkShader = client->saber[saberNum].g2WeaponMarkShader2; + } + } + else + { + if ( client->saber[saberNum].g2MarksShader ) + {//we have a shader to use instead of the standard mark shader + markShader = client->saber[saberNum].g2MarksShader; + } + if ( client->saber[saberNum].g2WeaponMarkShader ) + {//we have a shader to use as a splashback onto the weapon model + weaponMarkShader = client->saber[saberNum].g2WeaponMarkShader; + } + } + } + CG_AddGhoul2Mark(markShader, flrand(3.0f, 4.0f), + trace.endpos, ePos, trace.entityNum, trEnt->lerpOrigin, trEnt->lerpAngles[YAW], + trEnt->ghoul2, trEnt->modelScale, Q_irand(5000, 10000)); + if ( weaponMarkShader ) + { + vec3_t splashBackDir; + VectorScale( ePos, -1 , splashBackDir ); + CG_AddGhoul2Mark(weaponMarkShader, flrand(0.5f, 2.0f), + trace.endpos, splashBackDir, owner->currentState.clientNum, owner->lerpOrigin, owner->lerpAngles[YAW], + owner->ghoul2, owner->modelScale, Q_irand(5000, 10000)); + } + } + } + } + } + } + else + { //otherwise, we're all set. + doEffect = qtrue; + } + + if (doEffect) + { + int hitPersonFxID = cgs.effects.mSaberBloodSparks; + int hitOtherFxID = cgs.effects.mSaberCut; + + if (owner->currentState.eType == ET_NPC) + { + client = owner->npcClient; + } + else + { + client = &cgs.clientinfo[owner->currentState.clientNum]; + } + if ( client && client->infoValid ) + { + if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) ) + {//use second blade style values + if ( client->saber[saberNum].hitPersonEffect2 ) + { + hitPersonFxID = client->saber[saberNum].hitPersonEffect2; + } + if ( client->saber[saberNum].hitOtherEffect2 ) + {//custom hit other effect + hitOtherFxID = client->saber[saberNum].hitOtherEffect2; + } + } + else + {//use first blade style values + if ( client->saber[saberNum].hitPersonEffect ) + { + hitPersonFxID = client->saber[saberNum].hitPersonEffect; + } + if ( client->saber[saberNum].hitOtherEffect ) + {//custom hit other effect + hitOtherFxID = client->saber[saberNum].hitOtherEffect; + } + } + } + if (!trace.plane.normal[0] && !trace.plane.normal[1] && !trace.plane.normal[2]) + { //who cares, just shoot it somewhere. + trace.plane.normal[1] = 1; + } + + if (owner->serverSaberFleshImpact) + { //do standard player/live ent hit sparks + trap_FX_PlayEffectID( hitPersonFxID, trace.endpos, trace.plane.normal, -1, -1 ); + //trap_S_StartSound(trace.endpos, trace.entityNum, CHAN_AUTO, trap_S_RegisterSound(va("sound/weapons/saber/saberhit%i.wav", Q_irand(1, 3)))); + } + else + { //do the cut effect + trap_FX_PlayEffectID( hitOtherFxID, trace.endpos, trace.plane.normal, -1, -1 ); + } + doEffect = qfalse; + } + } + + /* + if (!backWards) + { + backWards = qtrue; + } + else + { + doneWithTraces = qtrue; + } + */ + doneWithTraces = qtrue; //disabling backwards tr for now, sometimes it just makes too many effects. + } +} + +#define SABER_TRAIL_TIME 40.0f +#define FX_USE_ALPHA 0x08000000 + +#include "../namespace_begin.h" +qboolean BG_SuperBreakWinAnim( int anim ); +#include "../namespace_end.h" + +void CG_AddSaberBlade( centity_t *cent, centity_t *scent, refEntity_t *saber, int renderfx, int modelIndex, int saberNum, int bladeNum, vec3_t origin, vec3_t angles, qboolean fromSaber, qboolean dontDraw) +{ + vec3_t org_, end, v, + axis_[3] = {0,0,0, 0,0,0, 0,0,0}; // shut the compiler up + trace_t trace; + int i = 0; + int trailDur; + float saberLen; + float diff; + clientInfo_t *client; + centity_t *saberEnt; + saberTrail_t *saberTrail; + mdxaBone_t boltMatrix; + vec3_t futureAngles; + effectTrailArgStruct_t fx; + int scolor = 0; + int useModelIndex = 0; + + if (cent->currentState.eType == ET_NPC) + { + client = cent->npcClient; + assert(client); + } + else + { + client = &cgs.clientinfo[cent->currentState.number]; + } + + saberEnt = &cg_entities[cent->currentState.saberEntityNum]; + saberLen = client->saber[saberNum].blade[bladeNum].length; + + if (saberLen <= 0 && !dontDraw) + { //don't bother then. + return; + } + + futureAngles[YAW] = angles[YAW]; + futureAngles[PITCH] = angles[PITCH]; + futureAngles[ROLL] = angles[ROLL]; + + + if ( fromSaber ) + { + useModelIndex = 0; + } + else + { + useModelIndex = saberNum+1; + } + //Assume bladeNum is equal to the bolt index because bolts should be added in order of the blades. + //if there is an effect on this blade, play it + if ( !WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) + && client->saber[saberNum].bladeEffect ) + { + trap_FX_PlayBoltedEffectID(client->saber[saberNum].bladeEffect, scent->lerpOrigin, + scent->ghoul2, bladeNum, scent->currentState.number, useModelIndex, -1, qfalse); + } + else if ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) + && client->saber[saberNum].bladeEffect2 ) + { + trap_FX_PlayBoltedEffectID(client->saber[saberNum].bladeEffect2, scent->lerpOrigin, + scent->ghoul2, bladeNum, scent->currentState.number, useModelIndex, -1, qfalse); + } + //get the boltMatrix + trap_G2API_GetBoltMatrix(scent->ghoul2, useModelIndex, bladeNum, &boltMatrix, futureAngles, origin, cg.time, cgs.gameModels, scent->modelScale); + + // work the matrix axis stuff into the original axis and origins used. + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, org_); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, axis_[0]); + + if (!fromSaber && saberEnt && !cent->currentState.saberInFlight) + { + VectorCopy(org_, saberEnt->currentState.pos.trBase); + + VectorCopy(axis_[0], saberEnt->currentState.apos.trBase); + } + + VectorMA( org_, saberLen, axis_[0], end ); + + VectorAdd( end, axis_[0], end ); + + if (cent->currentState.eType == ET_NPC) + { + scolor = client->saber[saberNum].blade[bladeNum].color; + } + else + { + if (saberNum == 0) + { + scolor = client->icolor1; + } + else + { + scolor = client->icolor2; + } + } + + if (cgs.gametype >= GT_TEAM && + cgs.gametype != GT_SIEGE && + !cgs.jediVmerc && + cent->currentState.eType != ET_NPC) + { + if (client->team == TEAM_RED) + { + scolor = SABER_RED; + } + else if (client->team == TEAM_BLUE) + { + scolor = SABER_BLUE; + } + } + + if (!cg_saberContact.integer) + { //if we don't have saber contact enabled, just add the blade and don't care what it's touching + goto CheckTrail; + } + + if (!dontDraw) + { + if (cg_saberModelTraceEffect.integer) + { + CG_G2SaberEffects(org_, end, cent); + } + else if (cg_saberClientVisualCompensation.integer) + { + CG_Trace( &trace, org_, NULL, NULL, end, ENTITYNUM_NONE, MASK_SOLID ); + + if (trace.fraction != 1) + { //nudge the endpos a very small amount from the beginning to the end, so the comp trace hits at the end. + //I'm only bothering with this because I want to do a backwards trace too in the comp trace, so if the + //blade is sticking through a player or something the standard trace doesn't it, it will make sparks + //on each side. + vec3_t seDif; + + VectorSubtract(trace.endpos, org_, seDif); + VectorNormalize(seDif); + trace.endpos[0] += seDif[0]*0.1f; + trace.endpos[1] += seDif[1]*0.1f; + trace.endpos[2] += seDif[2]*0.1f; + } + + if (client->saber[saberNum].blade[bladeNum].storageTime < cg.time) + { //debounce it in case our framerate is absurdly high. Using storageTime since it's not used for anything else in the client. + CG_SaberCompWork(org_, trace.endpos, cent, saberNum, bladeNum); + + client->saber[saberNum].blade[bladeNum].storageTime = cg.time + 5; + } + } + + for ( i = 0; i < 1; i++ )//was 2 because it would go through architecture and leave saber trails on either side of the brush - but still looks bad if we hit a corner, blade is still 8 longer than hit + { + if ( i ) + {//tracing from end to base + CG_Trace( &trace, end, NULL, NULL, org_, ENTITYNUM_NONE, MASK_SOLID ); + } + else + {//tracing from base to end + CG_Trace( &trace, org_, NULL, NULL, end, ENTITYNUM_NONE, MASK_SOLID ); + } + + if ( trace.fraction < 1.0f ) + { + vec3_t trDir; + VectorCopy(trace.plane.normal, trDir); + if (!trDir[0] && !trDir[1] && !trDir[2]) + { + trDir[1] = 1; + } + + if ( (client->saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS) ) + {//don't actually draw the marks/impact effects + } + else + { + if (!(trace.surfaceFlags & SURF_NOIMPACT) ) // never spark on sky + { + trap_FX_PlayEffectID( cgs.effects.mSparks, trace.endpos, trDir, -1, -1 ); + } + } + + //Stop saber? (it wouldn't look right if it was stuck through a thin wall and unable to hurt players on the other side) + VectorSubtract(org_, trace.endpos, v); + saberLen = VectorLength(v); + + VectorCopy(trace.endpos, end); + + if ( (client->saber[saberNum].saberFlags2&SFL2_NO_WALL_MARKS) ) + {//don't actually draw the marks + } + else + {//draw marks if we hit a wall + // All I need is a bool to mark whether I have a previous point to work with. + //....come up with something better.. + if ( client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] ) + { + if ( trace.entityNum == ENTITYNUM_WORLD || cg_entities[trace.entityNum].currentState.eType == ET_TERRAIN || (cg_entities[trace.entityNum].currentState.eFlags & EF_PERMANENT) ) + {//only put marks on architecture + // Let's do some cool burn/glowing mark bits!!! + CG_CreateSaberMarks( client->saber[saberNum].blade[bladeNum].trail.oldPos[i], trace.endpos, trace.plane.normal ); + + //make a sound + if ( cg.time - client->saber[saberNum].blade[bladeNum].hitWallDebounceTime >= 100 ) + {//ugh, need to have a real sound debouncer... or do this game-side + client->saber[saberNum].blade[bladeNum].hitWallDebounceTime = cg.time; + trap_S_StartSound ( trace.endpos, -1, CHAN_WEAPON, trap_S_RegisterSound( va("sound/weapons/saber/saberhitwall%i", Q_irand(1, 3)) ) ); + } + } + } + else + { + // if we impact next frame, we'll mark a slash mark + client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] = qtrue; + // CG_ImpactMark( cgs.media.rivetMarkShader, client->saber[saberNum].blade[bladeNum].trail.oldPos[i], client->saber[saberNum].blade[bladeNum].trail.oldNormal[i], + // 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse ); + } + } + + // stash point so we can connect-the-dots later + VectorCopy( trace.endpos, client->saber[saberNum].blade[bladeNum].trail.oldPos[i] ); + VectorCopy( trace.plane.normal, client->saber[saberNum].blade[bladeNum].trail.oldNormal[i] ); + } + else + { + if ( client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] ) + { + // Hmmm, no impact this frame, but we have an old point + // Let's put the mark there, we should use an endcap mark to close the line, but we + // can probably just get away with a round mark + // CG_ImpactMark( cgs.media.rivetMarkShader, client->saber[saberNum].blade[bladeNum].trail.oldPos[i], client->saber[saberNum].blade[bladeNum].trail.oldNormal[i], + // 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, qfalse, 1.1f, qfalse ); + } + + // we aren't impacting, so turn off our mark tracking mechanism + client->saber[saberNum].blade[bladeNum].trail.haveOldPos[i] = qfalse; + } + } + } +CheckTrail: + + if (!cg_saberTrail.integer) + { //don't do the trail in this case + goto JustDoIt; + } + + if ( (!WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle > 1 ) + || ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle2 > 1 ) ) + {//don't actually draw the trail at all + goto JustDoIt; + } + + //FIXME: if trailStyle is 1, use the motion blur instead + + saberTrail = &client->saber[saberNum].blade[bladeNum].trail; + saberTrail->duration = saberMoveData[cent->currentState.saberMove].trailLength; + + trailDur = (saberTrail->duration/5.0f); + if (!trailDur) + { //hmm.. ok, default + if ( BG_SuperBreakWinAnim(cent->currentState.torsoAnim) ) + { + trailDur = 150; + } + else + { + trailDur = SABER_TRAIL_TIME; + } + } + + // if we happen to be timescaled or running in a high framerate situation, we don't want to flood + // the system with very small trail slices...but perhaps doing it by distance would yield better results? + if ( cg.time > saberTrail->lastTime + 2 || cg_saberTrail.integer == 2 ) // 2ms + { + if (!dontDraw) + { + if ( (BG_SuperBreakWinAnim(cent->currentState.torsoAnim) || saberMoveData[cent->currentState.saberMove].trailLength > 0 || ((cent->currentState.powerups & (1 << PW_SPEED) && cg_speedTrail.integer)) || (cent->currentState.saberInFlight && saberNum == 0)) && cg.time < saberTrail->lastTime + 2000 ) // if we have a stale segment, don't draw until we have a fresh one + { + #if 0 + if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) + { + polyVert_t verts[4]; + + VectorCopy( org_, verts[0].xyz ); + VectorMA( end, 3.0f, axis_[0], verts[1].xyz ); + VectorCopy( saberTrail->tip, verts[2].xyz ); + VectorCopy( saberTrail->base, verts[3].xyz ); + + //tc doesn't even matter since we're just gonna stencil an outline, but whatever. + 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; + + 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; + + 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; + + 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; + + //don't capture postrender objects (now we'll postrender the saber so it doesn't get in the capture) + trap_R_SetRefractProp(1.0f, 0.0f, qtrue, qtrue); + + //shader 2 is always the crazy refractive shader. + trap_R_AddPolyToScene( 2, 4, verts ); + } + else + #endif + { + vec3_t rgb1={255.0f,255.0f,255.0f}; + + switch( scolor ) + { + case SABER_RED: + VectorSet( rgb1, 255.0f, 0.0f, 0.0f ); + break; + case SABER_ORANGE: + VectorSet( rgb1, 255.0f, 64.0f, 0.0f ); + break; + case SABER_YELLOW: + VectorSet( rgb1, 255.0f, 255.0f, 0.0f ); + break; + case SABER_GREEN: + VectorSet( rgb1, 0.0f, 255.0f, 0.0f ); + break; + case SABER_BLUE: + VectorSet( rgb1, 0.0f, 64.0f, 255.0f ); + break; + case SABER_PURPLE: + VectorSet( rgb1, 220.0f, 0.0f, 255.0f ); + break; + default: + VectorSet( rgb1, 0.0f, 64.0f, 255.0f ); + break; + } + + //Here we will use the happy process of filling a struct in with arguments and passing it to a trap function + //so that we can take the struct and fill in an actual CTrail type using the data within it once we get it + //into the effects area + + // Go from new muzzle to new end...then to old end...back down to old muzzle...finally + // connect back to the new muzzle...this is our trail quad + VectorCopy( org_, fx.mVerts[0].origin ); + VectorMA( end, 3.0f, axis_[0], fx.mVerts[1].origin ); + + VectorCopy( saberTrail->tip, fx.mVerts[2].origin ); + VectorCopy( saberTrail->base, fx.mVerts[3].origin ); + + diff = cg.time - saberTrail->lastTime; + + // I'm not sure that clipping this is really the best idea + //This prevents the trail from showing at all in low framerate situations. + //if ( diff <= SABER_TRAIL_TIME * 2 ) + if ( diff <= 10000 ) + { //don't draw it if the last time is way out of date + float oldAlpha = 1.0f - ( diff / trailDur ); + + if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) + {//does other stuff below + } + else + { + if ( (!WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle == 1 ) + || ( WP_SaberBladeUseSecondBladeStyle( &client->saber[saberNum], bladeNum ) && client->saber[saberNum].trailStyle2 == 1 ) ) + {//motion trail + fx.mShader = cgs.media.swordTrailShader; + VectorSet( rgb1, 32.0f, 32.0f, 32.0f ); // make the sith sword trail pretty faint + trailDur *= 2.0f; // stay around twice as long? + } + else + { + fx.mShader = cgs.media.saberBlurShader; + } + fx.mKillTime = trailDur; + fx.mSetFlags = FX_USE_ALPHA; + } + + // New muzzle + VectorCopy( rgb1, fx.mVerts[0].rgb ); + fx.mVerts[0].alpha = 255.0f; + + fx.mVerts[0].ST[0] = 0.0f; + fx.mVerts[0].ST[1] = 1.0f; + fx.mVerts[0].destST[0] = 1.0f; + fx.mVerts[0].destST[1] = 1.0f; + + // new tip + VectorCopy( rgb1, fx.mVerts[1].rgb ); + fx.mVerts[1].alpha = 255.0f; + + fx.mVerts[1].ST[0] = 0.0f; + fx.mVerts[1].ST[1] = 0.0f; + fx.mVerts[1].destST[0] = 1.0f; + fx.mVerts[1].destST[1] = 0.0f; + + // old tip + VectorCopy( rgb1, fx.mVerts[2].rgb ); + fx.mVerts[2].alpha = 255.0f; + + fx.mVerts[2].ST[0] = 1.0f - oldAlpha; // NOTE: this just happens to contain the value I want + fx.mVerts[2].ST[1] = 0.0f; + fx.mVerts[2].destST[0] = 1.0f + fx.mVerts[2].ST[0]; + fx.mVerts[2].destST[1] = 0.0f; + + // old muzzle + VectorCopy( rgb1, fx.mVerts[3].rgb ); + fx.mVerts[3].alpha = 255.0f; + + fx.mVerts[3].ST[0] = 1.0f - oldAlpha; // NOTE: this just happens to contain the value I want + fx.mVerts[3].ST[1] = 1.0f; + fx.mVerts[3].destST[0] = 1.0f + fx.mVerts[2].ST[0]; + fx.mVerts[3].destST[1] = 1.0f; + + if (cg_saberTrail.integer == 2 && cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4) + { + trap_R_SetRefractProp(1.0f, 0.0f, qtrue, qtrue); //don't need to do this every frame.. but.. + + if (BG_SaberInAttack(cent->currentState.saberMove) + ||BG_SuperBreakWinAnim(cent->currentState.torsoAnim)) + { //in attack, strong trail + fx.mKillTime = 300; + } + else + { //faded trail + fx.mKillTime = 40; + } + fx.mShader = 2; //2 is always refractive shader + fx.mSetFlags = FX_USE_ALPHA; + } + /* + else + { + fx.mShader = cgs.media.saberBlurShader; + fx.mKillTime = trailDur; + fx.mSetFlags = FX_USE_ALPHA; + } + */ + + trap_FX_AddPrimitive(&fx); + } + } + } + } + + // we must always do this, even if we aren't active..otherwise we won't know where to pick up from + VectorCopy( org_, saberTrail->base ); + VectorMA( end, 3.0f, axis_[0], saberTrail->tip ); + saberTrail->lastTime = cg.time; + } + +JustDoIt: + + if (dontDraw) + { + return; + } + + if ( (client->saber[saberNum].saberFlags2&SFL2_NO_BLADE) ) + {//don't actually draw the blade at all + if ( client->saber[saberNum].numBlades < 3 + && !(client->saber[saberNum].saberFlags2&SFL2_NO_DLIGHT) ) + {//hmm, but still add the dlight + CG_DoSaberLight( &client->saber[saberNum] ); + } + return; + } + // Pass in the renderfx flags attached to the saber weapon model...this is done so that saber glows + // will get rendered properly in a mirror...not sure if this is necessary?? + //CG_DoSaber( org_, axis_[0], saberLen, client->saber[saberNum].blade[bladeNum].lengthMax, client->saber[saberNum].blade[bladeNum].radius, + // scolor, renderfx, (qboolean)(saberNum==0&&bladeNum==0) ); + CG_DoSaber( org_, axis_[0], saberLen, client->saber[saberNum].blade[bladeNum].lengthMax, client->saber[saberNum].blade[bladeNum].radius, + scolor, renderfx, (qboolean)(client->saber[saberNum].numBlades < 3 && !(client->saber[saberNum].saberFlags2&SFL2_NO_DLIGHT)) ); +} + +int CG_IsMindTricked(int trickIndex1, int trickIndex2, int trickIndex3, int trickIndex4, int client) +{ + int checkIn; + int sub = 0; + + if (cg_entities[client].currentState.forcePowersActive & (1 << FP_SEE)) + { + return 0; + } + + if (client > 47) + { + checkIn = trickIndex4; + sub = 48; + } + else if (client > 31) + { + checkIn = trickIndex3; + sub = 32; + } + else if (client > 15) + { + checkIn = trickIndex2; + sub = 16; + } + else + { + checkIn = trickIndex1; + } + + if (checkIn & (1 << (client-sub))) + { + return 1; + } + + return 0; +} + +#define SPEED_TRAIL_DISTANCE 6 + +void CG_DrawPlayerSphere(centity_t *cent, vec3_t origin, float scale, int shader) +{ + refEntity_t ent; + vec3_t ang; + float vLen; + vec3_t viewDir; + + // Don't draw the shield when the player is dead. + if (cent->currentState.eFlags & EF_DEAD) + { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( origin, ent.origin ); + ent.origin[2] += 9.0; + + VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (vLen <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + VectorCopy(ent.axis[0], viewDir); + VectorInverse(viewDir); + VectorNormalize(viewDir); + + vectoangles(ent.axis[0], ang); + ang[ROLL] += 180.0f; + ang[PITCH] += 180.0f; + AnglesToAxis(ang, ent.axis); + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], scale, ent.axis[2]); + + ent.nonNormalizedAxes = qtrue; + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = shader; + + trap_R_AddRefEntityToScene( &ent ); + + if (!cg.renderingThirdPerson && cent->currentState.number == cg.predictedPlayerState.clientNum) + { //don't do the rest then + return; + } + if (!cg_renderToTextureFX.integer) + { + return; + } + + ang[PITCH] -= 180.0f; + AnglesToAxis(ang, ent.axis); + + VectorScale(ent.axis[0], scale*0.5f, ent.axis[0]); + VectorScale(ent.axis[1], scale*0.5f, ent.axis[1]); + VectorScale(ent.axis[2], scale*0.5f, ent.axis[2]); + + ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); + if (shader == cgs.media.invulnerabilityShader) + { //ok, ok, this is a little hacky. sorry! + ent.shaderRGBA[0] = 0; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 0; + ent.shaderRGBA[3] = 100; + } + else if (shader == cgs.media.ysalimariShader) + { + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 0; + ent.shaderRGBA[3] = 100; + } + else if (shader == cgs.media.endarkenmentShader) + { + ent.shaderRGBA[0] = 100; + ent.shaderRGBA[1] = 0; + ent.shaderRGBA[2] = 0; + ent.shaderRGBA[3] = 20; + } + else if (shader == cgs.media.enlightenmentShader) + { + ent.shaderRGBA[0] = 255; + ent.shaderRGBA[1] = 255; + ent.shaderRGBA[2] = 255; + ent.shaderRGBA[3] = 20; + } + else + { //ysal red/blue, boon + ent.shaderRGBA[0] = 255.0f; + ent.shaderRGBA[1] = 255.0f; + ent.shaderRGBA[2] = 255.0f; + ent.shaderRGBA[3] = 20; + } + + ent.radius = 256; + + VectorMA(ent.origin, 40.0f, viewDir, ent.origin); + + ent.customShader = trap_R_RegisterShader("effects/refract_2"); + trap_R_AddRefEntityToScene( &ent ); +} + +void CG_AddLightningBeam(vec3_t start, vec3_t end) +{ + vec3_t dir, chaos, + c1, c2, + v1, v2; + float len, + s1, s2, s3; + + addbezierArgStruct_t b; + + VectorCopy(start, b.start); + VectorCopy(end, b.end); + + VectorSubtract( b.end, b.start, dir ); + len = VectorNormalize( dir ); + + // Get the base control points, we'll work from there + VectorMA( b.start, 0.3333f * len, dir, c1 ); + VectorMA( b.start, 0.6666f * len, dir, c2 ); + + // get some chaos values that really aren't very chaotic :) + s1 = sin( cg.time * 0.005f ) * 2 + crandom() * 0.2f; + s2 = sin( cg.time * 0.001f ); + s3 = sin( cg.time * 0.011f ); + + VectorSet( chaos, len * 0.01f * s1, + len * 0.02f * s2, + len * 0.04f * (s1 + s2 + s3)); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, -len * 0.02f * s3, + len * 0.01f * (s1 * s2), + -len * 0.02f * (s1 + s2 * s3)); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 2.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + VectorCopy(c1, b.control1); + VectorCopy(vec3_origin, b.control1Vel); + VectorCopy(c2, b.control2); + VectorCopy(vec3_origin, b.control2Vel); + + b.size1 = 6.0f; + b.size2 = 6.0f; + b.sizeParm = 0.0f; + b.alpha1 = 0.0f; + b.alpha2 = 0.2f; + b.alphaParm = 0.5f; + + /* + VectorCopy(WHITE, b.sRGB); + VectorCopy(WHITE, b.eRGB); + */ + + b.sRGB[0] = 255; + b.sRGB[1] = 255; + b.sRGB[2] = 255; + VectorCopy(b.sRGB, b.eRGB); + + b.rgbParm = 0.0f; + b.killTime = 50; + b.shader = trap_R_RegisterShader( "gfx/misc/electric2" ); + b.flags = 0x00000001; //FX_ALPHA_LINEAR + + trap_FX_AddBezier(&b); +} + +void CG_AddRandomLightning(vec3_t start, vec3_t end) +{ + vec3_t inOrg, outOrg; + + VectorCopy(start, inOrg); + VectorCopy(end, outOrg); + + if ( rand() & 1 ) + { + outOrg[0] += Q_irand(0, 24); + inOrg[0] += Q_irand(0, 8); + } + else + { + outOrg[0] -= Q_irand(0, 24); + inOrg[0] -= Q_irand(0, 8); + } + + if ( rand() & 1 ) + { + outOrg[1] += Q_irand(0, 24); + inOrg[1] += Q_irand(0, 8); + } + else + { + outOrg[1] -= Q_irand(0, 24); + inOrg[1] -= Q_irand(0, 8); + } + + if ( rand() & 1 ) + { + outOrg[2] += Q_irand(0, 50); + inOrg[2] += Q_irand(0, 40); + } + else + { + outOrg[2] -= Q_irand(0, 64); + inOrg[2] -= Q_irand(0, 40); + } + + CG_AddLightningBeam(inOrg, outOrg); +} + +extern char *forceHolocronModels[]; + +qboolean CG_ThereIsAMaster(void) +{ + int i = 0; + centity_t *cent; + + while (i < MAX_CLIENTS) + { + cent = &cg_entities[i]; + + if (cent && cent->currentState.isJediMaster) + { + return qtrue; + } + + i++; + } + + return qfalse; +} + +#if 0 +void CG_DrawNoForceSphere(centity_t *cent, vec3_t origin, float scale, int shader) +{ + refEntity_t ent; + + // Don't draw the shield when the player is dead. + if (cent->currentState.eFlags & EF_DEAD) + { + return; + } + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( origin, ent.origin ); + ent.origin[2] += 9.0; + + VectorSubtract(cg.refdef.vieworg, ent.origin, ent.axis[0]); + if (VectorNormalize(ent.axis[0]) <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + VectorCopy(cg.refdef.viewaxis[2], ent.axis[2]); + CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], -scale, ent.axis[2]); + + ent.shaderRGBA[3] = (cent->currentState.genericenemyindex - cg.time)/8; + ent.renderfx |= RF_RGB_TINT; + if (ent.shaderRGBA[3] > 200) + { + ent.shaderRGBA[3] = 200; + } + if (ent.shaderRGBA[3] < 1) + { + ent.shaderRGBA[3] = 1; + } + + ent.shaderRGBA[2] = 0; + ent.shaderRGBA[0] = ent.shaderRGBA[1] = ent.shaderRGBA[3]; + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = shader; + + trap_R_AddRefEntityToScene( &ent ); +} +#endif + +//Checks to see if the model string has a * appended with a custom skin name after. +//If so, it terminates the model string correctly, parses the skin name out, and returns +//the handle of the registered skin. +int CG_HandleAppendedSkin(char *modelName) +{ + char skinName[MAX_QPATH]; + char *p; + qhandle_t skinID = 0; + int i = 0; + + //see if it has a skin name + p = Q_strrchr(modelName, '*'); + + if (p) + { //found a *, we should have a model name before it and a skin name after it. + *p = 0; //terminate the modelName string at this point, then go ahead and parse to the next 0 for the skin. + p++; + + while (p && *p) + { + skinName[i] = *p; + i++; + p++; + } + skinName[i] = 0; + + if (skinName[0]) + { //got it, register the skin under the model path. + char baseFolder[MAX_QPATH]; + + strcpy(baseFolder, modelName); + p = Q_strrchr(baseFolder, '/'); //go back to the first /, should be the path point + + if (p) + { //got it.. terminate at the slash and register. + char *useSkinName; + + *p = 0; + + if (strchr(skinName, '|')) + {//three part skin + useSkinName = va("%s/|%s", baseFolder, skinName); + } + else + { + useSkinName = va("%s/model_%s.skin", baseFolder, skinName); + } + + skinID = trap_R_RegisterSkin(useSkinName); + } + } + } + + return skinID; +} + +//Create a temporary ghoul2 instance and get the gla name so we can try loading animation data and sounds. +#include "../namespace_begin.h" +void BG_GetVehicleModelName(char *modelname); +void BG_GetVehicleSkinName(char *skinname); +#include "../namespace_end.h" + +void CG_CacheG2AnimInfo(char *modelName) +{ + void *g2 = NULL; + char *slash; + char useModel[MAX_QPATH]; + char useSkin[MAX_QPATH]; + int animIndex; + + strcpy(useModel, modelName); + strcpy(useSkin, modelName); + + if (modelName[0] == '$') + { //it's a vehicle name actually, let's precache the whole vehicle + BG_GetVehicleModelName(useModel); + BG_GetVehicleSkinName(useSkin); + if ( useSkin[0] ) + { //use a custom skin + trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", useModel, useSkin)); + } + else + { + trap_R_RegisterSkin(va("models/players/%s/model_default.skin", useModel)); + } + strcpy(useModel, va("models/players/%s/model.glm", useModel)); + } + + trap_G2API_InitGhoul2Model(&g2, useModel, 0, 0, 0, 0, 0); + + if (g2) + { + char GLAName[MAX_QPATH]; + char originalModelName[MAX_QPATH]; + + animIndex = -1; + + GLAName[0] = 0; + trap_G2API_GetGLAName(g2, 0, GLAName); + + strcpy(originalModelName, useModel); + + slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + animIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + + if (animIndex != -1) + { + slash = Q_strrchr( originalModelName, '/' ); + if ( slash ) + { + slash++; + *slash = 0; + } + + BG_ParseAnimationEvtFile(originalModelName, animIndex, bgNumAnimEvents); + } + + //Now free the temp instance + trap_G2API_CleanGhoul2Models(&g2); + } +} + +static void CG_RegisterVehicleAssets( Vehicle_t *pVeh ) +{ + /* + if ( pVeh->m_pVehicleInfo->exhaustFX ) + { + pVeh->m_pVehicleInfo->iExhaustFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->exhaustFX ); + } + if ( pVeh->m_pVehicleInfo->trailFX ) + { + pVeh->m_pVehicleInfo->iTrailFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->trailFX ); + } + if ( pVeh->m_pVehicleInfo->impactFX ) + { + pVeh->m_pVehicleInfo->iImpactFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->impactFX ); + } + if ( pVeh->m_pVehicleInfo->explodeFX ) + { + pVeh->m_pVehicleInfo->iExplodeFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->explodeFX ); + } + if ( pVeh->m_pVehicleInfo->wakeFX ) + { + pVeh->m_pVehicleInfo->iWakeFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wakeFX ); + } + + if ( pVeh->m_pVehicleInfo->dmgFX ) + { + pVeh->m_pVehicleInfo->iDmgFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->dmgFX ); + } + if ( pVeh->m_pVehicleInfo->wpn1FX ) + { + pVeh->m_pVehicleInfo->iWpn1FX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn1FX ); + } + if ( pVeh->m_pVehicleInfo->wpn2FX ) + { + pVeh->m_pVehicleInfo->iWpn2FX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn2FX ); + } + if ( pVeh->m_pVehicleInfo->wpn1FireFX ) + { + pVeh->m_pVehicleInfo->iWpn1FireFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn1FireFX ); + } + if ( pVeh->m_pVehicleInfo->wpn2FireFX ) + { + pVeh->m_pVehicleInfo->iWpn2FireFX = trap_FX_RegisterEffect( pVeh->m_pVehicleInfo->wpn2FireFX ); + } + */ +} + +extern void CG_HandleNPCSounds(centity_t *cent); + +#include "../namespace_begin.h" +extern void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strAnimalType ); +extern void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType ); +extern void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strAnimalType ); +extern void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType ); +#include "../namespace_end.h" + +extern playerState_t *cgSendPS[MAX_GENTITIES]; +void CG_G2AnimEntModelLoad(centity_t *cent) +{ + const char *cModelName = CG_ConfigString( CS_MODELS+cent->currentState.modelindex ); + + if (!cent->npcClient) + { //have not init'd client yet + return; + } + + if (cModelName && cModelName[0]) + { + char modelName[MAX_QPATH]; + int skinID; + char *slash; + + strcpy(modelName, cModelName); + + if (cent->currentState.NPC_class == CLASS_VEHICLE && modelName[0] == '$') + { //vehicles pass their veh names over as model names, then we get the model name from the veh type + //create a vehicle object clientside for this type + char *vehType = &modelName[1]; + int iVehIndex = BG_VehicleGetIndex( vehType ); + + switch( g_vehicleInfo[iVehIndex].type ) + { + case VH_ANIMAL: + // Create the animal (making sure all it's data is initialized). + G_CreateAnimalNPC( ¢->m_pVehicle, vehType ); + break; + case VH_SPEEDER: + // Create the speeder (making sure all it's data is initialized). + G_CreateSpeederNPC( ¢->m_pVehicle, vehType ); + break; + case VH_FIGHTER: + // Create the fighter (making sure all it's data is initialized). + G_CreateFighterNPC( ¢->m_pVehicle, vehType ); + break; + case VH_WALKER: + // Create the walker (making sure all it's data is initialized). + G_CreateWalkerNPC( ¢->m_pVehicle, vehType ); + break; + + default: + assert(!"vehicle with an unknown type - couldn't create vehicle_t"); + break; + } + + //set up my happy prediction hack + cent->m_pVehicle->m_vOrientation = &cgSendPS[cent->currentState.number]->vehOrientation[0]; + + cent->m_pVehicle->m_pParentEntity = (bgEntity_t *)cent; + + //attach the handles for fx cgame-side + CG_RegisterVehicleAssets(cent->m_pVehicle); + + BG_GetVehicleModelName(modelName); + if (cent->m_pVehicle->m_pVehicleInfo->skin && + cent->m_pVehicle->m_pVehicleInfo->skin[0]) + { //use a custom skin + skinID = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", modelName, cent->m_pVehicle->m_pVehicleInfo->skin)); + } + else + { + skinID = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelName)); + } + strcpy(modelName, va("models/players/%s/model.glm", modelName)); + + //this sound is *only* used for vehicles now + cgs.media.noAmmoSound = trap_S_RegisterSound( "sound/weapons/noammo.wav" ); + } + else + { + skinID = CG_HandleAppendedSkin(modelName); //get the skin if there is one. + } + + if (cent->ghoul2) + { //clean it first! + trap_G2API_CleanGhoul2Models(¢->ghoul2); + } + + trap_G2API_InitGhoul2Model(¢->ghoul2, modelName, 0, skinID, 0, 0, 0); + + if (cent->ghoul2) + { + char GLAName[MAX_QPATH]; + char originalModelName[MAX_QPATH]; + char *saber; + int j = 0; + + if (cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle) + { //do special vehicle stuff + char strTemp[128]; + int i; + + // Setup the default first bolt + i = trap_G2API_AddBolt( cent->ghoul2, 0, "model_root" ); + + // Setup the droid unit. + cent->m_pVehicle->m_iDroidUnitTag = trap_G2API_AddBolt( cent->ghoul2, 0, "*droidunit" ); + + // Setup the Exhausts. + for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ ) + { + Com_sprintf( strTemp, 128, "*exhaust%i", i + 1 ); + cent->m_pVehicle->m_iExhaustTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp ); + } + + // Setup the Muzzles. + for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) + { + Com_sprintf( strTemp, 128, "*muzzle%i", i + 1 ); + cent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp ); + if ( cent->m_pVehicle->m_iMuzzleTag[i] == -1 ) + {//ergh, try *flash? + Com_sprintf( strTemp, 128, "*flash%i", i + 1 ); + cent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, strTemp ); + } + } + + // Setup the Turrets. + for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) + { + if ( cent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ) + { + cent->m_pVehicle->m_iGunnerViewTag[i] = trap_G2API_AddBolt( cent->ghoul2, 0, cent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ); + } + else + { + cent->m_pVehicle->m_iGunnerViewTag[i] = -1; + } + } + } + + if (cent->currentState.npcSaber1) + { + saber = (char *)CG_ConfigString(CS_MODELS+cent->currentState.npcSaber1); + assert(!saber || !saber[0] || saber[0] == '@'); + //valid saber names should always start with '@' for NPCs + + if (saber && saber[0]) + { + saber++; //skip over the @ + WP_SetSaber(cent->currentState.number, cent->npcClient->saber, 0, saber); + } + } + if (cent->currentState.npcSaber2) + { + saber = (char *)CG_ConfigString(CS_MODELS+cent->currentState.npcSaber2); + assert(!saber || !saber[0] || saber[0] == '@'); + //valid saber names should always start with '@' for NPCs + + if (saber && saber[0]) + { + saber++; //skip over the @ + WP_SetSaber(cent->currentState.number, cent->npcClient->saber, 1, saber); + } + } + + // If this is a not vehicle, give it saber stuff... + if ( cent->currentState.NPC_class != CLASS_VEHICLE ) + { + while (j < MAX_SABERS) + { + if (cent->npcClient->saber[j].model[0]) + { + if (cent->npcClient->ghoul2Weapons[j]) + { //free the old instance(s) + trap_G2API_CleanGhoul2Models(¢->npcClient->ghoul2Weapons[j]); + cent->npcClient->ghoul2Weapons[j] = 0; + } + + CG_InitG2SaberData(j, cent->npcClient); + } + j++; + } + } + + trap_G2API_SetSkin(cent->ghoul2, 0, skinID, skinID); + + cent->localAnimIndex = -1; + + GLAName[0] = 0; + trap_G2API_GetGLAName(cent->ghoul2, 0, GLAName); + + strcpy(originalModelName, modelName); + + if (GLAName[0] && + !strstr(GLAName, "players/_humanoid/") /*&& + !strstr(GLAName, "players/rockettrooper/")*/) + { //it doesn't use humanoid anims. + slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + cent->localAnimIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + } + else + { //humanoid index. + trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand"); + trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(cent->ghoul2, 0, "*chestg"); + + //claw bolts + trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand_cap_r_arm"); + trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand_cap_l_arm"); + + if (strstr(GLAName, "players/rockettrooper/")) + { + cent->localAnimIndex = 1; + } + else + { + cent->localAnimIndex = 0; + } + + if (trap_G2API_AddBolt(cent->ghoul2, 0, "*head_top") == -1) + { + trap_G2API_AddBolt(cent->ghoul2, 0, "ceyebrow"); + } + trap_G2API_AddBolt(cent->ghoul2, 0, "Motion"); + } + + // If this is a not vehicle... + if ( cent->currentState.NPC_class != CLASS_VEHICLE ) + { + if (trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar") == -1) + { //check now to see if we have this bone for setting anims and such + cent->noLumbar = qtrue; + } + + if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cent->noFace = qtrue; + } + } + else + { + cent->noLumbar = qtrue; + cent->noFace = qtrue; + } + + if (cent->localAnimIndex != -1) + { + slash = Q_strrchr( originalModelName, '/' ); + if ( slash ) + { + slash++; + *slash = 0; + } + + cent->eventAnimIndex = BG_ParseAnimationEvtFile(originalModelName, cent->localAnimIndex, bgNumAnimEvents); + } + } + } + + trap_S_ShutUp(qtrue); + CG_HandleNPCSounds(cent); //handle sound loading here as well. + trap_S_ShutUp(qfalse); +} + +//for now this is just gonna create a big explosion on the area of the surface, +//because I am lazy. +static void CG_CreateSurfaceDebris(centity_t *cent, int surfNum, int fxID, qboolean throwPart) +{ + int lostPartFX = 0; + int b; + vec3_t v, d; + mdxaBone_t boltMatrix; + const char *surfName = bgToggleableSurfaces[surfNum]; + + if (!cent->ghoul2) + { //oh no + return; + } + + //let's add the surface as a bolt so we can get the base point of it + if (bgToggleableSurfaceDebris[surfNum] == 3) + { //right wing flame + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_wingdamage"); + if ( throwPart + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iRWingFX; + } + } + else if (bgToggleableSurfaceDebris[surfNum] == 4) + { //left wing flame + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_wingdamage"); + if ( throwPart + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iLWingFX; + } + } + else if (bgToggleableSurfaceDebris[surfNum] == 5) + { //right wing flame 2 + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_wingdamage"); + if ( throwPart + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iRWingFX; + } + } + else if (bgToggleableSurfaceDebris[surfNum] == 6) + { //left wing flame 2 + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_wingdamage"); + if ( throwPart + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iLWingFX; + } + } + else if (bgToggleableSurfaceDebris[surfNum] == 7) + { //nose flame + b = trap_G2API_AddBolt(cent->ghoul2, 0, "*nosedamage"); + if ( cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo ) + { + lostPartFX = cent->m_pVehicle->m_pVehicleInfo->iNoseFX; + } + } + else + { + b = trap_G2API_AddBolt(cent->ghoul2, 0, surfName); + } + + if (b == -1) + { //couldn't find this surface apparently + return; + } + + //now let's get the position and direction of this surface and make a big explosion + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time, + cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, v); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_Z, d); + + trap_FX_PlayEffectID(fxID, v, d, -1, -1); + if ( throwPart && lostPartFX ) + {//throw off a ship part, too + vec3_t fxFwd; + AngleVectors( cent->lerpAngles, fxFwd, NULL, NULL ); + trap_FX_PlayEffectID(lostPartFX, v, fxFwd, -1, -1); + } +} + +//for now this is just gonna create a big explosion on the area of the surface, +//because I am lazy. +static void CG_CreateSurfaceSmoke(centity_t *cent, int shipSurf, int fxID) +{ + int b = -1; + vec3_t v, d; + mdxaBone_t boltMatrix; + const char *surfName = NULL; + + if (!cent->ghoul2) + { //oh no + return; + } + + //let's add the surface as a bolt so we can get the base point of it + if ( shipSurf == SHIPSURF_FRONT ) + { //front flame/smoke + surfName = "*nosedamage"; + } + else if (shipSurf == SHIPSURF_BACK ) + { //back flame/smoke + surfName = "*exhaust1";//FIXME: random? Some point in-between? + } + else if (shipSurf == SHIPSURF_RIGHT ) + { //right wing flame/smoke + surfName = "*r_wingdamage"; + } + else if (shipSurf == SHIPSURF_LEFT ) + { //left wing flame/smoke + surfName = "*l_wingdamage"; + } + else + {//unknown surf! + return; + } + b = trap_G2API_AddBolt(cent->ghoul2, 0, surfName); + if (b == -1) + { //couldn't find this surface apparently + return; + } + + //now let's get the position and direction of this surface and make a big explosion + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, b, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time, + cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, v); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_Z, d); + + trap_FX_PlayEffectID(fxID, v, d, -1, -1); +} + +#define SMOOTH_G2ANIM_LERPANGLES + +qboolean CG_VehicleShouldDrawShields( centity_t *vehCent ) +{ + if ( vehCent->damageTime > cg.time //ship shields currently taking damage + && vehCent->currentState.NPC_class == CLASS_VEHICLE + && vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo ) + { + return qtrue; + } + return qfalse; +} + +/* +extern vmCvar_t cg_showVehBounds; +extern void BG_VehicleAdjustBBoxForOrientation( Vehicle_t *veh, vec3_t origin, vec3_t mins, vec3_t maxs, + int clientNum, int tracemask, + void (*localTrace)(trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask)); // bg_pmove.c +*/ +qboolean CG_VehicleAttachDroidUnit( centity_t *droidCent, refEntity_t *legs ) +{ + if ( droidCent + && droidCent->currentState.owner + && droidCent->currentState.clientNum >= MAX_CLIENTS ) + {//the only NPCs that can ride a vehicle are droids...??? + centity_t *vehCent = &cg_entities[droidCent->currentState.owner]; + if ( vehCent + && vehCent->m_pVehicle + && vehCent->ghoul2 + && vehCent->m_pVehicle->m_iDroidUnitTag != -1 ) + { + mdxaBone_t boltMatrix; + vec3_t fwd, rt, tempAng; + + trap_G2API_GetBoltMatrix(vehCent->ghoul2, 0, vehCent->m_pVehicle->m_iDroidUnitTag, &boltMatrix, vehCent->lerpAngles, vehCent->lerpOrigin, cg.time, + cgs.gameModels, vehCent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, droidCent->lerpOrigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, fwd);//WTF??? + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, rt);//WTF??? + vectoangles( fwd, droidCent->lerpAngles ); + vectoangles( rt, tempAng ); + droidCent->lerpAngles[ROLL] = tempAng[PITCH]; + + return qtrue; + } + } + return qfalse; +} + +void CG_G2Animated( centity_t *cent ) +{ +#ifdef SMOOTH_G2ANIM_LERPANGLES + float angSmoothFactor = 0.7f; +#endif + + + if (!cent->ghoul2) + { //Initialize this g2 anim ent, then return (will start rendering next frame) + CG_G2AnimEntModelLoad(cent); + cent->npcLocalSurfOff = 0; + cent->npcLocalSurfOn = 0; + return; + } + + if (cent->npcLocalSurfOff != cent->currentState.surfacesOff || + cent->npcLocalSurfOn != cent->currentState.surfacesOn) + { //looks like it's time for an update. + int i = 0; + + while (i < BG_NUM_TOGGLEABLE_SURFACES && bgToggleableSurfaces[i]) + { + if (!(cent->npcLocalSurfOff & (1 << i)) && + (cent->currentState.surfacesOff & (1 << i))) + { //it wasn't off before but it's off now, so reflect this change in the g2 instance. + if (bgToggleableSurfaceDebris[i] > 0) + { //make some local debris of this thing? + //FIXME: throw off the proper model effect, too + CG_CreateSurfaceDebris(cent, i, cgs.effects.mShipDestDestroyed, qtrue); + } + + trap_G2API_SetSurfaceOnOff(cent->ghoul2, bgToggleableSurfaces[i], TURN_OFF); + } + + if (!(cent->npcLocalSurfOn & (1 << i)) && + (cent->currentState.surfacesOn & (1 << i))) + { //same as above, but on instead of off. + trap_G2API_SetSurfaceOnOff(cent->ghoul2, bgToggleableSurfaces[i], TURN_ON); + } + + i++; + } + + cent->npcLocalSurfOff = cent->currentState.surfacesOff; + cent->npcLocalSurfOn = cent->currentState.surfacesOn; + } + + + /* + if (cent->currentState.weapon && + !trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1) && + !(cent->currentState.eFlags & EF_DEAD)) + { //if the server says we have a weapon and we haven't copied one onto ourselves yet, then do so. + trap_G2API_CopySpecificGhoul2Model(g2WeaponInstances[cent->currentState.weapon], 0, cent->ghoul2, 1); + + if (cent->currentState.weapon == WP_SABER) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" )); + } + } + */ + + if (cent->torsoBolt && !(cent->currentState.eFlags & EF_DEAD)) + { //he's alive and has a limb missing still, reattach it and reset the weapon + CG_ReattachLimb(cent); + } + + if (((cent->currentState.eFlags & EF_DEAD) || (cent->currentState.eFlags & EF_RAG)) && !cent->localAnimIndex) + { + vec3_t forcedAngles; + + VectorClear(forcedAngles); + forcedAngles[YAW] = cent->lerpAngles[YAW]; + + CG_RagDoll(cent, forcedAngles); + } + +#ifdef SMOOTH_G2ANIM_LERPANGLES + if ((cent->lerpAngles[YAW] > 0 && cent->smoothYaw < 0) || + (cent->lerpAngles[YAW] < 0 && cent->smoothYaw > 0)) + { //keep it from snapping around on the threshold + cent->smoothYaw = -cent->smoothYaw; + } + cent->lerpAngles[YAW] = cent->smoothYaw+(cent->lerpAngles[YAW]-cent->smoothYaw)*angSmoothFactor; + cent->smoothYaw = cent->lerpAngles[YAW]; +#endif + + //now just render as a player + CG_Player(cent); + + /* + if ( cg_showVehBounds.integer ) + {//show vehicle bboxes + if ( cent->currentState.clientNum >= MAX_CLIENTS + && cent->currentState.NPC_class == CLASS_VEHICLE + && cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo + && cent->currentState.clientNum != cg.predictedVehicleState.clientNum ) + {//not the predicted vehicle + vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0}; + vec3_t absmin, absmax; + vec3_t bmins, bmaxs; + float *old = cent->m_pVehicle->m_vOrientation; + cent->m_pVehicle->m_vOrientation = ¢->lerpAngles[0]; + + BG_VehicleAdjustBBoxForOrientation( cent->m_pVehicle, cent->lerpOrigin, bmins, bmaxs, + cent->currentState.number, MASK_PLAYERSOLID, NULL ); + cent->m_pVehicle->m_vOrientation = old; + + VectorAdd( cent->lerpOrigin, bmins, absmin ); + VectorAdd( cent->lerpOrigin, bmaxs, absmax ); + CG_Cube( absmin, absmax, NPCDEBUG_RED, 0.25 ); + } + } + */ +} +//rww - here ends the majority of my g2animent stuff. + +//Disabled for now, I'm too lazy to keep it working with all the stuff changing around. +#if 0 +int cgFPLSState = 0; + +void CG_ForceFPLSPlayerModel(centity_t *cent, clientInfo_t *ci) +{ + animation_t *anim; + + if (cg_fpls.integer && !cg.renderingThirdPerson) + { + int skinHandle; + + skinHandle = trap_R_RegisterSkin("models/players/kyle/model_fpls2.skin"); + + trap_G2API_CleanGhoul2Models(&(ci->ghoul2Model)); + + ci->torsoSkin = skinHandle; + trap_G2API_InitGhoul2Model(&ci->ghoul2Model, "models/players/kyle/model.glm", 0, ci->torsoSkin, 0, 0, 0); + + ci->bolt_rhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand"); + + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, cg.time, -1, -1); + trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, cg.time); + trap_G2API_SetBoneAngles(ci->ghoul2Model, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, cg.time); + + ci->bolt_lhand = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(ci->ghoul2Model, 0, "*chestg"); + + //claw bolts + trap_G2API_AddBolt(ci->ghoul2Model, 0, "*r_hand_cap_r_arm"); + trap_G2API_AddBolt(ci->ghoul2Model, 0, "*l_hand_cap_l_arm"); + + ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "*head_top"); + if (ci->bolt_head == -1) + { + ci->bolt_head = trap_G2API_AddBolt(ci->ghoul2Model, 0, "ceyebrow"); + } + + ci->bolt_motion = trap_G2API_AddBolt(ci->ghoul2Model, 0, "Motion"); + + //We need a lower lumbar bolt for footsteps + ci->bolt_llumbar = trap_G2API_AddBolt(ci->ghoul2Model, 0, "lower_lumbar"); + + CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, ci->ghoul2Model); + } + else + { + CG_RegisterClientModelname(ci, ci->modelName, ci->skinName, ci->teamName, cent->currentState.number); + } + + anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.legsAnim ]; + + if (anim) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE; + int firstFrame = anim->firstFrame; + int setFrame = -1; + float animSpeed = 50.0f / anim->frameLerp; + + if (anim->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cent->pe.legs.frame >= anim->firstFrame && cent->pe.legs.frame <= (anim->firstFrame + anim->numFrames)) + { + setFrame = cent->pe.legs.frame; + } + + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "model_root", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150); + + cent->currentState.legsAnim = 0; + } + + anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.torsoAnim ]; + + if (anim) + { + int flags = BONE_ANIM_OVERRIDE_FREEZE; + int firstFrame = anim->firstFrame; + int setFrame = -1; + float animSpeed = 50.0f / anim->frameLerp; + + if (anim->loopFrames != -1) + { + flags = BONE_ANIM_OVERRIDE_LOOP; + } + + if (cent->pe.torso.frame >= anim->firstFrame && cent->pe.torso.frame <= (anim->firstFrame + anim->numFrames)) + { + setFrame = cent->pe.torso.frame; + } + + trap_G2API_SetBoneAnim(ci->ghoul2Model, 0, "lower_lumbar", firstFrame, anim->firstFrame + anim->numFrames, flags, animSpeed, cg.time, setFrame, 150); + + cent->currentState.torsoAnim = 0; + } + + trap_G2API_CleanGhoul2Models(&(cent->ghoul2)); + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, ¢->ghoul2); + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse); +} +#endif + +//for allocating and freeing npc clientinfo structures. +//Remember to free this before game shutdown no matter what +//and don't stomp over it, as it is dynamic memory from the +//exe. +void CG_CreateNPCClient(clientInfo_t **ci) +{ + //trap_TrueMalloc((void **)ci, sizeof(clientInfo_t)); + *ci = (clientInfo_t *) BG_Alloc(sizeof(clientInfo_t)); +} + +void CG_DestroyNPCClient(clientInfo_t **ci) +{ + memset(*ci, 0, sizeof(clientInfo_t)); + //trap_TrueFree((void **)ci); +} + +static void CG_ForceElectrocution( centity_t *cent, const vec3_t origin, vec3_t tempAngles, qhandle_t shader, qboolean alwaysDo ) +{ + // Undoing for now, at least this code should compile if I ( or anyone else ) decides to work on this effect + qboolean found = qfalse; + vec3_t fxOrg, fxOrg2, dir; + vec3_t rgb; + mdxaBone_t boltMatrix; + trace_t tr; + int bolt=-1; + int iter=0; + int torsoBolt = -1; + int crotchBolt = -1; + int elbowLBolt = -1; + int elbowRBolt = -1; + int handLBolt = -1; + int handRBolt = -1; + int kneeLBolt = -1; + int kneeRBolt = -1; + int footLBolt = -1; + int footRBolt = -1; + + VectorSet(rgb, 1, 1, 1); + + if (cent->localAnimIndex <= 1) + { //humanoid + torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar"); + crotchBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis"); + elbowLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_arm_elbow"); + elbowRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_arm_elbow"); + handLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_hand"); + handRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_hand"); + kneeLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hips_l_knee"); + kneeRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hips_r_knee"); + footLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*l_leg_foot"); + footRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*r_leg_foot"); + } + else if (cent->currentState.NPC_class == CLASS_PROTOCOL) + { //any others that can use these bolts too? + torsoBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "lower_lumbar"); + crotchBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "pelvis"); + elbowLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*bicep_lg"); + elbowRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*bicep_rg"); + handLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*hand_l"); + handRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*weapon"); + kneeLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*thigh_lg"); + kneeRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*thigh_rg"); + footLBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*foot_lg"); + footRBolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*foot_rg"); + } + + // Pick a random start point + while (bolt<0) + { + int test; + if (iter>5) + { + test=iter-5; + } + else + { + test=Q_irand(0,6); + } + switch(test) + { + case 0: + // Right Elbow + bolt=elbowRBolt; + break; + case 1: + // Left Hand + bolt=handLBolt; + break; + case 2: + // Right hand + bolt=handRBolt; + break; + case 3: + // Left Foot + bolt=footLBolt; + break; + case 4: + // Right foot + bolt=footRBolt; + break; + case 5: + // Torso + bolt=torsoBolt; + break; + case 6: + default: + // Left Elbow + bolt=elbowLBolt; + break; + } + if (++iter==20) + break; + } + if (bolt>=0) + { + found = trap_G2API_GetBoltMatrix( cent->ghoul2, 0, bolt, + &boltMatrix, tempAngles, origin, cg.time, + cgs.gameModels, cent->modelScale); + } + + // Make sure that it's safe to even try and get these values out of the Matrix, otherwise the values could be garbage + if ( found ) + { + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, fxOrg ); + if ( random() > 0.5f ) + { + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, dir ); + } + else + { + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + } + + // Add some fudge, makes us not normalized, but that isn't really important + dir[0] += crandom() * 0.4f; + dir[1] += crandom() * 0.4f; + dir[2] += crandom() * 0.4f; + } + else + { + // Just use the lerp Origin and a random direction + VectorCopy( cent->lerpOrigin, fxOrg ); + VectorSet( dir, crandom(), crandom(), crandom() ); // Not normalized, but who cares. + switch ( cent->currentState.NPC_class ) + { + case CLASS_PROBE: + fxOrg[2] += 50; + break; + case CLASS_MARK1: + fxOrg[2] += 50; + break; + case CLASS_ATST: + fxOrg[2] += 120; + break; + default: + break; + } + } + + VectorMA( fxOrg, random() * 40 + 40, dir, fxOrg2 ); + + CG_Trace( &tr, fxOrg, NULL, NULL, fxOrg2, -1, CONTENTS_SOLID ); + + if ( tr.fraction < 1.0f || random() > 0.94f || alwaysDo ) + { + addElectricityArgStruct_t p; + + VectorCopy(fxOrg, p.start); + VectorCopy(tr.endpos, p.end); + p.size1 = 1.5f; + p.size2 = 4.0f; + p.sizeParm = 0.0f; + p.alpha1 = 1.0f; + p.alpha2 = 0.5f; + p.alphaParm = 0.0f; + VectorCopy(rgb, p.sRGB); + VectorCopy(rgb, p.eRGB); + p.rgbParm = 0.0f; + p.chaos = 5.0f; + p.killTime = (random() * 50 + 100); + p.shader = shader; + p.flags = (0x00000001 | 0x00000100 | 0x02000000 | 0x04000000 | 0x01000000); + + trap_FX_AddElectricity(&p); + + //In other words: + /* + FX_AddElectricity( fxOrg, tr.endpos, + 1.5f, 4.0f, 0.0f, + 1.0f, 0.5f, 0.0f, + rgb, rgb, 0.0f, + 5.5f, random() * 50 + 100, shader, FX_ALPHA_LINEAR | FX_SIZE_LINEAR | FX_BRANCH | FX_GROW | FX_TAPER ); + */ + } +} + +void *cg_g2JetpackInstance = NULL; + +#define JETPACK_MODEL "models/weapons2/jetpack/model.glm" + +void CG_InitJetpackGhoul2(void) +{ + if (cg_g2JetpackInstance) + { + assert(!"Tried to init jetpack inst, already init'd"); + return; + } + + trap_G2API_InitGhoul2Model(&cg_g2JetpackInstance, JETPACK_MODEL, 0, 0, 0, 0, 0); + + assert(cg_g2JetpackInstance); + + //Indicate which bolt on the player we will be attached to + //In this case bolt 0 is rhand, 1 is lhand, and 2 is the bolt + //for the jetpack (*chestg) + trap_G2API_SetBoltInfo(cg_g2JetpackInstance, 0, 2); + + //Add the bolts jet effects will be played from + trap_G2API_AddBolt(cg_g2JetpackInstance, 0, "torso_ljet"); + trap_G2API_AddBolt(cg_g2JetpackInstance, 0, "torso_rjet"); +} + +void CG_CleanJetpackGhoul2(void) +{ + if (cg_g2JetpackInstance) + { + trap_G2API_CleanGhoul2Models(&cg_g2JetpackInstance); + cg_g2JetpackInstance = NULL; + } +} + +#define RARMBIT (1 << (G2_MODELPART_RARM-10)) +#define RHANDBIT (1 << (G2_MODELPART_RHAND-10)) +#define WAISTBIT (1 << (G2_MODELPART_WAIST-10)) + +#if 0 +static void CG_VehicleHeatEffect( vec3_t org, centity_t *cent ) +{ + refEntity_t ent; + vec3_t ang; + float scale; + float vLen; + float alpha; + + if (!cg_renderToTextureFX.integer) + { + return; + } + scale = 0.1f; + + alpha = 200.0f; + + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( org, ent.origin ); + + VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (VectorNormalize(ent.axis[0]) <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + + vectoangles(ent.axis[0], ang); + AnglesToAxis(ang, ent.axis); + + //radius must be a power of 2, and is the actual captured texture size + ent.radius = 32; + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], -scale, ent.axis[2]); + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = cgs.media.cloakedShader; + + //make it partially transparent so it blends with the background + ent.renderfx = (RF_DISTORTION|RF_FORCE_ENT_ALPHA); + ent.shaderRGBA[0] = 255.0f; + ent.shaderRGBA[1] = 255.0f; + ent.shaderRGBA[2] = 255.0f; + ent.shaderRGBA[3] = alpha; + + trap_R_AddRefEntityToScene( &ent ); +} +#endif + +static int lastFlyBySound[MAX_GENTITIES] = {0}; +#define FLYBYSOUNDTIME 2000 +int cg_lastHyperSpaceEffectTime = 0; +static CGAME_INLINE void CG_VehicleEffects(centity_t *cent) +{ + Vehicle_t *pVehNPC; + + if (cent->currentState.eType != ET_NPC || + cent->currentState.NPC_class != CLASS_VEHICLE || + !cent->m_pVehicle) + { + return; + } + + pVehNPC = cent->m_pVehicle; + + if ( cent->currentState.clientNum == cg.predictedPlayerState.m_iVehicleNum//my vehicle + && (cent->currentState.eFlags2&EF2_HYPERSPACE) )//hyperspacing + {//in hyperspace! + if ( cg.predictedVehicleState.hyperSpaceTime + && (cg.time-cg.predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME ) + { + if ( !cg_lastHyperSpaceEffectTime + || (cg.time - cg_lastHyperSpaceEffectTime) > HYPERSPACE_TIME+500 ) + {//can't be from the last time we were in hyperspace, so play the effect! + trap_FX_PlayBoltedEffectID( cgs.effects.mHyperspaceStars, cent->lerpOrigin, cent->ghoul2, 0, + cent->currentState.number, 0, 0, qtrue ); + cg_lastHyperSpaceEffectTime = cg.time; + } + } + } + + //FLYBY sound + if ( cent->currentState.clientNum != cg.predictedPlayerState.m_iVehicleNum + && (pVehNPC->m_pVehicleInfo->soundFlyBy||pVehNPC->m_pVehicleInfo->soundFlyBy2) ) + {//not my vehicle + if ( cent->currentState.speed && cg.predictedPlayerState.speed+cent->currentState.speed > 500 ) + {//he's moving and between the two of us, we're moving fast + vec3_t diff; + VectorSubtract( cent->lerpOrigin, cg.predictedPlayerState.origin, diff ); + if ( VectorLength( diff ) < 2048 ) + {//close + vec3_t myFwd, theirFwd; + AngleVectors( cg.predictedPlayerState.viewangles, myFwd, NULL, NULL ); + VectorScale( myFwd, cg.predictedPlayerState.speed, myFwd ); + AngleVectors( cent->lerpAngles, theirFwd, NULL, NULL ); + VectorScale( theirFwd, cent->currentState.speed, theirFwd ); + if ( lastFlyBySound[cent->currentState.clientNum]+FLYBYSOUNDTIME < cg.time ) + {//okay to do a flyby sound on this vehicle + if ( DotProduct( myFwd, theirFwd ) < 500 ) + { + int flyBySound = 0; + if ( pVehNPC->m_pVehicleInfo->soundFlyBy && pVehNPC->m_pVehicleInfo->soundFlyBy2 ) + { + flyBySound = Q_irand(0,1)?pVehNPC->m_pVehicleInfo->soundFlyBy:pVehNPC->m_pVehicleInfo->soundFlyBy2; + } + else if ( pVehNPC->m_pVehicleInfo->soundFlyBy ) + { + flyBySound = pVehNPC->m_pVehicleInfo->soundFlyBy; + } + else //if ( pVehNPC->m_pVehicleInfo->soundFlyBy2 ) + { + flyBySound = pVehNPC->m_pVehicleInfo->soundFlyBy2; + } + trap_S_StartSound(NULL, cent->currentState.clientNum, CHAN_LESS_ATTEN, flyBySound ); + lastFlyBySound[cent->currentState.clientNum] = cg.time; + } + } + } + } + } + + if ( !cent->currentState.speed//was stopped + && cent->nextState.speed > 0//now moving forward + && cent->m_pVehicle->m_pVehicleInfo->soundEngineStart ) + {//engines rev up for the first time + trap_S_StartSound(NULL, cent->currentState.clientNum, CHAN_LESS_ATTEN, cent->m_pVehicle->m_pVehicleInfo->soundEngineStart ); + } + // Animals don't exude any effects... + if ( pVehNPC->m_pVehicleInfo->type != VH_ANIMAL ) + { + if (pVehNPC->m_pVehicleInfo->surfDestruction && cent->ghoul2) + { //see if anything has been blown off + int i = 0; + qboolean surfDmg = qfalse; + + while (i < BG_NUM_TOGGLEABLE_SURFACES) + { + if (bgToggleableSurfaceDebris[i] > 1) + { //this is decidedly a destroyable surface, let's check its status + int surfTest = trap_G2API_GetSurfaceRenderStatus(cent->ghoul2, 0, bgToggleableSurfaces[i]); + + if ( surfTest != -1 + && (surfTest&TURN_OFF) ) + { //it exists, but it's off... + surfDmg = qtrue; + + //create some flames + CG_CreateSurfaceDebris(cent, i, cgs.effects.mShipDestBurning, qfalse); + } + } + + i++; + } + + if (surfDmg) + { //if any surface are damaged, neglect exhaust etc effects (so we don't have exhaust trails coming out of invisible surfaces) + return; + } + } + + if ( pVehNPC->m_iLastFXTime <= cg.time ) + {//until we attach it, we need to debounce this + vec3_t fwd, rt, up; + vec3_t flat; + float nextFXDelay = 50; + VectorSet(flat, 0, cent->lerpAngles[1], cent->lerpAngles[2]); + AngleVectors( flat, fwd, rt, up ); + if ( cent->currentState.speed > 0 ) + {//FIXME: only do this when accelerator is being pressed! (must have a driver?) + vec3_t org; + qboolean doExhaust = qfalse; + VectorMA( cent->lerpOrigin, -16, up, org ); + VectorMA( org, -42, fwd, org ); + // Play damage effects. + //if ( pVehNPC->m_iArmor <= 75 ) + if (0) + {//hurt + trap_FX_PlayEffectID( cgs.effects.mBlackSmoke, org, fwd, -1, -1 ); + } + else if ( pVehNPC->m_pVehicleInfo->iTrailFX ) + {//okay, do normal trail + trap_FX_PlayEffectID( pVehNPC->m_pVehicleInfo->iTrailFX, org, fwd, -1, -1 ); + } + //===================================================================== + //EXHAUST FX + //===================================================================== + //do exhaust + if ( (cent->currentState.eFlags&EF_JETPACK_ACTIVE) ) + {//cheap way of telling us the vehicle is in "turbo" mode + doExhaust = (pVehNPC->m_pVehicleInfo->iTurboFX!=0); + } + else + { + doExhaust = (pVehNPC->m_pVehicleInfo->iExhaustFX!=0); + } + if ( doExhaust && cent->ghoul2 ) + { + int i; + int fx; + + for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ ) + { + // We hit an invalid tag, we quit (they should be created in order so tough luck if not). + if ( pVehNPC->m_iExhaustTag[i] == -1 ) + { + break; + } + + if ( (cent->currentState.brokenLimbs&(1<currentState.brokenLimbs&(1<currentState.eFlags&EF_JETPACK_ACTIVE) //cheap way of telling us the vehicle is in "turbo" mode + && pVehNPC->m_pVehicleInfo->iTurboFX )//they have a valid turbo exhaust effect to play + { + fx = pVehNPC->m_pVehicleInfo->iTurboFX; + } + else + {//play the normal one + fx = pVehNPC->m_pVehicleInfo->iExhaustFX; + } + + if (pVehNPC->m_pVehicleInfo->type == VH_FIGHTER) + { + trap_FX_PlayBoltedEffectID(fx, cent->lerpOrigin, cent->ghoul2, pVehNPC->m_iExhaustTag[i], + cent->currentState.number, 0, 0, qtrue); + } + else + { //fixme: bolt these too + mdxaBone_t boltMatrix; + vec3_t boltOrg, boltDir; + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, pVehNPC->m_iExhaustTag[i], &boltMatrix, flat, + cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + VectorCopy(fwd, boltDir); //fixme? + + trap_FX_PlayEffectID( fx, boltOrg, boltDir, -1, -1 ); + } + } + } + //===================================================================== + //WING TRAIL FX + //===================================================================== + //do trail + //FIXME: not in space!!! + if ( pVehNPC->m_pVehicleInfo->iTrailFX != 0 && cent->ghoul2 ) + { + int i; + vec3_t boltOrg, boltDir; + mdxaBone_t boltMatrix; + vec3_t getBoltAngles; + + VectorCopy(cent->lerpAngles, getBoltAngles); + if (pVehNPC->m_pVehicleInfo->type != VH_FIGHTER) + { //only fighters use pitch/roll in refent axis + getBoltAngles[PITCH] = getBoltAngles[ROLL] = 0.0f; + } + + for ( i = 1; i < 5; i++ ) + { + int trailBolt = trap_G2API_AddBolt(cent->ghoul2, 0, va("*trail%d",i) ); + // We hit an invalid tag, we quit (they should be created in order so tough luck if not). + if ( trailBolt == -1 ) + { + break; + } + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, trailBolt, &boltMatrix, getBoltAngles, + cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + VectorCopy(fwd, boltDir); //fixme? + + trap_FX_PlayEffectID( pVehNPC->m_pVehicleInfo->iTrailFX, boltOrg, boltDir, -1, -1 ); + } + } + } + //FIXME armor needs to be sent over network + { + if ( (cent->currentState.eFlags&EF_DEAD) ) + {//just plain dead, use flames + vec3_t up ={0,0,1}; + vec3_t boltOrg; + + //if ( pVehNPC->m_iDriverTag == -1 ) + {//doh! no tag + VectorCopy( cent->lerpOrigin, boltOrg ); + } + //else + //{ + // mdxaBone_t boltMatrix; + // vec3_t getBoltAngles; + + // VectorCopy(cent->lerpAngles, getBoltAngles); + // if (pVehNPC->m_pVehicleInfo->type != VH_FIGHTER) + // { //only fighters use pitch/roll in refent axis + // getBoltAngles[PITCH] = getBoltAngles[ROLL] = 0.0f; + // } + + // trap_G2API_GetBoltMatrix(cent->ghoul2, 0, pVehNPC->m_iDriverTag, &boltMatrix, getBoltAngles, + // cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + + // BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + //} + trap_FX_PlayEffectID( cgs.effects.mShipDestBurning, boltOrg, up, -1, -1 ); + } + } + if ( cent->currentState.brokenLimbs ) + { + int i; + if ( !Q_irand( 0, 5 ) ) + { + for ( i = SHIPSURF_FRONT; i <= SHIPSURF_LEFT; i++ ) + { + if ( (cent->currentState.brokenLimbs&(1<<((i-SHIPSURF_FRONT)+SHIPSURF_DAMAGE_FRONT_HEAVY))) ) + {//heavy damage, do both effects + if ( pVehNPC->m_pVehicleInfo->iInjureFX ) + { + CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iInjureFX ); + } + if ( pVehNPC->m_pVehicleInfo->iDmgFX ) + { + CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iDmgFX ); + } + } + else if ( (cent->currentState.brokenLimbs&(1<<((i-SHIPSURF_FRONT)+SHIPSURF_DAMAGE_FRONT_LIGHT))) ) + {//only light damage + if ( pVehNPC->m_pVehicleInfo->iInjureFX ) + { + CG_CreateSurfaceSmoke( cent, i, pVehNPC->m_pVehicleInfo->iInjureFX ); + } + } + } + } + } + /* + if ( pVehNPC->m_iArmor <= 50 ) + {//FIXME: use as a proportion of max armor? + VectorMA( cent->lerpOrigin, 64, fwd, org ); + VectorScale( fwd, -1, fwd ); + + trap_FX_PlayEffectID( cgs.effects.mBlackSmoke, org, fwd, -1, -1 ); + } + if ( pVehNPC->m_iArmor <= 0 ) + {//FIXME: should use something attached.. but want it to build up over time, so... + if ( flrand( 0, cg.time - pVehNPC->m_iDieTime ) < 1000 ) + {//flaming! + VectorMA( cent->lerpOrigin, flrand(-64, 64), fwd, org ); + VectorScale( fwd, -1, fwd ); + trap_FX_PlayEffectID( trap_FX_RegisterEffect("ships/fire"), org, fwd, -1, -1 ); + nextFXDelay = 50; + } + } + */ + pVehNPC->m_iLastFXTime = cg.time + nextFXDelay; + } + } +} + +/* +=============== +CG_Player +=============== +*/ +#include "../namespace_begin.h" +int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); +#include "../namespace_end.h" + +float CG_RadiusForCent( centity_t *cent ) +{ + if ( cent->currentState.eType == ET_NPC ) + { + if (cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle && + cent->m_pVehicle->m_pVehicleInfo->g2radius) + { //has override + return cent->m_pVehicle->m_pVehicleInfo->g2radius; + } + else if ( cent->currentState.g2radius ) + { + return cent->currentState.g2radius; + } + } + else if ( cent->currentState.g2radius ) + { + return cent->currentState.g2radius; + } + return 64.0f; +} + +static float cg_vehThirdPersonAlpha = 1.0f; +extern vec3_t cg_crosshairPos; +extern vec3_t cameraCurLoc; +void CG_CheckThirdPersonAlpha( centity_t *cent, refEntity_t *legs ) +{ + float alpha = 1.0f; + int setFlags = 0; + + if ( cent->m_pVehicle ) + {//a vehicle + if ( cg.predictedPlayerState.m_iVehicleNum != cent->currentState.clientNum//not mine + && cent->m_pVehicle->m_pVehicleInfo + && cent->m_pVehicle->m_pVehicleInfo->cameraOverride + && cent->m_pVehicle->m_pVehicleInfo->cameraAlpha )//it has alpha + {//make sure it's not using any alpha + legs->renderfx |= RF_FORCE_ENT_ALPHA; + legs->shaderRGBA[3] = 255; + return; + } + } + + if ( !cg.renderingThirdPerson ) + { + return; + } + + if ( cg.predictedPlayerState.m_iVehicleNum ) + {//in a vehicle + if ( cg.predictedPlayerState.m_iVehicleNum == cent->currentState.clientNum ) + {//this is my vehicle + if ( cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo + && cent->m_pVehicle->m_pVehicleInfo->cameraOverride + && cent->m_pVehicle->m_pVehicleInfo->cameraAlpha ) + {//vehicle has auto third-person alpha on + trace_t trace; + vec3_t dir2Crosshair, end; + VectorSubtract( cg_crosshairPos, cameraCurLoc, dir2Crosshair ); + VectorNormalize( dir2Crosshair ); + VectorMA( cameraCurLoc, cent->m_pVehicle->m_pVehicleInfo->cameraRange*2.0f, dir2Crosshair, end ); + CG_G2Trace( &trace, cameraCurLoc, vec3_origin, vec3_origin, end, ENTITYNUM_NONE, CONTENTS_BODY ); + if ( trace.entityNum == cent->currentState.clientNum + || trace.entityNum == cg.predictedPlayerState.clientNum) + {//hit me or the vehicle I'm in + cg_vehThirdPersonAlpha -= 0.1f*cg.frametime/50.0f; + if ( cg_vehThirdPersonAlpha < cent->m_pVehicle->m_pVehicleInfo->cameraAlpha ) + { + cg_vehThirdPersonAlpha = cent->m_pVehicle->m_pVehicleInfo->cameraAlpha; + } + } + else + { + cg_vehThirdPersonAlpha += 0.1f*cg.frametime/50.0f; + if ( cg_vehThirdPersonAlpha > 1.0f ) + { + cg_vehThirdPersonAlpha = 1.0f; + } + } + alpha = cg_vehThirdPersonAlpha; + } + else + {//use the cvar + //reset this + cg_vehThirdPersonAlpha = 1.0f; + //use the cvar + alpha = cg_thirdPersonAlpha.value; + } + } + } + else if ( cg.predictedPlayerState.clientNum == cent->currentState.clientNum ) + {//it's me + //reset this + cg_vehThirdPersonAlpha = 1.0f; + //use the cvar + setFlags = RF_FORCE_ENT_ALPHA; + alpha = cg_thirdPersonAlpha.value; + } + + if ( alpha < 1.0f ) + { + legs->renderfx |= setFlags; + legs->shaderRGBA[3] = (unsigned char)(alpha * 255.0f); + } +} + +void CG_Player( centity_t *cent ) { + clientInfo_t *ci; + refEntity_t legs; + refEntity_t torso; + int clientNum; + int renderfx; + qboolean shadow = qfalse; + float shadowPlane = 0; + qboolean dead = qfalse; + vec3_t rootAngles; + float angle; + vec3_t angles, dir, elevated, enang, seekorg; + int iwantout = 0, successchange = 0; + int team; + mdxaBone_t boltMatrix, lHandMatrix; + int doAlpha = 0; + qboolean gotLHandMatrix = qfalse; + qboolean g2HasWeapon = qfalse; + qboolean drawPlayerSaber = qfalse; + qboolean checkDroidShields = qfalse; + + //first if we are not an npc and we are using an emplaced gun then make sure our + //angles are visually capped to the constraints (otherwise it's possible to lerp + //a little outside and look kind of twitchy) + if (cent->currentState.weapon == WP_EMPLACED_GUN && + cent->currentState.otherEntityNum2) + { + float empYaw; + + if (BG_EmplacedView(cent->lerpAngles, cg_entities[cent->currentState.otherEntityNum2].currentState.angles, &empYaw, cg_entities[cent->currentState.otherEntityNum2].currentState.origin2[0])) + { + cent->lerpAngles[YAW] = empYaw; + } + } + + if (cent->currentState.iModelScale) + { //if the server says we have a custom scale then set it now. + cent->modelScale[0] = cent->modelScale[1] = cent->modelScale[2] = cent->currentState.iModelScale/100.0f; + if ( cent->currentState.NPC_class != CLASS_VEHICLE ) + { + if (cent->modelScale[2] && cent->modelScale[2] != 1.0f) + { + cent->lerpOrigin[2] += 24 * (cent->modelScale[2] - 1); + } + } + } + else + { + VectorClear(cent->modelScale); + } + + if ((cg_smoothClients.integer || cent->currentState.heldByClient) && (cent->currentState.groundEntityNum >= ENTITYNUM_WORLD || cent->currentState.eType == ET_TERRAIN) && + !(cent->currentState.eFlags2 & EF2_HYPERSPACE) && cg.predictedPlayerState.m_iVehicleNum != cent->currentState.number) + { //always smooth when being thrown + vec3_t posDif; + float smoothFactor; + int k = 0; + float fTolerance = 20000.0f; + + if (cent->currentState.heldByClient) + { //smooth the origin more when in this state, because movement is origin-based on server. + smoothFactor = 0.2f; + } + else if ( (cent->currentState.powerups & (1 << PW_SPEED)) || + (cent->currentState.forcePowersActive & (1 << FP_RAGE)) ) + { //we're moving fast so don't smooth as much + smoothFactor = 0.6f; + } + else if (cent->currentState.eType == ET_NPC && cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { //greater smoothing for flying vehicles, since they move so fast + fTolerance = 6000000.0f;//500000.0f; //yeah, this is so wrong..but.. + smoothFactor = 0.5f; + } + else + { + smoothFactor = 0.5f; + } + + if (DistanceSquared(cent->beamEnd,cent->lerpOrigin) > smoothFactor*fTolerance) //10000 + { + VectorCopy(cent->lerpOrigin, cent->beamEnd); + } + + VectorSubtract(cent->lerpOrigin, cent->beamEnd, posDif); + + for (k=0;k<3;k++) + { + cent->beamEnd[k]=(cent->beamEnd[k]+posDif[k]*smoothFactor); + cent->lerpOrigin[k]=cent->beamEnd[k]; + } + } + else + { + VectorCopy(cent->lerpOrigin, cent->beamEnd); + } + + if (cent->currentState.m_iVehicleNum && + cent->currentState.NPC_class != CLASS_VEHICLE) + { //this player is riding a vehicle + centity_t *veh = &cg_entities[cent->currentState.m_iVehicleNum]; + + cent->lerpAngles[YAW] = veh->lerpAngles[YAW]; + + //Attach ourself to the vehicle + if (veh->m_pVehicle && + cent->playerState && + veh->playerState && + cent->ghoul2 && + veh->ghoul2 ) + { + if ( veh->currentState.owner != cent->currentState.clientNum ) + {//FIXME: what about visible passengers? + if ( CG_VehicleAttachDroidUnit( cent, &legs ) ) + { + checkDroidShields = qtrue; + } + } + else if ( veh->currentState.owner != ENTITYNUM_NONE) + {//has a pilot...??? + vec3_t oldPSOrg; + + //make sure it has its pilot and parent set + veh->m_pVehicle->m_pPilot = (bgEntity_t *)&cg_entities[veh->currentState.owner]; + veh->m_pVehicle->m_pParentEntity = (bgEntity_t *)veh; + + VectorCopy(veh->playerState->origin, oldPSOrg); + + //update the veh's playerstate org for getting the bolt + VectorCopy(veh->lerpOrigin, veh->playerState->origin); + VectorCopy(cent->lerpOrigin, cent->playerState->origin); + + //Now do the attach + VectorCopy(veh->lerpAngles, veh->playerState->viewangles); + veh->m_pVehicle->m_pVehicleInfo->AttachRiders(veh->m_pVehicle); + + //copy the "playerstate origin" to the lerpOrigin since that's what we use to display + VectorCopy(cent->playerState->origin, cent->lerpOrigin); + + VectorCopy(oldPSOrg, veh->playerState->origin); + } + } + } + + // 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 + if (cent->currentState.eType != ET_NPC) + { + clientNum = cent->currentState.clientNum; + if ( clientNum < 0 || clientNum >= MAX_CLIENTS ) { + CG_Error( "Bad clientNum on player entity"); + } + ci = &cgs.clientinfo[ clientNum ]; + } + else + { + if (!cent->npcClient) + { + CG_CreateNPCClient(¢->npcClient); //allocate memory for it + + if (!cent->npcClient) + { + assert(0); + return; + } + + memset(cent->npcClient, 0, sizeof(clientInfo_t)); + cent->npcClient->ghoul2Model = NULL; + } + + assert(cent->npcClient); + + if (cent->npcClient->ghoul2Model != cent->ghoul2 && cent->ghoul2) + { + cent->npcClient->ghoul2Model = cent->ghoul2; + if (cent->localAnimIndex <= 1) + { + cent->npcClient->bolt_rhand = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*r_hand"); + cent->npcClient->bolt_lhand = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*chestg"); + + //claw bolts + trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*r_hand_cap_r_arm"); + trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*l_hand_cap_l_arm"); + + cent->npcClient->bolt_head = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "*head_top"); + if (cent->npcClient->bolt_head == -1) + { + cent->npcClient->bolt_head = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "ceyebrow"); + } + cent->npcClient->bolt_motion = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "Motion"); + cent->npcClient->bolt_llumbar = trap_G2API_AddBolt(cent->npcClient->ghoul2Model, 0, "lower_lumbar"); + } + else + { + cent->npcClient->bolt_rhand = -1; + cent->npcClient->bolt_lhand = -1; + cent->npcClient->bolt_head = -1; + cent->npcClient->bolt_motion = -1; + cent->npcClient->bolt_llumbar = -1; + } + cent->npcClient->team = TEAM_FREE; + cent->npcClient->infoValid = qtrue; + } + ci = cent->npcClient; + } + + // it is possible to see corpses from disconnected players that may + // not have valid clientinfo + if ( !ci->infoValid ) { + return; + } + + // Add the player to the radar if on the same team and its a team game + if (cgs.gametype >= GT_TEAM) + { + if ( cent->currentState.eType != ET_NPC && + cg.snap->ps.clientNum != cent->currentState.number && + ci->team == cg.snap->ps.persistant[PERS_TEAM] ) + { + CG_AddRadarEnt(cent); + } + } + + if (cent->currentState.eType == ET_NPC && + cent->currentState.NPC_class == CLASS_VEHICLE) + { //add vehicles + CG_AddRadarEnt(cent); + if ( CG_InFighter() ) + {//this is a vehicle, bracket it + if ( cg.predictedPlayerState.m_iVehicleNum != cent->currentState.clientNum ) + {//don't add the vehicle I'm in... :) + CG_AddBracketedEnt(cent); + } + } + + } + + if (!cent->ghoul2) + { //not ready yet? +#ifdef _DEBUG + Com_Printf("WARNING: Client %i has a null ghoul2 instance\n", cent->currentState.number); +#endif + trap_G2API_ClearAttachedInstance(cent->currentState.number); + + if (ci->ghoul2Model && + trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { +#ifdef _DEBUG + Com_Printf("Clientinfo instance was valid, duplicating for cent\n"); +#endif + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, ¢->ghoul2); + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse); + + if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cent->noFace = qtrue; + } + + cent->localAnimIndex = CG_G2SkelForModel(cent->ghoul2); + cent->eventAnimIndex = CG_G2EvIndexForModel(cent->ghoul2, cent->localAnimIndex); + } + return; + } + + if (ci->superSmoothTime) + { //do crazy smoothing + if (ci->superSmoothTime > cg.time) + { //do it + trap_G2API_AbsurdSmoothing(cent->ghoul2, qtrue); + } + else + { //turn it off + ci->superSmoothTime = 0; + trap_G2API_AbsurdSmoothing(cent->ghoul2, qfalse); + } + } + + if (cg.predictedPlayerState.pm_type == PM_INTERMISSION) + { //don't show all this shit during intermission + if ( cent->currentState.eType == ET_NPC + && cent->currentState.NPC_class != CLASS_VEHICLE ) + {//NPC in intermission + } + else + {//don't render players or vehicles in intermissions, allow other NPCs for scripts + return; + } + } + + CG_VehicleEffects(cent); + + if ((cent->currentState.eFlags & EF_JETPACK) && !(cent->currentState.eFlags & EF_DEAD) && + cg_g2JetpackInstance) + { //should have a jetpack attached + //1 is rhand weap, 2 is lhand weap (akimbo sabs), 3 is jetpack + if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 3)) + { + trap_G2API_CopySpecificGhoul2Model(cg_g2JetpackInstance, 0, cent->ghoul2, 3); + } + + if (cent->currentState.eFlags & EF_JETPACK_ACTIVE) + { + mdxaBone_t mat; + vec3_t flamePos, flameDir; + int n = 0; + + while (n < 2) + { + //Get the position/dir of the flame bolt on the jetpack model bolted to the player + trap_G2API_GetBoltMatrix(cent->ghoul2, 3, n, &mat, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&mat, ORIGIN, flamePos); + + if (n == 0) + { + BG_GiveMeVectorFromMatrix(&mat, NEGATIVE_Y, flameDir); + VectorMA(flamePos, -9.5f, flameDir, flamePos); + BG_GiveMeVectorFromMatrix(&mat, POSITIVE_X, flameDir); + VectorMA(flamePos, -13.5f, flameDir, flamePos); + } + else + { + BG_GiveMeVectorFromMatrix(&mat, POSITIVE_X, flameDir); + VectorMA(flamePos, -9.5f, flameDir, flamePos); + BG_GiveMeVectorFromMatrix(&mat, NEGATIVE_Y, flameDir); + VectorMA(flamePos, -13.5f, flameDir, flamePos); + } + + if (cent->currentState.eFlags & EF_JETPACK_FLAMING) + { //create effects + //FIXME: Just one big effect + //Play the effect + trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1); + trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1); + + //Keep the jet fire sound looping + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + trap_S_RegisterSound( "sound/effects/fire_lp" ) ); + } + else + { //just idling + //FIXME: Different smaller effect for idle + //Play the effect + trap_FX_PlayEffectID(cgs.effects.mBobaJet, flamePos, flameDir, -1, -1); + } + + n++; + } + + trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + trap_S_RegisterSound( "sound/boba/JETHOVER" ) ); + } + } + else if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 3)) + { //fixme: would be good if this could be done not every frame + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 3); + } + + g2HasWeapon = trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1); + + if (!g2HasWeapon) + { //force a redup of the weapon instance onto the client instance + cent->ghoul2weapon = NULL; + cent->weapon = 0; + } + + if (cent->torsoBolt && !(cent->currentState.eFlags & EF_DEAD)) + { //he's alive and has a limb missing still, reattach it and reset the weapon + CG_ReattachLimb(cent); + } + + if (cent->isRagging && !(cent->currentState.eFlags & EF_DEAD) && !(cent->currentState.eFlags & EF_RAG)) + { //make sure we don't ragdoll ever while alive unless directly told to with eFlags + cent->isRagging = qfalse; + trap_G2API_SetRagDoll(cent->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + + if (cent->ghoul2 && cent->torsoBolt && ((cent->torsoBolt & RARMBIT) || (cent->torsoBolt & RHANDBIT) || (cent->torsoBolt & WAISTBIT)) && g2HasWeapon) + { //kill the weapon if the limb holding it is no longer on the model + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } + + if (!cent->trickAlphaTime || (cg.time - cent->trickAlphaTime) > 1000) + { //things got out of sync, perhaps a new client is trying to fill in this slot + cent->trickAlpha = 255; + cent->trickAlphaTime = cg.time; + } + + if (cent->currentState.eFlags & EF_NODRAW) + { //If nodraw, return here + return; + } + else if (cent->currentState.eFlags2 & EF2_SHIP_DEATH) + { //died in ship, don't draw, we were "obliterated" + return; + } + + //If this client has tricked you. + if (CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg.snap->ps.clientNum)) + { + if (cent->trickAlpha > 1) + { + cent->trickAlpha -= (cg.time - cent->trickAlphaTime)*0.5; + cent->trickAlphaTime = cg.time; + + if (cent->trickAlpha < 0) + { + cent->trickAlpha = 0; + } + + doAlpha = 1; + } + else + { + doAlpha = 1; + cent->trickAlpha = 1; + cent->trickAlphaTime = cg.time; + iwantout = 1; + } + } + else + { + if (cent->trickAlpha < 255) + { + cent->trickAlpha += (cg.time - cent->trickAlphaTime); + cent->trickAlphaTime = cg.time; + + if (cent->trickAlpha > 255) + { + cent->trickAlpha = 255; + } + + doAlpha = 1; + } + else + { + cent->trickAlpha = 255; + cent->trickAlphaTime = cg.time; + } + } + + // get the player model information + renderfx = 0; + if ( cent->currentState.number == cg.snap->ps.clientNum) { + if (!cg.renderingThirdPerson) { +#if 0 + if (!cg_fpls.integer || cent->currentState.weapon != WP_SABER) +#else + if (cent->currentState.weapon != WP_SABER) +#endif + { + renderfx = RF_THIRD_PERSON; // only draw in mirrors + } + } else { + if (cg_cameraMode.integer) { + iwantout = 1; + + + // goto minimal_add; + + // NOTENOTE Temporary + return; + } + } + } + + // Update the player's client entity information regarding weapons. + // Explanation: The entitystate has a weapond defined on it. The cliententity does as well. + // The cliententity's weapon tells us what the ghoul2 instance on the cliententity has bolted to it. + // If the entitystate and cliententity weapons differ, then the state's needs to be copied to the client. + // Save the old weapon, to verify that it is or is not the same as the new weapon. + // rww - Make sure weapons don't get set BEFORE cent->ghoul2 is initialized or else we'll have no + // weapon bolted on + if (cent->currentState.saberInFlight) + { + cent->ghoul2weapon = CG_G2WeaponInstance(cent, WP_SABER); + } + + if (cent->ghoul2 && + (cent->currentState.eType != ET_NPC || (cent->currentState.NPC_class != CLASS_VEHICLE&¢->currentState.NPC_class != CLASS_REMOTE&¢->currentState.NPC_class != CLASS_SEEKER)) && //don't add weapon models to NPCs that have no bolt for them! + cent->ghoul2weapon != CG_G2WeaponInstance(cent, cent->currentState.weapon) && + !(cent->currentState.eFlags & EF_DEAD) && !cent->torsoBolt && + cg.snap && (cent->currentState.number != cg.snap->ps.clientNum || (cg.snap->ps.pm_flags & PMF_FOLLOW))) + { + if (ci->team == TEAM_SPECTATOR) + { + cent->ghoul2weapon = NULL; + cent->weapon = 0; + } + else + { + CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, cent->ghoul2); + + if (cent->currentState.eType != ET_NPC) + { + if (cent->weapon == WP_SABER + && cent->weapon != cent->currentState.weapon + && !cent->currentState.saberHolstered) + { //switching away from the saber + //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" )); + if (ci->saber[0].soundOff + && !cent->currentState.saberHolstered) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[0].soundOff); + } + + if (ci->saber[1].soundOff && + ci->saber[1].model[0] && + !cent->currentState.saberHolstered) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[1].soundOff); + } + + } + else if (cent->currentState.weapon == WP_SABER + && cent->weapon != cent->currentState.weapon + && !cent->saberWasInFlight) + { //switching to the saber + //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" )); + if (ci->saber[0].soundOn) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[0].soundOn); + } + + if (ci->saber[1].soundOn) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, ci->saber[1].soundOn); + } + + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + } + + cent->weapon = cent->currentState.weapon; + cent->ghoul2weapon = CG_G2WeaponInstance(cent, cent->currentState.weapon); + } + } + else if ((cent->currentState.eFlags & EF_DEAD) || cent->torsoBolt) + { + cent->ghoul2weapon = NULL; //be sure to update after respawning/getting limb regrown + } + + + if (cent->saberWasInFlight && g2HasWeapon) + { + cent->saberWasInFlight = qfalse; + } + + memset (&legs, 0, sizeof(legs)); + + CG_SetGhoul2Info(&legs, cent); + + VectorCopy(cent->modelScale, legs.modelScale); + legs.radius = CG_RadiusForCent( cent ); + VectorClear(legs.angles); + + if (ci->colorOverride[0] != 0.0f || + ci->colorOverride[1] != 0.0f || + ci->colorOverride[2] != 0.0f) + { + legs.shaderRGBA[0] = ci->colorOverride[0]*255.0f; + legs.shaderRGBA[1] = ci->colorOverride[1]*255.0f; + legs.shaderRGBA[2] = ci->colorOverride[2]*255.0f; + legs.shaderRGBA[3] = cent->currentState.customRGBA[3]; + } + else + { + legs.shaderRGBA[0] = cent->currentState.customRGBA[0]; + legs.shaderRGBA[1] = cent->currentState.customRGBA[1]; + legs.shaderRGBA[2] = cent->currentState.customRGBA[2]; + legs.shaderRGBA[3] = cent->currentState.customRGBA[3]; + } + +// minimal_add: + + team = ci->team; + + if (cgs.gametype >= GT_TEAM && cg_drawFriend.integer && + cent->currentState.number != cg.snap->ps.clientNum && + cent->currentState.eType != ET_NPC) + { // If the view is either a spectator or on the same team as this character, show a symbol above their head. + if ((cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cg.snap->ps.persistant[PERS_TEAM] == team) && + !(cent->currentState.eFlags & EF_DEAD)) + { + if (cgs.gametype == GT_SIEGE) + { //check for per-map team shaders + if (team == SIEGETEAM_TEAM1) + { + if (cgSiegeTeam1PlShader) + { + CG_PlayerFloatSprite( cent, cgSiegeTeam1PlShader); + } + else + { //if there isn't one fallback to default + CG_PlayerFloatSprite( cent, cgs.media.teamRedShader); + } + } + else + { + if (cgSiegeTeam2PlShader) + { + CG_PlayerFloatSprite( cent, cgSiegeTeam2PlShader); + } + else + { //if there isn't one fallback to default + CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader); + } + } + } + else + { //generic teamplay + if (team == TEAM_RED) + { + CG_PlayerFloatSprite( cent, cgs.media.teamRedShader); + } + else // if (team == TEAM_BLUE) + { + CG_PlayerFloatSprite( cent, cgs.media.teamBlueShader); + } + } + } + } + else if (cgs.gametype == GT_POWERDUEL && cg_drawFriend.integer && + cent->currentState.number != cg.snap->ps.clientNum) + { + if (cg.predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR && + cent->currentState.number < MAX_CLIENTS && + !(cent->currentState.eFlags & EF_DEAD) && + ci && + cgs.clientinfo[cg.snap->ps.clientNum].duelTeam == ci->duelTeam) + { //ally in powerduel, so draw the icon + CG_PlayerFloatSprite( cent, cgs.media.powerDuelAllyShader); + } + else if (cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR && + cent->currentState.number < MAX_CLIENTS && + !(cent->currentState.eFlags & EF_DEAD) && + ci->duelTeam == DUELTEAM_DOUBLE) + { + CG_PlayerFloatSprite( cent, cgs.media.powerDuelAllyShader); + } + } + + if (cgs.gametype == GT_JEDIMASTER && cg_drawFriend.integer && + cent->currentState.number != cg.snap->ps.clientNum) // Don't show a sprite above a player's own head in 3rd person. + { // If the view is either a spectator or on the same team as this character, show a symbol above their head. + if ((cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR || cg.snap->ps.persistant[PERS_TEAM] == team) && + !(cent->currentState.eFlags & EF_DEAD)) + { + if (CG_ThereIsAMaster()) + { + if (!cg.snap->ps.isJediMaster) + { + if (!cent->currentState.isJediMaster) + { + CG_PlayerFloatSprite( cent, cgs.media.teamRedShader); + } + } + } + } + } + + // add the shadow + shadow = CG_PlayerShadow( cent, &shadowPlane ); + + if ( ((cent->currentState.eFlags & EF_SEEKERDRONE) || cent->currentState.genericenemyindex != -1) && cent->currentState.eType != ET_NPC ) + { + refEntity_t seeker; + + memset( &seeker, 0, sizeof(seeker) ); + + VectorCopy(cent->lerpOrigin, elevated); + elevated[2] += 40; + + VectorCopy( elevated, seeker.lightingOrigin ); + seeker.shadowPlane = shadowPlane; + seeker.renderfx = 0; //renderfx; + //don't show in first person? + + angle = ((cg.time / 12) & 255) * (M_PI * 2) / 255; + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 5; + VectorAdd(elevated, dir, seeker.origin); + + VectorCopy(seeker.origin, seekorg); + + if (cent->currentState.genericenemyindex > MAX_GENTITIES) + { + float prefig = (cent->currentState.genericenemyindex-cg.time)/80; + + if (prefig > 55) + { + prefig = 55; + } + else if (prefig < 1) + { + prefig = 1; + } + + elevated[2] -= 55-prefig; + + angle = ((cg.time / 12) & 255) * (M_PI * 2) / 255; + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 5; + VectorAdd(elevated, dir, seeker.origin); + } + else if (cent->currentState.genericenemyindex != ENTITYNUM_NONE && cent->currentState.genericenemyindex != -1) + { + centity_t *enent = &cg_entities[cent->currentState.genericenemyindex]; + + if (enent) + { + VectorSubtract(enent->lerpOrigin, seekorg, enang); + VectorNormalize(enang); + vectoangles(enang, angles); + successchange = 1; + } + } + + if (!successchange) + { + angles[0] = sin(angle) * 30; + angles[1] = (angle * 180 / M_PI) + 90; + if (angles[1] > 360) + angles[1] -= 360; + angles[2] = 0; + } + + AnglesToAxis( angles, seeker.axis ); + + seeker.hModel = trap_R_RegisterModel("models/items/remote.md3"); + trap_R_AddRefEntityToScene( &seeker ); + } + + // add a water splash if partially in and out of water + CG_PlayerSplash( cent ); + + if ( (cg_shadows.integer == 3 || cg_shadows.integer == 2) && shadow ) { + renderfx |= RF_SHADOW_PLANE; + } + renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all + + // if we've been hit, display proper fullscreen fx + CG_PlayerHitFX(cent); + + VectorCopy( cent->lerpOrigin, legs.origin ); + + VectorCopy( cent->lerpOrigin, legs.lightingOrigin ); + legs.shadowPlane = shadowPlane; + legs.renderfx = renderfx; + if (cg_shadows.integer == 2 && (renderfx & RF_THIRD_PERSON)) + { //can see own shadow + legs.renderfx |= RF_SHADOW_ONLY; + } + VectorCopy (legs.origin, legs.oldorigin); // don't positionally lerp at all + + CG_G2PlayerAngles( cent, legs.axis, rootAngles ); + CG_G2PlayerHeadAnims( cent ); + + if ( (cent->currentState.eFlags2&EF2_HELD_BY_MONSTER) + && cent->currentState.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor... + { + centity_t *rancor = &cg_entities[cent->currentState.lookTarget]; + if ( rancor ) + { + BG_AttachToRancor( rancor->ghoul2, //ghoul2 info + rancor->lerpAngles[YAW], + rancor->lerpOrigin, + cg.time, + cgs.gameModels, + rancor->modelScale, + (rancor->currentState.eFlags2&EF2_GENERIC_NPC_FLAG), + legs.origin, + legs.angles, + NULL ); + + if ( cent->isRagging ) + {//hack, ragdoll has you way at bottom of bounding box + VectorMA( legs.origin, 32, legs.axis[2], legs.origin ); + } + VectorCopy( legs.origin, legs.oldorigin ); + VectorCopy( legs.origin, legs.lightingOrigin ); + + VectorCopy( legs.angles, cent->lerpAngles ); + VectorCopy( cent->lerpAngles, rootAngles );//??? tempAngles );//tempAngles is needed a lot below + VectorCopy( cent->lerpAngles, cent->turAngles ); + VectorCopy( legs.origin, cent->lerpOrigin ); + } + } + //This call is mainly just to reconstruct the skeleton. But we'll get the left hand matrix while we're at it. + //If we don't reconstruct the skeleton after setting the bone angles, we will get bad bolt points on the model + //(e.g. the weapon model bolt will look "lagged") if there's no other GetBoltMatrix call for the rest of the + //frame. Yes, this is stupid and needs to be fixed properly. + //The current solution is to force it not to reconstruct the skeleton for the first GBM call in G2PlayerAngles. + //It works and we end up only reconstructing it once, but it doesn't seem like the best solution. + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + gotLHandMatrix = qtrue; +#if 0 + if (cg.renderingThirdPerson) + { + if (cgFPLSState != 0) + { + CG_ForceFPLSPlayerModel(cent, ci); + cgFPLSState = 0; + return; + } + } + else if (ci->team == TEAM_SPECTATOR || (cg.snap && (cg.snap->ps.pm_flags & PMF_FOLLOW))) + { //don't allow this when spectating + if (cgFPLSState != 0) + { + trap_Cvar_Set("cg_fpls", "0"); + cg_fpls.integer = 0; + + CG_ForceFPLSPlayerModel(cent, ci); + cgFPLSState = 0; + return; + } + + if (cg_fpls.integer) + { + trap_Cvar_Set("cg_fpls", "0"); + } + } + else + { + if (cg_fpls.integer && cent->currentState.weapon == WP_SABER && cg.snap && cent->currentState.number == cg.snap->ps.clientNum) + { + + if (cgFPLSState != cg_fpls.integer) + { + CG_ForceFPLSPlayerModel(cent, ci); + cgFPLSState = cg_fpls.integer; + return; + } + + /* + mdxaBone_t headMatrix; + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_head, &headMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&headMatrix, ORIGIN, cg.refdef.vieworg); + */ + } + else if (!cg_fpls.integer && cgFPLSState) + { + if (cgFPLSState != cg_fpls.integer) + { + CG_ForceFPLSPlayerModel(cent, ci); + cgFPLSState = cg_fpls.integer; + return; + } + } + } +#endif + + if (cent->currentState.eFlags & EF_DEAD) + { + dead = qtrue; + //rww - since our angles are fixed when we're dead this shouldn't be an issue anyway + //we need to render the dying/dead player because we are now spawning the body on respawn instead of death + //return; + } + + ScaleModelAxis(&legs); + + memset( &torso, 0, sizeof(torso) ); + + //rww - force speed "trail" effect + if (!(cent->currentState.powerups & (1 << PW_SPEED)) || doAlpha || !cg_speedTrail.integer) + { + cent->frame_minus1_refreshed = 0; + cent->frame_minus2_refreshed = 0; + } + + if (cent->frame_minus1_refreshed || + cent->frame_minus2_refreshed) + { + vec3_t tDir; + int distVelBase; + + VectorCopy(cent->currentState.pos.trDelta, tDir); + distVelBase = SPEED_TRAIL_DISTANCE*(VectorNormalize(tDir)*0.004); + + if (cent->frame_minus1_refreshed) + { + refEntity_t reframe_minus1 = legs; + reframe_minus1.renderfx |= RF_FORCE_ENT_ALPHA; + reframe_minus1.shaderRGBA[0] = legs.shaderRGBA[0]; + reframe_minus1.shaderRGBA[1] = legs.shaderRGBA[1]; + reframe_minus1.shaderRGBA[2] = legs.shaderRGBA[2]; + reframe_minus1.shaderRGBA[3] = 100; + + //rww - if the client gets a bad framerate we will only receive frame positions + //once per frame anyway, so we might end up with speed trails very spread out. + //in order to avoid that, we'll get the direction of the last trail from the player + //and place the trail refent a set distance from the player location this frame + VectorSubtract(cent->frame_minus1, legs.origin, tDir); + VectorNormalize(tDir); + + cent->frame_minus1[0] = legs.origin[0]+tDir[0]*distVelBase; + cent->frame_minus1[1] = legs.origin[1]+tDir[1]*distVelBase; + cent->frame_minus1[2] = legs.origin[2]+tDir[2]*distVelBase; + + VectorCopy(cent->frame_minus1, reframe_minus1.origin); + + //reframe_minus1.customShader = 2; + + trap_R_AddRefEntityToScene(&reframe_minus1); + } + + if (cent->frame_minus2_refreshed) + { + refEntity_t reframe_minus2 = legs; + + reframe_minus2.renderfx |= RF_FORCE_ENT_ALPHA; + reframe_minus2.shaderRGBA[0] = legs.shaderRGBA[0]; + reframe_minus2.shaderRGBA[1] = legs.shaderRGBA[1]; + reframe_minus2.shaderRGBA[2] = legs.shaderRGBA[2]; + reframe_minus2.shaderRGBA[3] = 50; + + //Same as above but do it between trail points instead of the player and first trail entry + VectorSubtract(cent->frame_minus2, cent->frame_minus1, tDir); + VectorNormalize(tDir); + + cent->frame_minus2[0] = cent->frame_minus1[0]+tDir[0]*distVelBase; + cent->frame_minus2[1] = cent->frame_minus1[1]+tDir[1]*distVelBase; + cent->frame_minus2[2] = cent->frame_minus1[2]+tDir[2]*distVelBase; + + VectorCopy(cent->frame_minus2, reframe_minus2.origin); + + //reframe_minus2.customShader = 2; + + trap_R_AddRefEntityToScene(&reframe_minus2); + } + } + + //trigger animation-based sounds, done before next lerp frame. + CG_TriggerAnimSounds(cent); + + // 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 ); + + // add the talk baloon or disconnect icon + CG_PlayerSprites( cent ); + + if (cent->currentState.eFlags & EF_DEAD) + { //keep track of death anim frame for when we copy off the bodyqueue + ci->frame = cent->pe.torso.frame; + } + + if (cent->currentState.activeForcePass > FORCE_LEVEL_3 + && cent->currentState.NPC_class != CLASS_VEHICLE) + { + vec3_t axis[3]; + vec3_t tAng, fAng, fxDir; + vec3_t efOrg; + + int realForceLev = (cent->currentState.activeForcePass - FORCE_LEVEL_3); + + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + VectorSet( fAng, cent->pe.torso.pitchAngle, cent->pe.torso.yawAngle, 0 ); + + AngleVectors( fAng, fxDir, NULL, NULL ); + + if ( cent->currentState.torsoAnim == BOTH_FORCE_2HANDEDLIGHTNING_HOLD + && Q_irand( 0, 1 ) ) + {//alternate back and forth between left and right + mdxaBone_t rHandMatrix; + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &rHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + efOrg[0] = rHandMatrix.matrix[0][3]; + efOrg[1] = rHandMatrix.matrix[1][3]; + efOrg[2] = rHandMatrix.matrix[2][3]; + } + else + { + //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + if (!gotLHandMatrix) + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + gotLHandMatrix = qtrue; + } + efOrg[0] = lHandMatrix.matrix[0][3]; + efOrg[1] = lHandMatrix.matrix[1][3]; + efOrg[2] = lHandMatrix.matrix[2][3]; + } + + AnglesToAxis( fAng, axis ); + + if ( realForceLev > FORCE_LEVEL_2 ) + {//arc + //trap_FX_PlayEffectID( cgs.effects.forceLightningWide, efOrg, fxDir ); + //trap_FX_PlayEntityEffectID(cgs.effects.forceDrainWide, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); + trap_FX_PlayEntityEffectID(cgs.effects.forceDrainWide, efOrg, axis, -1, -1, -1, -1); + } + else + {//line + //trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir ); + //trap_FX_PlayEntityEffectID(cgs.effects.forceDrain, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); + trap_FX_PlayEntityEffectID(cgs.effects.forceDrain, efOrg, axis, -1, -1, -1, -1); + } + + /* + if (cent->bolt4 < cg.time) + { + cent->bolt4 = cg.time + 100; + trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/drain.wav") ); + } + */ + } + else if ( cent->currentState.activeForcePass + && cent->currentState.NPC_class != CLASS_VEHICLE) + {//doing the electrocuting + vec3_t axis[3]; + vec3_t tAng, fAng, fxDir; + vec3_t efOrg; + + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + VectorSet( fAng, cent->pe.torso.pitchAngle, cent->pe.torso.yawAngle, 0 ); + + AngleVectors( fAng, fxDir, NULL, NULL ); + + //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + if (!gotLHandMatrix) + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + gotLHandMatrix = qtrue; + } + + efOrg[0] = lHandMatrix.matrix[0][3]; + efOrg[1] = lHandMatrix.matrix[1][3]; + efOrg[2] = lHandMatrix.matrix[2][3]; + + AnglesToAxis( fAng, axis ); + + if ( cent->currentState.activeForcePass > FORCE_LEVEL_2 ) + {//arc + //trap_FX_PlayEffectID( cgs.effects.forceLightningWide, efOrg, fxDir ); + //trap_FX_PlayEntityEffectID(cgs.effects.forceLightningWide, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); + trap_FX_PlayEntityEffectID(cgs.effects.forceLightningWide, efOrg, axis, -1, -1, -1, -1); + } + else + {//line + //trap_FX_PlayEffectID( cgs.effects.forceLightning, efOrg, fxDir ); + //trap_FX_PlayEntityEffectID(cgs.effects.forceLightning, efOrg, axis, cent->boltInfo, cent->currentState.number, -1, -1); + trap_FX_PlayEntityEffectID(cgs.effects.forceLightning, efOrg, axis, -1, -1, -1, -1); + } + + /* + if (cent->bolt4 < cg.time) + { + cent->bolt4 = cg.time + 100; + trap_S_StartSound(NULL, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound("sound/weapons/force/lightning.wav") ); + } + */ + } + + //fullbody push effect + if (cent->currentState.eFlags & EF_BODYPUSH) + { + CG_ForcePushBodyBlur(cent); + } + + if ( cent->currentState.powerups & (1 << PW_DISINT_4) ) + { + vec3_t tAng; + vec3_t efOrg; + + //VectorSet( tAng, 0, cent->pe.torso.yawAngle, 0 ); + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + //trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + if (!gotLHandMatrix) + { + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_lhand, &lHandMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + gotLHandMatrix = qtrue; + } + + efOrg[0] = lHandMatrix.matrix[0][3]; + efOrg[1] = lHandMatrix.matrix[1][3]; + efOrg[2] = lHandMatrix.matrix[2][3]; + + if ( (cent->currentState.forcePowersActive & (1 << FP_GRIP)) && + (cg.renderingThirdPerson || cent->currentState.number != cg.snap->ps.clientNum) ) + { + vec3_t boltDir; + vec3_t origBolt; + VectorCopy(efOrg, origBolt); + BG_GiveMeVectorFromMatrix( &lHandMatrix, NEGATIVE_Y, boltDir ); + + CG_ForceGripEffect( efOrg ); + CG_ForceGripEffect( efOrg ); + + /* + //Render a scaled version of the model's hand with a n337 looking shader + { + const char *rotateBone; + char *limbName; + char *limbCapName; + vec3_t armAng; + refEntity_t regrip_arm; + float wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f; + + //rotateBone = "lradius"; + rotateBone = "lradiusX"; + limbName = "l_arm"; + limbCapName = "l_arm_cap_torso"; + + if (cent->grip_arm && trap_G2_HaveWeGhoul2Models(cent->grip_arm)) + { + trap_G2API_CleanGhoul2Models(&(cent->grip_arm)); + } + + memset( ®rip_arm, 0, sizeof(regrip_arm) ); + + VectorCopy(origBolt, efOrg); + + + //efOrg[2] += 8; + efOrg[2] -= 4; + + VectorCopy(efOrg, regrip_arm.origin); + VectorCopy(regrip_arm.origin, regrip_arm.lightingOrigin); + + //VectorCopy(cent->lerpAngles, armAng); + VectorAdd(vec3_origin, rootAngles, armAng); + //armAng[ROLL] = -90; + armAng[ROLL] = 0; + armAng[PITCH] = 0; + AnglesToAxis(armAng, regrip_arm.axis); + + trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, ¢->grip_arm); + + //remove all other models + if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 1)) + { //weapon right + trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 1); + } + if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 2)) + { //weapon left + trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 2); + } + if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->grip_arm), 3)) + { //jetpack + trap_G2API_RemoveGhoul2Model(&(cent->grip_arm), 3); + } + + trap_G2API_SetRootSurface(cent->grip_arm, 0, limbName); + trap_G2API_SetNewOrigin(cent->grip_arm, trap_G2API_AddBolt(cent->grip_arm, 0, rotateBone)); + trap_G2API_SetSurfaceOnOff(cent->grip_arm, limbCapName, 0); + + regrip_arm.modelScale[0] = 1;//+(wv*6); + regrip_arm.modelScale[1] = 1;//+(wv*6); + regrip_arm.modelScale[2] = 1;//+(wv*6); + ScaleModelAxis(®rip_arm); + + regrip_arm.radius = 64; + + regrip_arm.customShader = trap_R_RegisterShader( "gfx/misc/red_portashield" ); + + regrip_arm.renderfx |= RF_RGB_TINT; + regrip_arm.shaderRGBA[0] = 255 - (wv*900); + if (regrip_arm.shaderRGBA[0] < 30) + { + regrip_arm.shaderRGBA[0] = 30; + } + if (regrip_arm.shaderRGBA[0] > 255) + { + regrip_arm.shaderRGBA[0] = 255; + } + regrip_arm.shaderRGBA[1] = regrip_arm.shaderRGBA[2] = regrip_arm.shaderRGBA[0]; + + regrip_arm.ghoul2 = cent->grip_arm; + trap_R_AddRefEntityToScene( ®rip_arm ); + } + */ + } + else if (!(cent->currentState.forcePowersActive & (1 << FP_GRIP))) + { + //use refractive effect + CG_ForcePushBlur( efOrg, cent ); + } + } + else if (cent->bodyFadeTime) + { //reset the counter for keeping track of push refraction effect state + cent->bodyFadeTime = 0; + } + + if (cent->currentState.weapon == WP_STUN_BATON && cent->currentState.number == cg.snap->ps.clientNum) + { + trap_S_AddLoopingSound( cent->currentState.number, cg.refdef.vieworg, vec3_origin, + trap_S_RegisterSound( "sound/weapons/baton/idle.wav" ) ); + } + + //NOTE: All effects that should be visible during mindtrick should go above here + + if (iwantout) + { + goto stillDoSaber; + //return; + } + else if (doAlpha) + { + legs.renderfx |= RF_FORCE_ENT_ALPHA; + legs.shaderRGBA[3] = cent->trickAlpha; + + if (legs.shaderRGBA[3] < 1) + { //don't cancel it out even if it's < 1 + legs.shaderRGBA[3] = 1; + } + } + + if (cent->teamPowerEffectTime > cg.time) + { + if (cent->teamPowerType == 3) + { //absorb is a somewhat different effect entirely + //Guess I'll take care of it where it's always been, just checking these values instead. + } + else + { + vec4_t preCol; + int preRFX; + + preRFX = legs.renderfx; + + legs.renderfx |= RF_RGB_TINT; + legs.renderfx |= RF_FORCE_ENT_ALPHA; + + preCol[0] = legs.shaderRGBA[0]; + preCol[1] = legs.shaderRGBA[1]; + preCol[2] = legs.shaderRGBA[2]; + preCol[3] = legs.shaderRGBA[3]; + + if (cent->teamPowerType == 1) + { //heal + legs.shaderRGBA[0] = 0; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + } + else if (cent->teamPowerType == 0) + { //regen + legs.shaderRGBA[0] = 0; + legs.shaderRGBA[1] = 0; + legs.shaderRGBA[2] = 255; + } + else + { //drain + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 0; + legs.shaderRGBA[2] = 0; + } + + legs.shaderRGBA[3] = ((cent->teamPowerEffectTime - cg.time)/8); + + legs.customShader = trap_R_RegisterShader( "powerups/ysalimarishell" ); + trap_R_AddRefEntityToScene(&legs); + + legs.customShader = 0; + legs.renderfx = preRFX; + legs.shaderRGBA[0] = preCol[0]; + legs.shaderRGBA[1] = preCol[1]; + legs.shaderRGBA[2] = preCol[2]; + legs.shaderRGBA[3] = preCol[3]; + } + } + + //If you've tricked this client. + if (CG_IsMindTricked(cg.snap->ps.fd.forceMindtrickTargetIndex, + cg.snap->ps.fd.forceMindtrickTargetIndex2, + cg.snap->ps.fd.forceMindtrickTargetIndex3, + cg.snap->ps.fd.forceMindtrickTargetIndex4, + cent->currentState.number)) + { + if (cent->ghoul2) + { + vec3_t efOrg; + vec3_t tAng, fxAng; + vec3_t axis[3]; + + //VectorSet( tAng, 0, cent->pe.torso.yawAngle, 0 ); + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_head, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, efOrg); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, fxAng); + + axis[0][0] = boltMatrix.matrix[0][0]; + axis[0][1] = boltMatrix.matrix[1][0]; + axis[0][2] = boltMatrix.matrix[2][0]; + + axis[1][0] = boltMatrix.matrix[0][1]; + axis[1][1] = boltMatrix.matrix[1][1]; + axis[1][2] = boltMatrix.matrix[2][1]; + + axis[2][0] = boltMatrix.matrix[0][2]; + axis[2][1] = boltMatrix.matrix[1][2]; + axis[2][2] = boltMatrix.matrix[2][2]; + + //trap_FX_PlayEntityEffectID(trap_FX_RegisterEffect("force/confusion.efx"), efOrg, axis, cent->boltInfo, cent->currentState.number); + trap_FX_PlayEntityEffectID(cgs.effects.mForceConfustionOld, efOrg, axis, -1, -1, -1, -1); + } + } + + if (cgs.gametype == GT_HOLOCRON && cent->currentState.time2 && (cg.renderingThirdPerson || cg.snap->ps.clientNum != cent->currentState.number)) + { + int i = 0; + int renderedHolos = 0; + refEntity_t holoRef; + + while (i < NUM_FORCE_POWERS && renderedHolos < 3) + { + if (cent->currentState.time2 & (1 << i)) + { + memset( &holoRef, 0, sizeof(holoRef) ); + + VectorCopy(cent->lerpOrigin, elevated); + elevated[2] += 8; + + VectorCopy( elevated, holoRef.lightingOrigin ); + holoRef.shadowPlane = shadowPlane; + holoRef.renderfx = 0;//RF_THIRD_PERSON; + + if (renderedHolos == 0) + { + angle = ((cg.time / 8) & 255) * (M_PI * 2) / 255; + dir[0] = cos(angle) * 20; + dir[1] = sin(angle) * 20; + dir[2] = cos(angle) * 20; + VectorAdd(elevated, dir, holoRef.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, holoRef.axis ); + } + else if (renderedHolos == 1) + { + angle = ((cg.time / 8) & 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(elevated, dir, holoRef.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, holoRef.axis ); + } + else + { + angle = ((cg.time / 6) & 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(elevated, dir, holoRef.origin); + + VectorCopy(dir, holoRef.axis[1]); + VectorNormalize(holoRef.axis[1]); + VectorSet(holoRef.axis[2], 0, 0, 1); + CrossProduct(holoRef.axis[1], holoRef.axis[2], holoRef.axis[0]); + } + + holoRef.modelScale[0] = 0.5; + holoRef.modelScale[1] = 0.5; + holoRef.modelScale[2] = 0.5; + ScaleModelAxis(&holoRef); + + { + float wv; + addspriteArgStruct_t fxSArgs; + vec3_t holoCenter; + + holoCenter[0] = holoRef.origin[0] + holoRef.axis[2][0]*18; + holoCenter[1] = holoRef.origin[1] + holoRef.axis[2][1]*18; + holoCenter[2] = holoRef.origin[2] + holoRef.axis[2][2]*18; + + wv = sin( cg.time * 0.004f ) * 0.08f + 0.1f; + + VectorCopy(holoCenter, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = wv*60; + fxSArgs.dscale = wv*60; + fxSArgs.sAlpha = wv*12; + fxSArgs.eAlpha = wv*12; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + + fxSArgs.flags = 0x08000000|0x00000001; + + if (forcePowerDarkLight[i] == FORCE_DARKSIDE) + { //dark + fxSArgs.sAlpha *= 3; + fxSArgs.eAlpha *= 3; + fxSArgs.shader = cgs.media.redSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else if (forcePowerDarkLight[i] == FORCE_LIGHTSIDE) + { //light + fxSArgs.sAlpha *= 1.5; + fxSArgs.eAlpha *= 1.5; + fxSArgs.shader = cgs.media.redSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.blueSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else + { //neutral + if (i == FP_SABER_OFFENSE || + i == FP_SABER_DEFENSE || + i == FP_SABERTHROW) + { //saber power + fxSArgs.sAlpha *= 1.5; + fxSArgs.eAlpha *= 1.5; + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + else + { + fxSArgs.sAlpha *= 0.5; + fxSArgs.eAlpha *= 0.5; + fxSArgs.shader = cgs.media.greenSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + fxSArgs.shader = cgs.media.blueSaberGlowShader; + trap_FX_AddSprite(&fxSArgs); + } + } + } + + holoRef.hModel = trap_R_RegisterModel(forceHolocronModels[i]); + trap_R_AddRefEntityToScene( &holoRef ); + + renderedHolos++; + } + i++; + } + } + + if ((cent->currentState.powerups & (1 << PW_YSALAMIRI)) || + (cgs.gametype == GT_CTY && ((cent->currentState.powerups & (1 << PW_REDFLAG)) || (cent->currentState.powerups & (1 << PW_BLUEFLAG)))) ) + { + if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_REDFLAG))) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysaliredShader ); + } + else if (cgs.gametype == GT_CTY && (cent->currentState.powerups & (1 << PW_BLUEFLAG))) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysaliblueShader ); + } + else + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.4f, cgs.media.ysalimariShader ); + } + } + + if (cent->currentState.powerups & (1 << PW_FORCE_BOON)) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.boonShader ); + } + + if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_DARK)) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.endarkenmentShader ); + } + else if (cent->currentState.powerups & (1 << PW_FORCE_ENLIGHTENED_LIGHT)) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 2.0f, cgs.media.enlightenmentShader ); + } + + if (cent->currentState.eFlags & EF_INVULNERABLE) + { + CG_DrawPlayerSphere(cent, cent->lerpOrigin, 1.0f, cgs.media.invulnerabilityShader ); + } +stillDoSaber: + if ((cent->currentState.eFlags & EF_DEAD) && cent->currentState.weapon == WP_SABER) + { + //cent->saberLength = 0; + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + drawPlayerSaber = qtrue; + } + else if (cent->currentState.weapon == WP_SABER + && cent->currentState.saberHolstered < 2 ) + { + if ( (!cent->currentState.saberInFlight //saber not in flight + || ci->saber[1].soundLoop) //??? + && !(cent->currentState.eFlags & EF_DEAD))//still alive + { + vec3_t soundSpot; + qboolean didFirstSound = qfalse; + + if (cg.snap->ps.clientNum == cent->currentState.number) + { + //trap_S_AddLoopingSound( cent->currentState.number, cg.refdef.vieworg, vec3_origin, + // trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) ); + VectorCopy(cg.refdef.vieworg, soundSpot); + } + else + { + //trap_S_AddLoopingSound( cent->currentState.number, cent->lerpOrigin, vec3_origin, + // trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ) ); + VectorCopy(cent->lerpOrigin, soundSpot); + } + + if (ci->saber[0].model[0] + && ci->saber[0].soundLoop + && !cent->currentState.saberInFlight) + { + int i = 0; + qboolean hasLen = qfalse; + + while (i < ci->saber[0].numBlades) + { + if (ci->saber[0].blade[i].length) + { + hasLen = qtrue; + break; + } + i++; + } + + if (hasLen) + { + trap_S_AddLoopingSound( cent->currentState.number, soundSpot, vec3_origin, + ci->saber[0].soundLoop ); + didFirstSound = qtrue; + } + } + if (ci->saber[1].model[0] + && ci->saber[1].soundLoop + && (!didFirstSound || ci->saber[0].soundLoop != ci->saber[1].soundLoop)) + { + int i = 0; + qboolean hasLen = qfalse; + + while (i < ci->saber[1].numBlades) + { + if (ci->saber[1].blade[i].length) + { + hasLen = qtrue; + break; + } + i++; + } + + if (hasLen) + { + trap_S_AddLoopingSound( cent->currentState.number, soundSpot, vec3_origin, + ci->saber[1].soundLoop ); + } + } + } + + if (iwantout + && !cent->currentState.saberInFlight) + { + if (cent->currentState.eFlags & EF_DEAD) + { + if (cent->ghoul2 + && cent->currentState.saberInFlight + && g2HasWeapon) + { //special case, kill the saber on a freshly dead player if another source says to. + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } + } + return; + //goto endOfCall; + } + + if (g2HasWeapon + && cent->currentState.saberInFlight) + { //keep this set, so we don't re-unholster the thing when we get it back, even if it's knocked away. + cent->saberWasInFlight = qtrue; + } + + if (cent->currentState.saberInFlight + && cent->currentState.saberEntityNum) + { + centity_t *saberEnt; + + saberEnt = &cg_entities[cent->currentState.saberEntityNum]; + + if (/*!cent->bolt4 &&*/ g2HasWeapon || !cent->bolt3 || + saberEnt->serverSaberHitIndex != saberEnt->currentState.modelindex/*|| !cent->saberLength*/) + { //saber is in flight, do not have it as a standard weapon model + qboolean addBolts = qfalse; + mdxaBone_t boltMat; + + if (g2HasWeapon) + { + //ah well, just stick it over the right hand right now. + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &boltMat, cent->turAngles, cent->lerpOrigin, + cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMat, ORIGIN, saberEnt->currentState.pos.trBase); + + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } + + //cent->bolt4 = 1; + + saberEnt->currentState.pos.trTime = cg.time; + saberEnt->currentState.apos.trTime = cg.time; + + VectorCopy(saberEnt->currentState.pos.trBase, saberEnt->lerpOrigin); + VectorCopy(saberEnt->currentState.apos.trBase, saberEnt->lerpAngles); + + cent->bolt3 = saberEnt->currentState.apos.trBase[0]; + if (!cent->bolt3) + { + cent->bolt3 = 1; + } + cent->bolt2 = 0; + + saberEnt->currentState.bolt2 = 123; + + if (saberEnt->ghoul2 && + saberEnt->serverSaberHitIndex == saberEnt->currentState.modelindex) + { + // now set up the gun bolt on it + addBolts = qtrue; + } + else + { + const char *saberModel = CG_ConfigString( CS_MODELS+saberEnt->currentState.modelindex ); + + saberEnt->serverSaberHitIndex = saberEnt->currentState.modelindex; + + if (saberEnt->ghoul2) + { //clean if we already have one (because server changed model string index) + trap_G2API_CleanGhoul2Models(&(saberEnt->ghoul2)); + saberEnt->ghoul2 = 0; + } + + if (saberModel && saberModel[0]) + { + trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, saberModel, 0, 0, 0, 0, 0); + } + else if (ci->saber[0].model[0]) + { + trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, ci->saber[0].model, 0, 0, 0, 0, 0); + } + else + { + trap_G2API_InitGhoul2Model(&saberEnt->ghoul2, "models/weapons2/saber/saber_w.glm", 0, 0, 0, 0, 0); + } + //trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, &saberEnt->ghoul2); + + if (saberEnt->ghoul2) + { + addBolts = qtrue; + //cent->bolt4 = 2; + + VectorCopy(saberEnt->currentState.pos.trBase, saberEnt->lerpOrigin); + VectorCopy(saberEnt->currentState.apos.trBase, saberEnt->lerpAngles); + saberEnt->currentState.pos.trTime = cg.time; + saberEnt->currentState.apos.trTime = cg.time; + } + } + + if (addBolts) + { + int m = 0; + int tagBolt; + char *tagName; + + while (m < ci->saber[0].numBlades) + { + tagName = va("*blade%i", m+1); + tagBolt = trap_G2API_AddBolt(saberEnt->ghoul2, 0, tagName); + + if (tagBolt == -1) + { + if (m == 0) + { //guess this is an 0ldsk3wl saber + tagBolt = trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash"); + + if (tagBolt == -1) + { + assert(0); + } + break; + } + + if (tagBolt == -1) + { + assert(0); + break; + } + } + + m++; + } + } + } + /*else if (cent->bolt4 != 2) + { + if (saberEnt->ghoul2) + { + trap_G2API_AddBolt(saberEnt->ghoul2, 0, "*flash"); + cent->bolt4 = 2; + } + }*/ + + if (saberEnt && saberEnt->ghoul2 /*&& cent->bolt4 == 2*/) + { + vec3_t bladeAngles; + vec3_t tAng; + vec3_t efOrg; + float wv; + int k = 0; + int l = 0; + addspriteArgStruct_t fxSArgs; + + if (!cent->bolt2) + { + cent->bolt2 = cg.time; + } + + if (cent->bolt3 != 90) + { + if (cent->bolt3 < 90) + { + cent->bolt3 += (cg.time - cent->bolt2)*0.5; + + if (cent->bolt3 > 90) + { + cent->bolt3 = 90; + } + } + else if (cent->bolt3 > 90) + { + cent->bolt3 -= (cg.time - cent->bolt2)*0.5; + + if (cent->bolt3 < 90) + { + cent->bolt3 = 90; + } + } + } + + cent->bolt2 = cg.time; + + saberEnt->currentState.apos.trBase[0] = cent->bolt3; + saberEnt->lerpAngles[0] = cent->bolt3; + + if (!saberEnt->currentState.saberInFlight && saberEnt->currentState.bolt2 != 123) + { //owner is pulling is back + if ( !(ci->saber[0].saberFlags&SFL_RETURN_DAMAGE) + || cent->currentState.saberHolstered ) + { + vec3_t owndir; + + VectorSubtract(saberEnt->lerpOrigin, cent->lerpOrigin, owndir); + VectorNormalize(owndir); + + vectoangles(owndir, owndir); + + owndir[0] += 90; + + VectorCopy(owndir, saberEnt->currentState.apos.trBase); + VectorCopy(owndir, saberEnt->lerpAngles); + VectorClear(saberEnt->currentState.apos.trDelta); + } + } + + //We don't actually want to rely entirely on server updates to render the position of the saber, because we actually know generally where + //it's going to be before the first position update even gets here, and it needs to start getting rendered the instant the saber model is + //removed from the player hand. So we'll just render it manually and let normal rendering for the entity be ignored. + if (!saberEnt->currentState.saberInFlight && saberEnt->currentState.bolt2 != 123) + { //tell it that we're a saber and to render the glow around our handle because we're being pulled back + saberEnt->bolt3 = 999; + } + + saberEnt->currentState.modelGhoul2 = 1; + CG_ManualEntityRender(saberEnt); + saberEnt->bolt3 = 0; + saberEnt->currentState.modelGhoul2 = 127; + + VectorCopy(saberEnt->lerpAngles, bladeAngles); + bladeAngles[ROLL] = 0; + + if ( ci->saber[0].numBlades > 1//staff + && cent->currentState.saberHolstered == 1 )//extra blades off + {//only first blade should be on + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[0], -1, 0); + } + else + { + BG_SI_SetDesiredLength(&ci->saber[0], -1, -1); + } + if ( ci->saber[1].model //dual sabers + && cent->currentState.saberHolstered == 1 )//second one off + { + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + else + { + BG_SI_SetDesiredLength(&ci->saber[1], -1, -1); + } + + //while (l < MAX_SABERS) + //Only want to do for the first saber actually, it's the one in flight. + while (l < 1) + { + if (!ci->saber[l].model[0]) + { + break; + } + + k = 0; + while (k < ci->saber[l].numBlades) + { + if ( //cent->currentState.fireflag == SS_STAFF&& //in saberstaff style + l == 0//first saber + && cent->currentState.saberHolstered == 1 //extra blades should be off + && k > 0 )//this is an extra blade + {//extra blades off + //don't draw them + CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, l, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qtrue); + } + else + { + CG_AddSaberBlade(cent, saberEnt, NULL, 0, 0, l, k, saberEnt->lerpOrigin, bladeAngles, qtrue, qfalse); + } + + k++; + } + if ( ci->saber[l].numBlades > 2 ) + {//add a single glow for the saber based on all the blade colors combined + CG_DoSaberLight( &ci->saber[l] ); + } + + l++; + } + + //Make the player's hand glow while guiding the saber + VectorSet( tAng, cent->turAngles[PITCH], cent->turAngles[YAW], cent->turAngles[ROLL] ); + + trap_G2API_GetBoltMatrix(cent->ghoul2, 0, ci->bolt_rhand, &boltMatrix, tAng, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + + efOrg[0] = boltMatrix.matrix[0][3]; + efOrg[1] = boltMatrix.matrix[1][3]; + efOrg[2] = boltMatrix.matrix[2][3]; + + wv = sin( cg.time * 0.003f ) * 0.08f + 0.1f; + + //trap_FX_AddSprite( NULL, efOrg, NULL, NULL, 8.0f, 8.0f, wv, wv, 0.0f, 0.0f, 1.0f, cgs.media.yellowSaberGlowShader, 0x08000000 ); + VectorCopy(efOrg, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = 8.0f; + fxSArgs.dscale = 8.0f; + fxSArgs.sAlpha = wv; + fxSArgs.eAlpha = wv; + fxSArgs.rotation = 0.0f; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + fxSArgs.shader = cgs.media.yellowDroppedSaberShader; + fxSArgs.flags = 0x08000000; + trap_FX_AddSprite(&fxSArgs); + } + } + else + { + if ( ci->saber[0].numBlades > 1//staff + && cent->currentState.saberHolstered == 1 )//extra blades off + {//only first blade should be on + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[0], -1, 0); + } + else + { + BG_SI_SetDesiredLength(&ci->saber[0], -1, -1); + } + if ( ci->saber[1].model //dual sabers + && cent->currentState.saberHolstered == 1 )//second one off + { + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + else + { + BG_SI_SetDesiredLength(&ci->saber[1], -1, -1); + } + } + + //If the arm the saber is in is broken, turn it off. + /* + if (cent->currentState.brokenLimbs & (1 << BROKENLIMB_RARM)) + { + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + } + */ + //Leaving right arm on, at least for now. + if (cent->currentState.brokenLimbs & (1 << BROKENLIMB_LARM)) + { + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + + if (!cent->currentState.saberEntityNum) + { + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + //BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + /* + else + { + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + } + */ + drawPlayerSaber = qtrue; + } + else if (cent->currentState.weapon == WP_SABER) + { + //cent->saberLength = 0; + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + drawPlayerSaber = qtrue; + } + else + { + //cent->saberLength = 0; + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + BG_SI_SetLength(&ci->saber[0], 0); + BG_SI_SetLength(&ci->saber[1], 0); + } + +#ifdef _RAG_BOLT_TESTING + if (cent->currentState.eFlags & EF_RAG) + { + CG_TempTestFunction(cent, cent->turAngles); + } +#endif + + if (cent->currentState.weapon == WP_SABER) + { + BG_SI_SetLengthGradual(&ci->saber[0], cg.time); + BG_SI_SetLengthGradual(&ci->saber[1], cg.time); + } + + if (drawPlayerSaber) + { + centity_t *saberEnt; + int k = 0; + int l = 0; + + if (!cent->currentState.saberEntityNum) + { + l = 1; //The "primary" saber is missing or in flight or something, so only try to draw in the second one + } + else if (!cent->currentState.saberInFlight) + { + saberEnt = &cg_entities[cent->currentState.saberEntityNum]; + + if (/*cent->bolt4 && */!g2HasWeapon) + { + trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, WP_SABER), 0, cent->ghoul2, 1); + + if (saberEnt && saberEnt->ghoul2) + { + trap_G2API_CleanGhoul2Models(&(saberEnt->ghoul2)); + } + + saberEnt->currentState.modelindex = 0; + saberEnt->ghoul2 = NULL; + VectorClear(saberEnt->currentState.pos.trBase); + } + + cent->bolt3 = 0; + cent->bolt2 = 0; + } + else + { + l = 1; //The "primary" saber is missing or in flight or something, so only try to draw in the second one + } + + while (l < MAX_SABERS) + { + k = 0; + + if (!ci->saber[l].model[0]) + { + break; + } + + if (cent->currentState.eFlags2&EF2_HELD_BY_MONSTER) + { + //vectoangles(legs.axis[0], rootAngles); +#if 0 + if ( cent->currentState.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor... + { + centity_t *rancor = &cg_entities[cent->currentState.lookTarget]; + if ( rancor && rancor->ghoul2 ) + { + BG_AttachToRancor( rancor->ghoul2, //ghoul2 info + rancor->lerpAngles[YAW], + rancor->lerpOrigin, + cg.time, + cgs.gameModels, + rancor->modelScale, + (rancor->currentState.eFlags2&EF2_GENERIC_NPC_FLAG), + legs.origin, + rootAngles, + NULL ); + } + } +#else + vectoangles(legs.axis[0], rootAngles); +#endif + } + + while (k < ci->saber[l].numBlades) + { + if ( //cent->currentState.fireflag == SS_STAFF&& //in saberstaff style + cent->currentState.saberHolstered == 1 //extra blades should be off + && k > 0 //this is an extra blade + && ci->saber[l].blade[k].length <= 0 )//it's completely off + {//extra blades off + //don't draw them + CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qtrue); + } + else if ( ci->saber[1].model[0]//we have a second saber + && cent->currentState.saberHolstered == 1 //it should be off + && l > 0//and this is the second one + && ci->saber[l].blade[k].length <= 0 )//it's completely off + {//second saber is turned off and this blade is done with turning off + CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qtrue); + } + else + { + CG_AddSaberBlade( cent, cent, NULL, 0, 0, l, k, legs.origin, rootAngles, qfalse, qfalse); + } + + k++; + } + if ( ci->saber[l].numBlades > 2 ) + {//add a single glow for the saber based on all the blade colors combined + CG_DoSaberLight( &ci->saber[l] ); + } + + l++; + } + } + + if (cent->currentState.saberInFlight && !cent->currentState.saberEntityNum) + { //reset the length if the saber is knocked away + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + if (g2HasWeapon) + { //and remember to kill the bolton model in case we didn't get a thrown saber update first + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } + cent->bolt3 = 0; + cent->bolt2 = 0; + } + + if (cent->currentState.eFlags & EF_DEAD) + { + if (cent->ghoul2 && cent->currentState.saberInFlight && g2HasWeapon) + { //special case, kill the saber on a freshly dead player if another source says to. + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + g2HasWeapon = qfalse; + } + } + + if (iwantout) + { + return; + //goto endOfCall; + } + + if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg.snap->ps.clientNum != cent->currentState.number) + { + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + legs.renderfx |= RF_MINLIGHT; + } + + if (cg.snap->ps.duelInProgress /*&& cent->currentState.number != cg.snap->ps.clientNum*/) + { //I guess go ahead and glow your own client too in a duel + if (cent->currentState.number != cg.snap->ps.duelIndex && + cent->currentState.number != cg.snap->ps.clientNum) + { //everyone not involved in the duel is drawn very dark + legs.shaderRGBA[0] /= 5.0f; + legs.shaderRGBA[1] /= 5.0f; + legs.shaderRGBA[2] /= 5.0f; + legs.renderfx |= RF_RGB_TINT; + } + else + { //adjust the glow by how far away you are from your dueling partner + centity_t *duelEnt; + + duelEnt = &cg_entities[cg.snap->ps.duelIndex]; + + if (duelEnt) + { + vec3_t vecSub; + float subLen = 0; + + VectorSubtract(duelEnt->lerpOrigin, cg.snap->ps.origin, vecSub); + subLen = VectorLength(vecSub); + + if (subLen < 1) + { + subLen = 1; + } + + if (subLen > 1020) + { + subLen = 1020; + } + + { + const unsigned char savRGBA[3] = {legs.shaderRGBA[0],legs.shaderRGBA[1],legs.shaderRGBA[2]}; + legs.shaderRGBA[0] = max(255-subLen/4,1); + legs.shaderRGBA[1] = max(255-subLen/4,1); + legs.shaderRGBA[2] = max(255-subLen/4,1); + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.customShader = cgs.media.forceShell; + + trap_R_AddRefEntityToScene( &legs ); //draw the shell + + legs.customShader = 0; //reset to player model + + legs.shaderRGBA[0] = max(savRGBA[0]-subLen/8,1); + legs.shaderRGBA[1] = max(savRGBA[1]-subLen/8,1); + legs.shaderRGBA[2] = max(savRGBA[2]-subLen/8,1); + } + + if (subLen <= 1024) + { + legs.renderfx |= RF_RGB_TINT; + } + } + } + } + else + { + if (cent->currentState.bolt1 && !(cent->currentState.eFlags & EF_DEAD) && cent->currentState.number != cg.snap->ps.clientNum && (!cg.snap->ps.duelInProgress || cg.snap->ps.duelIndex != cent->currentState.number)) + { + legs.shaderRGBA[0] = 50; + legs.shaderRGBA[1] = 50; + legs.shaderRGBA[2] = 50; + legs.renderfx |= RF_RGB_TINT; + } + } + + if (cent->currentState.eFlags & EF_DISINTEGRATION) + { + if (!cent->dustTrailTime) + { + cent->dustTrailTime = cg.time; + cent->miscTime = legs.frame; + } + + if ((cg.time - cent->dustTrailTime) > 1500) + { //avoid rendering the entity after disintegration has finished anyway + //goto endOfCall; + return; + } + + trap_G2API_SetBoneAnim(legs.ghoul2, 0, "model_root", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1); + + if (!cent->noLumbar) + { + trap_G2API_SetBoneAnim(legs.ghoul2, 0, "lower_lumbar", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1); + + if (cent->localAnimIndex <= 1) + { + trap_G2API_SetBoneAnim(legs.ghoul2, 0, "Motion", cent->miscTime, cent->miscTime, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->miscTime, -1); + } + } + + CG_Disintegration(cent, &legs); + + //goto endOfCall; + return; + } + else + { + cent->dustTrailTime = 0; + cent->miscTime = 0; + } + + if (cent->currentState.powerups & (1 << PW_CLOAKED)) + { + if (!cent->cloaked) + { + cent->cloaked = qtrue; + cent->uncloaking = cg.time + 2000; + } + } + else if (cent->cloaked) + { + cent->cloaked = qfalse; + cent->uncloaking = cg.time + 2000; + } + + if (cent->uncloaking > cg.time) + {//in the middle of cloaking + if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) + && cg.snap->ps.clientNum != cent->currentState.number) + {//just draw him + trap_R_AddRefEntityToScene( &legs ); + } + else + { + float perc = (float)(cent->uncloaking - cg.time) / 2000.0f; + if (( cent->currentState.powerups & ( 1 << PW_CLOAKED ))) + {//actually cloaking, so reverse it + perc = 1.0f - perc; + } + + if ( perc >= 0.0f && perc <= 1.0f ) + { + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx |= RF_RGB_TINT; + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255.0f * perc; + legs.shaderRGBA[3] = 0; + legs.customShader = cgs.media.cloakedShader; + trap_R_AddRefEntityToScene( &legs ); + + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255; + legs.shaderRGBA[3] = 255 * (1.0f - perc); // let model alpha in + legs.customShader = 0; // use regular skin + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx |= RF_FORCE_ENT_ALPHA; + trap_R_AddRefEntityToScene( &legs ); + } + } + } + else if (( cent->currentState.powerups & ( 1 << PW_CLOAKED ))) + {//fully cloaked + if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) + && cg.snap->ps.clientNum != cent->currentState.number) + {//just draw him + trap_R_AddRefEntityToScene( &legs ); + } + else + { + if (cg.renderingThirdPerson || cent->currentState.number != cg.predictedPlayerState.clientNum) + { + /* + legs.renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE); + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = legs.shaderRGBA[3] = 255; + legs.customShader = cgs.media.cloakedShader; + + legs.nonNormalizedAxes = qtrue; + + legs.modelScale[0] = 1.02f; + legs.modelScale[1] = 1.02f; + legs.modelScale[2] = 1.02f; + VectorScale( legs.axis[0], legs.modelScale[0], legs.axis[0] ); + VectorScale( legs.axis[1], legs.modelScale[1], legs.axis[1] ); + VectorScale( legs.axis[2], legs.modelScale[2], legs.axis[2] ); + + ScaleModelAxis(&legs); + + trap_R_AddRefEntityToScene( &legs ); + + legs.modelScale[0] = 0.98f; + legs.modelScale[1] = 0.98f; + legs.modelScale[2] = 0.98f; + VectorScale( legs.axis[0], legs.modelScale[0], legs.axis[0] ); + VectorScale( legs.axis[1], legs.modelScale[1], legs.axis[1] ); + VectorScale( legs.axis[2], legs.modelScale[2], legs.axis[2] ); + + ScaleModelAxis(&legs); + */ + + if (cg_shadows.integer != 2 && cgs.glconfig.stencilBits >= 4 && cg_renderToTextureFX.integer) + { + trap_R_SetRefractProp(1.0f, 0.0f, qfalse, qfalse); //don't need to do this every frame.. but.. + legs.customShader = 2; //crazy "refractive" shader + trap_R_AddRefEntityToScene( &legs ); + legs.customShader = 0; + } + else + { //stencil buffer's in use, sorry + legs.renderfx = 0;//&= ~(RF_RGB_TINT|RF_ALPHA_FADE); + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = legs.shaderRGBA[3] = 255; + legs.customShader = cgs.media.cloakedShader; + trap_R_AddRefEntityToScene( &legs ); + legs.customShader = 0; + } + } + } + } + + if (!(cent->currentState.powerups & (1 << PW_CLOAKED))) + { //don't add the normal model if cloaked + CG_CheckThirdPersonAlpha( cent, &legs ); + trap_R_AddRefEntityToScene(&legs); + } + + //cent->frame_minus2 = cent->frame_minus1; + VectorCopy(cent->frame_minus1, cent->frame_minus2); + + if (cent->frame_minus1_refreshed) + { + cent->frame_minus2_refreshed = 1; + } + + //cent->frame_minus1 = legs; + VectorCopy(legs.origin, cent->frame_minus1); + + cent->frame_minus1_refreshed = 1; + + if (!cent->frame_hold_refreshed && (cent->currentState.powerups & (1 << PW_SPEEDBURST))) + { + cent->frame_hold_time = cg.time + 254; + } + + if (cent->frame_hold_time >= cg.time) + { + refEntity_t reframe_hold; + + if (!cent->frame_hold_refreshed) + { //We're taking the ghoul2 instance from the original refent and duplicating it onto our refent alias so that we can then freeze the frame and fade it for the effect + if (cent->frame_hold && trap_G2_HaveWeGhoul2Models(cent->frame_hold) && + cent->frame_hold != cent->ghoul2) + { + trap_G2API_CleanGhoul2Models(&(cent->frame_hold)); + } + reframe_hold = legs; + cent->frame_hold_refreshed = 1; + reframe_hold.ghoul2 = NULL; + + trap_G2API_DuplicateGhoul2Instance(cent->ghoul2, ¢->frame_hold); + + //Set the animation to the current frame and freeze on end + //trap_G2API_SetBoneAnim(cent->frame_hold.ghoul2, 0, "model_root", cent->frame_hold.frame, cent->frame_hold.frame, BONE_ANIM_OVERRIDE_FREEZE, 1.0f, cg.time, cent->frame_hold.frame, -1); + trap_G2API_SetBoneAnim(cent->frame_hold, 0, "model_root", legs.frame, legs.frame, 0, 1.0f, cg.time, legs.frame, -1); + } + else + { + reframe_hold = legs; + reframe_hold.ghoul2 = cent->frame_hold; + } + + reframe_hold.renderfx |= RF_FORCE_ENT_ALPHA; + reframe_hold.shaderRGBA[3] = (cent->frame_hold_time - cg.time); + if (reframe_hold.shaderRGBA[3] > 254) + { + reframe_hold.shaderRGBA[3] = 254; + } + if (reframe_hold.shaderRGBA[3] < 1) + { + reframe_hold.shaderRGBA[3] = 1; + } + + reframe_hold.ghoul2 = cent->frame_hold; + trap_R_AddRefEntityToScene(&reframe_hold); + } + else + { + cent->frame_hold_refreshed = 0; + } + + // + // add the gun / barrel / flash + // + if (cent->currentState.weapon != WP_EMPLACED_GUN) + { + CG_AddPlayerWeapon( &legs, NULL, cent, ci->team, rootAngles, qtrue ); + } + // add powerups floating behind the player + CG_PlayerPowerups( cent, &legs ); + + if ((cent->currentState.forcePowersActive & (1 << FP_RAGE)) && + (cg.renderingThirdPerson || cent->currentState.number != cg.snap->ps.clientNum)) + { + //legs.customShader = cgs.media.rageShader; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx &= ~RF_MINLIGHT; + + legs.renderfx |= RF_RGB_TINT; + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = legs.shaderRGBA[2] = 0; + legs.shaderRGBA[3] = 255; + + if ( rand() & 1 ) + { + legs.customShader = cgs.media.electricBodyShader; + } + else + { + legs.customShader = cgs.media.electricBody2Shader; + } + + trap_R_AddRefEntityToScene(&legs); + } + + if (!cg.snap->ps.duelInProgress && cent->currentState.bolt1 && !(cent->currentState.eFlags & EF_DEAD) && cent->currentState.number != cg.snap->ps.clientNum && (!cg.snap->ps.duelInProgress || cg.snap->ps.duelIndex != cent->currentState.number)) + { + legs.shaderRGBA[0] = 50; + legs.shaderRGBA[1] = 50; + legs.shaderRGBA[2] = 255; + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.customShader = cgs.media.forceSightBubble; + + trap_R_AddRefEntityToScene( &legs ); + } + + if ( CG_VehicleShouldDrawShields( cent ) //vehicle + || (checkDroidShields && CG_VehicleShouldDrawShields( &cg_entities[cent->currentState.m_iVehicleNum] )) )//droid in vehicle + {//Vehicles have form-fitting shields + Vehicle_t *pVeh = cent->m_pVehicle; + if ( checkDroidShields ) + { + pVeh = cg_entities[cent->currentState.m_iVehicleNum].m_pVehicle; + } + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 255; + legs.shaderRGBA[3] = 10.0f+(sin((float)(cg.time/4))*128.0f);//112.0 * ((cent->damageTime - cg.time) / MIN_SHIELD_TIME) + random()*16; + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + + if ( pVeh + && pVeh->m_pVehicleInfo + && pVeh->m_pVehicleInfo->shieldShaderHandle ) + {//use the vehicle-specific shader + legs.customShader = pVeh->m_pVehicleInfo->shieldShaderHandle; + } + else + { + legs.customShader = cgs.media.playerShieldDamage; + } + + trap_R_AddRefEntityToScene( &legs ); + } + //For now, these two are using the old shield shader. This is just so that you + //can tell it apart from the JM/duel shaders, but it's still very obvious. + if (cent->currentState.forcePowersActive & (1 << FP_PROTECT)) + { //aborb is represented by green.. + refEntity_t prot; + + memcpy(&prot, &legs, sizeof(prot)); + + prot.shaderRGBA[0] = 0; + prot.shaderRGBA[1] = 128; + prot.shaderRGBA[2] = 0; + prot.shaderRGBA[3] = 254; + + prot.renderfx &= ~RF_RGB_TINT; + prot.renderfx &= ~RF_FORCE_ENT_ALPHA; + prot.customShader = cgs.media.protectShader; + + /* + if (!prot.modelScale[0] && !prot.modelScale[1] && !prot.modelScale[2]) + { + prot.modelScale[0] = prot.modelScale[1] = prot.modelScale[2] = 1.0f; + } + VectorScale(prot.modelScale, 1.1f, prot.modelScale); + prot.origin[2] -= 2.0f; + ScaleModelAxis(&prot); + */ + + trap_R_AddRefEntityToScene( &prot ); + } + //if (cent->currentState.forcePowersActive & (1 << FP_ABSORB)) + //Showing only when the power has been active (absorbed something) recently now, instead of always. + //AND + //always show if it is you with the absorb on + if ((cent->currentState.number == cg.predictedPlayerState.clientNum && (cg.predictedPlayerState.fd.forcePowersActive & (1<teamPowerEffectTime > cg.time && cent->teamPowerType == 3)) + { //aborb is represented by blue.. + legs.shaderRGBA[0] = 0; + legs.shaderRGBA[1] = 0; + legs.shaderRGBA[2] = 255; + legs.shaderRGBA[3] = 254; + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.customShader = cgs.media.playerShieldDamage; + + trap_R_AddRefEntityToScene( &legs ); + } + + if (cent->currentState.isJediMaster && cg.snap->ps.clientNum != cent->currentState.number) + { + legs.shaderRGBA[0] = 100; + legs.shaderRGBA[1] = 100; + legs.shaderRGBA[2] = 255; + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx |= RF_NODEPTH; + legs.customShader = cgs.media.forceShell; + + trap_R_AddRefEntityToScene( &legs ); + + legs.renderfx &= ~RF_NODEPTH; + } + + if ((cg.snap->ps.fd.forcePowersActive & (1 << FP_SEE)) && cg.snap->ps.clientNum != cent->currentState.number && cg_auraShell.integer) + { + if (cgs.gametype == GT_SIEGE) + { // A team game + if ( ci->team == TEAM_SPECTATOR || ci->team == TEAM_FREE ) + {//yellow + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + } + else if ( ci->team != cgs.clientinfo[cg.snap->ps.clientNum].team ) + {//red + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 50; + legs.shaderRGBA[2] = 50; + } + else + {//green + legs.shaderRGBA[0] = 50; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 50; + } + } + else if (cgs.gametype >= GT_TEAM) + { // A team game + switch(ci->team) + { + case TEAM_RED: + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 50; + legs.shaderRGBA[2] = 50; + break; + case TEAM_BLUE: + legs.shaderRGBA[0] = 75; + legs.shaderRGBA[1] = 75; + legs.shaderRGBA[2] = 255; + break; + + default: + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + break; + } + } + else + { // Not a team game + legs.shaderRGBA[0] = 255; + legs.shaderRGBA[1] = 255; + legs.shaderRGBA[2] = 0; + } + +/* if (cg.snap->ps.fd.forcePowerLevel[FP_SEE] <= FORCE_LEVEL_1) + { + legs.renderfx |= RF_MINLIGHT; + } + else +*/ { // See through walls. + legs.renderfx |= RF_MINLIGHT | RF_NODEPTH; + + if (cg.snap->ps.fd.forcePowerLevel[FP_SEE] < FORCE_LEVEL_2) + { //only level 2+ can see players through walls + legs.renderfx &= ~RF_NODEPTH; + } + } + + legs.renderfx &= ~RF_RGB_TINT; + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.customShader = cgs.media.sightShell; + + trap_R_AddRefEntityToScene( &legs ); + } + + // Electricity + //------------------------------------------------ + if ( cent->currentState.emplacedOwner > cg.time ) + { + int dif = cent->currentState.emplacedOwner - cg.time; + vec3_t tempAngles; + + if ( dif > 0 && random() > 0.4f ) + { + // fade out over the last 500 ms + int brightness = 255; + + if ( dif < 500 ) + { + brightness = floor((dif - 500.0f) / 500.0f * 255.0f ); + } + + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx &= ~RF_MINLIGHT; + + legs.renderfx |= RF_RGB_TINT; + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = brightness; + legs.shaderRGBA[3] = 255; + + if ( rand() & 1 ) + { + legs.customShader = cgs.media.electricBodyShader; + } + else + { + legs.customShader = cgs.media.electricBody2Shader; + } + + trap_R_AddRefEntityToScene( &legs ); + + if ( random() > 0.9f ) + trap_S_StartSound ( NULL, cent->currentState.number, CHAN_AUTO, cgs.media.crackleSound ); + } + + VectorSet(tempAngles, 0, cent->lerpAngles[YAW], 0); + CG_ForceElectrocution( cent, legs.origin, tempAngles, cgs.media.boltShader, qfalse ); + } + + if (cent->currentState.powerups & (1 << PW_SHIELDHIT)) + { + /* + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = 255.0f * 0.5f;//t; + legs.shaderRGBA[3] = 255; + legs.renderfx &= ~RF_ALPHA_FADE; + legs.renderfx |= RF_RGB_TINT; + */ + + legs.shaderRGBA[0] = legs.shaderRGBA[1] = legs.shaderRGBA[2] = Q_irand(1, 255); + + legs.renderfx &= ~RF_FORCE_ENT_ALPHA; + legs.renderfx &= ~RF_MINLIGHT; + legs.renderfx &= ~RF_RGB_TINT; + legs.customShader = cgs.media.playerShieldDamage; + + trap_R_AddRefEntityToScene( &legs ); + } +#if 0 +endOfCall: + + if (cgBoneAnglePostSet.refreshSet) + { + trap_G2API_SetBoneAngles(cgBoneAnglePostSet.ghoul2, cgBoneAnglePostSet.modelIndex, cgBoneAnglePostSet.boneName, + cgBoneAnglePostSet.angles, cgBoneAnglePostSet.flags, cgBoneAnglePostSet.up, cgBoneAnglePostSet.right, + cgBoneAnglePostSet.forward, cgBoneAnglePostSet.modelList, cgBoneAnglePostSet.blendTime, cgBoneAnglePostSet.currentTime); + + cgBoneAnglePostSet.refreshSet = qfalse; + } +#endif +} + + +//===================================================================== + +/* +=============== +CG_ResetPlayerEntity + +A player just came into view or teleported, so reset all animation info +=============== +*/ +void CG_ResetPlayerEntity( centity_t *cent ) +{ + clientInfo_t *ci; + int i = 0; + int j = 0; + +// cent->errorTime = -99999; // guarantee no error decay added +// cent->extrapolated = qfalse; + + if (cent->currentState.eType == ET_NPC) + { + if (cent->currentState.NPC_class == CLASS_VEHICLE && + cent->m_pVehicle && + cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER && + cg.predictedPlayerState.m_iVehicleNum && + cent->currentState.number == cg.predictedPlayerState.m_iVehicleNum) + { //holy hackery, batman! + //I don't think this will break anything. But really, do I ever? + return; + } + + if (!cent->npcClient) + { + CG_CreateNPCClient(¢->npcClient); //allocate memory for it + + if (!cent->npcClient) + { + assert(0); + return; + } + + memset(cent->npcClient, 0, sizeof(clientInfo_t)); + cent->npcClient->ghoul2Model = NULL; + } + + ci = cent->npcClient; + + assert(ci); + + //just force these guys to be set again, it won't hurt anything if they're + //already set. + cent->npcLocalSurfOff = 0; + cent->npcLocalSurfOn = 0; + } + else + { + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + } + + while (i < MAX_SABERS) + { + j = 0; + while (j < ci->saber[i].numBlades) + { + ci->saber[i].blade[j].trail.lastTime = -20000; + j++; + } + i++; + } + + ci->facial_blink = -1; + ci->facial_frown = 0; + ci->facial_aux = 0; + ci->superSmoothTime = 0; + + //reset lerp origin smooth point + VectorCopy(cent->lerpOrigin, cent->beamEnd); + + if (cent->currentState.eType != ET_NPC || + !(cent->currentState.eFlags & EF_DEAD)) + { + CG_ClearLerpFrame( cent, ci, ¢->pe.legs, cent->currentState.legsAnim, qfalse); + CG_ClearLerpFrame( cent, ci, ¢->pe.torso, cent->currentState.torsoAnim, qtrue); + + 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 (cent->currentState.eType == ET_NPC) + { //just start them off at 0 pitch + cent->pe.torso.pitchAngle = 0; + } + + if ((cent->ghoul2 == NULL) && ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + trap_G2API_DuplicateGhoul2Instance(ci->ghoul2Model, ¢->ghoul2); + cent->weapon = 0; + cent->ghoul2weapon = NULL; + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(cent->ghoul2, cent->currentState.number, qfalse); + + if (trap_G2API_AddBolt(cent->ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cent->noFace = qtrue; + } + + cent->localAnimIndex = CG_G2SkelForModel(cent->ghoul2); + cent->eventAnimIndex = CG_G2EvIndexForModel(cent->ghoul2, cent->localAnimIndex); + + //CG_CopyG2WeaponInstance(cent->currentState.weapon, ci->ghoul2Model); + //cent->weapon = cent->currentState.weapon; + } + } + + //do this to prevent us from making a saber unholster sound the first time we enter the pvs + if (cent->currentState.number != cg.predictedPlayerState.clientNum && + cent->currentState.weapon == WP_SABER && + cent->weapon != cent->currentState.weapon) + { + cent->weapon = cent->currentState.weapon; + if (cent->ghoul2 && ci->ghoul2Model) + { + CG_CopyG2WeaponInstance(cent, cent->currentState.weapon, cent->ghoul2); + cent->ghoul2weapon = CG_G2WeaponInstance(cent, cent->currentState.weapon); + } + if (!cent->currentState.saberHolstered) + { //if not holstered set length and desired length for both blades to full right now. + BG_SI_SetDesiredLength(&ci->saber[0], 0, -1); + BG_SI_SetDesiredLength(&ci->saber[1], 0, -1); + + i = 0; + while (i < MAX_SABERS) + { + j = 0; + while (j < ci->saber[i].numBlades) + { + ci->saber[i].blade[j].length = ci->saber[i].blade[j].lengthMax; + j++; + } + i++; + } + } + } + + + if ( cg_debugPosition.integer ) { + CG_Printf("%i ResetPlayerEntity yaw=%i\n", cent->currentState.number, cent->pe.torso.yawAngle ); + } +} + diff --git a/code/cgame/cg_playerstate.c b/code/cgame/cg_playerstate.c new file mode 100644 index 0000000..7c307e1 --- /dev/null +++ b/code/cgame/cg_playerstate.c @@ -0,0 +1,535 @@ +// 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 ) { +#if 0 + 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_BRYAR_PISTOL; i < WP_NUM_WEAPONS ; i++ ) { + if ( ! ( weapons & ( 1 << i ) ) ) { + continue; + } + switch ( i ) + { + case WP_BRYAR_PISTOL: + case WP_CONCUSSION: + case WP_BRYAR_OLD: + case WP_BLASTER: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_DEMP2: + case WP_FLECHETTE: + case WP_ROCKET_LAUNCHER: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + case WP_EMPLACED_GUN: + total += cg.snap->ps.ammo[weaponData[i].ammoIndex] * 1000; + break; + default: + total += cg.snap->ps.ammo[weaponData[i].ammoIndex] * 200; + break; + } + if ( total >= 5000 ) { + cg.lowAmmoWarning = 0; + return; + } + } + + previous = cg.lowAmmoWarning; + + if ( total == 0 ) { + cg.lowAmmoWarning = 2; + } else { + cg.lowAmmoWarning = 1; + } + + if (cg.snap->ps.weapon == WP_SABER) + { + cg.lowAmmoWarning = 0; + } + + // play a sound on transitions + if ( cg.lowAmmoWarning != previous ) { + trap_S_StartLocalSound( cgs.media.noAmmoSound, CHAN_LOCAL_SOUND ); + } +#endif + //disabled silly ammo warning stuff for now +} + +/* +============== +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; + + AngleVectors( angles, dir, NULL, NULL ); + 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; + +//JLFRUMBLE +#ifdef _XBOX +extern void FF_XboxShake(float intensity, int duration); +extern void FF_XboxDamage(int damage, float xpos); + +//FF_XboxShake(kick, 500); +FF_XboxDamage(damage, -left); + + +#endif + +} + + + + +/* +================ +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; +} + +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_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) ]; +//JLF ADDED to hopefully mark events as player event + cent->playerState = ps; + 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_entities[ps->clientNum]; + 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 +================== +*/ +#ifdef JK2AWARDS +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; + } +} +#endif + +int cgAnnouncerTime = 0; //to prevent announce sounds from playing on top of each other + +/* +================== +CG_CheckLocalSounds +================== +*/ +void CG_CheckLocalSounds( playerState_t *ps, playerState_t *ops ) { + int highScore, health, armor, reward; +#ifdef JK2AWARDS + sfxHandle_t sfx; +#endif + + // 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; + + if (armor > health/2) + { // We also hit shields along the way, so consider them "pierced". +// trap_S_StartLocalSound( cgs.media.shieldPierceSound, CHAN_LOCAL_SOUND ); + } + else + { // Shields didn't really stand in our way. +// trap_S_StartLocalSound( cgs.media.hitSound, CHAN_LOCAL_SOUND ); + } + + //FIXME: Hit sounds? + /* + 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 if ( ps->persistant[PERS_HITS] < ops->persistant[PERS_HITS] ) { + //trap_S_StartLocalSound( cgs.media.hitTeamSound, CHAN_LOCAL_SOUND ); + } + + // health changes of more than -3 should make pain sounds + if (cg_oldPainSounds.integer) + { + if ( ps->stats[STAT_HEALTH] < (ops->stats[STAT_HEALTH] - 3)) + { + if ( ps->stats[STAT_HEALTH] > 0 ) + { + CG_PainEvent( &cg_entities[cg.predictedPlayerState.clientNum], ps->stats[STAT_HEALTH] ); + } + } + } + + // if we are going into the intermission, don't start any voices + if ( cg.intermissionStarted ) { + return; + } + +#ifdef JK2AWARDS + // 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]) { + sfx = cgs.media.impressiveSound; + + 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]) { + sfx = cgs.media.excellentSound; + 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]) { + sfx = cgs.media.humiliationSound; + 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"); + } + // 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 ); + } + reward = qtrue; + } +#else + reward = qfalse; +#endif + // lead changes + if (!reward && cgAnnouncerTime < cg.time) { + // + if ( !cg.warmup && cgs.gametype != GT_POWERDUEL ) { + // never play lead changes during warmup and powerduel + if ( ps->persistant[PERS_RANK] != ops->persistant[PERS_RANK] ) { + if ( cgs.gametype < GT_TEAM) { + /* + if ( ps->persistant[PERS_RANK] == 0 ) { + CG_AddBufferedSound(cgs.media.takenLeadSound); + cgAnnouncerTime = cg.time + 3000; + } else if ( ps->persistant[PERS_RANK] == RANK_TIED_FLAG ) { + //CG_AddBufferedSound(cgs.media.tiedLeadSound); + } else if ( ( ops->persistant[PERS_RANK] & ~RANK_TIED_FLAG ) == 0 ) { + //rww - only bother saying this if you have more than 1 kill already. + //joining the server and hearing "the force is not with you" is silly. + if (ps->persistant[PERS_SCORE] > 0) + { + CG_AddBufferedSound(cgs.media.lostLeadSound); + cgAnnouncerTime = cg.time + 3000; + } + } + */ + } + } + } + } + + // timelimit warnings + if ( cgs.timelimit > 0 && cgAnnouncerTime < cg.time ) { + 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 ); + cgAnnouncerTime = cg.time + 3000; + } + 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 ); + cgAnnouncerTime = cg.time + 3000; + } + } + + // fraglimit warnings + if ( cgs.fraglimit > 0 && cgs.gametype < GT_CTF && cgs.gametype != GT_DUEL && cgs.gametype != GT_POWERDUEL && cgs.gametype != GT_SIEGE && cgAnnouncerTime < cg.time) { + highScore = cgs.scores1; + if ( !( cg.fraglimitWarnings & 4 ) && highScore == (cgs.fraglimit - 1) ) { + cg.fraglimitWarnings |= 1 | 2 | 4; + CG_AddBufferedSound(cgs.media.oneFragSound); + cgAnnouncerTime = cg.time + 3000; + } + else if ( cgs.fraglimit > 2 && !( cg.fraglimitWarnings & 2 ) && highScore == (cgs.fraglimit - 2) ) { + cg.fraglimitWarnings |= 1 | 2; + CG_AddBufferedSound(cgs.media.twoFragSound); + cgAnnouncerTime = cg.time + 3000; + } + else if ( cgs.fraglimit > 3 && !( cg.fraglimitWarnings & 1 ) && highScore == (cgs.fraglimit - 3) ) { + cg.fraglimitWarnings |= 1; + CG_AddBufferedSound(cgs.media.threeFragSound); + cgAnnouncerTime = cg.time + 3000; + } + } +} + +/* +=============== +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/code/cgame/cg_predict.c b/code/cgame/cg_predict.c new file mode 100644 index 0000000..1782261 --- /dev/null +++ b/code/cgame/cg_predict.c @@ -0,0 +1,1512 @@ +// 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]; + +//is this client piloting this veh? +static CGAME_INLINE qboolean CG_Piloting(int vehNum) +{ + centity_t *veh; + + if (!vehNum) + { + return qfalse; + } + + veh = &cg_entities[vehNum]; + + if (veh->currentState.owner != cg.predictedPlayerState.clientNum) + { //the owner should be the current pilot + return qfalse; + } + + return qtrue; +} + +/* +==================== +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; + vec3_t difference; + float dsquared; + + 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; + + if ( ent->eType == ET_ITEM || ent->eType == ET_PUSH_TRIGGER || ent->eType == ET_TELEPORT_TRIGGER ) { + cg_triggerEntities[cg_numTriggerEntities] = cent; + cg_numTriggerEntities++; + continue; + } + + if ( cent->nextState.solid ) { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + continue; + } + } + + //rww - Horrible, terrible, awful hack. + //We don't send your client entity from the server, + //so it isn't added into the solid list from the snapshot, + //and in addition, it has no solid data. So we will force + //adding it in based on a hardcoded player bbox size. + //This will cause issues if the player box size is ever + //changed.. + if (cg_numSolidEntities < MAX_ENTITIES_IN_SNAPSHOT) + { + vec3_t playerMins = {-15, -15, DEFAULT_MINS_2}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + int i, j, k; + + i = playerMaxs[0]; + if (i<1) + i = 1; + if (i>255) + i = 255; + + // z is not symetric + j = (-playerMins[2]); + if (j<1) + j = 1; + if (j>255) + j = 255; + + // and z playerMaxs can be negative... + k = (playerMaxs[2]+32); + if (k<1) + k = 1; + if (k>255) + k = 255; + + cg_solidEntities[cg_numSolidEntities] = &cg_entities[cg.predictedPlayerState.clientNum]; + cg_solidEntities[cg_numSolidEntities]->currentState.solid = (k<<16) | (j<<8) | i; + + cg_numSolidEntities++; + } + + dsquared = /*RMG_distancecull.value*/5000+500; + dsquared *= dsquared; + + for(i=0;ilerpOrigin, snap->ps.origin, difference); + if (cent->currentState.eType == ET_TERRAIN || + ((difference[0]*difference[0]) + (difference[1]*difference[1]) + (difference[2]*difference[2])) <= dsquared) + { + cent->currentValid = qtrue; + if ( cent->nextState.solid ) + { + cg_solidEntities[cg_numSolidEntities] = cent; + cg_numSolidEntities++; + } + } + else + { + cent->currentValid = qfalse; + } + } +} + +static CGAME_INLINE qboolean CG_VehicleClipCheck(centity_t *ignored, trace_t *trace) +{ + if (!trace || trace->entityNum < 0 || trace->entityNum >= ENTITYNUM_WORLD) + { //it's alright then + return qtrue; + } + + if (ignored->currentState.eType != ET_PLAYER && + ignored->currentState.eType != ET_NPC) + { //can't possibly be valid then + return qtrue; + } + + if (ignored->currentState.m_iVehicleNum) + { //see if the ignore ent is a vehicle/rider - if so, see if the ent we supposedly hit is a vehicle/rider. + //if they belong to each other, we don't want to collide them. + centity_t *otherguy = &cg_entities[trace->entityNum]; + + if (otherguy->currentState.eType != ET_PLAYER && + otherguy->currentState.eType != ET_NPC) + { //can't possibly be valid then + return qtrue; + } + + if (otherguy->currentState.m_iVehicleNum) + { //alright, both of these are either a vehicle or a player who is on a vehicle + int index; + + if (ignored->currentState.eType == ET_PLAYER + || (ignored->currentState.eType == ET_NPC && ignored->currentState.NPC_class != CLASS_VEHICLE) ) + { //must be a player or NPC riding a vehicle + index = ignored->currentState.m_iVehicleNum; + } + else + { //a vehicle + index = ignored->currentState.m_iVehicleNum-1; + } + + if (index == otherguy->currentState.number) + { //this means we're riding or being ridden by this guy, so don't collide + return qfalse; + } + else + {//see if I'm hitting one of my own passengers + if (otherguy->currentState.eType == ET_PLAYER + || (otherguy->currentState.eType == ET_NPC && otherguy->currentState.NPC_class != CLASS_VEHICLE) ) + { //must be a player or NPC riding a vehicle + if (otherguy->currentState.m_iVehicleNum==ignored->currentState.number) + { //this means we're other guy is riding the ignored ent + return qfalse; + } + } + } + } + } + + return qtrue; +} + +//rww - I'm disabling this warning for this function. It complains about oldTrace but as you can see it +//always gets set before use, and I am not wasting CPU memsetting it to shut the compiler up. +#pragma warning(disable : 4701) //local variable may be used without having been initialized +/* +==================== +CG_ClipMoveToEntities + +==================== +*/ +#include "../namespace_begin.h" +extern void BG_VehicleAdjustBBoxForOrientation( Vehicle_t *veh, vec3_t origin, vec3_t mins, vec3_t maxs, + int clientNum, int tracemask, + void (*localTrace)(trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask)); // bg_pmove.c +#include "../namespace_end.h" +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, qboolean g2Check ) { + int i, x, zd, zu; + trace_t trace, oldTrace; + entityState_t *ent; + clipHandle_t cmodel; + vec3_t bmins, bmaxs; + vec3_t origin, angles; + centity_t *cent; + centity_t *ignored = NULL; + + if (skipNumber != -1 && skipNumber != ENTITYNUM_NONE) + { + ignored = &cg_entities[skipNumber]; + } + + for ( i = 0 ; i < cg_numSolidEntities ; i++ ) { + cent = cg_solidEntities[ i ]; + ent = ¢->currentState; + + if ( ent->number == skipNumber ) { + continue; + } + + if ( ent->number > MAX_CLIENTS && + (ent->genericenemyindex-MAX_GENTITIES==cg.predictedPlayerState.clientNum || ent->genericenemyindex-MAX_GENTITIES==cg.predictedVehicleState.clientNum) ) +// if (ent->number > MAX_CLIENTS && cg.snap && ent->genericenemyindex && (ent->genericenemyindex-MAX_GENTITIES) == cg.snap->ps.clientNum) + { //rww - method of keeping objects from colliding in client-prediction (in case of ownership) + 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; + + if (ent->eType == ET_NPC && ent->NPC_class == CLASS_VEHICLE && + cent->m_pVehicle) + { //try to dynamically adjust his bbox dynamically, if possible + float *old = cent->m_pVehicle->m_vOrientation; + cent->m_pVehicle->m_vOrientation = ¢->lerpAngles[0]; + BG_VehicleAdjustBBoxForOrientation(cent->m_pVehicle, cent->lerpOrigin, bmins, bmaxs, + cent->currentState.number, MASK_PLAYERSOLID, NULL); + cent->m_pVehicle->m_vOrientation = old; + } + + 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); + trace.entityNum = trace.fraction != 1.0 ? ent->number : ENTITYNUM_NONE; + + if (g2Check || (ignored && ignored->currentState.m_iVehicleNum)) + { + //keep these older variables around for a bit, incase we need to replace them in the Ghoul2 Collision check + //or in the vehicle owner trace check + oldTrace = *tr; + } + + if (trace.allsolid || trace.fraction < tr->fraction) { + trace.entityNum = ent->number; + *tr = trace; + } else if (trace.startsolid) { + tr->startsolid = qtrue; + + //rww 12-02-02 + tr->entityNum = trace.entityNum = ent->number; + } + if ( tr->allsolid ) + { + if (ignored && ignored->currentState.m_iVehicleNum) + { + trace.entityNum = ent->number; + if (CG_VehicleClipCheck(ignored, &trace)) + { //this isn't our vehicle, we're really stuck + return; + } + else + { //it's alright, keep going + trace = oldTrace; + *tr = trace; + } + } + else + { + return; + } + } + + if (g2Check) + { + if (trace.entityNum == ent->number && cent->ghoul2) + { + CG_G2TraceCollide(&trace, mins, maxs, start, end); + + if (trace.entityNum == ENTITYNUM_NONE) + { //g2 trace failed, so put it back where it was. + trace = oldTrace; + *tr = trace; + } + } + } + + if (ignored && ignored->currentState.m_iVehicleNum) + { //see if this is the vehicle we hit + centity_t *hit = &cg_entities[trace.entityNum]; + if (!CG_VehicleClipCheck(ignored, &trace)) + { //looks like it + trace = oldTrace; + *tr = trace; + } + else if (hit->currentState.eType == ET_MISSILE && + hit->currentState.owner == ignored->currentState.number) + { //hack, don't hit own missiles + trace = oldTrace; + *tr = trace; + } + } + } +} +#pragma warning(default : 4701) //local variable may be used without having been initialized + +/* +================ +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, qfalse); + + *result = t; +} + +/* +================ +CG_G2Trace +================ +*/ +void CG_G2Trace( 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, qtrue); + + *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] ); + } + +} + +static void CG_InterpolateVehiclePlayerState( qboolean grabAngles ) { + float f; + int i; + playerState_t *out; + snapshot_t *prev, *next; + + out = &cg.predictedVehicleState; + prev = cg.snap; + next = cg.nextSnap; + + *out = cg.snap->vps; + + // 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->vps.bobCycle; + if ( i < prev->vps.bobCycle ) { + i += 256; // handle wraparound + } + out->bobCycle = prev->vps.bobCycle + f * ( i - prev->vps.bobCycle ); + + for ( i = 0 ; i < 3 ; i++ ) { + out->origin[i] = prev->vps.origin[i] + f * (next->vps.origin[i] - prev->vps.origin[i] ); + if ( !grabAngles ) { + out->viewangles[i] = LerpAngle( + prev->vps.viewangles[i], next->vps.viewangles[i], f ); + } + out->velocity[i] = prev->vps.velocity[i] + + f * (next->vps.velocity[i] - prev->vps.velocity[i] ); + } + +} + +/* +=================== +CG_TouchItem +=================== +*/ +static void CG_TouchItem( centity_t *cent ) { + gitem_t *item; + + if ( !cg_predictItems.integer ) { + return; + } + if ( !BG_PlayerTouchesItem( &cg.predictedPlayerState, ¢->currentState, cg.time ) ) { + return; + } + + if (cent->currentState.brokenLimbs) + { //dropped item + return; + } + + if (cent->currentState.eFlags & EF_ITEMPLACEHOLDER) + { + return; + } + + if (cent->currentState.eFlags & EF_NODRAW) + { + 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 ]; + + //Currently there is no reliable way of knowing if the client has touched a certain item before another if they are next to each other, or rather + //if the server has touched them in the same order. This results often in grabbing an item in the prediction and the server giving you the other + //item. So for now prediction of armor, health, and ammo is disabled. +/* + if (item->giType == IT_ARMOR) + { //rww - this will be stomped next update, but it's set so that we don't try to pick up two shields in one prediction and have the server cancel one + // cg.predictedPlayerState.stats[STAT_ARMOR] += item->quantity; + + //FIXME: This can't be predicted properly at the moment + return; + } + + if (item->giType == IT_HEALTH) + { //same as above, for health + // cg.predictedPlayerState.stats[STAT_HEALTH] += item->quantity; + + //FIXME: This can't be predicted properly at the moment + return; + } + + if (item->giType == IT_AMMO) + { //same as above, for ammo + // cg.predictedPlayerState.ammo[item->giTag] += item->quantity; + + //FIXME: This can't be predicted properly at the moment + return; + } + + if (item->giType == IT_HOLDABLE) + { //same as above, for holdables + // cg.predictedPlayerState.stats[STAT_HOLDABLE_ITEMS] |= (1 << item->giTag); + } +*/ + // Special case for flags. + // We don't predict touching our own flag + if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { + 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; + } + + if (item->giType == IT_POWERUP && + (item->giTag == PW_FORCE_ENLIGHTENED_LIGHT || item->giTag == PW_FORCE_ENLIGHTENED_DARK)) + { + if (item->giTag == PW_FORCE_ENLIGHTENED_LIGHT) + { + if (cg.predictedPlayerState.fd.forceSide != FORCE_LIGHTSIDE) + { + return; + } + } + else + { + if (cg.predictedPlayerState.fd.forceSide != FORCE_DARKSIDE) + { + return; + } + } + } + + + // grab it + BG_AddPredictableEventToPlayerstate( EV_ITEM_PICKUP, cent->currentState.number , &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 && cg.predictedPlayerState.pm_type != PM_JETPACK && cg.predictedPlayerState.pm_type != PM_FLOAT && !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; + } + + 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 ) { + 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; + } +} + +#if 0 +static ID_INLINE void CG_EntityStateToPlayerState( entityState_t *s, playerState_t *ps ) +{ + //currently unused vars commented out for speed.. only uncomment if you need them. + ps->clientNum = s->number; + VectorCopy( s->pos.trBase, ps->origin ); + VectorCopy( s->pos.trDelta, ps->velocity ); + ps->saberLockFrame = s->forceFrame; + ps->legsAnim = s->legsAnim; + ps->torsoAnim = s->torsoAnim; + ps->legsFlip = s->legsFlip; + ps->torsoFlip = s->torsoFlip; + ps->clientNum = s->clientNum; + ps->saberMove = s->saberMove; + + /* + VectorCopy( s->apos.trBase, ps->viewangles ); + + ps->fd.forceMindtrickTargetIndex = s->trickedentindex; + ps->fd.forceMindtrickTargetIndex2 = s->trickedentindex2; + ps->fd.forceMindtrickTargetIndex3 = s->trickedentindex3; + ps->fd.forceMindtrickTargetIndex4 = s->trickedentindex4; + + ps->electrifyTime = s->emplacedOwner; + + ps->speed = s->speed; + + ps->genericEnemyIndex = s->genericenemyindex; + + ps->activeForcePass = s->activeForcePass; + + ps->movementDir = s->angles2[YAW]; + + ps->eFlags = s->eFlags; + + ps->saberInFlight = s->saberInFlight; + ps->saberEntityNum = s->saberEntityNum; + + ps->fd.forcePowersActive = s->forcePowersActive; + + if (s->bolt1) + { + ps->duelInProgress = qtrue; + } + else + { + ps->duelInProgress = qfalse; + } + + if (s->bolt2) + { + ps->dualBlade = qtrue; + } + else + { + ps->dualBlade = qfalse; + } + + ps->emplacedIndex = s->otherEntityNum2; + + ps->saberHolstered = s->saberHolstered; //reuse bool in entitystate for players differently + + ps->genericEnemyIndex = -1; //no real option for this + + //The client has no knowledge of health levels (except for the client entity) + if (s->eFlags & EF_DEAD) + { + ps->stats[STAT_HEALTH] = 0; + } + else + { + ps->stats[STAT_HEALTH] = 100; + } + + 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++; + } + + ps->weapon = s->weapon; + ps->groundEntityNum = s->groundEntityNum; + + for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { + if (s->powerups & (1 << i)) + { + ps->powerups[i] = 30; + } + else + { + ps->powerups[i] = 0; + } + } + + ps->loopSound = s->loopSound; + ps->generic1 = s->generic1; + */ +} +#endif + +// This many playerState_t structures is painfully large. And we +// don't need that many. So we just use a small pool of them. +// PC gets to keep one per entity, just in case. +#ifdef _XBOX + +struct psLinkedNode_t +{ + playerState_t ps; + psLinkedNode_t *next; +}; + +#define CG_SEND_PS_POOL_SIZE 64 +psLinkedNode_t cgSendPSPool[ CG_SEND_PS_POOL_SIZE ]; +psLinkedNode_t *cgSendPSFreeList; + +#else +playerState_t cgSendPSPool[ MAX_GENTITIES ]; +#endif + +playerState_t *cgSendPS[MAX_GENTITIES]; + +#ifdef _XBOX +void AllocSendPlayerstate(int entNum) +{ + if (cgSendPS[entNum]) + { + //Com_Printf( S_COLOR_RED "ERROR: Entity %d already has a playerstate!\n", entNum ); + return; + } + + if (!cgSendPSFreeList) + Com_Error( ERR_DROP, "ERROR: No free playerstates! Increase CG_SEND_PS_POOL_SIZE\n" ); + + cgSendPS[entNum] = &cgSendPSFreeList->ps; + cgSendPSFreeList = cgSendPSFreeList->next; +} +#endif + +//#define _PROFILE_ES_TO_PS + +#ifdef _PROFILE_ES_TO_PS +int g_cgEStoPSTime = 0; +#endif + +//Assign all the entity playerstate pointers to the corresponding one +//so that we can access playerstate stuff in bg code (and then translate +//it back to entitystate data) +void CG_PmoveClientPointerUpdate() +{ + int i; + + memset(&cgSendPSPool[0], 0, sizeof(cgSendPSPool)); + + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) + { +#ifdef _XBOX + cgSendPS[i] = NULL; +#else + cgSendPS[i] = &cgSendPSPool[i]; +#endif + + // These will be invalid at this point on Xbox + cg_entities[i].playerState = cgSendPS[i]; + } + +#ifdef _XBOX + for ( i = 0; i < CG_SEND_PS_POOL_SIZE - 1; i++ ) + { + cgSendPSPool[i].next = &cgSendPSPool[i+1]; + } + + // Last .next is already NULL from memset above + cgSendPSFreeList = &cgSendPSPool[0]; +#endif + + //Set up bg entity data + cg_pmove.baseEnt = (bgEntity_t *)cg_entities; + cg_pmove.entSize = sizeof(centity_t); + + cg_pmove.ghoul2 = NULL; +} + +//check if local client is on an eweb +qboolean CG_UsingEWeb(void) +{ + if (cg.predictedPlayerState.weapon == WP_EMPLACED_GUN && cg.predictedPlayerState.emplacedIndex && + cg_entities[cg.predictedPlayerState.emplacedIndex].currentState.weapon == WP_NONE) + { + return qtrue; + } + + return qfalse; +} + +/* +================= +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. +================= +*/ +extern void CG_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ); +extern vmCvar_t cg_showVehBounds; +pmove_t cg_vehPmove; +qboolean cg_vehPmoveSet = qfalse; + +#pragma warning(disable : 4701) //local variable may be used without having been initialized +void CG_PredictPlayerState( void ) { + int cmdNum, current, i; + playerState_t oldPlayerState; + playerState_t oldVehicleState; + qboolean moved; + usercmd_t oldestCmd; + usercmd_t latestCmd; + centity_t *pEnt; + clientInfo_t *ci; + + 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; + if (CG_Piloting(cg.snap->ps.m_iVehicleNum)) + { + cg.predictedVehicleState = cg.snap->vps; + } + } + + // demo playback just copies the moves + if ( cg.demoPlayback || (cg.snap->ps.pm_flags & PMF_FOLLOW) ) { + CG_InterpolatePlayerState( qfalse ); + if (CG_Piloting(cg.predictedPlayerState.m_iVehicleNum)) + { + CG_InterpolateVehiclePlayerState(qfalse); + } + return; + } + + // non-predicting local movement will grab the latest angles + if ( cg_nopredict.integer || cg_synchronousClients.integer || CG_UsingEWeb() ) { + CG_InterpolatePlayerState( qtrue ); + if (CG_Piloting(cg.predictedPlayerState.m_iVehicleNum)) + { + CG_InterpolateVehiclePlayerState(qtrue); + } + return; + } + + // prepare for pmove + cg_pmove.ps = &cg.predictedPlayerState; + cg_pmove.trace = CG_Trace; + cg_pmove.pointcontents = CG_PointContents; + + pEnt = &cg_entities[cg.predictedPlayerState.clientNum]; + //rww - bgghoul2 + if (cg_pmove.ghoul2 != pEnt->ghoul2) //only update it if the g2 instance has changed + { + if (cg.snap && + pEnt->ghoul2 && + !(cg.snap->ps.pm_flags & PMF_FOLLOW) && + cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR) + { + cg_pmove.ghoul2 = pEnt->ghoul2; + cg_pmove.g2Bolts_LFoot = trap_G2API_AddBolt(pEnt->ghoul2, 0, "*l_leg_foot"); + cg_pmove.g2Bolts_RFoot = trap_G2API_AddBolt(pEnt->ghoul2, 0, "*r_leg_foot"); + } + else + { + cg_pmove.ghoul2 = NULL; + } + } + + ci = &cgs.clientinfo[cg.predictedPlayerState.clientNum]; + + //I'll just do this every frame in case the scale changes in realtime (don't need to update the g2 inst for that) + VectorCopy(pEnt->modelScale, cg_pmove.modelScale); + //rww end bgghoul2 + + 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; + if (CG_Piloting(cg.predictedPlayerState.m_iVehicleNum)) + { + oldVehicleState = cg.predictedVehicleState; + } + + 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.nextSnap->ps.slopeRecalcTime = cg.predictedPlayerState.slopeRecalcTime; //this is the only value we want to maintain seperately on server/client + cg.predictedPlayerState = cg.nextSnap->ps; + if (CG_Piloting(cg.nextSnap->ps.m_iVehicleNum)) + { + cg.predictedVehicleState = cg.nextSnap->vps; + } + cg.physicsTime = cg.nextSnap->serverTime; + } else { + cg.snap->ps.slopeRecalcTime = cg.predictedPlayerState.slopeRecalcTime; //this is the only value we want to maintain seperately on server/client + cg.predictedPlayerState = cg.snap->ps; + if (CG_Piloting(cg.snap->ps.m_iVehicleNum)) + { + cg.predictedVehicleState = cg.snap->vps; + } + 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; + + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) + { + //Written this way for optimal speed, even though it doesn't look pretty. + //(we don't want to spend the time assigning pointers as it does take + //a small precious fraction of time and adds up in the loop.. so says + //the precision timer!) + + if (cg_entities[i].currentState.eType == ET_PLAYER || + cg_entities[i].currentState.eType == ET_NPC) + { + // Need a new playerState_t on Xbox +#ifdef _XBOX + AllocSendPlayerstate(i); +#endif + VectorCopy( cg_entities[i].currentState.pos.trBase, cgSendPS[i]->origin ); + VectorCopy( cg_entities[i].currentState.pos.trDelta, cgSendPS[i]->velocity ); + cgSendPS[i]->saberLockFrame = cg_entities[i].currentState.forceFrame; + cgSendPS[i]->legsAnim = cg_entities[i].currentState.legsAnim; + cgSendPS[i]->torsoAnim = cg_entities[i].currentState.torsoAnim; + cgSendPS[i]->legsFlip = cg_entities[i].currentState.legsFlip; + cgSendPS[i]->torsoFlip = cg_entities[i].currentState.torsoFlip; + cgSendPS[i]->clientNum = cg_entities[i].currentState.clientNum; + cgSendPS[i]->saberMove = cg_entities[i].currentState.saberMove; + } + } + + if (CG_Piloting(cg.predictedPlayerState.m_iVehicleNum)) + { + cg_entities[cg.predictedPlayerState.clientNum].playerState = &cg.predictedPlayerState; + cg_entities[cg.predictedPlayerState.m_iVehicleNum].playerState = &cg.predictedVehicleState; + + //use the player command time, because we are running with the player cmds (this is even the case + //on the server) + cg.predictedVehicleState.commandTime = cg.predictedPlayerState.commandTime; + } + + // 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_Piloting(oldPlayerState.m_iVehicleNum) && + cg.predictedVehicleState.commandTime == oldVehicleState.commandTime ) + { + vec3_t delta; + float len; + + if ( cg.thisFrameTeleport ) { + // a teleport will not cause an error decay + VectorClear( cg.predictedError ); + if ( cg_showVehMiss.integer ) { + CG_Printf( "VEH PredictionTeleport\n" ); + } + cg.thisFrameTeleport = qfalse; + } else { + vec3_t adjusted; + CG_AdjustPositionForMover( cg.predictedVehicleState.origin, + cg.predictedVehicleState.groundEntityNum, cg.physicsTime, cg.oldTime, adjusted ); + + if ( cg_showVehMiss.integer ) { + if (!VectorCompare( oldVehicleState.origin, adjusted )) { + CG_Printf("VEH prediction error\n"); + } + } + VectorSubtract( oldVehicleState.origin, adjusted, delta ); + len = VectorLength( delta ); + if ( len > 0.1 ) { + if ( cg_showVehMiss.integer ) { + CG_Printf("VEH 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_showVehMiss.integer ) { + CG_Printf("VEH 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; + } + // + if ( cg_showVehMiss.integer ) { + if (!VectorCompare( oldVehicleState.vehOrientation, cg.predictedVehicleState.vehOrientation )) { + CG_Printf("VEH orient prediction error\n"); + CG_Printf("VEH pitch prediction miss: %f\n", AngleSubtract( oldVehicleState.vehOrientation[0], cg.predictedVehicleState.vehOrientation[0] ) ); + CG_Printf("VEH yaw prediction miss: %f\n", AngleSubtract( oldVehicleState.vehOrientation[1], cg.predictedVehicleState.vehOrientation[1] ) ); + CG_Printf("VEH roll prediction miss: %f\n", AngleSubtract( oldVehicleState.vehOrientation[2], cg.predictedVehicleState.vehOrientation[2] ) ); + } + } + } + } + else if ( !oldPlayerState.m_iVehicleNum && //don't do pred err on ps while riding veh + 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; + } + } + } + + if ( cg_pmove.pmove_fixed ) { + cg_pmove.cmd.serverTime = ((cg_pmove.cmd.serverTime + pmove_msec.integer-1) / pmove_msec.integer) * pmove_msec.integer; + } + + cg_pmove.animations = bgAllAnims[pEnt->localAnimIndex].anims; + cg_pmove.gametype = cgs.gametype; + + cg_pmove.debugMelee = cgs.debugMelee; + cg_pmove.stepSlideFix = cgs.stepSlideFix; + cg_pmove.noSpecMove = cgs.noSpecMove; + + cg_pmove.nonHumanoid = (pEnt->localAnimIndex > 0); + + if (cg.snap && cg.snap->ps.saberLockTime > cg.time) + { + centity_t *blockOpp = &cg_entities[cg.snap->ps.saberLockEnemy]; + + if (blockOpp) + { + vec3_t lockDir, lockAng; + + VectorSubtract( blockOpp->lerpOrigin, cg.snap->ps.origin, lockDir ); + vectoangles(lockDir, lockAng); + + VectorCopy(lockAng, cg_pmove.ps->viewangles); + } + } + + //THIS is pretty much bad, but... + cg_pmove.ps->fd.saberAnimLevelBase = cg_pmove.ps->fd.saberAnimLevel; + if ( cg_pmove.ps->saberHolstered == 1 ) + { + if ( ci->saber[0].numBlades > 0 ) + { + cg_pmove.ps->fd.saberAnimLevelBase = SS_STAFF; + } + else if ( ci->saber[1].model[0] ) + { + cg_pmove.ps->fd.saberAnimLevelBase = SS_DUAL; + } + } + + Pmove (&cg_pmove); + + if (CG_Piloting(cg.predictedPlayerState.m_iVehicleNum) && + cg.predictedPlayerState.pm_type != PM_INTERMISSION) + { //we're riding a vehicle, let's predict it + centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + int x, zd, zu; + + if (veh->m_pVehicle) + { //make sure pointer is set up to go to our predicted state + veh->m_pVehicle->m_vOrientation = &cg.predictedVehicleState.vehOrientation[0]; + + //keep this updated based on what the playerstate says + veh->m_pVehicle->m_iRemovedSurfaces = cg.predictedVehicleState.vehSurfaces; + + trap_GetUserCmd( cmdNum, &veh->m_pVehicle->m_ucmd ); + + if ( veh->m_pVehicle->m_ucmd.buttons & BUTTON_TALK ) + { //forced input if "chat bubble" is up + veh->m_pVehicle->m_ucmd.buttons = BUTTON_TALK; + veh->m_pVehicle->m_ucmd.forwardmove = 0; + veh->m_pVehicle->m_ucmd.rightmove = 0; + veh->m_pVehicle->m_ucmd.upmove = 0; + } + cg_vehPmove.ps = &cg.predictedVehicleState; + cg_vehPmove.animations = bgAllAnims[veh->localAnimIndex].anims; + + memcpy(&cg_vehPmove.cmd, &veh->m_pVehicle->m_ucmd, sizeof(usercmd_t)); + /* + cg_vehPmove.cmd.rightmove = 0; //no vehicle can move right/left + cg_vehPmove.cmd.upmove = 0; //no vehicle can move up/down + */ + + cg_vehPmove.gametype = cgs.gametype; + cg_vehPmove.ghoul2 = veh->ghoul2; + + cg_vehPmove.nonHumanoid = (veh->localAnimIndex > 0); + + /* + x = (veh->currentState.solid & 255); + zd = (veh->currentState.solid & 255); + zu = (veh->currentState.solid & 255) - 32; + + cg_vehPmove.mins[0] = cg_vehPmove.mins[1] = -x; + cg_vehPmove.maxs[0] = cg_vehPmove.maxs[1] = x; + cg_vehPmove.mins[2] = -zd; + cg_vehPmove.maxs[2] = zu; + */ + //I think this was actually wrong.. just copy-pasted from id code. Oh well. + x = (veh->currentState.solid)&255; + zd = (veh->currentState.solid>>8)&255; + zu = (veh->currentState.solid>>15)&255; + + zu -= 32; //I don't quite get the reason for this. + zd = -zd; + + //z/y must be symmetrical (blah) + cg_vehPmove.mins[0] = cg_vehPmove.mins[1] = -x; + cg_vehPmove.maxs[0] = cg_vehPmove.maxs[1] = x; + cg_vehPmove.mins[2] = zd; + cg_vehPmove.maxs[2] = zu; + + VectorCopy(veh->modelScale, cg_vehPmove.modelScale); + + if (!cg_vehPmoveSet) + { //do all the one-time things + cg_vehPmove.trace = CG_Trace; + cg_vehPmove.pointcontents = CG_PointContents; + cg_vehPmove.tracemask = MASK_PLAYERSOLID; + cg_vehPmove.debugLevel = 0; + cg_vehPmove.g2Bolts_LFoot = -1; + cg_vehPmove.g2Bolts_RFoot = -1; + + cg_vehPmove.baseEnt = (bgEntity_t *)cg_entities; + cg_vehPmove.entSize = sizeof(centity_t); + + cg_vehPmoveSet = qtrue; + } + + cg_vehPmove.noFootsteps = ( cgs.dmflags & DF_NO_FOOTSTEPS ) > 0; + cg_vehPmove.pmove_fixed = pmove_fixed.integer; + cg_vehPmove.pmove_msec = pmove_msec.integer; + + cg_entities[cg.predictedPlayerState.clientNum].playerState = &cg.predictedPlayerState; + veh->playerState = &cg.predictedVehicleState; + + //update boarding value sent from server. boarding is not predicted, but no big deal + veh->m_pVehicle->m_iBoarding = cg.predictedVehicleState.vehBoarding; + + Pmove(&cg_vehPmove); + /* + if ( !cg_paused.integer ) + { + Com_Printf( "%d - PITCH change %4.2f\n", cg.time, AngleSubtract(veh->m_pVehicle->m_vOrientation[0],veh->m_pVehicle->m_vPrevOrientation[0]) ); + } + */ + if ( cg_showVehBounds.integer ) + { + vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0}; + vec3_t absmin, absmax; + VectorAdd( cg_vehPmove.ps->origin, cg_vehPmove.mins, absmin ); + VectorAdd( cg_vehPmove.ps->origin, cg_vehPmove.maxs, absmax ); + CG_Cube( absmin, absmax, NPCDEBUG_RED, 0.25 ); + } + } + } + + 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" ); + } + goto revertES; + } + + if (CG_Piloting(cg.predictedPlayerState.m_iVehicleNum)) + { + CG_AdjustPositionForMover( cg.predictedVehicleState.origin, + cg.predictedVehicleState.groundEntityNum, + cg.physicsTime, cg.time, cg.predictedVehicleState.origin ); + } + else + { + // 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; + } + } + + if (cg.predictedPlayerState.m_iVehicleNum && + !CG_Piloting(cg.predictedPlayerState.m_iVehicleNum)) + { //a passenger on this vehicle, bolt them in + centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + VectorCopy(veh->lerpAngles, cg.predictedPlayerState.viewangles); + VectorCopy(veh->lerpOrigin, cg.predictedPlayerState.origin); + } + +revertES: + if (CG_Piloting(cg.predictedPlayerState.m_iVehicleNum)) + { + centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + + if (veh->m_pVehicle) + { + //switch ptr back for this ent in case we stop riding it + veh->m_pVehicle->m_vOrientation = &cgSendPS[veh->currentState.number]->vehOrientation[0]; + } + + cg_entities[cg.predictedPlayerState.clientNum].playerState = cgSendPS[cg.predictedPlayerState.clientNum]; + veh->playerState = cgSendPS[veh->currentState.number]; + } + + //copy some stuff back into the entstates to help actually "predict" them if applicable + for ( i = 0 ; i < MAX_GENTITIES ; i++ ) + { + if (cg_entities[i].currentState.eType == ET_PLAYER || + cg_entities[i].currentState.eType == ET_NPC) + { + cg_entities[i].currentState.torsoAnim = cgSendPS[i]->torsoAnim; + cg_entities[i].currentState.legsAnim = cgSendPS[i]->legsAnim; + cg_entities[i].currentState.forceFrame = cgSendPS[i]->saberLockFrame; + cg_entities[i].currentState.saberMove = cgSendPS[i]->saberMove; + } + } +} +#pragma warning(default : 4701) //local variable may be used without having been initialized diff --git a/code/cgame/cg_public.h b/code/cgame/cg_public.h new file mode 100644 index 0000000..44e85bb --- /dev/null +++ b/code/cgame/cg_public.h @@ -0,0 +1,597 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#ifndef __CG_PUBLIC_H +#define __CG_PUBLIC_H + +#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 + playerState_t vps; //vehicle I'm riding's playerstate (if applicable) -rww + + 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 5 + +typedef enum { + CG_PRINT = 0, + CG_ERROR, + CG_MILLISECONDS, + + //Also for profiling.. do not use for game related tasks. + CG_PRECISIONTIMER_START, + CG_PRECISIONTIMER_END, + + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_CVAR_GETHIDDENVALUE, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_FS_GETFILELIST, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_REMOVECOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_TEMPCAPSULEMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_CAPSULETRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_TRANSFORMEDCAPSULETRACE, + CG_CM_MARKFRAGMENTS, + CG_S_GETVOICEVOLUME, + CG_S_MUTESOUND, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_ADDREALLOOPINGSOUND, + CG_S_STOPLOOPINGSOUND, + CG_S_RESPATIALIZE, + CG_S_SHUTUP, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + + //rww - AS trap implem + CG_S_UPDATEAMBIENTSET, + CG_AS_PARSESETS, + CG_AS_ADDPRECACHEENTRY, + CG_S_ADDLOCALSET, + CG_AS_GETBMODELSOUND, + + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_REGISTERSHADERNOMIP, + CG_R_REGISTERFONT, + CG_R_FONT_STRLENPIXELS, + CG_R_FONT_STRLENCHARS, + CG_R_FONT_STRHEIGHTPIXELS, + CG_R_FONT_DRAWSTRING, + CG_LANGUAGE_ISASIAN, + CG_LANGUAGE_USESSPACES, + CG_ANYLANGUAGE_READCHARFROMSTRING, + + CGAME_MEMSET = 100, + CGAME_MEMCPY, + CGAME_STRNCPY, + CGAME_SIN, + CGAME_COS, + CGAME_ATAN2, + CGAME_SQRT, + CGAME_MATRIXMULTIPLY, + CGAME_ANGLEVECTORS, + CGAME_PERPENDICULARVECTOR, + CGAME_FLOOR, + CGAME_CEIL, + + CGAME_TESTPRINTINT, + CGAME_TESTPRINTFLOAT, + + CGAME_ACOS, + CGAME_ASIN, + + CG_R_CLEARSCENE = 200, + CG_R_CLEARDECALS, + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDPOLYSTOSCENE, + CG_R_ADDDECALTOSCENE, + CG_R_LIGHTFORPOINT, + CG_R_ADDLIGHTTOSCENE, + CG_R_ADDADDITIVELIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_R_DRAWROTATEPIC, + CG_R_DRAWROTATEPIC2, + CG_R_SETRANGEFOG, //linear fogging, with settable range -rww + CG_R_SETREFRACTIONPROP, //set some properties for the draw layer for my refractive effect (here primarily for mod authors) -rww + CG_R_REMAP_SHADER, + CG_R_GET_LIGHT_STYLE, + CG_R_SET_LIGHT_STYLE, + CG_R_GET_BMODEL_VERTS, + CG_R_GETDISTANCECULL, + + CG_R_GETREALRES, + CG_R_AUTOMAPELEVADJ, + CG_R_INITWIREFRAMEAUTO, + + CG_FX_ADDLINE, + + CG_GETGLCONFIG, + CG_GETGAMESTATE, + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETDEFAULTSTATE, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_SETCLIENTFORCEANGLE, + CG_SETCLIENTTURNEXTENT, + CG_OPENUIMENU, + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_MEMORY_REMAINING, + 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_PC_LOAD_GLOBAL_DEFINES, + CG_PC_REMOVE_ALL_GLOBAL_DEFINES, + + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, + CG_SNAPVECTOR, + CG_CIN_PLAYCINEMATIC, + CG_CIN_STOPCINEMATIC, + CG_CIN_RUNCINEMATIC, + CG_CIN_DRAWCINEMATIC, + CG_CIN_SETEXTENTS, + + CG_GET_ENTITY_TOKEN, + CG_R_INPVS, + + CG_FX_REGISTER_EFFECT, + CG_FX_PLAY_EFFECT, + CG_FX_PLAY_ENTITY_EFFECT, + CG_FX_PLAY_EFFECT_ID, + CG_FX_PLAY_PORTAL_EFFECT_ID, + CG_FX_PLAY_ENTITY_EFFECT_ID, + CG_FX_PLAY_BOLTED_EFFECT_ID, + CG_FX_ADD_SCHEDULED_EFFECTS, + CG_FX_INIT_SYSTEM, + CG_FX_SET_REFDEF, + CG_FX_FREE_SYSTEM, + CG_FX_ADJUST_TIME, + CG_FX_DRAW_2D_EFFECTS, + CG_FX_RESET, + CG_FX_ADDPOLY, + CG_FX_ADDBEZIER, + CG_FX_ADDPRIMITIVE, + CG_FX_ADDSPRITE, + CG_FX_ADDELECTRICITY, + +// CG_SP_PRINT, + CG_SP_GETSTRINGTEXTSTRING, + + CG_ROFF_CLEAN, + CG_ROFF_UPDATE_ENTITIES, + CG_ROFF_CACHE, + CG_ROFF_PLAY, + CG_ROFF_PURGE_ENT, + + + //rww - dynamic vm memory allocation! + CG_TRUEMALLOC, + CG_TRUEFREE, + +/* +Ghoul2 Insert Start +*/ + CG_G2_LISTSURFACES, + CG_G2_LISTBONES, + CG_G2_SETMODELS, + CG_G2_HAVEWEGHOULMODELS, + CG_G2_GETBOLT, + CG_G2_GETBOLT_NOREC, + CG_G2_GETBOLT_NOREC_NOROT, + CG_G2_INITGHOUL2MODEL, + CG_G2_SETSKIN, + CG_G2_COLLISIONDETECT, + CG_G2_COLLISIONDETECTCACHE, + CG_G2_CLEANMODELS, + CG_G2_ANGLEOVERRIDE, + CG_G2_PLAYANIM, + CG_G2_GETBONEANIM, + CG_G2_GETBONEFRAME, //trimmed down version of GBA, so I don't have to pass all those unused args across the VM-exe border + CG_G2_GETGLANAME, + CG_G2_COPYGHOUL2INSTANCE, + CG_G2_COPYSPECIFICGHOUL2MODEL, + CG_G2_DUPLICATEGHOUL2INSTANCE, + CG_G2_HASGHOUL2MODELONINDEX, + CG_G2_REMOVEGHOUL2MODEL, + CG_G2_SKINLESSMODEL, + CG_G2_GETNUMGOREMARKS, + CG_G2_ADDSKINGORE, + CG_G2_CLEARSKINGORE, + CG_G2_SIZE, + CG_G2_ADDBOLT, + CG_G2_ATTACHENT, + CG_G2_SETBOLTON, + CG_G2_SETROOTSURFACE, + CG_G2_SETSURFACEONOFF, + CG_G2_SETNEWORIGIN, + CG_G2_DOESBONEEXIST, + CG_G2_GETSURFACERENDERSTATUS, + + CG_G2_GETTIME, + CG_G2_SETTIME, + + CG_G2_ABSURDSMOOTHING, + +/* + //rww - RAGDOLL_BEGIN +*/ + CG_G2_SETRAGDOLL, + CG_G2_ANIMATEG2MODELS, +/* + //rww - RAGDOLL_END +*/ + + //additional ragdoll options -rww + CG_G2_RAGPCJCONSTRAINT, + CG_G2_RAGPCJGRADIENTSPEED, + CG_G2_RAGEFFECTORGOAL, + CG_G2_GETRAGBONEPOS, + CG_G2_RAGEFFECTORKICK, + CG_G2_RAGFORCESOLVE, + + //rww - ik move method, allows you to specify a bone and move it to a world point (within joint constraints) + //by using the majority of gil's existing bone angling stuff from the ragdoll code. + CG_G2_SETBONEIKSTATE, + CG_G2_IKMOVE, + + CG_G2_REMOVEBONE, + + CG_G2_ATTACHINSTANCETOENTNUM, + CG_G2_CLEARATTACHEDINSTANCE, + CG_G2_CLEANENTATTACHMENTS, + CG_G2_OVERRIDESERVER, + + CG_G2_GETSURFACENAME, + + CG_SET_SHARED_BUFFER, + + CG_CM_REGISTER_TERRAIN, + CG_RMG_INIT, + CG_RE_INIT_RENDERER_TERRAIN, + CG_R_WEATHER_CONTENTS_OVERRIDE, + CG_R_WORLDEFFECTCOMMAND, + //Adding trap to get weather working + CG_WE_ADDWEATHERZONE + +/* +Ghoul2 Insert End +*/ +} 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); + + CG_POINT_CONTENTS, +// int CG_PointContents( const vec3_t point, int passEntityNum ); + + CG_GET_LERP_ORIGIN, +// void CG_LerpOrigin(int num, vec3_t result); + + CG_GET_LERP_DATA, + CG_GET_GHOUL2, + CG_GET_MODEL_LIST, + + CG_CALC_LERP_POSITIONS, +// void CG_CalcEntityLerpPositions(int num); + + CG_TRACE, + CG_G2TRACE, +//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 ); + + CG_G2MARK, + + CG_RAG_CALLBACK, + + CG_INCOMING_CONSOLE_COMMAND, + + CG_GET_USEABLE_FORCE, + + CG_GET_ORIGIN, // int entnum, vec3_t origin + CG_GET_ANGLES, // int entnum, vec3_t angle + + CG_GET_ORIGIN_TRAJECTORY, // int entnum + CG_GET_ANGLE_TRAJECTORY, // int entnum + + CG_ROFF_NOTETRACK_CALLBACK, // int entnum, char *notetrack + + CG_IMPACT_MARK, +//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 ) + + CG_MAP_CHANGE, + + CG_AUTOMAP_INPUT, + + CG_MISC_ENT, //rwwRMG - added + + CG_GET_SORTED_FORCE_POWER, + + CG_FX_CAMERASHAKE,//mcg post-gold added +} cgameExport_t; + +typedef struct +{ + float up; + float down; + float yaw; + float pitch; + qboolean goToDefaults; +} autoMapInput_t; + +// CG_POINT_CONTENTS +typedef struct +{ + vec3_t mPoint; // input + int mPassEntityNum; // input +} TCGPointContents; + +// CG_GET_BOLT_POS +typedef struct +{ + vec3_t mOrigin; // output + vec3_t mAngles; // output + vec3_t mScale; // output + int mEntityNum; // input +} TCGGetBoltData; + +// CG_IMPACT_MARK +typedef struct +{ + int mHandle; + vec3_t mPoint; + vec3_t mAngle; + float mRotation; + float mRed; + float mGreen; + float mBlue; + float mAlphaStart; + float mSizeStart; +} TCGImpactMark; + +// CG_GET_LERP_ORIGIN +// CG_GET_LERP_ANGLES +// CG_GET_MODEL_SCALE +typedef struct +{ + int mEntityNum; // input + vec3_t mPoint; // output +} TCGVectorData; + +// CG_TRACE/CG_G2TRACE +typedef struct +{ + trace_t mResult; // output + vec3_t mStart, mMins, mMaxs, mEnd; // input + int mSkipNumber, mMask; // input +} TCGTrace; + +// CG_G2MARK +typedef struct +{ + int shader; + float size; + vec3_t start, dir; +} TCGG2Mark; + +// CG_INCOMING_CONSOLE_COMMAND +typedef struct +{ + char conCommand[1024]; +} TCGIncomingConsoleCommand; + +// CG_FX_CAMERASHAKE +typedef struct +{ + vec3_t mOrigin; // input + float mIntensity; // input + int mRadius; // input + int mTime; // input +} TCGCameraShake; + +// CG_MISC_ENT +typedef struct +{ + char mModel[MAX_QPATH]; // input + vec3_t mOrigin, mAngles, mScale; // input +} TCGMiscEnt; + +typedef struct +{ + refEntity_t ent; // output + void *ghoul2; // input + int modelIndex; // input + int boltIndex; // input + vec3_t origin; // input + vec3_t angles; // input + vec3_t modelScale; // input +} TCGPositionOnBolt; + +//ragdoll callback structs -rww +#define RAG_CALLBACK_NONE 0 +#define RAG_CALLBACK_DEBUGBOX 1 +typedef struct +{ + vec3_t mins; + vec3_t maxs; + int duration; +} ragCallbackDebugBox_t; + +#define RAG_CALLBACK_DEBUGLINE 2 +typedef struct +{ + vec3_t start; + vec3_t end; + int time; + int color; + int radius; +} ragCallbackDebugLine_t; + +#define RAG_CALLBACK_BONESNAP 3 +typedef struct +{ + char boneName[128]; //name of the bone in question + int entNum; //index of entity who owns the bone in question +} ragCallbackBoneSnap_t; + +#define RAG_CALLBACK_BONEIMPACT 4 +typedef struct +{ + char boneName[128]; //name of the bone in question + int entNum; //index of entity who owns the bone in question +} ragCallbackBoneImpact_t; + +#define RAG_CALLBACK_BONEINSOLID 5 +typedef struct +{ + vec3_t bonePos; //world coordinate position of the bone + int entNum; //index of entity who owns the bone in question + int solidCount; //higher the count, the longer we've been in solid (the worse off we are) +} ragCallbackBoneInSolid_t; + +#define RAG_CALLBACK_TRACELINE 6 +typedef struct +{ + trace_t tr; + vec3_t start; + vec3_t end; + vec3_t mins; + vec3_t maxs; + int ignore; + int mask; +} ragCallbackTraceLine_t; + +#define MAX_CG_SHARED_BUFFER_SIZE 2048 + +//---------------------------------------------- + +#endif // __CG_PUBLIC_H diff --git a/code/cgame/cg_saga.c b/code/cgame/cg_saga.c new file mode 100644 index 0000000..12aeca4 --- /dev/null +++ b/code/cgame/cg_saga.c @@ -0,0 +1,1094 @@ +// Copyright (C) 2000-2002 Raven Software, Inc. +// +/***************************************************************************** + * name: cg_siege.c + * + * desc: Clientgame-side module for Siege gametype. + * + * $Author: osman $ + * $Revision: 1.5 $ + * + *****************************************************************************/ +#include "cg_local.h" +#include "bg_saga.h" + +int cgSiegeRoundState = 0; +int cgSiegeRoundTime = 0; + +static char team1[512]; +static char team2[512]; + +int team1Timed = 0; +int team2Timed = 0; + +int cgSiegeTeam1PlShader = 0; +int cgSiegeTeam2PlShader = 0; + +static char cgParseObjectives[MAX_SIEGE_INFO_SIZE]; + +extern void CG_LoadCISounds(clientInfo_t *ci, qboolean modelloaded); //cg_players.c + +void CG_DrawSiegeMessage( const char *str, int objectiveScreen ); +void CG_DrawSiegeMessageNonMenu( const char *str ); +void CG_SiegeBriefingDisplay(int team, int dontshow); + +void CG_PrecacheSiegeObjectiveAssetsForTeam(int myTeam) +{ + char teamstr[64]; + char objstr[256]; + char foundobjective[MAX_SIEGE_INFO_SIZE]; + + if (!siege_valid) + { + CG_Error("Siege data does not exist on client!\n"); + return; + } + + if (myTeam == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { + int i = 1; + while (i < 32) + { //eh, just try 32 I guess + Com_sprintf(objstr, sizeof(objstr), "Objective%i", i); + + if (BG_SiegeGetValueGroup(cgParseObjectives, objstr, foundobjective)) + { + char str[MAX_QPATH]; + + if (BG_SiegeGetPairedValue(foundobjective, "sound_team1", str)) + { + trap_S_RegisterSound(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "sound_team2", str)) + { + trap_S_RegisterSound(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "objgfx", str)) + { + trap_R_RegisterShaderNoMip(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "mapicon", str)) + { + trap_R_RegisterShaderNoMip(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "litmapicon", str)) + { + trap_R_RegisterShaderNoMip(str); + } + if (BG_SiegeGetPairedValue(foundobjective, "donemapicon", str)) + { + trap_R_RegisterShaderNoMip(str); + } + } + else + { //no more + break; + } + i++; + } + } +} + +void CG_PrecachePlayersForSiegeTeam(int team) +{ + siegeTeam_t *stm; + int i = 0; + + stm = BG_SiegeFindThemeForTeam(team); + + if (!stm) + { //invalid team/no theme for team? + return; + } + + while (i < stm->numClasses) + { + siegeClass_t *scl = stm->classes[i]; + + if (scl->forcedModel[0]) + { + clientInfo_t fake; + + memset(&fake, 0, sizeof(fake)); + strcpy(fake.modelName, scl->forcedModel); + + trap_R_RegisterModel(va("models/players/%s/model.glm", scl->forcedModel)); + if (scl->forcedSkin[0]) + { + trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", scl->forcedModel, scl->forcedSkin)); + strcpy(fake.skinName, scl->forcedSkin); + } + else + { + strcpy(fake.skinName, "default"); + } + + //precache the sounds for the model... + CG_LoadCISounds(&fake, qtrue); + } + + i++; + } +} + +void CG_InitSiegeMode(void) +{ + char levelname[MAX_QPATH]; + char btime[1024]; + char teams[2048]; + char teamInfo[MAX_SIEGE_INFO_SIZE]; + int len = 0; + int i = 0; + int j = 0; + siegeClass_t *cl; + siegeTeam_t *sTeam; + fileHandle_t f; + char teamIcon[128]; + + if (cgs.gametype != GT_SIEGE) + { + goto failure; + } + + Com_sprintf(levelname, sizeof(levelname), "%s\0", cgs.mapname); + + i = strlen(levelname)-1; + + while (i > 0 && levelname[i] && levelname[i] != '.') + { + i--; + } + + if (!i) + { + goto failure; + } + + levelname[i] = '\0'; //kill the ".bsp" + + Com_sprintf(levelname, sizeof(levelname), "%s.siege\0", levelname); + + if (!levelname || !levelname[0]) + { + goto failure; + } + + len = trap_FS_FOpenFile(levelname, &f, FS_READ); + + if (!f || len >= MAX_SIEGE_INFO_SIZE) + { + goto failure; + } + + trap_FS_Read(siege_info, len, f); + + trap_FS_FCloseFile(f); + + siege_valid = 1; + + if (BG_SiegeGetValueGroup(siege_info, "Teams", teams)) + { + char buf[1024]; + + trap_Cvar_VariableStringBuffer("cg_siegeTeam1", buf, 1024); + if (buf[0] && Q_stricmp(buf, "none")) + { + strcpy(team1, buf); + } + else + { + BG_SiegeGetPairedValue(teams, "team1", team1); + } + + if (team1[0] == '@') + { //it's a damn stringed reference. + char b[256]; + trap_SP_GetStringTextString(team1+1, b, 256); + trap_Cvar_Set("cg_siegeTeam1Name", b); + } + else + { + trap_Cvar_Set("cg_siegeTeam1Name", team1); + } + + trap_Cvar_VariableStringBuffer("cg_siegeTeam2", buf, 1024); + if (buf[0] && Q_stricmp(buf, "none")) + { + strcpy(team2, buf); + } + else + { + BG_SiegeGetPairedValue(teams, "team2", team2); + } + + if (team2[0] == '@') + { //it's a damn stringed reference. + char b[256]; + trap_SP_GetStringTextString(team2+1, b, 256); + trap_Cvar_Set("cg_siegeTeam2Name", b); + } + else + { + trap_Cvar_Set("cg_siegeTeam2Name", team2); + } + } + else + { + CG_Error("Siege teams not defined"); + } + + if (BG_SiegeGetValueGroup(siege_info, team1, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "TeamIcon", teamIcon)) + { + trap_Cvar_Set( "team1_icon", teamIcon); + } + + if (BG_SiegeGetPairedValue(teamInfo, "Timed", btime)) + { + team1Timed = atoi(btime)*1000; + CG_SetSiegeTimerCvar ( team1Timed ); + } + else + { + team1Timed = 0; + } + } + else + { + CG_Error("No team entry for '%s'\n", team1); + } + + if (BG_SiegeGetPairedValue(siege_info, "mapgraphic", teamInfo)) + { + trap_Cvar_Set("siege_mapgraphic", teamInfo); + } + else + { + trap_Cvar_Set("siege_mapgraphic", "gfx/mplevels/siege1_hoth"); + } + + if (BG_SiegeGetPairedValue(siege_info, "missionname", teamInfo)) + { + trap_Cvar_Set("siege_missionname", teamInfo); + } + else + { + trap_Cvar_Set("siege_missionname", " "); + } + + if (BG_SiegeGetValueGroup(siege_info, team2, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "TeamIcon", teamIcon)) + { + trap_Cvar_Set( "team2_icon", teamIcon); + } + + if (BG_SiegeGetPairedValue(teamInfo, "Timed", btime)) + { + team2Timed = atoi(btime)*1000; + CG_SetSiegeTimerCvar ( team2Timed ); + } + else + { + team2Timed = 0; + } + } + else + { + CG_Error("No team entry for '%s'\n", team2); + } + + //Load the player class types + BG_SiegeLoadClasses(NULL); + + if (!bgNumSiegeClasses) + { //We didn't find any?! + CG_Error("Couldn't find any player classes for Siege"); + } + + //Now load the teams since we have class data. + BG_SiegeLoadTeams(); + + if (!bgNumSiegeTeams) + { //React same as with classes. + CG_Error("Couldn't find any player teams for Siege"); + } + + //Get and set the team themes for each team. This will control which classes can be + //used on each team. + if (BG_SiegeGetValueGroup(siege_info, team1, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "UseTeam", btime)) + { + BG_SiegeSetTeamTheme(SIEGETEAM_TEAM1, btime); + } + if (BG_SiegeGetPairedValue(teamInfo, "FriendlyShader", btime)) + { + cgSiegeTeam1PlShader = trap_R_RegisterShaderNoMip(btime); + } + else + { + cgSiegeTeam1PlShader = 0; + } + } + if (BG_SiegeGetValueGroup(siege_info, team2, teamInfo)) + { + if (BG_SiegeGetPairedValue(teamInfo, "UseTeam", btime)) + { + BG_SiegeSetTeamTheme(SIEGETEAM_TEAM2, btime); + } + if (BG_SiegeGetPairedValue(teamInfo, "FriendlyShader", btime)) + { + cgSiegeTeam2PlShader = trap_R_RegisterShaderNoMip(btime); + } + else + { + cgSiegeTeam2PlShader = 0; + } + } + + //Now go through the classes used by the loaded teams and try to precache + //any forced models or forced skins. + i = SIEGETEAM_TEAM1; + + while (i <= SIEGETEAM_TEAM2) + { + j = 0; + sTeam = BG_SiegeFindThemeForTeam(i); + + if (!sTeam) + { + i++; + continue; + } + + //Get custom team shaders while we're at it. + if (i == SIEGETEAM_TEAM1) + { + cgSiegeTeam1PlShader = sTeam->friendlyShader; + } + else if (i == SIEGETEAM_TEAM2) + { + cgSiegeTeam2PlShader = sTeam->friendlyShader; + } + + while (j < sTeam->numClasses) + { + cl = sTeam->classes[j]; + + if (cl->forcedModel[0]) + { //This class has a forced model, so precache it. + trap_R_RegisterModel(va("models/players/%s/model.glm", cl->forcedModel)); + + if (cl->forcedSkin[0]) + { //also has a forced skin, precache it. + char *useSkinName; + + if (strchr(cl->forcedSkin, '|')) + {//three part skin + useSkinName = va("models/players/%s/|%s", cl->forcedModel, cl->forcedSkin); + } + else + { + useSkinName = va("models/players/%s/model_%s.skin", cl->forcedModel, cl->forcedSkin); + } + + trap_R_RegisterSkin(useSkinName); + } + } + + j++; + } + i++; + } + + //precache saber data for classes that use sabers on both teams + BG_PrecacheSabersForSiegeTeam(SIEGETEAM_TEAM1); + BG_PrecacheSabersForSiegeTeam(SIEGETEAM_TEAM2); + + CG_PrecachePlayersForSiegeTeam(SIEGETEAM_TEAM1); + CG_PrecachePlayersForSiegeTeam(SIEGETEAM_TEAM2); + + CG_PrecachePlayersForSiegeTeam(SIEGETEAM_TEAM1); + CG_PrecachePlayersForSiegeTeam(SIEGETEAM_TEAM2); + + CG_PrecacheSiegeObjectiveAssetsForTeam(SIEGETEAM_TEAM1); + CG_PrecacheSiegeObjectiveAssetsForTeam(SIEGETEAM_TEAM2); + + return; +failure: + siege_valid = 0; +} + +static char CGAME_INLINE *CG_SiegeObjectiveBuffer(int team, int objective) +{ + static char buf[8192]; + char teamstr[1024]; + + if (team == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { //found the team group + if (BG_SiegeGetValueGroup(cgParseObjectives, va("Objective%i", objective), buf)) + { //found the objective group + return buf; + } + } + + return NULL; +} + +void CG_ParseSiegeObjectiveStatus(const char *str) +{ + int i = 0; + int team = SIEGETEAM_TEAM1; + char *cvarName; + char *s; + int objectiveNum = 0; + + if (!str || !str[0]) + { + return; + } + + while (str[i]) + { + if (str[i] == '|') + { //switch over to team2, this is the next section + team = SIEGETEAM_TEAM2; + objectiveNum = 0; + } + else if (str[i] == '-') + { + objectiveNum++; + i++; + + cvarName = va("team%i_objective%i", team, objectiveNum); + if (str[i] == '1') + { //it's completed + trap_Cvar_Set(cvarName, "1"); + } + else + { //otherwise assume it is not + trap_Cvar_Set(cvarName, "0"); + } + + s = CG_SiegeObjectiveBuffer(team, objectiveNum); + if (s && s[0]) + { //now set the description and graphic cvars to by read by the menu + char buffer[8192]; + + cvarName = va("team%i_objective%i_longdesc", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "objdesc", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_gfx", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "objgfx", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_mapicon", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "mapicon", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_litmapicon", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "litmapicon", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_donemapicon", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "donemapicon", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "UNSPECIFIED"); + } + + cvarName = va("team%i_objective%i_mappos", team, objectiveNum); + if (BG_SiegeGetPairedValue(s, "mappos", buffer)) + { + trap_Cvar_Set(cvarName, buffer); + } + else + { + trap_Cvar_Set(cvarName, "0 0 32 32"); + } + } + } + i++; + } + + if (cg.predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR) + { //update menu cvars + CG_SiegeBriefingDisplay(cg.predictedPlayerState.persistant[PERS_TEAM], 1); + } +} + +void CG_SiegeRoundOver(centity_t *ent, int won) +{ + int myTeam; + char teamstr[64]; + char appstring[1024]; + char soundstr[1024]; + int success = 0; + playerState_t *ps = NULL; + + if (!siege_valid) + { + CG_Error("ERROR: Siege data does not exist on client!\n"); + return; + } + + if (cg.snap) + { //this should always be true, if it isn't though use the predicted ps as a fallback + ps = &cg.snap->ps; + } + else + { + ps = &cg.predictedPlayerState; + } + + if (!ps) + { + assert(0); + return; + } + + myTeam = ps->persistant[PERS_TEAM]; + + if (myTeam == TEAM_SPECTATOR) + { + return; + } + + if (myTeam == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { + if (won == myTeam) + { + success = BG_SiegeGetPairedValue(cgParseObjectives, "wonround", appstring); + } + else + { + success = BG_SiegeGetPairedValue(cgParseObjectives, "lostround", appstring); + } + + if (success) + { + CG_DrawSiegeMessage(appstring, 0); + } + + appstring[0] = 0; + soundstr[0] = 0; + + if (myTeam == won) + { + Com_sprintf(teamstr, sizeof(teamstr), "roundover_sound_wewon"); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), "roundover_sound_welost"); + } + + if (BG_SiegeGetPairedValue(cgParseObjectives, teamstr, appstring)) + { + Com_sprintf(soundstr, sizeof(soundstr), appstring); + } + /* + else + { + if (myTeam != won) + { + Com_sprintf(soundstr, sizeof(soundstr), DEFAULT_LOSE_ROUND); + } + else + { + Com_sprintf(soundstr, sizeof(soundstr), DEFAULT_WIN_ROUND); + } + } + */ + + if (soundstr[0]) + { + trap_S_StartLocalSound(trap_S_RegisterSound(soundstr), CHAN_ANNOUNCER); + } + } +} + +void CG_SiegeGetObjectiveDescription(int team, int objective, char *buffer) +{ + char teamstr[1024]; + char objectiveStr[8192]; + + buffer[0] = 0; //set to 0 ahead of time in case we fail to find the objective group/name + + if (team == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { //found the team group + if (BG_SiegeGetValueGroup(cgParseObjectives, va("Objective%i", objective), objectiveStr)) + { //found the objective group + //Parse the name right into the buffer. + BG_SiegeGetPairedValue(objectiveStr, "goalname", buffer); + } + } +} + +int CG_SiegeGetObjectiveFinal(int team, int objective ) +{ + char finalStr[64]; + char teamstr[1024]; + char objectiveStr[8192]; + + if (team == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { //found the team group + if (BG_SiegeGetValueGroup(cgParseObjectives, va("Objective%i", objective), objectiveStr)) + { //found the objective group + //Parse the name right into the buffer. + BG_SiegeGetPairedValue(objectiveStr, "final", finalStr); + return (atoi( finalStr )); + } + } + return 0; +} + +void CG_SiegeBriefingDisplay(int team, int dontshow) +{ + char teamstr[64]; + char briefing[8192]; + char properValue[1024]; + char objectiveDesc[1024]; + int i = 1; + int useTeam = team; + qboolean primary = qfalse; + + if (!siege_valid) + { + return; + } + + if (team == TEAM_SPECTATOR) + { + return; + } + + if (team == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (useTeam != SIEGETEAM_TEAM1 && useTeam != SIEGETEAM_TEAM2) + { //This shouldn't be happening. But just fall back to team 2 anyway. + useTeam = SIEGETEAM_TEAM2; + } + + trap_Cvar_Set(va("siege_primobj_inuse"), "0"); + + while (i < 16) + { //do up to 16 objectives I suppose + //Get the value for this objective on this team + //Now set the cvar for the menu to display. + + //primary = (CG_SiegeGetObjectiveFinal(useTeam, i)>-1)?qtrue:qfalse; + primary = (CG_SiegeGetObjectiveFinal(useTeam, i)>0)?qtrue:qfalse; + + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i", i), properValue); + } + + //Now set the long desc cvar for the menu to display. + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i_longdesc", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj_longdesc"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i_longdesc", i), properValue); + } + + //Now set the gfx cvar for the menu to display. + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i_gfx", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj_gfx"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i_gfx", i), properValue); + } + + //Now set the mapicon cvar for the menu to display. + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i_mapicon", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj_mapicon"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i_mapicon", i), properValue); + } + + //Now set the mappos cvar for the menu to display. + properValue[0] = 0; + trap_Cvar_VariableStringBuffer(va("team%i_objective%i_mappos", useTeam, i), properValue, 1024); + if (primary) + { + trap_Cvar_Set(va("siege_primobj_mappos"), properValue); + } + else + { + trap_Cvar_Set(va("siege_objective%i_mappos", i), properValue); + } + + //Now set the description cvar for the objective + CG_SiegeGetObjectiveDescription(useTeam, i, objectiveDesc); + + if (objectiveDesc[0]) + { //found a valid objective description + if ( primary ) + { + trap_Cvar_Set(va("siege_primobj_desc"), objectiveDesc); + //this one is marked not in use because it gets primobj + trap_Cvar_Set(va("siege_objective%i_inuse", i), "0"); + trap_Cvar_Set(va("siege_primobj_inuse"), "1"); + + trap_Cvar_Set(va("team%i_objective%i_inuse", useTeam, i), "1"); + + } + else + { + trap_Cvar_Set(va("siege_objective%i_desc", i), objectiveDesc); + trap_Cvar_Set(va("siege_objective%i_inuse", i), "2"); + trap_Cvar_Set(va("team%i_objective%i_inuse", useTeam, i), "2"); + + } + } + else + { //didn't find one, so set the "inuse" cvar to 0 for the objective and mark it non-complete. + trap_Cvar_Set(va("siege_objective%i_inuse", i), "0"); + trap_Cvar_Set(va("siege_objective%i", i), "0"); + trap_Cvar_Set(va("team%i_objective%i_inuse", useTeam, i), "0"); + trap_Cvar_Set(va("team%i_objective%i", useTeam, i), "0"); + + trap_Cvar_Set(va("siege_objective%i_mappos", i), ""); + trap_Cvar_Set(va("team%i_objective%i_mappos", useTeam, i), ""); + trap_Cvar_Set(va("siege_objective%i_gfx", i), ""); + trap_Cvar_Set(va("team%i_objective%i_gfx", useTeam, i), ""); + trap_Cvar_Set(va("siege_objective%i_mapicon", i), ""); + trap_Cvar_Set(va("team%i_objective%i_mapicon", useTeam, i), ""); + } + + i++; + } + + if (dontshow) + { + return; + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { + if (BG_SiegeGetPairedValue(cgParseObjectives, "briefing", briefing)) + { + CG_DrawSiegeMessage(briefing, 1); + } + } +} + +void CG_SiegeObjectiveCompleted(centity_t *ent, int won, int objectivenum) +{ + int myTeam; + char teamstr[64]; + char objstr[256]; + char foundobjective[MAX_SIEGE_INFO_SIZE]; + char appstring[1024]; + char soundstr[1024]; + int success = 0; + playerState_t *ps = NULL; + + if (!siege_valid) + { + CG_Error("Siege data does not exist on client!\n"); + return; + } + + if (cg.snap) + { //this should always be true, if it isn't though use the predicted ps as a fallback + ps = &cg.snap->ps; + } + else + { + ps = &cg.predictedPlayerState; + } + + if (!ps) + { + assert(0); + return; + } + + myTeam = ps->persistant[PERS_TEAM]; + + if (myTeam == TEAM_SPECTATOR) + { + return; + } + + if (won == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), team1); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), team2); + } + + if (BG_SiegeGetValueGroup(siege_info, teamstr, cgParseObjectives)) + { + Com_sprintf(objstr, sizeof(objstr), "Objective%i", objectivenum); + + if (BG_SiegeGetValueGroup(cgParseObjectives, objstr, foundobjective)) + { + if (myTeam == SIEGETEAM_TEAM1) + { + success = BG_SiegeGetPairedValue(foundobjective, "message_team1", appstring); + } + else + { + success = BG_SiegeGetPairedValue(foundobjective, "message_team2", appstring); + } + + if (success) + { + CG_DrawSiegeMessageNonMenu(appstring); + } + + appstring[0] = 0; + soundstr[0] = 0; + + if (myTeam == SIEGETEAM_TEAM1) + { + Com_sprintf(teamstr, sizeof(teamstr), "sound_team1"); + } + else + { + Com_sprintf(teamstr, sizeof(teamstr), "sound_team2"); + } + + if (BG_SiegeGetPairedValue(foundobjective, teamstr, appstring)) + { + Com_sprintf(soundstr, sizeof(soundstr), appstring); + } + /* + else + { + if (myTeam != won) + { + Com_sprintf(soundstr, sizeof(soundstr), DEFAULT_LOSE_OBJECTIVE); + } + else + { + Com_sprintf(soundstr, sizeof(soundstr), DEFAULT_WIN_OBJECTIVE); + } + } + */ + + if (soundstr[0]) + { + trap_S_StartLocalSound(trap_S_RegisterSound(soundstr), CHAN_ANNOUNCER); + } + } + } +} + +siegeExtended_t cg_siegeExtendedData[MAX_CLIENTS]; + +//parse a single extended siege data entry +void CG_ParseSiegeExtendedDataEntry(const char *conStr) +{ + char s[MAX_STRING_CHARS]; + char *str = (char *)conStr; + int argParses = 0; + int i; + int maxAmmo = 0, clNum = -1, health = 1, maxhealth = 1, ammo = 1; + centity_t *cent; + + if (!conStr || !conStr[0]) + { + return; + } + + while (*str && argParses < 4) + { + i = 0; + while (*str && *str != '|') + { + s[i] = *str; + i++; + *str++; + } + s[i] = 0; + switch (argParses) + { + case 0: + clNum = atoi(s); + break; + case 1: + health = atoi(s); + break; + case 2: + maxhealth = atoi(s); + break; + case 3: + ammo = atoi(s); + break; + default: + break; + } + argParses++; + str++; + } + + if (clNum < 0 || clNum >= MAX_CLIENTS) + { + return; + } + + cg_siegeExtendedData[clNum].health = health; + cg_siegeExtendedData[clNum].maxhealth = maxhealth; + cg_siegeExtendedData[clNum].ammo = ammo; + + cent = &cg_entities[clNum]; + + maxAmmo = ammoData[weaponData[cent->currentState.weapon].ammoIndex].max; + if ( (cent->currentState.eFlags & EF_DOUBLE_AMMO) ) + { + maxAmmo *= 2.0f; + } + if (ammo >= 0 && ammo <= maxAmmo ) + { //assure the weapon number is valid and not over max + //keep the weapon so if it changes before our next ext data update we'll know + //that the ammo is not applicable. + cg_siegeExtendedData[clNum].weapon = cent->currentState.weapon; + } + else + { //not valid? Oh well, just invalidate the weapon too then so we don't display ammo + cg_siegeExtendedData[clNum].weapon = -1; + } + + cg_siegeExtendedData[clNum].lastUpdated = cg.time; +} + +//parse incoming siege data, see counterpart in g_saga.c +void CG_ParseSiegeExtendedData(void) +{ + int numEntries = trap_Argc(); + int i = 0; + + if (numEntries < 1) + { + assert(!"Bad numEntries for sxd"); + return; + } + + while (i < numEntries) + { + CG_ParseSiegeExtendedDataEntry(CG_Argv(i+1)); + i++; + } +} + +void CG_SetSiegeTimerCvar ( int msec ) +{ + int seconds; + int mins; + int tens; + + seconds = msec / 1000; + mins = seconds / 60; + seconds -= mins * 60; + tens = seconds / 10; + seconds -= tens * 10; + + trap_Cvar_Set("ui_siegeTimer", va( "%i:%i%i", mins, tens, seconds ) ); +} diff --git a/code/cgame/cg_scoreboard.c b/code/cgame/cg_scoreboard.c new file mode 100644 index 0000000..15d9f18 --- /dev/null +++ b/code/cgame/cg_scoreboard.c @@ -0,0 +1,639 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_scoreboard -- draw the scoreboard on top of the game screen +#include "cg_local.h" +#include "../ui/ui_shared.h" +#include "../game/bg_saga.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 25 +#define SB_INTER_HEIGHT 15 // 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 100 +#define SB_SCORELINE_WIDTH (640 - SB_SCORELINE_X * 2) + +#define SB_RATING_WIDTH 0 // (6 * BIGCHAR_WIDTH) +#define SB_NAME_X (SB_SCORELINE_X) +#define SB_SCORE_X (SB_SCORELINE_X + .55 * SB_SCORELINE_WIDTH) +#define SB_PING_X (SB_SCORELINE_X + .70 * SB_SCORELINE_WIDTH) +#define SB_TIME_X (SB_SCORELINE_X + .85 * SB_SCORELINE_WIDTH) + +// 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 ) +{ + //vec3_t headAngles; + clientInfo_t *ci; + int iconx, headx; + float scale; + + if ( largeFormat ) + { + scale = 1.0f; + } + else + { + scale = 0.75f; + } + + 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*cgs.screenXScale, y*cgs.screenYScale, 32*cgs.screenXScale, 32*cgs.screenYScale, TEAM_RED, qfalse ); + } + else + { + CG_DrawFlagModel( iconx*cgs.screenXScale, y*cgs.screenYScale, 32*cgs.screenXScale, 32*cgs.screenYScale, TEAM_RED, qfalse ); + } + } + else if ( ci->powerups & ( 1 << PW_BLUEFLAG ) ) + { + if( largeFormat ) + { + CG_DrawFlagModel( iconx*cgs.screenXScale, y*cgs.screenYScale, 32*cgs.screenXScale, 32*cgs.screenYScale, TEAM_BLUE, qfalse ); + } + else + { + CG_DrawFlagModel( iconx*cgs.screenXScale, y*cgs.screenYScale, 32*cgs.screenXScale, 32*cgs.screenYScale, TEAM_BLUE, qfalse ); + } + } + else if (cgs.gametype == GT_POWERDUEL && + (ci->duelTeam == DUELTEAM_LONE || ci->duelTeam == DUELTEAM_DOUBLE)) + { + if (ci->duelTeam == DUELTEAM_LONE) + { + CG_DrawPic ( iconx, y, 32, 32, trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_lone" ) ); + } + else + { + CG_DrawPic ( iconx, y, 32, 32, trap_R_RegisterShaderNoMip ( "gfx/mp/pduel_icon_double" ) ); + } + } + else if (cgs.gametype == GT_SIEGE) + { //try to draw the shader for this class on the scoreboard + if (ci->siegeIndex != -1) + { + siegeClass_t *scl = &bgSiegeClasses[ci->siegeIndex]; + + if (scl->classShader) + { + CG_DrawPic (iconx, y, largeFormat?24:12, largeFormat?24:12, scl->classShader); + } + } + } + else + { + // draw the wins / losses + /* + if ( cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL ) + { + CG_DrawSmallStringColor( iconx, y + SMALLCHAR_HEIGHT/2, va("%i/%i", ci->wins, ci->losses ), color ); + } + */ + //rww - in duel, we now show wins/losses in place of "frags". This is because duel now defaults to 1 kill per round. + } + + // 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 - 5, y + 2, 640 - SB_SCORELINE_X * 2 + 10, largeFormat?SB_NORMAL_HEIGHT:SB_INTER_HEIGHT, hcolor ); + } + + CG_Text_Paint (SB_NAME_X, y, 0.9f * scale, colorWhite, ci->name,0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + + if ( score->ping != -1 ) + { + if ( ci->team != TEAM_SPECTATOR || cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL ) + { + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) + { + CG_Text_Paint (SB_SCORE_X, y, 1.0f * scale, colorWhite, va("%i/%i", ci->wins, ci->losses),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + } + else + { + CG_Text_Paint (SB_SCORE_X, y, 1.0f * scale, colorWhite, va("%i", score->score),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + } + } + + CG_Text_Paint (SB_PING_X, y, 1.0f * scale, colorWhite, va("%i", score->ping),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + CG_Text_Paint (SB_TIME_X, y, 1.0f * scale, colorWhite, va("%i", score->time),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + } + else + { + CG_Text_Paint (SB_SCORE_X, y, 1.0f * scale, colorWhite, "-",0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + CG_Text_Paint (SB_PING_X, y, 1.0f * scale, colorWhite, "-",0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + CG_Text_Paint (SB_TIME_X, y, 1.0f * scale, colorWhite, "-",0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL ); + } + + // add the "ready" marker for intermission exiting + if ( cg.snap->ps.stats[ STAT_CLIENTS_READY ] & ( 1 << score->client ) ) + { + CG_Text_Paint (SB_NAME_X - 64, y + 2, 0.7f * scale, colorWhite, CG_GetStringEdString("MP_INGAME", "READY"),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } +} + +/* +================= +CG_TeamScoreboard +================= +*/ +static int CG_TeamScoreboard( int y, team_t team, float fade, int maxClients, int lineHeight, qboolean countOnly ) +{ + 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; + } + + if ( !countOnly ) + { + CG_DrawClientScore( y + lineHeight * count, score, color, fade, lineHeight == SB_NORMAL_HEIGHT ); + } + + count++; + } + + return count; +} + +int CG_GetClassCount(team_t team,int siegeClass ) +{ + int i = 0; + int count = 0; + clientInfo_t *ci; + siegeClass_t *scl; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) + { + ci = &cgs.clientinfo[ i ]; + + if ((!ci->infoValid) || ( team != ci->team )) + { + continue; + } + + scl = &bgSiegeClasses[ci->siegeIndex]; + + // Correct class? + if ( siegeClass != scl->classShader ) + { + continue; + } + + count++; + } + + return count; + +} + +int CG_GetTeamNonScoreCount(team_t team) +{ + int i = 0,count=0; + clientInfo_t *ci; + + for ( i = 0 ; i < cgs.maxclients ; i++ ) + { + ci = &cgs.clientinfo[ i ]; + + if ( (!ci->infoValid) || (team != ci->team && team != ci->siegeDesiredTeam) ) + { + continue; + } + + count++; + } + + return count; +} + +int CG_GetTeamCount(team_t team, int maxClients) +{ + int i = 0; + int count = 0; + clientInfo_t *ci; + score_t *score; + + for ( i = 0 ; i < cg.numScores && count < maxClients ; i++ ) + { + score = &cg.scores[i]; + ci = &cgs.clientinfo[ score->client ]; + + if ( team != ci->team ) + { + continue; + } + + count++; + } + + return count; + +} +/* +================= +CG_DrawScoreboard + +Draw the normal in-game scoreboard +================= +*/ +int cg_siegeWinTeam = 0; +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; + } + + // 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 + // or if in intermission and duel, prints the winner of the duel round + if ((cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) && cgs.duelWinner != -1 && + cg.predictedPlayerState.pm_type == PM_INTERMISSION) + { + s = va("%s^7 %s", cgs.clientinfo[cgs.duelWinner].name, CG_GetStringEdString("MP_INGAME", "DUEL_WINS") ); + /*w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + */ + x = ( SCREEN_WIDTH ) / 2; + y = 40; + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + else if ((cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) && cgs.duelist1 != -1 && cgs.duelist2 != -1 && + cg.predictedPlayerState.pm_type == PM_INTERMISSION) + { + if (cgs.gametype == GT_POWERDUEL && cgs.duelist3 != -1) + { + s = va("%s^7 %s %s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name, CG_GetStringEdString("MP_INGAME", "AND"), cgs.clientinfo[cgs.duelist3].name ); + } + else + { + s = va("%s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name ); + } + /*w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + */ + x = ( SCREEN_WIDTH ) / 2; + y = 40; + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + else if ( cg.killerName[0] ) { + s = va("%s %s", CG_GetStringEdString("MP_INGAME", "KILLEDBY"), cg.killerName ); + /*w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH - w ) / 2; + y = 40; + CG_DrawBigString( x, y, s, fade ); + */ + x = ( SCREEN_WIDTH ) / 2; + y = 40; + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + + // current rank + if (cgs.gametype == GT_POWERDUEL) + { //do nothing? + } + else if ( cgs.gametype < GT_TEAM) { + if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR ) + { + char sPlace[256]; + char sOf[256]; + char sWith[256]; + + trap_SP_GetStringTextString("MP_INGAME_PLACE", sPlace, sizeof(sPlace)); + trap_SP_GetStringTextString("MP_INGAME_OF", sOf, sizeof(sOf)); + trap_SP_GetStringTextString("MP_INGAME_WITH", sWith, sizeof(sWith)); + + s = va("%s %s (%s %i) %s %i", + CG_PlaceString( cg.snap->ps.persistant[PERS_RANK] + 1 ), + sPlace, + sOf, + cg.numScores, + sWith, + cg.snap->ps.persistant[PERS_SCORE] ); + w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; + x = ( SCREEN_WIDTH ) / 2; + y = 60; + //CG_DrawBigString( x, y, s, fade ); + UI_DrawProportionalString(x, y, s, UI_CENTER|UI_DROPSHADOW, colorTable[CT_WHITE]); + } + } + else if (cgs.gametype != GT_SIEGE) + { + if ( cg.teamScores[0] == cg.teamScores[1] ) { + s = va("%s %i", CG_GetStringEdString("MP_INGAME", "TIEDAT"), cg.teamScores[0] ); + } else if ( cg.teamScores[0] >= cg.teamScores[1] ) { + s = va("%s, %i / %i", CG_GetStringEdString("MP_INGAME", "RED_LEADS"), cg.teamScores[0], cg.teamScores[1] ); + } else { + s = va("%s, %i / %i", CG_GetStringEdString("MP_INGAME", "BLUE_LEADS"), cg.teamScores[1], cg.teamScores[0] ); + } + + x = ( SCREEN_WIDTH ) / 2; + y = 60; + + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + else if (cgs.gametype == GT_SIEGE && (cg_siegeWinTeam == 1 || cg_siegeWinTeam == 2)) + { + if (cg_siegeWinTeam == 1) + { + s = va("%s", CG_GetStringEdString("MP_INGAME", "SIEGETEAM1WIN") ); + } + else + { + s = va("%s", CG_GetStringEdString("MP_INGAME", "SIEGETEAM2WIN") ); + } + + x = ( SCREEN_WIDTH ) / 2; + y = 60; + + CG_Text_Paint ( x - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, y, 1.0f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + + // scoreboard + y = SB_HEADER; + + CG_DrawPic ( SB_SCORELINE_X - 40, y - 5, SB_SCORELINE_WIDTH + 80, 40, trap_R_RegisterShaderNoMip ( "gfx/menus/menu_buttonback.tga" ) ); + + CG_Text_Paint ( SB_NAME_X, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "NAME"),0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) + { + char sWL[100]; + trap_SP_GetStringTextString("MP_INGAME_W_L", sWL, sizeof(sWL)); + + CG_Text_Paint ( SB_SCORE_X, y, 1.0f, colorWhite, sWL, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + else + { + CG_Text_Paint ( SB_SCORE_X, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "SCORE"), 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + } + CG_Text_Paint ( SB_PING_X, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "PING"), 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + CG_Text_Paint ( SB_TIME_X, y, 1.0f, colorWhite, CG_GetStringEdString("MP_INGAME", "TIME"), 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_MEDIUM ); + + 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 = 8; + bottomBorderSize = 8; + } + + localClient = qfalse; + + + //I guess this should end up being able to display 19 clients at once. + //In a team game, if there are 9 or more clients on the team not in the lead, + //we only want to show 10 of the clients on the team in the lead, so that we + //have room to display the clients in the lead on the losing team. + + //I guess this can be accomplished simply by printing the first teams score with a maxClients + //value passed in related to how many players are on both teams. + if ( cgs.gametype >= GT_TEAM ) { + // + // teamplay scoreboard + // + y += lineHeight/2; + + if ( cg.teamScores[0] >= cg.teamScores[1] ) { + int team1MaxCl = CG_GetTeamCount(TEAM_RED, maxClients); + int team2MaxCl = CG_GetTeamCount(TEAM_BLUE, maxClients); + + if (team1MaxCl > 10 && (team1MaxCl+team2MaxCl) > maxClients) + { + team1MaxCl -= team2MaxCl; + //subtract as many as you have to down to 10, once we get there + //we just set it to 10 + + if (team1MaxCl < 10) + { + team1MaxCl = 10; + } + } + + team2MaxCl = (maxClients-team1MaxCl); //team2 can display however many is left over after team1's display + + n1 = CG_TeamScoreboard( y, TEAM_RED, fade, team1MaxCl, lineHeight, qtrue ); + CG_DrawTeamBackground( SB_SCORELINE_X - 5, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + CG_TeamScoreboard( y, TEAM_RED, fade, team1MaxCl, lineHeight, qfalse ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + + //maxClients -= n1; + + n2 = CG_TeamScoreboard( y, TEAM_BLUE, fade, team2MaxCl, lineHeight, qtrue ); + CG_DrawTeamBackground( SB_SCORELINE_X - 5, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + CG_TeamScoreboard( y, TEAM_BLUE, fade, team2MaxCl, lineHeight, qfalse ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + + //maxClients -= n2; + + maxClients -= (team1MaxCl+team2MaxCl); + } else { + int team1MaxCl = CG_GetTeamCount(TEAM_BLUE, maxClients); + int team2MaxCl = CG_GetTeamCount(TEAM_RED, maxClients); + + if (team1MaxCl > 10 && (team1MaxCl+team2MaxCl) > maxClients) + { + team1MaxCl -= team2MaxCl; + //subtract as many as you have to down to 10, once we get there + //we just set it to 10 + + if (team1MaxCl < 10) + { + team1MaxCl = 10; + } + } + + team2MaxCl = (maxClients-team1MaxCl); //team2 can display however many is left over after team1's display + + n1 = CG_TeamScoreboard( y, TEAM_BLUE, fade, team1MaxCl, lineHeight, qtrue ); + CG_DrawTeamBackground( SB_SCORELINE_X - 5, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n1 * lineHeight + bottomBorderSize, 0.33f, TEAM_BLUE ); + CG_TeamScoreboard( y, TEAM_BLUE, fade, team1MaxCl, lineHeight, qfalse ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + + //maxClients -= n1; + + n2 = CG_TeamScoreboard( y, TEAM_RED, fade, team2MaxCl, lineHeight, qtrue ); + CG_DrawTeamBackground( SB_SCORELINE_X - 5, y - topBorderSize, 640 - SB_SCORELINE_X * 2 + 10, n2 * lineHeight + bottomBorderSize, 0.33f, TEAM_RED ); + CG_TeamScoreboard( y, TEAM_RED, fade, team2MaxCl, lineHeight, qfalse ); + y += (n2 * lineHeight) + BIGCHAR_HEIGHT; + + //maxClients -= n2; + + maxClients -= (team1MaxCl+team2MaxCl); + } + n1 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients, lineHeight, qfalse ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + + } else { + // + // free for all scoreboard + // + n1 = CG_TeamScoreboard( y, TEAM_FREE, fade, maxClients, lineHeight, qfalse ); + y += (n1 * lineHeight) + BIGCHAR_HEIGHT; + n2 = CG_TeamScoreboard( y, TEAM_SPECTATOR, fade, maxClients - n1, lineHeight, qfalse ); + 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 ) { + 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; +} + +//================================================================================ + diff --git a/code/cgame/cg_servercmds.c b/code/cgame/cg_servercmds.c new file mode 100644 index 0000000..b9acf33 --- /dev/null +++ b/code/cgame/cg_servercmds.c @@ -0,0 +1,1687 @@ +// 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" +#if !defined(CL_LIGHT_H_INC) + #include "cg_lights.h" +#endif +#include "..\ghoul2\g2.h" +#include "../ui/ui_public.h" + +/* +================= +CG_ParseScores + +================= +*/ +static void CG_ParseScores( void ) { + int i, powerups, readScores; + + cg.numScores = atoi( CG_Argv( 1 ) ); + + readScores = cg.numScores; + + if (readScores > MAX_CLIENT_SCORE_SEND) + { + readScores = MAX_CLIENT_SCORE_SEND; + } + + if ( cg.numScores > MAX_CLIENTS ) { + cg.numScores = MAX_CLIENTS; + } + + cg.numScores = readScores; + + 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 < readScores ; i++ ) { + // + cg.scores[i].client = atoi( CG_Argv( i * 14 + 4 ) ); + cg.scores[i].score = atoi( CG_Argv( i * 14 + 5 ) ); + cg.scores[i].ping = atoi( CG_Argv( i * 14 + 6 ) ); + cg.scores[i].time = atoi( CG_Argv( i * 14 + 7 ) ); + cg.scores[i].scoreFlags = atoi( CG_Argv( i * 14 + 8 ) ); + powerups = atoi( CG_Argv( i * 14 + 9 ) ); + cg.scores[i].accuracy = atoi(CG_Argv(i * 14 + 10)); + cg.scores[i].impressiveCount = atoi(CG_Argv(i * 14 + 11)); + cg.scores[i].excellentCount = atoi(CG_Argv(i * 14 + 12)); + cg.scores[i].guantletCount = atoi(CG_Argv(i * 14 + 13)); + cg.scores[i].defendCount = atoi(CG_Argv(i * 14 + 14)); + cg.scores[i].assistCount = atoi(CG_Argv(i * 14 + 15)); + cg.scores[i].perfect = atoi(CG_Argv(i * 14 + 16)); + cg.scores[i].captures = atoi(CG_Argv(i * 14 + 17)); + + 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; + } + CG_SetScoreSelection(NULL); +} + +/* +================= +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; + const char *tinfo; + char *mapname; + + info = CG_ConfigString( CS_SERVERINFO ); + + cgs.debugMelee = atoi( Info_ValueForKey( info, "g_debugMelee" ) ); //trap_Cvar_GetHiddenVarValue("g_iknowkungfu"); + cgs.stepSlideFix = atoi( Info_ValueForKey( info, "g_stepSlideFix" ) ); + + cgs.noSpecMove = atoi( Info_ValueForKey( info, "g_noSpecMove" ) ); + + trap_Cvar_Set("bg_fighterAltControl", Info_ValueForKey( info, "bg_fighterAltControl" )); + + cgs.siegeTeamSwitch = atoi( Info_ValueForKey( info, "g_siegeTeamSwitch" ) ); + + cgs.showDuelHealths = atoi( Info_ValueForKey( info, "g_showDuelHealths" ) ); + + cgs.gametype = atoi( Info_ValueForKey( info, "g_gametype" ) ); + trap_Cvar_Set("g_gametype", va("%i", cgs.gametype)); + cgs.needpass = atoi( Info_ValueForKey( info, "needpass" ) ); + cgs.jediVmerc = atoi( Info_ValueForKey( info, "g_jediVmerc" ) ); + cgs.wDisable = atoi( Info_ValueForKey( info, "wdisable" ) ); + cgs.fDisable = atoi( Info_ValueForKey( info, "fdisable" ) ); + cgs.dmflags = atoi( Info_ValueForKey( info, "dmflags" ) ); + cgs.teamflags = atoi( Info_ValueForKey( info, "teamflags" ) ); + cgs.fraglimit = atoi( Info_ValueForKey( info, "fraglimit" ) ); + cgs.duel_fraglimit = atoi( Info_ValueForKey( info, "duel_fraglimit" ) ); + cgs.capturelimit = atoi( Info_ValueForKey( info, "capturelimit" ) ); + cgs.timelimit = atoi( Info_ValueForKey( info, "timelimit" ) ); + cgs.maxclients = atoi( Info_ValueForKey( info, "sv_maxclients" ) ); + mapname = Info_ValueForKey( info, "mapname" ); + + //rww - You must do this one here, Info_ValueForKey always uses the same memory pointer. + trap_Cvar_Set ( "ui_about_mapname", 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); + + trap_Cvar_Set ( "ui_about_gametype", va("%i", cgs.gametype ) ); + trap_Cvar_Set ( "ui_about_fraglimit", va("%i", cgs.fraglimit ) ); + trap_Cvar_Set ( "ui_about_duellimit", va("%i", cgs.duel_fraglimit ) ); + trap_Cvar_Set ( "ui_about_capturelimit", va("%i", cgs.capturelimit ) ); + trap_Cvar_Set ( "ui_about_timelimit", va("%i", cgs.timelimit ) ); + trap_Cvar_Set ( "ui_about_maxclients", va("%i", cgs.maxclients ) ); + trap_Cvar_Set ( "ui_about_dmflags", va("%i", cgs.dmflags ) ); + trap_Cvar_Set ( "ui_about_hostname", Info_ValueForKey( info, "sv_hostname" ) ); + trap_Cvar_Set ( "ui_about_needpass", Info_ValueForKey( info, "g_needpass" ) ); + trap_Cvar_Set ( "ui_about_botminplayers", Info_ValueForKey ( info, "bot_minplayers" ) ); + + //Set the siege teams based on what the server has for overrides. + trap_Cvar_Set("cg_siegeTeam1", Info_ValueForKey(info, "g_siegeTeam1")); + trap_Cvar_Set("cg_siegeTeam2", Info_ValueForKey(info, "g_siegeTeam2")); + + tinfo = CG_ConfigString( CS_TERRAINS + 1 ); + if ( !tinfo || !*tinfo ) + { + cg.mInRMG = qfalse; + } + else + { + int weather = 0; + + cg.mInRMG = qtrue; + trap_Cvar_Set("RMG", "1"); + + weather = atoi( Info_ValueForKey( info, "RMG_weather" ) ); + + trap_Cvar_Set("RMG_weather", va("%i", weather)); + + if (weather == 1 || weather == 2) + { + cg.mRMGWeather = qtrue; + } + else + { + cg.mRMGWeather = qfalse; + } + } +} + +/* +================== +CG_ParseWarmup +================== +*/ +static void CG_ParseWarmup( void ) { + const char *info; + int warmup; + + info = CG_ConfigString( CS_WARMUP ); + + warmup = atoi( info ); + cg.warmupCount = -1; + + cg.warmup = warmup; +} + +/* +================ +CG_SetConfigValues + +Called on load to set the initial values from configure strings +================ +*/ +void CG_SetConfigValues( void ) +{ + const char *s; + const char *str; + + 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 || cgs.gametype == GT_CTY ) { + s = CG_ConfigString( CS_FLAGSTATUS ); + cgs.redflag = s[0] - '0'; + cgs.blueflag = s[1] - '0'; + } + cg.warmup = atoi( CG_ConfigString( CS_WARMUP ) ); + + // Track who the jedi master is + cgs.jediMaster = atoi ( CG_ConfigString ( CS_CLIENT_JEDIMASTER ) ); + cgs.duelWinner = atoi ( CG_ConfigString ( CS_CLIENT_DUELWINNER ) ); + + str = CG_ConfigString(CS_CLIENT_DUELISTS); + + if (str && str[0]) + { + char buf[64]; + int c = 0; + int i = 0; + + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist1 = atoi ( buf ); + c = 0; + + i++; + while (str[i]) + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist2 = atoi ( buf ); + } +} + +/* +===================== +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; + } + } +} + +extern char *cg_customSoundNames[MAX_CUSTOM_SOUNDS]; +extern const char *cg_customCombatSoundNames[MAX_CUSTOM_COMBAT_SOUNDS]; +extern const char *cg_customExtraSoundNames[MAX_CUSTOM_EXTRA_SOUNDS]; +extern const char *cg_customJediSoundNames[MAX_CUSTOM_JEDI_SOUNDS]; +extern const char *cg_customDuelSoundNames[MAX_CUSTOM_DUEL_SOUNDS]; + +static const char *GetCustomSoundForType(int setType, int index) +{ + switch (setType) + { + case 1: + return cg_customSoundNames[index]; + case 2: + return cg_customCombatSoundNames[index]; + case 3: + return cg_customExtraSoundNames[index]; + case 4: + return cg_customJediSoundNames[index]; + case 5: + return bg_customSiegeSoundNames[index]; + case 6: + return cg_customDuelSoundNames[index]; + default: + assert(0); + return NULL; + } +} + +void SetCustomSoundForType(clientInfo_t *ci, int setType, int index, sfxHandle_t sfx) +{ + switch (setType) + { + case 1: + ci->sounds[index] = sfx; + break; + case 2: + ci->combatSounds[index] = sfx; + break; + case 3: + ci->extraSounds[index] = sfx; + break; + case 4: + ci->jediSounds[index] = sfx; + break; + case 5: + ci->siegeSounds[index] = sfx; + break; + case 6: + ci->duelSounds[index] = sfx; + break; + default: + assert(0); + break; + } +} + +static void CG_RegisterCustomSounds(clientInfo_t *ci, int setType, const char *psDir) +{ + int iTableEntries = 0; + int i; + + switch (setType) + { + case 1: + iTableEntries = MAX_CUSTOM_SOUNDS; + break; + case 2: + iTableEntries = MAX_CUSTOM_COMBAT_SOUNDS; + break; + case 3: + iTableEntries = MAX_CUSTOM_EXTRA_SOUNDS; + break; + case 4: + iTableEntries = MAX_CUSTOM_JEDI_SOUNDS; + break; + case 5: + iTableEntries = MAX_CUSTOM_SIEGE_SOUNDS; + default: + assert(0); + return; + } + + for ( i = 0 ; inpcClient) + { + return; + } + + //standard + if (cent->currentState.csSounds_Std) + { + const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Std ); + + if (s && s[0]) + { + char sEnd[MAX_QPATH]; + int i = 2; + int j = 0; + + //Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates + //it is an NPC custom sound dir. + while (s[i]) + { + sEnd[j] = s[i]; + j++; + i++; + } + sEnd[j] = 0; + + CG_RegisterCustomSounds(cent->npcClient, 1, sEnd); + } + } + else + { + memset(¢->npcClient->sounds, 0, sizeof(cent->npcClient->sounds)); + } + + //combat + if (cent->currentState.csSounds_Combat) + { + const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Combat ); + + if (s && s[0]) + { + char sEnd[MAX_QPATH]; + int i = 2; + int j = 0; + + //Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates + //it is an NPC custom sound dir. + while (s[i]) + { + sEnd[j] = s[i]; + j++; + i++; + } + sEnd[j] = 0; + + CG_RegisterCustomSounds(cent->npcClient, 2, sEnd); + } + } + else + { + memset(¢->npcClient->combatSounds, 0, sizeof(cent->npcClient->combatSounds)); + } + + //extra + if (cent->currentState.csSounds_Extra) + { + const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Extra ); + + if (s && s[0]) + { + char sEnd[MAX_QPATH]; + int i = 2; + int j = 0; + + //Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates + //it is an NPC custom sound dir. + while (s[i]) + { + sEnd[j] = s[i]; + j++; + i++; + } + sEnd[j] = 0; + + CG_RegisterCustomSounds(cent->npcClient, 3, sEnd); + } + } + else + { + memset(¢->npcClient->extraSounds, 0, sizeof(cent->npcClient->extraSounds)); + } + + //jedi + if (cent->currentState.csSounds_Jedi) + { + const char *s = CG_ConfigString( CS_SOUNDS + cent->currentState.csSounds_Jedi ); + + if (s && s[0]) + { + char sEnd[MAX_QPATH]; + int i = 2; + int j = 0; + + //Parse past the initial "*" which indicates this is a custom sound, and the $ which indicates + //it is an NPC custom sound dir. + while (s[i]) + { + sEnd[j] = s[i]; + j++; + i++; + } + sEnd[j] = 0; + + CG_RegisterCustomSounds(cent->npcClient, 4, sEnd); + } + } + else + { + memset(¢->npcClient->jediSounds, 0, sizeof(cent->npcClient->jediSounds)); + } +} + +int CG_HandleAppendedSkin(char *modelName); +void CG_CacheG2AnimInfo(char *modelName); + +// nmckenzie: DUEL_HEALTH - fixme - we could really clean this up immensely with some helper functions. +void SetDuelistHealthsFromConfigString ( const char *str ) { + char buf[64]; + int c = 0; + int i = 0; + + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist1health = atoi ( buf ); + + c = 0; + i++; + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist2health = atoi ( buf ); + + c = 0; + i++; + if ( str[i] == '!' ) + { // we only have 2 duelists, apparently. + cgs.duelist3health = -1; + return; + } + + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist3health = atoi ( buf ); +} + +/* +================ +CG_ConfigStringModified + +================ +*/ +extern int cgSiegeRoundState; +extern int cgSiegeRoundTime; +void CG_ParseSiegeObjectiveStatus(const char *str); +void CG_ParseWeatherEffect(const char *str); +extern void CG_ParseSiegeState(const char *str); //cg_main.c +extern int cg_beatingSiegeTime; +extern int cg_siegeWinTeam; +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 ); + + // do something with it if necessary + if ( num == CS_MUSIC ) { + CG_StartMusic( qtrue ); + } 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_CLIENT_JEDIMASTER ) { + cgs.jediMaster = atoi ( str ); + } + else if ( num == CS_CLIENT_DUELWINNER ) + { + cgs.duelWinner = atoi ( str ); + } + else if ( num == CS_CLIENT_DUELISTS ) + { + char buf[64]; + int c = 0; + int i = 0; + + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist1 = atoi ( buf ); + c = 0; + + i++; + while (str[i] && str[i] != '|') + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist2 = atoi ( buf ); + + if (str[i]) + { + c = 0; + i++; + + while (str[i]) + { + buf[c] = str[i]; + c++; + i++; + } + buf[c] = 0; + + cgs.duelist3 = atoi(buf); + } + } + else if ( num == CS_CLIENT_DUELHEALTHS ) { // nmckenzie: DUEL_HEALTH + SetDuelistHealthsFromConfigString(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 ) ); + } 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 ) ); + } else if ( num == CS_INTERMISSION ) { + cg.intermissionStarted = atoi( str ); + } else if ( num >= CS_MODELS && num < CS_MODELS+MAX_MODELS ) { + char modelName[MAX_QPATH]; + strcpy(modelName, str); + if (strstr(modelName, ".glm") || modelName[0] == '$') + { //Check to see if it has a custom skin attached. + CG_HandleAppendedSkin(modelName); + CG_CacheG2AnimInfo(modelName); + } + + if (modelName[0] != '$' && modelName[0] != '@') + { //don't register vehicle names and saber names as models. + cgs.gameModels[ num-CS_MODELS ] = trap_R_RegisterModel( modelName ); + } + else + { + cgs.gameModels[ num-CS_MODELS ] = 0; + } +// GHOUL2 Insert start + /* + } else if ( num >= CS_CHARSKINS && num < CS_CHARSKINS+MAX_CHARSKINS ) { + cgs.skins[ num-CS_CHARSKINS ] = trap_R_RegisterSkin( str ); + */ + //rww - removed and replaced with CS_G2BONES +// Ghoul2 Insert end + } 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 ); + } + else if (str[1] == '$') + { //an NPC soundset + CG_PrecacheNPCSounds(str); + } + } else if ( num >= CS_EFFECTS && num < CS_EFFECTS+MAX_FX ) { + if (str[0] == '*') + { //it's a special global weather effect + CG_ParseWeatherEffect(str); + cgs.gameEffects[ num-CS_EFFECTS] = 0; + } + else + { + cgs.gameEffects[ num-CS_EFFECTS] = trap_FX_RegisterEffect( str ); + } + } + else if ( num >= CS_SIEGE_STATE && num < CS_SIEGE_STATE+1 ) + { + if (str[0]) + { + CG_ParseSiegeState(str); + } + } + else if ( num >= CS_SIEGE_WINTEAM && num < CS_SIEGE_WINTEAM+1 ) + { + if (str[0]) + { + cg_siegeWinTeam = atoi(str); + } + } + else if ( num >= CS_SIEGE_OBJECTIVES && num < CS_SIEGE_OBJECTIVES+1 ) + { + CG_ParseSiegeObjectiveStatus(str); + } + else if (num >= CS_SIEGE_TIMEOVERRIDE && num < CS_SIEGE_TIMEOVERRIDE+1) + { + cg_beatingSiegeTime = atoi(str); + CG_SetSiegeTimerCvar ( cg_beatingSiegeTime ); + } + else if ( num >= CS_PLAYERS && num < CS_PLAYERS+MAX_CLIENTS ) + { + CG_NewClientInfo( num - CS_PLAYERS, qtrue); + CG_BuildSpectatorString(); + } else if ( num == CS_FLAGSTATUS ) { + if( cgs.gametype == GT_CTF || cgs.gametype == GT_CTY ) { + // 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'; + } + } + else if ( num == CS_SHADERSTATE ) { + CG_ShaderStateChanged(); + } + else if ( num >= CS_LIGHT_STYLES && num < CS_LIGHT_STYLES + (MAX_LIGHT_STYLES * 3)) + { + CG_SetLightstyle(num - CS_LIGHT_STYLES); + } + +} + +//frees all ghoul2 stuff and npc stuff from a centity -rww +void CG_KillCEntityG2(int entNum) +{ + int j; + clientInfo_t *ci = NULL; + centity_t *cent = &cg_entities[entNum]; + + if (entNum < MAX_CLIENTS) + { + ci = &cgs.clientinfo[entNum]; + } + else + { + ci = cent->npcClient; + } + + if (ci) + { + if (ci == cent->npcClient) + { //never going to be != cent->ghoul2, unless cent->ghoul2 has already been removed (and then this ptr is not valid) + ci->ghoul2Model = NULL; + } + else if (ci->ghoul2Model == cent->ghoul2) + { + ci->ghoul2Model = NULL; + } + else if (ci->ghoul2Model && trap_G2_HaveWeGhoul2Models(ci->ghoul2Model)) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Model); + ci->ghoul2Model = NULL; + } + + //Clean up any weapon instances for custom saber stuff + j = 0; + while (j < MAX_SABERS) + { + if (ci->ghoul2Weapons[j] && trap_G2_HaveWeGhoul2Models(ci->ghoul2Weapons[j])) + { + trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[j]); + ci->ghoul2Weapons[j] = NULL; + } + + j++; + } + } + + if (cent->ghoul2 && trap_G2_HaveWeGhoul2Models(cent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(¢->ghoul2); + cent->ghoul2 = NULL; + } + + if (cent->grip_arm && trap_G2_HaveWeGhoul2Models(cent->grip_arm)) + { + trap_G2API_CleanGhoul2Models(¢->grip_arm); + cent->grip_arm = NULL; + } + + if (cent->frame_hold && trap_G2_HaveWeGhoul2Models(cent->frame_hold)) + { + trap_G2API_CleanGhoul2Models(¢->frame_hold); + cent->frame_hold = NULL; + } + + if (cent->npcClient) + { + CG_DestroyNPCClient(¢->npcClient); + } + + cent->isRagging = qfalse; //just in case. + cent->ikStatus = qfalse; + + cent->localAnimIndex = 0; +} + +void CG_KillCEntityInstances(void) +{ + int i = 0; + centity_t *cent; + + while (i < MAX_GENTITIES) + { + cent = &cg_entities[i]; + + if (i >= MAX_CLIENTS && cent->currentState.number == i) + { //do not clear G2 instances on client ents, they are constant + CG_KillCEntityG2(i); + } + + cent->bolt1 = 0; + cent->bolt2 = 0; + cent->bolt3 = 0; + cent->bolt4 = 0; + + cent->bodyHeight = 0;//SABER_LENGTH_MAX; + //cent->saberExtendTime = 0; + + cent->boltInfo = 0; + + cent->frame_minus1_refreshed = 0; + cent->frame_minus2_refreshed = 0; + cent->dustTrailTime = 0; + cent->ghoul2weapon = NULL; + //cent->torsoBolt = 0; + cent->trailTime = 0; + cent->frame_hold_time = 0; + cent->frame_hold_refreshed = 0; + cent->trickAlpha = 0; + cent->trickAlphaTime = 0; + VectorClear(cent->turAngles); + cent->weapon = 0; + cent->teamPowerEffectTime = 0; + cent->teamPowerType = 0; + cent->numLoopingSounds = 0; + + cent->localAnimIndex = 0; + + i++; + } +} + +/* +=============== +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" ); + } + + trap_R_ClearDecals ( ); + //FIXME: trap_FX_Reset? + + CG_InitLocalEntities(); + CG_InitMarkPolys(); + CG_ClearParticles (); + CG_KillCEntityInstances(); + + // 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(qtrue); + + trap_S_ClearLoopingSounds(); + + // 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_SIEGE && cgs.gametype != GT_POWERDUEL/* && cgs.gametype == GT_DUEL */) { + trap_S_StartLocalSound( cgs.media.countFightSound, CHAN_ANNOUNCER ); + CG_CenterPrint( CG_GetStringEdString("MP_SVGAME", "BEGIN_DUEL"), 120, GIANTCHAR_WIDTH*2 ); + } + /* + 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)); + } + } + */ + trap_Cvar_Set("cg_thirdPerson", "0"); +} + +/* +================= +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'; +} + +#define MAX_STRINGED_SV_STRING 1024 // this is an quake-engine limit, not a StringEd limit + +void CG_CheckSVStringEdRef(char *buf, const char *str) +{ //I don't really like doing this. But it utilizes the system that was already in place. + int i = 0; + int b = 0; + int strLen = 0; + qboolean gotStrip = qfalse; + + if (!str || !str[0]) + { + if (str) + { + strcpy(buf, str); + } + return; + } + + strcpy(buf, str); + + strLen = strlen(str); + + if (strLen >= MAX_STRINGED_SV_STRING) + { + return; + } + + while (i < strLen && str[i]) + { + gotStrip = qfalse; + + if (str[i] == '@' && (i+1) < strLen) + { + if (str[i+1] == '@' && (i+2) < strLen) + { + if (str[i+2] == '@' && (i+3) < strLen) + { //@@@ should mean to insert a StringEd reference here, so insert it into buf at the current place + char stringRef[MAX_STRINGED_SV_STRING]; + int r = 0; + + while (i < strLen && str[i] == '@') + { + i++; + } + + while (i < strLen && str[i] && str[i] != ' ' && str[i] != ':' && str[i] != '.' && str[i] != '\n') + { + stringRef[r] = str[i]; + r++; + i++; + } + stringRef[r] = 0; + + buf[b] = 0; + Q_strcat(buf, MAX_STRINGED_SV_STRING, CG_GetStringEdString("MP_SVGAME", stringRef)); + b = strlen(buf); + } + } + } + + if (!gotStrip) + { + buf[b] = str[i]; + b++; + } + i++; + } + + buf[b] = 0; +} + +static void CG_BodyQueueCopy(centity_t *cent, int clientNum, int knownWeapon) +{ + centity_t *source; + animation_t *anim; + float animSpeed; + int flags=BONE_ANIM_OVERRIDE_FREEZE; + clientInfo_t *ci; + + if (cent->ghoul2) + { + trap_G2API_CleanGhoul2Models(¢->ghoul2); + } + + if (clientNum < 0 || clientNum >= MAX_CLIENTS) + { + return; + } + + source = &cg_entities[ clientNum ]; + ci = &cgs.clientinfo[ clientNum ]; + + if (!source) + { + return; + } + + if (!source->ghoul2) + { + return; + } + + cent->isRagging = qfalse; //reset in case it's still set from another body that was in this cent slot. + cent->ownerRagging = source->isRagging; //if the owner was in ragdoll state, then we want to go into it too right away. + +#if 0 + VectorCopy(source->lerpOriginOffset, cent->lerpOriginOffset); +#endif + + cent->bodyFadeTime = 0; + cent->bodyHeight = 0; + + cent->dustTrailTime = source->dustTrailTime; + + trap_G2API_DuplicateGhoul2Instance(source->ghoul2, ¢->ghoul2); + + if (source->isRagging) + { //just reset it now. + source->isRagging = qfalse; + trap_G2API_SetRagDoll(source->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + + //either force the weapon from when we died or remove it if it was a dropped weapon + if (knownWeapon > WP_BRYAR_PISTOL && trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(cent->ghoul2), 1); + } + else if (trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { + trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, knownWeapon), 0, cent->ghoul2, 1); + } + + if (!cent->ownerRagging) + { + int aNum; + int eFrame; + qboolean fallBack = qfalse; + + //anim = &bgAllAnims[cent->localAnimIndex].anims[ cent->currentState.torsoAnim ]; + if (!BG_InDeathAnim(source->currentState.torsoAnim)) + { //then just snap the corpse into a default + anim = &bgAllAnims[source->localAnimIndex].anims[ BOTH_DEAD1 ]; + fallBack = qtrue; + } + else + { + anim = &bgAllAnims[source->localAnimIndex].anims[ source->currentState.torsoAnim ]; + } + animSpeed = 50.0f / anim->frameLerp; + + if (!fallBack) + { + //this will just set us to the last frame of the animation, in theory + aNum = cgs.clientinfo[source->currentState.number].frame+1; + + while (aNum >= anim->firstFrame+anim->numFrames) + { + aNum--; + } + + if (aNum < anim->firstFrame-1) + { //wrong animation...? + aNum = (anim->firstFrame+anim->numFrames)-1; + } + } + else + { + aNum = anim->firstFrame; + } + + eFrame = anim->firstFrame + anim->numFrames; + + //if (!cgs.clientinfo[source->currentState.number].frame || (cent->currentState.torsoAnim) != (source->currentState.torsoAnim) ) + //{ + // aNum = (anim->firstFrame+anim->numFrames)-1; + //} + + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "upper_lumbar", aNum, eFrame, flags, animSpeed, cg.time, -1, 150); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "model_root", aNum, eFrame, flags, animSpeed, cg.time, -1, 150); + trap_G2API_SetBoneAnim(cent->ghoul2, 0, "Motion", aNum, eFrame, flags, animSpeed, cg.time, -1, 150); + } + + //After we create the bodyqueue, regenerate any limbs on the real instance + if (source->torsoBolt) + { + CG_ReattachLimb(source); + } +} + +/* +================= +CG_ServerCommand + +The string has been tokenized and can be retrieved with +Cmd_Argc() / Cmd_Argv() +================= +*/ +void CG_SiegeBriefingDisplay(int team, int dontshow); +void CG_ParseSiegeExtendedData(void); +extern void CG_ChatBox_AddString(char *chatStr); //cg_draw.c +static void CG_ServerCommand( void ) { + const char *cmd; + char text[MAX_SAY_TEXT]; + qboolean IRCG = qfalse; + + cmd = CG_Argv(0); + + if ( !cmd[0] ) { + // server claimed the command + return; + } + +#if 0 + // never seems to get used -Ste + if ( !strcmp( cmd, "spd" ) ) + { + const char *ID; + int holdInt,count,i; + char string[1204]; + + count = trap_Argc(); + + ID = CG_Argv(1); + holdInt = atoi(ID); + + memset( &string, 0, sizeof( string ) ); + + Com_sprintf( string,sizeof(string)," \"%s\"", (const char *) CG_Argv(2)); + + for (i=3;i= 2) + { + indexNum = atoi(CG_Argv(2)); + + if (indexNum != -1) + { + trackerent = &cg_entities[indexNum]; + } + } + + if (clent) + { + CG_S_StopLoopingSound(clent->currentState.number, -1); + } + if (trackerent) + { + CG_S_StopLoopingSound(trackerent->currentState.number, -1); + } + + return; + } + + if (!strcmp(cmd, "ircg")) + { //this means param 2 is the body index and we want to copy to bodyqueue on it + IRCG = qtrue; + } + + if (!strcmp(cmd, "rcg") || IRCG) + { //rcg - Restore Client Ghoul (make sure limbs are reattached and ragdoll state is reset - this must be done reliably) + int indexNum = 0; + int argNum = trap_Argc(); + centity_t *clent; + + if (argNum < 1) + { + assert(0); + return; + } + + indexNum = atoi(CG_Argv(1)); + if (indexNum < 0 || indexNum >= MAX_CLIENTS) + { + assert(0); + return; + } + + clent = &cg_entities[indexNum]; + + //assert(clent->ghoul2); + if (!clent->ghoul2) + { //this can happen while connecting as a client + return; + } + +#ifdef _DEBUG + if (!trap_G2_HaveWeGhoul2Models(clent->ghoul2)) + { + assert(!"Tried to reset state on a bad instance. Crash is inevitable."); + } +#endif + + if (IRCG) + { + int bodyIndex = 0; + int weaponIndex = 0; + int side = 0; + centity_t *body; + + assert(argNum >= 3); + bodyIndex = atoi(CG_Argv(2)); + weaponIndex = atoi(CG_Argv(3)); + side = atoi(CG_Argv(4)); + + body = &cg_entities[bodyIndex]; + + if (side) + { + body->teamPowerType = qtrue; //light side + } + else + { + body->teamPowerType = qfalse; //dark side + } + + CG_BodyQueueCopy(body, clent->currentState.number, weaponIndex); + } + + //reattach any missing limbs + if (clent->torsoBolt) + { + CG_ReattachLimb(clent); + } + + //make sure ragdoll state is reset + if (clent->isRagging) + { + clent->isRagging = qfalse; + trap_G2API_SetRagDoll(clent->ghoul2, NULL); //calling with null parms resets to no ragdoll. + } + + //clear all the decals as well + trap_G2API_ClearSkinGore(clent->ghoul2); + + clent->weapon = 0; + clent->ghoul2weapon = NULL; //force a weapon reinit + + return; + } + + if ( !strcmp( cmd, "cp" ) ) { + char strEd[MAX_STRINGED_SV_STRING]; + CG_CheckSVStringEdRef(strEd, CG_Argv(1)); + CG_CenterPrint( strEd, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + return; + } + + if ( !strcmp( cmd, "cps" ) ) { + char strEd[MAX_STRINGED_SV_STRING]; + char *x = (char *)CG_Argv(1); + if (x[0] == '@') + { + x++; + } + trap_SP_GetStringTextString(x, strEd, MAX_STRINGED_SV_STRING); + CG_CenterPrint( strEd, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH ); + return; + } + + if ( !strcmp( cmd, "cs" ) ) { + CG_ConfigStringModified(); + return; + } + + if ( !strcmp( cmd, "print" ) ) { + char strEd[MAX_STRINGED_SV_STRING]; + CG_CheckSVStringEdRef(strEd, CG_Argv(1)); + CG_Printf( "%s", strEd ); + 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_ChatBox_AddString(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_ChatBox_AddString(text); + CG_Printf( "*%s\n", text ); + + return; + } + + //chat with location, possibly localized. + if ( !strcmp( cmd, "lchat" ) ) { + if ( !cg_teamChatsOnly.integer ) { + char name[MAX_STRING_CHARS]; + char loc[MAX_STRING_CHARS]; + char color[8]; + char message[MAX_STRING_CHARS]; + + if (trap_Argc() < 4) + { + return; + } + + strcpy(name, CG_Argv(1)); + strcpy(loc, CG_Argv(2)); + strcpy(color, CG_Argv(3)); + strcpy(message, CG_Argv(4)); + + if (loc[0] == '@') + { //get localized text + trap_SP_GetStringTextString(loc+1, loc, MAX_STRING_CHARS); + } + + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + //Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + Com_sprintf(text, MAX_SAY_TEXT, "%s<%s>^%s%s", name, loc, color, message); + CG_RemoveChatEscapeChar( text ); + CG_ChatBox_AddString(text); + CG_Printf( "*%s\n", text ); + } + return; + } + if ( !strcmp( cmd, "ltchat" ) ) { + char name[MAX_STRING_CHARS]; + char loc[MAX_STRING_CHARS]; + char color[8]; + char message[MAX_STRING_CHARS]; + + if (trap_Argc() < 4) + { + return; + } + + strcpy(name, CG_Argv(1)); + strcpy(loc, CG_Argv(2)); + strcpy(color, CG_Argv(3)); + strcpy(message, CG_Argv(4)); + + if (loc[0] == '@') + { //get localized text + trap_SP_GetStringTextString(loc+1, loc, MAX_STRING_CHARS); + } + + trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); + //Q_strncpyz( text, CG_Argv(1), MAX_SAY_TEXT ); + Com_sprintf(text, MAX_SAY_TEXT, "%s<%s> ^%s%s", name, loc, color, message); + CG_RemoveChatEscapeChar( text ); + CG_ChatBox_AddString(text); + CG_Printf( "*%s\n", text ); + + 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; + } + + 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/code/cgame/cg_snapshot.c b/code/cgame/cg_snapshot.c new file mode 100644 index 0000000..aa539ac --- /dev/null +++ b/code/cgame/cg_snapshot.c @@ -0,0 +1,414 @@ +// 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.eFlags & EF_G2ANIMATING) + { //reset the animation state + cent->pe.torso.animationNumber = -1; + cent->pe.legs.animationNumber = -1; + } + +#if 0 + if (cent->isRagging && (cent->currentState.eFlags & EF_DEAD)) + { + VectorAdd(cent->lerpOrigin, cent->lerpOriginOffset, cent->lerpOrigin); + } +#endif + + if ( cent->currentState.eType == ET_PLAYER || cent->currentState.eType == ET_NPC ) { + CG_ResetPlayerEntity( cent ); + } +} + +/* +=============== +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; + + // 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; + + if ((cg_entities[snap->ps.clientNum].ghoul2 == NULL) && trap_G2_HaveWeGhoul2Models(cgs.clientinfo[snap->ps.clientNum].ghoul2Model)) + { + trap_G2API_DuplicateGhoul2Instance(cgs.clientinfo[snap->ps.clientNum].ghoul2Model, &cg_entities[snap->ps.clientNum].ghoul2); + CG_CopyG2WeaponInstance(&cg_entities[snap->ps.clientNum], FIRST_WEAPON, cg_entities[snap->ps.clientNum].ghoul2); + + if (trap_G2API_AddBolt(cg_entities[snap->ps.clientNum].ghoul2, 0, "face") == -1) + { //check now to see if we have this bone for setting anims and such + cg_entities[snap->ps.clientNum].noFace = qtrue; + } + } + 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; + + CG_ResetEntity( cent ); + + // check for events + CG_CheckEvents( cent ); + } +} + + +/* +=================== +CG_TransitionSnapshot + +The transition point from snap to nextSnap has passed +=================== +*/ +extern qboolean CG_UsingEWeb(void); //cg_predict.c +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; + + //CG_CheckPlayerG2Weapons(&cg.snap->ps, &cg_entities[cg.snap->ps.clientNum]); + //CG_CheckPlayerG2Weapons(&cg.snap->ps, &cg.predictedPlayerEntity); + 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_UsingEWeb() ) { + 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; + + //CG_CheckPlayerG2Weapons(&cg.snap->ps, &cg_entities[cg.snap->ps.clientNum]); + //CG_CheckPlayerG2Weapons(&cg.snap->ps, &cg.predictedPlayerEntity); + BG_PlayerStateToEntityState( &snap->ps, &cg_entities[ snap->ps.clientNum ].nextState, qfalse ); + //cg_entities[ cg.snap->ps.clientNum ].interpolate = qtrue; + //No longer want to do this, as the cg_entities[clnum] and cg.predictedPlayerEntity are one in the same. + + // 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/code/cgame/cg_strap.c b/code/cgame/cg_strap.c new file mode 100644 index 0000000..ba635a8 --- /dev/null +++ b/code/cgame/cg_strap.c @@ -0,0 +1,73 @@ +//rww - shared trap call system +#include "cg_local.h" + +#include "../namespace_begin.h" + +qboolean strap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ + return trap_G2API_GetBoltMatrix(ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale); +} + +qboolean strap_G2API_GetBoltMatrix_NoReconstruct(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ + return trap_G2API_GetBoltMatrix_NoReconstruct(ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale); +} + +qboolean strap_G2API_GetBoltMatrix_NoRecNoRot(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ + return trap_G2API_GetBoltMatrix_NoRecNoRot(ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale); +} + +qboolean strap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ) +{ + return trap_G2API_SetBoneAngles(ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList, blendTime, currentTime); +} + +qboolean strap_G2API_SetBoneAnim(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ) +{ + return trap_G2API_SetBoneAnim(ghoul2, modelIndex, boneName, startFrame, endFrame, flags, animSpeed, currentTime, setFrame, blendTime); +} + +qboolean strap_G2API_GetBoneAnim(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex) +{ + return trap_G2API_GetBoneAnim(ghoul2, boneName, currentTime, currentFrame, startFrame, endFrame, flags, animSpeed, modelList, modelIndex); +} + +void strap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params) +{ + trap_G2API_SetRagDoll(ghoul2, params); +} + +void strap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params) +{ + trap_G2API_AnimateG2Models(ghoul2, time, params); +} + +qboolean strap_G2API_SetBoneIKState(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params) +{ + return trap_G2API_SetBoneIKState(ghoul2, time, boneName, ikState, params); +} + +qboolean strap_G2API_IKMove(void *ghoul2, int time, sharedIKMoveParams_t *params) +{ + return trap_G2API_IKMove(ghoul2, time, params); +} + +void strap_TrueMalloc(void **ptr, int size) +{ + trap_TrueMalloc(ptr, size); +} + +void strap_TrueFree(void **ptr) +{ + trap_TrueFree(ptr); +} + +#include "../namespace_end.h" diff --git a/code/cgame/cg_syscalls.c b/code/cgame/cg_syscalls.c new file mode 100644 index 0000000..1d6a8da --- /dev/null +++ b/code/cgame/cg_syscalls.c @@ -0,0 +1,1124 @@ +// 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 +#include "cg_local.h" + +static intptr_t (QDECL *syscall)( intptr_t arg, ... ) = (intptr_t (QDECL *)( intptr_t, ...))-1; + +#include "../namespace_begin.h" +Q_EXPORT void dllEntry( intptr_t (QDECL *syscallptr)( intptr_t 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 ); +} + +//rww - precision timer funcs... -ALWAYS- call end after start with supplied ptr, or you'll get a nasty memory leak. +//not that you should be using these outside of debug anyway.. because you shouldn't be. So don't. + +//Start should be suppled with a pointer to an empty pointer (e.g. void *blah; trap_PrecisionTimer_Start(&blah);), +//the empty pointer will be filled with an exe address to our timer (this address means nothing in vm land however). +//You must pass this pointer back unmodified to the timer end func. +void trap_PrecisionTimer_Start(void **theNewTimer) +{ + syscall(CG_PRECISIONTIMER_START, theNewTimer); +} + +//If you're using the above example, the appropriate call for this is int result = trap_PrecisionTimer_End(blah); +int trap_PrecisionTimer_End(void *theTimer) +{ + return syscall(CG_PRECISIONTIMER_END, theTimer); +} + +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_Cvar_GetHiddenVarValue(const char *name) +{ + return syscall(CG_CVAR_GETHIDDENVALUE, name); +} + +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_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ) { + return syscall( CG_FS_GETFILELIST, path, extension, listbuf, bufsize ); +} + +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, qboolean SubBSP ) { + syscall( CG_CM_LOADMAP, mapname, SubBSP ); +} + +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 ); +} + +int trap_S_GetVoiceVolume( int entityNum ) { + return syscall( CG_S_GETVOICEVOLUME, entityNum ); +} + +void trap_S_MuteSound( int entityNum, int entchannel ) { + syscall( CG_S_MUTESOUND, entityNum, entchannel ); +} + +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(void) { + syscall( CG_S_CLEARLOOPINGSOUNDS ); +} + +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_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + syscall( CG_S_UPDATEENTITYPOSITION, entityNum, origin ); +} + +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_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) { + syscall( CG_S_RESPATIALIZE, entityNum, origin, axis, inwater ); +} + +void trap_S_ShutUp(qboolean shutUpFactor) +{ + syscall(CG_S_SHUTUP, shutUpFactor); +} + +sfxHandle_t trap_S_RegisterSound( const char *sample ) { + return syscall( CG_S_REGISTERSOUND, sample ); +} + +void trap_S_StartBackgroundTrack( const char *intro, const char *loop, qboolean bReturnWithoutStarting ) { + syscall( CG_S_STARTBACKGROUNDTRACK, intro, loop, bReturnWithoutStarting ); +} + +void trap_S_UpdateAmbientSet( const char *name, vec3_t origin ) +{ + syscall(CG_S_UPDATEAMBIENTSET, name, origin); +} + +void trap_AS_ParseSets( void ) +{ + syscall(CG_AS_PARSESETS); +} + +void trap_AS_AddPrecacheEntry( const char *name ) +{ + syscall(CG_AS_ADDPRECACHEENTRY, name); +} + +int trap_S_AddLocalSet( const char *name, vec3_t listener_origin, vec3_t origin, int entID, int time ) +{ + return syscall(CG_S_ADDLOCALSET, name, listener_origin, origin, entID, time); +} + +sfxHandle_t trap_AS_GetBModelSound( const char *name, int stage ) +{ + return syscall(CG_AS_GETBMODELSOUND, name, stage); +} + +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 ); +} + +qhandle_t trap_R_RegisterFont( const char *fontName ) +{ + return syscall( CG_R_REGISTERFONT, fontName); +} + +int trap_R_Font_StrLenPixels(const char *text, const int iFontIndex, const float scale) +{ + return syscall( CG_R_FONT_STRLENPIXELS, text, iFontIndex, PASSFLOAT(scale)); +} + +int trap_R_Font_StrLenChars(const char *text) +{ + return syscall( CG_R_FONT_STRLENCHARS, text); +} + +int trap_R_Font_HeightPixels(const int iFontIndex, const float scale) +{ + return syscall( CG_R_FONT_STRHEIGHTPIXELS, iFontIndex, PASSFLOAT(scale)); +} + +void trap_R_Font_DrawString(int ox, int oy, const char *text, const float *rgba, const int setIndex, int iCharLimit, const float scale) +{ + syscall( CG_R_FONT_DRAWSTRING, ox, oy, text, rgba, setIndex, iCharLimit, PASSFLOAT(scale)); +} + +qboolean trap_Language_IsAsian(void) +{ + return syscall( CG_LANGUAGE_ISASIAN ); +} + +qboolean trap_Language_UsesSpaces(void) +{ + return syscall( CG_LANGUAGE_USESSPACES ); +} + +unsigned int trap_AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation/* = NULL*/ ) +{ + return syscall( CG_ANYLANGUAGE_READCHARFROMSTRING, psText, piAdvanceCount, pbIsTrailingPunctuation); +} + +void trap_R_ClearScene( void ) { + syscall( CG_R_CLEARSCENE ); +} + +void trap_R_ClearDecals ( void ) +{ + syscall ( CG_R_CLEARDECALS ); +} + +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 ); +} + +void trap_R_AddDecalToScene ( qhandle_t shader, const vec3_t origin, const vec3_t dir, float orientation, float r, float g, float b, float a, qboolean alphaFade, float radius, qboolean temporary ) +{ + syscall( CG_R_ADDDECALTOSCENE, shader, origin, dir, PASSFLOAT(orientation), PASSFLOAT(r), PASSFLOAT(g), PASSFLOAT(b), PASSFLOAT(a), alphaFade, PASSFLOAT(radius), temporary ); +} + +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_DrawRotatePic( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) +{ + syscall( CG_R_DRAWROTATEPIC, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), PASSFLOAT(a), hShader ); +} + +void trap_R_DrawRotatePic2( float x, float y, float w, float h, + float s1, float t1, float s2, float t2,float a, qhandle_t hShader ) +{ + syscall( CG_R_DRAWROTATEPIC2, PASSFLOAT(x), PASSFLOAT(y), PASSFLOAT(w), PASSFLOAT(h), PASSFLOAT(s1), PASSFLOAT(t1), PASSFLOAT(s2), PASSFLOAT(t2), PASSFLOAT(a), hShader ); +} + +//linear fogging, with settable range -rww +void trap_R_SetRangeFog(float range) +{ + syscall(CG_R_SETRANGEFOG, PASSFLOAT(range)); +} + +//set some properties for the draw layer for my refractive effect (here primarily for mod authors) -rww +void trap_R_SetRefractProp(float alpha, float stretch, qboolean prepost, qboolean negate) +{ + syscall(CG_R_SETREFRACTIONPROP, PASSFLOAT(alpha), PASSFLOAT(stretch), prepost, negate); +} + +void trap_R_RemapShader( const char *oldShader, const char *newShader, const char *timeOffset ) +{ + syscall( CG_R_REMAP_SHADER, oldShader, newShader, timeOffset ); +} + +void trap_R_GetLightStyle(int style, color4ub_t color) +{ + syscall( CG_R_GET_LIGHT_STYLE, style, color ); +} + +void trap_R_SetLightStyle(int style, int color) +{ + syscall( CG_R_SET_LIGHT_STYLE, style, color ); +} + +void trap_R_GetBModelVerts(int bmodelIndex, vec3_t *verts, vec3_t normal ) +{ + syscall( CG_R_GET_BMODEL_VERTS, bmodelIndex, verts, normal ); +} + +void trap_R_GetDistanceCull(float *f) +{ + syscall(CG_R_GETDISTANCECULL, f); +} + +//get screen resolution -rww +void trap_R_GetRealRes(int *w, int *h) +{ + syscall( CG_R_GETREALRES, w, h ); +} + + +//automap elevation setting -rww +void trap_R_AutomapElevAdj(float newHeight) +{ + syscall( CG_R_AUTOMAPELEVADJ, PASSFLOAT(newHeight) ); +} + +//initialize automap -rww +qboolean trap_R_InitWireframeAutomap(void) +{ + return syscall( CG_R_INITWIREFRAMEAUTO ); +} + +void trap_FX_AddLine( const vec3_t start, const vec3_t end, float size1, float size2, float sizeParm, + float alpha1, float alpha2, float alphaParm, + const vec3_t sRGB, const vec3_t eRGB, float rgbParm, + int killTime, qhandle_t shader, int flags) +{ + syscall( CG_FX_ADDLINE, start, end, PASSFLOAT(size1), PASSFLOAT(size2), PASSFLOAT(sizeParm), + PASSFLOAT(alpha1), PASSFLOAT(alpha2), PASSFLOAT(alphaParm), + sRGB, eRGB, PASSFLOAT(rgbParm), + killTime, shader, flags); +} + +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_GetDefaultState(int entityIndex, entityState_t *state ) +{ //rwwRMG - added [NEWTRAP] + return syscall( CG_GETDEFAULTSTATE, entityIndex, state ); +} + +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, float mPitchOverride, float mYawOverride, float mSensitivityOverride, int fpSel, int invenSel, qboolean fighterControls ) { + syscall( CG_SETUSERCMDVALUE, stateValue, PASSFLOAT(sensitivityScale), PASSFLOAT(mPitchOverride), PASSFLOAT(mYawOverride), PASSFLOAT(mSensitivityOverride), fpSel, invenSel, fighterControls ); +} + +void trap_SetClientForceAngle(int time, vec3_t angle) +{ + syscall( CG_SETCLIENTFORCEANGLE, time, angle ); +} + +void trap_SetClientTurnExtent(float turnAdd, float turnSub, int turnTime) +{ + syscall( CG_SETCLIENTTURNEXTENT, PASSFLOAT(turnAdd), PASSFLOAT(turnSub), turnTime ); +} + +void trap_OpenUIMenu(int menuID) +{ + syscall( CG_OPENUIMENU, menuID ); +} + +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 ); +} + +int trap_PC_LoadGlobalDefines ( const char* filename ) +{ + return syscall ( CG_PC_LOAD_GLOBAL_DEFINES, filename ); +} + +void trap_PC_RemoveAllGlobalDefines ( void ) +{ + syscall ( CG_PC_REMOVE_ALL_GLOBAL_DEFINES ); +} + +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_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, byte *mask ) { + return syscall( CG_R_INPVS, p1, p2, mask ); +} + + +int trap_FX_RegisterEffect(const char *file) +{ + return syscall( CG_FX_REGISTER_EFFECT, file); +} + +void trap_FX_PlayEffect( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ) +{ + syscall( CG_FX_PLAY_EFFECT, file, org, fwd, vol, rad); +} + +void trap_FX_PlayEntityEffect( const char *file, vec3_t org, + vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ) +{ + syscall( CG_FX_PLAY_ENTITY_EFFECT, file, org, axis, boltInfo, entNum, vol, rad ); +} + +void trap_FX_PlayEffectID( int id, vec3_t org, vec3_t fwd, int vol, int rad ) +{ + syscall( CG_FX_PLAY_EFFECT_ID, id, org, fwd, vol, rad ); +} + +void trap_FX_PlayPortalEffectID( int id, vec3_t org, vec3_t fwd, int vol, int rad ) +{ + syscall( CG_FX_PLAY_PORTAL_EFFECT_ID, id, org, fwd); +} + +void trap_FX_PlayEntityEffectID( int id, vec3_t org, + vec3_t axis[3], const int boltInfo, const int entNum, int vol, int rad ) +{ + syscall( CG_FX_PLAY_ENTITY_EFFECT_ID, id, org, axis, boltInfo, entNum, vol, rad ); +} + +void trap_FX_PlayBoltedEffectID( int id, vec3_t org, + void *ghoul2, const int boltNum, const int entNum, const int modelNum, int iLooptime, qboolean isRelative ) +{ + syscall( CG_FX_PLAY_BOLTED_EFFECT_ID, id, org, ghoul2, boltNum, entNum, modelNum, iLooptime, isRelative ); +} + +void trap_FX_AddScheduledEffects( qboolean skyPortal ) +{ + syscall( CG_FX_ADD_SCHEDULED_EFFECTS, skyPortal ); +} + +void trap_FX_Draw2DEffects ( float screenXScale, float screenYScale ) +{ + syscall( CG_FX_DRAW_2D_EFFECTS, PASSFLOAT(screenXScale), PASSFLOAT(screenYScale) ); +} + +int trap_FX_InitSystem( refdef_t* refdef ) +{ + return syscall( CG_FX_INIT_SYSTEM, refdef ); +} + +void trap_FX_SetRefDef( refdef_t* refdef ) +{ + syscall( CG_FX_SET_REFDEF, refdef ); +} + +qboolean trap_FX_FreeSystem( void ) +{ + return syscall( CG_FX_FREE_SYSTEM ); +} + +void trap_FX_Reset ( void ) +{ + syscall ( CG_FX_RESET ); +} + +void trap_FX_AdjustTime( int time ) +{ + syscall( CG_FX_ADJUST_TIME, time ); +} + + +void trap_FX_AddPoly( addpolyArgStruct_t *p ) +{ + syscall( CG_FX_ADDPOLY, p ); +} + +void trap_FX_AddBezier( addbezierArgStruct_t *p ) +{ + syscall( CG_FX_ADDBEZIER, p ); +} + +void trap_FX_AddPrimitive( effectTrailArgStruct_t *p ) +{ + syscall( CG_FX_ADDPRIMITIVE, p ); +} + +void trap_FX_AddSprite( addspriteArgStruct_t *p ) +{ + syscall( CG_FX_ADDSPRITE, p ); +} + +void trap_FX_AddElectricity( addElectricityArgStruct_t *p ) +{ + syscall( CG_FX_ADDELECTRICITY, p ); +} + +//void trap_SP_Print(const unsigned ID, byte *Data) +//{ +// syscall( CG_SP_PRINT, ID, Data); +//} + +int trap_SP_GetStringTextString(const char *text, char *buffer, int bufferLength) +{ + return syscall( CG_SP_GETSTRINGTEXTSTRING, text, buffer, bufferLength ); +} + +qboolean trap_ROFF_Clean( void ) +{ + return syscall( CG_ROFF_CLEAN ); +} + +void trap_ROFF_UpdateEntities( void ) +{ + syscall( CG_ROFF_UPDATE_ENTITIES ); +} + +int trap_ROFF_Cache( char *file ) +{ + return syscall( CG_ROFF_CACHE, file ); +} + +qboolean trap_ROFF_Play( int entID, int roffID, qboolean doTranslation ) +{ + return syscall( CG_ROFF_PLAY, entID, roffID, doTranslation ); +} + +qboolean trap_ROFF_Purge_Ent( int entID ) +{ + return syscall( CG_ROFF_PURGE_ENT, entID ); +} + + +//rww - dynamic vm memory allocation! +void trap_TrueMalloc(void **ptr, int size) +{ + syscall(CG_TRUEMALLOC, ptr, size); +} + +void trap_TrueFree(void **ptr) +{ + syscall(CG_TRUEFREE, ptr); +} + +/* +Ghoul2 Insert Start +*/ +// CG Specific API calls +void trap_G2_ListModelSurfaces(void *ghlInfo) +{ + syscall( CG_G2_LISTSURFACES, ghlInfo); +} + +void trap_G2_ListModelBones(void *ghlInfo, int frame) +{ + syscall( CG_G2_LISTBONES, ghlInfo, frame); +} + +void trap_G2_SetGhoul2ModelIndexes(void *ghoul2, qhandle_t *modelList, qhandle_t *skinList) +{ + syscall( CG_G2_SETMODELS, ghoul2, modelList, skinList); +} + +qboolean trap_G2_HaveWeGhoul2Models(void *ghoul2) +{ + return (qboolean)(syscall(CG_G2_HAVEWEGHOULMODELS, ghoul2)); +} + +qboolean trap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ + return (qboolean)(syscall(CG_G2_GETBOLT, ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale)); +} + +qboolean trap_G2API_GetBoltMatrix_NoReconstruct(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ //Same as above but force it to not reconstruct the skeleton before getting the bolt position + return (qboolean)(syscall(CG_G2_GETBOLT_NOREC, ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale)); +} + +qboolean trap_G2API_GetBoltMatrix_NoRecNoRot(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale) +{ //Same as above but force it to not reconstruct the skeleton before getting the bolt position + return (qboolean)(syscall(CG_G2_GETBOLT_NOREC_NOROT, ghoul2, modelIndex, boltIndex, matrix, angles, position, frameNum, modelList, scale)); +} + +int trap_G2API_InitGhoul2Model(void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias) +{ + return syscall(CG_G2_INITGHOUL2MODEL, ghoul2Ptr, fileName, modelIndex, customSkin, customShader, modelFlags, lodBias); +} + +qboolean trap_G2API_SetSkin(void *ghoul2, int modelIndex, qhandle_t customSkin, qhandle_t renderSkin) +{ + return syscall(CG_G2_SETSKIN, ghoul2, modelIndex, customSkin, renderSkin); +} + +void trap_G2API_CollisionDetect ( + CollisionRecord_t *collRecMap, + void* ghoul2, + const vec3_t angles, + const vec3_t position, + int frameNumber, + int entNum, + const vec3_t rayStart, + const vec3_t rayEnd, + const vec3_t scale, + int traceFlags, + int useLod, + float fRadius + ) +{ + syscall ( CG_G2_COLLISIONDETECT, collRecMap, ghoul2, angles, position, frameNumber, entNum, rayStart, rayEnd, scale, traceFlags, useLod, PASSFLOAT(fRadius) ); +} + +void trap_G2API_CollisionDetectCache ( + CollisionRecord_t *collRecMap, + void* ghoul2, + const vec3_t angles, + const vec3_t position, + int frameNumber, + int entNum, + const vec3_t rayStart, + const vec3_t rayEnd, + const vec3_t scale, + int traceFlags, + int useLod, + float fRadius + ) +{ + syscall ( CG_G2_COLLISIONDETECTCACHE, collRecMap, ghoul2, angles, position, frameNumber, entNum, rayStart, rayEnd, scale, traceFlags, useLod, PASSFLOAT(fRadius) ); +} + +void trap_G2API_CleanGhoul2Models(void **ghoul2Ptr) +{ + syscall(CG_G2_CLEANMODELS, ghoul2Ptr); +} + +qboolean trap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ) +{ + return (syscall(CG_G2_ANGLEOVERRIDE, ghoul2, modelIndex, boneName, angles, flags, up, right, forward, modelList, blendTime, currentTime)); +} + +qboolean trap_G2API_SetBoneAnim(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ) +{ + return syscall(CG_G2_PLAYANIM, ghoul2, modelIndex, boneName, startFrame, endFrame, flags, PASSFLOAT(animSpeed), currentTime, PASSFLOAT(setFrame), blendTime); +} + +qboolean trap_G2API_GetBoneAnim(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex) +{ + return syscall(CG_G2_GETBONEANIM, ghoul2, boneName, currentTime, currentFrame, startFrame, endFrame, flags, animSpeed, modelList, modelIndex); +} + +qboolean trap_G2API_GetBoneFrame(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, int *modelList, const int modelIndex) +{ + return syscall(CG_G2_GETBONEFRAME, ghoul2, boneName, currentTime, currentFrame, modelList, modelIndex); +} + +void trap_G2API_GetGLAName(void *ghoul2, int modelIndex, char *fillBuf) +{ + syscall(CG_G2_GETGLANAME, ghoul2, modelIndex, fillBuf); +} + +int trap_G2API_CopyGhoul2Instance(void *g2From, void *g2To, int modelIndex) +{ + return syscall(CG_G2_COPYGHOUL2INSTANCE, g2From, g2To, modelIndex); +} + +void trap_G2API_CopySpecificGhoul2Model(void *g2From, int modelFrom, void *g2To, int modelTo) +{ + syscall(CG_G2_COPYSPECIFICGHOUL2MODEL, g2From, modelFrom, g2To, modelTo); +} + +void trap_G2API_DuplicateGhoul2Instance(void *g2From, void **g2To) +{ + syscall(CG_G2_DUPLICATEGHOUL2INSTANCE, g2From, g2To); +} + +qboolean trap_G2API_HasGhoul2ModelOnIndex(void *ghlInfo, int modelIndex) +{ + return syscall(CG_G2_HASGHOUL2MODELONINDEX, ghlInfo, modelIndex); +} + +qboolean trap_G2API_RemoveGhoul2Model(void *ghlInfo, int modelIndex) +{ + return syscall(CG_G2_REMOVEGHOUL2MODEL, ghlInfo, modelIndex); +} + +qboolean trap_G2API_SkinlessModel(void *ghlInfo, int modelIndex) +{ + return syscall(CG_G2_SKINLESSMODEL, ghlInfo, modelIndex); +} + +int trap_G2API_GetNumGoreMarks(void *ghlInfo, int modelIndex) +{ + return syscall(CG_G2_GETNUMGOREMARKS, ghlInfo, modelIndex); +} + +void trap_G2API_AddSkinGore(void *ghlInfo,SSkinGoreData *gore) +{ + syscall(CG_G2_ADDSKINGORE, ghlInfo, gore); +} + +void trap_G2API_ClearSkinGore ( void* ghlInfo ) +{ + syscall(CG_G2_CLEARSKINGORE, ghlInfo ); +} + +int trap_G2API_Ghoul2Size ( void* ghlInfo ) +{ + return syscall(CG_G2_SIZE, ghlInfo ); +} + +int trap_G2API_AddBolt(void *ghoul2, int modelIndex, const char *boneName) +{ + return syscall(CG_G2_ADDBOLT, ghoul2, modelIndex, boneName); +} + +qboolean trap_G2API_AttachEnt(int *boltInfo, void *ghlInfoTo, int toBoltIndex, int entNum, int toModelNum) +{ + return syscall(CG_G2_ATTACHENT, boltInfo, ghlInfoTo, toBoltIndex, entNum, toModelNum); +} + +void trap_G2API_SetBoltInfo(void *ghoul2, int modelIndex, int boltInfo) +{ + syscall(CG_G2_SETBOLTON, ghoul2, modelIndex, boltInfo); +} + +qboolean trap_G2API_SetRootSurface(void *ghoul2, const int modelIndex, const char *surfaceName) +{ + return syscall(CG_G2_SETROOTSURFACE, ghoul2, modelIndex, surfaceName); +} + +qboolean trap_G2API_SetSurfaceOnOff(void *ghoul2, const char *surfaceName, const int flags) +{ + return syscall(CG_G2_SETSURFACEONOFF, ghoul2, surfaceName, flags); +} + +qboolean trap_G2API_SetNewOrigin(void *ghoul2, const int boltIndex) +{ + return syscall(CG_G2_SETNEWORIGIN, ghoul2, boltIndex); +} + +//check if a bone exists on skeleton without actually adding to the bone list -rww +qboolean trap_G2API_DoesBoneExist(void *ghoul2, int modelIndex, const char *boneName) +{ + return syscall(CG_G2_DOESBONEEXIST, ghoul2, modelIndex, boneName); +} + +int trap_G2API_GetSurfaceRenderStatus(void *ghoul2, const int modelIndex, const char *surfaceName) +{ + return syscall(CG_G2_GETSURFACERENDERSTATUS, ghoul2, modelIndex, surfaceName); +} + +int trap_G2API_GetTime(void) +{ + return syscall(CG_G2_GETTIME); +} + +void trap_G2API_SetTime(int time, int clock) +{ + syscall(CG_G2_SETTIME, time, clock); +} + +//hack for smoothing during ugly situations. forgive me. +void trap_G2API_AbsurdSmoothing(void *ghoul2, qboolean status) +{ + syscall(CG_G2_ABSURDSMOOTHING, ghoul2, status); +} + +//rww - RAGDOLL_BEGIN +void trap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params) +{ + syscall(CG_G2_SETRAGDOLL, ghoul2, params); +} + +void trap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params) +{ + syscall(CG_G2_ANIMATEG2MODELS, ghoul2, time, params); +} +//rww - RAGDOLL_END + +//additional ragdoll options -rww +qboolean trap_G2API_RagPCJConstraint(void *ghoul2, const char *boneName, vec3_t min, vec3_t max) //override default pcj bonee constraints +{ + return syscall(CG_G2_RAGPCJCONSTRAINT, ghoul2, boneName, min, max); +} + +qboolean trap_G2API_RagPCJGradientSpeed(void *ghoul2, const char *boneName, const float speed) //override the default gradient movespeed for a pcj bone +{ + return syscall(CG_G2_RAGPCJGRADIENTSPEED, ghoul2, boneName, PASSFLOAT(speed)); +} + +qboolean trap_G2API_RagEffectorGoal(void *ghoul2, const char *boneName, vec3_t pos) //override an effector bone's goal position (world coordinates) +{ + return syscall(CG_G2_RAGEFFECTORGOAL, ghoul2, boneName, pos); +} + +qboolean trap_G2API_GetRagBonePos(void *ghoul2, const char *boneName, vec3_t pos, vec3_t entAngles, vec3_t entPos, vec3_t entScale) //current position of said bone is put into pos (world coordinates) +{ + return syscall(CG_G2_GETRAGBONEPOS, ghoul2, boneName, pos, entAngles, entPos, entScale); +} + +qboolean trap_G2API_RagEffectorKick(void *ghoul2, const char *boneName, vec3_t velocity) //add velocity to a rag bone +{ + return syscall(CG_G2_RAGEFFECTORKICK, ghoul2, boneName, velocity); +} + +qboolean trap_G2API_RagForceSolve(void *ghoul2, qboolean force) //make sure we are actively performing solve/settle routines, if desired +{ + return syscall(CG_G2_RAGFORCESOLVE, ghoul2, force); +} + +qboolean trap_G2API_SetBoneIKState(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params) +{ + return syscall(CG_G2_SETBONEIKSTATE, ghoul2, time, boneName, ikState, params); +} + +qboolean trap_G2API_IKMove(void *ghoul2, int time, sharedIKMoveParams_t *params) +{ + return syscall(CG_G2_IKMOVE, ghoul2, time, params); +} + +qboolean trap_G2API_RemoveBone(void *ghoul2, const char *boneName, int modelIndex) +{ + return syscall(CG_G2_REMOVEBONE, ghoul2, boneName, modelIndex); +} + +//rww - Stuff to allow association of ghoul2 instances to entity numbers. +//This way, on listen servers when both the client and server are doing +//ghoul2 operations, we can copy relevant data off the client instance +//directly onto the server instance and slash the transforms and whatnot +//right in half. +void trap_G2API_AttachInstanceToEntNum(void *ghoul2, int entityNum, qboolean server) +{ + syscall(CG_G2_ATTACHINSTANCETOENTNUM, ghoul2, entityNum, server); +} + +void trap_G2API_ClearAttachedInstance(int entityNum) +{ + syscall(CG_G2_CLEARATTACHEDINSTANCE, entityNum); +} + +void trap_G2API_CleanEntAttachments(void) +{ + syscall(CG_G2_CLEANENTATTACHMENTS); +} + +qboolean trap_G2API_OverrideServer(void *serverInstance) +{ + return syscall(CG_G2_OVERRIDESERVER, serverInstance); +} + +void trap_G2API_GetSurfaceName(void *ghoul2, int surfNumber, int modelIndex, char *fillBuf) +{ + syscall(CG_G2_GETSURFACENAME, ghoul2, surfNumber, modelIndex, fillBuf); +} + +void trap_CG_RegisterSharedMemory(char *memory) +{ + syscall(CG_SET_SHARED_BUFFER, memory); +} + +int trap_CM_RegisterTerrain(const char *config) +{ //rwwRMG - added [NEWTRAP] + return syscall(CG_CM_REGISTER_TERRAIN, config); +} + +void trap_RMG_Init(int terrainID, const char *terrainInfo) +{ //rwwRMG - added [NEWTRAP] + syscall(CG_RMG_INIT, terrainID, terrainInfo); +} + +void trap_RE_InitRendererTerrain( const char *info ) +{ //rwwRMG - added [NEWTRAP] + syscall(CG_RE_INIT_RENDERER_TERRAIN, info); +} + +void trap_R_WeatherContentsOverride( int contents ) +{ //rwwRMG - added [NEWTRAP] + syscall(CG_R_WEATHER_CONTENTS_OVERRIDE, contents); +} + +void trap_R_WorldEffectCommand(const char *cmd) +{ + syscall(CG_R_WORLDEFFECTCOMMAND, cmd); +} + +void trap_WE_AddWeatherZone( const vec3_t mins, const vec3_t maxs ) +{ + syscall( CG_WE_ADDWEATHERZONE, mins, maxs ); +} + +/* +Ghoul2 Insert End +*/ + +#include "../namespace_end.h" diff --git a/code/cgame/cg_turret.c b/code/cgame/cg_turret.c new file mode 100644 index 0000000..61ad73e --- /dev/null +++ b/code/cgame/cg_turret.c @@ -0,0 +1,242 @@ +#include "cg_local.h" +#include "..\game\q_shared.h" +#include "..\ghoul2\g2.h" + +//rww - The turret is heavily dependant on bone angles. We can't happily set that on the server, so it is done client-only. + +void CreepToPosition(vec3_t ideal, vec3_t current) +{ + float max_degree_switch = 90; + int degrees_negative = 0; + int degrees_positive = 0; + int doNegative = 0; + + int angle_ideal; + int angle_current; + + angle_ideal = (int)ideal[YAW]; + angle_current = (int)current[YAW]; + + if (angle_ideal <= angle_current) + { + degrees_negative = (angle_current - angle_ideal); + + degrees_positive = (360 - angle_current) + angle_ideal; + } + else + { + degrees_negative = angle_current + (360 - angle_ideal); + + degrees_positive = (angle_ideal - angle_current); + } + + if (degrees_negative < degrees_positive) + { + doNegative = 1; + } + + if (doNegative) + { + current[YAW] -= max_degree_switch; + + if (current[YAW] < ideal[YAW] && (current[YAW]+(max_degree_switch*2)) >= ideal[YAW]) + { + current[YAW] = ideal[YAW]; + } + + if (current[YAW] < 0) + { + current[YAW] += 361; + } + } + else + { + current[YAW] += max_degree_switch; + + if (current[YAW] > ideal[YAW] && (current[YAW]-(max_degree_switch*2)) <= ideal[YAW]) + { + current[YAW] = ideal[YAW]; + } + + if (current[YAW] > 360) + { + current[YAW] -= 361; + } + } + + if (ideal[PITCH] < 0) + { + ideal[PITCH] += 360; + } + + angle_ideal = (int)ideal[PITCH]; + angle_current = (int)current[PITCH]; + + doNegative = 0; + + if (angle_ideal <= angle_current) + { + degrees_negative = (angle_current - angle_ideal); + + degrees_positive = (360 - angle_current) + angle_ideal; + } + else + { + degrees_negative = angle_current + (360 - angle_ideal); + + degrees_positive = (angle_ideal - angle_current); + } + + if (degrees_negative < degrees_positive) + { + doNegative = 1; + } + + if (doNegative) + { + current[PITCH] -= max_degree_switch; + + if (current[PITCH] < ideal[PITCH] && (current[PITCH]+(max_degree_switch*2)) >= ideal[PITCH]) + { + current[PITCH] = ideal[PITCH]; + } + + if (current[PITCH] < 0) + { + current[PITCH] += 361; + } + } + else + { + current[PITCH] += max_degree_switch; + + if (current[PITCH] > ideal[PITCH] && (current[PITCH]-(max_degree_switch*2)) <= ideal[PITCH]) + { + current[PITCH] = ideal[PITCH]; + } + + if (current[PITCH] > 360) + { + current[PITCH] -= 361; + } + } +} + +void TurretClientRun(centity_t *ent) +{ + if (!ent->ghoul2) + { + weaponInfo_t *weaponInfo; + + trap_G2API_InitGhoul2Model(&ent->ghoul2, CG_ConfigString( CS_MODELS+ent->currentState.modelindex ), 0, 0, 0, 0, 0); + + if (!ent->ghoul2) + { //bad + return; + } + + ent->torsoBolt = trap_G2API_AddBolt( ent->ghoul2, 0, "*flash02" ); + + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_hinge", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg.time ); + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_gback", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg.time ); + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_barrel", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_Y, POSITIVE_Z, POSITIVE_X, NULL, 100, cg.time ); + + trap_G2API_SetBoneAnim( ent->ghoul2, 0, "model_root", 0, 11, BONE_ANIM_OVERRIDE_FREEZE, 0.8f, cg.time, 0, 0 ); + + ent->turAngles[ROLL] = 0; + ent->turAngles[PITCH] = 90; + ent->turAngles[YAW] = 0; + + weaponInfo = &cg_weapons[WP_TURRET]; + + if ( !weaponInfo->registered ) + { + CG_RegisterWeapon(WP_TURRET); + } + } + + if (ent->currentState.fireflag == 2) + { //I'm about to blow + if (ent->turAngles) + { + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_hinge", ent->turAngles, BONE_ANGLES_REPLACE, NEGATIVE_Y, NEGATIVE_Z, NEGATIVE_X, NULL, 100, cg.time ); + } + return; + } + else if (ent->currentState.fireflag && ent->bolt4 != ent->currentState.fireflag) + { + vec3_t muzzleOrg, muzzleDir; + mdxaBone_t boltMatrix; + + trap_G2API_GetBoltMatrix(ent->ghoul2, 0, ent->torsoBolt, &boltMatrix, /*ent->lerpAngles*/vec3_origin, ent->lerpOrigin, cg.time, cgs.gameModels, ent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, muzzleOrg); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, muzzleDir); + + trap_FX_PlayEffectID(cgs.effects.mTurretMuzzleFlash, muzzleOrg, muzzleDir, -1, -1); + + ent->bolt4 = ent->currentState.fireflag; + } + else if (!ent->currentState.fireflag) + { + ent->bolt4 = 0; + } + + if (ent->currentState.bolt2 != ENTITYNUM_NONE) + { //turn toward the enemy + centity_t *enemy = &cg_entities[ent->currentState.bolt2]; + + if (enemy) + { + vec3_t enAng; + vec3_t enPos; + + VectorCopy(enemy->currentState.pos.trBase, enPos); + + VectorSubtract(enPos, ent->lerpOrigin, enAng); + VectorNormalize(enAng); + vectoangles(enAng, enAng); + enAng[ROLL] = 0; + enAng[PITCH] += 90; + + CreepToPosition(enAng, ent->turAngles); + } + } + else + { + vec3_t idleAng; + float turnAmount; + + if (ent->turAngles[YAW] > 360) + { + ent->turAngles[YAW] -= 361; + } + + if (!ent->dustTrailTime) + { + ent->dustTrailTime = cg.time; + } + + turnAmount = (cg.time-ent->dustTrailTime)*0.03; + + if (turnAmount > 360) + { + turnAmount = 360; + } + + idleAng[PITCH] = 90; + idleAng[ROLL] = 0; + idleAng[YAW] = ent->turAngles[YAW] + turnAmount; + ent->dustTrailTime = cg.time; + + CreepToPosition(idleAng, ent->turAngles); + } + + if (cg.time < ent->frame_minus1_refreshed) + { + ent->frame_minus1_refreshed = cg.time; + return; + } + + ent->frame_minus1_refreshed = cg.time; + trap_G2API_SetBoneAngles( ent->ghoul2, 0, "bone_hinge", ent->turAngles, BONE_ANGLES_REPLACE, NEGATIVE_Y, NEGATIVE_Z, NEGATIVE_X, NULL, 100, cg.time ); +} diff --git a/code/cgame/cg_view.c b/code/cgame/cg_view.c new file mode 100644 index 0000000..452090a --- /dev/null +++ b/code/cgame/cg_view.c @@ -0,0 +1,2755 @@ +// 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" + +#include "bg_saga.h" + +#if !defined(CL_LIGHT_H_INC) + #include "cg_lights.h" +#endif + +#define MASK_CAMERACLIP (MASK_SOLID|CONTENTS_PLAYERCLIP) +#define CAMERA_SIZE 4 + + +/* +============================================================================= + + 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.refdef.viewangles[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; + + // rww - 9-13-01 [1-26-01-sof2] + cg.testModelEntity.renderfx = 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; +} + +//============================================================================== + +//============================================================================== +//============================================================================== +// 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; + } +} + +#define CAMERA_DAMP_INTERVAL 50 + +static vec3_t cameramins = { -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE }; +static vec3_t cameramaxs = { CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE }; +vec3_t camerafwd, cameraup; + +vec3_t cameraFocusAngles, cameraFocusLoc; +vec3_t cameraIdealTarget, cameraIdealLoc; +vec3_t cameraCurTarget={0,0,0}, cameraCurLoc={0,0,0}; +vec3_t cameraOldLoc={0,0,0}, cameraNewLoc={0,0,0}; +int cameraLastFrame=0; + +float cameraLastYaw=0; +float cameraStiffFactor=0.0f; + +/* +=============== +Notes on the camera viewpoint in and out... + +cg.refdef.vieworg +--at the start of the function holds the player actor's origin (center of player model). +--it is set to the final view location of the camera at the end of the camera code. +cg.refdef.viewangles +--at the start holds the client's view angles +--it is set to the final view angle of the camera at the end of the camera code. + +=============== +*/ + +extern qboolean gCGHasFallVector; +extern vec3_t gCGFallVector; + +/* +=============== +CG_CalcTargetThirdPersonViewLocation + +=============== +*/ +static void CG_CalcIdealThirdPersonViewTarget(void) +{ + // Initialize IdealTarget + if (gCGHasFallVector) + { + VectorCopy(gCGFallVector, cameraFocusLoc); + } + else + { + VectorCopy(cg.refdef.vieworg, cameraFocusLoc); + } + + // Add in the new viewheight + cameraFocusLoc[2] += cg.snap->ps.viewheight; + + // Add in a vertical offset from the viewpoint, which puts the actual target above the head, regardless of angle. +// VectorMA(cameraFocusLoc, thirdPersonVertOffset, cameraup, cameraIdealTarget); + + // Add in a vertical offset from the viewpoint, which puts the actual target above the head, regardless of angle. + VectorCopy( cameraFocusLoc, cameraIdealTarget ); + + { + float vertOffset = cg_thirdPersonVertOffset.value; + + if (cg.snap && cg.snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg.snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + if ( veh->m_pVehicle->m_pVehicleInfo->cameraPitchDependantVertOffset ) + { + if ( cg.snap->ps.viewangles[PITCH] > 0 ) + { + vertOffset = 130+cg.predictedPlayerState.viewangles[PITCH]*-10; + if ( vertOffset < -170 ) + { + vertOffset = -170; + } + } + else if ( cg.snap->ps.viewangles[PITCH] < 0 ) + { + vertOffset = 130+cg.predictedPlayerState.viewangles[PITCH]*-5; + if ( vertOffset > 130 ) + { + vertOffset = 130; + } + } + else + { + vertOffset = 30; + } + } + else + { + vertOffset = veh->m_pVehicle->m_pVehicleInfo->cameraVertOffset; + } + } + else if ( veh->m_pVehicle + && veh->m_pVehicle->m_pVehicleInfo + && veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL ) + { + vertOffset = 0; + } + } + cameraIdealTarget[2] += vertOffset; + } + //VectorMA(cameraFocusLoc, cg_thirdPersonVertOffset.value, cameraup, cameraIdealTarget); +} + + + +/* +=============== +CG_CalcTargetThirdPersonViewLocation + +=============== +*/ +static void CG_CalcIdealThirdPersonViewLocation(void) +{ + float thirdPersonRange = cg_thirdPersonRange.value; + + if (cg.snap && cg.snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg.snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + thirdPersonRange = veh->m_pVehicle->m_pVehicleInfo->cameraRange; + if ( veh->playerState->hackingTime ) + { + thirdPersonRange += fabs(((float)veh->playerState->hackingTime)/MAX_STRAFE_TIME) * 100.0f; + } + } + } + + if ( cg.snap + && (cg.snap->ps.eFlags2&EF2_HELD_BY_MONSTER) + && cg.snap->ps.hasLookTarget + && cg_entities[cg.snap->ps.lookTarget].currentState.NPC_class == CLASS_RANCOR )//only possibility for now, may add Wampa and sand creature later + {//stay back + //thirdPersonRange = 180.0f; + thirdPersonRange = 120.0f; + } + + VectorMA(cameraIdealTarget, -(thirdPersonRange), camerafwd, cameraIdealLoc); +} + + + +static void CG_ResetThirdPersonViewDamp(void) +{ + trace_t trace; + + // Cap the pitch within reasonable limits + if (cameraFocusAngles[PITCH] > 89.0) + { + cameraFocusAngles[PITCH] = 89.0; + } + else if (cameraFocusAngles[PITCH] < -89.0) + { + cameraFocusAngles[PITCH] = -89.0; + } + + AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup); + + // Set the cameraIdealTarget + CG_CalcIdealThirdPersonViewTarget(); + + // Set the cameraIdealLoc + CG_CalcIdealThirdPersonViewLocation(); + + // Now, we just set everything to the new positions. + VectorCopy(cameraIdealLoc, cameraCurLoc); + VectorCopy(cameraIdealTarget, cameraCurTarget); + + // First thing we do is trace from the first person viewpoint out to the new target location. + CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, cg.snap->ps.clientNum, MASK_CAMERACLIP); + if (trace.fraction <= 1.0) + { + VectorCopy(trace.endpos, cameraCurTarget); + } + + // Now we trace from the new target location to the new view location, to make sure there is nothing in the way. + CG_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg.snap->ps.clientNum, MASK_CAMERACLIP); + if (trace.fraction <= 1.0) + { + VectorCopy(trace.endpos, cameraCurLoc); + } + + cameraLastFrame = cg.time; + cameraLastYaw = cameraFocusAngles[YAW]; + cameraStiffFactor = 0.0f; +} + +// This is called every frame. +static void CG_UpdateThirdPersonTargetDamp(void) +{ + trace_t trace; + vec3_t targetdiff; + float dampfactor, dtime, ratio; + + // Set the cameraIdealTarget + // Automatically get the ideal target, to avoid jittering. + CG_CalcIdealThirdPersonViewTarget(); + + if ( cg.predictedVehicleState.hyperSpaceTime + && (cg.time-cg.predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME ) + {//hyperspacing, no damp + VectorCopy(cameraIdealTarget, cameraCurTarget); + } + else if (cg_thirdPersonTargetDamp.value>=1.0||cg.thisFrameTeleport||cg.predictedPlayerState.m_iVehicleNum) + { // No damping. + VectorCopy(cameraIdealTarget, cameraCurTarget); + } + else if (cg_thirdPersonTargetDamp.value>=0.0) + { + // Calculate the difference from the current position to the new one. + VectorSubtract(cameraIdealTarget, cameraCurTarget, targetdiff); + + // Now we calculate how much of the difference we cover in the time allotted. + // The equation is (Damp)^(time) + dampfactor = 1.0-cg_thirdPersonTargetDamp.value; // We must exponent the amount LEFT rather than the amount bled off + dtime = (float)(cg.time-cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1". + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = powf(dampfactor, dtime); + + // This value is how much distance is "left" from the ideal. + VectorMA(cameraIdealTarget, -ratio, targetdiff, cameraCurTarget); + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + } + + // Now we trace to see if the new location is cool or not. + + // First thing we do is trace from the first person viewpoint out to the new target location. + CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, cg.snap->ps.clientNum, MASK_CAMERACLIP); + if (trace.fraction < 1.0) + { + VectorCopy(trace.endpos, cameraCurTarget); + } + + // Note that previously there was an upper limit to the number of physics traces that are done through the world + // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame. + // This has the benefit that the camera is a lot smoother now (before it lerped between tested points), + // however two full volume traces each frame is a bit scary to think about. +} + +// This can be called every interval, at the user's discretion. +extern void CG_CalcEntityLerpPositions( centity_t *cent ); //cg_ents.c +static void CG_UpdateThirdPersonCameraDamp(void) +{ + trace_t trace; + vec3_t locdiff; + float dampfactor, dtime, ratio; + + // Set the cameraIdealLoc + CG_CalcIdealThirdPersonViewLocation(); + + + // First thing we do is calculate the appropriate damping factor for the camera. + dampfactor=0.0; + if ( cg.predictedVehicleState.hyperSpaceTime + && (cg.time-cg.predictedVehicleState.hyperSpaceTime) < HYPERSPACE_TIME ) + {//hyperspacing - don't damp camera + dampfactor = 1.0f; + } + else if (cg_thirdPersonCameraDamp.value != 0.0) + { + float pitch; + float dFactor; + + if (!cg.predictedPlayerState.m_iVehicleNum) + { + dFactor = cg_thirdPersonCameraDamp.value; + } + else + { + dFactor = 1.0f; + } + + // Note that the camera pitch has already been capped off to 89. + pitch = Q_fabs(cameraFocusAngles[PITCH]); + + // The higher the pitch, the larger the factor, so as you look up, it damps a lot less. + pitch /= 115.0; + dampfactor = (1.0-dFactor)*(pitch*pitch); + + dampfactor += dFactor; + + // Now we also multiply in the stiff factor, so that faster yaw changes are stiffer. + if (cameraStiffFactor > 0.0f) + { // The cameraStiffFactor is how much of the remaining damp below 1 should be shaved off, i.e. approach 1 as stiffening increases. + dampfactor += (1.0-dampfactor)*cameraStiffFactor; + } + } + + if (dampfactor>=1.0||cg.thisFrameTeleport) + { // No damping. + VectorCopy(cameraIdealLoc, cameraCurLoc); + } + else if (dampfactor>=0.0) + { + // Calculate the difference from the current position to the new one. + VectorSubtract(cameraIdealLoc, cameraCurLoc, locdiff); + + // Now we calculate how much of the difference we cover in the time allotted. + // The equation is (Damp)^(time) + dampfactor = 1.0-dampfactor; // We must exponent the amount LEFT rather than the amount bled off + dtime = (float)(cg.time-cameraLastFrame) * (1.0/(float)CAMERA_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1". + + // Note that since there are a finite number of "practical" delta millisecond values possible, + // the ratio should be initialized into a chart ultimately. + ratio = powf(dampfactor, dtime); + + // This value is how much distance is "left" from the ideal. + VectorMA(cameraIdealLoc, -ratio, locdiff, cameraCurLoc); + ///////////////////////////////////////////////////////////////////////////////////////////////////////// + } + + // Now we trace from the new target location to the new view location, to make sure there is nothing in the way. + CG_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg.snap->ps.clientNum, MASK_CAMERACLIP); + + if (trace.fraction < 1.0) + { + if (trace.entityNum < ENTITYNUM_WORLD && + cg_entities[trace.entityNum].currentState.solid == SOLID_BMODEL && + cg_entities[trace.entityNum].currentState.eType == ET_MOVER) + { //get a different position for movers -rww + centity_t *mover = &cg_entities[trace.entityNum]; + + //this is absolutely hackiful, since we calc view values before we add packet ents and lerp, + //if we hit a mover we want to update its lerp pos and force it when we do the trace against + //it. + if (mover->currentState.pos.trType != TR_STATIONARY && + mover->currentState.pos.trType != TR_LINEAR) + { + int curTr = mover->currentState.pos.trType; + vec3_t curTrB; + + VectorCopy(mover->currentState.pos.trBase, curTrB); + + //calc lerporigin for this client frame + CG_CalcEntityLerpPositions(mover); + + //force the calc'd lerp to be the base and say we are stationary so we don't try to extrapolate + //out further. + mover->currentState.pos.trType = TR_STATIONARY; + VectorCopy(mover->lerpOrigin, mover->currentState.pos.trBase); + + //retrace + CG_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg.snap->ps.clientNum, MASK_CAMERACLIP); + + //copy old data back in + mover->currentState.pos.trType = (trType_t) curTr; + VectorCopy(curTrB, mover->currentState.pos.trBase); + } + if (trace.fraction < 1.0f) + { //still hit it, so take the proper trace endpos and use that. + VectorCopy(trace.endpos, cameraCurLoc); + } + } + else + { + VectorCopy( trace.endpos, cameraCurLoc ); + } + } + + // Note that previously there was an upper limit to the number of physics traces that are done through the world + // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame. + // This has the benefit that the camera is a lot smoother now (before it lerped between tested points), + // however two full volume traces each frame is a bit scary to think about. +} + + + + +/* +===============` +CG_OffsetThirdPersonView + +=============== +*/ +extern vmCvar_t cg_thirdPersonHorzOffset; +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); +static void CG_OffsetThirdPersonView( void ) +{ + vec3_t diff; + float thirdPersonHorzOffset = cg_thirdPersonHorzOffset.value; + float deltayaw; + + if (cg.snap && cg.snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg.snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + thirdPersonHorzOffset = veh->m_pVehicle->m_pVehicleInfo->cameraHorzOffset; + if ( veh->playerState->hackingTime ) + { + thirdPersonHorzOffset += (((float)veh->playerState->hackingTime)/MAX_STRAFE_TIME) * -80.0f; + } + } + } + + cameraStiffFactor = 0.0; + + // Set camera viewing direction. + VectorCopy( cg.refdef.viewangles, cameraFocusAngles ); + + // if dead, look at killer + if ( cg.snap + && (cg.snap->ps.eFlags2&EF2_HELD_BY_MONSTER) + && cg.snap->ps.hasLookTarget + && cg_entities[cg.snap->ps.lookTarget].currentState.NPC_class == CLASS_RANCOR )//only possibility for now, may add Wampa and sand creature later + {//being held + //vec3_t monsterPos, dir2Me; + centity_t *monster = &cg_entities[cg.snap->ps.lookTarget]; + VectorSet( cameraFocusAngles, 0, AngleNormalize180(monster->lerpAngles[YAW]+180), 0 ); + //make the look angle the vector from his mouth to me + /* + VectorCopy( monster->lerpOrigin, monsterPos ); + monsterPos[2] = cg.snap->ps.origin[2]; + VectorSubtract( monsterPos, cg.snap->ps.origin, dir2Me ); + vectoangles( dir2Me, cameraFocusAngles ); + */ + } + else if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) + { + cameraFocusAngles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW]; + } + else + { // Add in the third Person Angle. + cameraFocusAngles[YAW] += cg_thirdPersonAngle.value; + { + float pitchOffset = cg_thirdPersonPitchOffset.value; + if (cg.snap && cg.snap->ps.m_iVehicleNum) + { + centity_t *veh = &cg_entities[cg.snap->ps.m_iVehicleNum]; + if (veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride) + { //override the range with what the vehicle wants it to be + if ( veh->m_pVehicle->m_pVehicleInfo->cameraPitchDependantVertOffset ) + { + if ( cg.snap->ps.viewangles[PITCH] > 0 ) + { + pitchOffset = cg.predictedPlayerState.viewangles[PITCH]*-0.75; + } + else if ( cg.snap->ps.viewangles[PITCH] < 0 ) + { + pitchOffset = cg.predictedPlayerState.viewangles[PITCH]*-0.75; + } + else + { + pitchOffset = 0; + } + } + else + { + pitchOffset = veh->m_pVehicle->m_pVehicleInfo->cameraPitchOffset; + } + } + } + if ( 0 && cg.predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg.predictedPlayerState, cg_entities[cg.predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + { + float pitchPerc = ((90.0f-fabs(cameraFocusAngles[ROLL]))/90.0f); + cameraFocusAngles[PITCH] += pitchOffset*pitchPerc; + if ( cameraFocusAngles[ROLL] > 0 ) + { + cameraFocusAngles[YAW] -= pitchOffset-(pitchOffset*pitchPerc); + } + else + { + cameraFocusAngles[YAW] += pitchOffset-(pitchOffset*pitchPerc); + } + } + else + { + cameraFocusAngles[PITCH] += pitchOffset; + } + } + } + + // The next thing to do is to see if we need to calculate a new camera target location. + + // If we went back in time for some reason, or if we just started, reset the sample. + if (cameraLastFrame == 0 || cameraLastFrame > cg.time) + { + CG_ResetThirdPersonViewDamp(); + } + else + { + // Cap the pitch within reasonable limits + if ( cg.predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg.predictedPlayerState, cg_entities[cg.predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//no clamp on pitch + //FIXME: when pitch >= 90 or <= -90, camera rotates oddly... need to CrossProduct not just vectoangles + } + else + { + if (cameraFocusAngles[PITCH] > 80.0) + { + cameraFocusAngles[PITCH] = 80.0; + } + else if (cameraFocusAngles[PITCH] < -80.0) + { + cameraFocusAngles[PITCH] = -80.0; + } + } + + AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup); + + deltayaw = fabs(cameraFocusAngles[YAW] - cameraLastYaw); + if (deltayaw > 180.0f) + { // Normalize this angle so that it is between 0 and 180. + deltayaw = fabs(deltayaw - 360.0f); + } + cameraStiffFactor = deltayaw / (float)(cg.time-cameraLastFrame); + if (cameraStiffFactor < 1.0) + { + cameraStiffFactor = 0.0; + } + else if (cameraStiffFactor > 2.5) + { + cameraStiffFactor = 0.75; + } + else + { // 1 to 2 scales from 0.0 to 0.5 + cameraStiffFactor = (cameraStiffFactor-1.0f)*0.5f; + } + cameraLastYaw = cameraFocusAngles[YAW]; + + // Move the target to the new location. + CG_UpdateThirdPersonTargetDamp(); + CG_UpdateThirdPersonCameraDamp(); + } + + // Now interestingly, the Quake method is to calculate a target focus point above the player, and point the camera at it. + // We won't do that for now. + + // We must now take the angle taken from the camera target and location. + /*VectorSubtract(cameraCurTarget, cameraCurLoc, diff); + VectorNormalize(diff); + vectoangles(diff, cg.refdef.viewangles);*/ + VectorSubtract(cameraCurTarget, cameraCurLoc, diff); + { + float dist = VectorNormalize(diff); + //under normal circumstances, should never be 0.00000 and so on. + if ( !dist || (diff[0] == 0 || diff[1] == 0) ) + {//must be hitting something, need some value to calc angles, so use cam forward + VectorCopy( camerafwd, diff ); + } + } + if ( 0 && cg.predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg.predictedPlayerState, cg_entities[cg.predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//FIXME: this causes camera jerkiness, need to blend the roll? + float sav_Roll = cg.refdef.viewangles[ROLL]; + vectoangles(diff, cg.refdef.viewangles); + cg.refdef.viewangles[ROLL] = sav_Roll; + } + else + { + vectoangles(diff, cg.refdef.viewangles); + } + + // Temp: just move the camera to the side a bit + if ( thirdPersonHorzOffset != 0.0f ) + { + AnglesToAxis( cg.refdef.viewangles, cg.refdef.viewaxis ); + VectorMA( cameraCurLoc, thirdPersonHorzOffset, cg.refdef.viewaxis[1], cameraCurLoc ); + } + + // ...and of course we should copy the new view location to the proper spot too. + VectorCopy(cameraCurLoc, cg.refdef.vieworg); + + cameraLastFrame=cg.time; +} + +void CG_GetVehicleCamPos( vec3_t camPos ) +{ + VectorCopy( cg.refdef.vieworg, camPos ); +} + +/* +=============== +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.refdef.viewangles, focusAngles ); + + // if dead, look at killer + if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { + focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + cg.refdef.viewangles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW]; + } + + if ( focusAngles[PITCH] > 45 ) { + focusAngles[PITCH] = 45; // don't go too far overhead + } + AngleVectors( focusAngles, forward, NULL, NULL ); + + VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint ); + + VectorCopy( cg.refdef.vieworg, view ); + + view[2] += 8; + + cg.refdef.viewangles[PITCH] *= 0.5; + + AngleVectors( cg.refdef.viewangles, 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_CAMERACLIP); + + 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_CAMERACLIP); + 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.refdef.viewangles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist ); + cg.refdef.viewangles[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; + int kickTime; + + if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { + return; + } + + origin = cg.refdef.vieworg; + angles = cg.refdef.viewangles; + + // 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 + kickTime = (cg.time - cg.kick_time); + if ( kickTime < 800 ) + {//kicks are always 1 second long. Deal with it. + float kickPerc = 0.0f; + if ( kickTime <= 200 ) + {//winding up + kickPerc = kickTime/200.0f; + } + else + {//returning to normal + kickTime = 800 - kickTime; + kickPerc = kickTime/600.0f; + } + VectorMA( angles, kickPerc, 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.refdef.viewangles, forward, NULL, up ); + VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg ); + } +#endif +} + +static void CG_OffsetFighterView( void ) +{ + vec3_t vehFwd, vehRight, vehUp, backDir; + vec3_t camOrg, camBackOrg; + float horzOffset = cg_thirdPersonHorzOffset.value; + float vertOffset = cg_thirdPersonVertOffset.value; + float pitchOffset = cg_thirdPersonPitchOffset.value; + float yawOffset = cg_thirdPersonAngle.value; + float range = cg_thirdPersonRange.value; + trace_t trace; + centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + + AngleVectors( cg.refdef.viewangles, vehFwd, vehRight, vehUp ); + + if ( veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->cameraOverride ) + { //override the horizontal offset with what the vehicle wants it to be + horzOffset = veh->m_pVehicle->m_pVehicleInfo->cameraHorzOffset; + vertOffset = veh->m_pVehicle->m_pVehicleInfo->cameraVertOffset; + //NOTE: no yaw offset? + pitchOffset = veh->m_pVehicle->m_pVehicleInfo->cameraPitchOffset; + range = veh->m_pVehicle->m_pVehicleInfo->cameraRange; + if ( veh->playerState->hackingTime ) + { + horzOffset += (((float)veh->playerState->hackingTime)/MAX_STRAFE_TIME) * -80.0f; + range += fabs(((float)veh->playerState->hackingTime)/MAX_STRAFE_TIME) * 100.0f; + } + } + + //Set camera viewing position + VectorMA( cg.refdef.vieworg, horzOffset, vehRight, camOrg ); + VectorMA( camOrg, vertOffset, vehUp, camOrg ); + + //trace to that pos + CG_Trace(&trace, cg.refdef.vieworg, cameramins, cameramaxs, camOrg, cg.snap->ps.clientNum, MASK_CAMERACLIP); + if ( trace.fraction < 1.0 ) + { + VectorCopy( trace.endpos, camOrg ); + } + + // Set camera viewing direction. + cg.refdef.viewangles[YAW] += yawOffset; + cg.refdef.viewangles[PITCH] += pitchOffset; + + //Now bring the cam back from that pos and angles at range + AngleVectors( cg.refdef.viewangles, backDir, NULL, NULL ); + VectorScale( backDir, -1, backDir ); + + VectorMA( camOrg, range, backDir, camBackOrg ); + + //trace to that pos + CG_Trace(&trace, camOrg, cameramins, cameramaxs, camBackOrg, cg.snap->ps.clientNum, MASK_CAMERACLIP); + VectorCopy( trace.endpos, camOrg ); + + //FIXME: do we need to smooth the org? + // ...and of course we should copy the new view location to the proper spot too. + VectorCopy(camOrg, cg.refdef.vieworg); +} +//====================================================================== + +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_CalcFovFromX + +Calcs Y FOV from given X FOV +==================== +*/ +qboolean CG_CalcFOVFromX( float fov_x ) +{ + float x; +// float phase; +// float v; +// int contents; + float fov_y; + qboolean inwater; + + x = cg.refdef.width / tan( fov_x / 360 * M_PI ); + fov_y = atan2( cg.refdef.height, x ); + fov_y = fov_y * 360 / M_PI; + + // there's a problem with this, it only takes the leafbrushes into account, not the entity brushes, + // so if you give slime/water etc properties to a func_door area brush in order to move the whole water + // level up/down this doesn't take into account the door position, so warps the view the whole time + // whether the water is up or not. Fortunately there's only one slime area in Trek that you can be under, + // so lose it... +#if 0 +/* + // 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; + } +*/ +#else + inwater = qfalse; +#endif + + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + +#ifdef _XBOX + if(cg.widescreen) + cg.refdef.fov_x *= 1.125f; +#endif + + return (inwater); +} + +/* +==================== +CG_CalcFov + +Fixed fov at intermissions, otherwise account for fov variable and zooms. +==================== +*/ +#define WAVE_AMPLITUDE 1 +#define WAVE_FREQUENCY 0.4 +float zoomFov; //this has to be global client-side + +static int CG_CalcFov( void ) { + float x; + float phase; + float v; + float fov_x, fov_y; + float f; + int inwater; + float cgFov = cg_fov.value; + + if (cgFov < 1) + { + cgFov = 1; + } + if (cgFov > 97) + { + cgFov = 97; + } + + if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { + // if in intermission, use a fixed value + fov_x = 80;//90; + } else { + // user selectable + if ( cgs.dmflags & DF_FIXED_FOV ) { + // dmflag to prevent wide fov for all clients + fov_x = 80;//90; + } else { + fov_x = cgFov; + if ( fov_x < 1 ) { + fov_x = 1; + } else if ( fov_x > 160 ) { + fov_x = 160; + } + } + + if (cg.predictedPlayerState.zoomMode == 2) + { //binoculars + if (zoomFov > 40.0f) + { + zoomFov -= cg.frametime * 0.075f; + + if (zoomFov < 40.0f) + { + zoomFov = 40.0f; + } + else if (zoomFov > cgFov) + { + zoomFov = cgFov; + } + } + + fov_x = zoomFov; + } + else if (cg.predictedPlayerState.zoomMode) + { + if (!cg.predictedPlayerState.zoomLocked) + { + if (zoomFov > 50) + { //Now starting out at nearly half zoomed in + zoomFov = 50; + } + zoomFov -= cg.frametime * 0.035f;//0.075f; + + if (zoomFov < MAX_ZOOM_FOV) + { + zoomFov = MAX_ZOOM_FOV; + } + else if (zoomFov > cgFov) + { + zoomFov = cgFov; + } + else + { // Still zooming + static int zoomSoundTime = 0; + + if (zoomSoundTime < cg.time || zoomSoundTime > cg.time + 10000) + { + trap_S_StartSound(cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, cgs.media.disruptorZoomLoop); + zoomSoundTime = cg.time + 300; + } + } + } + + if (zoomFov < MAX_ZOOM_FOV) + { + zoomFov = 50; // hack to fix zoom during vid restart + } + fov_x = zoomFov; + } + else + { + zoomFov = 80; + + f = ( cg.time - cg.predictedPlayerState.zoomTime ) / ZOOM_OUT_TIME; + if ( f > 1.0 ) + { + fov_x = fov_x; + } + else + { + fov_x = cg.predictedPlayerState.zoomFov + f * ( fov_x - cg.predictedPlayerState.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 + cg.refdef.viewContents = CG_PointContents( cg.refdef.vieworg, -1 ); + if ( cg.refdef.viewContents & ( 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; + } + +#ifdef _XBOX + if(cg.widescreen) + fov_x = fov_y * 1.77777f; +#endif + + + // set it + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + if (cg.predictedPlayerState.zoomMode) + { + cg.zoomSensitivity = zoomFov/cgFov; + } + else 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; + } + + 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 * ( 1.0 - ((float)t / maxTime) ); + + if (cg.snap->ps.damageType == 0) + { //pure health + ent.customShader = cgs.media.viewPainShader; + ent.shaderRGBA[0] = 180 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[1] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[2] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[3] = 255; + } + else if (cg.snap->ps.damageType == 1) + { //pure shields + ent.customShader = cgs.media.viewPainShader_Shields; + ent.shaderRGBA[0] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[1] = 180 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[2] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[3] = 255; + } + else + { //shields and health + ent.customShader = cgs.media.viewPainShader_ShieldsAndHealth; + ent.shaderRGBA[0] = 180 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[1] = 180 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[2] = 50 * ( 1.0 - ((float)t / maxTime) ); + ent.shaderRGBA[3] = 255; + } + trap_R_AddRefEntityToScene( &ent ); +} + +int cg_actionCamLastTime = 0; +vec3_t cg_actionCamLastPos; + +//action cam routine -rww +static qboolean CG_ThirdPersonActionCam(void) +{ + centity_t *cent = &cg_entities[cg.snap->ps.clientNum]; + clientInfo_t *ci = &cgs.clientinfo[cg.snap->ps.clientNum]; + trace_t tr; + vec3_t positionDir; + vec3_t desiredAngles; + vec3_t desiredPos; + vec3_t v; + const float smoothFactor = 0.1f*cg_timescale.value; + int i; + + if (!cent->ghoul2) + { //if we don't have a g2 instance this frame for whatever reason then do nothing + return qfalse; + } + + if (cent->currentState.weapon != WP_SABER) + { //just being safe, should not ever happen + return qfalse; + } + + if ((cg.time - ci->saber[0].blade[0].trail.lastTime) > 300) + { //too long since we last got the blade position + return qfalse; + } + + //get direction from base to ent origin + VectorSubtract(ci->saber[0].blade[0].trail.base, cent->lerpOrigin, positionDir); + VectorNormalize(positionDir); + + //position the cam based on the direction and saber position + VectorMA(cent->lerpOrigin, cg_thirdPersonRange.value*2, positionDir, desiredPos); + + //trace to the desired pos to see how far that way we can actually go before we hit something + //the endpos will be valid for our desiredpos no matter what + CG_Trace(&tr, cent->lerpOrigin, NULL, NULL, desiredPos, cent->currentState.number, MASK_SOLID); + VectorCopy(tr.endpos, desiredPos); + + if ((cg.time - cg_actionCamLastTime) > 300) + { + //do a third person offset first and grab the initial point from that + CG_OffsetThirdPersonView(); + VectorCopy(cg.refdef.vieworg, cg_actionCamLastPos); + } + + cg_actionCamLastTime = cg.time; + + //lerp the vieworg to the desired pos from the last valid + VectorSubtract(desiredPos, cg_actionCamLastPos, v); + + if (VectorLength(v) > 64.0f) + { //don't bother moving yet if not far from the last pos + for (i = 0; i < 3; i++) + { + cg_actionCamLastPos[i] = (cg_actionCamLastPos[i] + (v[i]*smoothFactor)); + cg.refdef.vieworg[i] = cg_actionCamLastPos[i]; + } + } + else + { + VectorCopy(cg_actionCamLastPos, cg.refdef.vieworg); + } + + //Make sure the point is alright + CG_Trace(&tr, cent->lerpOrigin, NULL, NULL, cg.refdef.vieworg, cent->currentState.number, MASK_SOLID); + VectorCopy(tr.endpos, cg.refdef.vieworg); + + VectorSubtract(cent->lerpOrigin, cg.refdef.vieworg, positionDir); + vectoangles(positionDir, desiredAngles); + + //just set the angles for now + VectorCopy(desiredAngles, cg.refdef.viewangles); + return qtrue; +} + +vec3_t cg_lastTurretViewAngles={0}; +qboolean CG_CheckPassengerTurretView( void ) +{ + if ( cg.predictedPlayerState.m_iVehicleNum //in a vehicle + && cg.predictedPlayerState.generic1 )//as a passenger + {//passenger in a vehicle + centity_t *vehCent = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + if ( vehCent->m_pVehicle + && vehCent->m_pVehicle->m_pVehicleInfo + && vehCent->m_pVehicle->m_pVehicleInfo->maxPassengers ) + {//a vehicle capable of carrying passengers + int turretNum; + for ( turretNum = 0; turretNum < MAX_VEHICLE_TURRETS; turretNum++ ) + { + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].iAmmoMax ) + {// valid turret + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].passengerNum == cg.predictedPlayerState.generic1 ) + {//I control this turret + int boltIndex = -1; + qboolean hackPosAndAngle = qfalse; + if ( vehCent->m_pVehicle->m_iGunnerViewTag[turretNum] != -1 ) + { + boltIndex = vehCent->m_pVehicle->m_iGunnerViewTag[turretNum]; + } + else + {//crap... guess? + hackPosAndAngle = qtrue; + if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].yawBone ) + { + boltIndex = trap_G2API_AddBolt( vehCent->ghoul2, 0, vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].yawBone ); + } + else if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].pitchBone ) + { + boltIndex = trap_G2API_AddBolt( vehCent->ghoul2, 0, vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].pitchBone ); + } + else + {//well, no way of knowing, so screw it + return qfalse; + } + } + if ( boltIndex != -1 ) + { + mdxaBone_t boltMatrix; + vec3_t fwd, up; + trap_G2API_GetBoltMatrix_NoRecNoRot(vehCent->ghoul2, 0, boltIndex, &boltMatrix, vehCent->lerpAngles, + vehCent->lerpOrigin, cg.time, NULL, vehCent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, cg.refdef.vieworg); + if ( hackPosAndAngle ) + { + //FIXME: these are assumptions, externalize? BETTER YET: give me a controller view bolt/tag for each turret + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, fwd); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, up); + VectorMA( cg.refdef.vieworg, 8.0f, fwd, cg.refdef.vieworg ); + VectorMA( cg.refdef.vieworg, 4.0f, up, cg.refdef.vieworg ); + } + else + { + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, fwd); + } + { + vec3_t newAngles, deltaAngles; + vectoangles( fwd, newAngles ); + AnglesSubtract( newAngles, cg_lastTurretViewAngles, deltaAngles ); + VectorMA( cg_lastTurretViewAngles, 0.5f*(float)cg.frametime/100.0f, deltaAngles, cg.refdef.viewangles ); + } + return qtrue; + } + } + } + } + } + } + return qfalse; +} +/* +=============== +CG_CalcViewValues + +Sets cg.refdef view values +=============== +*/ +void CG_EmplacedView(vec3_t angles); +static int CG_CalcViewValues( void ) { + qboolean manningTurret = qfalse; + 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.refdef.viewangles); + AnglesToAxis( cg.refdef.viewangles, 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.refdef.viewangles ); + AnglesToAxis( cg.refdef.viewangles, 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] ); + + if (cg.xyspeed > 270) + { + cg.xyspeed = 270; + } + + manningTurret = CG_CheckPassengerTurretView(); + if ( !manningTurret ) + {//not manning a turret on a vehicle + VectorCopy( ps->origin, cg.refdef.vieworg ); +#ifdef VEH_CONTROL_SCHEME_4 + if ( cg.predictedPlayerState.m_iVehicleNum )//in a vehicle + { + Vehicle_t *pVeh = cg_entities[cg.predictedPlayerState.m_iVehicleNum].m_pVehicle; + if ( BG_UnrestrainedPitchRoll( &cg.predictedPlayerState, pVeh ) )//can roll/pitch without restriction + {//use the vehicle's viewangles to render view! + VectorCopy( cg.predictedVehicleState.viewangles, cg.refdef.viewangles ); + } + else if ( pVeh //valid vehicle data pointer + && pVeh->m_pVehicleInfo//valid vehicle info + && pVeh->m_pVehicleInfo->type == VH_FIGHTER )//fighter + { + VectorCopy( cg.predictedVehicleState.viewangles, cg.refdef.viewangles ); + cg.refdef.viewangles[PITCH] = AngleNormalize180( cg.refdef.viewangles[PITCH] ); + } + else + { + VectorCopy( ps->viewangles, cg.refdef.viewangles ); + } + } +#else// VEH_CONTROL_SCHEME_4 + if ( cg.predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg.predictedPlayerState, cg_entities[cg.predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//use the vehicle's viewangles to render view! + VectorCopy( cg.predictedVehicleState.viewangles, cg.refdef.viewangles ); + } +#endif// VEH_CONTROL_SCHEME_4 + else + { + VectorCopy( ps->viewangles, cg.refdef.viewangles ); + } + } + VectorCopy( cg.refdef.viewangles, cg_lastTurretViewAngles ); + + 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.snap->ps.weapon == WP_EMPLACED_GUN && + cg.snap->ps.emplacedIndex) + { //constrain the view properly for emplaced guns + CG_EmplacedView(cg_entities[cg.snap->ps.emplacedIndex].currentState.angles); + } + + if ( !manningTurret ) + { + if ( cg.predictedPlayerState.m_iVehicleNum //in a vehicle + && BG_UnrestrainedPitchRoll( &cg.predictedPlayerState, cg_entities[cg.predictedPlayerState.m_iVehicleNum].m_pVehicle ) )//can roll/pitch without restriction + {//use the vehicle's viewangles to render view! + CG_OffsetFighterView(); + } + else if ( cg.renderingThirdPerson ) { + // back away from character + if (cg_thirdPersonSpecialCam.integer && + BG_SaberInSpecial(cg.snap->ps.saberMove)) + { //the action cam + if (!CG_ThirdPersonActionCam()) + { //couldn't do it for whatever reason, resort back to third person then + CG_OffsetThirdPersonView(); + } + } + else + { + CG_OffsetThirdPersonView(); + } + } else { + // offset for local bobbing and kicks + CG_OffsetFirstPersonView(); + } + } + + // position eye relative to origin + AnglesToAxis( cg.refdef.viewangles, 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++ ) { + 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_DrawSkyBoxPortal +============== +*/ +extern qboolean cg_skyOri; +extern vec3_t cg_skyOriPos; +extern float cg_skyOriScale; +extern qboolean cg_noFogOutsidePortal; +void CG_DrawSkyBoxPortal(const char *cstr) +{ + static float lastfov; + refdef_t backuprefdef; + float fov_x; + float fov_y; + float x; + char *token; + float f = 0; + + lastfov = zoomFov; // for transitions back from zoomed in modes + + backuprefdef = cg.refdef; + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n"); + } + cg.refdef.vieworg[0] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n"); + } + cg.refdef.vieworg[1] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n"); + } + cg.refdef.vieworg[2] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n"); + } + fov_x = atoi(token); + + if (!fov_x) + { + fov_x = cg_fov.value; + } + + // setup fog the first time, ignore this part of the configstring after that + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog state\n"); + } + else + { + vec4_t fogColor; + int fogStart, fogEnd; + + if(atoi(token)) + { // this camera has fog + token = COM_ParseExt(&cstr, qfalse); + + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[0]\n"); + } + fogColor[0] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[1]\n"); + } + fogColor[1] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[2]\n"); + } + fogColor[2] = atof(token); + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + fogStart = 0; + } + else + { + fogStart = atoi(token); + } + + token = COM_ParseExt(&cstr, qfalse); + if (!token || !token[0]) + { + fogEnd = 0; + } + else + { + fogEnd = atoi(token); + } + } + } + + if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) + { + // if in intermission, use a fixed value + fov_x = cg_fov.value; + } + else + { + fov_x = cg_fov.value; + if ( fov_x < 1 ) + { + fov_x = 1; + } + else if ( fov_x > 160 ) + { + fov_x = 160; + } + + if (cg.predictedPlayerState.zoomMode) + { + fov_x = zoomFov; + } + + // do smooth transitions for zooming + if (cg.predictedPlayerState.zoomMode) + { //zoomed/zooming in + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_OUT_TIME; + if ( f > 1.0 ) { + fov_x = zoomFov; + } else { + fov_x = fov_x + f * ( zoomFov - fov_x ); + } + lastfov = fov_x; + } + else + { //zooming out + f = ( cg.time - cg.zoomTime ) / (float)ZOOM_OUT_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; + + cg.refdef.fov_x = fov_x; + cg.refdef.fov_y = fov_y; + + cg.refdef.rdflags |= RDF_SKYBOXPORTAL; + cg.refdef.rdflags |= RDF_DRAWSKYBOX; + + cg.refdef.time = cg.time; + + if ( !cg.hyperspace) + { //rww - also had to add this to add effects being rendered in portal sky areas properly. + trap_FX_AddScheduledEffects(qtrue); + } + + CG_AddPacketEntities(qtrue); //rww - There was no proper way to put real entities inside the portal view before. + //This will put specially flagged entities in the render. + + if (cg_skyOri) + { //ok, we want to orient the sky refdef vieworg based on the normal vieworg's relation to the ori pos + vec3_t dif; + + VectorSubtract(backuprefdef.vieworg, cg_skyOriPos, dif); + VectorScale(dif, cg_skyOriScale, dif); + VectorAdd(cg.refdef.vieworg, dif, cg.refdef.vieworg); + } + + if (cg_noFogOutsidePortal) + { //make sure no fog flag is stripped first, and make sure it is set on the normal refdef + cg.refdef.rdflags &= ~RDF_NOFOG; + backuprefdef.rdflags |= RDF_NOFOG; + } + + // draw the skybox + trap_R_RenderScene( &cg.refdef ); + + cg.refdef = backuprefdef; +} + +/* +===================== +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; + } + } +} + +void CG_UpdateSoundTrackers() +{ + int num; + centity_t *cent; + + for ( num = 0 ; num < ENTITYNUM_NONE ; num++ ) + { + cent = &cg_entities[num]; + + if (cent && (cent->currentState.eFlags & EF_SOUNDTRACKER) && cent->currentState.number == num) + //make sure the thing is valid at least. + { //keep sound for this entity updated in accordance with its attached entity at all times + if (cg.snap && cent->currentState.trickedentindex == cg.snap->ps.clientNum) + { //this is actually the player, so center the sound origin right on top of us + VectorCopy(cg.refdef.vieworg, cent->lerpOrigin); + trap_S_UpdateEntityPosition( cent->currentState.number, cent->lerpOrigin ); + } + else + { + trap_S_UpdateEntityPosition( cent->currentState.number, cg_entities[cent->currentState.trickedentindex].lerpOrigin ); + } + } + + if (cent->currentState.number == num) + { + //update all looping sounds.. + CG_S_UpdateLoopingSounds(num); + } + } +} + +//========================================================================= + +/* +================================ +Screen Effect stuff starts here +================================ +*/ +#define CAMERA_DEFAULT_FOV 90.0f +#define MAX_SHAKE_INTENSITY 16.0f + +cgscreffects_t cgScreenEffects; + +void CG_SE_UpdateShake( vec3_t origin, vec3_t angles ) +{ + vec3_t moveDir; + float intensity_scale, intensity; + int i; + + if ( cgScreenEffects.shake_duration <= 0 ) + return; + + if ( cg.time > ( cgScreenEffects.shake_start + cgScreenEffects.shake_duration ) ) + { + cgScreenEffects.shake_intensity = 0; + cgScreenEffects.shake_duration = 0; + cgScreenEffects.shake_start = 0; + return; + } + + cgScreenEffects.FOV = CAMERA_DEFAULT_FOV; + cgScreenEffects.FOV2 = CAMERA_DEFAULT_FOV; + + //intensity_scale now also takes into account FOV with 90.0 as normal + intensity_scale = 1.0f - ( (float) ( cg.time - cgScreenEffects.shake_start ) / (float) cgScreenEffects.shake_duration ) * (((cgScreenEffects.FOV+cgScreenEffects.FOV2)/2.0f)/90.0f); + + intensity = cgScreenEffects.shake_intensity * intensity_scale; + + for ( i = 0; i < 3; i++ ) + { + moveDir[i] = ( crandom() * intensity ); + } + + //Move the camera + VectorAdd( origin, moveDir, origin ); + + for ( i=0; i < 2; i++ ) // Don't do ROLL + moveDir[i] = ( crandom() * intensity ); + + //Move the angles + VectorAdd( angles, moveDir, angles ); +} + +void CG_SE_UpdateMusic(void) +{ + if (cgScreenEffects.music_volume_multiplier < 0.1) + { + cgScreenEffects.music_volume_multiplier = 1.0; + return; + } + + if (cgScreenEffects.music_volume_time < cg.time) + { + if (cgScreenEffects.music_volume_multiplier != 1.0 || cgScreenEffects.music_volume_set) + { + char musMultStr[512]; + + cgScreenEffects.music_volume_multiplier += 0.1; + if (cgScreenEffects.music_volume_multiplier > 1.0) + { + cgScreenEffects.music_volume_multiplier = 1.0; + } + + Com_sprintf(musMultStr, sizeof(musMultStr), "%f", cgScreenEffects.music_volume_multiplier); + trap_Cvar_Set("s_musicMult", musMultStr); + + if (cgScreenEffects.music_volume_multiplier == 1.0) + { + cgScreenEffects.music_volume_set = qfalse; + } + else + { + cgScreenEffects.music_volume_time = cg.time + 200; + } + } + + return; + } + + if (!cgScreenEffects.music_volume_set) + { //if the volume_time is >= cg.time, we should have a volume multiplier set + char musMultStr[512]; + + Com_sprintf(musMultStr, sizeof(musMultStr), "%f", cgScreenEffects.music_volume_multiplier); + trap_Cvar_Set("s_musicMult", musMultStr); + cgScreenEffects.music_volume_set = qtrue; + } +} + +/* +================= +CG_CalcScreenEffects + +Currently just for screen shaking (and music volume management) +================= +*/ +void CG_CalcScreenEffects(void) +{ + CG_SE_UpdateShake(cg.refdef.vieworg, cg.refdef.viewangles); + CG_SE_UpdateMusic(); +} + +void CGCam_Shake( float intensity, int duration ) +{ + if ( intensity > MAX_SHAKE_INTENSITY ) + intensity = MAX_SHAKE_INTENSITY; + + cgScreenEffects.shake_intensity = intensity; + cgScreenEffects.shake_duration = duration; + + + cgScreenEffects.shake_start = cg.time; +//JLFRUMBLE +#ifdef _XBOX +extern void FF_XboxShake(float intensity, int duration); + +FF_XboxShake(intensity, duration); + +#endif +} + +void CG_DoCameraShake( vec3_t origin, float intensity, int radius, int time ) +{ + //FIXME: When exactly is the vieworg calculated in relation to the rest of the frame?s + + vec3_t dir; + float dist, intensityScale; + float realIntensity; + + VectorSubtract( cg.refdef.vieworg, origin, dir ); + dist = VectorNormalize( dir ); + + //Use the dir to add kick to the explosion + + if ( dist > radius ) + return; + + intensityScale = 1 - ( dist / (float) radius ); + realIntensity = intensity * intensityScale; + + CGCam_Shake( realIntensity, time ); +} + +void CGCam_SetMusicMult( float multiplier, int duration ) +{ + if (multiplier < 0.1f) + { + multiplier = 0.1f; + } + + if (multiplier > 1.0f) + { + multiplier = 1.0f; + } + + cgScreenEffects.music_volume_multiplier = multiplier; + cgScreenEffects.music_volume_time = cg.time + duration; + cgScreenEffects.music_volume_set = qfalse; +} + +/* +================================ +Screen Effect stuff ends here +================================ +*/ + +/* +================= +CG_EmplacedView + +Keep view reasonably constrained in relation to gun -rww +================= +*/ +#include "../namespace_begin.h" +int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); +#include "../namespace_end.h" + +void CG_EmplacedView(vec3_t angles) +{ + float yaw; + int override; + + override = BG_EmplacedView(cg.refdef.viewangles, angles, &yaw, + cg_entities[cg.snap->ps.emplacedIndex].currentState.origin2[0]); + + if (override) + { + cg.refdef.viewangles[YAW] = yaw; + AnglesToAxis(cg.refdef.viewangles, cg.refdef.viewaxis); + + if (override == 2) + { + trap_SetClientForceAngle(cg.time + 5000, cg.refdef.viewangles); + } + } + + //we want to constrain the predicted player state viewangles as well + override = BG_EmplacedView(cg.predictedPlayerState.viewangles, angles, &yaw, + cg_entities[cg.snap->ps.emplacedIndex].currentState.origin2[0]); + if (override) + { + cg.predictedPlayerState.viewangles[YAW] = yaw; + } +} + +//specially add cent's for automap +static void CG_AddRefentForAutoMap(centity_t *cent) +{ + refEntity_t ent; + vec3_t flat; + + if (cent->currentState.eFlags & EF_NODRAW) + { + return; + } + + memset(&ent, 0, sizeof(refEntity_t)); + ent.reType = RT_MODEL; + + VectorCopy(cent->lerpAngles, flat); + flat[PITCH] = flat[ROLL] = 0.0f; + + VectorCopy(cent->lerpOrigin, ent.origin); + VectorCopy(flat, ent.angles); + AnglesToAxis(flat, ent.axis); + + if (cent->ghoul2 && + (cent->currentState.eType == ET_PLAYER || + cent->currentState.eType == ET_NPC || + cent->currentState.modelGhoul2)) + { //using a ghoul2 model + ent.ghoul2 = cent->ghoul2; + ent.radius = cent->currentState.g2radius; + + if (!ent.radius) + { + ent.radius = 64.0f; + } + } + else + { //then assume a standard indexed model + ent.hModel = cgs.gameModels[cent->currentState.modelindex]; + } + + trap_R_AddRefEntityToScene(&ent); +} + +//add all entities that would be on the radar +void CG_AddRadarAutomapEnts(void) +{ + int i = 0; + + //first add yourself + CG_AddRefentForAutoMap(&cg_entities[cg.predictedPlayerState.clientNum]); + + while (i < cg.radarEntityCount) + { + CG_AddRefentForAutoMap(&cg_entities[cg.radarEntities[i]]); + i++; + } +} + +/* +================ +CG_DrawAutoMap + +Draws the automap scene. -rww +================ +*/ +float cg_autoMapZoom = 512.0f; +float cg_autoMapZoomMainOffset = 0.0f; +vec3_t cg_autoMapAngle = {90.0f, 0.0f, 0.0f}; +autoMapInput_t cg_autoMapInput; +int cg_autoMapInputTime = 0; +#define SIDEFRAME_WIDTH 16 +#define SIDEFRAME_HEIGHT 32 +void CG_DrawAutoMap(void) +{ + clientInfo_t *local; + refdef_t refdef; + trace_t tr; + vec3_t fwd; + vec3_t playerMins, playerMaxs; + int vWidth, vHeight; + float hScale, vScale; + float x, y, w, h; + + if (!cg_autoMap.integer) + { //don't do anything then + return; + } + + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) + { //don't show when dead + return; + } + + if ( (cg.predictedPlayerState.pm_flags & PMF_FOLLOW) || cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR ) + { //don't show when spec + return; + } + + local = &cgs.clientinfo[ cg.predictedPlayerState.clientNum ]; + if ( !local->infoValid ) + { //don't show if bad ci + return; + } + + if (cgs.gametype < GT_TEAM) + { //don't show in non-team gametypes + return; + } + + if (cg_autoMapInputTime >= cg.time) + { + if (cg_autoMapInput.up) + { + cg_autoMapZoom -= cg_autoMapInput.up; + if (cg_autoMapZoom < cg_autoMapZoomMainOffset+64.0f) + { + cg_autoMapZoom = cg_autoMapZoomMainOffset+64.0f; + } + } + + if (cg_autoMapInput.down) + { + cg_autoMapZoom += cg_autoMapInput.down; + if (cg_autoMapZoom > cg_autoMapZoomMainOffset+4096.0f) + { + cg_autoMapZoom = cg_autoMapZoomMainOffset+4096.0f; + } + } + + if (cg_autoMapInput.yaw) + { + cg_autoMapAngle[YAW] += cg_autoMapInput.yaw; + } + + if (cg_autoMapInput.pitch) + { + cg_autoMapAngle[PITCH] += cg_autoMapInput.pitch; + } + + if (cg_autoMapInput.goToDefaults) + { + cg_autoMapZoom = 512.0f; + VectorSet(cg_autoMapAngle, 90.0f, 0.0f, 0.0f); + } + } + + memset( &refdef, 0, sizeof( refdef ) ); + + refdef.rdflags = (RDF_NOWORLDMODEL|RDF_AUTOMAP); + + VectorCopy(cg.predictedPlayerState.origin, refdef.vieworg); + VectorCopy(cg_autoMapAngle, refdef.viewangles); + + //scale out in the direction of the view angles base on the zoom factor + AngleVectors(refdef.viewangles, fwd, 0, 0); + VectorMA(refdef.vieworg, -cg_autoMapZoom, fwd, refdef.vieworg); + + AnglesToAxis(refdef.viewangles, refdef.viewaxis); + + refdef.fov_x = 50; + refdef.fov_y = 50; + + //guess this doesn't need to be done every frame, but eh + trap_R_GetRealRes(&vWidth, &vHeight); + + //set scaling values so that the 640x480 will result at 1.0/1.0 + hScale = vWidth/640.0f; + vScale = vHeight/480.0f; + + x = cg_autoMapX.value; + y = cg_autoMapY.value; + w = cg_autoMapW.value; + h = cg_autoMapH.value; + + refdef.x = x*hScale; + refdef.y = y*vScale; + refdef.width = w*hScale; + refdef.height = h*vScale; + + CG_DrawPic(x-SIDEFRAME_WIDTH, y, SIDEFRAME_WIDTH, h, cgs.media.wireframeAutomapFrame_left); + CG_DrawPic(x+w, y, SIDEFRAME_WIDTH, h, cgs.media.wireframeAutomapFrame_right); + CG_DrawPic(x-SIDEFRAME_WIDTH, y-SIDEFRAME_HEIGHT, w+(SIDEFRAME_WIDTH*2), SIDEFRAME_HEIGHT, cgs.media.wireframeAutomapFrame_top); + CG_DrawPic(x-SIDEFRAME_WIDTH, y+h, w+(SIDEFRAME_WIDTH*2), SIDEFRAME_HEIGHT, cgs.media.wireframeAutomapFrame_bottom); + + refdef.time = cg.time; + + trap_R_ClearScene(); + CG_AddRadarAutomapEnts(); + + if (cg.predictedPlayerState.m_iVehicleNum && + cg_entities[cg.predictedPlayerState.m_iVehicleNum].currentState.eType == ET_NPC && + cg_entities[cg.predictedPlayerState.m_iVehicleNum].currentState.NPC_class == CLASS_VEHICLE && + cg_entities[cg.predictedPlayerState.m_iVehicleNum].m_pVehicle && + cg_entities[cg.predictedPlayerState.m_iVehicleNum].m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { //constantly adjust to current height + trap_R_AutomapElevAdj(cg.predictedPlayerState.origin[2]); + } + else + { + //Trace down and set the ground elevation as the main automap elevation point + VectorSet(playerMins, -15, -15, DEFAULT_MINS_2); + VectorSet(playerMaxs, 15, 15, DEFAULT_MAXS_2); + + VectorCopy(cg.predictedPlayerState.origin, fwd); + fwd[2] -= 4096.0f; + CG_Trace(&tr, cg.predictedPlayerState.origin, playerMins, playerMaxs, fwd, cg.predictedPlayerState.clientNum, MASK_SOLID); + + if (!tr.startsolid && !tr.allsolid) + { + trap_R_AutomapElevAdj(tr.endpos[2]); + } + } + trap_R_RenderScene( &refdef ); +} + +/* +================= +CG_DrawActiveFrame + +Generates and draws a game scene and status information at the given time. +================= +*/ +static qboolean cg_rangedFogging = qfalse; //so we know if we should go back to normal fog +float cg_linearFogOverride = 0.0f; //designer-specified override for linear fogging style + +#include "../namespace_begin.h" +extern void BG_VehicleTurnRateForSpeed( Vehicle_t *pVeh, float speed, float *mPitchOverride, float *mYawOverride ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +#include "../namespace_end.h" + +extern qboolean cgQueueLoad; +extern void CG_ActualLoadDeferredPlayers( void ); + +static int cg_siegeClassIndex = -2; + +void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) { + int inwater; + const char *cstr; + float mSensitivity = cg.zoomSensitivity; + float mPitchOverride = 0.0f; + float mYawOverride = 0.0f; + static centity_t *veh = NULL; +#ifdef VEH_CONTROL_SCHEME_4 + float mSensitivityOverride = 0.0f; + qboolean bUseFighterPitch = qfalse; + qboolean isFighter = qfalse; +#endif + + if (cgQueueLoad) + { //do this before you start messing around with adding ghoul2 refents and crap + CG_ActualLoadDeferredPlayers(); + cgQueueLoad = qfalse; + } + + cg.time = serverTime; + cg.demoPlayback = demoPlayback; + + if (cg.snap && ui_myteam.integer != cg.snap->ps.persistant[PERS_TEAM]) + { + trap_Cvar_Set ( "ui_myteam", va("%i", cg.snap->ps.persistant[PERS_TEAM]) ); + } + if (cgs.gametype == GT_SIEGE && + cg.snap && + cg_siegeClassIndex != cgs.clientinfo[cg.snap->ps.clientNum].siegeIndex) + { + cg_siegeClassIndex = cgs.clientinfo[cg.snap->ps.clientNum].siegeIndex; + if (cg_siegeClassIndex == -1) + { + trap_Cvar_Set("ui_mySiegeClass", ""); + } + else + { + trap_Cvar_Set("ui_mySiegeClass", bgSiegeClasses[cg_siegeClassIndex].name); + } + } + + // 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; + } + + trap_FX_AdjustTime( cg.time ); + + CG_RunLightStyles(); + + // any looped sounds will be respecified as entities + // are added to the render list + trap_S_ClearLoopingSounds(); + + // clear all the render lists + trap_R_ClearScene(); + + // set up cg.snap and possibly cg.nextSnap + CG_ProcessSnapshots(); + + trap_ROFF_UpdateEntities(); + + // 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 ) ) + { +#if 0 + // Transition from zero to negative one on the snapshot timeout. + // The reason we do this is because the first client frame is responsible for + // some farily slow processing (such as weather) and we dont want to include + // that processing time into our calculations + if ( !cg.snapshotTimeoutTime ) + { + cg.snapshotTimeoutTime = -1; + } + // Transition the snapshot timeout time from -1 to the current time in + // milliseconds which will start the timeout. + else if ( cg.snapshotTimeoutTime == -1 ) + { + cg.snapshotTimeoutTime = trap_Milliseconds ( ); + } + + // If we have been waiting too long then just error out + if ( cg.snapshotTimeoutTime > 0 && (trap_Milliseconds ( ) - cg.snapshotTimeoutTime > cg_snapshotTimeout.integer * 1000) ) + { + Com_Error ( ERR_DROP, CG_GetStringEdString("MP_SVGAME", "SNAPSHOT_TIMEOUT")); + return; + } +#endif + CG_DrawInformation(); + return; + } + + // let the client system know what our weapon and zoom settings are + if (cg.snap && cg.snap->ps.saberLockTime > cg.time) + { + mSensitivity = 0.01f; + } + else if (cg.predictedPlayerState.weapon == WP_EMPLACED_GUN) + { //lower sens for emplaced guns and vehicles + mSensitivity = 0.2f; + } +#ifdef VEH_CONTROL_SCHEME_4 + else if (cg.predictedPlayerState.m_iVehicleNum//in a vehicle + && !cg.predictedPlayerState.generic1 )//not as a passenger + { + centity_t *cent = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + if ( cent->m_pVehicle + && cent->m_pVehicle->m_pVehicleInfo + && cent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + { + BG_VehicleTurnRateForSpeed( cent->m_pVehicle, cent->currentState.speed, &mPitchOverride, &mYawOverride ); + //mSensitivityOverride = 5.0f;//old default value + mSensitivityOverride = 0.0f; + bUseFighterPitch = qtrue; + trap_SetUserCmdValue( cg.weaponSelect, mSensitivity, mPitchOverride, mYawOverride, mSensitivityOverride, cg.forceSelect, cg.itemSelect, bUseFighterPitch ); + isFighter = qtrue; + } + } + + if ( !isFighter ) +#endif //VEH_CONTROL_SCHEME_4 + { + if (cg.predictedPlayerState.m_iVehicleNum) + { + veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum]; + } + if (veh && + veh->currentState.eType == ET_NPC && + veh->currentState.NPC_class == CLASS_VEHICLE && + veh->m_pVehicle && + veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER && + bg_fighterAltControl.integer) + { + trap_SetUserCmdValue( cg.weaponSelect, mSensitivity, mPitchOverride, mYawOverride, 0.0f, cg.forceSelect, cg.itemSelect, qtrue ); + veh = NULL; //this is done because I don't want an extra assign each frame because I am so perfect and super efficient. + } + else + { + trap_SetUserCmdValue( cg.weaponSelect, mSensitivity, mPitchOverride, mYawOverride, 0.0f, cg.forceSelect, cg.itemSelect, qfalse ); + } + } + + // 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); + + if (cg.snap->ps.stats[STAT_HEALTH] > 0) + { + if (cg.predictedPlayerState.weapon == WP_EMPLACED_GUN && cg.predictedPlayerState.emplacedIndex /*&& + cg_entities[cg.predictedPlayerState.emplacedIndex].currentState.weapon == WP_NONE*/) + { //force third person for e-web and emplaced use + cg.renderingThirdPerson = 1; + } + else if (cg.predictedPlayerState.weapon == WP_SABER || cg.predictedPlayerState.weapon == WP_MELEE || + BG_InGrappleMove(cg.predictedPlayerState.torsoAnim) || BG_InGrappleMove(cg.predictedPlayerState.legsAnim) || + cg.predictedPlayerState.forceHandExtend == HANDEXTEND_KNOCKDOWN || cg.predictedPlayerState.fallingToDeath || + cg.predictedPlayerState.m_iVehicleNum || PM_InKnockDown(&cg.predictedPlayerState)) + { + if (cg_fpls.integer && cg.predictedPlayerState.weapon == WP_SABER) + { //force to first person for fpls + cg.renderingThirdPerson = 0; + } + else + { + cg.renderingThirdPerson = 1; + } + } + else if (cg.snap->ps.zoomMode) + { //always force first person when zoomed + cg.renderingThirdPerson = 0; + } + } + + if (cg.predictedPlayerState.pm_type == PM_SPECTATOR) + { //always first person for spec + cg.renderingThirdPerson = 0; + } + + + if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR) + { + cg.renderingThirdPerson = 0; + } + + // build cg.refdef + inwater = CG_CalcViewValues(); + + if (cg_linearFogOverride) + { + trap_R_SetRangeFog(-cg_linearFogOverride); + } + else if (cg.predictedPlayerState.zoomMode) + { //zooming with binoculars or sniper, set the fog range based on the zoom level -rww + cg_rangedFogging = qtrue; + //smaller the fov the less fog we have between the view and cull dist + trap_R_SetRangeFog(cg.refdef.fov_x*64.0f); + } + else if (cg_rangedFogging) + { //disable it + cg_rangedFogging = qfalse; + trap_R_SetRangeFog(0.0f); + } + + cstr = CG_ConfigString(CS_SKYBOXORG); + + if (cstr && cstr[0]) + { //we have a skyportal + CG_DrawSkyBoxPortal(cstr); + } + + CG_CalcScreenEffects(); + + // first person blend blobs, done after AnglesToAxis + if ( !cg.renderingThirdPerson && cg.predictedPlayerState.pm_type != PM_SPECTATOR ) { + CG_DamageBlendBlob(); + } + + // build the render lists + if ( !cg.hyperspace ) { + CG_AddPacketEntities(qfalse); // adter calcViewValues, so predicted player state is correct + CG_AddMarks(); + CG_AddParticles (); + CG_AddLocalEntities(); + CG_DrawMiscEnts(); + } + CG_AddViewWeapon( &cg.predictedPlayerState ); + + if ( !cg.hyperspace) + { + trap_FX_AddScheduledEffects(qfalse); + } + + // add buffered sounds + CG_PlayBufferedSounds(); + + // 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(); + + // if there are any entities flagged as sound trackers and attached to other entities, update their sound pos + CG_UpdateSoundTrackers(); + + if (gCGHasFallVector) + { + vec3_t lookAng; + + VectorSubtract(cg.snap->ps.origin, cg.refdef.vieworg, lookAng); + VectorNormalize(lookAng); + vectoangles(lookAng, lookAng); + + VectorCopy(gCGFallVector, cg.refdef.vieworg); + AnglesToAxis(lookAng, cg.refdef.viewaxis); + } + + //This is done from the vieworg to get origin for non-attenuated sounds + cstr = CG_ConfigString( CS_GLOBAL_AMBIENT_SET ); + + if (cstr && cstr[0]) + { + trap_S_UpdateAmbientSet( cstr, cg.refdef.vieworg ); + } + + // 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 ); + + CG_DrawAutoMap(); + + if ( cg_stats.integer ) { + CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame ); + } +} + diff --git a/code/cgame/cg_weaponinit.c b/code/cgame/cg_weaponinit.c new file mode 100644 index 0000000..294f9d5 --- /dev/null +++ b/code/cgame/cg_weaponinit.c @@ -0,0 +1,592 @@ +// +// cg_weaponinit.c -- events and effects dealing with weapons +#include "cg_local.h" +#include "fx_local.h" + + +/* +================= +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] ); + // load in-view model also + weaponInfo->viewModel = trap_R_RegisterModel(item->view_model); + + // 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->view_model ); +// COM_StripExtension( path, path ); +// strcat( path, "_flash.md3" ); + weaponInfo->flashModel = 0;//trap_R_RegisterModel( path ); + + if (weaponNum == WP_DISRUPTOR || + weaponNum == WP_FLECHETTE || + weaponNum == WP_REPEATER || + weaponNum == WP_ROCKET_LAUNCHER) + { + strcpy( path, item->view_model ); + COM_StripExtension( path, path ); + strcat( path, "_barrel.md3" ); + weaponInfo->barrelModel = trap_R_RegisterModel( path ); + } + else if (weaponNum == WP_STUN_BATON) + { //only weapon with more than 1 barrel.. + trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel.md3"); + trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel2.md3"); + trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel3.md3"); + } + else + { + weaponInfo->barrelModel = 0; + } + + if (weaponNum != WP_SABER) + { + strcpy( path, item->view_model ); + COM_StripExtension( path, path ); + strcat( path, "_hand.md3" ); + weaponInfo->handsModel = trap_R_RegisterModel( path ); + } + else + { + weaponInfo->handsModel = 0; + } + +// if ( !weaponInfo->handsModel ) { +// weaponInfo->handsModel = trap_R_RegisterModel( "models/weapons2/shotgun/shotgun_hand.md3" ); +// } + + switch ( weaponNum ) { + case WP_STUN_BATON: + case WP_MELEE: +/* MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/saber/saberhum.wav" ); +// weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/melee/fstatck.wav" ); +*/ + //trap_R_RegisterShader( "gfx/effects/stunPass" ); + trap_FX_RegisterEffect( "stunBaton/flesh_impact" ); + + if (weaponNum == WP_STUN_BATON) + { + trap_S_RegisterSound( "sound/weapons/baton/idle.wav" ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/baton/fire.mp3" ); + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/baton/fire.mp3" ); + } + else + { + /* + int j = 0; + + while (j < 4) + { + weaponInfo->flashSound[j] = trap_S_RegisterSound( va("sound/weapons/melee/swing%i", j+1) ); + weaponInfo->altFlashSound[j] = weaponInfo->flashSound[j]; + j++; + } + */ + //No longer needed, animsound config plays them for us + } + break; + case WP_SABER: + MAKERGB( weaponInfo->flashDlightColor, 0.6f, 0.6f, 1.0f ); + weaponInfo->firingSound = trap_S_RegisterSound( "sound/weapons/saber/saberhum1.wav" ); + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/saber/saber_w.glm" ); + break; + + case WP_CONCUSSION: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/concussion/select.wav"); + + weaponInfo->flashSound[0] = NULL_SOUND; + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "concussion/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; + //weaponInfo->missileDlightColor= {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_ConcussionProjectileThink; + + weaponInfo->altFlashSound[0] = NULL_SOUND; + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound( "sound/weapons/bryar/altcharge.wav"); + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "concussion/altmuzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; + //weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_ConcussionProjectileThink; + + cgs.effects.disruptorAltMissEffect = trap_FX_RegisterEffect( "disruptor/alt_miss" ); + + cgs.effects.concussionShotEffect = trap_FX_RegisterEffect( "concussion/shot" ); + cgs.effects.concussionImpactEffect = trap_FX_RegisterEffect( "concussion/explosion" ); + trap_R_RegisterShader("gfx/effects/blueLine"); + trap_R_RegisterShader("gfx/misc/whiteline2"); + break; + + case WP_BRYAR_PISTOL: + case WP_BRYAR_OLD: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/bryar/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bryar/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "bryar/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; + //weaponInfo->missileDlightColor= {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_BryarProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/bryar/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound( "sound/weapons/bryar/altcharge.wav"); + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "bryar/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; + //weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_BryarAltProjectileThink; + + cgs.effects.bryarShotEffect = trap_FX_RegisterEffect( "bryar/shot" ); + cgs.effects.bryarPowerupShotEffect = trap_FX_RegisterEffect( "bryar/crackleShot" ); + cgs.effects.bryarWallImpactEffect = trap_FX_RegisterEffect( "bryar/wall_impact" ); + cgs.effects.bryarWallImpactEffect2 = trap_FX_RegisterEffect( "bryar/wall_impact2" ); + cgs.effects.bryarWallImpactEffect3 = trap_FX_RegisterEffect( "bryar/wall_impact3" ); + cgs.effects.bryarFleshImpactEffect = trap_FX_RegisterEffect( "bryar/flesh_impact" ); + cgs.effects.bryarDroidImpactEffect = trap_FX_RegisterEffect( "bryar/droid_impact" ); + + cgs.media.bryarFrontFlash = trap_R_RegisterShader( "gfx/effects/bryarFrontFlash" ); + + // Note these are temp shared effects + trap_FX_RegisterEffect("blaster/wall_impact.efx"); + trap_FX_RegisterEffect("blaster/flesh_impact.efx"); + + break; + + case WP_BLASTER: + case WP_EMPLACED_GUN: //rww - just use the same as this for now.. + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/blaster/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/blaster/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "blaster/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_BlasterProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/blaster/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "blaster/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_BlasterProjectileThink; + + trap_FX_RegisterEffect( "blaster/deflect" ); + cgs.effects.blasterShotEffect = trap_FX_RegisterEffect( "blaster/shot" ); + cgs.effects.blasterWallImpactEffect = trap_FX_RegisterEffect( "blaster/wall_impact" ); + cgs.effects.blasterFleshImpactEffect = trap_FX_RegisterEffect( "blaster/flesh_impact" ); + cgs.effects.blasterDroidImpactEffect = trap_FX_RegisterEffect( "blaster/droid_impact" ); + break; + + case WP_DISRUPTOR: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/disruptor/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/disruptor/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "disruptor/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = 0; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/disruptor/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound("sound/weapons/disruptor/altCharge.wav"); + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "disruptor/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + cgs.effects.disruptorRingsEffect = trap_FX_RegisterEffect( "disruptor/rings" ); + cgs.effects.disruptorProjectileEffect = trap_FX_RegisterEffect( "disruptor/projectile" ); + cgs.effects.disruptorWallImpactEffect = trap_FX_RegisterEffect( "disruptor/wall_impact" ); + cgs.effects.disruptorFleshImpactEffect = trap_FX_RegisterEffect( "disruptor/flesh_impact" ); + cgs.effects.disruptorAltMissEffect = trap_FX_RegisterEffect( "disruptor/alt_miss" ); + cgs.effects.disruptorAltHitEffect = trap_FX_RegisterEffect( "disruptor/alt_hit" ); + + trap_R_RegisterShader( "gfx/effects/redLine" ); + trap_R_RegisterShader( "gfx/misc/whiteline2" ); + trap_R_RegisterShader( "gfx/effects/smokeTrail" ); + + trap_S_RegisterSound("sound/weapons/disruptor/zoomstart.wav"); + trap_S_RegisterSound("sound/weapons/disruptor/zoomend.wav"); + + // Disruptor gun zoom interface + cgs.media.disruptorMask = trap_R_RegisterShader( "gfx/2d/cropCircle2"); + cgs.media.disruptorInsert = trap_R_RegisterShader( "gfx/2d/cropCircle"); + cgs.media.disruptorLight = trap_R_RegisterShader( "gfx/2d/cropCircleGlow" ); + cgs.media.disruptorInsertTick = trap_R_RegisterShader( "gfx/2d/insertTick" ); + cgs.media.disruptorChargeShader = trap_R_RegisterShaderNoMip("gfx/2d/crop_charge"); + + cgs.media.disruptorZoomLoop = trap_S_RegisterSound( "sound/weapons/disruptor/zoomloop.wav" ); + break; + + case WP_BOWCASTER: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/bowcaster/select.wav"); + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/bowcaster/fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "bowcaster/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor = {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_BowcasterProjectileThink; + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/bowcaster/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = trap_S_RegisterSound( "sound/weapons/bowcaster/altcharge.wav"); + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "bowcaster/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor= {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_BowcasterAltProjectileThink; + + cgs.effects.bowcasterShotEffect = trap_FX_RegisterEffect( "bowcaster/shot" ); + cgs.effects.bowcasterImpactEffect = trap_FX_RegisterEffect( "bowcaster/explosion" ); + + trap_FX_RegisterEffect( "bowcaster/deflect" ); + + cgs.media.greenFrontFlash = trap_R_RegisterShader( "gfx/effects/greenFrontFlash" ); + break; + + case WP_REPEATER: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/repeater/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/repeater/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "repeater/muzzle_flash" ); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_RepeaterProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/repeater/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "repeater/muzzle_flash" ); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_RepeaterAltProjectileThink; + + cgs.effects.repeaterProjectileEffect = trap_FX_RegisterEffect( "repeater/projectile" ); + cgs.effects.repeaterAltProjectileEffect = trap_FX_RegisterEffect( "repeater/alt_projectile" ); + cgs.effects.repeaterWallImpactEffect = trap_FX_RegisterEffect( "repeater/wall_impact" ); + cgs.effects.repeaterFleshImpactEffect = trap_FX_RegisterEffect( "repeater/flesh_impact" ); + //cgs.effects.repeaterAltWallImpactEffect = trap_FX_RegisterEffect( "repeater/alt_wall_impact" ); + cgs.effects.repeaterAltWallImpactEffect = trap_FX_RegisterEffect( "repeater/concussion" ); + break; + + case WP_DEMP2: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/demp2/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound("sound/weapons/demp2/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect("demp2/muzzle_flash"); + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_DEMP2_ProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound("sound/weapons/demp2/altfire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound("sound/weapons/demp2/altCharge.wav"); + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect("demp2/muzzle_flash"); + weaponInfo->altMissileModel = NULL_HANDLE; + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + cgs.effects.demp2ProjectileEffect = trap_FX_RegisterEffect( "demp2/projectile" ); + cgs.effects.demp2WallImpactEffect = trap_FX_RegisterEffect( "demp2/wall_impact" ); + cgs.effects.demp2FleshImpactEffect = trap_FX_RegisterEffect( "demp2/flesh_impact" ); + + cgs.media.demp2Shell = trap_R_RegisterModel( "models/items/sphere.md3" ); + cgs.media.demp2ShellShader = trap_R_RegisterShader( "gfx/effects/demp2shell" ); + + cgs.media.lightningFlash = trap_R_RegisterShader("gfx/misc/lightningFlash"); + break; + + case WP_FLECHETTE: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/flechette/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/flechette/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "flechette/muzzle_flash" ); + weaponInfo->missileModel = trap_R_RegisterModel("models/weapons2/golan_arms/projectileMain.md3"); + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_FlechetteProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/flechette/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "flechette/muzzle_flash" ); + weaponInfo->altMissileModel = trap_R_RegisterModel( "models/weapons2/golan_arms/projectile.md3" ); + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_FlechetteAltProjectileThink; + + cgs.effects.flechetteShotEffect = trap_FX_RegisterEffect( "flechette/shot" ); + cgs.effects.flechetteAltShotEffect = trap_FX_RegisterEffect( "flechette/alt_shot" ); + cgs.effects.flechetteWallImpactEffect = trap_FX_RegisterEffect( "flechette/wall_impact" ); + cgs.effects.flechetteFleshImpactEffect = trap_FX_RegisterEffect( "flechette/flesh_impact" ); + break; + + case WP_ROCKET_LAUNCHER: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/rocket/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = trap_FX_RegisterEffect( "rocket/muzzle_flash" ); //trap_FX_RegisterEffect( "rocket/muzzle_flash2" ); + //flash2 still looks crappy with the fx bolt stuff. Because the fx bolt stuff doesn't work entirely right. + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/merr_sonn/projectile.md3" ); + weaponInfo->missileSound = trap_S_RegisterSound( "sound/weapons/rocket/missleloop.wav"); + weaponInfo->missileDlight = 125; + VectorSet(weaponInfo->missileDlightColor, 1.0, 1.0, 0.5); + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_RocketProjectileThink; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/alt_fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = trap_FX_RegisterEffect( "rocket/altmuzzle_flash" ); + weaponInfo->altMissileModel = trap_R_RegisterModel( "models/weapons2/merr_sonn/projectile.md3" ); + weaponInfo->altMissileSound = trap_S_RegisterSound( "sound/weapons/rocket/missleloop.wav"); + weaponInfo->altMissileDlight = 125; + VectorSet(weaponInfo->altMissileDlightColor, 1.0, 1.0, 0.5); + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = FX_RocketAltProjectileThink; + + cgs.effects.rocketShotEffect = trap_FX_RegisterEffect( "rocket/shot" ); + cgs.effects.rocketExplosionEffect = trap_FX_RegisterEffect( "rocket/explosion" ); + + trap_R_RegisterShaderNoMip( "gfx/2d/wedge" ); + trap_R_RegisterShaderNoMip( "gfx/2d/lock" ); + + trap_S_RegisterSound( "sound/weapons/rocket/lock.wav" ); + trap_S_RegisterSound( "sound/weapons/rocket/tick.wav" ); + break; + + case WP_THERMAL: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/thermal/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/thermal/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = trap_S_RegisterSound( "sound/weapons/thermal/charge.wav"); + weaponInfo->muzzleEffect = NULL_FX; + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/thermal/thermal_proj.md3" ); + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = 0; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/thermal/fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = trap_S_RegisterSound( "sound/weapons/thermal/charge.wav"); + weaponInfo->altMuzzleEffect = NULL_FX; + weaponInfo->altMissileModel = trap_R_RegisterModel( "models/weapons2/thermal/thermal_proj.md3" ); + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + cgs.effects.thermalExplosionEffect = trap_FX_RegisterEffect( "thermal/explosion" ); + cgs.effects.thermalShockwaveEffect = trap_FX_RegisterEffect( "thermal/shockwave" ); + + cgs.media.grenadeBounce1 = trap_S_RegisterSound( "sound/weapons/thermal/bounce1.wav" ); + cgs.media.grenadeBounce2 = trap_S_RegisterSound( "sound/weapons/thermal/bounce2.wav" ); + + trap_S_RegisterSound( "sound/weapons/thermal/thermloop.wav" ); + trap_S_RegisterSound( "sound/weapons/thermal/warning.wav" ); + + break; + + case WP_TRIP_MINE: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/detpack/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/laser_trap/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = NULL_FX; + weaponInfo->missileModel = 0;//trap_R_RegisterModel( "models/weapons2/laser_trap/laser_trap_w.md3" ); + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = 0; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/laser_trap/fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = NULL_FX; + weaponInfo->altMissileModel = 0;//trap_R_RegisterModel( "models/weapons2/laser_trap/laser_trap_w.md3" ); + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + cgs.effects.tripmineLaserFX = trap_FX_RegisterEffect("tripMine/laserMP.efx"); + cgs.effects.tripmineGlowFX = trap_FX_RegisterEffect("tripMine/glowbit.efx"); + + trap_FX_RegisterEffect( "tripMine/explosion" ); + // NOTENOTE temp stuff + trap_S_RegisterSound( "sound/weapons/laser_trap/stick.wav" ); + trap_S_RegisterSound( "sound/weapons/laser_trap/warning.wav" ); + break; + + case WP_DET_PACK: + weaponInfo->selectSound = trap_S_RegisterSound("sound/weapons/detpack/select.wav"); + + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/detpack/fire.wav"); + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = NULL_FX; + weaponInfo->missileModel = trap_R_RegisterModel( "models/weapons2/detpack/det_pack.md3" ); + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; +// weaponInfo->missileDlightColor = {0,0,0}; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = 0; + + weaponInfo->altFlashSound[0] = trap_S_RegisterSound( "sound/weapons/detpack/fire.wav"); + weaponInfo->altFiringSound = NULL_SOUND; + weaponInfo->altChargeSound = NULL_SOUND; + weaponInfo->altMuzzleEffect = NULL_FX; + weaponInfo->altMissileModel = trap_R_RegisterModel( "models/weapons2/detpack/det_pack.md3" ); + weaponInfo->altMissileSound = NULL_SOUND; + weaponInfo->altMissileDlight = 0; +// weaponInfo->altMissileDlightColor= {0,0,0}; + weaponInfo->altMissileHitSound = NULL_SOUND; + weaponInfo->altMissileTrailFunc = 0; + + trap_R_RegisterModel( "models/weapons2/detpack/det_pack.md3" ); + trap_S_RegisterSound( "sound/weapons/detpack/stick.wav" ); + trap_S_RegisterSound( "sound/weapons/detpack/warning.wav" ); + trap_S_RegisterSound( "sound/weapons/explosions/explode5.wav" ); + break; + case WP_TURRET: + weaponInfo->flashSound[0] = NULL_SOUND; + weaponInfo->firingSound = NULL_SOUND; + weaponInfo->chargeSound = NULL_SOUND; + weaponInfo->muzzleEffect = NULL_HANDLE; + weaponInfo->missileModel = NULL_HANDLE; + weaponInfo->missileSound = NULL_SOUND; + weaponInfo->missileDlight = 0; + weaponInfo->missileHitSound = NULL_SOUND; + weaponInfo->missileTrailFunc = FX_TurretProjectileThink; + + trap_FX_RegisterEffect("effects/blaster/wall_impact.efx"); + trap_FX_RegisterEffect("effects/blaster/flesh_impact.efx"); + break; + + default: + MAKERGB( weaponInfo->flashDlightColor, 1, 1, 1 ); + weaponInfo->flashSound[0] = trap_S_RegisterSound( "sound/weapons/rocket/rocklf1a.wav" ); + break; + } +} diff --git a/code/cgame/cg_weapons.c b/code/cgame/cg_weapons.c new file mode 100644 index 0000000..021b940 --- /dev/null +++ b/code/cgame/cg_weapons.c @@ -0,0 +1,2582 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// cg_weapons.c -- events and effects dealing with weapons +#include "cg_local.h" +#include "fx_local.h" + +extern vec4_t bluehudtint; +extern vec4_t redhudtint; +extern float *hudTintColor; + +/* +Ghoul2 Insert Start +*/ +// set up the appropriate ghoul2 info to a refent +void CG_SetGhoul2InfoRef( refEntity_t *ent, refEntity_t *s1) +{ + ent->ghoul2 = s1->ghoul2; + VectorCopy( s1->modelScale, ent->modelScale); + ent->radius = s1->radius; + VectorCopy( s1->angles, ent->angles); +} + +/* +Ghoul2 Insert End +*/ + +/* +================= +CG_RegisterItemVisuals + +The server says this item is used on this level +================= +*/ +void CG_RegisterItemVisuals( int itemNum ) { + itemInfo_t *itemInfo; + gitem_t *item; + int handle; + + 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; + + if (item->giType == IT_TEAM && + (item->giTag == PW_REDFLAG || item->giTag == PW_BLUEFLAG) && + cgs.gametype == GT_CTY) + { //in CTY the flag model is different + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[1] ); + } + else if (item->giType == IT_WEAPON && + (item->giTag == WP_THERMAL || item->giTag == WP_TRIP_MINE || item->giTag == WP_DET_PACK)) + { + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[1] ); + } + else + { + itemInfo->models[0] = trap_R_RegisterModel( item->world_model[0] ); + } +/* +Ghoul2 Insert Start +*/ + if (!Q_stricmp(&item->world_model[0][strlen(item->world_model[0]) - 4], ".glm")) + { + handle = trap_G2API_InitGhoul2Model(&itemInfo->g2Models[0], item->world_model[0], 0 , 0, 0, 0, 0); + if (handle<0) + { + itemInfo->g2Models[0] = NULL; + } + else + { + itemInfo->radius[0] = 60; + } + } +/* +Ghoul2 Insert End +*/ + if (item->icon) + { + if (item->giType == IT_HEALTH) + { //medpack gets nomip'd by the ui or something I guess. + itemInfo->icon = trap_R_RegisterShaderNoMip( item->icon ); + } + else + { + itemInfo->icon = trap_R_RegisterShader( item->icon ); + } + } + else + { + itemInfo->icon = 0; + } + + 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 + +======================================================================================== +*/ + +#define WEAPON_FORCE_BUSY_HOLSTER + +#ifdef WEAPON_FORCE_BUSY_HOLSTER +//rww - this was done as a last resort. Forgive me. +static int cgWeapFrame = 0; +static int cgWeapFrameTime = 0; +#endif + +/* +================= +CG_MapTorsoToWeaponFrame + +================= +*/ +static int CG_MapTorsoToWeaponFrame( clientInfo_t *ci, int frame, int animNum ) { + animation_t *animations = bgHumanoidAnimations; +#ifdef WEAPON_FORCE_BUSY_HOLSTER + if (cg.snap->ps.forceHandExtend != HANDEXTEND_NONE || cgWeapFrameTime > cg.time) + { //the reason for the after delay is so that it doesn't snap the weapon frame to the "idle" (0) frame + //for a very quick moment + if (cgWeapFrame < 6) + { + cgWeapFrame = 6; + cgWeapFrameTime = cg.time + 10; + } + + if (cgWeapFrameTime < cg.time && cgWeapFrame < 10) + { + cgWeapFrame++; + cgWeapFrameTime = cg.time + 10; + } + + if (cg.snap->ps.forceHandExtend != HANDEXTEND_NONE && + cgWeapFrame == 10) + { + cgWeapFrameTime = cg.time + 100; + } + + return cgWeapFrame; + } + else + { + cgWeapFrame = 0; + cgWeapFrameTime = 0; + } +#endif + + switch( animNum ) + { + case TORSO_DROPWEAP1: + if ( frame >= animations[animNum].firstFrame && frame < animations[animNum].firstFrame + 5 ) + { + return frame - animations[animNum].firstFrame + 6; + } + break; + + case TORSO_RAISEWEAP1: + if ( frame >= animations[animNum].firstFrame && frame < animations[animNum].firstFrame + 4 ) + { + return frame - animations[animNum].firstFrame + 6 + 4; + } + break; + case BOTH_ATTACK1: + case BOTH_ATTACK2: + case BOTH_ATTACK3: + case BOTH_ATTACK4: + case BOTH_ATTACK10: + case BOTH_THERMAL_THROW: + if ( frame >= animations[animNum].firstFrame && frame < animations[animNum].firstFrame + 6 ) + { + return 1 + ( frame - animations[animNum].firstFrame ); + } + + break; + } + return -1; +} + + +/* +============== +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.refdef.viewangles, 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; + + //Must be a durational weapon that continuously generates an effect. + if ( cent->currentState.weapon == WP_DEMP2 && cent->currentState.eFlags & EF_ALT_FIRING ) + { /*nothing*/ } + else + { + return; + } + + memset( &beam, 0, sizeof( beam ) ); + + // NOTENOTE No lightning gun-ish stuff yet. +/* + // 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.refdef.viewangles[i]; + if (a > 180) { + a -= 360; + } + if (a < -180) { + a += 360; + } + + angle[i] = cg.refdef.viewangles[i] + a * (1.0 - cg_trueLightning.value); + if (angle[i] < 0) { + angle[i] += 360; + } + if (angle[i] > 360) { + angle[i] -= 360; + } + } + + AngleVectors(angle, forward, NULL, NULL ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); +// VectorCopy(cg.refdef.vieworg, muzzlePoint ); + } else { + // !CPMA + AngleVectors( cent->lerpAngles, forward, NULL, NULL ); + VectorCopy(cent->lerpOrigin, muzzlePoint ); + } + + // FIXME: crouch + muzzlePoint[2] += DEFAULT_VIEWHEIGHT; + + 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 ); + + beam.reType = RT_LIGHTNING; + beam.customShader = cgs.media.lightningShader; + trap_R_AddRefEntityToScene( &beam ); +*/ + + // NOTENOTE No lightning gun-ish stuff yet. +/* + // 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_AddWeaponWithPowerups +======================== +*/ +static void CG_AddWeaponWithPowerups( refEntity_t *gun, int powerups ) { + // add powerup effects + trap_R_AddRefEntityToScene( gun ); + + if (cg.predictedPlayerState.electrifyTime > cg.time) + { //add electrocution shell + int preShader = gun->customShader; + if ( rand() & 1 ) + { + gun->customShader = cgs.media.electricBodyShader; + } + else + { + gun->customShader = cgs.media.electricBody2Shader; + } + trap_R_AddRefEntityToScene( gun ); + gun->customShader = preShader; //set back just to be safe + } +} + + +/* +============= +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, vec3_t newAngles, qboolean thirdPerson ) { + refEntity_t gun; + refEntity_t barrel; + vec3_t angles; + weapon_t weaponNum; + weaponInfo_t *weapon; + centity_t *nonPredictedCent; + refEntity_t flash; + + weaponNum = cent->currentState.weapon; + + if (cent->currentState.weapon == WP_EMPLACED_GUN) + { + return; + } + + if (cg.predictedPlayerState.pm_type == PM_SPECTATOR && + cent->currentState.number == cg.predictedPlayerState.clientNum) + { //spectator mode, don't draw it... + return; + } + + CG_RegisterWeapon( weaponNum ); + weapon = &cg_weapons[weaponNum]; +/* +Ghoul2 Insert Start +*/ + + memset( &gun, 0, sizeof( gun ) ); + + // only do this if we are in first person, since world weapons are now handled on the server by Ghoul2 + if (!thirdPerson) + { + + // add the weapon + VectorCopy( parent->lightingOrigin, gun.lightingOrigin ); + gun.shadowPlane = parent->shadowPlane; + gun.renderfx = parent->renderfx; + + if (ps) + { // this player, in first person view + gun.hModel = weapon->viewModel; + } + else + { + 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"); + + if (!CG_IsMindTricked(cent->currentState.trickedentindex, + cent->currentState.trickedentindex2, + cent->currentState.trickedentindex3, + cent->currentState.trickedentindex4, + cg.snap->ps.clientNum)) + { + CG_AddWeaponWithPowerups( &gun, cent->currentState.powerups ); //don't draw the weapon if the player is invisible + /* + if ( weaponNum == WP_STUN_BATON ) + { + gun.shaderRGBA[0] = gun.shaderRGBA[1] = gun.shaderRGBA[2] = 25; + + gun.customShader = trap_R_RegisterShader( "gfx/effects/stunPass" ); + gun.renderfx = RF_RGB_TINT | RF_FIRST_PERSON | RF_DEPTHHACK; + trap_R_AddRefEntityToScene( &gun ); + } + */ + } + + if (weaponNum == WP_STUN_BATON) + { + int i = 0; + + while (i < 3) + { + memset( &barrel, 0, sizeof( barrel ) ); + VectorCopy( parent->lightingOrigin, barrel.lightingOrigin ); + barrel.shadowPlane = parent->shadowPlane; + barrel.renderfx = parent->renderfx; + + if (i == 0) + { + barrel.hModel = trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel.md3"); + } + else if (i == 1) + { + barrel.hModel = trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel2.md3"); + } + else + { + barrel.hModel = trap_R_RegisterModel("models/weapons2/stun_baton/baton_barrel3.md3"); + } + angles[YAW] = 0; + angles[PITCH] = 0; + angles[ROLL] = 0; + + AnglesToAxis( angles, barrel.axis ); + + if (i == 0) + { + CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel" ); + } + else if (i == 1) + { + CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel2" ); + } + else + { + CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel3" ); + } + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); + + i++; + } + } + else + { + // 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] = 0; + + AnglesToAxis( angles, barrel.axis ); + + CG_PositionRotatedEntityOnTag( &barrel, parent/*&gun*/, /*weapon->weaponModel*/weapon->handsModel, "tag_barrel" ); + + CG_AddWeaponWithPowerups( &barrel, cent->currentState.powerups ); + } + } + } +/* +Ghoul2 Insert End +*/ + + memset (&flash, 0, sizeof(flash)); + CG_PositionEntityOnTag( &flash, &gun, gun.hModel, "tag_flash"); + + VectorCopy(flash.origin, cg.lastFPFlashPoint); + + // Do special charge bits + //----------------------- + if ( (ps || cg.renderingThirdPerson || cg.predictedPlayerState.clientNum != cent->currentState.number) && + ( ( cent->currentState.modelindex2 == WEAPON_CHARGING_ALT && cent->currentState.weapon == WP_BRYAR_PISTOL ) || + ( cent->currentState.modelindex2 == WEAPON_CHARGING_ALT && cent->currentState.weapon == WP_BRYAR_OLD ) || + ( cent->currentState.weapon == WP_BOWCASTER && cent->currentState.modelindex2 == WEAPON_CHARGING ) || + ( cent->currentState.weapon == WP_DEMP2 && cent->currentState.modelindex2 == WEAPON_CHARGING_ALT) ) ) + { + int shader = 0; + float val = 0.0f; + float scale = 1.0f; + addspriteArgStruct_t fxSArgs; + vec3_t flashorigin, flashdir; + + if (!thirdPerson) + { + VectorCopy(flash.origin, flashorigin); + VectorCopy(flash.axis[0], flashdir); + } + else + { + mdxaBone_t boltMatrix; + + if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { //it's quite possible that we may have have no weapon model and be in a valid state, so return here if this is the case + return; + } + + // go away and get me the bolt position for this frame please + if (!(trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, newAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale))) + { // Couldn't find bolt point. + return; + } + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, flashorigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, flashdir); + } + + if ( cent->currentState.weapon == WP_BRYAR_PISTOL || + cent->currentState.weapon == WP_BRYAR_OLD) + { + // Hardcoded max charge time of 1 second + val = ( cg.time - cent->currentState.constantLight ) * 0.001f; + shader = cgs.media.bryarFrontFlash; + } + else if ( cent->currentState.weapon == WP_BOWCASTER ) + { + // Hardcoded max charge time of 1 second + val = ( cg.time - cent->currentState.constantLight ) * 0.001f; + shader = cgs.media.greenFrontFlash; + } + else if ( cent->currentState.weapon == WP_DEMP2 ) + { + val = ( cg.time - cent->currentState.constantLight ) * 0.001f; + shader = cgs.media.lightningFlash; + scale = 1.75f; + } + + if ( val < 0.0f ) + { + val = 0.0f; + } + else if ( val > 1.0f ) + { + val = 1.0f; + if (ps && cent->currentState.number == ps->clientNum) + { + CGCam_Shake( /*0.1f*/0.2f, 100 ); + } + } + else + { + if (ps && cent->currentState.number == ps->clientNum) + { + CGCam_Shake( val * val * /*0.3f*/0.6f, 100 ); + } + } + + val += random() * 0.5f; + + VectorCopy(flashorigin, fxSArgs.origin); + VectorClear(fxSArgs.vel); + VectorClear(fxSArgs.accel); + fxSArgs.scale = 3.0f*val*scale; + fxSArgs.dscale = 0.0f; + fxSArgs.sAlpha = 0.7f; + fxSArgs.eAlpha = 0.7f; + fxSArgs.rotation = random()*360; + fxSArgs.bounce = 0.0f; + fxSArgs.life = 1.0f; + fxSArgs.shader = shader; + fxSArgs.flags = 0x08000000; + + //FX_AddSprite( flash.origin, NULL, NULL, 3.0f * val, 0.0f, 0.7f, 0.7f, WHITE, WHITE, random() * 360, 0.0f, 1.0f, shader, FX_USE_ALPHA ); + trap_FX_AddSprite(&fxSArgs); + } + + // 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_DEMP2) + && ( nonPredictedCent->currentState.eFlags & EF_FIRING ) ) + { + // continuous flash + } else { + // impulse flash + if ( cg.time - cent->muzzleFlashTime > MUZZLE_FLASH_TIME) { + return; + } + } + + if ( ps || cg.renderingThirdPerson || + cent->currentState.number != cg.predictedPlayerState.clientNum ) + { // Make sure we don't do the thirdperson model effects for the local player if we're in first person + vec3_t flashorigin, flashdir; + refEntity_t flash; + + memset (&flash, 0, sizeof(flash)); + + if (!thirdPerson) + { + CG_PositionEntityOnTag( &flash, &gun, gun.hModel, "tag_flash"); + VectorCopy(flash.origin, flashorigin); + VectorCopy(flash.axis[0], flashdir); + } + else + { + mdxaBone_t boltMatrix; + + if (!trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { //it's quite possible that we may have have no weapon model and be in a valid state, so return here if this is the case + return; + } + + // go away and get me the bolt position for this frame please + if (!(trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, newAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale))) + { // Couldn't find bolt point. + return; + } + + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, flashorigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, POSITIVE_X, flashdir); + } + + if ( cg.time - cent->muzzleFlashTime <= MUZZLE_FLASH_TIME + 10 ) + { // Handle muzzle flashes + if ( cent->currentState.eFlags & EF_ALT_FIRING ) + { // Check the alt firing first. + if (weapon->altMuzzleEffect) + { + if (!thirdPerson) + { + trap_FX_PlayEntityEffectID(weapon->altMuzzleEffect, flashorigin, flash.axis, -1, -1, -1, -1 ); + } + else + { + trap_FX_PlayEffectID(weapon->altMuzzleEffect, flashorigin, flashdir, -1, -1); + } + } + } + else + { // Regular firing + if (weapon->muzzleEffect) + { + if (!thirdPerson) + { + trap_FX_PlayEntityEffectID(weapon->muzzleEffect, flashorigin, flash.axis, -1, -1, -1, -1 ); + } + else + { + trap_FX_PlayEffectID(weapon->muzzleEffect, flashorigin, flashdir, -1, -1); + } + } + } + } + + // add lightning bolt + CG_LightningBolt( nonPredictedCent, flashorigin ); + + if ( weapon->flashDlightColor[0] || weapon->flashDlightColor[1] || weapon->flashDlightColor[2] ) { + trap_R_AddLightToScene( flashorigin, 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; + float cgFov = cg_fov.value; + + if (cgFov < 1) + { + cgFov = 1; + } + if (cgFov > 97) + { + cgFov = 97; + } + + 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 || cg.predictedPlayerState.zoomMode) { + 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 ( cgFov > 90 ) { + fovOffset = -0.2 * ( cgFov - 90 ); + } else { + fovOffset = 0; + } + + cent = &cg_entities[cg.predictedPlayerState.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 + if (cent->currentState.eType == ET_NPC) + { + if (!cent->npcClient) + { + return; + } + + ci = cent->npcClient; + } + else + { + ci = &cgs.clientinfo[ cent->currentState.clientNum ]; + } + hand.frame = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.frame, cent->currentState.torsoAnim ); + hand.oldframe = CG_MapTorsoToWeaponFrame( ci, cent->pe.torso.oldFrame, cent->currentState.torsoAnim ); + hand.backlerp = cent->pe.torso.backlerp; + + // Handle the fringe situation where oldframe is invalid + if ( hand.frame == -1 ) + { + hand.frame = 0; + hand.oldframe = 0; + hand.backlerp = 0; + } + else if ( hand.oldframe == -1 ) + { + hand.oldframe = hand.frame; + hand.backlerp = 0; + } + } + + hand.hModel = weapon->handsModel; + hand.renderfx = RF_DEPTHHACK | RF_FIRST_PERSON;// | RF_MINLIGHT; + + // add everything onto the hand + CG_AddPlayerWeapon( &hand, ps, &cg_entities[cg.predictedPlayerState.clientNum], ps->persistant[PERS_TEAM], angles, qfalse ); +} + +/* +============================================================================== + +WEAPON SELECTION + +============================================================================== +*/ +#define ICON_WEAPONS 0 +#define ICON_FORCE 1 +#define ICON_INVENTORY 2 + + +void CG_DrawIconBackground(void) +{ + int height,xAdd,x2,y2,t; +// int prongLeftX,prongRightX; + float inTime = cg.invenSelectTime+WEAPON_SELECT_TIME; + float wpTime = cg.weaponSelectTime+WEAPON_SELECT_TIME; + float fpTime = cg.forceSelectTime+WEAPON_SELECT_TIME; +// int drawType = cgs.media.weaponIconBackground; +// int yOffset = 0; + +#ifdef _XBOX + //yOffset = -50; +#endif + + // don't display if dead + if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) + { + return; + } + + if (cg_hudFiles.integer) + { //simple hud + return; + } + + x2 = 30; + y2 = SCREEN_HEIGHT-70; + + //prongLeftX =x2+37; + //prongRightX =x2+544; + + if (inTime > wpTime) + { +// drawType = cgs.media.inventoryIconBackground; + cg.iconSelectTime = cg.invenSelectTime; + } + else + { +// drawType = cgs.media.weaponIconBackground; + cg.iconSelectTime = cg.weaponSelectTime; + } + + if (fpTime > inTime && fpTime > wpTime) + { +// drawType = cgs.media.forceIconBackground; + cg.iconSelectTime = cg.forceSelectTime; + } + + if ((cg.iconSelectTime+WEAPON_SELECT_TIME)1) + { + cg.iconHUDActive = qtrue; + cg.iconHUDPercent=1; + } + else if (cg.iconHUDPercent<0) + { + cg.iconHUDPercent=0; + } + } + else + { + cg.iconHUDPercent=1; + } + + //trap_R_SetColor( colorTable[CT_WHITE] ); + //height = (int) (60.0f*cg.iconHUDPercent); + //CG_DrawPic( x2+60, y2+30+yOffset, 460, -height, drawType); // Top half + //CG_DrawPic( x2+60, y2+30-2+yOffset, 460, height, drawType); // Bottom half + + // And now for the prongs +/* if ((cg.inventorySelectTime+WEAPON_SELECT_TIME)>cg.time) + { + cgs.media.currentBackground = ICON_INVENTORY; + background = &cgs.media.inventoryProngsOn; + } + else if ((cg.weaponSelectTime+WEAPON_SELECT_TIME)>cg.time) + { + cgs.media.currentBackground = ICON_WEAPONS; + } + else + { + cgs.media.currentBackground = ICON_FORCE; + background = &cgs.media.forceProngsOn; + } +*/ + // Side Prongs +// trap_R_SetColor( colorTable[CT_WHITE]); +// xAdd = (int) 8*cg.iconHUDPercent; +// CG_DrawPic( prongLeftX+xAdd, y2-10, 40, 80, background); +// CG_DrawPic( prongRightX-xAdd, y2-10, -40, 80, background); + +} + +qboolean CG_WeaponCheck(int weap) +{ + if (cg.snap->ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].energyPerShot && + cg.snap->ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].altEnergyPerShot) + { + return qfalse; + } + + return qtrue; +} + +/* +=============== +CG_WeaponSelectable +=============== +*/ +static qboolean CG_WeaponSelectable( int i ) { + /*if ( !cg.snap->ps.ammo[weaponData[i].ammoIndex] ) { + return qfalse; + }*/ + if (!i) + { + return qfalse; + } + + if (cg.predictedPlayerState.ammo[weaponData[i].ammoIndex] < weaponData[i].energyPerShot && + cg.predictedPlayerState.ammo[weaponData[i].ammoIndex] < weaponData[i].altEnergyPerShot) + { + return qfalse; + } + + if (i == WP_DET_PACK && cg.predictedPlayerState.ammo[weaponData[i].ammoIndex] < 1 && + !cg.predictedPlayerState.hasDetPackPlanted) + { + return qfalse; + } + + if ( ! (cg.predictedPlayerState.stats[ STAT_WEAPONS ] & ( 1 << i ) ) ) { + return qfalse; + } + + return qtrue; +} + +/* +=================== +CG_DrawWeaponSelect +=================== +*/ +#ifdef _XBOX +extern bool CL_ExtendSelectTime(void); +#endif +void CG_DrawWeaponSelect( void ) { + int i; + int bits; + int count; + int smallIconSize,bigIconSize; + int holdX,x,y,pad; + int sideLeftIconCnt,sideRightIconCnt; + int sideMax,holdCount,iconCnt; + int height; + int yOffset = 0; + qboolean drewConc = qfalse; + + if (cg.predictedPlayerState.emplacedIndex) + { //can't cycle when on a weapon + cg.weaponSelectTime = 0; + } + + if ((cg.weaponSelectTime+WEAPON_SELECT_TIME) (2*sideMax)) // Go to the max on each side + { + sideLeftIconCnt = sideMax; + sideRightIconCnt = sideMax; + } + else // Less than max, so do the calc + { + sideLeftIconCnt = holdCount/2; + sideRightIconCnt = holdCount - sideLeftIconCnt; + } + + if ( cg.weaponSelect == WP_CONCUSSION ) + { + i = WP_FLECHETTE; + } + else + { + i = cg.weaponSelect - 1; + } + if (i<1) + { + i = LAST_USEABLE_WEAPON; + } + + smallIconSize = 40; + bigIconSize = 80; + pad = 12; + + x = 320; + y = 410; + + // Background +// memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); +// calcColor[3] = .35f; +// trap_R_SetColor( calcColor); + + // Left side ICONS + trap_R_SetColor(colorTable[CT_WHITE]); + // Work backwards from current icon + holdX = x - ((bigIconSize/2) + pad + smallIconSize); + height = smallIconSize * 1;//cg.iconHUDPercent; + drewConc = qfalse; + + for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--) + { + if ( i == WP_CONCUSSION ) + { + i--; + } + else if ( i == WP_FLECHETTE && !drewConc && cg.weaponSelect != WP_CONCUSSION ) + { + i = WP_CONCUSSION; + } + if (i<1) + { + //i = 13; + //...don't ever do this. + i = LAST_USEABLE_WEAPON; + } + + if ( !(bits & ( 1 << i ))) // Does he have this weapon? + { + if ( i == WP_CONCUSSION ) + { + drewConc = qtrue; + i = WP_ROCKET_LAUNCHER; + } + continue; + } + + if ( !CG_WeaponSelectable(i) && + (i == WP_THERMAL || i == WP_TRIP_MINE) ) + { //Don't show thermal and tripmine when out of them + continue; + } + + ++iconCnt; // Good icon + + if (cgs.media.weaponIcons[i]) + { + weaponInfo_t *weaponInfo; + CG_RegisterWeapon( i ); + weaponInfo = &cg_weapons[i]; + + trap_R_SetColor(colorTable[CT_WHITE]); + if (!CG_WeaponCheck(i)) + { + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, /*weaponInfo->weaponIconNoAmmo*/cgs.media.weaponIcons_NA[i] ); + } + else + { + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, /*weaponInfo->weaponIcon*/cgs.media.weaponIcons[i] ); + } + + holdX -= (smallIconSize+pad); + } + if ( i == WP_CONCUSSION ) + { + drewConc = qtrue; + i = WP_ROCKET_LAUNCHER; + } + } + + // Current Center Icon + height = bigIconSize * cg.iconHUDPercent; + if (cgs.media.weaponIcons[cg.weaponSelect]) + { + weaponInfo_t *weaponInfo; + CG_RegisterWeapon( cg.weaponSelect ); + weaponInfo = &cg_weapons[cg.weaponSelect]; + + trap_R_SetColor( colorTable[CT_WHITE]); + if (!CG_WeaponCheck(cg.weaponSelect)) + { + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10+yOffset, bigIconSize, bigIconSize, cgs.media.weaponIcons_NA[cg.weaponSelect] ); + } + else + { + CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10+yOffset, bigIconSize, bigIconSize, cgs.media.weaponIcons[cg.weaponSelect] ); + } + } + + if ( cg.weaponSelect == WP_CONCUSSION ) + { + i = WP_ROCKET_LAUNCHER; + } + else + { + i = cg.weaponSelect + 1; + } + if (i> LAST_USEABLE_WEAPON) + { + i = 1; + } + + // Right side ICONS + // Work forwards from current icon + holdX = x + (bigIconSize/2) + pad; + height = smallIconSize * cg.iconHUDPercent; + for (iconCnt=1;iconCnt<(sideRightIconCnt+1);i++) + { + if ( i == WP_CONCUSSION ) + { + i++; + } + else if ( i == WP_ROCKET_LAUNCHER && !drewConc && cg.weaponSelect != WP_CONCUSSION ) + { + i = WP_CONCUSSION; + } + if (i>LAST_USEABLE_WEAPON) + { + i = 1; + } + + if ( !(bits & ( 1 << i ))) // Does he have this weapon? + { + if ( i == WP_CONCUSSION ) + { + drewConc = qtrue; + i = WP_FLECHETTE; + } + continue; + } + + if ( !CG_WeaponSelectable(i) && + (i == WP_THERMAL || i == WP_TRIP_MINE) ) + { //Don't show thermal and tripmine when out of them + continue; + } + + ++iconCnt; // Good icon + + if (/*weaponData[i].weaponIcon[0]*/cgs.media.weaponIcons[i]) + { + weaponInfo_t *weaponInfo; + CG_RegisterWeapon( i ); + weaponInfo = &cg_weapons[i]; + // No ammo for this weapon? + trap_R_SetColor( colorTable[CT_WHITE]); + if (!CG_WeaponCheck(i)) + { + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, cgs.media.weaponIcons_NA[i] ); + } + else + { + CG_DrawPic( holdX, y+10+yOffset, smallIconSize, smallIconSize, cgs.media.weaponIcons[i] ); + } + + + holdX += (smallIconSize+pad); + } + if ( i == WP_CONCUSSION ) + { + drewConc = qtrue; + i = WP_FLECHETTE; + } + } + + // draw the selected name + if ( cg_weapons[ cg.weaponSelect ].item ) + { + vec4_t textColor = { .875f, .718f, .121f, 1.0f }; + char text[1024]; + char upperKey[1024]; + + strcpy(upperKey, cg_weapons[ cg.weaponSelect ].item->classname); + + if ( trap_SP_GetStringTextString( va("SP_INGAME_%s",Q_strupr(upperKey)), text, sizeof( text ))) + { + UI_DrawProportionalString(320, y+45+yOffset, text, UI_CENTER|UI_SMALLFONT, textColor); + } + else + { + UI_DrawProportionalString(320, y+45+yOffset, cg_weapons[ cg.weaponSelect ].item->classname, UI_CENTER|UI_SMALLFONT, textColor); + } + } + + trap_R_SetColor( NULL ); +} + + +/* +=============== +CG_NextWeapon_f +=============== +*/ +void CG_NextWeapon_f( void ) { + int i; + int original; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if (cg.predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + if (cg.snap->ps.emplacedIndex) + { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + for ( i = 0 ; i < WP_NUM_WEAPONS ; i++ ) { + //*SIGH*... Hack to put concussion rifle before rocketlauncher + if ( cg.weaponSelect == WP_FLECHETTE ) + { + cg.weaponSelect = WP_CONCUSSION; + } + else if ( cg.weaponSelect == WP_CONCUSSION ) + { + cg.weaponSelect = WP_ROCKET_LAUNCHER; + } + else if ( cg.weaponSelect == WP_DET_PACK ) + { + cg.weaponSelect = WP_BRYAR_OLD; + } + else + { + cg.weaponSelect++; + } + if ( cg.weaponSelect == WP_NUM_WEAPONS ) { + cg.weaponSelect = 0; + } + // if ( cg.weaponSelect == WP_STUN_BATON ) { + // continue; // never cycle to gauntlet + // } + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + break; + } + } + if ( i == WP_NUM_WEAPONS ) { + cg.weaponSelect = original; + } + else + { + trap_S_MuteSound(cg.snap->ps.clientNum, CHAN_WEAPON); + } +} + +/* +=============== +CG_PrevWeapon_f +=============== +*/ +void CG_PrevWeapon_f( void ) { + int i; + int original; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if (cg.predictedPlayerState.pm_type == PM_SPECTATOR) + { + return; + } + + if (cg.snap->ps.emplacedIndex) + { + return; + } + + cg.weaponSelectTime = cg.time; + original = cg.weaponSelect; + + for ( i = 0 ; i < WP_NUM_WEAPONS ; i++ ) { + //*SIGH*... Hack to put concussion rifle before rocketlauncher + if ( cg.weaponSelect == WP_ROCKET_LAUNCHER ) + { + cg.weaponSelect = WP_CONCUSSION; + } + else if ( cg.weaponSelect == WP_CONCUSSION ) + { + cg.weaponSelect = WP_FLECHETTE; + } + else if ( cg.weaponSelect == WP_BRYAR_OLD ) + { + cg.weaponSelect = WP_DET_PACK; + } + else + { + cg.weaponSelect--; + } + if ( cg.weaponSelect == -1 ) { + cg.weaponSelect = WP_NUM_WEAPONS-1; + } + // if ( cg.weaponSelect == WP_STUN_BATON ) { + // continue; // never cycle to gauntlet + // } + if ( CG_WeaponSelectable( cg.weaponSelect ) ) { + break; + } + } + if ( i == WP_NUM_WEAPONS ) { + cg.weaponSelect = original; + } + else + { + trap_S_MuteSound(cg.snap->ps.clientNum, CHAN_WEAPON); + } +} + +/* +=============== +CG_Weapon_f +=============== +*/ +void CG_Weapon_f( void ) { + int num; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if (cg.snap->ps.emplacedIndex) + { + return; + } + + num = atoi( CG_Argv( 1 ) ); + + if ( num < 1 || num > LAST_USEABLE_WEAPON ) { + return; + } + + if (num == 1 && cg.snap->ps.weapon == WP_SABER) + { + if (cg.snap->ps.weaponTime < 1) + { + trap_SendConsoleCommand("sv_saberswitch\n"); + } + return; + } + + //rww - hack to make weapon numbers same as single player + if (num > WP_STUN_BATON) + { + //num++; + num += 2; //I suppose this is getting kind of crazy, what with the wp_melee in there too now. + } + else + { + if (cg.snap->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + num = WP_SABER; + } + else + { + num = WP_MELEE; + } + } + + if (num > LAST_USEABLE_WEAPON+1) + { //other weapons are off limits due to not actually being weapon weapons + return; + } + + if (num >= WP_THERMAL && num <= WP_DET_PACK) + { + int weap, i = 0; + + if (cg.snap->ps.weapon >= WP_THERMAL && + cg.snap->ps.weapon <= WP_DET_PACK) + { + // already in cycle range so start with next cycle item + weap = cg.snap->ps.weapon + 1; + } + else + { + // not in cycle range, so start with thermal detonator + weap = WP_THERMAL; + } + + // prevent an endless loop + while ( i <= 4 ) + { + if (weap > WP_DET_PACK) + { + weap = WP_THERMAL; + } + + if (CG_WeaponSelectable(weap)) + { + num = weap; + break; + } + + weap++; + i++; + } + } + + if (!CG_WeaponSelectable(num)) + { + return; + } + + cg.weaponSelectTime = cg.time; + + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) + { + if (num == WP_SABER) + { //don't have saber, try melee on the same slot + num = WP_MELEE; + + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) + { + return; + } + } + else + { + return; // don't have the weapon + } + } + + if (cg.weaponSelect != num) + { + trap_S_MuteSound(cg.snap->ps.clientNum, CHAN_WEAPON); + } + + cg.weaponSelect = num; +} + + +//Version of the above which doesn't add +2 to a weapon. The above can't +//triger WP_MELEE or WP_STUN_BATON. Derogatory comments go here. +void CG_WeaponClean_f( void ) { + int num; + + if ( !cg.snap ) { + return; + } + if ( cg.snap->ps.pm_flags & PMF_FOLLOW ) { + return; + } + + if (cg.snap->ps.emplacedIndex) + { + return; + } + + num = atoi( CG_Argv( 1 ) ); + + if ( num < 1 || num > LAST_USEABLE_WEAPON ) { + return; + } + + if (num == 1 && cg.snap->ps.weapon == WP_SABER) + { + if (cg.snap->ps.weaponTime < 1) + { + trap_SendConsoleCommand("sv_saberswitch\n"); + } + return; + } + + if(num == WP_STUN_BATON) { + if (cg.snap->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + num = WP_SABER; + } + else + { + num = WP_MELEE; + } + } + + if (num > LAST_USEABLE_WEAPON+1) + { //other weapons are off limits due to not actually being weapon weapons + return; + } + + if (num >= WP_THERMAL && num <= WP_DET_PACK) + { + int weap, i = 0; + + if (cg.snap->ps.weapon >= WP_THERMAL && + cg.snap->ps.weapon <= WP_DET_PACK) + { + // already in cycle range so start with next cycle item + weap = cg.snap->ps.weapon + 1; + } + else + { + // not in cycle range, so start with thermal detonator + weap = WP_THERMAL; + } + + // prevent an endless loop + while ( i <= 4 ) + { + if (weap > WP_DET_PACK) + { + weap = WP_THERMAL; + } + + if (CG_WeaponSelectable(weap)) + { + num = weap; + break; + } + + weap++; + i++; + } + } + + if (!CG_WeaponSelectable(num)) + { + return; + } + + cg.weaponSelectTime = cg.time; + + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) + { + if (num == WP_SABER) + { //don't have saber, try melee on the same slot + num = WP_MELEE; + + if ( ! ( cg.snap->ps.stats[STAT_WEAPONS] & ( 1 << num ) ) ) + { + return; + } + } + else + { + return; // don't have the weapon + } + } + + if (cg.weaponSelect != num) + { + trap_S_MuteSound(cg.snap->ps.clientNum, CHAN_WEAPON); + } + + cg.weaponSelect = num; +} + + + +/* +=================== +CG_OutOfAmmoChange + +The current weapon has just run out of ammo +=================== +*/ +void CG_OutOfAmmoChange( int oldWeapon ) +{ + int i; + + cg.weaponSelectTime = cg.time; + + for ( i = LAST_USEABLE_WEAPON ; i > 0 ; i-- ) //We don't want the emplaced or turret + { + if ( CG_WeaponSelectable( i ) ) + { + /* + if ( 1 == cg_autoswitch.integer && + ( i == WP_TRIP_MINE || i == WP_DET_PACK || i == WP_THERMAL || i == WP_ROCKET_LAUNCHER) ) // safe weapon switch + */ + //rww - Don't we want to make sure i != one of these if autoswitch is 1 (safe)? + if (cg_autoswitch.integer != 1 || (i != WP_TRIP_MINE && i != WP_DET_PACK && i != WP_THERMAL && i != WP_ROCKET_LAUNCHER)) + { + if (i != oldWeapon) + { //don't even do anything if we're just selecting the weapon we already have/had + cg.weaponSelect = i; + break; + } + } + } + } + + trap_S_MuteSound(cg.snap->ps.clientNum, CHAN_WEAPON); +} + + + +/* +=================================================================================================== + +WEAPON EVENTS + +=================================================================================================== +*/ + +void CG_GetClientWeaponMuzzleBoltPoint(int clIndex, vec3_t to) +{ + centity_t *cent; + mdxaBone_t boltMatrix; + + if (clIndex < 0 || clIndex >= MAX_CLIENTS) + { + return; + } + + cent = &cg_entities[clIndex]; + + if (!cent || !cent->ghoul2 || !trap_G2_HaveWeGhoul2Models(cent->ghoul2) || + !trap_G2API_HasGhoul2ModelOnIndex(&(cent->ghoul2), 1)) + { + return; + } + + trap_G2API_GetBoltMatrix(cent->ghoul2, 1, 0, &boltMatrix, cent->turAngles, cent->lerpOrigin, cg.time, cgs.gameModels, cent->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, to); +} + +/* +================ +CG_FireWeapon + +Caused by an EV_FIRE_WEAPON event +================ +*/ +void CG_FireWeapon( centity_t *cent, qboolean altFire ) { + entityState_t *ent; + int c; + weaponInfo_t *weap; + + 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 + cent->muzzleFlashTime = cg.time; + + if (cg.predictedPlayerState.clientNum == cent->currentState.number) + { + if ((ent->weapon == WP_BRYAR_PISTOL && altFire) || + (ent->weapon == WP_BRYAR_OLD && altFire) || + (ent->weapon == WP_BOWCASTER && !altFire) || + (ent->weapon == WP_DEMP2 && altFire)) + { + float val = ( cg.time - cent->currentState.constantLight ) * 0.001f; + + if (val > 3) + { + val = 3; + } + if (val < 0.2) + { + val = 0.2; + } + + val *= 2; + + CGCam_Shake( val, 250 ); + } + else if (ent->weapon == WP_ROCKET_LAUNCHER || + (ent->weapon == WP_REPEATER && altFire) || + ent->weapon == WP_FLECHETTE || + (ent->weapon == WP_CONCUSSION && !altFire)) + { + if (ent->weapon == WP_CONCUSSION) + { + if (!cg.renderingThirdPerson )//gives an advantage to being in 3rd person, but would look silly otherwise + {//kick the view back + cg.kick_angles[PITCH] = flrand( -10, -15 ); + cg.kick_time = cg.time; + } + } + else if (ent->weapon == WP_ROCKET_LAUNCHER) + { + CGCam_Shake(flrand(2, 3), 350); + } + else if (ent->weapon == WP_REPEATER) + { + CGCam_Shake(flrand(2, 3), 350); + } + else if (ent->weapon == WP_FLECHETTE) + { + if (altFire) + { + CGCam_Shake(flrand(2, 3), 350); + } + else + { + CGCam_Shake(1.5, 250); + } + } + } + } + // lightning gun only does this this on initial press + if ( ent->weapon == WP_DEMP2 ) { + 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 + if (altFire) + { + // play a sound + for ( c = 0 ; c < 4 ; c++ ) { + if ( !weap->altFlashSound[c] ) { + break; + } + } + if ( c > 0 ) { + c = rand() % c; + if ( weap->altFlashSound[c] ) + { + trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSound[c] ); + } + } +// if ( weap->altFlashSnd ) +// { +// trap_S_StartSound( NULL, ent->number, CHAN_WEAPON, weap->altFlashSnd ); +// } + } + else + { + // 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] ); + } + } + } +} + +qboolean CG_VehicleWeaponImpact( centity_t *cent ) +{//see if this is a missile entity that's owned by a vehicle and should do a special, overridden impact effect + if ((cent->currentState.eFlags&EF_JETPACK_ACTIVE)//hack so we know we're a vehicle Weapon shot + && cent->currentState.otherEntityNum2 + && g_vehWeaponInfo[cent->currentState.otherEntityNum2].iImpactFX) + {//missile is from a special vehWeapon + vec3_t normal; + ByteToDir( cent->currentState.eventParm, normal ); + + trap_FX_PlayEffectID( g_vehWeaponInfo[cent->currentState.otherEntityNum2].iImpactFX, cent->lerpOrigin, normal, -1, -1 ); + return qtrue; + } + return qfalse; +} + +/* +================= +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, qboolean altFire, int charge) +{ + int parm; + vec3_t up={0,0,1}; + + switch( weapon ) + { + case WP_BRYAR_PISTOL: + if ( altFire ) + { + parm = charge; + FX_BryarAltHitWall( origin, dir, parm ); + } + else + { + FX_BryarHitWall( origin, dir ); + } + break; + + case WP_CONCUSSION: + FX_ConcussionHitWall( origin, dir ); + break; + + case WP_BRYAR_OLD: + if ( altFire ) + { + parm = charge; + FX_BryarAltHitWall( origin, dir, parm ); + } + else + { + FX_BryarHitWall( origin, dir ); + } + break; + + case WP_TURRET: + FX_TurretHitWall( origin, dir ); + break; + + case WP_BLASTER: + FX_BlasterWeaponHitWall( origin, dir ); + break; + + case WP_DISRUPTOR: + FX_DisruptorAltMiss( origin, dir ); + break; + + case WP_BOWCASTER: + FX_BowcasterHitWall( origin, dir ); + break; + + case WP_REPEATER: + if ( altFire ) + { + FX_RepeaterAltHitWall( origin, dir ); + } + else + { + FX_RepeaterHitWall( origin, dir ); + } + break; + + case WP_DEMP2: + if (altFire) + { + trap_FX_PlayEffectID(cgs.effects.mAltDetonate, origin, dir, -1, -1); + } + else + { + FX_DEMP2_HitWall( origin, dir ); + } + break; + + case WP_FLECHETTE: + /*if (altFire) + { + CG_SurfaceExplosion(origin, dir, 20.0f, 12.0f, qtrue); + } + else + */ + if (!altFire) + { + FX_FlechetteWeaponHitWall( origin, dir ); + } + break; + + case WP_ROCKET_LAUNCHER: + FX_RocketHitWall( origin, dir ); + break; + + case WP_THERMAL: + trap_FX_PlayEffectID( cgs.effects.thermalExplosionEffect, origin, dir, -1, -1 ); + trap_FX_PlayEffectID( cgs.effects.thermalShockwaveEffect, origin, up, -1, -1 ); + break; + + case WP_EMPLACED_GUN: + FX_BlasterWeaponHitWall( origin, dir ); + //FIXME: Give it its own hit wall effect + break; + } +} + + +/* +================= +CG_MissileHitPlayer +================= +*/ +void CG_MissileHitPlayer(int weapon, vec3_t origin, vec3_t dir, int entityNum, qboolean altFire) +{ + qboolean humanoid = qtrue; + vec3_t up={0,0,1}; + + /* + // NOTENOTE Non-portable code from single player + if ( cent->gent ) + { + other = &g_entities[cent->gent->s.otherEntityNum]; + + if ( other->client && other->client->playerTeam == TEAM_BOTS ) + { + humanoid = qfalse; + } + } + */ + + // NOTENOTE No bleeding in this game +// CG_Bleed( origin, entityNum ); + + // some weapons will make an explosion with the blood, while + // others will just make the blood + switch ( weapon ) { + case WP_BRYAR_PISTOL: + if ( altFire ) + { + FX_BryarAltHitPlayer( origin, dir, humanoid ); + } + else + { + FX_BryarHitPlayer( origin, dir, humanoid ); + } + break; + + case WP_CONCUSSION: + FX_ConcussionHitPlayer( origin, dir, humanoid ); + break; + + case WP_BRYAR_OLD: + if ( altFire ) + { + FX_BryarAltHitPlayer( origin, dir, humanoid ); + } + else + { + FX_BryarHitPlayer( origin, dir, humanoid ); + } + break; + + case WP_TURRET: + FX_TurretHitPlayer( origin, dir, humanoid ); + break; + + case WP_BLASTER: + FX_BlasterWeaponHitPlayer( origin, dir, humanoid ); + break; + + case WP_DISRUPTOR: + FX_DisruptorAltHit( origin, dir); + break; + + case WP_BOWCASTER: + FX_BowcasterHitPlayer( origin, dir, humanoid ); + break; + + case WP_REPEATER: + if ( altFire ) + { + FX_RepeaterAltHitPlayer( origin, dir, humanoid ); + } + else + { + FX_RepeaterHitPlayer( origin, dir, humanoid ); + } + break; + + case WP_DEMP2: + // Do a full body effect here for some more feedback + // NOTENOTE The chaining of the demp2 is not yet implemented. + /* + if ( other ) + { + other->s.powerups |= ( 1 << PW_DISINT_1 ); + other->client->ps.powerups[PW_DISINT_1] = cg.time + 650; + } + */ + if (altFire) + { + trap_FX_PlayEffectID(cgs.effects.mAltDetonate, origin, dir, -1, -1); + } + else + { + FX_DEMP2_HitPlayer( origin, dir, humanoid ); + } + break; + + case WP_FLECHETTE: + FX_FlechetteWeaponHitPlayer( origin, dir, humanoid ); + break; + + case WP_ROCKET_LAUNCHER: + FX_RocketHitPlayer( origin, dir, humanoid ); + break; + + case WP_THERMAL: + trap_FX_PlayEffectID( cgs.effects.thermalExplosionEffect, origin, dir, -1, -1 ); + trap_FX_PlayEffectID( cgs.effects.thermalShockwaveEffect, origin, up, -1, -1 ); + break; + case WP_EMPLACED_GUN: + //FIXME: Its own effect? + FX_BlasterWeaponHitPlayer( origin, dir, humanoid ); + break; + + default: + break; + } +} + + +/* +============================================================================ + +BULLETS + +============================================================================ +*/ + + +/* +====================== +CG_CalcMuzzlePoint +====================== +*/ +qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ) { + vec3_t forward, right; + vec3_t gunpoint; + centity_t *cent; + int anim; + + if ( entityNum == cg.snap->ps.clientNum ) + { //I'm not exactly sure why we'd be rendering someone else's crosshair, but hey. + int weapontype = cg.snap->ps.weapon; + vec3_t weaponMuzzle; + centity_t *pEnt = &cg_entities[cg.predictedPlayerState.clientNum]; + + VectorCopy(WP_MuzzlePoint[weapontype], weaponMuzzle); + + if (weapontype == WP_DISRUPTOR || weapontype == WP_STUN_BATON || weapontype == WP_MELEE || weapontype == WP_SABER) + { + VectorClear(weaponMuzzle); + } + + if (cg.renderingThirdPerson) + { + VectorCopy( pEnt->lerpOrigin, gunpoint ); + AngleVectors( pEnt->lerpAngles, forward, right, NULL ); + } + else + { + VectorCopy( cg.refdef.vieworg, gunpoint ); + AngleVectors( cg.refdef.viewangles, forward, right, NULL ); + } + + if (weapontype == WP_EMPLACED_GUN && cg.snap->ps.emplacedIndex) + { + centity_t *gunEnt = &cg_entities[cg.snap->ps.emplacedIndex]; + + if (gunEnt) + { + vec3_t pitchConstraint; + + VectorCopy(gunEnt->lerpOrigin, gunpoint); + gunpoint[2] += 46; + + if (cg.renderingThirdPerson) + { + VectorCopy(pEnt->lerpAngles, pitchConstraint); + } + else + { + VectorCopy(cg.refdef.viewangles, pitchConstraint); + } + + if (pitchConstraint[PITCH] > 40) + { + pitchConstraint[PITCH] = 40; + } + AngleVectors( pitchConstraint, forward, right, NULL ); + } + } + + VectorCopy(gunpoint, muzzle); + + VectorMA(muzzle, weaponMuzzle[0], forward, muzzle); + VectorMA(muzzle, weaponMuzzle[1], right, muzzle); + + if (weapontype == WP_EMPLACED_GUN && cg.snap->ps.emplacedIndex) + { + //Do nothing + } + else if (cg.renderingThirdPerson) + { + muzzle[2] += cg.snap->ps.viewheight + weaponMuzzle[2]; + } + else + { + muzzle[2] += weaponMuzzle[2]; + } + + return qtrue; + } + + cent = &cg_entities[entityNum]; + if ( !cent->currentValid ) { + return qfalse; + } + + VectorCopy( cent->currentState.pos.trBase, muzzle ); + + AngleVectors( cent->currentState.apos.trBase, forward, NULL, NULL ); + anim = cent->currentState.legsAnim; + if ( anim == BOTH_CROUCH1WALK || anim == BOTH_CROUCH1IDLE ) { + muzzle[2] += CROUCH_VIEWHEIGHT; + } else { + muzzle[2] += DEFAULT_VIEWHEIGHT; + } + + VectorMA( muzzle, 14, forward, muzzle ); + + return qtrue; + +} + + + +/* +Ghoul2 Insert Start +*/ + +// create one instance of all the weapons we are going to use so we can just copy this info into each clients gun ghoul2 object in fast way +static void *g2WeaponInstances[MAX_WEAPONS]; + +void CG_InitG2Weapons(void) +{ + int i = 0; + gitem_t *item; + memset(g2WeaponInstances, 0, sizeof(g2WeaponInstances)); + for ( item = bg_itemlist + 1 ; item->classname ; item++ ) + { + if ( item->giType == IT_WEAPON ) + { + assert(item->giTag < MAX_WEAPONS); + + // initialise model + trap_G2API_InitGhoul2Model(&g2WeaponInstances[/*i*/item->giTag], item->world_model[0], 0, 0, 0, 0, 0); +// trap_G2API_InitGhoul2Model(&g2WeaponInstances[i], item->world_model[0],G_ModelIndex( item->world_model[0] ) , 0, 0, 0, 0); + if (g2WeaponInstances[/*i*/item->giTag]) + { + // indicate we will be bolted to model 0 (ie the player) on bolt 0 (always the right hand) when we get copied + trap_G2API_SetBoltInfo(g2WeaponInstances[/*i*/item->giTag], 0, 0); + // now set up the gun bolt on it + if (item->giTag == WP_SABER) + { + trap_G2API_AddBolt(g2WeaponInstances[/*i*/item->giTag], 0, "*blade1"); + } + else + { + trap_G2API_AddBolt(g2WeaponInstances[/*i*/item->giTag], 0, "*flash"); + } + i++; + } + if (i == MAX_WEAPONS) + { + assert(0); + break; + } + + } + } +} + +// clean out any g2 models we instanciated for copying purposes +void CG_ShutDownG2Weapons(void) +{ + int i; + for (i=0; icurrentState.eType != ET_PLAYER && + cent->currentState.eType != ET_NPC) + { + return g2WeaponInstances[weapon]; + } + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + } + else + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + + if (!ci) + { + return g2WeaponInstances[weapon]; + } + + //Try to return the custom saber instance if we can. + if (ci->saber[0].model[0] && + ci->ghoul2Weapons[0]) + { + return ci->ghoul2Weapons[0]; + } + + //If no custom then just use the default. + return g2WeaponInstances[weapon]; +} + +// what ghoul2 model do we want to copy ? +void CG_CopyG2WeaponInstance(centity_t *cent, int weaponNum, void *toGhoul2) +{ + //rww - the -1 is because there is no "weapon" for WP_NONE + assert(weaponNum < MAX_WEAPONS); + if (CG_G2WeaponInstance(cent, weaponNum/*-1*/)) + { + if (weaponNum == WP_SABER) + { + clientInfo_t *ci = NULL; + + if (cent->currentState.eType == ET_NPC) + { + ci = cent->npcClient; + } + else + { + ci = &cgs.clientinfo[cent->currentState.number]; + } + + if (!ci) + { + trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, weaponNum/*-1*/), 0, toGhoul2, 1); + } + else + { //Try both the left hand saber and the right hand saber + int i = 0; + + while (i < MAX_SABERS) + { + if (ci->saber[i].model[0] && + ci->ghoul2Weapons[i]) + { + trap_G2API_CopySpecificGhoul2Model(ci->ghoul2Weapons[i], 0, toGhoul2, i+1); + } + else if (ci->ghoul2Weapons[i]) + { //if the second saber has been removed, then be sure to remove it and free the instance. + qboolean g2HasSecondSaber = trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 2); + + if (g2HasSecondSaber) + { //remove it now since we're switching away from sabers + trap_G2API_RemoveGhoul2Model(&(toGhoul2), 2); + } + trap_G2API_CleanGhoul2Models(&ci->ghoul2Weapons[i]); + } + + i++; + } + } + } + else + { + qboolean g2HasSecondSaber = trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 2); + + if (g2HasSecondSaber) + { //remove it now since we're switching away from sabers + trap_G2API_RemoveGhoul2Model(&(toGhoul2), 2); + } + + if (weaponNum == WP_EMPLACED_GUN) + { //a bit of a hack to remove gun model when using an emplaced weap + if (trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(toGhoul2), 1); + } + } + else if (weaponNum == WP_MELEE) + { //don't want a weapon on the model for this one + if (trap_G2API_HasGhoul2ModelOnIndex(&(toGhoul2), 1)) + { + trap_G2API_RemoveGhoul2Model(&(toGhoul2), 1); + } + } + else + { + trap_G2API_CopySpecificGhoul2Model(CG_G2WeaponInstance(cent, weaponNum/*-1*/), 0, toGhoul2, 1); + } + } + } +} + +void CG_CheckPlayerG2Weapons(playerState_t *ps, centity_t *cent) +{ + if (!ps) + { + assert(0); + return; + } + + if (ps->pm_flags & PMF_FOLLOW) + { + return; + } + + if (cent->currentState.eType == ET_NPC) + { + assert(0); + return; + } + + // should we change the gun model on this player? + if (cent->currentState.saberInFlight) + { + cent->ghoul2weapon = CG_G2WeaponInstance(cent, WP_SABER); + } + + if (cent->currentState.eFlags & EF_DEAD) + { //no updating weapons when dead + cent->ghoul2weapon = NULL; + return; + } + + if (cent->torsoBolt) + { //got our limb cut off, no updating weapons until it's restored + cent->ghoul2weapon = NULL; + return; + } + + if (cgs.clientinfo[ps->clientNum].team == TEAM_SPECTATOR || + ps->persistant[PERS_TEAM] == TEAM_SPECTATOR) + { + cent->ghoul2weapon = cg_entities[ps->clientNum].ghoul2weapon = NULL; + cent->weapon = cg_entities[ps->clientNum].weapon = 0; + return; + } + + if (cent->ghoul2 && cent->ghoul2weapon != CG_G2WeaponInstance(cent, ps->weapon) && + ps->clientNum == cent->currentState.number) //don't want spectator mode forcing one client's weapon instance over another's + { + CG_CopyG2WeaponInstance(cent, ps->weapon, cent->ghoul2); + cent->ghoul2weapon = CG_G2WeaponInstance(cent, ps->weapon); + if (cent->weapon == WP_SABER && cent->weapon != ps->weapon && !ps->saberHolstered) + { //switching away from the saber + //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberoffquick.wav" )); + if (cgs.clientinfo[ps->clientNum].saber[0].soundOff && !ps->saberHolstered) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[0].soundOff); + } + + if (cgs.clientinfo[ps->clientNum].saber[1].soundOff && + cgs.clientinfo[ps->clientNum].saber[1].model[0] && + !ps->saberHolstered) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[1].soundOff); + } + } + else if (ps->weapon == WP_SABER && cent->weapon != ps->weapon && !cent->saberWasInFlight) + { //switching to the saber + //trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/saber/saberon.wav" )); + if (cgs.clientinfo[ps->clientNum].saber[0].soundOn) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[0].soundOn); + } + + if (cgs.clientinfo[ps->clientNum].saber[1].soundOn) + { + trap_S_StartSound(cent->lerpOrigin, cent->currentState.number, CHAN_AUTO, cgs.clientinfo[ps->clientNum].saber[1].soundOn); + } + + BG_SI_SetDesiredLength(&cgs.clientinfo[ps->clientNum].saber[0], 0, -1); + BG_SI_SetDesiredLength(&cgs.clientinfo[ps->clientNum].saber[1], 0, -1); + } + cent->weapon = ps->weapon; + } +} + + +/* +Ghoul2 Insert End +*/ diff --git a/code/cgame/fx_blaster.c b/code/cgame/fx_blaster.c new file mode 100644 index 0000000..05c1fb1 --- /dev/null +++ b/code/cgame/fx_blaster.c @@ -0,0 +1,65 @@ +// Blaster Weapon + +#include "cg_local.h" + +/* +------------------------- +FX_BlasterProjectileThink +------------------------- +*/ + +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.blasterShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_BlasterAltFireThink +------------------------- +*/ +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.blasterShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_BlasterWeaponHitWall +------------------------- +*/ +void FX_BlasterWeaponHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.blasterWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_BlasterWeaponHitPlayer +------------------------- +*/ +void FX_BlasterWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + if ( humanoid ) + { + trap_FX_PlayEffectID( cgs.effects.blasterFleshImpactEffect, origin, normal, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( cgs.effects.blasterDroidImpactEffect, origin, normal, -1, -1 ); + } +} diff --git a/code/cgame/fx_bowcaster.c b/code/cgame/fx_bowcaster.c new file mode 100644 index 0000000..0cf11f3 --- /dev/null +++ b/code/cgame/fx_bowcaster.c @@ -0,0 +1,62 @@ +// Bowcaster Weapon + +#include "cg_local.h" + +/* +--------------------------- +FX_BowcasterProjectileThink +--------------------------- +*/ + +void FX_BowcasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.bowcasterShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +--------------------------- +FX_BowcasterHitWall +--------------------------- +*/ + +void FX_BowcasterHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.bowcasterImpactEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_BowcasterHitPlayer +--------------------------- +*/ + +void FX_BowcasterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.bowcasterImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------------ +FX_BowcasterAltProjectileThink +------------------------------ +*/ + +void FX_BowcasterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.bowcasterShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + diff --git a/code/cgame/fx_bryarpistol.c b/code/cgame/fx_bryarpistol.c new file mode 100644 index 0000000..89b131a --- /dev/null +++ b/code/cgame/fx_bryarpistol.c @@ -0,0 +1,237 @@ +// Bryar Pistol Weapon Effects + +#include "cg_local.h" +#include "fx_local.h" + +/* +------------------------- + + MAIN FIRE + +------------------------- +FX_BryarProjectileThink +------------------------- +*/ +void FX_BryarProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.bryarShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_BryarHitWall +------------------------- +*/ +void FX_BryarHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_BryarHitPlayer +------------------------- +*/ +void FX_BryarHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + if ( humanoid ) + { + trap_FX_PlayEffectID( cgs.effects.bryarFleshImpactEffect, origin, normal, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( cgs.effects.bryarDroidImpactEffect, origin, normal, -1, -1 ); + } +} + + +/* +------------------------- + + ALT FIRE + +------------------------- +FX_BryarAltProjectileThink +------------------------- +*/ +void FX_BryarAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + int t; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + // see if we have some sort of extra charge going on + for (t = 1; t < cent->currentState.generic1; t++ ) + { + // just add ourselves over, and over, and over when we are charged + trap_FX_PlayEffectID( cgs.effects.bryarPowerupShotEffect, cent->lerpOrigin, forward, -1, -1 ); + } + + // for ( int t = 1; t < cent->gent->count; t++ ) // The single player stores the charge in count, which isn't accessible on the client + + trap_FX_PlayEffectID( cgs.effects.bryarShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_BryarAltHitWall +------------------------- +*/ +void FX_BryarAltHitWall( vec3_t origin, vec3_t normal, int power ) +{ + switch( power ) + { + case 4: + case 5: + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect3, origin, normal, -1, -1 ); + break; + + case 2: + case 3: + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect2, origin, normal, -1, -1 ); + break; + + default: + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect, origin, normal, -1, -1 ); + break; + } +} + +/* +------------------------- +FX_BryarAltHitPlayer +------------------------- +*/ +void FX_BryarAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + if ( humanoid ) + { + trap_FX_PlayEffectID( cgs.effects.bryarFleshImpactEffect, origin, normal, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( cgs.effects.bryarDroidImpactEffect, origin, normal, -1, -1 ); + } +} + + +//TURRET +/* +------------------------- +FX_TurretProjectileThink +------------------------- +*/ +void FX_TurretProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.turretShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_TurretHitWall +------------------------- +*/ +void FX_TurretHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.bryarWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_TurretHitPlayer +------------------------- +*/ +void FX_TurretHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + if ( humanoid ) + { + trap_FX_PlayEffectID( cgs.effects.bryarFleshImpactEffect, origin, normal, -1, -1 ); + } + else + { + trap_FX_PlayEffectID( cgs.effects.bryarDroidImpactEffect, origin, normal, -1, -1 ); + } +} + + + +//CONCUSSION (yeah, should probably make a new file for this.. or maybe just move all these stupid semi-redundant fx_ functions into one file) +/* +------------------------- +FX_ConcussionHitWall +------------------------- +*/ +void FX_ConcussionHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.concussionImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_ConcussionHitPlayer +------------------------- +*/ +void FX_ConcussionHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.concussionImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_ConcussionProjectileThink +------------------------- +*/ +void FX_ConcussionProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.concussionShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +--------------------------- +FX_ConcAltShot +--------------------------- +*/ +static vec3_t WHITE ={1.0f,1.0f,1.0f}; +static vec3_t BRIGHT={0.75f,0.5f,1.0f}; + +void FX_ConcAltShot( vec3_t start, vec3_t end ) +{ + //"concussion/beam" + trap_FX_AddLine( start, end, 0.1f, 10.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 175, trap_R_RegisterShader( "gfx/effects/blueLine" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + + // add some beef + trap_FX_AddLine( start, end, 0.1f, 7.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + BRIGHT, BRIGHT, 0.0f, + 150, trap_R_RegisterShader( "gfx/misc/whiteline2" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); +} diff --git a/code/cgame/fx_demp2.c b/code/cgame/fx_demp2.c new file mode 100644 index 0000000..1a0fe75 --- /dev/null +++ b/code/cgame/fx_demp2.c @@ -0,0 +1,259 @@ +// DEMP2 Weapon + +#include "cg_local.h" + +/* +--------------------------- +FX_DEMP2_ProjectileThink +--------------------------- +*/ + +void FX_DEMP2_ProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.demp2ProjectileEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +--------------------------- +FX_DEMP2_HitWall +--------------------------- +*/ + +void FX_DEMP2_HitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.demp2WallImpactEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_DEMP2_HitPlayer +--------------------------- +*/ + +void FX_DEMP2_HitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.demp2FleshImpactEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_DEMP2_AltBeam +--------------------------- +*/ +void FX_DEMP2_AltBeam( vec3_t start, vec3_t end, vec3_t normal, //qboolean spark, + vec3_t targ1, vec3_t targ2 ) +{ +//NOTENOTE Fix this after trap calls for all primitives are created. +/* + vec3_t dir, chaos, + c1, c2, + v1, v2; + float len, + s1, s2, s3; + + VectorSubtract( end, start, dir ); + len = VectorNormalize( dir ); + + // Get the base control points, we'll work from there + VectorMA( start, 0.3333f * len, dir, c1 ); + VectorMA( start, 0.6666f * len, dir, c2 ); + + // get some chaos values that really aren't very chaotic :) + s1 = sin( cg.time * 0.005f ) * 2 + crandom() * 0.2f; + s2 = sin( cg.time * 0.001f ); + s3 = sin( cg.time * 0.011f ); + + VectorSet( chaos, len * 0.01f * s1, + len * 0.02f * s2, + len * 0.04f * (s1 + s2 + s3)); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, -len * 0.02f * s3, + len * 0.01f * (s1 * s2), + -len * 0.02f * (s1 + s2 * s3)); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 2.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + FX_AddBezier( start, targ1, + c1, v1, c2, v2, + 5.0f + s1 * 2, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + FX_AddBezier( start, targ1, + c2, v2, c1, v1, + 3.0f + s3, 3.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + s1 = sin( cg.time * 0.0005f ) + crandom() * 0.1f; + s2 = sin( cg.time * 0.0025f ); + float cc2 = cos( cg.time * 0.0025f ); + s3 = sin( cg.time * 0.01f ) + crandom() * 0.1f; + + VectorSet( chaos, len * 0.08f * s2, + len * 0.04f * cc2,//s1 * -s3, + len * 0.06f * s3 ); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, len * 0.02f * s1 * s3, + len * 0.04f * s2, + len * 0.03f * s1 * s2 ); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 3.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + FX_AddBezier( start, targ1, + c1, v1, c2, v2, + 4.0f + s3, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + FX_AddBezier( start, targ1, + c2, v1, c1, v2, + 5.0f + s1 * 2, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + + VectorMA( start, 14.0f, dir, c1 ); + + FX_AddSprite( c1, NULL, NULL, 12.0f + crandom() * 4, 0.0f, 1.0f, 1.0f, random() * 360, 0.0f, 1.0f, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + FX_AddSprite( c1, NULL, NULL, 6.0f + crandom() * 2, 0.0f, 1.0f, 1.0f, random() * 360, 0.0f, 1.0f, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + + FX_AddSprite( targ1, NULL, NULL, 4.0f + crandom(), 0.0f, 1.0f, 0.0f, chaos, chaos, random() * 360, 0.0f, 10, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + FX_AddSprite( targ1, NULL, NULL, 8.0f + crandom() * 2, 0.0f, 1.0f, 0.0f, chaos, chaos, random() * 360, 0.0f, 10, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + + + //-------------------------------------------- + + VectorSubtract( targ2, targ1, dir ); + len = VectorNormalize( dir ); + + // Get the base control points, we'll work from there + VectorMA( targ1, 0.3333f * len, dir, c1 ); + VectorMA( targ1, 0.6666f * len, dir, c2 ); + + // get some chaos values that really aren't very chaotic :) + s1 = sin( cg.time * 0.005f ) * 2 + crandom() * 0.2f; + s2 = sin( cg.time * 0.001f ); + s3 = sin( cg.time * 0.011f ); + + VectorSet( chaos, len * 0.01f * s1, + len * 0.02f * s2, + len * 0.04f * (s1 + s2 + s3)); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, -len * 0.02f * s3, + len * 0.01f * (s1 * s2), + -len * 0.02f * (s1 + s2 * s3)); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 2.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + FX_AddBezier( targ1, targ2, + c1, v1, c2, v2, + 5.0f + s1 * 2, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + FX_AddBezier( targ1, targ2, + c2, v2, c1, v1, + 3.0f + s3, 3.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + s1 = sin( cg.time * 0.0005f ) + crandom() * 0.1f; + s2 = sin( cg.time * 0.0025f ); + cc2 = cos( cg.time * 0.0025f ); + s3 = sin( cg.time * 0.01f ) + crandom() * 0.1f; + + VectorSet( chaos, len * 0.08f * s2, + len * 0.04f * cc2,//s1 * -s3, + len * 0.06f * s3 ); + + VectorAdd( c1, chaos, c1 ); + VectorScale( chaos, 4.0f, v1 ); + + VectorSet( chaos, len * 0.02f * s1 * s3, + len * 0.04f * s2, + len * 0.03f * s1 * s2 ); + + VectorAdd( c2, chaos, c2 ); + VectorScale( chaos, 3.0f, v2 ); + + VectorSet( chaos, 1.0f, 1.0f, 1.0f ); + + FX_AddBezier( targ1, targ2, + c1, v1, c2, v2, + 4.0f + s3, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + FX_AddBezier( targ1, targ2, + c2, v1, c1, v2, + 5.0f + s1 * 2, 8.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + chaos, chaos, 0.0f, + 1.0f, trap_R_RegisterShader( "gfx/misc/electric2" ), FX_ALPHA_LINEAR ); + + + FX_AddSprite( targ2, NULL, NULL, 4.0f + crandom(), 0.0f, 1.0f, 0.0f, chaos, chaos, random() * 360, 0.0f, 10, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); + FX_AddSprite( targ2, NULL, NULL, 8.0f + crandom() * 2, 0.0f, 1.0f, 0.0f, chaos, chaos, random() * 360, 0.0f, 10, + trap_R_RegisterShader( "gfx/misc/lightningFlash" )); +*/ +} + +//--------------------------------------------- +void FX_DEMP2_AltDetonate( vec3_t org, float size ) +{ + localEntity_t *ex; + + ex = CG_AllocLocalEntity(); + ex->leType = LE_FADE_SCALE_MODEL; + memset( &ex->refEntity, 0, sizeof( refEntity_t )); + + ex->refEntity.renderfx |= RF_VOLUMETRIC; + + ex->startTime = cg.time; + ex->endTime = ex->startTime + 800;//1600; + + ex->radius = size; + ex->refEntity.customShader = cgs.media.demp2ShellShader; + ex->refEntity.hModel = cgs.media.demp2Shell; + VectorCopy( org, ex->refEntity.origin ); + + ex->color[0] = ex->color[1] = ex->color[2] = 255.0f; +} diff --git a/code/cgame/fx_disruptor.c b/code/cgame/fx_disruptor.c new file mode 100644 index 0000000..ac66bb6 --- /dev/null +++ b/code/cgame/fx_disruptor.c @@ -0,0 +1,148 @@ +// Disruptor Weapon + +#include "cg_local.h" +#include "fx_local.h" + +/* +--------------------------- +FX_DisruptorMainShot +--------------------------- +*/ +static vec3_t WHITE={1.0f,1.0f,1.0f}; + +void FX_DisruptorMainShot( vec3_t start, vec3_t end ) +{ +// vec3_t dir; +// float len; + + trap_FX_AddLine( start, end, 0.1f, 6.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 150, trap_R_RegisterShader( "gfx/effects/redLine" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + +// VectorSubtract( end, start, dir ); +// len = VectorNormalize( dir ); + +// FX_AddCylinder( start, dir, 5.0f, 5.0f, 0.0f, +// 5.0f, 5.0f, 0.0f, +// len, len, 0.0f, +// 1.0f, 1.0f, 0.0f, +// WHITE, WHITE, 0.0f, +// 400, cgi_R_RegisterShader( "gfx/effects/spiral" ), 0 ); +} + + +/* +--------------------------- +FX_DisruptorAltShot +--------------------------- +*/ +void FX_DisruptorAltShot( vec3_t start, vec3_t end, qboolean fullCharge ) +{ + trap_FX_AddLine( start, end, 0.1f, 10.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + WHITE, WHITE, 0.0f, + 175, trap_R_RegisterShader( "gfx/effects/redLine" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + + if ( fullCharge ) + { + vec3_t YELLER={0.8f,0.7f,0.0f}; + + // add some beef + trap_FX_AddLine( start, end, 0.1f, 7.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + YELLER, YELLER, 0.0f, + 150, trap_R_RegisterShader( "gfx/misc/whiteline2" ), + FX_SIZE_LINEAR | FX_ALPHA_LINEAR ); + } +} + + +/* +--------------------------- +FX_DisruptorAltMiss +--------------------------- +*/ +#define FX_ALPHA_WAVE 0x00000008 + +void FX_DisruptorAltMiss( vec3_t origin, vec3_t normal ) +{ + vec3_t pos, c1, c2; + addbezierArgStruct_t b; + + VectorMA( origin, 4.0f, normal, c1 ); + VectorCopy( c1, c2 ); + c1[2] += 4; + c2[2] += 12; + + VectorAdd( origin, normal, pos ); + pos[2] += 28; + + /* + FX_AddBezier( origin, pos, c1, vec3_origin, c2, vec3_origin, 6.0f, 6.0f, 0.0f, 0.0f, 0.2f, 0.5f, + WHITE, WHITE, 0.0f, 4000, trap_R_RegisterShader( "gfx/effects/smokeTrail" ), FX_ALPHA_WAVE ); + */ + + VectorCopy(origin, b.start); + VectorCopy(pos, b.end); + VectorCopy(c1, b.control1); + VectorCopy(vec3_origin, b.control1Vel); + VectorCopy(c2, b.control2); + VectorCopy(vec3_origin, b.control2Vel); + + b.size1 = 6.0f; + b.size2 = 6.0f; + b.sizeParm = 0.0f; + b.alpha1 = 0.0f; + b.alpha2 = 0.2f; + b.alphaParm = 0.5f; + + VectorCopy(WHITE, b.sRGB); + VectorCopy(WHITE, b.eRGB); + + b.rgbParm = 0.0f; + b.killTime = 4000; + b.shader = trap_R_RegisterShader( "gfx/effects/smokeTrail" ); + b.flags = FX_ALPHA_WAVE; + + trap_FX_AddBezier(&b); + + trap_FX_PlayEffectID( cgs.effects.disruptorAltMissEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_DisruptorAltHit +--------------------------- +*/ + +void FX_DisruptorAltHit( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.disruptorAltHitEffect, origin, normal, -1, -1 ); +} + + + +/* +--------------------------- +FX_DisruptorHitWall +--------------------------- +*/ + +void FX_DisruptorHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.disruptorWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_DisruptorHitPlayer +--------------------------- +*/ + +void FX_DisruptorHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.disruptorFleshImpactEffect, origin, normal, -1, -1 ); +} diff --git a/code/cgame/fx_flechette.c b/code/cgame/fx_flechette.c new file mode 100644 index 0000000..46f2ceb --- /dev/null +++ b/code/cgame/fx_flechette.c @@ -0,0 +1,67 @@ +// Golan Arms Flechette Weapon + +#include "cg_local.h" + +/* +------------------------- +FX_FlechetteProjectileThink +------------------------- +*/ + +void FX_FlechetteProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.flechetteShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------- +FX_FlechetteWeaponHitWall +------------------------- +*/ +void FX_FlechetteWeaponHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.flechetteWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------- +FX_FlechetteWeaponHitPlayer +------------------------- +*/ +void FX_FlechetteWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ +// if ( humanoid ) +// { + trap_FX_PlayEffectID( cgs.effects.flechetteFleshImpactEffect, origin, normal, -1, -1 ); +// } +// else +// { +// trap_FX_PlayEffect( "blaster/droid_impact", origin, normal ); +// } +} + + +/* +------------------------- +FX_FlechetteProjectileThink +------------------------- +*/ + +void FX_FlechetteAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.flechetteAltShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} diff --git a/code/cgame/fx_force.c b/code/cgame/fx_force.c new file mode 100644 index 0000000..55e7c36 --- /dev/null +++ b/code/cgame/fx_force.c @@ -0,0 +1,16 @@ +// Any dedicated force oriented effects + +#include "cg_local.h" + +/* +------------------------- +FX_ForceDrained +------------------------- +*/ +// This effect is not generic because of possible enhancements +void FX_ForceDrained(vec3_t origin, vec3_t dir) +{ + VectorScale(dir, -1.0, dir); + trap_FX_PlayEffectID(cgs.effects.forceDrained, origin, dir, -1, -1); +} + diff --git a/code/cgame/fx_heavyrepeater.c b/code/cgame/fx_heavyrepeater.c new file mode 100644 index 0000000..2ae5326 --- /dev/null +++ b/code/cgame/fx_heavyrepeater.c @@ -0,0 +1,155 @@ +// Heavy Repeater Weapon + +#include "cg_local.h" + +/* +--------------------------- +FX_RepeaterProjectileThink +--------------------------- +*/ + +void FX_RepeaterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.repeaterProjectileEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------ +FX_RepeaterHitWall +------------------------ +*/ + +void FX_RepeaterHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.repeaterWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------ +FX_RepeaterHitPlayer +------------------------ +*/ + +void FX_RepeaterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.repeaterFleshImpactEffect, origin, normal, -1, -1 ); +} + +static void CG_DistortionOrb( centity_t *cent ) +{ + refEntity_t ent; + vec3_t ang; + float scale = 0.5f; + float vLen; + + if (!cg_renderToTextureFX.integer) + { + return; + } + memset( &ent, 0, sizeof( ent ) ); + + VectorCopy( cent->lerpOrigin, ent.origin ); + + VectorSubtract(ent.origin, cg.refdef.vieworg, ent.axis[0]); + vLen = VectorLength(ent.axis[0]); + if (VectorNormalize(ent.axis[0]) <= 0.1f) + { // Entity is right on vieworg. quit. + return; + } + +// VectorCopy(cg.refdef.viewaxis[2], ent.axis[2]); +// CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]); + vectoangles(ent.axis[0], ang); + ang[ROLL] = cent->trickAlpha; + cent->trickAlpha += 16; //spin the half-sphere to give a "screwdriver" effect + AnglesToAxis(ang, ent.axis); + + //radius must be a power of 2, and is the actual captured texture size + if (vLen < 128) + { + ent.radius = 256; + } + else if (vLen < 256) + { + ent.radius = 128; + } + else if (vLen < 512) + { + ent.radius = 64; + } + else + { + ent.radius = 32; + } + + VectorScale(ent.axis[0], scale, ent.axis[0]); + VectorScale(ent.axis[1], scale, ent.axis[1]); + VectorScale(ent.axis[2], -scale, ent.axis[2]); + + ent.hModel = cgs.media.halfShieldModel; + ent.customShader = 0;//cgs.media.halfShieldShader; + +#if 1 + ent.renderfx = (RF_DISTORTION|RF_RGB_TINT); + + //tint the whole thing a shade of blue + ent.shaderRGBA[0] = 200.0f; + ent.shaderRGBA[1] = 200.0f; + ent.shaderRGBA[2] = 255.0f; +#else //no tint + ent.renderfx = RF_DISTORTION; +#endif + + trap_R_AddRefEntityToScene( &ent ); +} + +/* +------------------------------ +FX_RepeaterAltProjectileThink +----------------------------- +*/ + +void FX_RepeaterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + if (cg_repeaterOrb.integer) + { + CG_DistortionOrb(cent); + } + trap_FX_PlayEffectID( cgs.effects.repeaterAltProjectileEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +------------------------ +FX_RepeaterAltHitWall +------------------------ +*/ + +void FX_RepeaterAltHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.repeaterAltWallImpactEffect, origin, normal, -1, -1 ); +} + +/* +------------------------ +FX_RepeaterAltHitPlayer +------------------------ +*/ + +void FX_RepeaterAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.repeaterAltWallImpactEffect, origin, normal, -1, -1 ); +} diff --git a/code/cgame/fx_local.h b/code/cgame/fx_local.h new file mode 100644 index 0000000..fa7bae9 --- /dev/null +++ b/code/cgame/fx_local.h @@ -0,0 +1,63 @@ +// +// fx_*.c +// + +// NOTENOTE This is not the best, DO NOT CHANGE THESE! +#define FX_ALPHA_LINEAR 0x00000001 +#define FX_SIZE_LINEAR 0x00000100 + + + +// Bryar +void FX_BryarProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BryarAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BryarHitWall( vec3_t origin, vec3_t normal ); +void FX_BryarAltHitWall( vec3_t origin, vec3_t normal, int power ); +void FX_BryarHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_BryarAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// Blaster +void FX_BlasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterAltFireThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BlasterWeaponHitWall( vec3_t origin, vec3_t normal ); +void FX_BlasterWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// Disruptor +void FX_DisruptorMainShot( vec3_t start, vec3_t end ); +void FX_DisruptorAltShot( vec3_t start, vec3_t end, qboolean fullCharge ); +void FX_DisruptorAltMiss( vec3_t origin, vec3_t normal ); +void FX_DisruptorAltHit( vec3_t origin, vec3_t normal ); +void FX_DisruptorHitWall( vec3_t origin, vec3_t normal ); +void FX_DisruptorHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// Bowcaster +void FX_BowcasterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BowcasterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_BowcasterHitWall( vec3_t origin, vec3_t normal ); +void FX_BowcasterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// Heavy Repeater +void FX_RepeaterProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RepeaterAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RepeaterHitWall( vec3_t origin, vec3_t normal ); +void FX_RepeaterAltHitWall( vec3_t origin, vec3_t normal ); +void FX_RepeaterHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_RepeaterAltHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); + +// DEMP2 +void FX_DEMP2_ProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_DEMP2_HitWall( vec3_t origin, vec3_t normal ); +void FX_DEMP2_HitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_DEMP2_AltDetonate( vec3_t org, float size ); + +// Golan Arms Flechette +void FX_FlechetteProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_FlechetteWeaponHitWall( vec3_t origin, vec3_t normal ); +void FX_FlechetteWeaponHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); +void FX_FlechetteAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); + +// Personal Rocket Launcher +void FX_RocketProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RocketAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ); +void FX_RocketHitWall( vec3_t origin, vec3_t normal ); +void FX_RocketHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ); diff --git a/code/cgame/fx_rocketlauncher.c b/code/cgame/fx_rocketlauncher.c new file mode 100644 index 0000000..2d3e3ec --- /dev/null +++ b/code/cgame/fx_rocketlauncher.c @@ -0,0 +1,61 @@ +// Rocket Launcher Weapon + +#include "cg_local.h" + +/* +--------------------------- +FX_RocketProjectileThink +--------------------------- +*/ + +void FX_RocketProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.rocketShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} + +/* +--------------------------- +FX_RocketHitWall +--------------------------- +*/ + +void FX_RocketHitWall( vec3_t origin, vec3_t normal ) +{ + trap_FX_PlayEffectID( cgs.effects.rocketExplosionEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_RocketHitPlayer +--------------------------- +*/ + +void FX_RocketHitPlayer( vec3_t origin, vec3_t normal, qboolean humanoid ) +{ + trap_FX_PlayEffectID( cgs.effects.rocketExplosionEffect, origin, normal, -1, -1 ); +} + +/* +--------------------------- +FX_RocketAltProjectileThink +--------------------------- +*/ + +void FX_RocketAltProjectileThink( centity_t *cent, const struct weaponInfo_s *weapon ) +{ + vec3_t forward; + + if ( VectorNormalize2( cent->currentState.pos.trDelta, forward ) == 0.0f ) + { + forward[2] = 1.0f; + } + + trap_FX_PlayEffectID( cgs.effects.rocketShotEffect, cent->lerpOrigin, forward, -1, -1 ); +} diff --git a/code/cgame/holocronicons.h b/code/cgame/holocronicons.h new file mode 100644 index 0000000..3806e6c --- /dev/null +++ b/code/cgame/holocronicons.h @@ -0,0 +1,24 @@ +#if defined(_XBOX) && defined(_UI) +extern char *HolocronIcons[]; +#else +char *HolocronIcons[] = { + "gfx/mp/f_icon_lt_heal", //FP_HEAL, + "gfx/mp/f_icon_levitation", //FP_LEVITATION, + "gfx/mp/f_icon_speed", //FP_SPEED, + "gfx/mp/f_icon_push", //FP_PUSH, + "gfx/mp/f_icon_pull", //FP_PULL, + "gfx/mp/f_icon_lt_telepathy", //FP_TELEPATHY, + "gfx/mp/f_icon_dk_grip", //FP_GRIP, + "gfx/mp/f_icon_dk_l1", //FP_LIGHTNING, + "gfx/mp/f_icon_dk_rage", //FP_RAGE, + "gfx/mp/f_icon_lt_protect", //FP_PROTECT, + "gfx/mp/f_icon_lt_absorb", //FP_ABSORB, + "gfx/mp/f_icon_lt_healother", //FP_TEAM_HEAL, + "gfx/mp/f_icon_dk_forceother", //FP_TEAM_FORCE, + "gfx/mp/f_icon_dk_drain", //FP_DRAIN, + "gfx/mp/f_icon_sight", //FP_SEE, + "gfx/mp/f_icon_saber_attack", //FP_SABER_OFFENSE, + "gfx/mp/f_icon_saber_defend", //FP_SABER_DEFENSE, + "gfx/mp/f_icon_saber_throw" //FP_SABERTHROW +}; +#endif \ No newline at end of file diff --git a/code/cgame/tr_types.h b/code/cgame/tr_types.h new file mode 100644 index 0000000..4c63979 --- /dev/null +++ b/code/cgame/tr_types.h @@ -0,0 +1,339 @@ +// 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 +#ifdef _XBOX +#define MAX_ENTITIES 1024 // 11 bits, can't be increased without changing drawsurf bit packing (QSORT_ENTITYNUM_SHIFT) +#else +#define MAX_ENTITIES 2048 // 11 bits, can't be increased without changing drawsurf bit packing (QSORT_ENTITYNUM_SHIFT) +#endif +#define MAX_MINI_ENTITIES 1024 + +#define TR_WORLDENT (MAX_ENTITIES-1) + +// renderfx flags +#define RF_MINLIGHT 0x00001 // allways have some light (viewmodel, some items) +#define RF_THIRD_PERSON 0x00002 // don't draw through eyes, only mirrors (player bodies, chat sprites) +#define RF_FIRST_PERSON 0x00004 // only draw through eyes (view weapon, damage blood blob) +#define RF_DEPTHHACK 0x00008 // for view weapon Z crunching +#define RF_NODEPTH 0x00010 // No depth at all (seeing through walls) + +#define RF_VOLUMETRIC 0x00020 // fake volumetric shading + +#define RF_NOSHADOW 0x00040 // don't add stencil shadows + +#define RF_LIGHTING_ORIGIN 0x00080 // 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 0x00100 // use refEntity->shadowPlane +#define RF_WRAP_FRAMES 0x00200 // mod the model frames by the maxframes to allow continuous + // animation without needing to know the frame count + +#define RF_FORCE_ENT_ALPHA 0x00400 // override shader alpha settings +#define RF_RGB_TINT 0x00800 // override shader rgb settings + +#define RF_SHADOW_ONLY 0x01000 //add surfs for shadowing but don't draw them -rww + +#define RF_DISTORTION 0x02000 //area distortion effect -rww + +#define RF_FORKED 0x04000 // override lightning to have forks +#define RF_TAPERED 0x08000 // lightning tapers +#define RF_GROW 0x10000 // lightning grows from start to end during its life + +#define RF_DISINTEGRATE1 0x20000 // does a procedural hole-ripping thing. +#define RF_DISINTEGRATE2 0x40000 // does a procedural hole-ripping thing with scaling at the ripping point + +#define RF_SETANIMINDEX 0x80000 //use backEnd.currentEntity->e.skinNum for R_BindAnimatedImage + +#define RF_ALPHA_DEPTH 0x100000 //depth write on alpha model + +#define RF_FORCEPOST 0x200000 //force it to post-render -rww + +// refdef flags +#define RDF_NOWORLDMODEL 1 // used for player configuration screen +#define RDF_HYPERSPACE 4 // teleportation effect + +#define RDF_SKYBOXPORTAL 8 +#define RDF_DRAWSKYBOX 16 // the above marks a scene as being a 'portal sky'. this flag says to draw it or not + +#define RDF_AUTOMAP 32 //means this scene is to draw the automap -rww +#define RDF_NOFOG 64 //no global fog in this scene (but still brush fog) -rww + +extern int skyboxportal; +extern int drawskyboxportal; + +typedef byte color4ub_t[4]; + +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_ORIENTED_QUAD, + RT_BEAM, + RT_SABER_GLOW, + RT_ELECTRICITY, + RT_PORTALSURFACE, // doesn't draw anything, just info for portals + RT_LINE, + RT_ORIENTEDLINE, + RT_CYLINDER, + RT_ENT_CHAIN, + + RT_MAX_REF_ENTITY_TYPE +} refEntityType_t; + +typedef struct miniRefEntity_s +{ + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + vec3_t origin; // also used as MODEL_BEAM's "from" + + // previous data for frame interpolation + vec3_t oldorigin; // also used as MODEL_BEAM's "to" + + // texturing + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + vec2_t shaderTexCoord; // texture coordinates used by tcMod entity modifiers + + // extra sprite information + float radius; + float rotation; // size 2 for RT_CYLINDER or number of verts in RT_ELECTRICITY + + // misc + float shaderTime; // subtracted from refdef time to control effect start times + int frame; // also used as MODEL_BEAM's diameter + +} miniRefEntity_t; + +#pragma warning (disable : 4201 ) +typedef struct { + // this stucture must remain identical as the miniRefEntity_t + // + // + refEntityType_t reType; + int renderfx; + + qhandle_t hModel; // opaque type outside refresh + + // most recent data + vec3_t axis[3]; // rotation vectors + qboolean nonNormalizedAxes; // axis are not normalized, i.e. they have scale + vec3_t origin; // also used as MODEL_BEAM's "from" + + // previous data for frame interpolation + vec3_t oldorigin; // also used as MODEL_BEAM's "to" + + // texturing + qhandle_t customShader; // use one image for the entire thing + + // misc + byte shaderRGBA[4]; // colors used by rgbgen entity shaders + vec2_t shaderTexCoord; // texture coordinates used by tcMod entity modifiers + + // extra sprite information + float radius; + float rotation; + + // misc + float shaderTime; // subtracted from refdef time to control effect start times + int frame; // also used as MODEL_BEAM's diameter + // + // + // end miniRefEntity_t + + // + // + // specific full refEntity_t data + // + // + + // 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 + + // previous data for frame interpolation + int oldframe; + float backlerp; // 0.0 = current, 1.0 = old + + // texturing + int skinNum; // inline skin index + qhandle_t customSkin; // NULL for default skin + + // texturing + union + { +// int skinNum; // inline skin index +// ivec3_t terxelCoords; // coords of patch for RT_TERXELS + struct + { + int miniStart; + int miniCount; + } uMini; + } uRefEnt; + + // extra sprite information + union { + struct + { + float rotation; + float radius; + byte vertRGBA[4][4]; + } sprite; + struct + { + float width; + float width2; + float stscale; + } line; + struct // that whole put-the-opening-brace-on-the-same-line-as-the-beginning-of-the-definition coding style is fecal + { + float width; + vec3_t control1; + vec3_t control2; + } bezier; + struct + { + float width; + float width2; + float stscale; + float height; + float bias; + qboolean wrap; + } cylinder; + struct + { + float width; + float deviation; + float stscale; + qboolean wrap; + qboolean taper; + } electricity; + } data; + + float endTime; + float saberLength; + +/* +Ghoul2 Insert Start +*/ + vec3_t angles; // rotation angles - used for Ghoul2 + + vec3_t modelScale; // axis scale for models +// CGhoul2Info_v *ghoul2; // has to be at the end of the ref-ent in order for it to be created properly + void *ghoul2; // has to be at the end of the ref-ent in order for it to be created properly +/* +Ghoul2 Insert End +*/ +} 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 viewangles; + vec3_t viewaxis[3]; // transformation matrix + int viewContents; // world contents at vieworg + + // 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 +}; +typedef int 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, + TC_S3TC_DXT +} textureCompression_t; + +typedef struct { + const char *renderer_string; + const char *vendor_string; + const char *version_string; + const char *extensions_string; + + int maxTextureSize; // queried from GL + int maxActiveTextures; // multitexture ability + float maxTextureFilterAnisotropy; + + int colorBits, depthBits, stencilBits; + + qboolean deviceSupportsGamma; + textureCompression_t textureCompression; + qboolean textureEnvAddAvailable; + qboolean clampToEdgeAvailable; + + int vidWidth, vidHeight; + + 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; +} glconfig_t; + + +#if !defined _WIN32 + +#define OPENGL_DRIVER_NAME "libGL.so" + +#else + +#define OPENGL_DRIVER_NAME "opengl32" + +#endif // !defined _WIN32 + + +#endif // __TR_TYPES_H diff --git a/code/client/cl_avi.c b/code/client/cl_avi.c new file mode 100644 index 0000000..3b0d775 --- /dev/null +++ b/code/client/cl_avi.c @@ -0,0 +1,676 @@ +/* +=========================================================================== +Copyright (C) 2005-2006 Tim Angus + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_local.h" + +#define INDEX_FILE_EXTENSION ".index.dat" + +#define MAX_RIFF_CHUNKS 16 + +typedef struct audioFormat_s +{ + int rate; + int format; + int channels; + int bits; + + int sampleSize; + int totalBytes; +} audioFormat_t; + +typedef struct aviFileData_s +{ + qboolean fileOpen; + fileHandle_t f; + char fileName[ MAX_QPATH ]; + int fileSize; + int moviOffset; + int moviSize; + + fileHandle_t idxF; + int numIndices; + + int frameRate; + int framePeriod; + int width, height; + int numVideoFrames; + int maxRecordSize; + qboolean motionJpeg; + + qboolean audio; + audioFormat_t a; + int numAudioFrames; + + int chunkStack[ MAX_RIFF_CHUNKS ]; + int chunkStackTop; + + byte *cBuffer, *eBuffer; +} aviFileData_t; + +static aviFileData_t afd; + +#define MAX_AVI_BUFFER 2048 + +static byte buffer[ MAX_AVI_BUFFER ]; +static int bufIndex; + +/* +=============== +SafeFS_Write +=============== +*/ +static ID_INLINE void SafeFS_Write( const void *buffer, int len, fileHandle_t f ) +{ + if( FS_Write( buffer, len, f ) < len ) + Com_Error( ERR_DROP, "Failed to write avi file" ); +} + +/* +=============== +WRITE_STRING +=============== +*/ +static ID_INLINE void WRITE_STRING( const char *s ) +{ + Com_Memcpy( &buffer[ bufIndex ], s, strlen( s ) ); + bufIndex += strlen( s ); +} + +/* +=============== +WRITE_4BYTES +=============== +*/ +static ID_INLINE void WRITE_4BYTES( int x ) +{ + buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF ); + buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF ); + buffer[ bufIndex + 2 ] = (byte)( ( x >> 16 ) & 0xFF ); + buffer[ bufIndex + 3 ] = (byte)( ( x >> 24 ) & 0xFF ); + bufIndex += 4; +} + +/* +=============== +WRITE_2BYTES +=============== +*/ +static ID_INLINE void WRITE_2BYTES( int x ) +{ + buffer[ bufIndex + 0 ] = (byte)( ( x >> 0 ) & 0xFF ); + buffer[ bufIndex + 1 ] = (byte)( ( x >> 8 ) & 0xFF ); + bufIndex += 2; +} + +/* +=============== +WRITE_1BYTES +=============== +*/ +static ID_INLINE void WRITE_1BYTES( int x ) +{ + buffer[ bufIndex ] = x; + bufIndex += 1; +} + +/* +=============== +START_CHUNK +=============== +*/ +static ID_INLINE void START_CHUNK( const char *s ) +{ + if( afd.chunkStackTop == MAX_RIFF_CHUNKS ) + { + Com_Error( ERR_DROP, "ERROR: Top of chunkstack breached" ); + } + + afd.chunkStack[ afd.chunkStackTop ] = bufIndex; + afd.chunkStackTop++; + WRITE_STRING( s ); + WRITE_4BYTES( 0 ); +} + +/* +=============== +END_CHUNK +=============== +*/ +static ID_INLINE void END_CHUNK( void ) +{ + int endIndex = bufIndex; + + if( afd.chunkStackTop <= 0 ) + { + Com_Error( ERR_DROP, "ERROR: Bottom of chunkstack breached" ); + } + + afd.chunkStackTop--; + bufIndex = afd.chunkStack[ afd.chunkStackTop ]; + bufIndex += 4; + WRITE_4BYTES( endIndex - bufIndex - 4 ); + bufIndex = endIndex; + bufIndex = PAD( bufIndex, 2 ); +} + +/* +=============== +CL_WriteAVIHeader +=============== +*/ +void CL_WriteAVIHeader( void ) +{ + bufIndex = 0; + afd.chunkStackTop = 0; + + START_CHUNK( "RIFF" ); + { + WRITE_STRING( "AVI " ); + { + START_CHUNK( "LIST" ); + { + WRITE_STRING( "hdrl" ); + WRITE_STRING( "avih" ); + WRITE_4BYTES( 56 ); //"avih" "chunk" size + WRITE_4BYTES( afd.framePeriod ); //dwMicroSecPerFrame + WRITE_4BYTES( afd.maxRecordSize * + afd.frameRate ); //dwMaxBytesPerSec + WRITE_4BYTES( 0 ); //dwReserved1 + WRITE_4BYTES( 0x110 ); //dwFlags bits HAS_INDEX and IS_INTERLEAVED + WRITE_4BYTES( afd.numVideoFrames ); //dwTotalFrames + WRITE_4BYTES( 0 ); //dwInitialFrame + + if( afd.audio ) //dwStreams + WRITE_4BYTES( 2 ); + else + WRITE_4BYTES( 1 ); + + WRITE_4BYTES( afd.maxRecordSize ); //dwSuggestedBufferSize + WRITE_4BYTES( afd.width ); //dwWidth + WRITE_4BYTES( afd.height ); //dwHeight + WRITE_4BYTES( 0 ); //dwReserved[ 0 ] + WRITE_4BYTES( 0 ); //dwReserved[ 1 ] + WRITE_4BYTES( 0 ); //dwReserved[ 2 ] + WRITE_4BYTES( 0 ); //dwReserved[ 3 ] + + START_CHUNK( "LIST" ); + { + WRITE_STRING( "strl" ); + WRITE_STRING( "strh" ); + WRITE_4BYTES( 56 ); //"strh" "chunk" size + WRITE_STRING( "vids" ); + + if( afd.motionJpeg ) + WRITE_STRING( "MJPG" ); + else + WRITE_4BYTES( 0 ); // BI_RGB + + WRITE_4BYTES( 0 ); //dwFlags + WRITE_4BYTES( 0 ); //dwPriority + WRITE_4BYTES( 0 ); //dwInitialFrame + + WRITE_4BYTES( 1 ); //dwTimescale + WRITE_4BYTES( afd.frameRate ); //dwDataRate + WRITE_4BYTES( 0 ); //dwStartTime + WRITE_4BYTES( afd.numVideoFrames ); //dwDataLength + + WRITE_4BYTES( afd.maxRecordSize ); //dwSuggestedBufferSize + WRITE_4BYTES( -1 ); //dwQuality + WRITE_4BYTES( 0 ); //dwSampleSize + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( afd.width ); //rcFrame + WRITE_2BYTES( afd.height ); //rcFrame + + WRITE_STRING( "strf" ); + WRITE_4BYTES( 40 ); //"strf" "chunk" size + WRITE_4BYTES( 40 ); //biSize + WRITE_4BYTES( afd.width ); //biWidth + WRITE_4BYTES( afd.height ); //biHeight + WRITE_2BYTES( 1 ); //biPlanes + WRITE_2BYTES( 24 ); //biBitCount + + if( afd.motionJpeg ) //biCompression + { + WRITE_STRING( "MJPG" ); + WRITE_4BYTES( afd.width * + afd.height ); //biSizeImage + } + else + { + WRITE_4BYTES( 0 ); // BI_RGB + WRITE_4BYTES( afd.width * + afd.height * 3 ); //biSizeImage + } + + WRITE_4BYTES( 0 ); //biXPelsPetMeter + WRITE_4BYTES( 0 ); //biYPelsPetMeter + WRITE_4BYTES( 0 ); //biClrUsed + WRITE_4BYTES( 0 ); //biClrImportant + } + END_CHUNK( ); + + if( afd.audio ) + { + START_CHUNK( "LIST" ); + { + WRITE_STRING( "strl" ); + WRITE_STRING( "strh" ); + WRITE_4BYTES( 56 ); //"strh" "chunk" size + WRITE_STRING( "auds" ); + WRITE_4BYTES( 0 ); //FCC + WRITE_4BYTES( 0 ); //dwFlags + WRITE_4BYTES( 0 ); //dwPriority + WRITE_4BYTES( 0 ); //dwInitialFrame + + WRITE_4BYTES( afd.a.sampleSize ); //dwTimescale + WRITE_4BYTES( afd.a.sampleSize * + afd.a.rate ); //dwDataRate + WRITE_4BYTES( 0 ); //dwStartTime + WRITE_4BYTES( afd.a.totalBytes / + afd.a.sampleSize ); //dwDataLength + + WRITE_4BYTES( 0 ); //dwSuggestedBufferSize + WRITE_4BYTES( -1 ); //dwQuality + WRITE_4BYTES( afd.a.sampleSize ); //dwSampleSize + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + WRITE_2BYTES( 0 ); //rcFrame + + WRITE_STRING( "strf" ); + WRITE_4BYTES( 18 ); //"strf" "chunk" size + WRITE_2BYTES( afd.a.format ); //wFormatTag + WRITE_2BYTES( afd.a.channels ); //nChannels + WRITE_4BYTES( afd.a.rate ); //nSamplesPerSec + WRITE_4BYTES( afd.a.sampleSize * + afd.a.rate ); //nAvgBytesPerSec + WRITE_2BYTES( afd.a.sampleSize ); //nBlockAlign + WRITE_2BYTES( afd.a.bits ); //wBitsPerSample + WRITE_2BYTES( 0 ); //cbSize + } + END_CHUNK( ); + } + } + END_CHUNK( ); + + afd.moviOffset = bufIndex; + + START_CHUNK( "LIST" ); + { + WRITE_STRING( "movi" ); + } + } + } +} + +/* +=============== +CL_OpenAVIForWriting + +Creates an AVI file and gets it into a state where +writing the actual data can begin +=============== +*/ +qboolean CL_OpenAVIForWriting( const char *fileName ) +{ + if( afd.fileOpen ) + return qfalse; + + Com_Memset( &afd, 0, sizeof( aviFileData_t ) ); + + // Don't start if a framerate has not been chosen + if( cl_aviFrameRate->integer <= 0 ) + { + Com_Printf( S_COLOR_RED "cl_aviFrameRate must be >= 1\n" ); + return qfalse; + } + + if( ( afd.f = FS_FOpenFileWrite( fileName ) ) <= 0 ) + return qfalse; + + if( ( afd.idxF = FS_FOpenFileWrite( + va( "%s" INDEX_FILE_EXTENSION, fileName ) ) ) <= 0 ) + { + FS_FCloseFile( afd.f ); + return qfalse; + } + + Q_strncpyz( afd.fileName, fileName, MAX_QPATH ); + + afd.frameRate = cl_aviFrameRate->integer; + afd.framePeriod = (int)( 1000000.0f / afd.frameRate ); + afd.width = cls.glconfig.vidWidth; + afd.height = cls.glconfig.vidHeight; + + if( cl_aviMotionJpeg->integer ) + afd.motionJpeg = qtrue; + else + afd.motionJpeg = qfalse; + + // Buffers only need to store RGB pixels. + // Allocate a bit more space for the capture buffer to account for possible + // padding at the end of pixel lines, and padding for alignment + #define MAX_PACK_LEN 16 + afd.cBuffer = Z_Malloc((afd.width * 3 + MAX_PACK_LEN - 1) * afd.height + MAX_PACK_LEN - 1); + // raw avi files have pixel lines start on 4-byte boundaries + afd.eBuffer = Z_Malloc(PAD(afd.width * 3, AVI_LINE_PADDING) * afd.height); + + afd.a.rate = dma.speed; + afd.a.format = WAV_FORMAT_PCM; + afd.a.channels = dma.channels; + afd.a.bits = dma.samplebits; + afd.a.sampleSize = ( afd.a.bits / 8 ) * afd.a.channels; + + if( afd.a.rate % afd.frameRate ) + { + int suggestRate = afd.frameRate; + + while( ( afd.a.rate % suggestRate ) && suggestRate >= 1 ) + suggestRate--; + + Com_Printf( S_COLOR_YELLOW "WARNING: cl_aviFrameRate is not a divisor " + "of the audio rate, suggest %d\n", suggestRate ); + } + + if( !Cvar_VariableIntegerValue( "s_initsound" ) ) + { + afd.audio = qfalse; + } + else if( Q_stricmp( Cvar_VariableString( "s_backend" ), "OpenAL" ) ) + { + if( afd.a.bits != 16 || afd.a.channels != 2 ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: Audio format of %d bit/%d channels not supported", + afd.a.bits, afd.a.channels ); + afd.audio = qfalse; + } + else + afd.audio = qtrue; + } + else + { + afd.audio = qfalse; + Com_Printf( S_COLOR_YELLOW "WARNING: Audio capture is not supported " + "with OpenAL. Set s_useOpenAL to 0 for audio capture\n" ); + } + + // This doesn't write a real header, but allocates the + // correct amount of space at the beginning of the file + CL_WriteAVIHeader( ); + + SafeFS_Write( buffer, bufIndex, afd.f ); + afd.fileSize = bufIndex; + + bufIndex = 0; + START_CHUNK( "idx1" ); + SafeFS_Write( buffer, bufIndex, afd.idxF ); + + afd.moviSize = 4; // For the "movi" + afd.fileOpen = qtrue; + + return qtrue; +} + +/* +=============== +CL_CheckFileSize +=============== +*/ +static qboolean CL_CheckFileSize( int bytesToAdd ) +{ + unsigned int newFileSize; + + newFileSize = + afd.fileSize + // Current file size + bytesToAdd + // What we want to add + ( afd.numIndices * 16 ) + // The index + 4; // The index size + + // I assume all the operating systems + // we target can handle a 2Gb file + if( newFileSize > INT_MAX ) + { + // Close the current file... + CL_CloseAVI( ); + + // ...And open a new one + CL_OpenAVIForWriting( va( "%s_", afd.fileName ) ); + + return qtrue; + } + + return qfalse; +} + +/* +=============== +CL_WriteAVIVideoFrame +=============== +*/ +void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size ) +{ + int chunkOffset = afd.fileSize - afd.moviOffset - 8; + int chunkSize = 8 + size; + int paddingSize = PADLEN(size, 2); + byte padding[ 4 ] = { 0 }; + + if( !afd.fileOpen ) + return; + + // Chunk header + contents + padding + if( CL_CheckFileSize( 8 + size + 2 ) ) + return; + + bufIndex = 0; + WRITE_STRING( "00dc" ); + WRITE_4BYTES( size ); + + SafeFS_Write( buffer, 8, afd.f ); + SafeFS_Write( imageBuffer, size, afd.f ); + SafeFS_Write( padding, paddingSize, afd.f ); + afd.fileSize += ( chunkSize + paddingSize ); + + afd.numVideoFrames++; + afd.moviSize += ( chunkSize + paddingSize ); + + if( size > afd.maxRecordSize ) + afd.maxRecordSize = size; + + // Index + bufIndex = 0; + WRITE_STRING( "00dc" ); //dwIdentifier + WRITE_4BYTES( 0x00000010 ); //dwFlags (all frames are KeyFrames) + WRITE_4BYTES( chunkOffset ); //dwOffset + WRITE_4BYTES( size ); //dwLength + SafeFS_Write( buffer, 16, afd.idxF ); + + afd.numIndices++; +} + +#define PCM_BUFFER_SIZE 44100 + +/* +=============== +CL_WriteAVIAudioFrame +=============== +*/ +void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size ) +{ + static byte pcmCaptureBuffer[ PCM_BUFFER_SIZE ] = { 0 }; + static int bytesInBuffer = 0; + + if( !afd.audio ) + return; + + if( !afd.fileOpen ) + return; + + // Chunk header + contents + padding + if( CL_CheckFileSize( 8 + bytesInBuffer + size + 2 ) ) + return; + + if( bytesInBuffer + size > PCM_BUFFER_SIZE ) + { + Com_Printf( S_COLOR_YELLOW + "WARNING: Audio capture buffer overflow -- truncating\n" ); + size = PCM_BUFFER_SIZE - bytesInBuffer; + } + + Com_Memcpy( &pcmCaptureBuffer[ bytesInBuffer ], pcmBuffer, size ); + bytesInBuffer += size; + + // Only write if we have a frame's worth of audio + if( bytesInBuffer >= (int)ceil( (float)afd.a.rate / (float)afd.frameRate ) * + afd.a.sampleSize ) + { + int chunkOffset = afd.fileSize - afd.moviOffset - 8; + int chunkSize = 8 + bytesInBuffer; + int paddingSize = PADLEN(bytesInBuffer, 2); + byte padding[ 4 ] = { 0 }; + + bufIndex = 0; + WRITE_STRING( "01wb" ); + WRITE_4BYTES( bytesInBuffer ); + + SafeFS_Write( buffer, 8, afd.f ); + SafeFS_Write( pcmCaptureBuffer, bytesInBuffer, afd.f ); + SafeFS_Write( padding, paddingSize, afd.f ); + afd.fileSize += ( chunkSize + paddingSize ); + + afd.numAudioFrames++; + afd.moviSize += ( chunkSize + paddingSize ); + afd.a.totalBytes += bytesInBuffer; + + // Index + bufIndex = 0; + WRITE_STRING( "01wb" ); //dwIdentifier + WRITE_4BYTES( 0 ); //dwFlags + WRITE_4BYTES( chunkOffset ); //dwOffset + WRITE_4BYTES( bytesInBuffer ); //dwLength + SafeFS_Write( buffer, 16, afd.idxF ); + + afd.numIndices++; + + bytesInBuffer = 0; + } +} + +/* +=============== +CL_TakeVideoFrame +=============== +*/ +void CL_TakeVideoFrame( void ) +{ + // AVI file isn't open + if( !afd.fileOpen ) + return; + + re.TakeVideoFrame( afd.width, afd.height, + afd.cBuffer, afd.eBuffer, afd.motionJpeg ); +} + +/* +=============== +CL_CloseAVI + +Closes the AVI file and writes an index chunk +=============== +*/ +qboolean CL_CloseAVI( void ) +{ + int indexRemainder; + int indexSize = afd.numIndices * 16; + const char *idxFileName = va( "%s" INDEX_FILE_EXTENSION, afd.fileName ); + + // AVI file isn't open + if( !afd.fileOpen ) + return qfalse; + + afd.fileOpen = qfalse; + + FS_Seek( afd.idxF, 4, FS_SEEK_SET ); + bufIndex = 0; + WRITE_4BYTES( indexSize ); + SafeFS_Write( buffer, bufIndex, afd.idxF ); + FS_FCloseFile( afd.idxF ); + + // Write index + + // Open the temp index file + if( ( indexSize = FS_FOpenFileRead( idxFileName, + &afd.idxF, qtrue ) ) <= 0 ) + { + FS_FCloseFile( afd.f ); + return qfalse; + } + + indexRemainder = indexSize; + + // Append index to end of avi file + while( indexRemainder > MAX_AVI_BUFFER ) + { + FS_Read( buffer, MAX_AVI_BUFFER, afd.idxF ); + SafeFS_Write( buffer, MAX_AVI_BUFFER, afd.f ); + afd.fileSize += MAX_AVI_BUFFER; + indexRemainder -= MAX_AVI_BUFFER; + } + FS_Read( buffer, indexRemainder, afd.idxF ); + SafeFS_Write( buffer, indexRemainder, afd.f ); + afd.fileSize += indexRemainder; + FS_FCloseFile( afd.idxF ); + + // Remove temp index file + FS_HomeRemove( idxFileName ); + + // Write the real header + FS_Seek( afd.f, 0, FS_SEEK_SET ); + CL_WriteAVIHeader( ); + + bufIndex = 4; + WRITE_4BYTES( afd.fileSize - 8 ); // "RIFF" size + + bufIndex = afd.moviOffset + 4; // Skip "LIST" + WRITE_4BYTES( afd.moviSize ); + + SafeFS_Write( buffer, bufIndex, afd.f ); + + Z_Free( afd.cBuffer ); + Z_Free( afd.eBuffer ); + FS_FCloseFile( afd.f ); + + Com_Printf( "Wrote %d:%d frames to %s\n", afd.numVideoFrames, afd.numAudioFrames, afd.fileName ); + + return qtrue; +} + +/* +=============== +CL_VideoRecording +=============== +*/ +qboolean CL_VideoRecording( void ) +{ + return afd.fileOpen; +} diff --git a/code/client/cl_cgame.c b/code/client/cl_cgame.c new file mode 100644 index 0000000..f5fe02f --- /dev/null +++ b/code/client/cl_cgame.c @@ -0,0 +1,1100 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_cgame.c -- client system interaction with client game + +#include "client.h" + +#include "../botlib/botlib.h" + +#include "libmumblelink.h" + +extern botlib_export_t *botlib_export; + +extern qboolean loadCamera(const char *name); +extern void startCamera(int time); +extern qboolean getCameraInfo(int time, vec3_t *origin, vec3_t *angles); + +/* +==================== +CL_GetGameState +==================== +*/ +void CL_GetGameState( gameState_t *gs ) { + *gs = cl.gameState; +} + +/* +==================== +CL_GetGlconfig +==================== +*/ +void CL_GetGlconfig( glconfig_t *glconfig ) { + *glconfig = cls.glconfig; +} + + +/* +==================== +CL_GetUserCmd +==================== +*/ +qboolean CL_GetUserCmd( int cmdNumber, usercmd_t *ucmd ) { + // cmds[cmdNumber] is the last properly generated command + + // can't return anything that we haven't created yet + if ( cmdNumber > cl.cmdNumber ) { + Com_Error( ERR_DROP, "CL_GetUserCmd: %i >= %i", cmdNumber, cl.cmdNumber ); + } + + // the usercmd has been overwritten in the wrapping + // buffer because it is too far out of date + if ( cmdNumber <= cl.cmdNumber - CMD_BACKUP ) { + return qfalse; + } + + *ucmd = cl.cmds[ cmdNumber & CMD_MASK ]; + + return qtrue; +} + +int CL_GetCurrentCmdNumber( void ) { + return cl.cmdNumber; +} + + +/* +==================== +CL_GetParseEntityState +==================== +*/ +qboolean CL_GetParseEntityState( int parseEntityNumber, entityState_t *state ) { + // can't return anything that hasn't been parsed yet + if ( parseEntityNumber >= cl.parseEntitiesNum ) { + Com_Error( ERR_DROP, "CL_GetParseEntityState: %i >= %i", + parseEntityNumber, cl.parseEntitiesNum ); + } + + // can't return anything that has been overwritten in the circular buffer + if ( parseEntityNumber <= cl.parseEntitiesNum - MAX_PARSE_ENTITIES ) { + return qfalse; + } + + *state = cl.parseEntities[ parseEntityNumber & ( MAX_PARSE_ENTITIES - 1 ) ]; + return qtrue; +} + +/* +==================== +CL_GetCurrentSnapshotNumber +==================== +*/ +void CL_GetCurrentSnapshotNumber( int *snapshotNumber, int *serverTime ) { + *snapshotNumber = cl.snap.messageNum; + *serverTime = cl.snap.serverTime; +} + +/* +==================== +CL_GetSnapshot +==================== +*/ +qboolean CL_GetSnapshot( int snapshotNumber, snapshot_t *snapshot ) { + clSnapshot_t *clSnap; + int i, count; + + if ( snapshotNumber > cl.snap.messageNum ) { + Com_Error( ERR_DROP, "CL_GetSnapshot: snapshotNumber > cl.snapshot.messageNum" ); + } + + // if the frame has fallen out of the circular buffer, we can't return it + if ( cl.snap.messageNum - snapshotNumber >= PACKET_BACKUP ) { + return qfalse; + } + + // if the frame is not valid, we can't return it + clSnap = &cl.snapshots[snapshotNumber & PACKET_MASK]; + if ( !clSnap->valid ) { + return qfalse; + } + + // if the entities in the frame have fallen out of their + // circular buffer, we can't return it + if ( cl.parseEntitiesNum - clSnap->parseEntitiesNum >= MAX_PARSE_ENTITIES ) { + return qfalse; + } + + // write the snapshot + snapshot->snapFlags = clSnap->snapFlags; + snapshot->serverCommandSequence = clSnap->serverCommandNum; + snapshot->ping = clSnap->ping; + snapshot->serverTime = clSnap->serverTime; + Com_Memcpy( snapshot->areamask, clSnap->areamask, sizeof( snapshot->areamask ) ); + snapshot->ps = clSnap->ps; + count = clSnap->numEntities; + if ( count > MAX_ENTITIES_IN_SNAPSHOT ) { + Com_DPrintf( "CL_GetSnapshot: truncated %i entities to %i\n", count, MAX_ENTITIES_IN_SNAPSHOT ); + count = MAX_ENTITIES_IN_SNAPSHOT; + } + snapshot->numEntities = count; + for ( i = 0 ; i < count ; i++ ) { + snapshot->entities[i] = + cl.parseEntities[ ( clSnap->parseEntitiesNum + i ) & (MAX_PARSE_ENTITIES-1) ]; + } + + // FIXME: configstring changes and server commands!!! + + return qtrue; +} + +/* +===================== +CL_SetUserCmdValue +===================== +*/ +void CL_SetUserCmdValue( int userCmdValue, float sensitivityScale ) { + cl.cgameUserCmdValue = userCmdValue; + cl.cgameSensitivity = sensitivityScale; +} + +/* +===================== +CL_AddCgameCommand +===================== +*/ +void CL_AddCgameCommand( const char *cmdName ) { + Cmd_AddCommand( cmdName, NULL ); +} + +/* +===================== +CL_CgameError +===================== +*/ +void CL_CgameError( const char *string ) { + Com_Error( ERR_DROP, "%s", string ); +} + + +/* +===================== +CL_ConfigstringModified +===================== +*/ +void CL_ConfigstringModified( void ) { + char *old, *s; + int i, index; + char *dup; + gameState_t oldGs; + int len; + + index = atoi( Cmd_Argv(1) ); + if ( index < 0 || index >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + // get everything after "cs " + s = Cmd_ArgsFrom(2); + + old = cl.gameState.stringData + cl.gameState.stringOffsets[ index ]; + if ( !strcmp( old, s ) ) { + return; // unchanged + } + + // build the new gameState_t + oldGs = cl.gameState; + + Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + + // leave the first 0 for uninitialized strings + cl.gameState.dataCount = 1; + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( i == index ) { + dup = s; + } else { + dup = oldGs.stringData + oldGs.stringOffsets[ i ]; + } + if ( !dup[0] ) { + continue; // leave with the default empty string + } + + len = strlen( dup ); + + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, dup, len + 1 ); + cl.gameState.dataCount += len + 1; + } + + if ( index == CS_SYSTEMINFO ) { + // parse serverId and other cvars + CL_SystemInfoChanged(); + } + +} + + +/* +=================== +CL_GetServerCommand + +Set up argc/argv for the given command +=================== +*/ +qboolean CL_GetServerCommand( int serverCommandNumber ) { + char *s; + char *cmd; + static char bigConfigString[BIG_INFO_STRING]; + int argc; + + // if we have irretrievably lost a reliable command, drop the connection + if ( serverCommandNumber <= clc.serverCommandSequence - MAX_RELIABLE_COMMANDS ) { + // when a demo record was started after the client got a whole bunch of + // reliable commands then the client never got those first reliable commands + if ( clc.demoplaying ) + return qfalse; + Com_Error( ERR_DROP, "CL_GetServerCommand: a reliable command was cycled out" ); + return qfalse; + } + + if ( serverCommandNumber > clc.serverCommandSequence ) { + Com_Error( ERR_DROP, "CL_GetServerCommand: requested a command not received" ); + return qfalse; + } + + s = clc.serverCommands[ serverCommandNumber & ( MAX_RELIABLE_COMMANDS - 1 ) ]; + clc.lastExecutedServerCommand = serverCommandNumber; + + Com_DPrintf( "serverCommand: %i : %s\n", serverCommandNumber, s ); + +rescan: + Cmd_TokenizeString( s ); + cmd = Cmd_Argv(0); + argc = Cmd_Argc(); + + if ( !strcmp( cmd, "disconnect" ) ) { + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=552 + // allow server to indicate why they were disconnected + if ( argc >= 2 ) + Com_Error( ERR_SERVERDISCONNECT, "Server disconnected - %s", Cmd_Argv( 1 ) ); + else + Com_Error( ERR_SERVERDISCONNECT, "Server disconnected" ); + } + + if ( !strcmp( cmd, "bcs0" ) ) { + Com_sprintf( bigConfigString, BIG_INFO_STRING, "cs %s \"%s", Cmd_Argv(1), Cmd_Argv(2) ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs1" ) ) { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + return qfalse; + } + + if ( !strcmp( cmd, "bcs2" ) ) { + s = Cmd_Argv(2); + if( strlen(bigConfigString) + strlen(s) + 1 >= BIG_INFO_STRING ) { + Com_Error( ERR_DROP, "bcs exceeded BIG_INFO_STRING" ); + } + strcat( bigConfigString, s ); + strcat( bigConfigString, "\"" ); + s = bigConfigString; + goto rescan; + } + + if ( !strcmp( cmd, "cs" ) ) { + CL_ConfigstringModified(); + // reparse the string, because CL_ConfigstringModified may have done another Cmd_TokenizeString() + Cmd_TokenizeString( s ); + return qtrue; + } + + if ( !strcmp( cmd, "map_restart" ) ) { + // clear notify lines and outgoing commands before passing + // the restart to the cgame + Con_ClearNotify(); + // reparse the string, because Con_ClearNotify() may have done another Cmd_TokenizeString() + Cmd_TokenizeString( s ); + Com_Memset( cl.cmds, 0, sizeof( cl.cmds ) ); + return qtrue; + } + + // the clientLevelShot command is used during development + // to generate 128*128 screenshots from the intermission + // point of levels for the menu system to use + // we pass it along to the cgame to make apropriate adjustments, + // but we also clear the console and notify lines here + if ( !strcmp( cmd, "clientLevelShot" ) ) { + // don't do it if we aren't running the server locally, + // otherwise malicious remote servers could overwrite + // the existing thumbnails + if ( !com_sv_running->integer ) { + return qfalse; + } + // close the console + Con_Close(); + // take a special screenshot next frame + Cbuf_AddText( "wait ; wait ; wait ; wait ; screenshot levelshot\n" ); + return qtrue; + } + + // we may want to put a "connect to other server" command here + + // cgame can now act on the command + return qtrue; +} + + +/* +==================== +CL_CM_LoadMap + +Just adds default parameters that cgame doesn't need to know about +==================== +*/ +void CL_CM_LoadMap( const char *mapname ) { + int checksum; + + CM_LoadMap( mapname, qtrue, &checksum ); +} + +/* +==================== +CL_ShutdonwCGame + +==================== +*/ +void CL_ShutdownCGame( void ) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME ); + cls.cgameStarted = qfalse; + if ( !cgvm ) { + return; + } + VM_Call( cgvm, CG_SHUTDOWN ); + VM_Free( cgvm ); + cgvm = NULL; +} + +static int FloatAsInt( float f ) { + floatint_t fi; + fi.f = f; + return fi.i; +} + +/* +==================== +CL_CgameSystemCalls + +The cgame module is making a system call +==================== +*/ +intptr_t CL_CgameSystemCalls( intptr_t *args ) { + switch( args[0] ) { + case CG_PRINT: + Com_Printf( "%s", (const char*)VMA(1) ); + return 0; + case CG_ERROR: + Com_Error( ERR_DROP, "%s", (const char*)VMA(1) ); + return 0; + case CG_MILLISECONDS: + return Sys_Milliseconds(); + case CG_CVAR_REGISTER: + Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); + return 0; + case CG_CVAR_UPDATE: + Cvar_Update( VMA(1) ); + return 0; + case CG_CVAR_SET: + Cvar_SetSafe( VMA(1), VMA(2) ); + return 0; + case CG_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); + return 0; + case CG_ARGC: + return Cmd_Argc(); + case CG_ARGV: + Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); + return 0; + case CG_ARGS: + Cmd_ArgsBuffer( VMA(1), args[2] ); + return 0; + case CG_FS_FOPENFILE: + return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); + case CG_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + case CG_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; +// case CG_FS_SEEK: +// return FS_Seek( args[1], args[2], args[3] ); + case CG_SENDCONSOLECOMMAND: + Cbuf_AddText( VMA(1) ); + return 0; + case CG_ADDCOMMAND: + CL_AddCgameCommand( VMA(1) ); + return 0; + case CG_REMOVECOMMAND: + Cmd_RemoveCommandSafe( VMA(1) ); + return 0; + case CG_SENDCLIENTCOMMAND: + CL_AddReliableCommand(VMA(1), qfalse); + return 0; + case CG_UPDATESCREEN: + // this is used during lengthy level loading, so pump message loop +// Com_EventLoop(); // FIXME: if a server restarts here, BAD THINGS HAPPEN! +// We can't call Com_EventLoop here, a restart will crash and this _does_ happen +// if there is a map change while we are downloading at pk3. +// ZOID + SCR_UpdateScreen(); + return 0; + case CG_CM_LOADMAP: + CL_CM_LoadMap( VMA(1) ); + return 0; + case CG_CM_NUMINLINEMODELS: + return CM_NumInlineModels(); + case CG_CM_INLINEMODEL: + return CM_InlineModel( args[1] ); + case CG_CM_TEMPBOXMODEL: + return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qfalse ); + case CG_CM_TEMPCAPSULEMODEL: + return CM_TempBoxModel( VMA(1), VMA(2), /*int capsule*/ qtrue ); + case CG_CM_POINTCONTENTS: + return CM_PointContents( VMA(1), args[2] ); + case CG_CM_TRANSFORMEDPOINTCONTENTS: + return CM_TransformedPointContents( VMA(1), args[2], VMA(3), VMA(4) ); + case CG_CM_BOXTRACE: + CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qfalse ); + return 0; + case CG_CM_CAPSULETRACE: + CM_BoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], /*int capsule*/ qtrue ); + return 0; + case CG_CM_TRANSFORMEDBOXTRACE: + CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qfalse ); + return 0; + case CG_CM_TRANSFORMEDCAPSULETRACE: + CM_TransformedBoxTrace( VMA(1), VMA(2), VMA(3), VMA(4), VMA(5), args[6], args[7], VMA(8), VMA(9), /*int capsule*/ qtrue ); + return 0; + case CG_CM_MARKFRAGMENTS: + return re.MarkFragments( args[1], VMA(2), VMA(3), args[4], VMA(5), args[6], VMA(7) ); + case CG_S_STARTSOUND: + S_StartSound( VMA(1), args[2], args[3], args[4] ); + return 0; + case CG_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + case CG_S_CLEARLOOPINGSOUNDS: + S_ClearLoopingSounds(args[1]); + return 0; + case CG_S_ADDLOOPINGSOUND: + S_AddLoopingSound( args[1], VMA(2), VMA(3), args[4] ); + return 0; + case CG_S_ADDREALLOOPINGSOUND: + S_AddRealLoopingSound( args[1], VMA(2), VMA(3), args[4] ); + return 0; + case CG_S_STOPLOOPINGSOUND: + S_StopLoopingSound( args[1] ); + return 0; + case CG_S_UPDATEENTITYPOSITION: + S_UpdateEntityPosition( args[1], VMA(2) ); + return 0; + case CG_S_RESPATIALIZE: + S_Respatialize( args[1], VMA(2), VMA(3), args[4] ); + return 0; + case CG_S_REGISTERSOUND: + return S_RegisterSound( VMA(1), args[2] ); + case CG_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( VMA(1), VMA(2) ); + return 0; + case CG_R_LOADWORLDMAP: + re.LoadWorld( VMA(1) ); + return 0; + case CG_R_REGISTERMODEL: + return re.RegisterModel( VMA(1) ); + case CG_R_REGISTERSKIN: + return re.RegisterSkin( VMA(1) ); + case CG_R_REGISTERSHADER: + return re.RegisterShader( VMA(1) ); + case CG_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( VMA(1) ); + case CG_R_REGISTERFONT: + re.RegisterFont( VMA(1) ); + return 0; + case CG_R_CLEARSCENE: + re.ClearScene(); + return 0; + case CG_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( VMA(1) ); + return 0; + case CG_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); + return 0; + case CG_R_ADDPOLYSTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA(3), args[4] ); + return 0; + case CG_R_LIGHTFORPOINT: + return re.LightForPoint( VMA(1), VMA(2), VMA(3), VMA(4) ); + case CG_R_ADDLIGHTTOSCENE: + re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_ADDADDITIVELIGHTTOSCENE: + re.AddAdditiveLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + case CG_R_RENDERSCENE: + re.RenderScene( VMA(1) ); + return 0; + case CG_R_SETCOLOR: + re.SetColor( VMA(1) ); + return 0; + case CG_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); + return 0; + case CG_R_MODELBOUNDS: + re.ModelBounds( args[1], VMA(2), VMA(3) ); + return 0; + case CG_R_LERPTAG: + return re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); + case CG_GETGLCONFIG: + CL_GetGlconfig( VMA(1) ); + return 0; + case CG_GETGAMESTATE: + CL_GetGameState( VMA(1) ); + return 0; + case CG_GETCURRENTSNAPSHOTNUMBER: + CL_GetCurrentSnapshotNumber( VMA(1), VMA(2) ); + return 0; + case CG_GETSNAPSHOT: + return CL_GetSnapshot( args[1], VMA(2) ); + case CG_GETSERVERCOMMAND: + return CL_GetServerCommand( args[1] ); + case CG_GETCURRENTCMDNUMBER: + return CL_GetCurrentCmdNumber(); + case CG_GETUSERCMD: + return CL_GetUserCmd( args[1], VMA(2) ); + case CG_SETUSERCMDVALUE: + CL_SetUserCmdValue( args[1], VMF(2) ); + return 0; + case CG_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + case CG_KEY_ISDOWN: + return Key_IsDown( args[1] ); + case CG_KEY_GETCATCHER: + return Key_GetCatcher(); + case CG_KEY_SETCATCHER: + // Don't allow the cgame module to close the console + Key_SetCatcher( args[1] | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ); + return 0; + case CG_KEY_GETKEY: + return Key_GetKey( VMA(1) ); + +#if 0 + + case CG_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + case CG_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + case CG_STRNCPY: + strncpy( VMA(1), VMA(2), args[3] ); + return args[1]; + case CG_SIN: + return FloatAsInt( sin( VMF(1) ) ); + case CG_COS: + return FloatAsInt( cos( VMF(1) ) ); + case CG_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + case CG_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + case CG_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + case CG_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + case CG_ACOS: + return FloatAsInt( Q_acos( VMF(1) ) ); + +#endif + + case CG_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( VMA(1) ); + case CG_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( VMA(1) ); + case CG_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case CG_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); + case CG_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); + + case CG_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + + case CG_REAL_TIME: + return Com_RealTime( VMA(1) ); + case CG_SNAPVECTOR: + Q_SnapVector(VMA(1)); + return 0; + + case CG_CIN_PLAYCINEMATIC: + return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); + + case CG_CIN_STOPCINEMATIC: + return CIN_StopCinematic(args[1]); + + case CG_CIN_RUNCINEMATIC: + return CIN_RunCinematic(args[1]); + + case CG_CIN_DRAWCINEMATIC: + CIN_DrawCinematic(args[1]); + return 0; + + case CG_CIN_SETEXTENTS: + CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); + return 0; + + case CG_R_REMAP_SHADER: + re.RemapShader( VMA(1), VMA(2), VMA(3) ); + return 0; + +/* + case CG_LOADCAMERA: + return loadCamera(VMA(1)); + + case CG_STARTCAMERA: + startCamera(args[1]); + return 0; + + case CG_GETCAMERAINFO: + return getCameraInfo(args[1], VMA(2), VMA(3)); +*/ + case CG_GET_ENTITY_TOKEN: + return re.GetEntityToken( VMA(1), args[2] ); + case CG_R_INPVS: + return re.inPVS( VMA(1), VMA(2) ); + + default: + assert(0); + Com_Error( ERR_DROP, "Bad cgame system trap: %ld", (long int) args[0] ); + } + return 0; +} + + +/* +==================== +CL_InitCGame + +Should only be called by CL_StartHunkUsers +==================== +*/ +void CL_InitCGame( void ) { + const char *info; + const char *mapname; + int t1, t2; + vmInterpret_t interpret; + + t1 = Sys_Milliseconds(); + + // put away the console + Con_Close(); + + // find the current mapname + info = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SERVERINFO ]; + mapname = Info_ValueForKey( info, "mapname" ); + Com_sprintf( cl.mapname, sizeof( cl.mapname ), "maps/%s.bsp", mapname ); + + // load the dll or bytecode + if ( cl_connectedToPureServer != 0 ) { + // if sv_pure is set we only allow qvms to be loaded + interpret = VMI_COMPILED; + } + else { + interpret = Cvar_VariableValue( "vm_cgame" ); + } + cgvm = VM_Create( "cgame", CL_CgameSystemCalls, interpret ); + if ( !cgvm ) { + Com_Error( ERR_DROP, "VM_Create on cgame failed" ); + } + clc.state = CA_LOADING; + + // init for this gamestate + // use the lastExecutedServerCommand instead of the serverCommandSequence + // otherwise server commands sent just before a gamestate are dropped + VM_Call( cgvm, CG_INIT, clc.serverMessageSequence, clc.lastExecutedServerCommand, clc.clientNum ); + + // reset any CVAR_CHEAT cvars registered by cgame + if ( !clc.demoplaying && !cl_connectedToCheatServer ) + Cvar_SetCheatState(); + + // we will send a usercmd this frame, which + // will cause the server to send us the first snapshot + clc.state = CA_PRIMED; + + t2 = Sys_Milliseconds(); + + Com_Printf( "CL_InitCGame: %5.2f seconds\n", (t2-t1)/1000.0 ); + + // have the renderer touch all its images, so they are present + // on the card even if the driver does deferred loading + re.EndRegistration(); + + // make sure everything is paged in + if (!Sys_LowPhysicalMemory()) { + Com_TouchMemory(); + } + + // clear anything that got printed + Con_ClearNotify (); +} + + +/* +==================== +CL_GameCommand + +See if the current console command is claimed by the cgame +==================== +*/ +qboolean CL_GameCommand( void ) { + if ( !cgvm ) { + return qfalse; + } + + return VM_Call( cgvm, CG_CONSOLE_COMMAND ); +} + + + +/* +===================== +CL_CGameRendering +===================== +*/ +void CL_CGameRendering( stereoFrame_t stereo ) { + VM_Call( cgvm, CG_DRAW_ACTIVE_FRAME, cl.serverTime, stereo, clc.demoplaying ); + VM_Debug( 0 ); +} + + +/* +================= +CL_AdjustTimeDelta + +Adjust the clients view of server time. + +We attempt to have cl.serverTime exactly equal the server's view +of time plus the timeNudge, but with variable latencies over +the internet it will often need to drift a bit to match conditions. + +Our ideal time would be to have the adjusted time approach, but not pass, +the very latest snapshot. + +Adjustments are only made when a new snapshot arrives with a rational +latency, which keeps the adjustment process framerate independent and +prevents massive overadjustment during times of significant packet loss +or bursted delayed packets. +================= +*/ + +#define RESET_TIME 500 + +void CL_AdjustTimeDelta( void ) { + int newDelta; + int deltaDelta; + + cl.newSnapshots = qfalse; + + // the delta never drifts when replaying a demo + if ( clc.demoplaying ) { + return; + } + + newDelta = cl.snap.serverTime - cls.realtime; + deltaDelta = abs( newDelta - cl.serverTimeDelta ); + + if ( deltaDelta > RESET_TIME ) { + cl.serverTimeDelta = newDelta; + cl.oldServerTime = cl.snap.serverTime; // FIXME: is this a problem for cgame? + cl.serverTime = cl.snap.serverTime; + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + } else if ( deltaDelta > 100 ) { + // fast adjust, cut the difference in half + if ( cl_showTimeDelta->integer ) { + Com_Printf( " " ); + } + cl.serverTimeDelta = ( cl.serverTimeDelta + newDelta ) >> 1; + } else { + // slow drift adjust, only move 1 or 2 msec + + // if any of the frames between this and the previous snapshot + // had to be extrapolated, nudge our sense of time back a little + // the granularity of +1 / -2 is too high for timescale modified frametimes + if ( com_timescale->value == 0 || com_timescale->value == 1 ) { + if ( cl.extrapolatedSnapshot ) { + cl.extrapolatedSnapshot = qfalse; + cl.serverTimeDelta -= 2; + } else { + // otherwise, move our sense of time forward to minimize total latency + cl.serverTimeDelta++; + } + } + } + + if ( cl_showTimeDelta->integer ) { + Com_Printf( "%i ", cl.serverTimeDelta ); + } +} + + +/* +================== +CL_FirstSnapshot +================== +*/ +void CL_FirstSnapshot( void ) { + // ignore snapshots that don't have entities + if ( cl.snap.snapFlags & SNAPFLAG_NOT_ACTIVE ) { + return; + } + clc.state = CA_ACTIVE; + + // set the timedelta so we are exactly on this first frame + cl.serverTimeDelta = cl.snap.serverTime - cls.realtime; + cl.oldServerTime = cl.snap.serverTime; + + clc.timeDemoBaseTime = cl.snap.serverTime; + + // if this is the first frame of active play, + // execute the contents of activeAction now + // this is to allow scripting a timedemo to start right + // after loading + if ( cl_activeAction->string[0] ) { + Cbuf_AddText( cl_activeAction->string ); + Cvar_Set( "activeAction", "" ); + } + +#ifdef USE_MUMBLE + if ((cl_useMumble->integer) && !mumble_islinked()) { + int ret = mumble_link(CLIENT_WINDOW_TITLE); + Com_Printf("Mumble: Linking to Mumble application %s\n", ret==0?"ok":"failed"); + } +#endif + +#ifdef USE_VOIP + if (!clc.speexInitialized) { + int i; + speex_bits_init(&clc.speexEncoderBits); + speex_bits_reset(&clc.speexEncoderBits); + + clc.speexEncoder = speex_encoder_init(&speex_nb_mode); + + speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_FRAME_SIZE, + &clc.speexFrameSize); + speex_encoder_ctl(clc.speexEncoder, SPEEX_GET_SAMPLING_RATE, + &clc.speexSampleRate); + + clc.speexPreprocessor = speex_preprocess_state_init(clc.speexFrameSize, + clc.speexSampleRate); + + i = 1; + speex_preprocess_ctl(clc.speexPreprocessor, + SPEEX_PREPROCESS_SET_DENOISE, &i); + + i = 1; + speex_preprocess_ctl(clc.speexPreprocessor, + SPEEX_PREPROCESS_SET_AGC, &i); + + for (i = 0; i < MAX_CLIENTS; i++) { + speex_bits_init(&clc.speexDecoderBits[i]); + speex_bits_reset(&clc.speexDecoderBits[i]); + clc.speexDecoder[i] = speex_decoder_init(&speex_nb_mode); + clc.voipIgnore[i] = qfalse; + clc.voipGain[i] = 1.0f; + } + clc.speexInitialized = qtrue; + clc.voipMuteAll = qfalse; + Cmd_AddCommand ("voip", CL_Voip_f); + Cvar_Set("cl_voipSendTarget", "spatial"); + Com_Memset(clc.voipTargets, ~0, sizeof(clc.voipTargets)); + } +#endif +} + +/* +================== +CL_SetCGameTime +================== +*/ +void CL_SetCGameTime( void ) { + // getting a valid frame message ends the connection process + if ( clc.state != CA_ACTIVE ) { + if ( clc.state != CA_PRIMED ) { + return; + } + if ( clc.demoplaying ) { + // we shouldn't get the first snapshot on the same frame + // as the gamestate, because it causes a bad time skip + if ( !clc.firstDemoFrameSkipped ) { + clc.firstDemoFrameSkipped = qtrue; + return; + } + CL_ReadDemoMessage(); + } + if ( cl.newSnapshots ) { + cl.newSnapshots = qfalse; + CL_FirstSnapshot(); + } + if ( clc.state != CA_ACTIVE ) { + return; + } + } + + // if we have gotten to this point, cl.snap is guaranteed to be valid + if ( !cl.snap.valid ) { + Com_Error( ERR_DROP, "CL_SetCGameTime: !cl.snap.valid" ); + } + + // allow pause in single player + if ( sv_paused->integer && CL_CheckPaused() && com_sv_running->integer ) { + // paused + return; + } + + if ( cl.snap.serverTime < cl.oldFrameServerTime ) { + Com_Error( ERR_DROP, "cl.snap.serverTime < cl.oldFrameServerTime" ); + } + cl.oldFrameServerTime = cl.snap.serverTime; + + + // get our current view of time + + if ( clc.demoplaying && cl_freezeDemo->integer ) { + // cl_freezeDemo is used to lock a demo in place for single frame advances + + } else { + // cl_timeNudge is a user adjustable cvar that allows more + // or less latency to be added in the interest of better + // smoothness or better responsiveness. + int tn; + + tn = cl_timeNudge->integer; + if (tn<-30) { + tn = -30; + } else if (tn>30) { + tn = 30; + } + + cl.serverTime = cls.realtime + cl.serverTimeDelta - tn; + + // guarantee that time will never flow backwards, even if + // serverTimeDelta made an adjustment or cl_timeNudge was changed + if ( cl.serverTime < cl.oldServerTime ) { + cl.serverTime = cl.oldServerTime; + } + cl.oldServerTime = cl.serverTime; + + // note if we are almost past the latest frame (without timeNudge), + // so we will try and adjust back a bit when the next snapshot arrives + if ( cls.realtime + cl.serverTimeDelta >= cl.snap.serverTime - 5 ) { + cl.extrapolatedSnapshot = qtrue; + } + } + + // if we have gotten new snapshots, drift serverTimeDelta + // don't do this every frame, or a period of packet loss would + // make a huge adjustment + if ( cl.newSnapshots ) { + CL_AdjustTimeDelta(); + } + + if ( !clc.demoplaying ) { + return; + } + + // if we are playing a demo back, we can just keep reading + // messages from the demo file until the cgame definately + // has valid snapshots to interpolate between + + // a timedemo will always use a deterministic set of time samples + // no matter what speed machine it is run on, + // while a normal demo may have different time samples + // each time it is played back + if ( cl_timedemo->integer ) { + int now = Sys_Milliseconds( ); + int frameDuration; + + if (!clc.timeDemoStart) { + clc.timeDemoStart = clc.timeDemoLastFrame = now; + clc.timeDemoMinDuration = INT_MAX; + clc.timeDemoMaxDuration = 0; + } + + frameDuration = now - clc.timeDemoLastFrame; + clc.timeDemoLastFrame = now; + + // Ignore the first measurement as it'll always be 0 + if( clc.timeDemoFrames > 0 ) + { + if( frameDuration > clc.timeDemoMaxDuration ) + clc.timeDemoMaxDuration = frameDuration; + + if( frameDuration < clc.timeDemoMinDuration ) + clc.timeDemoMinDuration = frameDuration; + + // 255 ms = about 4fps + if( frameDuration > UCHAR_MAX ) + frameDuration = UCHAR_MAX; + + clc.timeDemoDurations[ ( clc.timeDemoFrames - 1 ) % + MAX_TIMEDEMO_DURATIONS ] = frameDuration; + } + + clc.timeDemoFrames++; + cl.serverTime = clc.timeDemoBaseTime + clc.timeDemoFrames * 50; + } + + while ( cl.serverTime >= cl.snap.serverTime ) { + // feed another messag, which should change + // the contents of cl.snap + CL_ReadDemoMessage(); + if ( clc.state != CA_ACTIVE ) { + return; // end of demo + } + } + +} + + + diff --git a/code/client/cl_cin.c b/code/client/cl_cin.c new file mode 100644 index 0000000..2ad348d --- /dev/null +++ b/code/client/cl_cin.c @@ -0,0 +1,1712 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: cl_cin.c + * + * desc: video and cinematic playback + * + * $Archive: /MissionPack/code/client/cl_cin.c $ + * + * cl_glconfig.hwtype trtypes 3dfx/ragepro need 256x256 + * + *****************************************************************************/ + +#include "client.h" +#include "snd_local.h" + +#define MAXSIZE 8 +#define MINSIZE 4 + +#define DEFAULT_CIN_WIDTH 512 +#define DEFAULT_CIN_HEIGHT 512 + +#define ROQ_QUAD 0x1000 +#define ROQ_QUAD_INFO 0x1001 +#define ROQ_CODEBOOK 0x1002 +#define ROQ_QUAD_VQ 0x1011 +#define ROQ_QUAD_JPEG 0x1012 +#define ROQ_QUAD_HANG 0x1013 +#define ROQ_PACKET 0x1030 +#define ZA_SOUND_MONO 0x1020 +#define ZA_SOUND_STEREO 0x1021 + +#define MAX_VIDEO_HANDLES 16 + + +static void RoQ_init( void ); + +/****************************************************************************** +* +* Class: trFMV +* +* Description: RoQ/RnR manipulation routines +* not entirely complete for first run +* +******************************************************************************/ + +static long ROQ_YY_tab[256]; +static long ROQ_UB_tab[256]; +static long ROQ_UG_tab[256]; +static long ROQ_VG_tab[256]; +static long ROQ_VR_tab[256]; +static unsigned short vq2[256*16*4]; +static unsigned short vq4[256*64*4]; +static unsigned short vq8[256*256*4]; + + +typedef struct { + byte linbuf[DEFAULT_CIN_WIDTH*DEFAULT_CIN_HEIGHT*4*2]; + byte file[65536]; + short sqrTable[256]; + + int mcomp[256]; + byte *qStatus[2][32768]; + + long oldXOff, oldYOff, oldysize, oldxsize; + + int currentHandle; +} cinematics_t; + +typedef struct { + char fileName[MAX_OSPATH]; + int CIN_WIDTH, CIN_HEIGHT; + int xpos, ypos, width, height; + qboolean looping, holdAtEnd, dirty, alterGameState, silent, shader; + fileHandle_t iFile; + e_status status; + unsigned int startTime; + unsigned int lastTime; + long tfps; + long RoQPlayed; + long ROQSize; + unsigned int RoQFrameSize; + long onQuad; + long numQuads; + long samplesPerLine; + unsigned int roq_id; + long screenDelta; + + void ( *VQ0)(byte *status, void *qdata ); + void ( *VQ1)(byte *status, void *qdata ); + void ( *VQNormal)(byte *status, void *qdata ); + void ( *VQBuffer)(byte *status, void *qdata ); + + long samplesPerPixel; // defaults to 2 + byte* gray; + unsigned int xsize, ysize, maxsize, minsize; + + qboolean half, smootheddouble, inMemory; + long normalBuffer0; + long roq_flags; + long roqF0; + long roqF1; + long t[2]; + long roqFPS; + int playonwalls; + byte* buf; + long drawX, drawY; +} cin_cache; + +static cinematics_t cin; +static cin_cache cinTable[MAX_VIDEO_HANDLES]; +static int currentHandle = -1; +static int CL_handle = -1; + +extern int s_soundtime; // sample PAIRS + + +void CIN_CloseAllVideos(void) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if (cinTable[i].fileName[0] != 0 ) { + CIN_StopCinematic(i); + } + } +} + + +static int CIN_HandleForVideo(void) { + int i; + + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if ( cinTable[i].fileName[0] == 0 ) { + return i; + } + } + Com_Error( ERR_DROP, "CIN_HandleForVideo: none free" ); + return -1; +} + + +extern int CL_ScaledMilliseconds(void); + +//----------------------------------------------------------------------------- +// RllSetupTable +// +// Allocates and initializes the square table. +// +// Parameters: None +// +// Returns: Nothing +//----------------------------------------------------------------------------- +static void RllSetupTable( void ) +{ + int z; + + for (z=0;z<128;z++) { + cin.sqrTable[z] = (short)(z*z); + cin.sqrTable[z+128] = (short)(-cin.sqrTable[z]); + } +} + + + +//----------------------------------------------------------------------------- +// RllDecodeMonoToMono +// +// Decode mono source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of shorts of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput ,unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z=0;z buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/4 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeMonoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput,unsigned short flag) +{ + unsigned int z; + int prev; + + if (signedOutput) + prev = flag - 0x8000; + else + prev = flag; + + for (z = 0; z < size; z++) { + prev = (short)(prev + cin.sqrTable[from[z]]); + to[z*2+0] = to[z*2+1] = (short)(prev); + } + + return size; // * 2 * sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToStereo +// +// Decode stereo source data into a stereo buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= 1/2 # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToStereo(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) +{ + unsigned int z; + unsigned char *zz = from; + int prevL, prevR; + + if (signedOutput) { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) - 0x8000; + } else { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z=0;z>1); //*sizeof(short)); +} + + +//----------------------------------------------------------------------------- +// RllDecodeStereoToMono +// +// Decode stereo source data into a mono buffer. +// +// Parameters: from -> buffer holding encoded data +// to -> buffer to hold decoded data +// size = number of bytes of input (= # of bytes of output) +// signedOutput = 0 for unsigned output, non-zero for signed output +// flag = flags from asset header +// +// Returns: Number of samples placed in output buffer +//----------------------------------------------------------------------------- +long RllDecodeStereoToMono(unsigned char *from,short *to,unsigned int size,char signedOutput, unsigned short flag) +{ + unsigned int z; + int prevL,prevR; + + if (signedOutput) { + prevL = (flag & 0xff00) - 0x8000; + prevR = ((flag & 0x00ff) << 8) -0x8000; + } else { + prevL = flag & 0xff00; + prevR = (flag & 0x00ff) << 8; + } + + for (z=0;z> 2) ); + } +} + +#define VQ2TO4(a,b,c,d) { \ + *c++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *c++ = a[1]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *c++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *c++ = b[1]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + *d++ = a[0]; \ + *d++ = a[0]; \ + *d++ = a[1]; \ + *d++ = a[1]; \ + *d++ = b[0]; \ + *d++ = b[0]; \ + *d++ = b[1]; \ + *d++ = b[1]; \ + a += 2; b += 2; } + +#define VQ2TO2(a,b,c,d) { \ + *c++ = *a; \ + *d++ = *a; \ + *d++ = *a; \ + *c++ = *b; \ + *d++ = *b; \ + *d++ = *b; \ + *d++ = *a; \ + *d++ = *a; \ + *d++ = *b; \ + *d++ = *b; \ + a++; b++; } + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static unsigned short yuv_to_rgb( long y, long u, long v ) +{ + long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 9; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 8; + b = (YY + ROQ_UB_tab[u]) >> 9; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 31) r = 31; if (g > 63) g = 63; if (b > 31) b = 31; + + return (unsigned short)((r<<11)+(g<<5)+(b)); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +static unsigned int yuv_to_rgb24( long y, long u, long v ) +{ + long r,g,b,YY = (long)(ROQ_YY_tab[(y)]); + + r = (YY + ROQ_VR_tab[v]) >> 6; + g = (YY + ROQ_UG_tab[u] + ROQ_VG_tab[v]) >> 6; + b = (YY + ROQ_UB_tab[u]) >> 6; + + if (r<0) r = 0; if (g<0) g = 0; if (b<0) b = 0; + if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; + + return LittleLong ((r)|(g<<8)|(b<<16)|(255<<24)); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void decodeCodeBook( byte *input, unsigned short roq_flags ) +{ + long i, j, two, four; + unsigned short *aptr, *bptr, *cptr, *dptr; + long y0,y1,y2,y3,cr,cb; + byte *bbptr, *baptr, *bcptr, *bdptr; + union { + unsigned int *i; + unsigned short *s; + } iaptr, ibptr, icptr, idptr; + + if (!roq_flags) { + two = four = 256; + } else { + two = roq_flags>>8; + if (!two) two = 256; + four = roq_flags&0xff; + } + + four *= 2; + + bptr = (unsigned short *)vq2; + + if (!cinTable[currentHandle].half) { + if (!cinTable[currentHandle].smootheddouble) { +// +// normal height +// + if (cinTable[currentHandle].samplesPerPixel==2) { + for(i=0;i cinTable[currentHandle].CIN_WIDTH) bigx = cinTable[currentHandle].CIN_WIDTH; + if (bigy > cinTable[currentHandle].CIN_HEIGHT) bigy = cinTable[currentHandle].CIN_HEIGHT; + + if ( (startX >= lowx) && (startX+quadSize) <= (bigx) && (startY+quadSize) <= (bigy) && (startY >= lowy) && quadSize <= MAXSIZE) { + useY = startY; + scroff = cin.linbuf + (useY+((cinTable[currentHandle].CIN_HEIGHT-bigy)>>1)+yOff)*(cinTable[currentHandle].samplesPerLine) + (((startX+xOff))*cinTable[currentHandle].samplesPerPixel); + + cin.qStatus[0][cinTable[currentHandle].onQuad ] = scroff; + cin.qStatus[1][cinTable[currentHandle].onQuad++] = scroff+offset; + } + + if ( quadSize != MINSIZE ) { + quadSize >>= 1; + recurseQuad( startX, startY , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY , quadSize, xOff, yOff ); + recurseQuad( startX, startY+quadSize , quadSize, xOff, yOff ); + recurseQuad( startX+quadSize, startY+quadSize , quadSize, xOff, yOff ); + } +} + + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void setupQuad( long xOff, long yOff ) +{ + long numQuadCels, i,x,y; + byte *temp; + + if (xOff == cin.oldXOff && yOff == cin.oldYOff && cinTable[currentHandle].ysize == cin.oldysize && cinTable[currentHandle].xsize == cin.oldxsize) { + return; + } + + cin.oldXOff = xOff; + cin.oldYOff = yOff; + cin.oldysize = cinTable[currentHandle].ysize; + cin.oldxsize = cinTable[currentHandle].xsize; + + numQuadCels = (cinTable[currentHandle].xsize*cinTable[currentHandle].ysize) / (16); + numQuadCels += numQuadCels/4; + numQuadCels += 64; // for overflow + + cinTable[currentHandle].onQuad = 0; + + for(y=0;y<(long)cinTable[currentHandle].ysize;y+=16) + for(x=0;x<(long)cinTable[currentHandle].xsize;x+=16) + recurseQuad( x, y, 16, xOff, yOff ); + + temp = NULL; + + for(i=(numQuadCels-64);i256) { + cinTable[currentHandle].drawX = 256; + } + if (cinTable[currentHandle].drawY>256) { + cinTable[currentHandle].drawY = 256; + } + if (cinTable[currentHandle].CIN_WIDTH != 256 || cinTable[currentHandle].CIN_HEIGHT != 256) { + Com_Printf("HACK: approxmimating cinematic for Rage Pro or Voodoo\n"); + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQPrepMcomp( long xoff, long yoff ) +{ + long i, j, x, y, temp, temp2; + + i=cinTable[currentHandle].samplesPerLine; j=cinTable[currentHandle].samplesPerPixel; + if ( cinTable[currentHandle].xsize == (cinTable[currentHandle].ysize*4) && !cinTable[currentHandle].half ) { j = j+j; i = i+i; } + + for(y=0;y<16;y++) { + temp2 = (y+yoff-8)*i; + for(x=0;x<16;x++) { + temp = (x+xoff-8)*j; + cin.mcomp[(x*16)+y] = cinTable[currentHandle].normalBuffer0-(temp2+temp); + } + } +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void initRoQ( void ) +{ + if (currentHandle < 0) return; + + cinTable[currentHandle].VQNormal = (void (*)(byte *, void *))blitVQQuad32fs; + cinTable[currentHandle].VQBuffer = (void (*)(byte *, void *))blitVQQuad32fs; + cinTable[currentHandle].samplesPerPixel = 4; + ROQ_GenYUVTables(); + RllSetupTable(); +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ +/* +static byte* RoQFetchInterlaced( byte *source ) { + int x, *src, *dst; + + if (currentHandle < 0) return NULL; + + src = (int *)source; + dst = (int *)cinTable[currentHandle].buf2; + + for(x=0;x<256*256;x++) { + *dst = *src; + dst++; src += 2; + } + return cinTable[currentHandle].buf2; +} +*/ +static void RoQReset( void ) { + + if (currentHandle < 0) return; + + FS_FCloseFile( cinTable[currentHandle].iFile ); + FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); + // let the background thread start reading ahead + FS_Read (cin.file, 16, cinTable[currentHandle].iFile); + RoQ_init(); + cinTable[currentHandle].status = FMV_LOOPED; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQInterrupt(void) +{ + byte *framedata; + short sbuf[32768]; + int ssize; + + if (currentHandle < 0) return; + + FS_Read( cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile ); + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if (cinTable[currentHandle].holdAtEnd==qfalse) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata = cin.file; +// +// new frame is ready +// +redump: + switch(cinTable[currentHandle].roq_id) + { + case ROQ_QUAD_VQ: + if ((cinTable[currentHandle].numQuads&1)) { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[1]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ1( (byte *)cin.qStatus[1], framedata); + cinTable[currentHandle].buf = cin.linbuf + cinTable[currentHandle].screenDelta; + } else { + cinTable[currentHandle].normalBuffer0 = cinTable[currentHandle].t[0]; + RoQPrepMcomp( cinTable[currentHandle].roqF0, cinTable[currentHandle].roqF1 ); + cinTable[currentHandle].VQ0( (byte *)cin.qStatus[0], framedata ); + cinTable[currentHandle].buf = cin.linbuf; + } + if (cinTable[currentHandle].numQuads == 0) { // first frame + Com_Memcpy(cin.linbuf+cinTable[currentHandle].screenDelta, cin.linbuf, cinTable[currentHandle].samplesPerLine*cinTable[currentHandle].ysize); + } + cinTable[currentHandle].numQuads++; + cinTable[currentHandle].dirty = qtrue; + break; + case ROQ_CODEBOOK: + decodeCodeBook( framedata, (unsigned short)cinTable[currentHandle].roq_flags ); + break; + case ZA_SOUND_MONO: + if (!cinTable[currentHandle].silent) { + ssize = RllDecodeMonoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples(0, ssize, 22050, 2, 1, (byte *)sbuf, 1.0f, -1); + } + break; + case ZA_SOUND_STEREO: + if (!cinTable[currentHandle].silent) { + if (cinTable[currentHandle].numQuads == -1) { + S_Update(); + s_rawend[0] = s_soundtime; + } + ssize = RllDecodeStereoToStereo( framedata, sbuf, cinTable[currentHandle].RoQFrameSize, 0, (unsigned short)cinTable[currentHandle].roq_flags); + S_RawSamples(0, ssize, 22050, 2, 2, (byte *)sbuf, 1.0f, -1); + } + break; + case ROQ_QUAD_INFO: + if (cinTable[currentHandle].numQuads == -1) { + readQuadInfo( framedata ); + setupQuad( 0, 0 ); + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; + } + if (cinTable[currentHandle].numQuads != 1) cinTable[currentHandle].numQuads = 0; + break; + case ROQ_PACKET: + cinTable[currentHandle].inMemory = cinTable[currentHandle].roq_flags; + cinTable[currentHandle].RoQFrameSize = 0; // for header + break; + case ROQ_QUAD_HANG: + cinTable[currentHandle].RoQFrameSize = 0; + break; + case ROQ_QUAD_JPEG: + break; + default: + cinTable[currentHandle].status = FMV_EOF; + break; + } +// +// read in next frame data +// + if ( cinTable[currentHandle].RoQPlayed >= cinTable[currentHandle].ROQSize ) { + if (cinTable[currentHandle].holdAtEnd==qfalse) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + cinTable[currentHandle].status = FMV_EOF; + } + } else { + cinTable[currentHandle].status = FMV_IDLE; + } + return; + } + + framedata += cinTable[currentHandle].RoQFrameSize; + cinTable[currentHandle].roq_id = framedata[0] + framedata[1]*256; + cinTable[currentHandle].RoQFrameSize = framedata[2] + framedata[3]*256 + framedata[4]*65536; + cinTable[currentHandle].roq_flags = framedata[6] + framedata[7]*256; + cinTable[currentHandle].roqF0 = (signed char)framedata[7]; + cinTable[currentHandle].roqF1 = (signed char)framedata[6]; + + if (cinTable[currentHandle].RoQFrameSize>65536||cinTable[currentHandle].roq_id==0x1084) { + Com_DPrintf("roq_size>65536||roq_id==0x1084\n"); + cinTable[currentHandle].status = FMV_EOF; + if (cinTable[currentHandle].looping) { + RoQReset(); + } + return; + } + if (cinTable[currentHandle].inMemory && (cinTable[currentHandle].status != FMV_EOF)) { cinTable[currentHandle].inMemory--; framedata += 8; goto redump; } +// +// one more frame hits the dust +// +// assert(cinTable[currentHandle].RoQFrameSize <= 65536); +// r = FS_Read( cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile ); + cinTable[currentHandle].RoQPlayed += cinTable[currentHandle].RoQFrameSize+8; +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQ_init( void ) +{ + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].startTime = cinTable[currentHandle].lastTime = CL_ScaledMilliseconds()*com_timescale->value; + + cinTable[currentHandle].RoQPlayed = 24; + +/* get frame rate */ + cinTable[currentHandle].roqFPS = cin.file[ 6] + cin.file[ 7]*256; + + if (!cinTable[currentHandle].roqFPS) cinTable[currentHandle].roqFPS = 30; + + cinTable[currentHandle].numQuads = -1; + + cinTable[currentHandle].roq_id = cin.file[ 8] + cin.file[ 9]*256; + cinTable[currentHandle].RoQFrameSize = cin.file[10] + cin.file[11]*256 + cin.file[12]*65536; + cinTable[currentHandle].roq_flags = cin.file[14] + cin.file[15]*256; + + if (cinTable[currentHandle].RoQFrameSize > 65536 || !cinTable[currentHandle].RoQFrameSize) { + return; + } + +} + +/****************************************************************************** +* +* Function: +* +* Description: +* +******************************************************************************/ + +static void RoQShutdown( void ) { + const char *s; + + if (!cinTable[currentHandle].buf) { + return; + } + + if ( cinTable[currentHandle].status == FMV_IDLE ) { + return; + } + Com_DPrintf("finished cinematic\n"); + cinTable[currentHandle].status = FMV_IDLE; + + if (cinTable[currentHandle].iFile) { + FS_FCloseFile( cinTable[currentHandle].iFile ); + cinTable[currentHandle].iFile = 0; + } + + if (cinTable[currentHandle].alterGameState) { + clc.state = CA_DISCONNECTED; + // we can't just do a vstr nextmap, because + // if we are aborting the intro cinematic with + // a devmap command, nextmap would be valid by + // the time it was referenced + s = Cvar_VariableString( "nextmap" ); + if ( s[0] ) { + Cbuf_ExecuteText( EXEC_APPEND, va("%s\n", s) ); + Cvar_Set( "nextmap", "" ); + } + CL_handle = -1; + } + cinTable[currentHandle].fileName[0] = 0; + currentHandle = -1; +} + +/* +================== +SCR_StopCinematic +================== +*/ +e_status CIN_StopCinematic(int handle) { + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + currentHandle = handle; + + Com_DPrintf("trFMV::stop(), closing %s\n", cinTable[currentHandle].fileName); + + if (!cinTable[currentHandle].buf) { + return FMV_EOF; + } + + if (cinTable[currentHandle].alterGameState) { + if ( clc.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + cinTable[currentHandle].status = FMV_EOF; + RoQShutdown(); + + return FMV_EOF; +} + +/* +================== +SCR_RunCinematic + +Fetch and decompress the pending frame +================== +*/ + + +e_status CIN_RunCinematic (int handle) +{ + int start = 0; + int thisTime = 0; + + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return FMV_EOF; + + if (cin.currentHandle != handle) { + currentHandle = handle; + cin.currentHandle = currentHandle; + cinTable[currentHandle].status = FMV_EOF; + RoQReset(); + } + + if (cinTable[handle].playonwalls < -1) + { + return cinTable[handle].status; + } + + currentHandle = handle; + + if (cinTable[currentHandle].alterGameState) { + if ( clc.state != CA_CINEMATIC ) { + return cinTable[currentHandle].status; + } + } + + if (cinTable[currentHandle].status == FMV_IDLE) { + return cinTable[currentHandle].status; + } + + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + thisTime = CL_ScaledMilliseconds()*com_timescale->value; + if (cinTable[currentHandle].shader && (abs(thisTime - cinTable[currentHandle].lastTime))>100) { + cinTable[currentHandle].startTime += thisTime - cinTable[currentHandle].lastTime; + } + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) - cinTable[currentHandle].startTime)*3)/100); + + start = cinTable[currentHandle].startTime; + while( (cinTable[currentHandle].tfps != cinTable[currentHandle].numQuads) + && (cinTable[currentHandle].status == FMV_PLAY) ) + { + RoQInterrupt(); + if (start != cinTable[currentHandle].startTime) { + // we need to use CL_ScaledMilliseconds because of the smp mode calls from the renderer + cinTable[currentHandle].tfps = ((((CL_ScaledMilliseconds()*com_timescale->value) + - cinTable[currentHandle].startTime)*3)/100); + start = cinTable[currentHandle].startTime; + } + } + + cinTable[currentHandle].lastTime = thisTime; + + if (cinTable[currentHandle].status == FMV_LOOPED) { + cinTable[currentHandle].status = FMV_PLAY; + } + + if (cinTable[currentHandle].status == FMV_EOF) { + if (cinTable[currentHandle].looping) { + RoQReset(); + } else { + RoQShutdown(); + } + } + + return cinTable[currentHandle].status; +} + +/* +================== +CL_PlayCinematic + +================== +*/ +int CIN_PlayCinematic( const char *arg, int x, int y, int w, int h, int systemBits ) { + unsigned short RoQID; + char name[MAX_OSPATH]; + int i; + + if (strstr(arg, "/") == NULL && strstr(arg, "\\") == NULL) { + Com_sprintf (name, sizeof(name), "video/%s", arg); + } else { + Com_sprintf (name, sizeof(name), "%s", arg); + } + + COM_DefaultExtension( name, sizeof( name ), ".roq" ); + + if (!(systemBits & CIN_system)) { + for ( i = 0 ; i < MAX_VIDEO_HANDLES ; i++ ) { + if (!strcmp(cinTable[i].fileName, name) ) { + return i; + } + } + } + + Com_DPrintf("SCR_PlayCinematic( %s )\n", arg); + + Com_Memset(&cin, 0, sizeof(cinematics_t) ); + currentHandle = CIN_HandleForVideo(); + + cin.currentHandle = currentHandle; + + strcpy(cinTable[currentHandle].fileName, name); + + cinTable[currentHandle].ROQSize = 0; + cinTable[currentHandle].ROQSize = FS_FOpenFileRead (cinTable[currentHandle].fileName, &cinTable[currentHandle].iFile, qtrue); + + if (cinTable[currentHandle].ROQSize<=0) { + Com_DPrintf("play(%s), ROQSize<=0\n", arg); + cinTable[currentHandle].fileName[0] = 0; + return -1; + } + + CIN_SetExtents(currentHandle, x, y, w, h); + CIN_SetLooping(currentHandle, (systemBits & CIN_loop)!=0); + + cinTable[currentHandle].CIN_HEIGHT = DEFAULT_CIN_HEIGHT; + cinTable[currentHandle].CIN_WIDTH = DEFAULT_CIN_WIDTH; + cinTable[currentHandle].holdAtEnd = (systemBits & CIN_hold) != 0; + cinTable[currentHandle].alterGameState = (systemBits & CIN_system) != 0; + cinTable[currentHandle].playonwalls = 1; + cinTable[currentHandle].silent = (systemBits & CIN_silent) != 0; + cinTable[currentHandle].shader = (systemBits & CIN_shader) != 0; + + if (cinTable[currentHandle].alterGameState) { + // close the menu + if ( uivm ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + } else { + cinTable[currentHandle].playonwalls = cl_inGameVideo->integer; + } + + initRoQ(); + + FS_Read (cin.file, 16, cinTable[currentHandle].iFile); + + RoQID = (unsigned short)(cin.file[0]) + (unsigned short)(cin.file[1])*256; + if (RoQID == 0x1084) + { + RoQ_init(); +// FS_Read (cin.file, cinTable[currentHandle].RoQFrameSize+8, cinTable[currentHandle].iFile); + + cinTable[currentHandle].status = FMV_PLAY; + Com_DPrintf("trFMV::play(), playing %s\n", arg); + + if (cinTable[currentHandle].alterGameState) { + clc.state = CA_CINEMATIC; + } + + Con_Close(); + + s_rawend[0] = s_soundtime; + + return currentHandle; + } + Com_DPrintf("trFMV::play(), invalid RoQ ID\n"); + + RoQShutdown(); + return -1; +} + +void CIN_SetExtents (int handle, int x, int y, int w, int h) { + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].xpos = x; + cinTable[handle].ypos = y; + cinTable[handle].width = w; + cinTable[handle].height = h; + cinTable[handle].dirty = qtrue; +} + +void CIN_SetLooping(int handle, qboolean loop) { + if (handle < 0 || handle>= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + cinTable[handle].looping = loop; +} + +/* +================== +CIN_ResampleCinematic + +Resample cinematic to 256x256 and store in buf2 +================== +*/ +void CIN_ResampleCinematic(int handle, int *buf2) { + int ix, iy, *buf3, xm, ym, ll; + byte *buf; + + buf = cinTable[handle].buf; + + xm = cinTable[handle].CIN_WIDTH/256; + ym = cinTable[handle].CIN_HEIGHT/256; + ll = 8; + if (cinTable[handle].CIN_WIDTH==512) { + ll = 9; + } + + buf3 = (int*)buf; + if (xm==2 && ym==2) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy<256; iy++) { + iiy = iy<<12; + for (ix = 0; ix<2048; ix+=8) { + for(ic = ix;ic<(ix+4);ic++) { + *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic]+bc3[iiy+2048+ic]+bc3[iiy+2048+4+ic])>>2; + bc2++; + } + } + } + } else if (xm==2 && ym==1) { + byte *bc2, *bc3; + int ic, iiy; + + bc2 = (byte *)buf2; + bc3 = (byte *)buf3; + for (iy = 0; iy<256; iy++) { + iiy = iy<<11; + for (ix = 0; ix<2048; ix+=8) { + for(ic = ix;ic<(ix+4);ic++) { + *bc2=(bc3[iiy+ic]+bc3[iiy+4+ic])>>1; + bc2++; + } + } + } + } else { + for (iy = 0; iy<256; iy++) { + for (ix = 0; ix<256; ix++) { + buf2[(iy<<8)+ix] = buf3[((iy*ym)<= MAX_VIDEO_HANDLES || cinTable[handle].status == FMV_EOF) return; + + if (!cinTable[handle].buf) { + return; + } + + x = cinTable[handle].xpos; + y = cinTable[handle].ypos; + w = cinTable[handle].width; + h = cinTable[handle].height; + buf = cinTable[handle].buf; + SCR_AdjustFrom640( &x, &y, &w, &h ); + + if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) { + int *buf2; + + buf2 = Hunk_AllocateTempMemory( 256*256*4 ); + + CIN_ResampleCinematic(handle, buf2); + + re.DrawStretchRaw( x, y, w, h, 256, 256, (byte *)buf2, handle, qtrue); + cinTable[handle].dirty = qfalse; + Hunk_FreeTempMemory(buf2); + return; + } + + re.DrawStretchRaw( x, y, w, h, cinTable[handle].drawX, cinTable[handle].drawY, buf, handle, cinTable[handle].dirty); + cinTable[handle].dirty = qfalse; +} + +/* +==================== +CL_CompleteCinematicName +==================== +*/ +void CL_CompleteCinematicName( char *args, int argNum ) { + if( argNum == 2 ) { + Field_CompleteFilename( "video", ".roq", qfalse, qfalse ); + } +} + +void CL_PlayCinematic_f(void) { + char *arg, *s; + int bits = CIN_system; + + Com_DPrintf("CL_PlayCinematic_f\n"); + if (clc.state == CA_CINEMATIC) { + SCR_StopCinematic(); + } + + arg = Cmd_Argv( 1 ); + s = Cmd_Argv(2); + + if ((s && s[0] == '1') || Q_stricmp(arg,"demoend.roq")==0 || Q_stricmp(arg,"end.roq")==0) { + bits |= CIN_hold; + } + if (s && s[0] == '2') { + bits |= CIN_loop; + } + + S_StopAllSounds (); + + CL_handle = CIN_PlayCinematic( arg, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, bits ); + if (CL_handle >= 0) { + do { + SCR_RunCinematic(); + } while (cinTable[currentHandle].buf == NULL && cinTable[currentHandle].status == FMV_PLAY); // wait for first frame (load codebook and sound) + } +} + + +void SCR_DrawCinematic (void) { + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_DrawCinematic(CL_handle); + } +} + +void SCR_RunCinematic (void) +{ + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_RunCinematic(CL_handle); + } +} + +void SCR_StopCinematic(void) { + if (CL_handle >= 0 && CL_handle < MAX_VIDEO_HANDLES) { + CIN_StopCinematic(CL_handle); + S_StopAllSounds (); + CL_handle = -1; + } +} + +void CIN_UploadCinematic(int handle) { + if (handle >= 0 && handle < MAX_VIDEO_HANDLES) { + if (!cinTable[handle].buf) { + return; + } + if (cinTable[handle].playonwalls <= 0 && cinTable[handle].dirty) { + if (cinTable[handle].playonwalls == 0) { + cinTable[handle].playonwalls = -1; + } else { + if (cinTable[handle].playonwalls == -1) { + cinTable[handle].playonwalls = -2; + } else { + cinTable[handle].dirty = qfalse; + } + } + } + + // Resample the video if needed + if (cinTable[handle].dirty && (cinTable[handle].CIN_WIDTH != cinTable[handle].drawX || cinTable[handle].CIN_HEIGHT != cinTable[handle].drawY)) { + int *buf2; + + buf2 = Hunk_AllocateTempMemory( 256*256*4 ); + + CIN_ResampleCinematic(handle, buf2); + + re.UploadCinematic( cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, 256, 256, (byte *)buf2, handle, qtrue); + cinTable[handle].dirty = qfalse; + Hunk_FreeTempMemory(buf2); + } else { + // Upload video at normal resolution + re.UploadCinematic( cinTable[handle].CIN_WIDTH, cinTable[handle].CIN_HEIGHT, cinTable[handle].drawX, cinTable[handle].drawY, + cinTable[handle].buf, handle, cinTable[handle].dirty); + cinTable[handle].dirty = qfalse; + } + + if (cl_inGameVideo->integer == 0 && cinTable[handle].playonwalls == 1) { + cinTable[handle].playonwalls--; + } + else if (cl_inGameVideo->integer != 0 && cinTable[handle].playonwalls != 1) { + cinTable[handle].playonwalls = 1; + } + } +} + diff --git a/code/client/cl_console.c b/code/client/cl_console.c new file mode 100644 index 0000000..5477836 --- /dev/null +++ b/code/client/cl_console.c @@ -0,0 +1,839 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// console.c + +#include "client.h" + + +int g_console_field_width = 78; + + +#define NUM_CON_TIMES 4 + +#define CON_TEXTSIZE 32768 +typedef struct { + qboolean initialized; + + short text[CON_TEXTSIZE]; + int current; // line where next message will be printed + int x; // offset in current line for next print + int display; // bottom of console displays this line + + int linewidth; // characters across screen + int totallines; // total lines in console scrollback + + float xadjust; // for wide aspect screens + + float displayFrac; // aproaches finalFrac at scr_conspeed + float finalFrac; // 0.0 to 1.0 lines of console to display + + int vislines; // in scanlines + + int times[NUM_CON_TIMES]; // cls.realtime time the line was generated + // for transparent notify lines + vec4_t color; +} console_t; + +console_t con; + +cvar_t *con_speed; +cvar_t *con_allowChat; +cvar_t *con_background; +cvar_t *con_opacity; +cvar_t *con_height; + +cvar_t *notify_time; +cvar_t *notify_xoffset; + +#define DEFAULT_CONSOLE_WIDTH 78 + + +/* +================ +Con_ToggleConsole_f +================ +*/ +void Con_ToggleConsole_f (void) { + // Can't toggle the console when it's the only thing available + if ( clc.state == CA_DISCONNECTED && Key_GetCatcher( ) == KEYCATCH_CONSOLE ) { + return; + } + + Field_Clear( &g_consoleField ); + g_consoleField.widthInChars = g_console_field_width; + + Con_ClearNotify (); + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_CONSOLE ); +} + +/* +================ +Con_MessageMode_f +================ +*/ +void Con_MessageMode_f (void) { + chat_playerNum = -1; + chat_team = qfalse; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +================ +Con_MessageMode2_f +================ +*/ +void Con_MessageMode2_f (void) { + chat_playerNum = -1; + chat_team = qtrue; + Field_Clear( &chatField ); + chatField.widthInChars = 25; + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +================ +Con_MessageMode3_f +================ +*/ +void Con_MessageMode3_f (void) { + chat_playerNum = VM_Call( cgvm, CG_CROSSHAIR_PLAYER ); + if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { + chat_playerNum = -1; + return; + } + chat_team = qfalse; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +================ +Con_MessageMode4_f +================ +*/ +void Con_MessageMode4_f (void) { + chat_playerNum = VM_Call( cgvm, CG_LAST_ATTACKER ); + if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) { + chat_playerNum = -1; + return; + } + chat_team = qfalse; + Field_Clear( &chatField ); + chatField.widthInChars = 30; + Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE ); +} + +/* +================ +Con_Clear_f +================ +*/ +void Con_Clear_f (void) { + int i; + + for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) { + con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' '; + } + + Con_Bottom(); // go to end +} + + +/* +================ +Con_Dump_f + +Save the console contents out to a file +================ +*/ +void Con_Dump_f (void) +{ + int l, x, i; + short *line; + fileHandle_t f; + char buffer[1024]; + char filename[MAX_QPATH]; + + if (Cmd_Argc() != 2) + { + Com_Printf ("usage: condump \n"); + return; + } + + Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) ); + + COM_DefaultExtension( filename, sizeof( filename ), ".txt" ); + + Com_Printf ("Dumped console text to %s.\n", filename ); + + f = FS_FOpenFileWrite( filename ); + if (!f) + { + Com_Printf ("ERROR: couldn't open %s.\n", filename); + return; + } + + // skip empty lines + for (l = con.current - con.totallines + 1 ; l <= con.current ; l++) + { + line = con.text + (l%con.totallines)*con.linewidth; + for (x=0 ; x=0 ; x--) + { + if (buffer[x] == ' ') + buffer[x] = 0; + else + break; + } + strcat( buffer, "\n" ); + FS_Write(buffer, strlen(buffer), f); + } + + FS_FCloseFile( f ); +} + + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify( void ) { + int i; + + for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) { + con.times[i] = 0; + } +} + + + +/* +================ +Con_CheckResize + +If the line width has changed, reformat the buffer. +================ +*/ +void Con_CheckResize (void) +{ + int i, j, width, oldwidth, oldtotallines, numlines, numchars; + short tbuf[CON_TEXTSIZE]; + + width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2; + + if (width == con.linewidth) + return; + + if (width < 1) // video hasn't been initialized yet + { + width = DEFAULT_CONSOLE_WIDTH; + con.linewidth = width; + con.totallines = CON_TEXTSIZE / con.linewidth; + for(i=0; i= 0) + { + if (skipnotify) + con.times[con.current % NUM_CON_TIMES] = 0; + else + con.times[con.current % NUM_CON_TIMES] = cls.realtime; + } + + con.x = 0; + if (con.display == con.current) + con.display++; + con.current++; + for(i=0; iinteger ) { + return; + } + + if (!con.initialized) { + con.color[0] = + con.color[1] = + con.color[2] = + con.color[3] = 1.0f; + con.linewidth = -1; + Con_CheckResize (); + con.initialized = qtrue; + } + + color = ColorIndex(COLOR_WHITE); + + while ( (c = *((unsigned char *) txt)) != 0 ) { + if ( Q_IsColorString( txt ) ) { + color = ColorIndex( *(txt+1) ); + txt += 2; + continue; + } + + // count word length + for (l=0 ; l< con.linewidth ; l++) { + if ( txt[l] <= ' ') { + break; + } + + } + + // word wrap + if (l != con.linewidth && (con.x + l >= con.linewidth) ) { + Con_Linefeed(skipnotify); + + } + + txt++; + + switch (c) + { + case '\n': + Con_Linefeed (skipnotify); + break; + case '\r': + con.x = 0; + break; + default: // display character and advance + y = con.current % con.totallines; + con.text[y*con.linewidth+con.x] = (color << 8) | c; + con.x++; + if(con.x >= con.linewidth) + Con_Linefeed(skipnotify); + break; + } + } + + + // mark time for transparent overlay + if (con.current >= 0) { + // NERVE - SMF + if ( skipnotify ) { + prev = con.current % NUM_CON_TIMES - 1; + if ( prev < 0 ) + prev = NUM_CON_TIMES - 1; + con.times[prev] = 0; + } + else + // -NERVE - SMF + con.times[con.current % NUM_CON_TIMES] = cls.realtime; + } +} + + +/* +============================================================================== + +DRAWING + +============================================================================== +*/ + + +/* +================ +Con_DrawInput + +Draw the editline after a ] prompt +================ +*/ +void Con_DrawInput (void) { + int y; + + if ( clc.state != CA_DISCONNECTED && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) { + return; + } + + y = con.vislines - ( SMALLCHAR_HEIGHT * 2 ); + + re.SetColor( con.color ); + + SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' ); + + Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y, + SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue, qtrue ); +} + + +/* +================ +Con_DrawNotify + +Draws the last few lines of output transparently over the game top +================ +*/ +void Con_DrawNotify (void) +{ + int x, v; + short *text; + int i; + int time; + int skip; + int currentColor; + + currentColor = 7; + re.SetColor( g_color_table[currentColor] ); + + v = 0; + for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++) + { + if (i < 0) + continue; + time = con.times[i % NUM_CON_TIMES]; + if (time == 0) + continue; + time = cls.realtime - time; + if (time > notify_time->value*1000) + continue; + text = con.text + (i % con.totallines)*con.linewidth; + + if (cl.snap.ps.pm_type != PM_INTERMISSION && Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME) ) { + continue; + } + + for (x = 0 ; x < con.linewidth ; x++) { + if ( ( text[x] & 0xff ) == ' ' ) { + continue; + } + if ( ( (text[x]>>8)&7 ) != currentColor ) { + currentColor = (text[x]>>8)&7; + re.SetColor( g_color_table[currentColor] ); + } + SCR_DrawSmallChar( notify_xoffset->integer + con.xadjust + (x+1)*SMALLCHAR_WIDTH, v, text[x] & 0xff ); + } + + v += SMALLCHAR_HEIGHT; + } + + re.SetColor( NULL ); + + if (Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME) ) { + return; + } + + // draw the chat line + if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) + { + if (chat_team) + { + SCR_DrawBigString (8, v, "say_team:", 1.0f, qfalse ); + skip = 10; + } + else + { + SCR_DrawBigString (8, v, "say:", 1.0f, qfalse ); + skip = 5; + } + + Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, v, + SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue, qtrue ); + + v += BIGCHAR_HEIGHT; + } + +} + +/* +================ +Con_DrawSolidConsole + +Draws the console with the solid background +================ +*/ +void Con_DrawSolidConsole( float frac ) { + int i, x, y; + int rows; + short *text; + int row; + int lines; +// qhandle_t conShader; + int currentColor; + vec4_t color; + + lines = cls.glconfig.vidHeight * frac; + if (lines <= 0) + return; + + if (lines > cls.glconfig.vidHeight ) + lines = cls.glconfig.vidHeight; + + // on wide screens, we will center the text + con.xadjust = 0; + SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL ); + + // draw the background + y = frac * SCREEN_HEIGHT; + if ( y < 1 ) { + y = 0; + } + else { + if( con_background->integer ) { + SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader ); + } + else { + color[0] = 0; + color[1] = 0; + color[2] = 0; + color[3] = con_opacity->value; + SCR_FillRect( 0, 0, SCREEN_WIDTH, y, color ); + } + } + + color[0] = 0; + color[1] = 0.5f; + color[2] = 1; + color[3] = 1; + SCR_FillRect( 0, y, SCREEN_WIDTH, 2, color ); + + + // draw the version number + + re.SetColor( color ); + + i = strlen( Q3_VERSION ); + + for (x=0 ; x= con.totallines) { + // past scrollback wrap point + continue; + } + + text = con.text + (row % con.totallines)*con.linewidth; + + for (x=0 ; x>8)&7 ) != currentColor ) { + currentColor = (text[x]>>8)&7; + re.SetColor( g_color_table[currentColor] ); + } + SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff ); + } + } + + // draw the input prompt, user text, and cursor if desired + Con_DrawInput (); + + re.SetColor( NULL ); +} + + + +/* +================== +Con_DrawConsole +================== +*/ +void Con_DrawConsole( void ) { + // check for console width changes from a vid mode change + Con_CheckResize (); + + // if disconnected, render console full screen + if ( clc.state == CA_DISCONNECTED ) { + if ( !( Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME)) ) { + Con_DrawSolidConsole( 1.0 ); + return; + } + } + + if ( con.displayFrac ) { + Con_DrawSolidConsole( con.displayFrac ); + } else { + // draw notify lines + if ( clc.state == CA_ACTIVE ) { + Con_DrawNotify (); + } + } +} + +//================================================================ + +/* +================== +Con_RunConsole + +Scroll it up or down +================== +*/ +void Con_RunConsole (void) { + // decide on the destination height of the console + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) + con.finalFrac = con_height->value; // half screen + else + con.finalFrac = 0; // none visible + + // scroll towards the destination height + if (con.finalFrac < con.displayFrac) + { + con.displayFrac -= con_speed->value*cls.realFrametime*0.001; + if (con.finalFrac > con.displayFrac) + con.displayFrac = con.finalFrac; + + } + else if (con.finalFrac > con.displayFrac) + { + con.displayFrac += con_speed->value*cls.realFrametime*0.001; + if (con.finalFrac < con.displayFrac) + con.displayFrac = con.finalFrac; + } + +} + + +void Con_PageUp( void ) { + con.display -= 2; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_PageDown( void ) { + con.display += 2; + if (con.display > con.current) { + con.display = con.current; + } +} + +void Con_Top( void ) { + con.display = con.totallines; + if ( con.current - con.display >= con.totallines ) { + con.display = con.current - con.totallines + 1; + } +} + +void Con_Bottom( void ) { + con.display = con.current; +} + + +void Con_Close( void ) { + if ( !com_cl_running->integer ) { + return; + } + Field_Clear( &g_consoleField ); + Con_ClearNotify (); + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CONSOLE ); + con.finalFrac = 0; // none visible + con.displayFrac = 0; +} diff --git a/code/client/cl_curl.c b/code/client/cl_curl.c new file mode 100644 index 0000000..b850824 --- /dev/null +++ b/code/client/cl_curl.c @@ -0,0 +1,321 @@ +/* +=========================================================================== +Copyright (C) 2006 Tony J. White (tjw@tjw.org) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifdef USE_CURL +#include "client.h" +cvar_t *cl_cURLLib; + +#ifdef USE_CURL_DLOPEN +#include "../sys/sys_loadlib.h" + +char* (*qcurl_version)(void); + +CURL* (*qcurl_easy_init)(void); +CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...); +CURLcode (*qcurl_easy_perform)(CURL *curl); +void (*qcurl_easy_cleanup)(CURL *curl); +CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...); +CURL* (*qcurl_easy_duphandle)(CURL *curl); +void (*qcurl_easy_reset)(CURL *curl); +const char *(*qcurl_easy_strerror)(CURLcode); + +CURLM* (*qcurl_multi_init)(void); +CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, + CURL *curl_handle); +CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, + CURL *curl_handle); +CURLMcode (*qcurl_multi_fdset)(CURLM *multi_handle, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *exc_fd_set, + int *max_fd); +CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, + int *running_handles); +CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle); +CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, + int *msgs_in_queue); +const char *(*qcurl_multi_strerror)(CURLMcode); + +static void *cURLLib = NULL; + +/* +================= +GPA +================= +*/ +static void *GPA(char *str) +{ + void *rv; + + rv = Sys_LoadFunction(cURLLib, str); + if(!rv) + { + Com_Printf("Can't load symbol %s\n", str); + clc.cURLEnabled = qfalse; + return NULL; + } + else + { + Com_DPrintf("Loaded symbol %s (0x%p)\n", str, rv); + return rv; + } +} +#endif /* USE_CURL_DLOPEN */ + +/* +================= +CL_cURL_Init +================= +*/ +qboolean CL_cURL_Init() +{ +#ifdef USE_CURL_DLOPEN + if(cURLLib) + return qtrue; + + + Com_Printf("Loading \"%s\"...", cl_cURLLib->string); + if(!(cURLLib = Sys_LoadDll(cl_cURLLib->string, qtrue))) + { +#ifdef ALTERNATE_CURL_LIB + // On some linux distributions there is no libcurl.so.3, but only libcurl.so.4. That one works too. + if(!(cURLLib = Sys_LoadDll(ALTERNATE_CURL_LIB, qtrue))) +#endif + return qfalse; + } + + clc.cURLEnabled = qtrue; + + qcurl_version = GPA("curl_version"); + + qcurl_easy_init = GPA("curl_easy_init"); + qcurl_easy_setopt = GPA("curl_easy_setopt"); + qcurl_easy_perform = GPA("curl_easy_perform"); + qcurl_easy_cleanup = GPA("curl_easy_cleanup"); + qcurl_easy_getinfo = GPA("curl_easy_getinfo"); + qcurl_easy_duphandle = GPA("curl_easy_duphandle"); + qcurl_easy_reset = GPA("curl_easy_reset"); + qcurl_easy_strerror = GPA("curl_easy_strerror"); + + qcurl_multi_init = GPA("curl_multi_init"); + qcurl_multi_add_handle = GPA("curl_multi_add_handle"); + qcurl_multi_remove_handle = GPA("curl_multi_remove_handle"); + qcurl_multi_fdset = GPA("curl_multi_fdset"); + qcurl_multi_perform = GPA("curl_multi_perform"); + qcurl_multi_cleanup = GPA("curl_multi_cleanup"); + qcurl_multi_info_read = GPA("curl_multi_info_read"); + qcurl_multi_strerror = GPA("curl_multi_strerror"); + + if(!clc.cURLEnabled) + { + CL_cURL_Shutdown(); + Com_Printf("FAIL One or more symbols not found\n"); + return qfalse; + } + Com_Printf("OK\n"); + + return qtrue; +#else + clc.cURLEnabled = qtrue; + return qtrue; +#endif /* USE_CURL_DLOPEN */ +} + +/* +================= +CL_cURL_Shutdown +================= +*/ +void CL_cURL_Shutdown( void ) +{ + CL_cURL_Cleanup(); +#ifdef USE_CURL_DLOPEN + if(cURLLib) + { + Sys_UnloadLibrary(cURLLib); + cURLLib = NULL; + } + qcurl_easy_init = NULL; + qcurl_easy_setopt = NULL; + qcurl_easy_perform = NULL; + qcurl_easy_cleanup = NULL; + qcurl_easy_getinfo = NULL; + qcurl_easy_duphandle = NULL; + qcurl_easy_reset = NULL; + + qcurl_multi_init = NULL; + qcurl_multi_add_handle = NULL; + qcurl_multi_remove_handle = NULL; + qcurl_multi_fdset = NULL; + qcurl_multi_perform = NULL; + qcurl_multi_cleanup = NULL; + qcurl_multi_info_read = NULL; + qcurl_multi_strerror = NULL; +#endif /* USE_CURL_DLOPEN */ +} + +void CL_cURL_Cleanup(void) +{ + if(clc.downloadCURLM) { + if(clc.downloadCURL) { + qcurl_multi_remove_handle(clc.downloadCURLM, + clc.downloadCURL); + qcurl_easy_cleanup(clc.downloadCURL); + } + qcurl_multi_cleanup(clc.downloadCURLM); + clc.downloadCURLM = NULL; + clc.downloadCURL = NULL; + } + else if(clc.downloadCURL) { + qcurl_easy_cleanup(clc.downloadCURL); + clc.downloadCURL = NULL; + } +} + +static int CL_cURL_CallbackProgress( void *dummy, double dltotal, double dlnow, + double ultotal, double ulnow ) +{ + clc.downloadSize = (int)dltotal; + Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); + clc.downloadCount = (int)dlnow; + Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); + return 0; +} + +static size_t CL_cURL_CallbackWrite(void *buffer, size_t size, size_t nmemb, + void *stream) +{ + FS_Write( buffer, size*nmemb, ((fileHandle_t*)stream)[0] ); + return size*nmemb; +} + +void CL_cURL_BeginDownload( const char *localName, const char *remoteURL ) +{ + clc.cURLUsed = qtrue; + Com_Printf("URL: %s\n", remoteURL); + Com_DPrintf("***** CL_cURL_BeginDownload *****\n" + "Localname: %s\n" + "RemoteURL: %s\n" + "****************************\n", localName, remoteURL); + CL_cURL_Cleanup(); + Q_strncpyz(clc.downloadURL, remoteURL, sizeof(clc.downloadURL)); + Q_strncpyz(clc.downloadName, localName, sizeof(clc.downloadName)); + Com_sprintf(clc.downloadTempName, sizeof(clc.downloadTempName), + "%s.tmp", localName); + + // Set so UI gets access to it + Cvar_Set("cl_downloadName", localName); + Cvar_Set("cl_downloadSize", "0"); + Cvar_Set("cl_downloadCount", "0"); + Cvar_SetValue("cl_downloadTime", cls.realtime); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + clc.downloadCURL = qcurl_easy_init(); + if(!clc.downloadCURL) { + Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_easy_init() " + "failed"); + return; + } + clc.download = FS_SV_FOpenFileWrite(clc.downloadTempName); + if(!clc.download) { + Com_Error(ERR_DROP, "CL_cURL_BeginDownload: failed to open " + "%s for writing", clc.downloadTempName); + return; + } + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, clc.download); + if(com_developer->integer) + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_VERBOSE, 1); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_URL, clc.downloadURL); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_TRANSFERTEXT, 0); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_REFERER, va("ioQ3://%s", + NET_AdrToString(clc.serverAddress))); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_USERAGENT, va("%s %s", + Q3_VERSION, qcurl_version())); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEFUNCTION, + CL_cURL_CallbackWrite); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_WRITEDATA, &clc.download); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_NOPROGRESS, 0); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSFUNCTION, + CL_cURL_CallbackProgress); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_PROGRESSDATA, NULL); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FAILONERROR, 1); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_FOLLOWLOCATION, 1); + qcurl_easy_setopt(clc.downloadCURL, CURLOPT_MAXREDIRS, 5); + clc.downloadCURLM = qcurl_multi_init(); + if(!clc.downloadCURLM) { + qcurl_easy_cleanup(clc.downloadCURL); + clc.downloadCURL = NULL; + Com_Error(ERR_DROP, "CL_cURL_BeginDownload: qcurl_multi_init() " + "failed"); + return; + } + qcurl_multi_add_handle(clc.downloadCURLM, clc.downloadCURL); + + if(!(clc.sv_allowDownload & DLF_NO_DISCONNECT) && + !clc.cURLDisconnected) { + + CL_AddReliableCommand("disconnect", qtrue); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + clc.cURLDisconnected = qtrue; + } +} + +void CL_cURL_PerformDownload(void) +{ + CURLMcode res; + CURLMsg *msg; + int c; + int i = 0; + + res = qcurl_multi_perform(clc.downloadCURLM, &c); + while(res == CURLM_CALL_MULTI_PERFORM && i < 100) { + res = qcurl_multi_perform(clc.downloadCURLM, &c); + i++; + } + if(res == CURLM_CALL_MULTI_PERFORM) + return; + msg = qcurl_multi_info_read(clc.downloadCURLM, &c); + if(msg == NULL) { + return; + } + FS_FCloseFile(clc.download); + if(msg->msg == CURLMSG_DONE && msg->data.result == CURLE_OK) { + FS_SV_Rename(clc.downloadTempName, clc.downloadName); + clc.downloadRestart = qtrue; + } + else { + long code; + + qcurl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, + &code); + Com_Error(ERR_DROP, "Download Error: %s Code: %ld URL: %s", + qcurl_easy_strerror(msg->data.result), + code, clc.downloadURL); + } + + CL_NextDownload(); +} +#endif /* USE_CURL */ diff --git a/code/client/cl_curl.h b/code/client/cl_curl.h new file mode 100644 index 0000000..c8d3006 --- /dev/null +++ b/code/client/cl_curl.h @@ -0,0 +1,102 @@ +/* +=========================================================================== +Copyright (C) 2006 Tony J. White (tjw@tjw.org) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#ifndef __QCURL_H__ +#define __QCURL_H__ + +extern cvar_t *cl_cURLLib; + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +#ifdef WIN32 +#define DEFAULT_CURL_LIB "libcurl-3.dll" +#elif defined(MACOS_X) +#define DEFAULT_CURL_LIB "libcurl.dylib" +#else +#define DEFAULT_CURL_LIB "libcurl.so.4" +#define ALTERNATE_CURL_LIB "libcurl.so.3" +#endif + +#ifdef USE_LOCAL_HEADERS + #include "../libcurl/curl/curl.h" +#else + #include +#endif + + +#ifdef USE_CURL_DLOPEN +extern char* (*qcurl_version)(void); + +extern CURL* (*qcurl_easy_init)(void); +extern CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...); +extern CURLcode (*qcurl_easy_perform)(CURL *curl); +extern void (*qcurl_easy_cleanup)(CURL *curl); +extern CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...); +extern void (*qcurl_easy_reset)(CURL *curl); +extern const char *(*qcurl_easy_strerror)(CURLcode); + +extern CURLM* (*qcurl_multi_init)(void); +extern CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, + CURL *curl_handle); +extern CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, + CURL *curl_handle); +extern CURLMcode (*qcurl_multi_fdset)(CURLM *multi_handle, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *exc_fd_set, + int *max_fd); +extern CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, + int *running_handles); +extern CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle); +extern CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, + int *msgs_in_queue); +extern const char *(*qcurl_multi_strerror)(CURLMcode); +#else +#define qcurl_version curl_version + +#define qcurl_easy_init curl_easy_init +#define qcurl_easy_setopt curl_easy_setopt +#define qcurl_easy_perform curl_easy_perform +#define qcurl_easy_cleanup curl_easy_cleanup +#define qcurl_easy_getinfo curl_easy_getinfo +#define qcurl_easy_duphandle curl_easy_duphandle +#define qcurl_easy_reset curl_easy_reset +#define qcurl_easy_strerror curl_easy_strerror + +#define qcurl_multi_init curl_multi_init +#define qcurl_multi_add_handle curl_multi_add_handle +#define qcurl_multi_remove_handle curl_multi_remove_handle +#define qcurl_multi_fdset curl_multi_fdset +#define qcurl_multi_perform curl_multi_perform +#define qcurl_multi_cleanup curl_multi_cleanup +#define qcurl_multi_info_read curl_multi_info_read +#define qcurl_multi_strerror curl_multi_strerror +#endif + +qboolean CL_cURL_Init( void ); +void CL_cURL_Shutdown( void ); +void CL_cURL_BeginDownload( const char *localName, const char *remoteURL ); +void CL_cURL_PerformDownload( void ); +void CL_cURL_Cleanup( void ); +#endif // __QCURL_H__ diff --git a/code/client/cl_input.c b/code/client/cl_input.c new file mode 100644 index 0000000..e9612c2 --- /dev/null +++ b/code/client/cl_input.c @@ -0,0 +1,1069 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl.input.c -- builds an intended movement command to send to the server + +#include "client.h" + +unsigned frame_msec; +int old_com_frameTime; + +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as argv(1) so it can be matched up with the release. + +argv(2) will be set to the time the event happened, which allows exact +control even at low framerates when the down and up events may both get qued +at the same time. + +=============================================================================== +*/ + + +kbutton_t in_left, in_right, in_forward, in_back; +kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright; +kbutton_t in_strafe, in_speed; +kbutton_t in_up, in_down; + +#ifdef USE_VOIP +kbutton_t in_voiprecord; +#endif + +kbutton_t in_buttons[16]; + + +qboolean in_mlooking; + + +void IN_MLookDown( void ) { + in_mlooking = qtrue; +} + +void IN_MLookUp( void ) { + in_mlooking = qfalse; + if ( !cl_freelook->integer ) { + IN_CenterView (); + } +} + +void IN_KeyDown( kbutton_t *b ) { + int k; + char *c; + + c = Cmd_Argv(1); + if ( c[0] ) { + k = atoi(c); + } else { + k = -1; // typed manually at the console for continuous down + } + + if ( k == b->down[0] || k == b->down[1] ) { + return; // repeating key + } + + if ( !b->down[0] ) { + b->down[0] = k; + } else if ( !b->down[1] ) { + b->down[1] = k; + } else { + Com_Printf ("Three keys down for a button!\n"); + return; + } + + if ( b->active ) { + return; // still down + } + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + b->downtime = atoi(c); + + b->active = qtrue; + b->wasPressed = qtrue; +} + +void IN_KeyUp( kbutton_t *b ) { + int k; + char *c; + unsigned uptime; + + c = Cmd_Argv(1); + if ( c[0] ) { + k = atoi(c); + } else { + // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->active = qfalse; + return; + } + + if ( b->down[0] == k ) { + b->down[0] = 0; + } else if ( b->down[1] == k ) { + b->down[1] = 0; + } else { + return; // key up without coresponding down (menu pass through) + } + if ( b->down[0] || b->down[1] ) { + return; // some other key is still holding it down + } + + b->active = qfalse; + + // save timestamp for partial frame summing + c = Cmd_Argv(2); + uptime = atoi(c); + if ( uptime ) { + b->msec += uptime - b->downtime; + } else { + b->msec += frame_msec / 2; + } + + b->active = qfalse; +} + + + +/* +=============== +CL_KeyState + +Returns the fraction of the frame that the key was down +=============== +*/ +float CL_KeyState( kbutton_t *key ) { + float val; + int msec; + + msec = key->msec; + key->msec = 0; + + if ( key->active ) { + // still down + if ( !key->downtime ) { + msec = com_frameTime; + } else { + msec += com_frameTime - key->downtime; + } + key->downtime = com_frameTime; + } + +#if 0 + if (msec) { + Com_Printf ("%i ", msec); + } +#endif + + val = (float)msec / frame_msec; + if ( val < 0 ) { + val = 0; + } + if ( val > 1 ) { + val = 1; + } + + return val; +} + + + +void IN_UpDown(void) {IN_KeyDown(&in_up);} +void IN_UpUp(void) {IN_KeyUp(&in_up);} +void IN_DownDown(void) {IN_KeyDown(&in_down);} +void IN_DownUp(void) {IN_KeyUp(&in_down);} +void IN_LeftDown(void) {IN_KeyDown(&in_left);} +void IN_LeftUp(void) {IN_KeyUp(&in_left);} +void IN_RightDown(void) {IN_KeyDown(&in_right);} +void IN_RightUp(void) {IN_KeyUp(&in_right);} +void IN_ForwardDown(void) {IN_KeyDown(&in_forward);} +void IN_ForwardUp(void) {IN_KeyUp(&in_forward);} +void IN_BackDown(void) {IN_KeyDown(&in_back);} +void IN_BackUp(void) {IN_KeyUp(&in_back);} +void IN_LookupDown(void) {IN_KeyDown(&in_lookup);} +void IN_LookupUp(void) {IN_KeyUp(&in_lookup);} +void IN_LookdownDown(void) {IN_KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {IN_KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) {IN_KeyDown(&in_moveleft);} +void IN_MoveleftUp(void) {IN_KeyUp(&in_moveleft);} +void IN_MoverightDown(void) {IN_KeyDown(&in_moveright);} +void IN_MoverightUp(void) {IN_KeyUp(&in_moveright);} + +void IN_SpeedDown(void) {IN_KeyDown(&in_speed);} +void IN_SpeedUp(void) {IN_KeyUp(&in_speed);} +void IN_StrafeDown(void) {IN_KeyDown(&in_strafe);} +void IN_StrafeUp(void) {IN_KeyUp(&in_strafe);} + +#ifdef USE_VOIP +void IN_VoipRecordDown(void) +{ + IN_KeyDown(&in_voiprecord); + Cvar_Set("cl_voipSend", "1"); +} + +void IN_VoipRecordUp(void) +{ + IN_KeyUp(&in_voiprecord); + Cvar_Set("cl_voipSend", "0"); +} +#endif + +void IN_Button0Down(void) {IN_KeyDown(&in_buttons[0]);} +void IN_Button0Up(void) {IN_KeyUp(&in_buttons[0]);} +void IN_Button1Down(void) {IN_KeyDown(&in_buttons[1]);} +void IN_Button1Up(void) {IN_KeyUp(&in_buttons[1]);} +void IN_Button2Down(void) {IN_KeyDown(&in_buttons[2]);} +void IN_Button2Up(void) {IN_KeyUp(&in_buttons[2]);} +void IN_Button3Down(void) {IN_KeyDown(&in_buttons[3]);} +void IN_Button3Up(void) {IN_KeyUp(&in_buttons[3]);} +void IN_Button4Down(void) {IN_KeyDown(&in_buttons[4]);} +void IN_Button4Up(void) {IN_KeyUp(&in_buttons[4]);} +void IN_Button5Down(void) {IN_KeyDown(&in_buttons[5]);} +void IN_Button5Up(void) {IN_KeyUp(&in_buttons[5]);} +void IN_Button6Down(void) {IN_KeyDown(&in_buttons[6]);} +void IN_Button6Up(void) {IN_KeyUp(&in_buttons[6]);} +void IN_Button7Down(void) {IN_KeyDown(&in_buttons[7]);} +void IN_Button7Up(void) {IN_KeyUp(&in_buttons[7]);} +void IN_Button8Down(void) {IN_KeyDown(&in_buttons[8]);} +void IN_Button8Up(void) {IN_KeyUp(&in_buttons[8]);} +void IN_Button9Down(void) {IN_KeyDown(&in_buttons[9]);} +void IN_Button9Up(void) {IN_KeyUp(&in_buttons[9]);} +void IN_Button10Down(void) {IN_KeyDown(&in_buttons[10]);} +void IN_Button10Up(void) {IN_KeyUp(&in_buttons[10]);} +void IN_Button11Down(void) {IN_KeyDown(&in_buttons[11]);} +void IN_Button11Up(void) {IN_KeyUp(&in_buttons[11]);} +void IN_Button12Down(void) {IN_KeyDown(&in_buttons[12]);} +void IN_Button12Up(void) {IN_KeyUp(&in_buttons[12]);} +void IN_Button13Down(void) {IN_KeyDown(&in_buttons[13]);} +void IN_Button13Up(void) {IN_KeyUp(&in_buttons[13]);} +void IN_Button14Down(void) {IN_KeyDown(&in_buttons[14]);} +void IN_Button14Up(void) {IN_KeyUp(&in_buttons[14]);} +void IN_Button15Down(void) {IN_KeyDown(&in_buttons[15]);} +void IN_Button15Up(void) {IN_KeyUp(&in_buttons[15]);} + +void IN_ButtonDown (void) { + IN_KeyDown(&in_buttons[1]);} +void IN_ButtonUp (void) { + IN_KeyUp(&in_buttons[1]);} + +void IN_CenterView (void) { + cl.viewangles[PITCH] = -SHORT2ANGLE(cl.snap.ps.delta_angles[PITCH]); +} + + +//========================================================================== + +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; + +cvar_t *cl_run; + +cvar_t *cl_anglespeedkey; + + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles( void ) { + float speed; + + if ( in_speed.active ) { + speed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + speed = 0.001 * cls.frametime; + } + + if ( !in_strafe.active ) { + cl.viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + cl.viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + } + + cl.viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_lookup); + cl.viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_lookdown); +} + +/* +================ +CL_KeyMove + +Sets the usercmd_t based on key states +================ +*/ +void CL_KeyMove( usercmd_t *cmd ) { + int movespeed; + int forward, side, up; + + // + // adjust for speed key / running + // the walking flag is to keep animations consistant + // even during acceleration and develeration + // + if ( in_speed.active ^ cl_run->integer ) { + movespeed = 127; + cmd->buttons &= ~BUTTON_WALKING; + } else { + cmd->buttons |= BUTTON_WALKING; + movespeed = 64; + } + + forward = 0; + side = 0; + up = 0; + if ( in_strafe.active ) { + side += movespeed * CL_KeyState (&in_right); + side -= movespeed * CL_KeyState (&in_left); + } + + side += movespeed * CL_KeyState (&in_moveright); + side -= movespeed * CL_KeyState (&in_moveleft); + + + up += movespeed * CL_KeyState (&in_up); + up -= movespeed * CL_KeyState (&in_down); + + forward += movespeed * CL_KeyState (&in_forward); + forward -= movespeed * CL_KeyState (&in_back); + + cmd->forwardmove = ClampChar( forward ); + cmd->rightmove = ClampChar( side ); + cmd->upmove = ClampChar( up ); +} + +/* +================= +CL_MouseEvent +================= +*/ +void CL_MouseEvent( int dx, int dy, int time ) { + if ( Key_GetCatcher( ) & KEYCATCH_UI ) { + VM_Call( uivm, UI_MOUSE_EVENT, dx, dy ); + } else if (Key_GetCatcher( ) & KEYCATCH_CGAME) { + VM_Call (cgvm, CG_MOUSE_EVENT, dx, dy); + } else { + cl.mouseDx[cl.mouseIndex] += dx; + cl.mouseDy[cl.mouseIndex] += dy; + } +} + +/* +================= +CL_JoystickEvent + +Joystick values stay set until changed +================= +*/ +void CL_JoystickEvent( int axis, int value, int time ) { + if ( axis < 0 || axis >= MAX_JOYSTICK_AXIS ) { + Com_Error( ERR_DROP, "CL_JoystickEvent: bad axis %i", axis ); + } + cl.joystickAxis[axis] = value; +} + +/* +================= +CL_JoystickMove +================= +*/ +void CL_JoystickMove( usercmd_t *cmd ) { + float anglespeed; + + if ( !(in_speed.active ^ cl_run->integer) ) { + cmd->buttons |= BUTTON_WALKING; + } + + if ( in_speed.active ) { + anglespeed = 0.001 * cls.frametime * cl_anglespeedkey->value; + } else { + anglespeed = 0.001 * cls.frametime; + } + + if ( !in_strafe.active ) { + cl.viewangles[YAW] += anglespeed * j_yaw->value * cl.joystickAxis[j_yaw_axis->integer]; + cmd->rightmove = ClampChar( cmd->rightmove + (int) (j_side->value * cl.joystickAxis[j_side_axis->integer]) ); + } else { + cl.viewangles[YAW] += anglespeed * j_side->value * cl.joystickAxis[j_side_axis->integer]; + cmd->rightmove = ClampChar( cmd->rightmove + (int) (j_yaw->value * cl.joystickAxis[j_yaw_axis->integer]) ); + } + + if ( in_mlooking ) { + cl.viewangles[PITCH] += anglespeed * j_forward->value * cl.joystickAxis[j_forward_axis->integer]; + cmd->forwardmove = ClampChar( cmd->forwardmove + (int) (j_pitch->value * cl.joystickAxis[j_pitch_axis->integer]) ); + } else { + cl.viewangles[PITCH] += anglespeed * j_pitch->value * cl.joystickAxis[j_pitch_axis->integer]; + cmd->forwardmove = ClampChar( cmd->forwardmove + (int) (j_forward->value * cl.joystickAxis[j_forward_axis->integer]) ); + } + + cmd->upmove = ClampChar( cmd->upmove + cl.joystickAxis[AXIS_UP] ); +} + +/* +================= +CL_MouseMove +================= +*/ + +void CL_MouseMove(usercmd_t *cmd) +{ + float mx, my; + + // allow mouse smoothing + if (m_filter->integer) + { + mx = (cl.mouseDx[0] + cl.mouseDx[1]) * 0.5f; + my = (cl.mouseDy[0] + cl.mouseDy[1]) * 0.5f; + } + else + { + mx = cl.mouseDx[cl.mouseIndex]; + my = cl.mouseDy[cl.mouseIndex]; + } + + cl.mouseIndex ^= 1; + cl.mouseDx[cl.mouseIndex] = 0; + cl.mouseDy[cl.mouseIndex] = 0; + + if (mx == 0.0f && my == 0.0f) + return; + + if (cl_mouseAccel->value != 0.0f) + { + if(cl_mouseAccelStyle->integer == 0) + { + float accelSensitivity; + float rate; + + rate = sqrt(mx * mx + my * my) / (float) frame_msec; + + accelSensitivity = cl_sensitivity->value + rate * cl_mouseAccel->value; + mx *= accelSensitivity; + my *= accelSensitivity; + + if(cl_showMouseRate->integer) + Com_Printf("rate: %f, accelSensitivity: %f\n", rate, accelSensitivity); + } + else + { + float rate[2]; + float power[2]; + + // sensitivity remains pretty much unchanged at low speeds + // cl_mouseAccel is a power value to how the acceleration is shaped + // cl_mouseAccelOffset is the rate for which the acceleration will have doubled the non accelerated amplification + // NOTE: decouple the config cvars for independent acceleration setup along X and Y? + + rate[0] = fabs(mx) / (float) frame_msec; + rate[1] = fabs(my) / (float) frame_msec; + power[0] = powf(rate[0] / cl_mouseAccelOffset->value, cl_mouseAccel->value); + power[1] = powf(rate[1] / cl_mouseAccelOffset->value, cl_mouseAccel->value); + + mx = cl_sensitivity->value * (mx + ((mx < 0) ? -power[0] : power[0]) * cl_mouseAccelOffset->value); + my = cl_sensitivity->value * (my + ((my < 0) ? -power[1] : power[1]) * cl_mouseAccelOffset->value); + + if(cl_showMouseRate->integer) + Com_Printf("ratex: %f, ratey: %f, powx: %f, powy: %f\n", rate[0], rate[1], power[0], power[1]); + } + } + else + { + mx *= cl_sensitivity->value; + my *= cl_sensitivity->value; + } + + // ingame FOV + mx *= cl.cgameSensitivity; + my *= cl.cgameSensitivity; + + // add mouse X/Y movement to cmd + if(in_strafe.active) + cmd->rightmove = ClampChar(cmd->rightmove + m_side->value * mx); + else + cl.viewangles[YAW] -= m_yaw->value * mx; + + if ((in_mlooking || cl_freelook->integer) && !in_strafe.active) + cl.viewangles[PITCH] += m_pitch->value * my; + else + cmd->forwardmove = ClampChar(cmd->forwardmove - m_forward->value * my); +} + + +/* +============== +CL_CmdButtons +============== +*/ +void CL_CmdButtons( usercmd_t *cmd ) { + int i; + + // + // figure button bits + // send a button bit even if the key was pressed and released in + // less than a frame + // + for (i = 0 ; i < 15 ; i++) { + if ( in_buttons[i].active || in_buttons[i].wasPressed ) { + cmd->buttons |= 1 << i; + } + in_buttons[i].wasPressed = qfalse; + } + + if ( Key_GetCatcher( ) ) { + cmd->buttons |= BUTTON_TALK; + } + + // allow the game to know if any key at all is + // currently pressed, even if it isn't bound to anything + if ( anykeydown && Key_GetCatcher( ) == 0 ) { + cmd->buttons |= BUTTON_ANY; + } +} + + +/* +============== +CL_FinishMove +============== +*/ +void CL_FinishMove( usercmd_t *cmd ) { + int i; + + // copy the state that the cgame is currently sending + cmd->weapon = cl.cgameUserCmdValue; + + // send the current server time so the amount of movement + // can be determined without allowing cheating + cmd->serverTime = cl.serverTime; + + for (i=0 ; i<3 ; i++) { + cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]); + } +} + + +/* +================= +CL_CreateCmd +================= +*/ +usercmd_t CL_CreateCmd( void ) { + usercmd_t cmd; + vec3_t oldAngles; + + VectorCopy( cl.viewangles, oldAngles ); + + // keyboard angle adjustment + CL_AdjustAngles (); + + Com_Memset( &cmd, 0, sizeof( cmd ) ); + + CL_CmdButtons( &cmd ); + + // get basic movement from keyboard + CL_KeyMove( &cmd ); + + // get basic movement from mouse + CL_MouseMove( &cmd ); + + // get basic movement from joystick + CL_JoystickMove( &cmd ); + + // check to make sure the angles haven't wrapped + if ( cl.viewangles[PITCH] - oldAngles[PITCH] > 90 ) { + cl.viewangles[PITCH] = oldAngles[PITCH] + 90; + } else if ( oldAngles[PITCH] - cl.viewangles[PITCH] > 90 ) { + cl.viewangles[PITCH] = oldAngles[PITCH] - 90; + } + + // store out the final values + CL_FinishMove( &cmd ); + + // draw debug graphs of turning for mouse testing + if ( cl_debugMove->integer ) { + if ( cl_debugMove->integer == 1 ) { + SCR_DebugGraph( abs(cl.viewangles[YAW] - oldAngles[YAW]) ); + } + if ( cl_debugMove->integer == 2 ) { + SCR_DebugGraph( abs(cl.viewangles[PITCH] - oldAngles[PITCH]) ); + } + } + + return cmd; +} + + +/* +================= +CL_CreateNewCommands + +Create a new usercmd_t structure for this frame +================= +*/ +void CL_CreateNewCommands( void ) { + int cmdNum; + + // no need to create usercmds until we have a gamestate + if ( clc.state < CA_PRIMED ) { + return; + } + + frame_msec = com_frameTime - old_com_frameTime; + + // if running less than 5fps, truncate the extra time to prevent + // unexpected moves after a hitch + if ( frame_msec > 200 ) { + frame_msec = 200; + } + old_com_frameTime = com_frameTime; + + + // generate a command for this frame + cl.cmdNumber++; + cmdNum = cl.cmdNumber & CMD_MASK; + cl.cmds[cmdNum] = CL_CreateCmd (); +} + +/* +================= +CL_ReadyToSendPacket + +Returns qfalse if we are over the maxpackets limit +and should choke back the bandwidth a bit by not sending +a packet this frame. All the commands will still get +delivered in the next packet, but saving a header and +getting more delta compression will reduce total bandwidth. +================= +*/ +qboolean CL_ReadyToSendPacket( void ) { + int oldPacketNum; + int delta; + + // don't send anything if playing back a demo + if ( clc.demoplaying || clc.state == CA_CINEMATIC ) { + return qfalse; + } + + // If we are downloading, we send no less than 50ms between packets + if ( *clc.downloadTempName && + cls.realtime - clc.lastPacketSentTime < 50 ) { + return qfalse; + } + + // if we don't have a valid gamestate yet, only send + // one packet a second + if ( clc.state != CA_ACTIVE && + clc.state != CA_PRIMED && + !*clc.downloadTempName && + cls.realtime - clc.lastPacketSentTime < 1000 ) { + return qfalse; + } + + // send every frame for loopbacks + if ( clc.netchan.remoteAddress.type == NA_LOOPBACK ) { + return qtrue; + } + + // send every frame for LAN + if ( cl_lanForcePackets->integer && Sys_IsLANAddress( clc.netchan.remoteAddress ) ) { + return qtrue; + } + + // check for exceeding cl_maxpackets + if ( cl_maxpackets->integer < 15 ) { + Cvar_Set( "cl_maxpackets", "15" ); + } else if ( cl_maxpackets->integer > 125 ) { + Cvar_Set( "cl_maxpackets", "125" ); + } + oldPacketNum = (clc.netchan.outgoingSequence - 1) & PACKET_MASK; + delta = cls.realtime - cl.outPackets[ oldPacketNum ].p_realtime; + if ( delta < 1000 / cl_maxpackets->integer ) { + // the accumulated commands will go out in the next packet + return qfalse; + } + + return qtrue; +} + +/* +=================== +CL_WritePacket + +Create and send the command packet to the server +Including both the reliable commands and the usercmds + +During normal gameplay, a client packet will contain something like: + +4 sequence number +2 qport +4 serverid +4 acknowledged sequence number +4 clc.serverCommandSequence + +1 clc_move or clc_moveNoDelta +1 command count + + +=================== +*/ +void CL_WritePacket( void ) { + msg_t buf; + byte data[MAX_MSGLEN]; + int i, j; + usercmd_t *cmd, *oldcmd; + usercmd_t nullcmd; + int packetNum; + int oldPacketNum; + int count, key; + + // don't send anything if playing back a demo + if ( clc.demoplaying || clc.state == CA_CINEMATIC ) { + return; + } + + Com_Memset( &nullcmd, 0, sizeof(nullcmd) ); + oldcmd = &nullcmd; + + MSG_Init( &buf, data, sizeof(data) ); + + MSG_Bitstream( &buf ); + // write the current serverId so the server + // can tell if this is from the current gameState + MSG_WriteLong( &buf, cl.serverId ); + + // write the last message we received, which can + // be used for delta compression, and is also used + // to tell if we dropped a gamestate + MSG_WriteLong( &buf, clc.serverMessageSequence ); + + // write the last reliable message we received + MSG_WriteLong( &buf, clc.serverCommandSequence ); + + // write any unacknowledged clientCommands + for ( i = clc.reliableAcknowledge + 1 ; i <= clc.reliableSequence ; i++ ) { + MSG_WriteByte( &buf, clc_clientCommand ); + MSG_WriteLong( &buf, i ); + MSG_WriteString( &buf, clc.reliableCommands[ i & (MAX_RELIABLE_COMMANDS-1) ] ); + } + + // we want to send all the usercmds that were generated in the last + // few packet, so even if a couple packets are dropped in a row, + // all the cmds will make it to the server + if ( cl_packetdup->integer < 0 ) { + Cvar_Set( "cl_packetdup", "0" ); + } else if ( cl_packetdup->integer > 5 ) { + Cvar_Set( "cl_packetdup", "5" ); + } + oldPacketNum = (clc.netchan.outgoingSequence - 1 - cl_packetdup->integer) & PACKET_MASK; + count = cl.cmdNumber - cl.outPackets[ oldPacketNum ].p_cmdNumber; + if ( count > MAX_PACKET_USERCMDS ) { + count = MAX_PACKET_USERCMDS; + Com_Printf("MAX_PACKET_USERCMDS\n"); + } + +#ifdef USE_VOIP + if (clc.voipOutgoingDataSize > 0) + { + if((clc.voipFlags & VOIP_SPATIAL) || Com_IsVoipTarget(clc.voipTargets, sizeof(clc.voipTargets), -1)) + { + MSG_WriteByte (&buf, clc_voip); + MSG_WriteByte (&buf, clc.voipOutgoingGeneration); + MSG_WriteLong (&buf, clc.voipOutgoingSequence); + MSG_WriteByte (&buf, clc.voipOutgoingDataFrames); + MSG_WriteData (&buf, clc.voipTargets, sizeof(clc.voipTargets)); + MSG_WriteByte(&buf, clc.voipFlags); + MSG_WriteShort (&buf, clc.voipOutgoingDataSize); + MSG_WriteData (&buf, clc.voipOutgoingData, clc.voipOutgoingDataSize); + + // If we're recording a demo, we have to fake a server packet with + // this VoIP data so it gets to disk; the server doesn't send it + // back to us, and we might as well eliminate concerns about dropped + // and misordered packets here. + if(clc.demorecording && !clc.demowaiting) + { + const int voipSize = clc.voipOutgoingDataSize; + msg_t fakemsg; + byte fakedata[MAX_MSGLEN]; + MSG_Init (&fakemsg, fakedata, sizeof (fakedata)); + MSG_Bitstream (&fakemsg); + MSG_WriteLong (&fakemsg, clc.reliableAcknowledge); + MSG_WriteByte (&fakemsg, svc_voip); + MSG_WriteShort (&fakemsg, clc.clientNum); + MSG_WriteByte (&fakemsg, clc.voipOutgoingGeneration); + MSG_WriteLong (&fakemsg, clc.voipOutgoingSequence); + MSG_WriteByte (&fakemsg, clc.voipOutgoingDataFrames); + MSG_WriteShort (&fakemsg, clc.voipOutgoingDataSize ); + MSG_WriteData (&fakemsg, clc.voipOutgoingData, voipSize); + MSG_WriteByte (&fakemsg, svc_EOF); + CL_WriteDemoMessage (&fakemsg, 0); + } + + clc.voipOutgoingSequence += clc.voipOutgoingDataFrames; + clc.voipOutgoingDataSize = 0; + clc.voipOutgoingDataFrames = 0; + } + else + { + // We have data, but no targets. Silently discard all data + clc.voipOutgoingDataSize = 0; + clc.voipOutgoingDataFrames = 0; + } + } +#endif + + if ( count >= 1 ) { + if ( cl_showSend->integer ) { + Com_Printf( "(%i)", count ); + } + + // begin a client move command + if ( cl_nodelta->integer || !cl.snap.valid || clc.demowaiting + || clc.serverMessageSequence != cl.snap.messageNum ) { + MSG_WriteByte (&buf, clc_moveNoDelta); + } else { + MSG_WriteByte (&buf, clc_move); + } + + // write the command count + MSG_WriteByte( &buf, count ); + + // use the checksum feed in the key + key = clc.checksumFeed; + // also use the message acknowledge + key ^= clc.serverMessageSequence; + // also use the last acknowledged server command in the key + key ^= MSG_HashKey(clc.serverCommands[ clc.serverCommandSequence & (MAX_RELIABLE_COMMANDS-1) ], 32); + + // write all the commands, including the predicted command + for ( i = 0 ; i < count ; i++ ) { + j = (cl.cmdNumber - count + i + 1) & CMD_MASK; + cmd = &cl.cmds[j]; + MSG_WriteDeltaUsercmdKey (&buf, key, oldcmd, cmd); + oldcmd = cmd; + } + } + + // + // deliver the message + // + packetNum = clc.netchan.outgoingSequence & PACKET_MASK; + cl.outPackets[ packetNum ].p_realtime = cls.realtime; + cl.outPackets[ packetNum ].p_serverTime = oldcmd->serverTime; + cl.outPackets[ packetNum ].p_cmdNumber = cl.cmdNumber; + clc.lastPacketSentTime = cls.realtime; + + if ( cl_showSend->integer ) { + Com_Printf( "%i ", buf.cursize ); + } + + CL_Netchan_Transmit (&clc.netchan, &buf); +} + +/* +================= +CL_SendCmd + +Called every frame to builds and sends a command packet to the server. +================= +*/ +void CL_SendCmd( void ) { + // don't send any message if not connected + if ( clc.state < CA_CONNECTED ) { + return; + } + + // don't send commands if paused + if ( com_sv_running->integer && sv_paused->integer && cl_paused->integer ) { + return; + } + + // we create commands even if a demo is playing, + CL_CreateNewCommands(); + + // don't send a packet if the last packet was sent too recently + if ( !CL_ReadyToSendPacket() ) { + if ( cl_showSend->integer ) { + Com_Printf( ". " ); + } + return; + } + + CL_WritePacket(); +} + +/* +============ +CL_InitInput +============ +*/ +void CL_InitInput( void ) { + Cmd_AddCommand ("centerview",IN_CenterView); + + Cmd_AddCommand ("+moveup",IN_UpDown); + Cmd_AddCommand ("-moveup",IN_UpUp); + Cmd_AddCommand ("+movedown",IN_DownDown); + Cmd_AddCommand ("-movedown",IN_DownUp); + Cmd_AddCommand ("+left",IN_LeftDown); + Cmd_AddCommand ("-left",IN_LeftUp); + Cmd_AddCommand ("+right",IN_RightDown); + Cmd_AddCommand ("-right",IN_RightUp); + Cmd_AddCommand ("+forward",IN_ForwardDown); + Cmd_AddCommand ("-forward",IN_ForwardUp); + Cmd_AddCommand ("+back",IN_BackDown); + Cmd_AddCommand ("-back",IN_BackUp); + Cmd_AddCommand ("+lookup", IN_LookupDown); + Cmd_AddCommand ("-lookup", IN_LookupUp); + Cmd_AddCommand ("+lookdown", IN_LookdownDown); + Cmd_AddCommand ("-lookdown", IN_LookdownUp); + Cmd_AddCommand ("+strafe", IN_StrafeDown); + Cmd_AddCommand ("-strafe", IN_StrafeUp); + Cmd_AddCommand ("+moveleft", IN_MoveleftDown); + Cmd_AddCommand ("-moveleft", IN_MoveleftUp); + Cmd_AddCommand ("+moveright", IN_MoverightDown); + Cmd_AddCommand ("-moveright", IN_MoverightUp); + Cmd_AddCommand ("+speed", IN_SpeedDown); + Cmd_AddCommand ("-speed", IN_SpeedUp); + Cmd_AddCommand ("+attack", IN_Button0Down); + Cmd_AddCommand ("-attack", IN_Button0Up); + Cmd_AddCommand ("+button0", IN_Button0Down); + Cmd_AddCommand ("-button0", IN_Button0Up); + Cmd_AddCommand ("+button1", IN_Button1Down); + Cmd_AddCommand ("-button1", IN_Button1Up); + Cmd_AddCommand ("+button2", IN_Button2Down); + Cmd_AddCommand ("-button2", IN_Button2Up); + Cmd_AddCommand ("+button3", IN_Button3Down); + Cmd_AddCommand ("-button3", IN_Button3Up); + Cmd_AddCommand ("+button4", IN_Button4Down); + Cmd_AddCommand ("-button4", IN_Button4Up); + Cmd_AddCommand ("+button5", IN_Button5Down); + Cmd_AddCommand ("-button5", IN_Button5Up); + Cmd_AddCommand ("+button6", IN_Button6Down); + Cmd_AddCommand ("-button6", IN_Button6Up); + Cmd_AddCommand ("+button7", IN_Button7Down); + Cmd_AddCommand ("-button7", IN_Button7Up); + Cmd_AddCommand ("+button8", IN_Button8Down); + Cmd_AddCommand ("-button8", IN_Button8Up); + Cmd_AddCommand ("+button9", IN_Button9Down); + Cmd_AddCommand ("-button9", IN_Button9Up); + Cmd_AddCommand ("+button10", IN_Button10Down); + Cmd_AddCommand ("-button10", IN_Button10Up); + Cmd_AddCommand ("+button11", IN_Button11Down); + Cmd_AddCommand ("-button11", IN_Button11Up); + Cmd_AddCommand ("+button12", IN_Button12Down); + Cmd_AddCommand ("-button12", IN_Button12Up); + Cmd_AddCommand ("+button13", IN_Button13Down); + Cmd_AddCommand ("-button13", IN_Button13Up); + Cmd_AddCommand ("+button14", IN_Button14Down); + Cmd_AddCommand ("-button14", IN_Button14Up); + Cmd_AddCommand ("+mlook", IN_MLookDown); + Cmd_AddCommand ("-mlook", IN_MLookUp); + +#ifdef USE_VOIP + Cmd_AddCommand ("+voiprecord", IN_VoipRecordDown); + Cmd_AddCommand ("-voiprecord", IN_VoipRecordUp); +#endif + + cl_nodelta = Cvar_Get ("cl_nodelta", "0", 0); + cl_debugMove = Cvar_Get ("cl_debugMove", "0", 0); +} + +/* +============ +CL_ShutdownInput +============ +*/ +void CL_ShutdownInput(void) +{ + Cmd_RemoveCommand("centerview"); + + Cmd_RemoveCommand("+moveup"); + Cmd_RemoveCommand("-moveup"); + Cmd_RemoveCommand("+movedown"); + Cmd_RemoveCommand("-movedown"); + Cmd_RemoveCommand("+left"); + Cmd_RemoveCommand("-left"); + Cmd_RemoveCommand("+right"); + Cmd_RemoveCommand("-right"); + Cmd_RemoveCommand("+forward"); + Cmd_RemoveCommand("-forward"); + Cmd_RemoveCommand("+back"); + Cmd_RemoveCommand("-back"); + Cmd_RemoveCommand("+lookup"); + Cmd_RemoveCommand("-lookup"); + Cmd_RemoveCommand("+lookdown"); + Cmd_RemoveCommand("-lookdown"); + Cmd_RemoveCommand("+strafe"); + Cmd_RemoveCommand("-strafe"); + Cmd_RemoveCommand("+moveleft"); + Cmd_RemoveCommand("-moveleft"); + Cmd_RemoveCommand("+moveright"); + Cmd_RemoveCommand("-moveright"); + Cmd_RemoveCommand("+speed"); + Cmd_RemoveCommand("-speed"); + Cmd_RemoveCommand("+attack"); + Cmd_RemoveCommand("-attack"); + Cmd_RemoveCommand("+button0"); + Cmd_RemoveCommand("-button0"); + Cmd_RemoveCommand("+button1"); + Cmd_RemoveCommand("-button1"); + Cmd_RemoveCommand("+button2"); + Cmd_RemoveCommand("-button2"); + Cmd_RemoveCommand("+button3"); + Cmd_RemoveCommand("-button3"); + Cmd_RemoveCommand("+button4"); + Cmd_RemoveCommand("-button4"); + Cmd_RemoveCommand("+button5"); + Cmd_RemoveCommand("-button5"); + Cmd_RemoveCommand("+button6"); + Cmd_RemoveCommand("-button6"); + Cmd_RemoveCommand("+button7"); + Cmd_RemoveCommand("-button7"); + Cmd_RemoveCommand("+button8"); + Cmd_RemoveCommand("-button8"); + Cmd_RemoveCommand("+button9"); + Cmd_RemoveCommand("-button9"); + Cmd_RemoveCommand("+button10"); + Cmd_RemoveCommand("-button10"); + Cmd_RemoveCommand("+button11"); + Cmd_RemoveCommand("-button11"); + Cmd_RemoveCommand("+button12"); + Cmd_RemoveCommand("-button12"); + Cmd_RemoveCommand("+button13"); + Cmd_RemoveCommand("-button13"); + Cmd_RemoveCommand("+button14"); + Cmd_RemoveCommand("-button14"); + Cmd_RemoveCommand("+mlook"); + Cmd_RemoveCommand("-mlook"); + +#ifdef USE_VOIP + Cmd_RemoveCommand("+voiprecord"); + Cmd_RemoveCommand("-voiprecord"); +#endif +} diff --git a/code/client/cl_keys.c b/code/client/cl_keys.c new file mode 100644 index 0000000..34e2f44 --- /dev/null +++ b/code/client/cl_keys.c @@ -0,0 +1,1543 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "client.h" + +/* + +key up events are sent even if in console mode + +*/ + +field_t historyEditLines[COMMAND_HISTORY]; + +int nextHistoryLine; // the last line in the history buffer, not masked +int historyLine; // the line being displayed from history buffer + // will be <= nextHistoryLine + +field_t g_consoleField; +field_t chatField; +qboolean chat_team; + +int chat_playerNum; + + +qboolean key_overstrikeMode; + +int anykeydown; +qkey_t keys[MAX_KEYS]; + + +typedef struct { + char *name; + int keynum; +} keyname_t; + + +// names not in this list can either be lowercase ascii, or '0xnn' hex sequences +keyname_t keynames[] = +{ + {"TAB", K_TAB}, + {"ENTER", K_ENTER}, + {"ESCAPE", K_ESCAPE}, + {"SPACE", K_SPACE}, + {"BACKSPACE", K_BACKSPACE}, + {"UPARROW", K_UPARROW}, + {"DOWNARROW", K_DOWNARROW}, + {"LEFTARROW", K_LEFTARROW}, + {"RIGHTARROW", K_RIGHTARROW}, + + {"ALT", K_ALT}, + {"CTRL", K_CTRL}, + {"SHIFT", K_SHIFT}, + + {"COMMAND", K_COMMAND}, + + {"CAPSLOCK", K_CAPSLOCK}, + + + {"F1", K_F1}, + {"F2", K_F2}, + {"F3", K_F3}, + {"F4", K_F4}, + {"F5", K_F5}, + {"F6", K_F6}, + {"F7", K_F7}, + {"F8", K_F8}, + {"F9", K_F9}, + {"F10", K_F10}, + {"F11", K_F11}, + {"F12", K_F12}, + {"F13", K_F13}, + {"F14", K_F14}, + {"F15", K_F15}, + + {"INS", K_INS}, + {"DEL", K_DEL}, + {"PGDN", K_PGDN}, + {"PGUP", K_PGUP}, + {"HOME", K_HOME}, + {"END", K_END}, + + {"MOUSE1", K_MOUSE1}, + {"MOUSE2", K_MOUSE2}, + {"MOUSE3", K_MOUSE3}, + {"MOUSE4", K_MOUSE4}, + {"MOUSE5", K_MOUSE5}, + + {"MWHEELUP", K_MWHEELUP }, + {"MWHEELDOWN", K_MWHEELDOWN }, + + {"JOY1", K_JOY1}, + {"JOY2", K_JOY2}, + {"JOY3", K_JOY3}, + {"JOY4", K_JOY4}, + {"JOY5", K_JOY5}, + {"JOY6", K_JOY6}, + {"JOY7", K_JOY7}, + {"JOY8", K_JOY8}, + {"JOY9", K_JOY9}, + {"JOY10", K_JOY10}, + {"JOY11", K_JOY11}, + {"JOY12", K_JOY12}, + {"JOY13", K_JOY13}, + {"JOY14", K_JOY14}, + {"JOY15", K_JOY15}, + {"JOY16", K_JOY16}, + {"JOY17", K_JOY17}, + {"JOY18", K_JOY18}, + {"JOY19", K_JOY19}, + {"JOY20", K_JOY20}, + {"JOY21", K_JOY21}, + {"JOY22", K_JOY22}, + {"JOY23", K_JOY23}, + {"JOY24", K_JOY24}, + {"JOY25", K_JOY25}, + {"JOY26", K_JOY26}, + {"JOY27", K_JOY27}, + {"JOY28", K_JOY28}, + {"JOY29", K_JOY29}, + {"JOY30", K_JOY30}, + {"JOY31", K_JOY31}, + {"JOY32", K_JOY32}, + + {"AUX1", K_AUX1}, + {"AUX2", K_AUX2}, + {"AUX3", K_AUX3}, + {"AUX4", K_AUX4}, + {"AUX5", K_AUX5}, + {"AUX6", K_AUX6}, + {"AUX7", K_AUX7}, + {"AUX8", K_AUX8}, + {"AUX9", K_AUX9}, + {"AUX10", K_AUX10}, + {"AUX11", K_AUX11}, + {"AUX12", K_AUX12}, + {"AUX13", K_AUX13}, + {"AUX14", K_AUX14}, + {"AUX15", K_AUX15}, + {"AUX16", K_AUX16}, + + {"KP_HOME", K_KP_HOME }, + {"KP_UPARROW", K_KP_UPARROW }, + {"KP_PGUP", K_KP_PGUP }, + {"KP_LEFTARROW", K_KP_LEFTARROW }, + {"KP_5", K_KP_5 }, + {"KP_RIGHTARROW", K_KP_RIGHTARROW }, + {"KP_END", K_KP_END }, + {"KP_DOWNARROW", K_KP_DOWNARROW }, + {"KP_PGDN", K_KP_PGDN }, + {"KP_ENTER", K_KP_ENTER }, + {"KP_INS", K_KP_INS }, + {"KP_DEL", K_KP_DEL }, + {"KP_SLASH", K_KP_SLASH }, + {"KP_MINUS", K_KP_MINUS }, + {"KP_PLUS", K_KP_PLUS }, + {"KP_NUMLOCK", K_KP_NUMLOCK }, + {"KP_STAR", K_KP_STAR }, + {"KP_EQUALS", K_KP_EQUALS }, + + {"PAUSE", K_PAUSE}, + + {"SEMICOLON", ';'}, // because a raw semicolon seperates commands + + {"WORLD_0", K_WORLD_0}, + {"WORLD_1", K_WORLD_1}, + {"WORLD_2", K_WORLD_2}, + {"WORLD_3", K_WORLD_3}, + {"WORLD_4", K_WORLD_4}, + {"WORLD_5", K_WORLD_5}, + {"WORLD_6", K_WORLD_6}, + {"WORLD_7", K_WORLD_7}, + {"WORLD_8", K_WORLD_8}, + {"WORLD_9", K_WORLD_9}, + {"WORLD_10", K_WORLD_10}, + {"WORLD_11", K_WORLD_11}, + {"WORLD_12", K_WORLD_12}, + {"WORLD_13", K_WORLD_13}, + {"WORLD_14", K_WORLD_14}, + {"WORLD_15", K_WORLD_15}, + {"WORLD_16", K_WORLD_16}, + {"WORLD_17", K_WORLD_17}, + {"WORLD_18", K_WORLD_18}, + {"WORLD_19", K_WORLD_19}, + {"WORLD_20", K_WORLD_20}, + {"WORLD_21", K_WORLD_21}, + {"WORLD_22", K_WORLD_22}, + {"WORLD_23", K_WORLD_23}, + {"WORLD_24", K_WORLD_24}, + {"WORLD_25", K_WORLD_25}, + {"WORLD_26", K_WORLD_26}, + {"WORLD_27", K_WORLD_27}, + {"WORLD_28", K_WORLD_28}, + {"WORLD_29", K_WORLD_29}, + {"WORLD_30", K_WORLD_30}, + {"WORLD_31", K_WORLD_31}, + {"WORLD_32", K_WORLD_32}, + {"WORLD_33", K_WORLD_33}, + {"WORLD_34", K_WORLD_34}, + {"WORLD_35", K_WORLD_35}, + {"WORLD_36", K_WORLD_36}, + {"WORLD_37", K_WORLD_37}, + {"WORLD_38", K_WORLD_38}, + {"WORLD_39", K_WORLD_39}, + {"WORLD_40", K_WORLD_40}, + {"WORLD_41", K_WORLD_41}, + {"WORLD_42", K_WORLD_42}, + {"WORLD_43", K_WORLD_43}, + {"WORLD_44", K_WORLD_44}, + {"WORLD_45", K_WORLD_45}, + {"WORLD_46", K_WORLD_46}, + {"WORLD_47", K_WORLD_47}, + {"WORLD_48", K_WORLD_48}, + {"WORLD_49", K_WORLD_49}, + {"WORLD_50", K_WORLD_50}, + {"WORLD_51", K_WORLD_51}, + {"WORLD_52", K_WORLD_52}, + {"WORLD_53", K_WORLD_53}, + {"WORLD_54", K_WORLD_54}, + {"WORLD_55", K_WORLD_55}, + {"WORLD_56", K_WORLD_56}, + {"WORLD_57", K_WORLD_57}, + {"WORLD_58", K_WORLD_58}, + {"WORLD_59", K_WORLD_59}, + {"WORLD_60", K_WORLD_60}, + {"WORLD_61", K_WORLD_61}, + {"WORLD_62", K_WORLD_62}, + {"WORLD_63", K_WORLD_63}, + {"WORLD_64", K_WORLD_64}, + {"WORLD_65", K_WORLD_65}, + {"WORLD_66", K_WORLD_66}, + {"WORLD_67", K_WORLD_67}, + {"WORLD_68", K_WORLD_68}, + {"WORLD_69", K_WORLD_69}, + {"WORLD_70", K_WORLD_70}, + {"WORLD_71", K_WORLD_71}, + {"WORLD_72", K_WORLD_72}, + {"WORLD_73", K_WORLD_73}, + {"WORLD_74", K_WORLD_74}, + {"WORLD_75", K_WORLD_75}, + {"WORLD_76", K_WORLD_76}, + {"WORLD_77", K_WORLD_77}, + {"WORLD_78", K_WORLD_78}, + {"WORLD_79", K_WORLD_79}, + {"WORLD_80", K_WORLD_80}, + {"WORLD_81", K_WORLD_81}, + {"WORLD_82", K_WORLD_82}, + {"WORLD_83", K_WORLD_83}, + {"WORLD_84", K_WORLD_84}, + {"WORLD_85", K_WORLD_85}, + {"WORLD_86", K_WORLD_86}, + {"WORLD_87", K_WORLD_87}, + {"WORLD_88", K_WORLD_88}, + {"WORLD_89", K_WORLD_89}, + {"WORLD_90", K_WORLD_90}, + {"WORLD_91", K_WORLD_91}, + {"WORLD_92", K_WORLD_92}, + {"WORLD_93", K_WORLD_93}, + {"WORLD_94", K_WORLD_94}, + {"WORLD_95", K_WORLD_95}, + + {"WINDOWS", K_SUPER}, + {"COMPOSE", K_COMPOSE}, + {"MODE", K_MODE}, + {"HELP", K_HELP}, + {"PRINT", K_PRINT}, + {"SYSREQ", K_SYSREQ}, + {"SCROLLOCK", K_SCROLLOCK }, + {"BREAK", K_BREAK}, + {"MENU", K_MENU}, + {"POWER", K_POWER}, + {"EURO", K_EURO}, + {"UNDO", K_UNDO}, + + {NULL,0} +}; + +/* +============================================================================= + +EDIT FIELDS + +============================================================================= +*/ + + +/* +=================== +Field_Draw + +Handles horizontal scrolling and cursor blinking +x, y, and width are in pixels +=================== +*/ +void Field_VariableSizeDraw( field_t *edit, int x, int y, int width, int size, qboolean showCursor, + qboolean noColorEscape ) { + int len; + int drawLen; + int prestep; + int cursorChar; + char str[MAX_STRING_CHARS]; + int i; + + drawLen = edit->widthInChars - 1; // - 1 so there is always a space for the cursor + len = strlen( edit->buffer ); + + // guarantee that cursor will be visible + if ( len <= drawLen ) { + prestep = 0; + } else { + if ( edit->scroll + drawLen > len ) { + edit->scroll = len - drawLen; + if ( edit->scroll < 0 ) { + edit->scroll = 0; + } + } + prestep = edit->scroll; + } + + if ( prestep + drawLen > len ) { + drawLen = len - prestep; + } + + // extract characters from the field at + if ( drawLen >= MAX_STRING_CHARS ) { + Com_Error( ERR_DROP, "drawLen >= MAX_STRING_CHARS" ); + } + + Com_Memcpy( str, edit->buffer + prestep, drawLen ); + str[ drawLen ] = 0; + + // draw it + if ( size == SMALLCHAR_WIDTH ) { + float color[4]; + + color[0] = color[1] = color[2] = color[3] = 1.0; + SCR_DrawSmallStringExt( x, y, str, color, qfalse, noColorEscape ); + } else { + // draw big string with drop shadow + SCR_DrawBigString( x, y, str, 1.0, noColorEscape ); + } + + // draw the cursor + if ( showCursor ) { + if ( (int)( cls.realtime >> 8 ) & 1 ) { + return; // off blink + } + + if ( key_overstrikeMode ) { + cursorChar = 11; + } else { + cursorChar = 10; + } + + i = drawLen - strlen( str ); + + if ( size == SMALLCHAR_WIDTH ) { + SCR_DrawSmallChar( x + ( edit->cursor - prestep - i ) * size, y, cursorChar ); + } else { + str[0] = cursorChar; + str[1] = 0; + SCR_DrawBigString( x + ( edit->cursor - prestep - i ) * size, y, str, 1.0, qfalse ); + + } + } +} + +void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ) +{ + Field_VariableSizeDraw( edit, x, y, width, SMALLCHAR_WIDTH, showCursor, noColorEscape ); +} + +void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ) +{ + Field_VariableSizeDraw( edit, x, y, width, BIGCHAR_WIDTH, showCursor, noColorEscape ); +} + +/* +================ +Field_Paste +================ +*/ +void Field_Paste( field_t *edit ) { + char *cbd; + int pasteLen, i; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + return; + } + + // send as if typed, so insert / overstrike works properly + pasteLen = strlen( cbd ); + for ( i = 0 ; i < pasteLen ; i++ ) { + Field_CharEvent( edit, cbd[i] ); + } + + Z_Free( cbd ); +} + +/* +================= +Field_KeyDownEvent + +Performs the basic line editing functions for the console, +in-game talk, and menu fields + +Key events are used for non-printable characters, others are gotten from char events. +================= +*/ +void Field_KeyDownEvent( field_t *edit, int key ) { + int len; + + // shift-insert is paste + if ( ( ( key == K_INS ) || ( key == K_KP_INS ) ) && keys[K_SHIFT].down ) { + Field_Paste( edit ); + return; + } + + key = tolower( key ); + len = strlen( edit->buffer ); + + switch ( key ) { + case K_DEL: + if ( edit->cursor < len ) { + memmove( edit->buffer + edit->cursor, + edit->buffer + edit->cursor + 1, len - edit->cursor ); + } + break; + + case K_RIGHTARROW: + if ( edit->cursor < len ) { + edit->cursor++; + } + break; + + case K_LEFTARROW: + if ( edit->cursor > 0 ) { + edit->cursor--; + } + break; + + case K_HOME: + edit->cursor = 0; + break; + + case K_END: + edit->cursor = len; + break; + + case K_INS: + key_overstrikeMode = !key_overstrikeMode; + break; + + default: + break; + } + + // Change scroll if cursor is no longer visible + if ( edit->cursor < edit->scroll ) { + edit->scroll = edit->cursor; + } else if ( edit->cursor >= edit->scroll + edit->widthInChars && edit->cursor <= len ) { + edit->scroll = edit->cursor - edit->widthInChars + 1; + } +} + +/* +================== +Field_CharEvent +================== +*/ +void Field_CharEvent( field_t *edit, int ch ) { + int len; + + if ( ch == 'v' - 'a' + 1 ) { // ctrl-v is paste + Field_Paste( edit ); + return; + } + + if ( ch == 'c' - 'a' + 1 ) { // ctrl-c clears the field + Field_Clear( edit ); + return; + } + + len = strlen( edit->buffer ); + + if ( ch == 'h' - 'a' + 1 ) { // ctrl-h is backspace + if ( edit->cursor > 0 ) { + memmove( edit->buffer + edit->cursor - 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->cursor--; + if ( edit->cursor < edit->scroll ) + { + edit->scroll--; + } + } + return; + } + + if ( ch == 'a' - 'a' + 1 ) { // ctrl-a is home + edit->cursor = 0; + edit->scroll = 0; + return; + } + + if ( ch == 'e' - 'a' + 1 ) { // ctrl-e is end + edit->cursor = len; + edit->scroll = edit->cursor - edit->widthInChars; + return; + } + + // + // ignore any other non printable chars + // + if ( ch < 32 ) { + return; + } + + if ( key_overstrikeMode ) { + // - 2 to leave room for the leading slash and trailing \0 + if ( edit->cursor == MAX_EDIT_LINE - 2 ) + return; + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } else { // insert mode + // - 2 to leave room for the leading slash and trailing \0 + if ( len == MAX_EDIT_LINE - 2 ) { + return; // all full + } + memmove( edit->buffer + edit->cursor + 1, + edit->buffer + edit->cursor, len + 1 - edit->cursor ); + edit->buffer[edit->cursor] = ch; + edit->cursor++; + } + + + if ( edit->cursor >= edit->widthInChars ) { + edit->scroll++; + } + + if ( edit->cursor == len + 1) { + edit->buffer[edit->cursor] = 0; + } +} + +/* +============================================================================= + +CONSOLE LINE EDITING + +============================================================================== +*/ + +/* +==================== +Console_Key + +Handles history and console scrollback +==================== +*/ +extern cvar_t *con_allowChat; +void Console_Key (int key) { + // ctrl-L clears screen + if ( key == 'l' && keys[K_CTRL].down ) { + Cbuf_AddText ("clear\n"); + return; + } + + // enter finishes the line + if ( key == K_ENTER || key == K_KP_ENTER ) { + if( !con_allowChat->integer ) { + Com_Printf( "]%s\n", g_consoleField.buffer ); + + if( !g_consoleField.buffer[0] ) { + return; // empty lines just scroll the console without adding to history + } + // leading slash is an explicit command + else if( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) { + Cbuf_AddText( g_consoleField.buffer + 1 ); // valid command + Cbuf_AddText( "\n" ); + } + else { + Cbuf_AddText( g_consoleField.buffer ); // valid command + Cbuf_AddText( "\n" ); + } + } + else { + // if not in the game explicitly prepend a slash if needed + if ( clc.state != CA_ACTIVE && + g_consoleField.buffer[0] && + g_consoleField.buffer[0] != '\\' && + g_consoleField.buffer[0] != '/' ) { + char temp[MAX_EDIT_LINE-1]; + + Q_strncpyz( temp, g_consoleField.buffer, sizeof( temp ) ); + Com_sprintf( g_consoleField.buffer, sizeof( g_consoleField.buffer ), "\\%s", temp ); + g_consoleField.cursor++; + } + + Com_Printf ( "]%s\n", g_consoleField.buffer ); + + // leading slash is an explicit command + if ( g_consoleField.buffer[0] == '\\' || g_consoleField.buffer[0] == '/' ) { + Cbuf_AddText( g_consoleField.buffer+1 ); // valid command + Cbuf_AddText ("\n"); + } else { + // other text will be chat messages + if ( !g_consoleField.buffer[0] ) { + return; // empty lines just scroll the console without adding to history + } else { + Cbuf_AddText ("cmd say "); + Cbuf_AddText( g_consoleField.buffer ); + Cbuf_AddText ("\n"); + } + } + } + + // copy line to history buffer + historyEditLines[nextHistoryLine % COMMAND_HISTORY] = g_consoleField; + nextHistoryLine++; + historyLine = nextHistoryLine; + + Field_Clear( &g_consoleField ); + + g_consoleField.widthInChars = g_console_field_width; + + CL_SaveConsoleHistory( ); + + if ( clc.state == CA_DISCONNECTED ) { + SCR_UpdateScreen (); // force an update, because the command + } // may take some time + return; + } + + // command completion + + if (key == K_TAB) { + Field_AutoComplete(&g_consoleField); + return; + } + + // command history (ctrl-p ctrl-n for unix style) + + if ( (key == K_MWHEELUP && keys[K_SHIFT].down) || ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) || + ( ( tolower(key) == 'p' ) && keys[K_CTRL].down ) ) { + if ( nextHistoryLine - historyLine < COMMAND_HISTORY + && historyLine > 0 ) { + historyLine--; + } + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + if ( (key == K_MWHEELDOWN && keys[K_SHIFT].down) || ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) || + ( ( tolower(key) == 'n' ) && keys[K_CTRL].down ) ) { + historyLine++; + if (historyLine >= nextHistoryLine) { + historyLine = nextHistoryLine; + Field_Clear( &g_consoleField ); + g_consoleField.widthInChars = g_console_field_width; + return; + } + g_consoleField = historyEditLines[ historyLine % COMMAND_HISTORY ]; + return; + } + + // console scrolling + if ( key == K_PGUP ) { + Con_PageUp(); + return; + } + + if ( key == K_PGDN) { + Con_PageDown(); + return; + } + + if ( key == K_MWHEELUP) { //----(SA) added some mousewheel functionality to the console + Con_PageUp(); + if(keys[K_CTRL].down) { // hold to accelerate scrolling + Con_PageUp(); + Con_PageUp(); + } + return; + } + + if ( key == K_MWHEELDOWN) { //----(SA) added some mousewheel functionality to the console + Con_PageDown(); + if(keys[K_CTRL].down) { // hold to accelerate scrolling + Con_PageDown(); + Con_PageDown(); + } + return; + } + + // ctrl-home = top of console + if ( key == K_HOME && keys[K_CTRL].down ) { + Con_Top(); + return; + } + + // ctrl-end = bottom of console + if ( key == K_END && keys[K_CTRL].down ) { + Con_Bottom(); + return; + } + + // pass to the normal editline routine + Field_KeyDownEvent( &g_consoleField, key ); +} + +//============================================================================ + + +/* +================ +Message_Key + +In game talk message +================ +*/ +void Message_Key( int key ) { + + char buffer[MAX_STRING_CHARS]; + + + if (key == K_ESCAPE) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_MESSAGE ); + Field_Clear( &chatField ); + return; + } + + if ( key == K_ENTER || key == K_KP_ENTER ) + { + if ( chatField.buffer[0] && clc.state == CA_ACTIVE ) { + if (chat_playerNum != -1 ) + + Com_sprintf( buffer, sizeof( buffer ), "tell %i \"%s\"\n", chat_playerNum, chatField.buffer ); + + else if (chat_team) + + Com_sprintf( buffer, sizeof( buffer ), "say_team \"%s\"\n", chatField.buffer ); + else + Com_sprintf( buffer, sizeof( buffer ), "say \"%s\"\n", chatField.buffer ); + + + + CL_AddReliableCommand(buffer, qfalse); + } + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_MESSAGE ); + Field_Clear( &chatField ); + return; + } + + Field_KeyDownEvent( &chatField, key ); +} + +//============================================================================ + + +qboolean Key_GetOverstrikeMode( void ) { + return key_overstrikeMode; +} + + +void Key_SetOverstrikeMode( qboolean state ) { + key_overstrikeMode = state; +} + + +/* +=================== +Key_IsDown +=================== +*/ +qboolean Key_IsDown( int keynum ) { + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return qfalse; + } + + return keys[keynum].down; +} + +/* +=================== +Key_StringToKeynum + +Returns a key number to be used to index keys[] by looking at +the given string. Single ascii characters return themselves, while +the K_* names are matched up. + +0x11 will be interpreted as raw hex, which will allow new controlers + +to be configured even if they don't have defined names. +=================== +*/ +int Key_StringToKeynum( char *str ) { + keyname_t *kn; + + if ( !str || !str[0] ) { + return -1; + } + if ( !str[1] ) { + return str[0]; + } + + // check for hex code + if ( strlen( str ) == 4 ) { + int n = Com_HexStrToInt( str ); + + if ( n >= 0 ) { + return n; + } + } + + // scan for a text match + for ( kn=keynames ; kn->name ; kn++ ) { + if ( !Q_stricmp( str,kn->name ) ) + return kn->keynum; + } + + return -1; +} + +/* +=================== +Key_KeynumToString + +Returns a string (either a single ascii char, a K_* name, or a 0x11 hex string) for the +given keynum. +=================== +*/ +char *Key_KeynumToString( int keynum ) { + keyname_t *kn; + static char tinystr[5]; + int i, j; + + if ( keynum == -1 ) { + return ""; + } + + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return ""; + } + + // check for printable ascii (don't use quote) + if ( keynum > 32 && keynum < 127 && keynum != '"' && keynum != ';' ) { + tinystr[0] = keynum; + tinystr[1] = 0; + return tinystr; + } + + // check for a key string + for ( kn=keynames ; kn->name ; kn++ ) { + if (keynum == kn->keynum) { + return kn->name; + } + } + + // make a hex string + i = keynum >> 4; + j = keynum & 15; + + tinystr[0] = '0'; + tinystr[1] = 'x'; + tinystr[2] = i > 9 ? i - 10 + 'a' : i + '0'; + tinystr[3] = j > 9 ? j - 10 + 'a' : j + '0'; + tinystr[4] = 0; + + return tinystr; +} + + +/* +=================== +Key_SetBinding +=================== +*/ +void Key_SetBinding( int keynum, const char *binding ) { + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return; + } + + // free old bindings + if ( keys[ keynum ].binding ) { + Z_Free( keys[ keynum ].binding ); + } + + // allocate memory for new binding + keys[keynum].binding = CopyString( binding ); + + // consider this like modifying an archived cvar, so the + // file write will be triggered at the next oportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; +} + + +/* +=================== +Key_GetBinding +=================== +*/ +char *Key_GetBinding( int keynum ) { + if ( keynum < 0 || keynum >= MAX_KEYS ) { + return ""; + } + + return keys[ keynum ].binding; +} + +/* +=================== +Key_GetKey +=================== +*/ + +int Key_GetKey(const char *binding) { + int i; + + if (binding) { + for (i=0 ; i < MAX_KEYS ; i++) { + if (keys[i].binding && Q_stricmp(binding, keys[i].binding) == 0) { + return i; + } + } + } + return -1; +} + +/* +=================== +Key_Unbind_f +=================== +*/ +void Key_Unbind_f (void) +{ + int b; + + if (Cmd_Argc() != 2) + { + Com_Printf ("unbind : remove commands from a key\n"); + return; + } + + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + Key_SetBinding (b, ""); +} + +/* +=================== +Key_Unbindall_f +=================== +*/ +void Key_Unbindall_f (void) +{ + int i; + + for (i=0 ; i < MAX_KEYS; i++) + if (keys[i].binding) + Key_SetBinding (i, ""); +} + + +/* +=================== +Key_Bind_f +=================== +*/ +void Key_Bind_f (void) +{ + int i, c, b; + char cmd[1024]; + + c = Cmd_Argc(); + + if (c < 2) + { + Com_Printf ("bind [command] : attach a command to a key\n"); + return; + } + b = Key_StringToKeynum (Cmd_Argv(1)); + if (b==-1) + { + Com_Printf ("\"%s\" isn't a valid key\n", Cmd_Argv(1)); + return; + } + + if (c == 2) + { + if (keys[b].binding) + Com_Printf ("\"%s\" = \"%s\"\n", Cmd_Argv(1), keys[b].binding ); + else + Com_Printf ("\"%s\" is not bound\n", Cmd_Argv(1) ); + return; + } + +// copy the rest of the command line + cmd[0] = 0; // start out with a null string + for (i=2 ; i< c ; i++) + { + strcat (cmd, Cmd_Argv(i)); + if (i != (c-1)) + strcat (cmd, " "); + } + + Key_SetBinding (b, cmd); +} + +/* +============ +Key_WriteBindings + +Writes lines containing "bind key value" +============ +*/ +void Key_WriteBindings( fileHandle_t f ) { + int i; + + FS_Printf (f, "unbindall\n" ); + + for (i=0 ; i args ) + Field_CompleteKeyname( ); + } +} + +/* +==================== +Key_CompleteBind +==================== +*/ +static void Key_CompleteBind( char *args, int argNum ) +{ + char *p; + + if( argNum == 2 ) + { + // Skip "bind " + p = Com_SkipTokens( args, 1, " " ); + + if( p > args ) + Field_CompleteKeyname( ); + } + else if( argNum >= 3 ) + { + // Skip "bind " + p = Com_SkipTokens( args, 2, " " ); + + if( p > args ) + Field_CompleteCommand( p, qtrue, qtrue ); + } +} + +/* +=================== +CL_InitKeyCommands +=================== +*/ +void CL_InitKeyCommands( void ) { + // register our functions + Cmd_AddCommand ("bind",Key_Bind_f); + Cmd_SetCommandCompletionFunc( "bind", Key_CompleteBind ); + Cmd_AddCommand ("unbind",Key_Unbind_f); + Cmd_SetCommandCompletionFunc( "unbind", Key_CompleteUnbind ); + Cmd_AddCommand ("unbindall",Key_Unbindall_f); + Cmd_AddCommand ("bindlist",Key_Bindlist_f); +} + +/* +=================== +CL_ParseBinding + +Execute the commands in the bind string +=================== +*/ +void CL_ParseBinding( int key, qboolean down, unsigned time ) +{ + char buf[ MAX_STRING_CHARS ], *p = buf, *end; + + if( !keys[key].binding || !keys[key].binding[0] ) + return; + Q_strncpyz( buf, keys[key].binding, sizeof( buf ) ); + + while( 1 ) + { + while( isspace( *p ) ) + p++; + end = strchr( p, ';' ); + if( end ) + *end = '\0'; + if( *p == '+' ) + { + // button commands add keynum and time as parameters + // so that multiple sources can be discriminated and + // subframe corrected + char cmd[1024]; + Com_sprintf( cmd, sizeof( cmd ), "%c%s %d %d\n", + ( down ) ? '+' : '-', p + 1, key, time ); + Cbuf_AddText( cmd ); + } + else if( down ) + { + // normal commands only execute on key press + Cbuf_AddText( p ); + Cbuf_AddText( "\n" ); + } + if( !end ) + break; + p = end + 1; + } +} + +/* +=================== +CL_KeyDownEvent + +Called by CL_KeyEvent to handle a keypress +=================== +*/ +void CL_KeyDownEvent( int key, unsigned time ) +{ + keys[key].down = qtrue; + keys[key].repeats++; + if( keys[key].repeats == 1 && key != K_SCROLLOCK && key != K_KP_NUMLOCK && key != K_CAPSLOCK ) + anykeydown++; + + if( keys[K_ALT].down && key == K_ENTER ) + { + Cvar_SetValue( "r_fullscreen", + !Cvar_VariableIntegerValue( "r_fullscreen" ) ); + return; + } + + // console key is hardcoded, so the user can never unbind it + if( key == K_CONSOLE || ( keys[K_SHIFT].down && key == K_ESCAPE ) ) + { + Con_ToggleConsole_f (); + Key_ClearStates (); + return; + } + + + // keys can still be used for bound actions + if ( ( key < 128 || key == K_MOUSE1 ) && + ( clc.demoplaying || clc.state == CA_CINEMATIC ) && Key_GetCatcher( ) == 0 ) { + + if (Cvar_VariableValue ("com_cameraMode") == 0) { + Cvar_Set ("nextdemo",""); + key = K_ESCAPE; + } + } + + // escape is always handled special + if ( key == K_ESCAPE ) { + if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) { + // clear message mode + Message_Key( key ); + return; + } + + // escape always gets out of CGAME stuff + if (Key_GetCatcher( ) & KEYCATCH_CGAME) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CGAME ); + VM_Call (cgvm, CG_EVENT_HANDLING, CGAME_EVENT_NONE); + return; + } + + if ( !( Key_GetCatcher( ) & KEYCATCH_UI ) ) { + if ( clc.state == CA_ACTIVE && !clc.demoplaying ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_INGAME ); + } + else if ( clc.state != CA_DISCONNECTED ) { + CL_Disconnect_f(); + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + return; + } + + VM_Call( uivm, UI_KEY_EVENT, key, qtrue ); + return; + } + + // distribute the key down event to the apropriate handler + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) { + Console_Key( key ); + } else if ( Key_GetCatcher( ) & KEYCATCH_UI ) { + if ( uivm ) { + VM_Call( uivm, UI_KEY_EVENT, key, qtrue ); + } + } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME ) { + if ( cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, qtrue ); + } + } else if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) { + Message_Key( key ); + } else if ( clc.state == CA_DISCONNECTED ) { + Console_Key( key ); + } else { + // send the bound action + CL_ParseBinding( key, qtrue, time ); + } + return; +} + +/* +=================== +CL_KeyUpEvent + +Called by CL_KeyEvent to handle a keyrelease +=================== +*/ +void CL_KeyUpEvent( int key, unsigned time ) +{ + keys[key].repeats = 0; + keys[key].down = qfalse; + if (key != K_SCROLLOCK && key != K_KP_NUMLOCK && key != K_CAPSLOCK) + anykeydown--; + + if (anykeydown < 0) { + anykeydown = 0; + } + + // don't process key-up events for the console key + if ( key == K_CONSOLE || ( key == K_ESCAPE && keys[K_SHIFT].down ) ) + return; + + // + // key up events only perform actions if the game key binding is + // a button command (leading + sign). These will be processed even in + // console mode and menu mode, to keep the character from continuing + // an action started before a mode switch. + // + if( clc.state != CA_DISCONNECTED ) + CL_ParseBinding( key, qfalse, time ); + + if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_KEY_EVENT, key, qfalse ); + } else if ( Key_GetCatcher( ) & KEYCATCH_CGAME && cgvm ) { + VM_Call( cgvm, CG_KEY_EVENT, key, qfalse ); + } +} + +/* +=================== +CL_KeyEvent + +Called by the system for both key up and key down events +=================== +*/ +void CL_KeyEvent (int key, qboolean down, unsigned time) { + if( down ) + CL_KeyDownEvent( key, time ); + else + CL_KeyUpEvent( key, time ); +} + +/* +=================== +CL_CharEvent + +Normal keyboard characters, already shifted / capslocked / etc +=================== +*/ +void CL_CharEvent( int key ) { + // delete is not a printable character and is + // otherwise handled by Field_KeyDownEvent + if ( key == 127 ) { + return; + } + + // distribute the key down event to the apropriate handler + if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) + { + Field_CharEvent( &g_consoleField, key ); + } + else if ( Key_GetCatcher( ) & KEYCATCH_UI ) + { + VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue ); + } + else if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE ) + { + Field_CharEvent( &chatField, key ); + } + else if ( clc.state == CA_DISCONNECTED ) + { + Field_CharEvent( &g_consoleField, key ); + } +} + + +/* +=================== +Key_ClearStates +=================== +*/ +void Key_ClearStates (void) +{ + int i; + + anykeydown = 0; + + for ( i=0 ; i < MAX_KEYS ; i++ ) { + if (i == K_SCROLLOCK || i == K_KP_NUMLOCK || i == K_CAPSLOCK) + continue; + + if ( keys[i].down ) { + CL_KeyEvent( i, qfalse, 0 ); + + } + keys[i].down = 0; + keys[i].repeats = 0; + } +} + +static int keyCatchers = 0; + +/* +==================== +Key_GetCatcher +==================== +*/ +int Key_GetCatcher( void ) { + return keyCatchers; +} + +/* +==================== +Key_SetCatcher +==================== +*/ +void Key_SetCatcher( int catcher ) { + // If the catcher state is changing, clear all key states + if( catcher != keyCatchers ) + Key_ClearStates( ); + + keyCatchers = catcher; +} + +// This must not exceed MAX_CMD_LINE +#define MAX_CONSOLE_SAVE_BUFFER 1024 +#define CONSOLE_HISTORY_FILE "q3history" +static char consoleSaveBuffer[ MAX_CONSOLE_SAVE_BUFFER ]; +static int consoleSaveBufferSize = 0; + +/* +================ +CL_LoadConsoleHistory + +Load the console history from cl_consoleHistory +================ +*/ +void CL_LoadConsoleHistory( void ) +{ + char *token, *text_p; + int i, numChars, numLines = 0; + fileHandle_t f; + + consoleSaveBufferSize = FS_FOpenFileRead( CONSOLE_HISTORY_FILE, &f, qfalse ); + if( !f ) + { + Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE ); + return; + } + + if( consoleSaveBufferSize <= MAX_CONSOLE_SAVE_BUFFER && + FS_Read( consoleSaveBuffer, consoleSaveBufferSize, f ) == consoleSaveBufferSize ) + { + text_p = consoleSaveBuffer; + + for( i = COMMAND_HISTORY - 1; i >= 0; i-- ) + { + if( !*( token = COM_Parse( &text_p ) ) ) + break; + + historyEditLines[ i ].cursor = atoi( token ); + + if( !*( token = COM_Parse( &text_p ) ) ) + break; + + historyEditLines[ i ].scroll = atoi( token ); + + if( !*( token = COM_Parse( &text_p ) ) ) + break; + + numChars = atoi( token ); + text_p++; + if( numChars > ( strlen( consoleSaveBuffer ) - ( text_p - consoleSaveBuffer ) ) ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: probable corrupt history\n" ); + break; + } + Com_Memcpy( historyEditLines[ i ].buffer, + text_p, numChars ); + historyEditLines[ i ].buffer[ numChars ] = '\0'; + text_p += numChars; + + numLines++; + } + + memmove( &historyEditLines[ 0 ], &historyEditLines[ i + 1 ], + numLines * sizeof( field_t ) ); + for( i = numLines; i < COMMAND_HISTORY; i++ ) + Field_Clear( &historyEditLines[ i ] ); + + historyLine = nextHistoryLine = numLines; + } + else + Com_Printf( "Couldn't read %s.\n", CONSOLE_HISTORY_FILE ); + + FS_FCloseFile( f ); +} + +/* +================ +CL_SaveConsoleHistory + +Save the console history into the cvar cl_consoleHistory +so that it persists across invocations of q3 +================ +*/ +void CL_SaveConsoleHistory( void ) +{ + int i; + int lineLength, saveBufferLength, additionalLength; + fileHandle_t f; + + consoleSaveBuffer[ 0 ] = '\0'; + + i = ( nextHistoryLine - 1 ) % COMMAND_HISTORY; + do + { + if( historyEditLines[ i ].buffer[ 0 ] ) + { + lineLength = strlen( historyEditLines[ i ].buffer ); + saveBufferLength = strlen( consoleSaveBuffer ); + + //ICK + additionalLength = lineLength + strlen( "999 999 999 " ); + + if( saveBufferLength + additionalLength < MAX_CONSOLE_SAVE_BUFFER ) + { + Q_strcat( consoleSaveBuffer, MAX_CONSOLE_SAVE_BUFFER, + va( "%d %d %d %s ", + historyEditLines[ i ].cursor, + historyEditLines[ i ].scroll, + lineLength, + historyEditLines[ i ].buffer ) ); + } + else + break; + } + i = ( i - 1 + COMMAND_HISTORY ) % COMMAND_HISTORY; + } + while( i != ( nextHistoryLine - 1 ) % COMMAND_HISTORY ); + + consoleSaveBufferSize = strlen( consoleSaveBuffer ); + + f = FS_FOpenFileWrite( CONSOLE_HISTORY_FILE ); + if( !f ) + { + Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE ); + return; + } + + if( FS_Write( consoleSaveBuffer, consoleSaveBufferSize, f ) < consoleSaveBufferSize ) + Com_Printf( "Couldn't write %s.\n", CONSOLE_HISTORY_FILE ); + + FS_FCloseFile( f ); +} diff --git a/code/client/cl_main.c b/code/client/cl_main.c new file mode 100644 index 0000000..593de8e --- /dev/null +++ b/code/client/cl_main.c @@ -0,0 +1,4647 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_main.c -- client main loop + +#include "client.h" +#include + +#include "../sys/sys_local.h" +#include "../sys/sys_loadlib.h" + +#ifdef USE_MUMBLE +#include "libmumblelink.h" +#endif + +#ifdef USE_MUMBLE +cvar_t *cl_useMumble; +cvar_t *cl_mumbleScale; +#endif + +#ifdef USE_VOIP +cvar_t *cl_voipUseVAD; +cvar_t *cl_voipVADThreshold; +cvar_t *cl_voipSend; +cvar_t *cl_voipSendTarget; +cvar_t *cl_voipGainDuringCapture; +cvar_t *cl_voipCaptureMult; +cvar_t *cl_voipShowMeter; +cvar_t *cl_voip; +#endif + +#ifdef USE_RENDERER_DLOPEN +cvar_t *cl_renderer; +#endif + +cvar_t *cl_nodelta; +cvar_t *cl_debugMove; + +cvar_t *cl_noprint; +#ifdef UPDATE_SERVER_NAME +cvar_t *cl_motd; +#endif + +cvar_t *rcon_client_password; +cvar_t *rconAddress; + +cvar_t *cl_timeout; +cvar_t *cl_maxpackets; +cvar_t *cl_packetdup; +cvar_t *cl_timeNudge; +cvar_t *cl_showTimeDelta; +cvar_t *cl_freezeDemo; + +cvar_t *cl_shownet; +cvar_t *cl_showSend; +cvar_t *cl_timedemo; +cvar_t *cl_timedemoLog; +cvar_t *cl_autoRecordDemo; +cvar_t *cl_aviFrameRate; +cvar_t *cl_aviMotionJpeg; +cvar_t *cl_forceavidemo; + +cvar_t *cl_freelook; +cvar_t *cl_sensitivity; + +cvar_t *cl_mouseAccel; +cvar_t *cl_mouseAccelOffset; +cvar_t *cl_mouseAccelStyle; +cvar_t *cl_showMouseRate; + +cvar_t *m_pitch; +cvar_t *m_pitchVeh; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; +cvar_t *m_filter; + +cvar_t *j_pitch; +cvar_t *j_yaw; +cvar_t *j_forward; +cvar_t *j_side; +cvar_t *j_pitch_axis; +cvar_t *j_yaw_axis; +cvar_t *j_forward_axis; +cvar_t *j_side_axis; + +cvar_t *cl_activeAction; + +cvar_t *cl_motdString; + +cvar_t *cl_allowDownload; +cvar_t *cl_inGameVideo; + +cvar_t *cl_serverStatusResendTime; +cvar_t *cl_trn; + +cvar_t *cl_lanForcePackets; + +cvar_t *cl_guidServerUniq; + +cvar_t *cl_consoleKeys; + +clientActive_t cl; +clientConnection_t clc; +clientStatic_t cls; +vm_t *cgvm; + +// Structure containing functions exported from refresh DLL +refexport_t re; +#ifdef USE_RENDERER_DLOPEN +static void *rendererLib = NULL; +#endif + +ping_t cl_pinglist[MAX_PINGREQUESTS]; + +typedef struct serverStatus_s +{ + char string[BIG_INFO_STRING]; + netadr_t address; + int time, startTime; + qboolean pending; + qboolean print; + qboolean retrieved; +} serverStatus_t; + +serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS]; +int serverStatusCount; + +#if defined __USEA3D && defined __A3D_GEOM + void hA3Dg_ExportRenderGeom (refexport_t *incoming_re); +#endif + +static int noGameRestart = qfalse; + +extern void SV_BotFrame( int time ); +void CL_CheckForResend( void ); +void CL_ShowIP_f(void); +void CL_ServerStatus_f(void); +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ); + +/* +=============== +CL_CDDialog + +Called by Com_Error when a cd is needed +=============== +*/ +void CL_CDDialog( void ) { + cls.cddialog = qtrue; // start it next frame +} + +#ifdef USE_MUMBLE +static +void CL_UpdateMumble(void) +{ + vec3_t pos, forward, up; + float scale = cl_mumbleScale->value; + float tmp; + + if(!cl_useMumble->integer) + return; + + // !!! FIXME: not sure if this is even close to correct. + AngleVectors( cl.snap.ps.viewangles, forward, NULL, up); + + pos[0] = cl.snap.ps.origin[0] * scale; + pos[1] = cl.snap.ps.origin[2] * scale; + pos[2] = cl.snap.ps.origin[1] * scale; + + tmp = forward[1]; + forward[1] = forward[2]; + forward[2] = tmp; + + tmp = up[1]; + up[1] = up[2]; + up[2] = tmp; + + if(cl_useMumble->integer > 1) { + fprintf(stderr, "%f %f %f, %f %f %f, %f %f %f\n", + pos[0], pos[1], pos[2], + forward[0], forward[1], forward[2], + up[0], up[1], up[2]); + } + + mumble_update_coordinates(pos, forward, up); +} +#endif + + +#ifdef USE_VOIP +static +void CL_UpdateVoipIgnore(const char *idstr, qboolean ignore) +{ + if ((*idstr >= '0') && (*idstr <= '9')) { + const int id = atoi(idstr); + if ((id >= 0) && (id < MAX_CLIENTS)) { + clc.voipIgnore[id] = ignore; + CL_AddReliableCommand(va("voip %s %d", + ignore ? "ignore" : "unignore", id), qfalse); + Com_Printf("VoIP: %s ignoring player #%d\n", + ignore ? "Now" : "No longer", id); + return; + } + } + Com_Printf("VoIP: invalid player ID#\n"); +} + +static +void CL_UpdateVoipGain(const char *idstr, float gain) +{ + if ((*idstr >= '0') && (*idstr <= '9')) { + const int id = atoi(idstr); + if (gain < 0.0f) + gain = 0.0f; + if ((id >= 0) && (id < MAX_CLIENTS)) { + clc.voipGain[id] = gain; + Com_Printf("VoIP: player #%d gain now set to %f\n", id, gain); + } + } +} + +void CL_Voip_f( void ) +{ + const char *cmd = Cmd_Argv(1); + const char *reason = NULL; + + if (clc.state != CA_ACTIVE) + reason = "Not connected to a server"; + else if (!clc.speexInitialized) + reason = "Speex not initialized"; + else if (!clc.voipEnabled) + reason = "Server doesn't support VoIP"; + else if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) + reason = "running in single-player mode"; + + if (reason != NULL) { + Com_Printf("VoIP: command ignored: %s\n", reason); + return; + } + + if (strcmp(cmd, "ignore") == 0) { + CL_UpdateVoipIgnore(Cmd_Argv(2), qtrue); + } else if (strcmp(cmd, "unignore") == 0) { + CL_UpdateVoipIgnore(Cmd_Argv(2), qfalse); + } else if (strcmp(cmd, "gain") == 0) { + if (Cmd_Argc() > 3) { + CL_UpdateVoipGain(Cmd_Argv(2), atof(Cmd_Argv(3))); + } else if (Q_isanumber(Cmd_Argv(2))) { + int id = atoi(Cmd_Argv(2)); + if (id >= 0 && id < MAX_CLIENTS) { + Com_Printf("VoIP: current gain for player #%d " + "is %f\n", id, clc.voipGain[id]); + } else { + Com_Printf("VoIP: invalid player ID#\n"); + } + } else { + Com_Printf("usage: voip gain [value]\n"); + } + } else if (strcmp(cmd, "muteall") == 0) { + Com_Printf("VoIP: muting incoming voice\n"); + CL_AddReliableCommand("voip muteall", qfalse); + clc.voipMuteAll = qtrue; + } else if (strcmp(cmd, "unmuteall") == 0) { + Com_Printf("VoIP: unmuting incoming voice\n"); + CL_AddReliableCommand("voip unmuteall", qfalse); + clc.voipMuteAll = qfalse; + } else { + Com_Printf("usage: voip [un]ignore \n" + " voip [un]muteall\n" + " voip gain [value]\n"); + } +} + + +static +void CL_VoipNewGeneration(void) +{ + // don't have a zero generation so new clients won't match, and don't + // wrap to negative so MSG_ReadLong() doesn't "fail." + clc.voipOutgoingGeneration++; + if (clc.voipOutgoingGeneration <= 0) + clc.voipOutgoingGeneration = 1; + clc.voipPower = 0.0f; + clc.voipOutgoingSequence = 0; +} + +/* +=============== +CL_VoipParseTargets + +sets clc.voipTargets according to cl_voipSendTarget +Generally we don't want who's listening to change during a transmission, +so this is only called when the key is first pressed +=============== +*/ +void CL_VoipParseTargets(void) +{ + const char *target = cl_voipSendTarget->string; + char *end; + int val; + + Com_Memset(clc.voipTargets, 0, sizeof(clc.voipTargets)); + clc.voipFlags &= ~VOIP_SPATIAL; + + while(target) + { + while(*target == ',' || *target == ' ') + target++; + + if(!*target) + break; + + if(isdigit(*target)) + { + val = strtol(target, &end, 10); + target = end; + } + else + { + if(!Q_stricmpn(target, "all", 3)) + { + Com_Memset(clc.voipTargets, ~0, sizeof(clc.voipTargets)); + return; + } + if(!Q_stricmpn(target, "spatial", 7)) + { + clc.voipFlags |= VOIP_SPATIAL; + target += 7; + continue; + } + else + { + if(!Q_stricmpn(target, "attacker", 8)) + { + val = VM_Call(cgvm, CG_LAST_ATTACKER); + target += 8; + } + else if(!Q_stricmpn(target, "crosshair", 9)) + { + val = VM_Call(cgvm, CG_CROSSHAIR_PLAYER); + target += 9; + } + else + { + while(*target && *target != ',' && *target != ' ') + target++; + + continue; + } + + if(val < 0) + continue; + } + } + + if(val < 0 || val >= MAX_CLIENTS) + { + Com_Printf(S_COLOR_YELLOW "WARNING: VoIP " + "target %d is not a valid client " + "number\n", val); + + continue; + } + + clc.voipTargets[val / 8] |= 1 << (val % 8); + } +} + +/* +=============== +CL_CaptureVoip + +Record more audio from the hardware if required and encode it into Speex + data for later transmission. +=============== +*/ +static +void CL_CaptureVoip(void) +{ + const float audioMult = cl_voipCaptureMult->value; + const qboolean useVad = (cl_voipUseVAD->integer != 0); + qboolean initialFrame = qfalse; + qboolean finalFrame = qfalse; + +#if USE_MUMBLE + // if we're using Mumble, don't try to handle VoIP transmission ourselves. + if (cl_useMumble->integer) + return; +#endif + + if (!clc.speexInitialized) + return; // just in case this gets called at a bad time. + + if (clc.voipOutgoingDataSize > 0) + return; // packet is pending transmission, don't record more yet. + + if (cl_voipUseVAD->modified) { + Cvar_Set("cl_voipSend", (useVad) ? "1" : "0"); + cl_voipUseVAD->modified = qfalse; + } + + if ((useVad) && (!cl_voipSend->integer)) + Cvar_Set("cl_voipSend", "1"); // lots of things reset this. + + if (cl_voipSend->modified) { + qboolean dontCapture = qfalse; + if (clc.state != CA_ACTIVE) + dontCapture = qtrue; // not connected to a server. + else if (!clc.voipEnabled) + dontCapture = qtrue; // server doesn't support VoIP. + else if (clc.demoplaying) + dontCapture = qtrue; // playing back a demo. + else if ( cl_voip->integer == 0 ) + dontCapture = qtrue; // client has VoIP support disabled. + else if ( audioMult == 0.0f ) + dontCapture = qtrue; // basically silenced incoming audio. + + cl_voipSend->modified = qfalse; + + if(dontCapture) + { + Cvar_Set("cl_voipSend", "0"); + return; + } + + if (cl_voipSend->integer) { + initialFrame = qtrue; + } else { + finalFrame = qtrue; + } + } + + // try to get more audio data from the sound card... + + if (initialFrame) { + S_MasterGain(Com_Clamp(0.0f, 1.0f, cl_voipGainDuringCapture->value)); + S_StartCapture(); + CL_VoipNewGeneration(); + CL_VoipParseTargets(); + } + + if ((cl_voipSend->integer) || (finalFrame)) { // user wants to capture audio? + int samples = S_AvailableCaptureSamples(); + const int mult = (finalFrame) ? 1 : 4; // 4 == 80ms of audio. + + // enough data buffered in audio hardware to process yet? + if (samples >= (clc.speexFrameSize * mult)) { + // audio capture is always MONO16 (and that's what speex wants!). + // 2048 will cover 12 uncompressed frames in narrowband mode. + static int16_t sampbuffer[2048]; + float voipPower = 0.0f; + int speexFrames = 0; + int wpos = 0; + int pos = 0; + + if (samples > (clc.speexFrameSize * 4)) + samples = (clc.speexFrameSize * 4); + + // !!! FIXME: maybe separate recording from encoding, so voipPower + // !!! FIXME: updates faster than 4Hz? + + samples -= samples % clc.speexFrameSize; + S_Capture(samples, (byte *) sampbuffer); // grab from audio card. + + // this will probably generate multiple speex packets each time. + while (samples > 0) { + int16_t *sampptr = &sampbuffer[pos]; + int i, bytes; + + // preprocess samples to remove noise... + speex_preprocess_run(clc.speexPreprocessor, sampptr); + + // check the "power" of this packet... + for (i = 0; i < clc.speexFrameSize; i++) { + const float flsamp = (float) sampptr[i]; + const float s = fabs(flsamp); + voipPower += s * s; + sampptr[i] = (int16_t) ((flsamp) * audioMult); + } + + // encode raw audio samples into Speex data... + speex_bits_reset(&clc.speexEncoderBits); + speex_encode_int(clc.speexEncoder, sampptr, + &clc.speexEncoderBits); + bytes = speex_bits_write(&clc.speexEncoderBits, + (char *) &clc.voipOutgoingData[wpos+1], + sizeof (clc.voipOutgoingData) - (wpos+1)); + assert((bytes > 0) && (bytes < 256)); + clc.voipOutgoingData[wpos] = (byte) bytes; + wpos += bytes + 1; + + // look at the data for the next packet... + pos += clc.speexFrameSize; + samples -= clc.speexFrameSize; + speexFrames++; + } + + clc.voipPower = (voipPower / (32768.0f * 32768.0f * + ((float) (clc.speexFrameSize * speexFrames)))) * + 100.0f; + + if ((useVad) && (clc.voipPower < cl_voipVADThreshold->value)) { + CL_VoipNewGeneration(); // no "talk" for at least 1/4 second. + } else { + clc.voipOutgoingDataSize = wpos; + clc.voipOutgoingDataFrames = speexFrames; + + Com_DPrintf("VoIP: Send %d frames, %d bytes, %f power\n", + speexFrames, wpos, clc.voipPower); + + #if 0 + static FILE *encio = NULL; + if (encio == NULL) encio = fopen("voip-outgoing-encoded.bin", "wb"); + if (encio != NULL) { fwrite(clc.voipOutgoingData, wpos, 1, encio); fflush(encio); } + static FILE *decio = NULL; + if (decio == NULL) decio = fopen("voip-outgoing-decoded.bin", "wb"); + if (decio != NULL) { fwrite(sampbuffer, speexFrames * clc.speexFrameSize * 2, 1, decio); fflush(decio); } + #endif + } + } + } + + // User requested we stop recording, and we've now processed the last of + // any previously-buffered data. Pause the capture device, etc. + if (finalFrame) { + S_StopCapture(); + S_MasterGain(1.0f); + clc.voipPower = 0.0f; // force this value so it doesn't linger. + } +} +#endif + +/* +======================================================================= + +CLIENT RELIABLE COMMAND COMMUNICATION + +======================================================================= +*/ + +/* +====================== +CL_AddReliableCommand + +The given command will be transmitted to the server, and is gauranteed to +not have future usercmd_t executed before it is executed +====================== +*/ +void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd) +{ + int unacknowledged = clc.reliableSequence - clc.reliableAcknowledge; + + // if we would be losing an old command that hasn't been acknowledged, + // we must drop the connection + // also leave one slot open for the disconnect command in this case. + + if ((isDisconnectCmd && unacknowledged > MAX_RELIABLE_COMMANDS) || + (!isDisconnectCmd && unacknowledged >= MAX_RELIABLE_COMMANDS)) + { + if(com_errorEntered) + return; + else + Com_Error(ERR_DROP, "Client command overflow"); + } + + Q_strncpyz(clc.reliableCommands[++clc.reliableSequence & (MAX_RELIABLE_COMMANDS - 1)], + cmd, sizeof(*clc.reliableCommands)); +} + +/* +====================== +CL_ChangeReliableCommand +====================== +*/ +void CL_ChangeReliableCommand( void ) { + int index, l; + + index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 ); + l = strlen(clc.reliableCommands[ index ]); + if ( l >= MAX_STRING_CHARS - 1 ) { + l = MAX_STRING_CHARS - 2; + } + clc.reliableCommands[ index ][ l ] = '\n'; + clc.reliableCommands[ index ][ l+1 ] = '\0'; +} + +/* +======================================================================= + +CLIENT SIDE DEMO RECORDING + +======================================================================= +*/ + +/* +==================== +CL_WriteDemoMessage + +Dumps the current net message, prefixed by the length +==================== +*/ + +void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) { + int len, swlen; + + // write the packet sequence + len = clc.serverMessageSequence; + swlen = LittleLong( len ); + FS_Write (&swlen, 4, clc.demofile); + // skip the packet sequencing information + len = msg->cursize - headerBytes; + swlen = LittleLong(len); + FS_Write (&swlen, 4, clc.demofile); + FS_Write ( msg->data + headerBytes, len, clc.demofile ); +} + + +/* +==================== +CL_StopRecording_f + +stop recording a demo +==================== +*/ +void CL_StopRecord_f( void ) { + int len; + + if ( !clc.demorecording ) { + Com_Printf ("Not recording a demo.\n"); + return; + } + + // finish up + len = -1; + FS_Write (&len, 4, clc.demofile); + FS_Write (&len, 4, clc.demofile); + FS_FCloseFile (clc.demofile); + clc.demofile = 0; + clc.demorecording = qfalse; + clc.spDemoRecording = qfalse; + Com_Printf ("Stopped demo.\n"); +} + +/* +================== +CL_DemoFilename +================== +*/ +void CL_DemoFilename( int number, char *fileName ) { + int a,b,c,d; + + if(number < 0 || number > 9999) + number = 9999; + + a = number / 1000; + number -= a*1000; + b = number / 100; + number -= b*100; + c = number / 10; + number -= c*10; + d = number; + + Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i" + , a, b, c, d ); +} + +/* +==================== +CL_Record_f + +record + +Begins recording a demo from the current position +==================== +*/ +static char demoName[MAX_QPATH]; // compiler bug workaround +void CL_Record_f( void ) { + char name[MAX_OSPATH]; + byte bufData[MAX_MSGLEN]; + msg_t buf; + int i; + int len; + entityState_t *ent; + entityState_t nullstate; + char *s; + + if ( Cmd_Argc() > 2 ) { + Com_Printf ("record \n"); + return; + } + + if ( clc.demorecording ) { + if (!clc.spDemoRecording) { + Com_Printf ("Already recording.\n"); + } + return; + } + + if ( clc.state != CA_ACTIVE ) { + Com_Printf ("You must be in a level to record.\n"); + return; + } + + // sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 .. + if ( NET_IsLocalAddress( clc.serverAddress ) && !Cvar_VariableValue( "g_synchronousClients" ) ) { + Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n"); + } + + if ( Cmd_Argc() == 2 ) { + s = Cmd_Argv(1); + Q_strncpyz( demoName, s, sizeof( demoName ) ); +#ifdef LEGACY_PROTOCOL + if(clc.compat) + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_legacyprotocol->integer); + else +#endif + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer); + } else { + int number; + + // scan for a free demo name + for ( number = 0 ; number <= 9999 ; number++ ) { + CL_DemoFilename( number, demoName ); +#ifdef LEGACY_PROTOCOL + if(clc.compat) + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_legacyprotocol->integer); + else +#endif + Com_sprintf(name, sizeof(name), "demos/%s.%s%d", demoName, DEMOEXT, com_protocol->integer); + + if (!FS_FileExists(name)) + break; // file doesn't exist + } + } + + // open the demo file + + Com_Printf ("recording to %s.\n", name); + clc.demofile = FS_FOpenFileWrite( name ); + if ( !clc.demofile ) { + Com_Printf ("ERROR: couldn't open.\n"); + return; + } + clc.demorecording = qtrue; + if (Cvar_VariableValue("ui_recordSPDemo")) { + clc.spDemoRecording = qtrue; + } else { + clc.spDemoRecording = qfalse; + } + + Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) ); + + // don't start saving messages until a non-delta compressed message is received + clc.demowaiting = qtrue; + + // write out the gamestate message + MSG_Init (&buf, bufData, sizeof(bufData)); + MSG_Bitstream(&buf); + + // NOTE, MRE: all server->client messages now acknowledge + MSG_WriteLong( &buf, clc.reliableSequence ); + + MSG_WriteByte (&buf, svc_gamestate); + MSG_WriteLong (&buf, clc.serverCommandSequence ); + + // configstrings + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + if ( !cl.gameState.stringOffsets[i] ) { + continue; + } + s = cl.gameState.stringData + cl.gameState.stringOffsets[i]; + MSG_WriteByte (&buf, svc_configstring); + MSG_WriteShort (&buf, i); + MSG_WriteBigString (&buf, s); + } + + // baselines + Com_Memset (&nullstate, 0, sizeof(nullstate)); + for ( i = 0; i < MAX_GENTITIES ; i++ ) { + ent = &cl.entityBaselines[i]; + if ( !ent->number ) { + continue; + } + MSG_WriteByte (&buf, svc_baseline); + MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue ); + } + + MSG_WriteByte( &buf, svc_EOF ); + + // finished writing the gamestate stuff + + // write the client num + MSG_WriteLong(&buf, clc.clientNum); + // write the checksum feed + MSG_WriteLong(&buf, clc.checksumFeed); + + // finished writing the client packet + MSG_WriteByte( &buf, svc_EOF ); + + // write it to the demo file + len = LittleLong( clc.serverMessageSequence - 1 ); + FS_Write (&len, 4, clc.demofile); + + len = LittleLong (buf.cursize); + FS_Write (&len, 4, clc.demofile); + FS_Write (buf.data, buf.cursize, clc.demofile); + + // the rest of the demo file will be copied from net messages +} + +/* +======================================================================= + +CLIENT SIDE DEMO PLAYBACK + +======================================================================= +*/ + +/* +================= +CL_DemoFrameDurationSDev +================= +*/ +static float CL_DemoFrameDurationSDev( void ) +{ + int i; + int numFrames; + float mean = 0.0f; + float variance = 0.0f; + + if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS ) + numFrames = MAX_TIMEDEMO_DURATIONS; + else + numFrames = clc.timeDemoFrames - 1; + + for( i = 0; i < numFrames; i++ ) + mean += clc.timeDemoDurations[ i ]; + mean /= numFrames; + + for( i = 0; i < numFrames; i++ ) + { + float x = clc.timeDemoDurations[ i ]; + + variance += ( ( x - mean ) * ( x - mean ) ); + } + variance /= numFrames; + + return sqrt( variance ); +} + +/* +================= +CL_DemoCompleted +================= +*/ +void CL_DemoCompleted( void ) +{ + char buffer[ MAX_STRING_CHARS ]; + + if( cl_timedemo && cl_timedemo->integer ) + { + int time; + + time = Sys_Milliseconds() - clc.timeDemoStart; + if( time > 0 ) + { + // Millisecond times are frame durations: + // minimum/average/maximum/std deviation + Com_sprintf( buffer, sizeof( buffer ), + "%i frames %3.1f seconds %3.1f fps %d.0/%.1f/%d.0/%.1f ms\n", + clc.timeDemoFrames, + time/1000.0, + clc.timeDemoFrames*1000.0 / time, + clc.timeDemoMinDuration, + time / (float)clc.timeDemoFrames, + clc.timeDemoMaxDuration, + CL_DemoFrameDurationSDev( ) ); + Com_Printf( "%s", buffer ); + + // Write a log of all the frame durations + if( cl_timedemoLog && strlen( cl_timedemoLog->string ) > 0 ) + { + int i; + int numFrames; + fileHandle_t f; + + if( ( clc.timeDemoFrames - 1 ) > MAX_TIMEDEMO_DURATIONS ) + numFrames = MAX_TIMEDEMO_DURATIONS; + else + numFrames = clc.timeDemoFrames - 1; + + f = FS_FOpenFileWrite( cl_timedemoLog->string ); + if( f ) + { + FS_Printf( f, "# %s", buffer ); + + for( i = 0; i < numFrames; i++ ) + FS_Printf( f, "%d\n", clc.timeDemoDurations[ i ] ); + + FS_FCloseFile( f ); + Com_Printf( "%s written\n", cl_timedemoLog->string ); + } + else + { + Com_Printf( "Couldn't open %s for writing\n", + cl_timedemoLog->string ); + } + } + } + } + + CL_Disconnect( qtrue ); + CL_NextDemo(); +} + +/* +================= +CL_ReadDemoMessage +================= +*/ +void CL_ReadDemoMessage( void ) { + int r; + msg_t buf; + byte bufData[ MAX_MSGLEN ]; + int s; + + if ( !clc.demofile ) { + CL_DemoCompleted (); + return; + } + + // get the sequence number + r = FS_Read( &s, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + clc.serverMessageSequence = LittleLong( s ); + + // init the message + MSG_Init( &buf, bufData, sizeof( bufData ) ); + + // get the length + r = FS_Read (&buf.cursize, 4, clc.demofile); + if ( r != 4 ) { + CL_DemoCompleted (); + return; + } + buf.cursize = LittleLong( buf.cursize ); + if ( buf.cursize == -1 ) { + CL_DemoCompleted (); + return; + } + if ( buf.cursize > buf.maxsize ) { + Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN"); + } + r = FS_Read( buf.data, buf.cursize, clc.demofile ); + if ( r != buf.cursize ) { + Com_Printf( "Demo file was truncated.\n"); + CL_DemoCompleted (); + return; + } + + clc.lastPacketTime = cls.realtime; + buf.readcount = 0; + CL_ParseServerMessage( &buf ); +} + +/* +==================== +CL_WalkDemoExt +==================== +*/ +static int CL_WalkDemoExt(char *arg, char *name, int *demofile) +{ + int i = 0; + *demofile = 0; + +#ifdef LEGACY_PROTOCOL + if(com_legacyprotocol->integer > 0) + { + Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_legacyprotocol->integer); + FS_FOpenFileRead(name, demofile, qtrue); + + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + return com_legacyprotocol->integer; + } + } + + if(com_protocol->integer != com_legacyprotocol->integer) +#endif + { + Com_sprintf(name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, com_protocol->integer); + FS_FOpenFileRead(name, demofile, qtrue); + + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + return com_protocol->integer; + } + } + + Com_Printf("Not found: %s\n", name); + + while(demo_protocols[i]) + { +#ifdef LEGACY_PROTOCOL + if(demo_protocols[i] == com_legacyprotocol->integer) + continue; +#endif + if(demo_protocols[i] == com_protocol->integer) + continue; + + Com_sprintf (name, MAX_OSPATH, "demos/%s.%s%d", arg, DEMOEXT, demo_protocols[i]); + FS_FOpenFileRead( name, demofile, qtrue ); + if (*demofile) + { + Com_Printf("Demo file: %s\n", name); + + return demo_protocols[i]; + } + else + Com_Printf("Not found: %s\n", name); + i++; + } + + return -1; +} + +/* +==================== +CL_CompleteDemoName +==================== +*/ +static void CL_CompleteDemoName( char *args, int argNum ) +{ + if( argNum == 2 ) + { + char demoExt[ 16 ]; + + Com_sprintf(demoExt, sizeof(demoExt), ".%s%d", DEMOEXT, com_protocol->integer); + Field_CompleteFilename( "demos", demoExt, qtrue, qtrue ); + } +} + +/* +==================== +CL_PlayDemo_f + +demo + +==================== +*/ +void CL_PlayDemo_f( void ) { + char name[MAX_OSPATH]; + char *arg, *ext_test; + int protocol, i; + char retry[MAX_OSPATH]; + + if (Cmd_Argc() != 2) { + Com_Printf ("demo \n"); + return; + } + + // make sure a local server is killed + // 2 means don't force disconnect of local client + Cvar_Set( "sv_killserver", "2" ); + + // open the demo file + arg = Cmd_Argv(1); + + CL_Disconnect( qtrue ); + + // check for an extension .DEMOEXT_?? (?? is protocol) + ext_test = strrchr(arg, '.'); + + if(ext_test && !Q_stricmpn(ext_test + 1, DEMOEXT, ARRAY_LEN(DEMOEXT) - 1)) + { + protocol = atoi(ext_test + ARRAY_LEN(DEMOEXT)); + + for(i = 0; demo_protocols[i]; i++) + { + if(demo_protocols[i] == protocol) + break; + } + + if(demo_protocols[i] || protocol == com_protocol->integer +#ifdef LEGACY_PROTOCOL + || protocol == com_legacyprotocol->integer +#endif + ) + { + Com_sprintf(name, sizeof(name), "demos/%s", arg); + FS_FOpenFileRead(name, &clc.demofile, qtrue); + } + else + { + int len; + + Com_Printf("Protocol %d not supported for demos\n", protocol); + len = ext_test - arg; + + if(len >= ARRAY_LEN(retry)) + len = ARRAY_LEN(retry) - 1; + + Q_strncpyz(retry, arg, len + 1); + retry[len] = '\0'; + protocol = CL_WalkDemoExt(retry, name, &clc.demofile); + } + } + else + protocol = CL_WalkDemoExt(arg, name, &clc.demofile); + + if (!clc.demofile) { + Com_Error( ERR_DROP, "couldn't open %s", name); + return; + } + Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) ); + + Con_Close(); + + clc.state = CA_CONNECTED; + clc.demoplaying = qtrue; + Q_strncpyz( clc.servername, Cmd_Argv(1), sizeof( clc.servername ) ); + +#ifdef LEGACY_PROTOCOL + if(protocol <= com_legacyprotocol->integer) + clc.compat = qtrue; + else + clc.compat = qfalse; +#endif + + // read demo messages until connected + while ( clc.state >= CA_CONNECTED && clc.state < CA_PRIMED ) { + CL_ReadDemoMessage(); + } + // don't get the first snapshot this frame, to prevent the long + // time from the gamestate load from messing causing a time skip + clc.firstDemoFrameSkipped = qfalse; +} + + +/* +==================== +CL_StartDemoLoop + +Closing the main menu will restart the demo loop +==================== +*/ +void CL_StartDemoLoop( void ) { + // start the demo loop again + Cbuf_AddText ("d1\n"); + Key_SetCatcher( 0 ); +} + +/* +================== +CL_NextDemo + +Called when a demo or cinematic finishes +If the "nextdemo" cvar is set, that command will be issued +================== +*/ +void CL_NextDemo( void ) { + char v[MAX_STRING_CHARS]; + + Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) ); + v[MAX_STRING_CHARS-1] = 0; + Com_DPrintf("CL_NextDemo: %s\n", v ); + if (!v[0]) { + return; + } + + Cvar_Set ("nextdemo",""); + Cbuf_AddText (v); + Cbuf_AddText ("\n"); + Cbuf_Execute(); +} + + +//====================================================================== + +/* +===================== +CL_ShutdownAll +===================== +*/ +void CL_ShutdownAll(qboolean shutdownRef) +{ + if(CL_VideoRecording()) + CL_CloseAVI(); + + if(clc.demorecording) + CL_StopRecord_f(); + +#ifdef USE_CURL + CL_cURL_Shutdown(); +#endif + // clear sounds + S_DisableSounds(); + // shutdown CGame + CL_ShutdownCGame(); + // shutdown UI + CL_ShutdownUI(); + + // shutdown the renderer + if(shutdownRef) + CL_ShutdownRef(); + else if(re.Shutdown) + re.Shutdown(qfalse); // don't destroy window or context + + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.rendererStarted = qfalse; + cls.soundRegistered = qfalse; +} + +/* +================= +CL_ClearMemory + +Called by Com_GameRestart +================= +*/ +void CL_ClearMemory(qboolean shutdownRef) +{ + // shutdown all the client stuff + CL_ShutdownAll(shutdownRef); + + // if not running a server clear the whole hunk + if ( !com_sv_running->integer ) { + // clear the whole hunk + Hunk_Clear(); + // clear collision map data + CM_ClearMap(); + } + else { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } +} + +/* +================= +CL_FlushMemory + +Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only +ways a client gets into a game +Also called by Com_Error +================= +*/ +void CL_FlushMemory(void) +{ + CL_ClearMemory(qfalse); + CL_StartHunkUsers(qfalse); +} + +/* +===================== +CL_MapLoading + +A local server is starting to load a map, so update the +screen to let the user know about it, then dump all client +memory on the hunk from cgame, ui, and renderer +===================== +*/ +void CL_MapLoading( void ) { + if ( com_dedicated->integer ) { + clc.state = CA_DISCONNECTED; + Key_SetCatcher( KEYCATCH_CONSOLE ); + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + Con_Close(); + Key_SetCatcher( 0 ); + + // if we are already connected to the local host, stay connected + if ( clc.state >= CA_CONNECTED && !Q_stricmp( clc.servername, "localhost" ) ) { + clc.state = CA_CONNECTED; // so the connect screen is drawn + Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) ); + Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) ); + Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) ); + clc.lastPacketSentTime = -9999; + SCR_UpdateScreen(); + } else { + // clear nextmap so the cinematic shutdown doesn't execute it + Cvar_Set( "nextmap", "" ); + CL_Disconnect( qtrue ); + Q_strncpyz( clc.servername, "localhost", sizeof(clc.servername) ); + clc.state = CA_CHALLENGING; // so the connect screen is drawn + Key_SetCatcher( 0 ); + SCR_UpdateScreen(); + clc.connectTime = -RETRANSMIT_TIMEOUT; + NET_StringToAdr( clc.servername, &clc.serverAddress, NA_UNSPEC); + // we don't need a challenge on the localhost + + CL_CheckForResend(); + } +} + +/* +===================== +CL_ClearState + +Called before parsing a gamestate +===================== +*/ +void CL_ClearState (void) { + +// S_StopAllSounds(); + + Com_Memset( &cl, 0, sizeof( cl ) ); +} + +/* +==================== +CL_UpdateGUID + +update cl_guid using QKEY_FILE and optional prefix +==================== +*/ +static void CL_UpdateGUID( const char *prefix, int prefix_len ) +{ + fileHandle_t f; + int len; + + len = FS_SV_FOpenFileRead( QKEY_FILE, &f ); + FS_FCloseFile( f ); + + if( len != QKEY_SIZE ) + Cvar_Set( "cl_guid", "" ); + else + Cvar_Set( "cl_guid", Com_MD5File( QKEY_FILE, QKEY_SIZE, + prefix, prefix_len ) ); +} + +static void CL_OldGame(void) +{ + if(cls.oldGameSet) + { + // change back to previous fs_game + cls.oldGameSet = qfalse; + Cvar_Set2("fs_game", cls.oldGame, qtrue); + FS_ConditionalRestart(clc.checksumFeed, qfalse); + } +} + +/* +===================== +CL_Disconnect + +Called when a connection, demo, or cinematic is being terminated. +Goes from a connected state to either a menu state or a console state +Sends a disconnect message to the server +This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors +===================== +*/ +void CL_Disconnect( qboolean showMainMenu ) { + if ( !com_cl_running || !com_cl_running->integer ) { + return; + } + + // shutting down the client so enter full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + if ( clc.demorecording ) { + CL_StopRecord_f (); + } + + if (clc.download) { + FS_FCloseFile( clc.download ); + clc.download = 0; + } + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + +#ifdef USE_MUMBLE + if (cl_useMumble->integer && mumble_islinked()) { + Com_Printf("Mumble: Unlinking from Mumble application\n"); + mumble_unlink(); + } +#endif + +#ifdef USE_VOIP + if (cl_voipSend->integer) { + int tmp = cl_voipUseVAD->integer; + cl_voipUseVAD->integer = 0; // disable this for a moment. + clc.voipOutgoingDataSize = 0; // dump any pending VoIP transmission. + Cvar_Set("cl_voipSend", "0"); + CL_CaptureVoip(); // clean up any state... + cl_voipUseVAD->integer = tmp; + } + + if (clc.speexInitialized) { + int i; + speex_bits_destroy(&clc.speexEncoderBits); + speex_encoder_destroy(clc.speexEncoder); + speex_preprocess_state_destroy(clc.speexPreprocessor); + for (i = 0; i < MAX_CLIENTS; i++) { + speex_bits_destroy(&clc.speexDecoderBits[i]); + speex_decoder_destroy(clc.speexDecoder[i]); + } + } + Cmd_RemoveCommand ("voip"); +#endif + + if ( clc.demofile ) { + FS_FCloseFile( clc.demofile ); + clc.demofile = 0; + } + + if ( uivm && showMainMenu ) { + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE ); + } + + SCR_StopCinematic (); + S_ClearSoundBuffer(); + + // send a disconnect message to the server + // send it a few times in case one is dropped + if ( clc.state >= CA_CONNECTED ) { + CL_AddReliableCommand("disconnect", qtrue); + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); + } + + // Remove pure paks + FS_PureServerSetLoadedPaks("", ""); + + CL_ClearState (); + + // wipe the client connection + Com_Memset( &clc, 0, sizeof( clc ) ); + + clc.state = CA_DISCONNECTED; + + // allow cheats locally + Cvar_Set( "sv_cheats", "1" ); + + // not connected to a pure server anymore + cl_connectedToPureServer = qfalse; + +#ifdef USE_VOIP + // not connected to voip server anymore. + clc.voipEnabled = qfalse; +#endif + + // Stop recording any video + if( CL_VideoRecording( ) ) { + // Finish rendering current frame + SCR_UpdateScreen( ); + CL_CloseAVI( ); + } + + CL_UpdateGUID( NULL, 0 ); + + if(!noGameRestart) + CL_OldGame(); + else + noGameRestart = qfalse; +} + + +/* +=================== +CL_ForwardCommandToServer + +adds the current command line as a clientCommand +things like godmode, noclip, etc, are commands directed to the server, +so when they are typed in at the console, they will need to be forwarded. +=================== +*/ +void CL_ForwardCommandToServer( const char *string ) { + char *cmd; + + cmd = Cmd_Argv(0); + + // ignore key up commands + if ( cmd[0] == '-' ) { + return; + } + + if ( clc.demoplaying || clc.state < CA_CONNECTED || cmd[0] == '+' ) { + Com_Printf ("Unknown command \"%s" S_COLOR_WHITE "\"\n", cmd); + return; + } + + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand(string, qfalse); + } else { + CL_AddReliableCommand(cmd, qfalse); + } +} + +/* +=================== +CL_RequestMotd + +=================== +*/ +void CL_RequestMotd( void ) { +#ifdef UPDATE_SERVER_NAME + char info[MAX_INFO_STRING]; + + if ( !cl_motd->integer ) { + return; + } + Com_Printf( "Resolving %s\n", UPDATE_SERVER_NAME ); + if ( !NET_StringToAdr( UPDATE_SERVER_NAME, &cls.updateServer, NA_IP ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + cls.updateServer.port = BigShort( PORT_UPDATE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", UPDATE_SERVER_NAME, + cls.updateServer.ip[0], cls.updateServer.ip[1], + cls.updateServer.ip[2], cls.updateServer.ip[3], + BigShort( cls.updateServer.port ) ); + + info[0] = 0; + + Com_sprintf( cls.updateChallenge, sizeof( cls.updateChallenge ), "%i", ((rand() << 16) ^ rand()) ^ Com_Milliseconds()); + + Info_SetValueForKey( info, "challenge", cls.updateChallenge ); + Info_SetValueForKey( info, "renderer", cls.glconfig.renderer_string ); + Info_SetValueForKey( info, "version", com_version->string ); + + NET_OutOfBandPrint( NS_CLIENT, cls.updateServer, "getmotd \"%s\"\n", info ); +#endif +} + +/* +=================== +CL_RequestAuthorization + +Authorization server protocol +----------------------------- + +All commands are text in Q3 out of band packets (leading 0xff 0xff 0xff 0xff). + +Whenever the client tries to get a challenge from the server it wants to +connect to, it also blindly fires off a packet to the authorize server: + +getKeyAuthorize + +cdkey may be "demo" + + +#OLD The authorize server returns a: +#OLD +#OLD keyAthorize +#OLD +#OLD A client will be accepted if the cdkey is valid and it has not been used by any other IP +#OLD address in the last 15 minutes. + + +The server sends a: + +getIpAuthorize + +The authorize server returns a: + +ipAuthorize + +A client will be accepted if a valid cdkey was sent by that ip (only) in the last 15 minutes. +If no response is received from the authorize server after two tries, the client will be let +in anyway. +=================== +*/ +#ifndef STANDALONE +void CL_RequestAuthorization( void ) { + char nums[64]; + int i, j, l; + cvar_t *fs; + + if ( !cls.authorizeServer.port ) { + Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); + if ( !NET_StringToAdr( AUTHORIZE_SERVER_NAME, &cls.authorizeServer, NA_IP ) ) { + Com_Printf( "Couldn't resolve address\n" ); + return; + } + + cls.authorizeServer.port = BigShort( PORT_AUTHORIZE ); + Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", AUTHORIZE_SERVER_NAME, + cls.authorizeServer.ip[0], cls.authorizeServer.ip[1], + cls.authorizeServer.ip[2], cls.authorizeServer.ip[3], + BigShort( cls.authorizeServer.port ) ); + } + if ( cls.authorizeServer.type == NA_BAD ) { + return; + } + + // only grab the alphanumeric values from the cdkey, to avoid any dashes or spaces + j = 0; + l = strlen( cl_cdkey ); + if ( l > 32 ) { + l = 32; + } + for ( i = 0 ; i < l ; i++ ) { + if ( ( cl_cdkey[i] >= '0' && cl_cdkey[i] <= '9' ) + || ( cl_cdkey[i] >= 'a' && cl_cdkey[i] <= 'z' ) + || ( cl_cdkey[i] >= 'A' && cl_cdkey[i] <= 'Z' ) + ) { + nums[j] = cl_cdkey[i]; + j++; + } + } + nums[j] = 0; + + fs = Cvar_Get ("cl_anonymous", "0", CVAR_INIT|CVAR_SYSTEMINFO ); + + NET_OutOfBandPrint(NS_CLIENT, cls.authorizeServer, "getKeyAuthorize %i %s", fs->integer, nums ); +} +#endif +/* +====================================================================== + +CONSOLE COMMANDS + +====================================================================== +*/ + +/* +================== +CL_ForwardToServer_f +================== +*/ +void CL_ForwardToServer_f( void ) { + if ( clc.state != CA_ACTIVE || clc.demoplaying ) { + Com_Printf ("Not connected to a server.\n"); + return; + } + + // don't forward the first argument + if ( Cmd_Argc() > 1 ) { + CL_AddReliableCommand(Cmd_Args(), qfalse); + } +} + +/* +================== +CL_Disconnect_f +================== +*/ +void CL_Disconnect_f( void ) { + SCR_StopCinematic(); + Cvar_Set("ui_singlePlayerActive", "0"); + if ( clc.state != CA_DISCONNECTED && clc.state != CA_CINEMATIC ) { + Com_Error (ERR_DISCONNECT, "Disconnected from server"); + } +} + + +/* +================ +CL_Reconnect_f + +================ +*/ +void CL_Reconnect_f( void ) { + if ( !strlen( clc.servername ) || !strcmp( clc.servername, "localhost" ) ) { + Com_Printf( "Can't reconnect to localhost.\n" ); + return; + } + Cvar_Set("ui_singlePlayerActive", "0"); + Cbuf_AddText( va("connect %s\n", clc.servername ) ); +} + +/* +================ +CL_Connect_f + +================ +*/ +void CL_Connect_f( void ) { + char *server; + const char *serverString; + int argc = Cmd_Argc(); + netadrtype_t family = NA_UNSPEC; + + if ( argc != 2 && argc != 3 ) { + Com_Printf( "usage: connect [-4|-6] server\n"); + return; + } + + if(argc == 2) + server = Cmd_Argv(1); + else + { + if(!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if(!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf( "warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + Cvar_Set("ui_singlePlayerActive", "0"); + + // fire a message off to the motd server + CL_RequestMotd(); + + // clear any previous "server full" type messages + clc.serverMessage[0] = 0; + + if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) { + // if running a local server, kill it + SV_Shutdown( "Server quit" ); + } + + // make sure a local server is killed + Cvar_Set( "sv_killserver", "1" ); + SV_Frame( 0 ); + + noGameRestart = qtrue; + CL_Disconnect( qtrue ); + Con_Close(); + + Q_strncpyz( clc.servername, server, sizeof(clc.servername) ); + + if (!NET_StringToAdr(clc.servername, &clc.serverAddress, family) ) { + Com_Printf ("Bad server address\n"); + clc.state = CA_DISCONNECTED; + return; + } + if (clc.serverAddress.port == 0) { + clc.serverAddress.port = BigShort( PORT_SERVER ); + } + + serverString = NET_AdrToStringwPort(clc.serverAddress); + + Com_Printf( "%s resolved to %s\n", clc.servername, serverString); + + if( cl_guidServerUniq->integer ) + CL_UpdateGUID( serverString, strlen( serverString ) ); + else + CL_UpdateGUID( NULL, 0 ); + + // if we aren't playing on a lan, we need to authenticate + // with the cd key + if(NET_IsLocalAddress(clc.serverAddress)) + clc.state = CA_CHALLENGING; + else + { + clc.state = CA_CONNECTING; + + // Set a client challenge number that ideally is mirrored back by the server. + clc.challenge = ((rand() << 16) ^ rand()) ^ Com_Milliseconds(); + } + + Key_SetCatcher( 0 ); + clc.connectTime = -99999; // CL_CheckForResend() will fire immediately + clc.connectPacketCount = 0; + + // server connection string + Cvar_Set( "cl_currentServerAddress", server ); +} + +#define MAX_RCON_MESSAGE 1024 + +/* +================== +CL_CompleteRcon +================== +*/ +static void CL_CompleteRcon( char *args, int argNum ) +{ + if( argNum == 2 ) + { + // Skip "rcon " + char *p = Com_SkipTokens( args, 1, " " ); + + if( p > args ) + Field_CompleteCommand( p, qtrue, qtrue ); + } +} + +/* +===================== +CL_Rcon_f + + Send the rest of the command line over as + an unconnected command. +===================== +*/ +void CL_Rcon_f( void ) { + char message[MAX_RCON_MESSAGE]; + netadr_t to; + + if ( !rcon_client_password->string ) { + Com_Printf ("You must set 'rconpassword' before\n" + "issuing an rcon command.\n"); + return; + } + + message[0] = -1; + message[1] = -1; + message[2] = -1; + message[3] = -1; + message[4] = 0; + + Q_strcat (message, MAX_RCON_MESSAGE, "rcon "); + + Q_strcat (message, MAX_RCON_MESSAGE, rcon_client_password->string); + Q_strcat (message, MAX_RCON_MESSAGE, " "); + + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 + Q_strcat (message, MAX_RCON_MESSAGE, Cmd_Cmd()+5); + + if ( clc.state >= CA_CONNECTED ) { + to = clc.netchan.remoteAddress; + } else { + if (!strlen(rconAddress->string)) { + Com_Printf ("You must either be connected,\n" + "or set the 'rconAddress' cvar\n" + "to issue rcon commands\n"); + + return; + } + NET_StringToAdr (rconAddress->string, &to, NA_UNSPEC); + if (to.port == 0) { + to.port = BigShort (PORT_SERVER); + } + } + + NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to); +} + +/* +================= +CL_SendPureChecksums +================= +*/ +void CL_SendPureChecksums( void ) { + char cMsg[MAX_INFO_VALUE]; + + // if we are pure we need to send back a command with our referenced pk3 checksums + Com_sprintf(cMsg, sizeof(cMsg), "cp %d %s", cl.serverId, FS_ReferencedPakPureChecksums()); + + CL_AddReliableCommand(cMsg, qfalse); +} + +/* +================= +CL_ResetPureClientAtServer +================= +*/ +void CL_ResetPureClientAtServer( void ) { + CL_AddReliableCommand("vdr", qfalse); +} + +/* +================= +CL_Vid_Restart_f + +Restart the video subsystem + +we also have to reload the UI and CGame because the renderer +doesn't know what graphics to reload +================= +*/ +void CL_Vid_Restart_f( void ) { + + // Settings may have changed so stop recording now + if( CL_VideoRecording( ) ) { + CL_CloseAVI( ); + } + + if(clc.demorecording) + CL_StopRecord_f(); + + // don't let them loop during the restart + S_StopAllSounds(); + + if(!FS_ConditionalRestart(clc.checksumFeed, qtrue)) + { + // if not running a server clear the whole hunk + if(com_sv_running->integer) + { + // clear all the client data on the hunk + Hunk_ClearToMark(); + } + else + { + // clear the whole hunk + Hunk_Clear(); + } + + // shutdown the UI + CL_ShutdownUI(); + // shutdown the CGame + CL_ShutdownCGame(); + // shutdown the renderer and clear the renderer interface + CL_ShutdownRef(); + // client is no longer pure untill new checksums are sent + CL_ResetPureClientAtServer(); + // clear pak references + FS_ClearPakReferences( FS_UI_REF | FS_CGAME_REF ); + // reinitialize the filesystem if the game directory or checksum has changed + + cls.rendererStarted = qfalse; + cls.uiStarted = qfalse; + cls.cgameStarted = qfalse; + cls.soundRegistered = qfalse; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set("cl_paused", "0"); + + // initialize the renderer interface + CL_InitRef(); + + // startup all the client stuff + CL_StartHunkUsers(qfalse); + + // start the cgame if connected + if(clc.state > CA_CONNECTED && clc.state != CA_CINEMATIC) + { + cls.cgameStarted = qtrue; + CL_InitCGame(); + // send pure checksums + CL_SendPureChecksums(); + } + } +} + +/* +================= +CL_Snd_Restart + +Restart the sound subsystem +================= +*/ +void CL_Snd_Shutdown(void) +{ + S_Shutdown(); + cls.soundStarted = qfalse; +} + +/* +================= +CL_Snd_Restart_f + +Restart the sound subsystem +The cgame and game must also be forced to restart because +handles will be invalid +================= +*/ +void CL_Snd_Restart_f(void) +{ + CL_Snd_Shutdown(); + // sound will be reinitialized by vid_restart + CL_Vid_Restart_f(); +} + + +/* +================== +CL_PK3List_f +================== +*/ +void CL_OpenedPK3List_f( void ) { + Com_Printf("Opened PK3 Names: %s\n", FS_LoadedPakNames()); +} + +/* +================== +CL_PureList_f +================== +*/ +void CL_ReferencedPK3List_f( void ) { + Com_Printf("Referenced PK3 Names: %s\n", FS_ReferencedPakNames()); +} + +/* +================== +CL_Configstrings_f +================== +*/ +void CL_Configstrings_f( void ) { + int i; + int ofs; + + if ( clc.state != CA_ACTIVE ) { + Com_Printf( "Not connected to a server.\n"); + return; + } + + for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) { + ofs = cl.gameState.stringOffsets[ i ]; + if ( !ofs ) { + continue; + } + Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs ); + } +} + +/* +============== +CL_Clientinfo_f +============== +*/ +void CL_Clientinfo_f( void ) { + Com_Printf( "--------- Client Information ---------\n" ); + Com_Printf( "state: %i\n", clc.state ); + Com_Printf( "Server: %s\n", clc.servername ); + Com_Printf ("User info settings:\n"); + Info_Print( Cvar_InfoString( CVAR_USERINFO ) ); + Com_Printf( "--------------------------------------\n" ); +} + + +//==================================================================== + +/* +================= +CL_DownloadsComplete + +Called when all downloading has been completed +================= +*/ +void CL_DownloadsComplete( void ) { + +#ifdef USE_CURL + // if we downloaded with cURL + if(clc.cURLUsed) { + clc.cURLUsed = qfalse; + CL_cURL_Shutdown(); + if( clc.cURLDisconnected ) { + if(clc.downloadRestart) { + FS_Restart(clc.checksumFeed); + clc.downloadRestart = qfalse; + } + clc.cURLDisconnected = qfalse; + CL_Reconnect_f(); + return; + } + } +#endif + + // if we downloaded files we need to restart the file system + if (clc.downloadRestart) { + clc.downloadRestart = qfalse; + + FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it + + // inform the server so we get new gamestate info + CL_AddReliableCommand("donedl", qfalse); + + // by sending the donedl command we request a new gamestate + // so we don't want to load stuff yet + return; + } + + // let the client game init and load data + clc.state = CA_LOADING; + + // Pump the loop, this may change gamestate! + Com_EventLoop(); + + // if the gamestate was changed by calling Com_EventLoop + // then we loaded everything already and we don't want to do it again. + if ( clc.state != CA_LOADING ) { + return; + } + + // starting to load a map so we get out of full screen ui mode + Cvar_Set("r_uiFullScreen", "0"); + + // flush client memory and start loading stuff + // this will also (re)load the UI + // if this is a local client then only the client part of the hunk + // will be cleared, note that this is done after the hunk mark has been set + CL_FlushMemory(); + + // initialize the CGame + cls.cgameStarted = qtrue; + CL_InitCGame(); + + // set pure checksums + CL_SendPureChecksums(); + + CL_WritePacket(); + CL_WritePacket(); + CL_WritePacket(); +} + +/* +================= +CL_BeginDownload + +Requests a file to download from the server. Stores it in the current +game directory. +================= +*/ +void CL_BeginDownload( const char *localName, const char *remoteName ) { + + Com_DPrintf("***** CL_BeginDownload *****\n" + "Localname: %s\n" + "Remotename: %s\n" + "****************************\n", localName, remoteName); + + Q_strncpyz ( clc.downloadName, localName, sizeof(clc.downloadName) ); + Com_sprintf( clc.downloadTempName, sizeof(clc.downloadTempName), "%s.tmp", localName ); + + // Set so UI gets access to it + Cvar_Set( "cl_downloadName", remoteName ); + Cvar_Set( "cl_downloadSize", "0" ); + Cvar_Set( "cl_downloadCount", "0" ); + Cvar_SetValue( "cl_downloadTime", cls.realtime ); + + clc.downloadBlock = 0; // Starting new file + clc.downloadCount = 0; + + CL_AddReliableCommand(va("download %s", remoteName), qfalse); +} + +/* +================= +CL_NextDownload + +A download completed or failed +================= +*/ +void CL_NextDownload(void) +{ + char *s; + char *remoteName, *localName; + qboolean useCURL = qfalse; + + // A download has finished, check whether this matches a referenced checksum + if(*clc.downloadName) + { + char *zippath = FS_BuildOSPath(Cvar_VariableString("fs_homepath"), clc.downloadName, ""); + zippath[strlen(zippath)-1] = '\0'; + + if(!FS_CompareZipChecksum(zippath)) + Com_Error(ERR_DROP, "Incorrect checksum for file: %s", clc.downloadName); + } + + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set("cl_downloadName", ""); + + // We are looking to start a download here + if (*clc.downloadList) { + s = clc.downloadList; + + // format is: + // @remotename@localname@remotename@localname, etc. + + if (*s == '@') + s++; + remoteName = s; + + if ( (s = strchr(s, '@')) == NULL ) { + CL_DownloadsComplete(); + return; + } + + *s++ = 0; + localName = s; + if ( (s = strchr(s, '@')) != NULL ) + *s++ = 0; + else + s = localName + strlen(localName); // point at the nul byte +#ifdef USE_CURL + if(!(cl_allowDownload->integer & DLF_NO_REDIRECT)) { + if(clc.sv_allowDownload & DLF_NO_REDIRECT) { + Com_Printf("WARNING: server does not " + "allow download redirection " + "(sv_allowDownload is %d)\n", + clc.sv_allowDownload); + } + else if(!*clc.sv_dlURL) { + Com_Printf("WARNING: server allows " + "download redirection, but does not " + "have sv_dlURL set\n"); + } + else if(!CL_cURL_Init()) { + Com_Printf("WARNING: could not load " + "cURL library\n"); + } + else { + CL_cURL_BeginDownload(localName, va("%s/%s", + clc.sv_dlURL, remoteName)); + useCURL = qtrue; + } + } + else if(!(clc.sv_allowDownload & DLF_NO_REDIRECT)) { + Com_Printf("WARNING: server allows download " + "redirection, but it disabled by client " + "configuration (cl_allowDownload is %d)\n", + cl_allowDownload->integer); + } +#endif /* USE_CURL */ + if(!useCURL) { + if((cl_allowDownload->integer & DLF_NO_UDP)) { + Com_Error(ERR_DROP, "UDP Downloads are " + "disabled on your client. " + "(cl_allowDownload is %d)", + cl_allowDownload->integer); + return; + } + else { + CL_BeginDownload( localName, remoteName ); + } + } + clc.downloadRestart = qtrue; + + // move over the rest + memmove( clc.downloadList, s, strlen(s) + 1); + + return; + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_InitDownloads + +After receiving a valid game state, we valid the cgame and local zip files here +and determine if we need to download them +================= +*/ +void CL_InitDownloads(void) { + char missingfiles[1024]; + + if ( !(cl_allowDownload->integer & DLF_ENABLE) ) + { + // autodownload is disabled on the client + // but it's possible that some referenced files on the server are missing + if (FS_ComparePaks( missingfiles, sizeof( missingfiles ), qfalse ) ) + { + // NOTE TTimo I would rather have that printed as a modal message box + // but at this point while joining the game we don't know wether we will successfully join or not + Com_Printf( "\nWARNING: You are missing some files referenced by the server:\n%s" + "You might not be able to join the game\n" + "Go to the setting menu to turn on autodownload, or get the file elsewhere\n\n", missingfiles ); + } + } + else if ( FS_ComparePaks( clc.downloadList, sizeof( clc.downloadList ) , qtrue ) ) { + + Com_Printf("Need paks: %s\n", clc.downloadList ); + + if ( *clc.downloadList ) { + // if autodownloading is not enabled on the server + clc.state = CA_CONNECTED; + + *clc.downloadTempName = *clc.downloadName = 0; + Cvar_Set( "cl_downloadName", "" ); + + CL_NextDownload(); + return; + } + + } + + CL_DownloadsComplete(); +} + +/* +================= +CL_CheckForResend + +Resend a connect message if the last one has timed out +================= +*/ +void CL_CheckForResend( void ) { + int port, i; + char info[MAX_INFO_STRING]; + char data[MAX_INFO_STRING]; + + // don't send anything if playing back a demo + if ( clc.demoplaying ) { + return; + } + + // resend if we haven't gotten a reply yet + if ( clc.state != CA_CONNECTING && clc.state != CA_CHALLENGING ) { + return; + } + + if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) { + return; + } + + clc.connectTime = cls.realtime; // for retransmit requests + clc.connectPacketCount++; + + + switch ( clc.state ) { + case CA_CONNECTING: + // requesting a challenge .. IPv6 users always get in as authorize server supports no ipv6. +#ifndef STANDALONE + if (!com_standalone->integer && clc.serverAddress.type == NA_IP && !Sys_IsLANAddress( clc.serverAddress ) ) + CL_RequestAuthorization(); +#endif + + // The challenge request shall be followed by a client challenge so no malicious server can hijack this connection. + // Add the gamename so the server knows we're running the correct game or can reject the client + // with a meaningful message + Com_sprintf(data, sizeof(data), "getchallenge %d %s", clc.challenge, com_gamename->string); + + NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "%s", data); + break; + + case CA_CHALLENGING: + // sending back the challenge + port = Cvar_VariableValue ("net_qport"); + + Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) ); + +#ifdef LEGACY_PROTOCOL + if(com_legacyprotocol->integer == com_protocol->integer) + clc.compat = qtrue; + + if(clc.compat) + Info_SetValueForKey(info, "protocol", va("%i", com_legacyprotocol->integer)); + else +#endif + Info_SetValueForKey(info, "protocol", va("%i", com_protocol->integer)); + Info_SetValueForKey( info, "qport", va("%i", port ) ); + Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) ); + + strcpy(data, "connect "); + // TTimo adding " " around the userinfo string to avoid truncated userinfo on the server + // (Com_TokenizeString tokenizes around spaces) + data[8] = '"'; + + for(i=0;iadr = *address; + server->clients = 0; + server->hostName[0] = '\0'; + server->mapName[0] = '\0'; + server->maxClients = 0; + server->maxPing = 0; + server->minPing = 0; + server->ping = -1; + server->game[0] = '\0'; + server->gameType = 0; + server->netType = 0; +} + +#define MAX_SERVERSPERPACKET 256 + +/* +=================== +CL_ServersResponsePacket +=================== +*/ +void CL_ServersResponsePacket( const netadr_t* from, msg_t *msg, qboolean extended ) { + int i, j, count, total; + netadr_t addresses[MAX_SERVERSPERPACKET]; + int numservers; + byte* buffptr; + byte* buffend; + + Com_Printf("CL_ServersResponsePacket\n"); + + if (cls.numglobalservers == -1) { + // state to detect lack of servers or lack of response + cls.numglobalservers = 0; + cls.numGlobalServerAddresses = 0; + } + + // parse through server response string + numservers = 0; + buffptr = msg->data; + buffend = buffptr + msg->cursize; + + // advance to initial token + do + { + if(*buffptr == '\\' || (extended && *buffptr == '/')) + break; + + buffptr++; + } while (buffptr < buffend); + + while (buffptr + 1 < buffend) + { + // IPv4 address + if (*buffptr == '\\') + { + buffptr++; + + if (buffend - buffptr < sizeof(addresses[numservers].ip) + sizeof(addresses[numservers].port) + 1) + break; + + for(i = 0; i < sizeof(addresses[numservers].ip); i++) + addresses[numservers].ip[i] = *buffptr++; + + addresses[numservers].type = NA_IP; + } + // IPv6 address, if it's an extended response + else if (extended && *buffptr == '/') + { + buffptr++; + + if (buffend - buffptr < sizeof(addresses[numservers].ip6) + sizeof(addresses[numservers].port) + 1) + break; + + for(i = 0; i < sizeof(addresses[numservers].ip6); i++) + addresses[numservers].ip6[i] = *buffptr++; + + addresses[numservers].type = NA_IP6; + addresses[numservers].scope_id = from->scope_id; + } + else + // syntax error! + break; + + // parse out port + addresses[numservers].port = (*buffptr++) << 8; + addresses[numservers].port += *buffptr++; + addresses[numservers].port = BigShort( addresses[numservers].port ); + + // syntax check + if (*buffptr != '\\' && *buffptr != '/') + break; + + numservers++; + if (numservers >= MAX_SERVERSPERPACKET) + break; + } + + count = cls.numglobalservers; + + for (i = 0; i < numservers && count < MAX_GLOBAL_SERVERS; i++) { + // build net address + serverInfo_t *server = &cls.globalServers[count]; + + // Tequila: It's possible to have sent many master server requests. Then + // we may receive many times the same addresses from the master server. + // We just avoid to add a server if it is still in the global servers list. + for (j = 0; j < count; j++) + { + if (NET_CompareAdr(cls.globalServers[j].adr, addresses[i])) + break; + } + + if (j < count) + continue; + + CL_InitServerInfo( server, &addresses[i] ); + // advance to next slot + count++; + } + + // if getting the global list + if ( count >= MAX_GLOBAL_SERVERS && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS ) + { + // if we couldn't store the servers in the main list anymore + for (; i < numservers && cls.numGlobalServerAddresses < MAX_GLOBAL_SERVERS; i++) + { + // just store the addresses in an additional list + cls.globalServerAddresses[cls.numGlobalServerAddresses++] = addresses[i]; + } + } + + cls.numglobalservers = count; + total = count + cls.numGlobalServerAddresses; + + Com_Printf("%d servers parsed (total %d)\n", numservers, total); +} + +/* +================= +CL_ConnectionlessPacket + +Responses to broadcasts, etc +================= +*/ +void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) { + char *s; + char *c; + int challenge = 0; + + MSG_BeginReadingOOB( msg ); + MSG_ReadLong( msg ); // skip the -1 + + s = MSG_ReadStringLine( msg ); + + Cmd_TokenizeString( s ); + + c = Cmd_Argv(0); + + Com_DPrintf ("CL packet %s: %s\n", NET_AdrToStringwPort(from), c); + + // challenge from the server we are connecting to + if (!Q_stricmp(c, "challengeResponse")) + { + char *strver; + int ver; + + if (clc.state != CA_CONNECTING) + { + Com_DPrintf("Unwanted challenge response received. Ignored.\n"); + return; + } + + c = Cmd_Argv(2); + if(*c) + challenge = atoi(c); + + strver = Cmd_Argv(3); + if(*strver) + { + ver = atoi(strver); + + if(ver != com_protocol->integer) + { +#ifdef LEGACY_PROTOCOL + if(com_legacyprotocol->integer > 0) + { + // Server is ioq3 but has a different protocol than we do. + // Fall back to idq3 protocol. + clc.compat = qtrue; + + Com_Printf(S_COLOR_YELLOW "Warning: Server reports protocol version %d, " + "we have %d. Trying legacy protocol %d.\n", + ver, com_protocol->integer, com_legacyprotocol->integer); + } + else +#endif + { + Com_Printf(S_COLOR_YELLOW "Warning: Server reports protocol version %d, we have %d. " + "Trying anyways.\n", ver, com_protocol->integer); + } + } + } +#ifdef LEGACY_PROTOCOL + else + clc.compat = qtrue; + + if(clc.compat) + { + if(!NET_CompareAdr(from, clc.serverAddress)) + { + // This challenge response is not coming from the expected address. + // Check whether we have a matching client challenge to prevent + // connection hi-jacking. + + if(!*c || challenge != clc.challenge) + { + Com_DPrintf("Challenge response received from unexpected source. Ignored.\n"); + return; + } + } + } + else +#endif + { + if(!*c || challenge != clc.challenge) + { + Com_Printf("Bad challenge for challengeResponse. Ignored.\n"); + return; + } + } + + // start sending challenge response instead of challenge request packets + clc.challenge = atoi(Cmd_Argv(1)); + clc.state = CA_CHALLENGING; + clc.connectPacketCount = 0; + clc.connectTime = -99999; + + // take this address as the new server address. This allows + // a server proxy to hand off connections to multiple servers + clc.serverAddress = from; + Com_DPrintf ("challengeResponse: %d\n", clc.challenge); + return; + } + + // server connection + if ( !Q_stricmp(c, "connectResponse") ) { + if ( clc.state >= CA_CONNECTED ) { + Com_Printf ("Dup connect received. Ignored.\n"); + return; + } + if ( clc.state != CA_CHALLENGING ) { + Com_Printf ("connectResponse packet while not connecting. Ignored.\n"); + return; + } + if ( !NET_CompareAdr( from, clc.serverAddress ) ) { + Com_Printf( "connectResponse from wrong address. Ignored.\n" ); + return; + } + +#ifdef LEGACY_PROTOCOL + if(!clc.compat) +#endif + { + c = Cmd_Argv(1); + + if(*c) + challenge = atoi(c); + else + { + Com_Printf("Bad connectResponse received. Ignored.\n"); + return; + } + + if(challenge != clc.challenge) + { + Com_Printf("ConnectResponse with bad challenge received. Ignored.\n"); + return; + } + } + +#ifdef LEGACY_PROTOCOL + Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"), + clc.challenge, clc.compat); +#else + Netchan_Setup(NS_CLIENT, &clc.netchan, from, Cvar_VariableValue("net_qport"), + clc.challenge, qfalse); +#endif + + clc.state = CA_CONNECTED; + clc.lastPacketSentTime = -9999; // send first packet immediately + return; + } + + // server responding to an info broadcast + if ( !Q_stricmp(c, "infoResponse") ) { + CL_ServerInfoPacket( from, msg ); + return; + } + + // server responding to a get playerlist + if ( !Q_stricmp(c, "statusResponse") ) { + CL_ServerStatusResponse( from, msg ); + return; + } + + // echo request from server + if ( !Q_stricmp(c, "echo") ) { + NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) ); + return; + } + + // cd check + if ( !Q_stricmp(c, "keyAuthorize") ) { + // we don't use these now, so dump them on the floor + return; + } + + // global MOTD from id + if ( !Q_stricmp(c, "motd") ) { + CL_MotdPacket( from ); + return; + } + + // echo request from server + if(!Q_stricmp(c, "print")){ + s = MSG_ReadString( msg ); + + Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) ); + Com_Printf( "%s", s ); + + return; + } + + // list of servers sent back by a master server (classic) + if ( !Q_strncmp(c, "getserversResponse", 18) ) { + CL_ServersResponsePacket( &from, msg, qfalse ); + return; + } + + // list of servers sent back by a master server (extended) + if ( !Q_strncmp(c, "getserversExtResponse", 21) ) { + CL_ServersResponsePacket( &from, msg, qtrue ); + return; + } + + Com_DPrintf ("Unknown connectionless packet command.\n"); +} + + +/* +================= +CL_PacketEvent + +A packet has arrived from the main event loop +================= +*/ +void CL_PacketEvent( netadr_t from, msg_t *msg ) { + int headerBytes; + + clc.lastPacketTime = cls.realtime; + + if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) { + CL_ConnectionlessPacket( from, msg ); + return; + } + + if ( clc.state < CA_CONNECTED ) { + return; // can't be a valid sequenced packet + } + + if ( msg->cursize < 4 ) { + Com_Printf ("%s: Runt packet\n", NET_AdrToStringwPort( from )); + return; + } + + // + // packet from server + // + if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) { + Com_DPrintf ("%s:sequenced packet without connection\n" + , NET_AdrToStringwPort( from ) ); + // FIXME: send a client disconnect? + return; + } + + if (!CL_Netchan_Process( &clc.netchan, msg) ) { + return; // out of order, duplicated, etc + } + + // the header is different lengths for reliable and unreliable messages + headerBytes = msg->readcount; + + // track the last message received so it can be returned in + // client messages, allowing the server to detect a dropped + // gamestate + clc.serverMessageSequence = LittleLong( *(int *)msg->data ); + + clc.lastPacketTime = cls.realtime; + CL_ParseServerMessage( msg ); + + // + // we don't know if it is ok to save a demo message until + // after we have parsed the frame + // + if ( clc.demorecording && !clc.demowaiting ) { + CL_WriteDemoMessage( msg, headerBytes ); + } +} + +/* +================== +CL_CheckTimeout + +================== +*/ +void CL_CheckTimeout( void ) { + // + // check timeout + // + if ( ( !CL_CheckPaused() || !sv_paused->integer ) + && clc.state >= CA_CONNECTED && clc.state != CA_CINEMATIC + && cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) { + if (++cl.timeoutcount > 5) { // timeoutcount saves debugger + Com_Printf ("\nServer connection timed out.\n"); + CL_Disconnect( qtrue ); + return; + } + } else { + cl.timeoutcount = 0; + } +} + +/* +================== +CL_CheckPaused +Check whether client has been paused. +================== +*/ +qboolean CL_CheckPaused(void) +{ + // if cl_paused->modified is set, the cvar has only been changed in + // this frame. Keep paused in this frame to ensure the server doesn't + // lag behind. + if(cl_paused->integer || cl_paused->modified) + return qtrue; + + return qfalse; +} + +//============================================================================ + +/* +================== +CL_CheckUserinfo + +================== +*/ +void CL_CheckUserinfo( void ) { + // don't add reliable commands when not yet connected + if(clc.state < CA_CONNECTED) + return; + + // don't overflow the reliable command buffer when paused + if(CL_CheckPaused()) + return; + + // send a reliable userinfo update if needed + if(cvar_modifiedFlags & CVAR_USERINFO) + { + cvar_modifiedFlags &= ~CVAR_USERINFO; + CL_AddReliableCommand(va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ), qfalse); + } +} + +/* +================== +CL_Frame + +================== +*/ +void CL_Frame ( int msec ) { + + if ( !com_cl_running->integer ) { + return; + } + +#ifdef USE_CURL + if(clc.downloadCURLM) { + CL_cURL_PerformDownload(); + // we can't process frames normally when in disconnected + // download mode since the ui vm expects clc.state to be + // CA_CONNECTED + if(clc.cURLDisconnected) { + cls.realFrametime = msec; + cls.frametime = msec; + cls.realtime += cls.frametime; + SCR_UpdateScreen(); + S_Update(); + Con_RunConsole(); + cls.framecount++; + return; + } + } +#endif + + if ( cls.cddialog ) { + // bring up the cd error dialog if needed + cls.cddialog = qfalse; + //VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD ); + } else if ( clc.state == CA_DISCONNECTED && !( Key_GetCatcher( ) & KEYCATCH_UI ) + && !com_sv_running->integer && uivm ) { + // if disconnected, bring up the menu + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + } + + // if recording an avi, lock to a fixed fps + if ( CL_VideoRecording( ) && cl_aviFrameRate->integer && msec) { + // save the current screen + if ( clc.state == CA_ACTIVE || cl_forceavidemo->integer) { + CL_TakeVideoFrame( ); + + // fixed time for next frame' + msec = (int)ceil( (1000.0f / cl_aviFrameRate->value) * com_timescale->value ); + if (msec == 0) { + msec = 1; + } + } + } + + if( cl_autoRecordDemo->integer ) { + if( clc.state == CA_ACTIVE && !clc.demorecording && !clc.demoplaying ) { + // If not recording a demo, and we should be, start one + qtime_t now; + char *nowString; + char *p; + char mapName[ MAX_QPATH ]; + char serverName[ MAX_OSPATH ]; + + Com_RealTime( &now ); + nowString = va( "%04d%02d%02d%02d%02d%02d", + 1900 + now.tm_year, + 1 + now.tm_mon, + now.tm_mday, + now.tm_hour, + now.tm_min, + now.tm_sec ); + + Q_strncpyz( serverName, clc.servername, MAX_OSPATH ); + // Replace the ":" in the address as it is not a valid + // file name character + p = strstr( serverName, ":" ); + if( p ) { + *p = '.'; + } + + Q_strncpyz( mapName, COM_SkipPath( cl.mapname ), sizeof( cl.mapname ) ); + COM_StripExtension(mapName, mapName, sizeof(mapName)); + + Cbuf_ExecuteText( EXEC_NOW, + va( "record %s-%s-%s", nowString, serverName, mapName ) ); + } + else if( clc.state != CA_ACTIVE && clc.demorecording ) { + // Recording, but not CA_ACTIVE, so stop recording + CL_StopRecord_f( ); + } + } + + // save the msec before checking pause + cls.realFrametime = msec; + + // decide the simulation time + cls.frametime = msec; + + cls.realtime += cls.frametime; + + if ( cl_timegraph->integer ) { + SCR_DebugGraph ( cls.realFrametime * 0.25 ); + } + + // see if we need to update any userinfo + CL_CheckUserinfo(); + + // if we haven't gotten a packet in a long time, + // drop the connection + CL_CheckTimeout(); + + // send intentions now + CL_SendCmd(); + + // resend a connection request if necessary + CL_CheckForResend(); + + // decide on the serverTime to render + CL_SetCGameTime(); + + // update the screen + SCR_UpdateScreen(); + + // update audio + S_Update(); + +#ifdef USE_VOIP + CL_CaptureVoip(); +#endif + +#ifdef USE_MUMBLE + CL_UpdateMumble(); +#endif + + // advance local effects for next frame + SCR_RunCinematic(); + + Con_RunConsole(); + + cls.framecount++; +} + + +//============================================================================ + +/* +================ +CL_RefPrintf + +DLL glue +================ +*/ +static __attribute__ ((format (printf, 2, 3))) void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) { + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + if ( print_level == PRINT_ALL ) { + Com_Printf ("%s", msg); + } else if ( print_level == PRINT_WARNING ) { + Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow + } else if ( print_level == PRINT_DEVELOPER ) { + Com_DPrintf (S_COLOR_RED "%s", msg); // red + } +} + + + +/* +============ +CL_ShutdownRef +============ +*/ +void CL_ShutdownRef( void ) { + if ( !re.Shutdown ) { + return; + } + re.Shutdown( qtrue ); + Com_Memset( &re, 0, sizeof( re ) ); +} + +/* +============ +CL_DrawLoadingScreen + +Borrowed from TurtleArena +============ +*/ +void CL_DrawLoadingScreen( void ) { + re.BeginFrame( STEREO_CENTER ); + + SCR_DrawPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, cls.splashShader ); + + if( com_speeds->integer ) { + re.EndFrame( &time_frontend, &time_backend ); + } else { + re.EndFrame( NULL, NULL ); + } +} + +/* +============ +CL_InitRenderer +============ +*/ +void CL_InitRenderer( void ) { + // this sets up the renderer and calls R_Init + re.BeginRegistration( &cls.glconfig ); + + cls.splashShader = re.RegisterShaderNoMip( "splash" ); + + // Draw loading screen the first time the game starts. + /* Borrowed from TurtleArena */ + if( !com_fullyInitialized ) { + CL_DrawLoadingScreen(); + } + + // load character sets + //cls.charSetShader = re.RegisterShader( "gfx/2d/bigchars" ); + cls.charSetShader = re.RegisterShaderNoMip( "gfx/2d/charsgrid_med" ); // this looks bad without proper offsets but at least not q3 asset + cls.whiteShader = re.RegisterShader( "white" ); + cls.consoleShader = re.RegisterShader( "console" ); + g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2; + g_consoleField.widthInChars = g_console_field_width; +} + +/* +============================ +CL_StartHunkUsers + +After the server has cleared the hunk, these will need to be restarted +This is the only place that any of these functions are called from +============================ +*/ +void CL_StartHunkUsers( qboolean rendererOnly ) { + if (!com_cl_running) { + return; + } + + if ( !com_cl_running->integer ) { + return; + } + + if ( !cls.rendererStarted ) { + cls.rendererStarted = qtrue; + CL_InitRenderer(); + } + + if ( rendererOnly ) { + return; + } + + if ( !cls.soundStarted ) { + cls.soundStarted = qtrue; + S_Init(); + } + + if ( !cls.soundRegistered ) { + cls.soundRegistered = qtrue; + S_BeginRegistration(); + } + + if( com_dedicated->integer ) { + return; + } + + if ( !cls.uiStarted ) { + cls.uiStarted = qtrue; + CL_InitUI(); + } +} + +/* +============ +CL_RefMalloc +============ +*/ +void *CL_RefMalloc( int size ) { + return Z_TagMalloc( size, TAG_RENDERER ); +} + +int CL_ScaledMilliseconds(void) { + return Sys_Milliseconds()*com_timescale->value; +} + +/* +============ +CL_InitRef +============ +*/ +void CL_InitRef( void ) { + refimport_t ri; + refexport_t *ret; +#ifdef USE_RENDERER_DLOPEN + GetRefAPI_t GetRefAPI; + char dllName[MAX_OSPATH]; +#endif + + Com_Printf( "----- Initializing Renderer ----\n" ); + +#ifdef USE_RENDERER_DLOPEN + cl_renderer = Cvar_Get("cl_renderer", "opengl1", CVAR_ARCHIVE | CVAR_LATCH); + + Com_sprintf(dllName, sizeof(dllName), "renderer_%s_" ARCH_STRING DLL_EXT, cl_renderer->string); + + if(!(rendererLib = Sys_LoadDll(dllName, qfalse)) && strcmp(cl_renderer->string, cl_renderer->resetString)) + { + Cvar_ForceReset("cl_renderer"); + + Com_sprintf(dllName, sizeof(dllName), "renderer_opengl1_" ARCH_STRING DLL_EXT); + rendererLib = Sys_LoadLibrary(dllName); + } + + if(!rendererLib) + { + Com_Printf("failed:\n\"%s\"\n", Sys_LibraryError()); + Com_Error(ERR_FATAL, "Failed to load renderer"); + } + + GetRefAPI = Sys_LoadFunction(rendererLib, "GetRefAPI"); + if(!GetRefAPI) + { + Com_Error(ERR_FATAL, "Can't load symbol GetRefAPI: '%s'", Sys_LibraryError()); + } +#endif + + ri.Cmd_AddCommand = Cmd_AddCommand; + ri.Cmd_RemoveCommand = Cmd_RemoveCommand; + ri.Cmd_Argc = Cmd_Argc; + ri.Cmd_Argv = Cmd_Argv; + ri.Cmd_ExecuteText = Cbuf_ExecuteText; + ri.Printf = CL_RefPrintf; + ri.Error = Com_Error; + ri.Milliseconds = CL_ScaledMilliseconds; + ri.Malloc = CL_RefMalloc; + ri.Free = Z_Free; +#ifdef HUNK_DEBUG + ri.Hunk_AllocDebug = Hunk_AllocDebug; +#else + ri.Hunk_Alloc = Hunk_Alloc; +#endif + ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory; + ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory; + + ri.CM_ClusterPVS = CM_ClusterPVS; + ri.CM_DrawDebugSurface = CM_DrawDebugSurface; + + ri.FS_ReadFile = FS_ReadFile; + ri.FS_FreeFile = FS_FreeFile; + ri.FS_WriteFile = FS_WriteFile; + ri.FS_FreeFileList = FS_FreeFileList; + ri.FS_ListFiles = FS_ListFiles; + ri.FS_FileIsInPAK = FS_FileIsInPAK; + ri.FS_FileExists = FS_FileExists; + ri.Cvar_Get = Cvar_Get; + ri.Cvar_Set = Cvar_Set; + ri.Cvar_SetValue = Cvar_SetValue; + ri.Cvar_CheckRange = Cvar_CheckRange; + ri.Cvar_VariableIntegerValue = Cvar_VariableIntegerValue; + + // cinematic stuff + + ri.CIN_UploadCinematic = CIN_UploadCinematic; + ri.CIN_PlayCinematic = CIN_PlayCinematic; + ri.CIN_RunCinematic = CIN_RunCinematic; + + ri.CL_WriteAVIVideoFrame = CL_WriteAVIVideoFrame; + + ri.IN_Init = IN_Init; + ri.IN_Shutdown = IN_Shutdown; + ri.IN_Restart = IN_Restart; + + ri.ftol = Q_ftol; + + ri.Sys_SetEnv = Sys_SetEnv; + ri.Sys_GLimpSafeInit = Sys_GLimpSafeInit; + ri.Sys_GLimpInit = Sys_GLimpInit; + ri.Sys_LowPhysicalMemory = Sys_LowPhysicalMemory; + + ret = GetRefAPI( REF_API_VERSION, &ri ); + +#if defined __USEA3D && defined __A3D_GEOM + hA3Dg_ExportRenderGeom (ret); +#endif + + Com_Printf( "-------------------------------\n"); + + if ( !ret ) { + Com_Error (ERR_FATAL, "Couldn't initialize refresh" ); + } + + re = *ret; + + // unpause so the cgame definately gets a snapshot and renders a frame + Cvar_Set( "cl_paused", "0" ); +} + + +//=========================================================================================== + + +void CL_SetModel_f( void ) { + char *arg; + char name[256]; + + arg = Cmd_Argv( 1 ); + if (arg[0]) { + Cvar_Set( "model", arg ); + Cvar_Set( "headmodel", arg ); + } else { + Cvar_VariableStringBuffer( "model", name, sizeof(name) ); + Com_Printf("model is set to %s\n", name); + } +} + + +//=========================================================================================== + + +/* +=============== +CL_Video_f + +video +video [filename] +=============== +*/ +void CL_Video_f( void ) +{ + char filename[ MAX_OSPATH ]; + int i, last; + + if( !clc.demoplaying ) + { + Com_Printf( "The video command can only be used when playing back demos\n" ); + return; + } + + if( Cmd_Argc( ) == 2 ) + { + // explicit filename + Com_sprintf( filename, MAX_OSPATH, "videos/%s.avi", Cmd_Argv( 1 ) ); + } + else + { + // scan for a free filename + for( i = 0; i <= 9999; i++ ) + { + int a, b, c, d; + + last = i; + + a = last / 1000; + last -= a * 1000; + b = last / 100; + last -= b * 100; + c = last / 10; + last -= c * 10; + d = last; + + Com_sprintf( filename, MAX_OSPATH, "videos/video%d%d%d%d.avi", + a, b, c, d ); + + if( !FS_FileExists( filename ) ) + break; // file doesn't exist + } + + if( i > 9999 ) + { + Com_Printf( S_COLOR_RED "ERROR: no free file names to create video\n" ); + return; + } + } + + CL_OpenAVIForWriting( filename ); +} + +/* +=============== +CL_StopVideo_f +=============== +*/ +void CL_StopVideo_f( void ) +{ + CL_CloseAVI( ); +} + +/* +=============== +CL_GenerateQKey + +test to see if a valid QKEY_FILE exists. If one does not, try to generate +it by filling it with 2048 bytes of random data. +=============== +*/ +static void CL_GenerateQKey(void) +{ + int len = 0; + unsigned char buff[ QKEY_SIZE ]; + fileHandle_t f; + + len = FS_SV_FOpenFileRead( QKEY_FILE, &f ); + FS_FCloseFile( f ); + if( len == QKEY_SIZE ) { + Com_Printf( "QKEY found.\n" ); + return; + } + else { + if( len > 0 ) { + Com_Printf( "QKEY file size != %d, regenerating\n", + QKEY_SIZE ); + } + + Com_Printf( "QKEY building random string\n" ); + Com_RandomBytes( buff, sizeof(buff) ); + + f = FS_SV_FOpenFileWrite( QKEY_FILE ); + if( !f ) { + Com_Printf( "QKEY could not open %s for write\n", + QKEY_FILE ); + return; + } + FS_Write( buff, sizeof(buff), f ); + FS_FCloseFile( f ); + Com_Printf( "QKEY generated\n" ); + } +} + +/* +==================== +CL_Init +==================== +*/ +void CL_Init( void ) { + Com_Printf( "----- Client Initialization -----\n" ); + + Con_Init (); + + if(!com_fullyInitialized) + { + CL_ClearState(); + clc.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED + cls.oldGameSet = qfalse; + } + + cls.realtime = 0; + + CL_InitInput (); + + // + // register our variables + // + cl_noprint = Cvar_Get( "cl_noprint", "0", 0 ); +#ifdef UPDATE_SERVER_NAME + cl_motd = Cvar_Get ("cl_motd", "1", 0); +#endif + + cl_timeout = Cvar_Get ("cl_timeout", "200", 0); + + cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP ); + cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP ); + cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP ); + cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP ); + cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP ); + rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP ); + cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP ); + + cl_timedemo = Cvar_Get ("timedemo", "0", 0); + cl_timedemoLog = Cvar_Get ("cl_timedemoLog", "", CVAR_ARCHIVE); + cl_autoRecordDemo = Cvar_Get ("cl_autoRecordDemo", "0", CVAR_ARCHIVE); + cl_aviFrameRate = Cvar_Get ("cl_aviFrameRate", "25", CVAR_ARCHIVE); + cl_aviMotionJpeg = Cvar_Get ("cl_aviMotionJpeg", "1", CVAR_ARCHIVE); + cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0); + + rconAddress = Cvar_Get ("rconAddress", "", 0); + + cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE); + cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE); + cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0); + + cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE ); + cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE ); + + cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE); + cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE); + cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE); + cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE ); + + // 0: legacy mouse acceleration + // 1: new implementation + cl_mouseAccelStyle = Cvar_Get( "cl_mouseAccelStyle", "0", CVAR_ARCHIVE ); + // offset for the power function (for style 1, ignored otherwise) + // this should be set to the max rate value + cl_mouseAccelOffset = Cvar_Get( "cl_mouseAccelOffset", "5", CVAR_ARCHIVE ); + Cvar_CheckRange(cl_mouseAccelOffset, 0.001f, 50000.0f, qfalse); + + cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0); + + cl_allowDownload = Cvar_Get ("cl_allowDownload", "0", CVAR_ARCHIVE); +#ifdef USE_CURL + cl_cURLLib = Cvar_Get("cl_cURLLib", DEFAULT_CURL_LIB, CVAR_ARCHIVE); +#endif + +#ifdef MACOS_X + // In game video is REALLY slow in Mac OS X right now due to driver slowness + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE); +#else + cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE); +#endif + + cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0); + + // init autoswitch so the ui will have it correctly even + // if the cgame hasn't been started + Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE); + + m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE); + m_pitchVeh = Cvar_Get ("m_pitchVeh", "0.022", CVAR_ARCHIVE); + m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE); + m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE); + m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE); +#ifdef MACOS_X + // Input is jittery on OS X w/o this + m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE); +#else + m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE); +#endif + + j_pitch = Cvar_Get ("j_pitch", "0.022", CVAR_ARCHIVE); + j_yaw = Cvar_Get ("j_yaw", "-0.022", CVAR_ARCHIVE); + j_forward = Cvar_Get ("j_forward", "-0.25", CVAR_ARCHIVE); + j_side = Cvar_Get ("j_side", "0.25", CVAR_ARCHIVE); + j_pitch_axis = Cvar_Get ("j_pitch_axis", "3", CVAR_ARCHIVE); + j_yaw_axis = Cvar_Get ("j_yaw_axis", "4", CVAR_ARCHIVE); + j_forward_axis = Cvar_Get ("j_forward_axis", "1", CVAR_ARCHIVE); + j_side_axis = Cvar_Get ("j_side_axis", "0", CVAR_ARCHIVE); + + cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM ); + + Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE ); + + cl_lanForcePackets = Cvar_Get ("cl_lanForcePackets", "1", CVAR_ARCHIVE); + + cl_guidServerUniq = Cvar_Get ("cl_guidServerUniq", "1", CVAR_ARCHIVE); + + // ~ and `, as keys and characters + cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60", CVAR_ARCHIVE); + + // userinfo + Cvar_Get ("name", "Padawan", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("rate", "25000", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("model", "kyle/default", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("char_color_red", "255", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("char_color_green", "255", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("char_color_blue", "255", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("color2", "4", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("forcepowers", "7-1-032330000000001333", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("teamtask", "0", CVAR_USERINFO ); + Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE ); + Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE ); + + Cvar_Get ("password", "", CVAR_USERINFO); + Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE ); + +#ifdef USE_MUMBLE + cl_useMumble = Cvar_Get ("cl_useMumble", "0", CVAR_ARCHIVE | CVAR_LATCH); + cl_mumbleScale = Cvar_Get ("cl_mumbleScale", "0.0254", CVAR_ARCHIVE); +#endif + +#ifdef USE_VOIP + cl_voipSend = Cvar_Get ("cl_voipSend", "0", 0); + cl_voipSendTarget = Cvar_Get ("cl_voipSendTarget", "spatial", 0); + cl_voipGainDuringCapture = Cvar_Get ("cl_voipGainDuringCapture", "0.2", CVAR_ARCHIVE); + cl_voipCaptureMult = Cvar_Get ("cl_voipCaptureMult", "2.0", CVAR_ARCHIVE); + cl_voipUseVAD = Cvar_Get ("cl_voipUseVAD", "0", CVAR_ARCHIVE); + cl_voipVADThreshold = Cvar_Get ("cl_voipVADThreshold", "0.25", CVAR_ARCHIVE); + cl_voipShowMeter = Cvar_Get ("cl_voipShowMeter", "1", CVAR_ARCHIVE); + + // This is a protocol version number. + cl_voip = Cvar_Get ("cl_voip", "1", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_LATCH); + Cvar_CheckRange( cl_voip, 0, 1, qtrue ); + + // If your data rate is too low, you'll get Connection Interrupted warnings + // when VoIP packets arrive, even if you have a broadband connection. + // This might work on rates lower than 25000, but for safety's sake, we'll + // just demand it. Who doesn't have at least a DSL line now, anyhow? If + // you don't, you don't need VoIP. :) + if ((cl_voip->integer) && (Cvar_VariableIntegerValue("rate") < 25000)) { + Com_Printf(S_COLOR_YELLOW "Your network rate is too slow for VoIP.\n"); + Com_Printf("Set 'Data Rate' to 'LAN/Cable/xDSL' in 'Setup/System/Network' and restart.\n"); + Com_Printf("Until then, VoIP is disabled.\n"); + Cvar_Set("cl_voip", "0"); + } +#endif + + + // cgame might not be initialized before menu is used + Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE ); + // Make sure cg_stereoSeparation is zero as that variable is deprecated and should not be used anymore. + Cvar_Get ("cg_stereoSeparation", "0", CVAR_ROM); + + // + // register our commands + // + Cmd_AddCommand ("cmd", CL_ForwardToServer_f); + Cmd_AddCommand ("configstrings", CL_Configstrings_f); + Cmd_AddCommand ("clientinfo", CL_Clientinfo_f); + Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f); + Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f); + Cmd_AddCommand ("disconnect", CL_Disconnect_f); + Cmd_AddCommand ("record", CL_Record_f); + Cmd_AddCommand ("demo", CL_PlayDemo_f); + Cmd_SetCommandCompletionFunc( "demo", CL_CompleteDemoName ); + Cmd_AddCommand ("cinematic", CL_PlayCinematic_f); + Cmd_SetCommandCompletionFunc( "cinematic", CL_CompleteCinematicName ); + Cmd_AddCommand ("stoprecord", CL_StopRecord_f); + Cmd_AddCommand ("connect", CL_Connect_f); + Cmd_AddCommand ("reconnect", CL_Reconnect_f); + Cmd_AddCommand ("localservers", CL_LocalServers_f); + Cmd_AddCommand ("globalservers", CL_GlobalServers_f); + Cmd_AddCommand ("rcon", CL_Rcon_f); + Cmd_SetCommandCompletionFunc( "rcon", CL_CompleteRcon ); + Cmd_AddCommand ("ping", CL_Ping_f ); + Cmd_AddCommand ("serverstatus", CL_ServerStatus_f ); + Cmd_AddCommand ("showip", CL_ShowIP_f ); + Cmd_AddCommand ("fs_openedList", CL_OpenedPK3List_f ); + Cmd_AddCommand ("fs_referencedList", CL_ReferencedPK3List_f ); + Cmd_AddCommand ("model", CL_SetModel_f ); + Cmd_AddCommand ("video", CL_Video_f ); + Cmd_AddCommand ("stopvideo", CL_StopVideo_f ); + CL_InitRef(); + + SCR_Init (); + +// Cbuf_Execute (); + + Cvar_Set( "cl_running", "1" ); + + CL_GenerateQKey(); + Cvar_Get( "cl_guid", "", CVAR_USERINFO | CVAR_ROM ); + CL_UpdateGUID( NULL, 0 ); + + Com_Printf( "----- Client Initialization Complete -----\n" ); +} + + +/* +=============== +CL_Shutdown + +=============== +*/ +void CL_Shutdown(char *finalmsg, qboolean disconnect, qboolean quit) +{ + static qboolean recursive = qfalse; + + // check whether the client is running at all. + if(!(com_cl_running && com_cl_running->integer)) + return; + + Com_Printf( "----- Client Shutdown (%s) -----\n", finalmsg ); + + if ( recursive ) { + Com_Printf( "WARNING: Recursive shutdown\n" ); + return; + } + recursive = qtrue; + + noGameRestart = quit; + + if(disconnect) + CL_Disconnect(qtrue); + + CL_ClearMemory(qtrue); + CL_Snd_Shutdown(); + + Cmd_RemoveCommand ("cmd"); + Cmd_RemoveCommand ("configstrings"); + Cmd_RemoveCommand ("clientinfo"); + Cmd_RemoveCommand ("snd_restart"); + Cmd_RemoveCommand ("vid_restart"); + Cmd_RemoveCommand ("disconnect"); + Cmd_RemoveCommand ("record"); + Cmd_RemoveCommand ("demo"); + Cmd_RemoveCommand ("cinematic"); + Cmd_RemoveCommand ("stoprecord"); + Cmd_RemoveCommand ("connect"); + Cmd_RemoveCommand ("reconnect"); + Cmd_RemoveCommand ("localservers"); + Cmd_RemoveCommand ("globalservers"); + Cmd_RemoveCommand ("rcon"); + Cmd_RemoveCommand ("ping"); + Cmd_RemoveCommand ("serverstatus"); + Cmd_RemoveCommand ("showip"); + Cmd_RemoveCommand ("fs_openedList"); + Cmd_RemoveCommand ("fs_referencedList"); + Cmd_RemoveCommand ("model"); + Cmd_RemoveCommand ("video"); + Cmd_RemoveCommand ("stopvideo"); + + CL_ShutdownInput(); + Con_Shutdown(); + + Cvar_Set( "cl_running", "0" ); + + recursive = qfalse; + + Com_Memset( &cls, 0, sizeof( cls ) ); + Key_SetCatcher( 0 ); + + Com_Printf( "-----------------------\n" ); + +} + +static void CL_SetServerInfo(serverInfo_t *server, const char *info, int ping) { + if (server) { + if (info) { + server->clients = atoi(Info_ValueForKey(info, "clients")); + Q_strncpyz(server->hostName,Info_ValueForKey(info, "hostname"), MAX_NAME_LENGTH); + Q_strncpyz(server->mapName, Info_ValueForKey(info, "mapname"), MAX_NAME_LENGTH); + server->maxClients = atoi(Info_ValueForKey(info, "sv_maxclients")); + Q_strncpyz(server->game,Info_ValueForKey(info, "game"), MAX_NAME_LENGTH); + server->gameType = atoi(Info_ValueForKey(info, "gametype")); + server->netType = atoi(Info_ValueForKey(info, "nettype")); + server->minPing = atoi(Info_ValueForKey(info, "minping")); + server->maxPing = atoi(Info_ValueForKey(info, "maxping")); + server->punkbuster = atoi(Info_ValueForKey(info, "punkbuster")); + server->g_humanplayers = atoi(Info_ValueForKey(info, "g_humanplayers")); + server->g_needpass = atoi(Info_ValueForKey(info, "g_needpass")); + } + server->ping = ping; + } +} + +static void CL_SetServerInfoByAddress(netadr_t from, const char *info, int ping) { + int i; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.localServers[i].adr)) { + CL_SetServerInfo(&cls.localServers[i], info, ping); + } + } + + for (i = 0; i < MAX_GLOBAL_SERVERS; i++) { + if (NET_CompareAdr(from, cls.globalServers[i].adr)) { + CL_SetServerInfo(&cls.globalServers[i], info, ping); + } + } + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + if (NET_CompareAdr(from, cls.favoriteServers[i].adr)) { + CL_SetServerInfo(&cls.favoriteServers[i], info, ping); + } + } + +} + +/* +=================== +CL_ServerInfoPacket +=================== +*/ +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) { + int i, type; + char info[MAX_INFO_STRING]; + char *infoString; + int prot; + char *gamename; + + infoString = MSG_ReadString( msg ); + + // if this isn't the correct gamename, ignore it + gamename = Info_ValueForKey( infoString, "gamename" ); + + if (gamename && *gamename && strcmp(gamename, com_gamename->string)) + { + Com_DPrintf( "Game mismatch in info packet: %s\n", infoString ); + return; + } + + // if this isn't the correct protocol version, ignore it + prot = atoi( Info_ValueForKey( infoString, "protocol" ) ); + + if(prot != com_protocol->integer +#ifdef LEGACY_PROTOCOL + && prot != com_legacyprotocol->integer +#endif + ) + { + Com_DPrintf( "Different protocol info packet: %s\n", infoString ); + return; + } + + // iterate servers waiting for ping response + for (i=0; iretrieved = qtrue; + return qfalse; + } + + // if this server status request has the same address + if ( NET_CompareAdr( to, serverStatus->address) ) { + // if we recieved an response for this server status request + if (!serverStatus->pending) { + Q_strncpyz(serverStatusString, serverStatus->string, maxLen); + serverStatus->retrieved = qtrue; + serverStatus->startTime = 0; + return qtrue; + } + // resend the request regularly + else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) { + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->time = 0; + serverStatus->startTime = Com_Milliseconds(); + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + } + // if retrieved + else if ( serverStatus->retrieved ) { + serverStatus->address = to; + serverStatus->print = qfalse; + serverStatus->pending = qtrue; + serverStatus->retrieved = qfalse; + serverStatus->startTime = Com_Milliseconds(); + serverStatus->time = 0; + NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" ); + return qfalse; + } + return qfalse; +} + +/* +=================== +CL_ServerStatusResponse +=================== +*/ +void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) { + char *s; + char info[MAX_INFO_STRING]; + int i, l, score, ping; + int len; + serverStatus_t *serverStatus; + + serverStatus = NULL; + for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) { + if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) { + serverStatus = &cl_serverStatusList[i]; + break; + } + } + // if we didn't request this server status + if (!serverStatus) { + return; + } + + s = MSG_ReadStringLine( msg ); + + len = 0; + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s); + + if (serverStatus->print) { + Com_Printf("Server settings:\n"); + // print cvars + while (*s) { + for (i = 0; i < 2 && *s; i++) { + if (*s == '\\') + s++; + l = 0; + while (*s) { + info[l++] = *s; + if (l >= MAX_INFO_STRING-1) + break; + s++; + if (*s == '\\') { + break; + } + } + info[l] = '\0'; + if (i) { + Com_Printf("%s\n", info); + } + else { + Com_Printf("%-24s", info); + } + } + } + } + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + if (serverStatus->print) { + Com_Printf("\nPlayers:\n"); + Com_Printf("num: score: ping: name:\n"); + } + for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) { + + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s); + + if (serverStatus->print) { + score = ping = 0; + sscanf(s, "%d %d", &score, &ping); + s = strchr(s, ' '); + if (s) + s = strchr(s+1, ' '); + if (s) + s++; + else + s = "unknown"; + Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s ); + } + } + len = strlen(serverStatus->string); + Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\"); + + serverStatus->time = Com_Milliseconds(); + serverStatus->address = from; + serverStatus->pending = qfalse; + if (serverStatus->print) { + serverStatus->retrieved = qtrue; + } +} + +/* +================== +CL_LocalServers_f +================== +*/ +void CL_LocalServers_f( void ) { + char *message; + int i, j; + netadr_t to; + + Com_Printf( "Scanning for servers on the local network...\n"); + + // reset the list, waiting for response + cls.numlocalservers = 0; + cls.pingUpdateSource = AS_LOCAL; + + for (i = 0; i < MAX_OTHER_SERVERS; i++) { + qboolean b = cls.localServers[i].visible; + Com_Memset(&cls.localServers[i], 0, sizeof(cls.localServers[i])); + cls.localServers[i].visible = b; + } + Com_Memset( &to, 0, sizeof( to ) ); + + // The 'xxx' in the message is a challenge that will be echoed back + // by the server. We don't care about that here, but master servers + // can use that to prevent spoofed server responses from invalid ip + message = "\377\377\377\377getinfo xxx"; + + // send each message twice in case one is dropped + for ( i = 0 ; i < 2 ; i++ ) { + // send a broadcast packet on each server port + // we support multiple server ports so a single machine + // can nicely run multiple servers + for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) { + to.port = BigShort( (short)(PORT_SERVER + j) ); + + to.type = NA_BROADCAST; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + to.type = NA_MULTICAST6; + NET_SendPacket( NS_CLIENT, strlen( message ), message, to ); + } + } +} + +/* +================== +CL_GlobalServers_f +================== +*/ +void CL_GlobalServers_f( void ) { + netadr_t to; + int count, i, masterNum; + char command[1024], *masteraddress; + + if ((count = Cmd_Argc()) < 3 || (masterNum = atoi(Cmd_Argv(1))) < 0 || masterNum > MAX_MASTER_SERVERS - 1) + { + Com_Printf("usage: globalservers [keywords]\n", MAX_MASTER_SERVERS - 1); + return; + } + + sprintf(command, "sv_master%d", masterNum + 1); + masteraddress = Cvar_VariableString(command); + + if(!*masteraddress) + { + Com_Printf( "CL_GlobalServers_f: Error: No master server address given.\n"); + return; + } + + // reset the list, waiting for response + // -1 is used to distinguish a "no response" + + i = NET_StringToAdr(masteraddress, &to, NA_UNSPEC); + + if(!i) + { + Com_Printf( "CL_GlobalServers_f: Error: could not resolve address of master %s\n", masteraddress); + return; + } + else if(i == 2) + to.port = BigShort(PORT_MASTER); + + Com_Printf("Requesting servers from master %s...\n", masteraddress); + + cls.numglobalservers = -1; + cls.pingUpdateSource = AS_GLOBAL; + + // Use the extended query for IPv6 masters + if (to.type == NA_IP6 || to.type == NA_MULTICAST6) + { + int v4enabled = Cvar_VariableIntegerValue("net_enabled") & NET_ENABLEV4; + + if(v4enabled) + { + Com_sprintf(command, sizeof(command), "getserversExt %s %s", + com_gamename->string, Cmd_Argv(2)); + } + else + { + Com_sprintf(command, sizeof(command), "getserversExt %s %s ipv6", + com_gamename->string, Cmd_Argv(2)); + } + } + else + Com_sprintf(command, sizeof(command), "getservers %s %s", + com_gamename->string, Cmd_Argv(2)); + + for (i=3; i < count; i++) + { + Q_strcat(command, sizeof(command), " "); + Q_strcat(command, sizeof(command), Cmd_Argv(i)); + } + + NET_OutOfBandPrint( NS_SERVER, to, "%s", command ); +} + + +/* +================== +CL_GetPing +================== +*/ +void CL_GetPing( int n, char *buf, int buflen, int *pingtime ) +{ + const char *str; + int time; + int maxPing; + + if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port) + { + // empty or invalid slot + buf[0] = '\0'; + *pingtime = 0; + return; + } + + str = NET_AdrToStringwPort( cl_pinglist[n].adr ); + Q_strncpyz( buf, str, buflen ); + + time = cl_pinglist[n].time; + if (!time) + { + // check for timeout + time = Sys_Milliseconds() - cl_pinglist[n].start; + maxPing = Cvar_VariableIntegerValue( "cl_maxPing" ); + if( maxPing < 100 ) { + maxPing = 100; + } + if (time < maxPing) + { + // not timed out yet + time = 0; + } + } + + CL_SetServerInfoByAddress(cl_pinglist[n].adr, cl_pinglist[n].info, cl_pinglist[n].time); + + *pingtime = time; +} + +/* +================== +CL_GetPingInfo +================== +*/ +void CL_GetPingInfo( int n, char *buf, int buflen ) +{ + if (n < 0 || n >= MAX_PINGREQUESTS || !cl_pinglist[n].adr.port) + { + // empty or invalid slot + if (buflen) + buf[0] = '\0'; + return; + } + + Q_strncpyz( buf, cl_pinglist[n].info, buflen ); +} + +/* +================== +CL_ClearPing +================== +*/ +void CL_ClearPing( int n ) +{ + if (n < 0 || n >= MAX_PINGREQUESTS) + return; + + cl_pinglist[n].adr.port = 0; +} + +/* +================== +CL_GetPingQueueCount +================== +*/ +int CL_GetPingQueueCount( void ) +{ + int i; + int count; + ping_t* pingptr; + + count = 0; + pingptr = cl_pinglist; + + for (i=0; iadr.port) { + count++; + } + } + + return (count); +} + +/* +================== +CL_GetFreePing +================== +*/ +ping_t* CL_GetFreePing( void ) +{ + ping_t* pingptr; + ping_t* best; + int oldest; + int i; + int time; + + pingptr = cl_pinglist; + for (i=0; iadr.port) + { + if (!pingptr->time) + { + if (Sys_Milliseconds() - pingptr->start < 500) + { + // still waiting for response + continue; + } + } + else if (pingptr->time < 500) + { + // results have not been queried + continue; + } + } + + // clear it + pingptr->adr.port = 0; + return (pingptr); + } + + // use oldest entry + pingptr = cl_pinglist; + best = cl_pinglist; + oldest = INT_MIN; + for (i=0; istart; + if (time > oldest) + { + oldest = time; + best = pingptr; + } + } + + return (best); +} + +/* +================== +CL_Ping_f +================== +*/ +void CL_Ping_f( void ) { + netadr_t to; + ping_t* pingptr; + char* server; + int argc; + netadrtype_t family = NA_UNSPEC; + + argc = Cmd_Argc(); + + if ( argc != 2 && argc != 3 ) { + Com_Printf( "usage: ping [-4|-6] server\n"); + return; + } + + if(argc == 2) + server = Cmd_Argv(1); + else + { + if(!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if(!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf( "warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + Com_Memset( &to, 0, sizeof(netadr_t) ); + + if ( !NET_StringToAdr( server, &to, family ) ) { + return; + } + + pingptr = CL_GetFreePing(); + + memcpy( &pingptr->adr, &to, sizeof (netadr_t) ); + pingptr->start = Sys_Milliseconds(); + pingptr->time = 0; + + CL_SetServerInfoByAddress(pingptr->adr, NULL, 0); + + NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" ); +} + +/* +================== +CL_UpdateVisiblePings_f +================== +*/ +qboolean CL_UpdateVisiblePings_f(int source) { + int slots, i; + char buff[MAX_STRING_CHARS]; + int pingTime; + int max; + qboolean status = qfalse; + + if (source < 0 || source > AS_FAVORITES) { + return qfalse; + } + + cls.pingUpdateSource = source; + + slots = CL_GetPingQueueCount(); + if (slots < MAX_PINGREQUESTS) { + serverInfo_t *server = NULL; + + switch (source) { + case AS_LOCAL : + server = &cls.localServers[0]; + max = cls.numlocalservers; + break; + case AS_GLOBAL : + server = &cls.globalServers[0]; + max = cls.numglobalservers; + break; + case AS_FAVORITES : + server = &cls.favoriteServers[0]; + max = cls.numfavoriteservers; + break; + default: + return qfalse; + } + for (i = 0; i < max; i++) { + if (server[i].visible) { + if (server[i].ping == -1) { + int j; + + if (slots >= MAX_PINGREQUESTS) { + break; + } + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + continue; + } + if (NET_CompareAdr( cl_pinglist[j].adr, server[i].adr)) { + // already on the list + break; + } + } + if (j >= MAX_PINGREQUESTS) { + status = qtrue; + for (j = 0; j < MAX_PINGREQUESTS; j++) { + if (!cl_pinglist[j].adr.port) { + break; + } + } + memcpy(&cl_pinglist[j].adr, &server[i].adr, sizeof(netadr_t)); + cl_pinglist[j].start = Sys_Milliseconds(); + cl_pinglist[j].time = 0; + NET_OutOfBandPrint( NS_CLIENT, cl_pinglist[j].adr, "getinfo xxx" ); + slots++; + } + } + // if the server has a ping higher than cl_maxPing or + // the ping packet got lost + else if (server[i].ping == 0) { + // if we are updating global servers + if (source == AS_GLOBAL) { + // + if ( cls.numGlobalServerAddresses > 0 ) { + // overwrite this server with one from the additional global servers + cls.numGlobalServerAddresses--; + CL_InitServerInfo(&server[i], &cls.globalServerAddresses[cls.numGlobalServerAddresses]); + // NOTE: the server[i].visible flag stays untouched + } + } + } + } + } + } + + if (slots) { + status = qtrue; + } + for (i = 0; i < MAX_PINGREQUESTS; i++) { + if (!cl_pinglist[i].adr.port) { + continue; + } + CL_GetPing( i, buff, MAX_STRING_CHARS, &pingTime ); + if (pingTime != 0) { + CL_ClearPing(i); + status = qtrue; + } + } + + return status; +} + +/* +================== +CL_ServerStatus_f +================== +*/ +void CL_ServerStatus_f(void) { + netadr_t to, *toptr = NULL; + char *server; + serverStatus_t *serverStatus; + int argc; + netadrtype_t family = NA_UNSPEC; + + argc = Cmd_Argc(); + + if ( argc != 2 && argc != 3 ) + { + if (clc.state != CA_ACTIVE || clc.demoplaying) + { + Com_Printf ("Not connected to a server.\n"); + Com_Printf( "usage: serverstatus [-4|-6] server\n"); + return; + } + + toptr = &clc.serverAddress; + } + + if(!toptr) + { + Com_Memset( &to, 0, sizeof(netadr_t) ); + + if(argc == 2) + server = Cmd_Argv(1); + else + { + if(!strcmp(Cmd_Argv(1), "-4")) + family = NA_IP; + else if(!strcmp(Cmd_Argv(1), "-6")) + family = NA_IP6; + else + Com_Printf( "warning: only -4 or -6 as address type understood.\n"); + + server = Cmd_Argv(2); + } + + toptr = &to; + if ( !NET_StringToAdr( server, toptr, family ) ) + return; + } + + NET_OutOfBandPrint( NS_CLIENT, *toptr, "getstatus" ); + + serverStatus = CL_GetServerStatus( *toptr ); + serverStatus->address = *toptr; + serverStatus->print = qtrue; + serverStatus->pending = qtrue; +} + +/* +================== +CL_ShowIP_f +================== +*/ +void CL_ShowIP_f(void) { + Sys_ShowIP(); +} + +/* +================= +CL_CDKeyValidate +================= +*/ +qboolean CL_CDKeyValidate( const char *key, const char *checksum ) { +#ifdef STANDALONE + return qtrue; +#else + char ch; + byte sum; + char chs[3]; + int i, len; + + len = strlen(key); + if( len != CDKEY_LEN ) { + return qfalse; + } + + if( checksum && strlen( checksum ) != CDCHKSUM_LEN ) { + return qfalse; + } + + sum = 0; + // for loop gets rid of conditional assignment warning + for (i = 0; i < len; i++) { + ch = *key++; + if (ch>='a' && ch<='z') { + ch -= 32; + } + switch( ch ) { + case '2': + case '3': + case '7': + case 'A': + case 'B': + case 'C': + case 'D': + case 'G': + case 'H': + case 'J': + case 'L': + case 'P': + case 'R': + case 'S': + case 'T': + case 'W': + sum += ch; + continue; + default: + return qfalse; + } + } + + sprintf(chs, "%02x", sum); + + if (checksum && !Q_stricmp(chs, checksum)) { + return qtrue; + } + + if (!checksum) { + return qtrue; + } + + return qfalse; +#endif +} diff --git a/code/client/cl_net_chan.c b/code/client/cl_net_chan.c new file mode 100644 index 0000000..9d9f239 --- /dev/null +++ b/code/client/cl_net_chan.c @@ -0,0 +1,187 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "client.h" + +#ifdef LEGACY_PROTOCOL +/* +============== +CL_Netchan_Encode + + // first 12 bytes of the data are always: + long serverId; + long messageAcknowledge; + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Encode( msg_t *msg ) { + int serverId, messageAcknowledge, reliableAcknowledge; + int i, index, srdc, sbit, soob; + byte key, *string; + + if ( msg->cursize <= CL_ENCODE_START ) { + return; + } + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->bit = 0; + msg->readcount = 0; + msg->oob = 0; + + serverId = MSG_ReadLong(msg); + messageAcknowledge = MSG_ReadLong(msg); + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *)clc.serverCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // + key = clc.challenge ^ serverId ^ messageAcknowledge; + for (i = CL_ENCODE_START; i < msg->cursize; i++) { + // modify the key with the last received now acknowledged server command + if (!string[index]) + index = 0; + if (string[index] > 127 || string[index] == '%') { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // encode the data with this key + *(msg->data + i) = (*(msg->data + i)) ^ key; + } +} + +/* +============== +CL_Netchan_Decode + + // first four bytes of the data are always: + long reliableAcknowledge; + +============== +*/ +static void CL_Netchan_Decode( msg_t *msg ) { + long reliableAcknowledge, i, index; + byte key, *string; + int srdc, sbit, soob; + + srdc = msg->readcount; + sbit = msg->bit; + soob = msg->oob; + + msg->oob = 0; + + reliableAcknowledge = MSG_ReadLong(msg); + + msg->oob = soob; + msg->bit = sbit; + msg->readcount = srdc; + + string = (byte *) clc.reliableCommands[ reliableAcknowledge & (MAX_RELIABLE_COMMANDS-1) ]; + index = 0; + // xor the client challenge with the netchan sequence number (need something that changes every message) + key = clc.challenge ^ LittleLong( *(unsigned *)msg->data ); + for (i = msg->readcount + CL_DECODE_START; i < msg->cursize; i++) { + // modify the key with the last sent and with this message acknowledged client command + if (!string[index]) + index = 0; + if (string[index] > 127 || string[index] == '%') { + key ^= '.' << (i & 1); + } + else { + key ^= string[index] << (i & 1); + } + index++; + // decode the data with this key + *(msg->data + i) = *(msg->data + i) ^ key; + } +} +#endif + +/* +================= +CL_Netchan_TransmitNextFragment +================= +*/ +qboolean CL_Netchan_TransmitNextFragment(netchan_t *chan) +{ + if(chan->unsentFragments) + { + Netchan_TransmitNextFragment(chan); + return qtrue; + } + + return qfalse; +} + +/* +=============== +CL_Netchan_Transmit +================ +*/ +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg ) { + MSG_WriteByte( msg, clc_EOF ); + +#ifdef LEGACY_PROTOCOL + if(chan->compat) + CL_Netchan_Encode(msg); +#endif + + Netchan_Transmit(chan, msg->cursize, msg->data); + + // Transmit all fragments without delay + while(CL_Netchan_TransmitNextFragment(chan)) + { + Com_DPrintf("WARNING: #462 unsent fragments (not supposed to happen!)\n"); + } +} + +/* +================= +CL_Netchan_Process +================= +*/ +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ) { + int ret; + + ret = Netchan_Process( chan, msg ); + if (!ret) + return qfalse; + +#ifdef LEGACY_PROTOCOL + if(chan->compat) + CL_Netchan_Decode(msg); +#endif + + return qtrue; +} diff --git a/code/client/cl_parse.c b/code/client/cl_parse.c new file mode 100644 index 0000000..679ec1a --- /dev/null +++ b/code/client/cl_parse.c @@ -0,0 +1,940 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_parse.c -- parse a message received from the server + +#include "client.h" + +char *svc_strings[256] = { + "svc_bad", + + "svc_nop", + "svc_gamestate", + "svc_configstring", + "svc_baseline", + "svc_serverCommand", + "svc_download", + "svc_snapshot", + "svc_setgame", + "svc_mapchange", + "svc_EOF", + "svc_voip", +}; + +void SHOWNET( msg_t *msg, char *s) { + if ( cl_shownet->integer >= 2) { + Com_Printf ("%3i:%s\n", msg->readcount-1, s); + } +} + + +/* +========================================================================= + +MESSAGE PARSING + +========================================================================= +*/ + +/* +================== +CL_DeltaEntity + +Parses deltas from the given base and adds the resulting entity +to the current frame +================== +*/ +void CL_DeltaEntity (msg_t *msg, clSnapshot_t *frame, int newnum, entityState_t *old, + qboolean unchanged) { + entityState_t *state; + + // save the parsed entity state into the big circular buffer so + // it can be used as the source for a later delta + state = &cl.parseEntities[cl.parseEntitiesNum & (MAX_PARSE_ENTITIES-1)]; + + if ( unchanged ) { + *state = *old; + } else { + MSG_ReadDeltaEntity( msg, old, state, newnum ); + } + + if ( state->number == (MAX_GENTITIES-1) ) { + return; // entity was delta removed + } + cl.parseEntitiesNum++; + frame->numEntities++; +} + +/* +================== +CL_ParsePacketEntities + +================== +*/ +void CL_ParsePacketEntities( msg_t *msg, clSnapshot_t *oldframe, clSnapshot_t *newframe) { + int newnum; + entityState_t *oldstate; + int oldindex, oldnum; + + newframe->parseEntitiesNum = cl.parseEntitiesNum; + newframe->numEntities = 0; + + // delta from the entities present in oldframe + oldindex = 0; + oldstate = NULL; + if (!oldframe) { + oldnum = 99999; + } else { + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + + while ( 1 ) { + // read the entity index number + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + + if ( newnum == (MAX_GENTITIES-1) ) { + break; + } + + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParsePacketEntities: end of message"); + } + + while ( oldnum < newnum ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } + if (oldnum == newnum) { + // delta from previous state + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: delta: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, oldstate, qfalse ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + continue; + } + + if ( oldnum > newnum ) { + // delta from baseline + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: baseline: %i\n", msg->readcount, newnum); + } + CL_DeltaEntity( msg, newframe, newnum, &cl.entityBaselines[newnum], qfalse ); + continue; + } + + } + + // any remaining entities in the old frame are copied over + while ( oldnum != 99999 ) { + // one or more entities from the old packet are unchanged + if ( cl_shownet->integer == 3 ) { + Com_Printf ("%3i: unchanged: %i\n", msg->readcount, oldnum); + } + CL_DeltaEntity( msg, newframe, oldnum, oldstate, qtrue ); + + oldindex++; + + if ( oldindex >= oldframe->numEntities ) { + oldnum = 99999; + } else { + oldstate = &cl.parseEntities[ + (oldframe->parseEntitiesNum + oldindex) & (MAX_PARSE_ENTITIES-1)]; + oldnum = oldstate->number; + } + } +} + + +/* +================ +CL_ParseSnapshot + +If the snapshot is parsed properly, it will be copied to +cl.snap and saved in cl.snapshots[]. If the snapshot is invalid +for any reason, no changes to the state will be made at all. +================ +*/ +void CL_ParseSnapshot( msg_t *msg ) { + int len; + clSnapshot_t *old; + clSnapshot_t newSnap; + int deltaNum; + int oldMessageNum; + int i, packetNum; + + // get the reliable sequence acknowledge number + // NOTE: now sent with all server to client messages + //clc.reliableAcknowledge = MSG_ReadLong( msg ); + + // read in the new snapshot to a temporary buffer + // we will only copy to cl.snap if it is valid + Com_Memset (&newSnap, 0, sizeof(newSnap)); + + // we will have read any new server commands in this + // message before we got to svc_snapshot + newSnap.serverCommandNum = clc.serverCommandSequence; + + newSnap.serverTime = MSG_ReadLong( msg ); + + // if we were just unpaused, we can only *now* really let the + // change come into effect or the client hangs. + cl_paused->modified = 0; + + newSnap.messageNum = clc.serverMessageSequence; + + deltaNum = MSG_ReadByte( msg ); + if ( !deltaNum ) { + newSnap.deltaNum = -1; + } else { + newSnap.deltaNum = newSnap.messageNum - deltaNum; + } + newSnap.snapFlags = MSG_ReadByte( msg ); + + // If the frame is delta compressed from data that we + // no longer have available, we must suck up the rest of + // the frame, but not use it, then ask for a non-compressed + // message + if ( newSnap.deltaNum <= 0 ) { + newSnap.valid = qtrue; // uncompressed frame + old = NULL; + clc.demowaiting = qfalse; // we can start recording now + } else { + old = &cl.snapshots[newSnap.deltaNum & PACKET_MASK]; + if ( !old->valid ) { + // should never happen + Com_Printf ("Delta from invalid frame (not supposed to happen!).\n"); + } else if ( old->messageNum != newSnap.deltaNum ) { + // The frame that the server did the delta from + // is too old, so we can't reconstruct it properly. + Com_Printf ("Delta frame too old.\n"); + } else if ( cl.parseEntitiesNum - old->parseEntitiesNum > MAX_PARSE_ENTITIES-128 ) { + Com_Printf ("Delta parseEntitiesNum too old.\n"); + } else { + newSnap.valid = qtrue; // valid delta parse + } + } + + // read areamask + len = MSG_ReadByte( msg ); + + if(len > sizeof(newSnap.areamask)) + { + Com_Error (ERR_DROP,"CL_ParseSnapshot: Invalid size %d for areamask", len); + return; + } + + MSG_ReadData( msg, &newSnap.areamask, len); + + // read playerinfo + SHOWNET( msg, "playerstate" ); + if ( old ) { + MSG_ReadDeltaPlayerstate( msg, &old->ps, &newSnap.ps, qfalse ); + } else { + MSG_ReadDeltaPlayerstate( msg, NULL, &newSnap.ps, qfalse ); + } + + // read packet entities + SHOWNET( msg, "packet entities" ); + CL_ParsePacketEntities( msg, old, &newSnap ); + + // if not valid, dump the entire thing now that it has + // been properly read + if ( !newSnap.valid ) { + return; + } + + // clear the valid flags of any snapshots between the last + // received and this one, so if there was a dropped packet + // it won't look like something valid to delta from next + // time we wrap around in the buffer + oldMessageNum = cl.snap.messageNum + 1; + + if ( newSnap.messageNum - oldMessageNum >= PACKET_BACKUP ) { + oldMessageNum = newSnap.messageNum - ( PACKET_BACKUP - 1 ); + } + for ( ; oldMessageNum < newSnap.messageNum ; oldMessageNum++ ) { + cl.snapshots[oldMessageNum & PACKET_MASK].valid = qfalse; + } + + // copy to the current good spot + cl.snap = newSnap; + cl.snap.ping = 999; + // calculate ping time + for ( i = 0 ; i < PACKET_BACKUP ; i++ ) { + packetNum = ( clc.netchan.outgoingSequence - 1 - i ) & PACKET_MASK; + if ( cl.snap.ps.commandTime >= cl.outPackets[ packetNum ].p_serverTime ) { + cl.snap.ping = cls.realtime - cl.outPackets[ packetNum ].p_realtime; + break; + } + } + // save the frame off in the backup array for later delta comparisons + cl.snapshots[cl.snap.messageNum & PACKET_MASK] = cl.snap; + + if (cl_shownet->integer == 3) { + Com_Printf( " snapshot:%i delta:%i ping:%i\n", cl.snap.messageNum, + cl.snap.deltaNum, cl.snap.ping ); + } + + cl.newSnapshots = qtrue; +} + + +//===================================================================== + +int cl_connectedToPureServer; +int cl_connectedToCheatServer; + +/* +================== +CL_SystemInfoChanged + +The systeminfo configstring has been changed, so parse +new information out of it. This will happen at every +gamestate, and possibly during gameplay. +================== +*/ +void CL_SystemInfoChanged( void ) { + char *systemInfo; + const char *s, *t; + char key[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + qboolean gameSet; + + systemInfo = cl.gameState.stringData + cl.gameState.stringOffsets[ CS_SYSTEMINFO ]; + // NOTE TTimo: + // when the serverId changes, any further messages we send to the server will use this new serverId + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=475 + // in some cases, outdated cp commands might get sent with this news serverId + cl.serverId = atoi( Info_ValueForKey( systemInfo, "sv_serverid" ) ); + + // don't set any vars when playing a demo + if ( clc.demoplaying ) { + return; + } + +#ifdef USE_VOIP +#ifdef LEGACY_PROTOCOL + if(clc.compat) + clc.voipEnabled = qfalse; + else +#endif + { + s = Info_ValueForKey( systemInfo, "sv_voip" ); + if ( Cvar_VariableValue( "g_gametype" ) == GT_SINGLE_PLAYER || Cvar_VariableValue("ui_singlePlayerActive")) + clc.voipEnabled = qfalse; + else + clc.voipEnabled = atoi(s); + } +#endif + + s = Info_ValueForKey( systemInfo, "sv_cheats" ); + cl_connectedToCheatServer = atoi( s ); + if ( !cl_connectedToCheatServer ) { + Cvar_SetCheatState(); + } + + // check pure server string + s = Info_ValueForKey( systemInfo, "sv_paks" ); + t = Info_ValueForKey( systemInfo, "sv_pakNames" ); + FS_PureServerSetLoadedPaks( s, t ); + + s = Info_ValueForKey( systemInfo, "sv_referencedPaks" ); + t = Info_ValueForKey( systemInfo, "sv_referencedPakNames" ); + FS_PureServerSetReferencedPaks( s, t ); + + gameSet = qfalse; + // scan through all the variables in the systeminfo and locally set cvars to match + s = systemInfo; + while ( s ) { + int cvar_flags; + + Info_NextPair( &s, key, value ); + if ( !key[0] ) { + break; + } + + // ehw! + if (!Q_stricmp(key, "fs_game")) + { + if(FS_CheckDirTraversal(value)) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Server sent invalid fs_game value %s\n", value); + continue; + } + + gameSet = qtrue; + } + + if((cvar_flags = Cvar_Flags(key)) == CVAR_NONEXISTENT) + Cvar_Get(key, value, CVAR_SERVER_CREATED | CVAR_ROM); + else + { + // If this cvar may not be modified by a server discard the value. + if(!(cvar_flags & (CVAR_SYSTEMINFO | CVAR_SERVER_CREATED | CVAR_USER_CREATED))) + { +#ifndef STANDALONE + if(Q_stricmp(key, "g_synchronousClients") && Q_stricmp(key, "pmove_fixed") && + Q_stricmp(key, "pmove_msec")) +#endif + { + Com_Printf(S_COLOR_YELLOW "WARNING: server is not allowed to set %s=%s\n", key, value); + continue; + } + } + + Cvar_SetSafe(key, value); + } + } + // if game folder should not be set and it is set at the client side + if ( !gameSet && *Cvar_VariableString("fs_game") ) { + Cvar_Set( "fs_game", "" ); + } + cl_connectedToPureServer = Cvar_VariableValue( "sv_pure" ); +} + +/* +================== +CL_ParseServerInfo +================== +*/ +static void CL_ParseServerInfo(void) +{ + const char *serverInfo; + + serverInfo = cl.gameState.stringData + + cl.gameState.stringOffsets[ CS_SERVERINFO ]; + + clc.sv_allowDownload = atoi(Info_ValueForKey(serverInfo, + "sv_allowDownload")); + Q_strncpyz(clc.sv_dlURL, + Info_ValueForKey(serverInfo, "sv_dlURL"), + sizeof(clc.sv_dlURL)); +} + +/* +================== +CL_ParseGamestate +================== +*/ +void CL_ParseGamestate( msg_t *msg ) { + int i; + entityState_t *es; + int newnum; + entityState_t nullstate; + int cmd; + char *s; + char oldGame[MAX_QPATH]; + + Con_Close(); + + clc.connectPacketCount = 0; + + // wipe local client state + CL_ClearState(); + + // a gamestate always marks a server command sequence + clc.serverCommandSequence = MSG_ReadLong( msg ); + + // parse all the configstrings and baselines + cl.gameState.dataCount = 1; // leave a 0 at the beginning for uninitialized configstrings + while ( 1 ) { + cmd = MSG_ReadByte( msg ); + + if ( cmd == svc_EOF ) { + break; + } + + if ( cmd == svc_configstring ) { + int len; + + i = MSG_ReadShort( msg ); + if ( i < 0 || i >= MAX_CONFIGSTRINGS ) { + Com_Error( ERR_DROP, "configstring > MAX_CONFIGSTRINGS" ); + } + s = MSG_ReadBigString( msg ); + len = strlen( s ); + + if ( len + 1 + cl.gameState.dataCount > MAX_GAMESTATE_CHARS ) { + Com_Error( ERR_DROP, "MAX_GAMESTATE_CHARS exceeded" ); + } + + // append it to the gameState string buffer + cl.gameState.stringOffsets[ i ] = cl.gameState.dataCount; + Com_Memcpy( cl.gameState.stringData + cl.gameState.dataCount, s, len + 1 ); + cl.gameState.dataCount += len + 1; + } else if ( cmd == svc_baseline ) { + newnum = MSG_ReadBits( msg, GENTITYNUM_BITS ); + if ( newnum < 0 || newnum >= MAX_GENTITIES ) { + Com_Error( ERR_DROP, "Baseline number out of range: %i", newnum ); + } + Com_Memset (&nullstate, 0, sizeof(nullstate)); + es = &cl.entityBaselines[ newnum ]; + MSG_ReadDeltaEntity( msg, &nullstate, es, newnum ); + } else { + Com_Error( ERR_DROP, "CL_ParseGamestate: bad command byte" ); + } + } + + clc.clientNum = MSG_ReadLong(msg); + // read the checksum feed + clc.checksumFeed = MSG_ReadLong( msg ); + + // save old gamedir + Cvar_VariableStringBuffer("fs_game", oldGame, sizeof(oldGame)); + + // parse useful values out of CS_SERVERINFO + CL_ParseServerInfo(); + + // parse serverId and other cvars + CL_SystemInfoChanged(); + + // stop recording now so the demo won't have an unnecessary level load at the end. + if(cl_autoRecordDemo->integer && clc.demorecording) + CL_StopRecord_f(); + + // reinitialize the filesystem if the game directory has changed + if(!cls.oldGameSet && (Cvar_Flags("fs_game") & CVAR_MODIFIED)) + { + cls.oldGameSet = qtrue; + Q_strncpyz(cls.oldGame, oldGame, sizeof(cls.oldGame)); + } + + FS_ConditionalRestart(clc.checksumFeed, qfalse); + + // This used to call CL_StartHunkUsers, but now we enter the download state before loading the + // cgame + CL_InitDownloads(); + + // make sure the game starts + Cvar_Set( "cl_paused", "0" ); +} + + +//===================================================================== + +/* +===================== +CL_ParseDownload + +A download message has been received from the server +===================== +*/ +void CL_ParseDownload ( msg_t *msg ) { + int size; + unsigned char data[MAX_MSGLEN]; + uint16_t block; + + if (!*clc.downloadTempName) { + Com_Printf("Server sending download, but no download was requested\n"); + CL_AddReliableCommand("stopdl", qfalse); + return; + } + + // read the data + block = MSG_ReadShort ( msg ); + + if(!block && !clc.downloadBlock) + { + // block zero is special, contains file size + clc.downloadSize = MSG_ReadLong ( msg ); + + Cvar_SetValue( "cl_downloadSize", clc.downloadSize ); + + if (clc.downloadSize < 0) + { + Com_Error( ERR_DROP, "%s", MSG_ReadString( msg ) ); + return; + } + } + + size = MSG_ReadShort ( msg ); + if (size < 0 || size > sizeof(data)) + { + Com_Error(ERR_DROP, "CL_ParseDownload: Invalid size %d for download chunk", size); + return; + } + + MSG_ReadData(msg, data, size); + + if((clc.downloadBlock & 0xFFFF) != block) + { + Com_DPrintf( "CL_ParseDownload: Expected block %d, got %d\n", (clc.downloadBlock & 0xFFFF), block); + return; + } + + // open the file if not opened yet + if (!clc.download) + { + clc.download = FS_SV_FOpenFileWrite( clc.downloadTempName ); + + if (!clc.download) { + Com_Printf( "Could not create %s\n", clc.downloadTempName ); + CL_AddReliableCommand("stopdl", qfalse); + CL_NextDownload(); + return; + } + } + + if (size) + FS_Write( data, size, clc.download ); + + CL_AddReliableCommand(va("nextdl %d", clc.downloadBlock), qfalse); + clc.downloadBlock++; + + clc.downloadCount += size; + + // So UI gets access to it + Cvar_SetValue( "cl_downloadCount", clc.downloadCount ); + + if (!size) { // A zero length block means EOF + if (clc.download) { + FS_FCloseFile( clc.download ); + clc.download = 0; + + // rename the file + FS_SV_Rename ( clc.downloadTempName, clc.downloadName ); + } + + // send intentions now + // We need this because without it, we would hold the last nextdl and then start + // loading right away. If we take a while to load, the server is happily trying + // to send us that last block over and over. + // Write it twice to help make sure we acknowledge the download + CL_WritePacket(); + CL_WritePacket(); + + // get another file if needed + CL_NextDownload (); + } +} + +#ifdef USE_VOIP +static +qboolean CL_ShouldIgnoreVoipSender(int sender) +{ + if (!cl_voip->integer) + return qtrue; // VoIP is disabled. + else if ((sender == clc.clientNum) && (!clc.demoplaying)) + return qtrue; // ignore own voice (unless playing back a demo). + else if (clc.voipMuteAll) + return qtrue; // all channels are muted with extreme prejudice. + else if (clc.voipIgnore[sender]) + return qtrue; // just ignoring this guy. + else if (clc.voipGain[sender] == 0.0f) + return qtrue; // too quiet to play. + + return qfalse; +} + +/* +===================== +CL_PlayVoip + +Play raw data +===================== +*/ + +static void CL_PlayVoip(int sender, int samplecnt, const byte *data, int flags) +{ + if(flags & VOIP_DIRECT) + { + S_RawSamples(sender + 1, samplecnt, clc.speexSampleRate, 2, 1, + data, clc.voipGain[sender], -1); + } + + if(flags & VOIP_SPATIAL) + { + S_RawSamples(sender + MAX_CLIENTS + 1, samplecnt, clc.speexSampleRate, 2, 1, + data, 1.0f, sender); + } +} + +/* +===================== +CL_ParseVoip + +A VoIP message has been received from the server +===================== +*/ +static +void CL_ParseVoip ( msg_t *msg ) { + static short decoded[4096]; // !!! FIXME: don't hardcode. + + const int sender = MSG_ReadShort(msg); + const int generation = MSG_ReadByte(msg); + const int sequence = MSG_ReadLong(msg); + const int frames = MSG_ReadByte(msg); + const int packetsize = MSG_ReadShort(msg); + const int flags = MSG_ReadBits(msg, VOIP_FLAGCNT); + char encoded[1024]; + int seqdiff = sequence - clc.voipIncomingSequence[sender]; + int written = 0; + int i; + + Com_DPrintf("VoIP: %d-byte packet from client %d\n", packetsize, sender); + + if (sender < 0) + return; // short/invalid packet, bail. + else if (generation < 0) + return; // short/invalid packet, bail. + else if (sequence < 0) + return; // short/invalid packet, bail. + else if (frames < 0) + return; // short/invalid packet, bail. + else if (packetsize < 0) + return; // short/invalid packet, bail. + + if (packetsize > sizeof (encoded)) { // overlarge packet? + int bytesleft = packetsize; + while (bytesleft) { + int br = bytesleft; + if (br > sizeof (encoded)) + br = sizeof (encoded); + MSG_ReadData(msg, encoded, br); + bytesleft -= br; + } + return; // overlarge packet, bail. + } + + if (!clc.speexInitialized) { + MSG_ReadData(msg, encoded, packetsize); // skip payload. + return; // can't handle VoIP without libspeex! + } else if (sender >= MAX_CLIENTS) { + MSG_ReadData(msg, encoded, packetsize); // skip payload. + return; // bogus sender. + } else if (CL_ShouldIgnoreVoipSender(sender)) { + MSG_ReadData(msg, encoded, packetsize); // skip payload. + return; // Channel is muted, bail. + } + + // !!! FIXME: make sure data is narrowband? Does decoder handle this? + + Com_DPrintf("VoIP: packet accepted!\n"); + + // This is a new "generation" ... a new recording started, reset the bits. + if (generation != clc.voipIncomingGeneration[sender]) { + Com_DPrintf("VoIP: new generation %d!\n", generation); + speex_bits_reset(&clc.speexDecoderBits[sender]); + clc.voipIncomingGeneration[sender] = generation; + seqdiff = 0; + } else if (seqdiff < 0) { // we're ahead of the sequence?! + // This shouldn't happen unless the packet is corrupted or something. + Com_DPrintf("VoIP: misordered sequence! %d < %d!\n", + sequence, clc.voipIncomingSequence[sender]); + // reset the bits just in case. + speex_bits_reset(&clc.speexDecoderBits[sender]); + seqdiff = 0; + } else if (seqdiff > 100) { // more than 2 seconds of audio dropped? + // just start over. + Com_DPrintf("VoIP: Dropped way too many (%d) frames from client #%d\n", + seqdiff, sender); + speex_bits_reset(&clc.speexDecoderBits[sender]); + seqdiff = 0; + } + + if (seqdiff != 0) { + Com_DPrintf("VoIP: Dropped %d frames from client #%d\n", + seqdiff, sender); + // tell speex that we're missing frames... + for (i = 0; i < seqdiff; i++) { + assert((written + clc.speexFrameSize) * 2 < sizeof (decoded)); + speex_decode_int(clc.speexDecoder[sender], NULL, decoded + written); + written += clc.speexFrameSize; + } + } + + for (i = 0; i < frames; i++) { + char encoded[256]; + const int len = MSG_ReadByte(msg); + if (len < 0) { + Com_DPrintf("VoIP: Short packet!\n"); + break; + } + MSG_ReadData(msg, encoded, len); + + // shouldn't happen, but just in case... + if ((written + clc.speexFrameSize) * 2 > sizeof (decoded)) { + Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", + written * 2, written, i); + + CL_PlayVoip(sender, written, (const byte *) decoded, flags); + written = 0; + } + + speex_bits_read_from(&clc.speexDecoderBits[sender], encoded, len); + speex_decode_int(clc.speexDecoder[sender], + &clc.speexDecoderBits[sender], decoded + written); + + #if 0 + static FILE *encio = NULL; + if (encio == NULL) encio = fopen("voip-incoming-encoded.bin", "wb"); + if (encio != NULL) { fwrite(encoded, len, 1, encio); fflush(encio); } + static FILE *decio = NULL; + if (decio == NULL) decio = fopen("voip-incoming-decoded.bin", "wb"); + if (decio != NULL) { fwrite(decoded+written, clc.speexFrameSize*2, 1, decio); fflush(decio); } + #endif + + written += clc.speexFrameSize; + } + + Com_DPrintf("VoIP: playback %d bytes, %d samples, %d frames\n", + written * 2, written, i); + + if(written > 0) + CL_PlayVoip(sender, written, (const byte *) decoded, flags); + + clc.voipIncomingSequence[sender] = sequence + frames; +} +#endif + + +/* +===================== +CL_ParseCommandString + +Command strings are just saved off until cgame asks for them +when it transitions a snapshot +===================== +*/ +void CL_ParseCommandString( msg_t *msg ) { + char *s; + int seq; + int index; + + seq = MSG_ReadLong( msg ); + s = MSG_ReadString( msg ); + + // see if we have already executed stored it off + if ( clc.serverCommandSequence >= seq ) { + return; + } + clc.serverCommandSequence = seq; + + index = seq & (MAX_RELIABLE_COMMANDS-1); + Q_strncpyz( clc.serverCommands[ index ], s, sizeof( clc.serverCommands[ index ] ) ); +} + + +/* +===================== +CL_ParseServerMessage +===================== +*/ +void CL_ParseServerMessage( msg_t *msg ) { + int cmd; + + if ( cl_shownet->integer == 1 ) { + Com_Printf ("%i ",msg->cursize); + } else if ( cl_shownet->integer >= 2 ) { + Com_Printf ("------------------\n"); + } + + MSG_Bitstream(msg); + + // get the reliable sequence acknowledge number + clc.reliableAcknowledge = MSG_ReadLong( msg ); + // + if ( clc.reliableAcknowledge < clc.reliableSequence - MAX_RELIABLE_COMMANDS ) { + clc.reliableAcknowledge = clc.reliableSequence; + } + + // + // parse the message + // + while ( 1 ) { + if ( msg->readcount > msg->cursize ) { + Com_Error (ERR_DROP,"CL_ParseServerMessage: read past end of server message"); + break; + } + + cmd = MSG_ReadByte( msg ); + + if (cmd == svc_EOF) { + SHOWNET( msg, "END OF MESSAGE" ); + break; + } + + if ( cl_shownet->integer >= 2 ) { + if ( (cmd < 0) || (!svc_strings[cmd]) ) { + Com_Printf( "%3i:BAD CMD %i\n", msg->readcount-1, cmd ); + } else { + SHOWNET( msg, svc_strings[cmd] ); + } + } + + // other commands + switch ( cmd ) { + default: + Com_Error (ERR_DROP,"CL_ParseServerMessage: Illegible server message"); + break; + case svc_nop: + break; + case svc_serverCommand: + CL_ParseCommandString( msg ); + break; + case svc_gamestate: + CL_ParseGamestate( msg ); + break; + case svc_snapshot: + CL_ParseSnapshot( msg ); + break; + case svc_download: + CL_ParseDownload( msg ); + break; + case svc_voip: +#ifdef USE_VOIP + CL_ParseVoip( msg ); +#endif + break; + } + } +} + + diff --git a/code/client/cl_scrn.c b/code/client/cl_scrn.c new file mode 100644 index 0000000..b1f6034 --- /dev/null +++ b/code/client/cl_scrn.c @@ -0,0 +1,592 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc + +#include "client.h" + +qboolean scr_initialized; // ready to draw + +cvar_t *cl_timegraph; +cvar_t *cl_debuggraph; +cvar_t *cl_graphheight; +cvar_t *cl_graphscale; +cvar_t *cl_graphshift; + +/* +================ +SCR_DrawNamedPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ) { + qhandle_t hShader; + + assert( width != 0 ); + + hShader = re.RegisterShader( picname ); + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + +/* +================ +SCR_AdjustFrom640 + +Adjusted for resolution and screen aspect ratio +================ +*/ +void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ) { + float xscale; + float yscale; + +#if 0 + // adjust for wide screens + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + *x += 0.5 * ( cls.glconfig.vidWidth - ( cls.glconfig.vidHeight * 640 / 480 ) ); + } +#endif + + // scale for screen sizes + xscale = cls.glconfig.vidWidth / 640.0; + yscale = cls.glconfig.vidHeight / 480.0; + if ( x ) { + *x *= xscale; + } + if ( y ) { + *y *= yscale; + } + if ( w ) { + *w *= xscale; + } + if ( h ) { + *h *= yscale; + } +} + +/* +================ +SCR_FillRect + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_FillRect( float x, float y, float width, float height, const float *color ) { + re.SetColor( color ); + + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 0, 0, cls.whiteShader ); + + re.SetColor( NULL ); +} + + +/* +================ +SCR_DrawPic + +Coordinates are 640*480 virtual values +================= +*/ +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ) { + SCR_AdjustFrom640( &x, &y, &width, &height ); + re.DrawStretchPic( x, y, width, height, 0, 0, 1, 1, hShader ); +} + + + +/* +** SCR_DrawChar +** chars are drawn at 640*480 virtual screen size +*/ +static void SCR_DrawChar( int x, int y, float size, int ch ) { + int row, col; + float frow, fcol; + float ax, ay, aw, ah; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -size ) { + return; + } + + ax = x; + ay = y; + aw = size; + ah = size; + SCR_AdjustFrom640( &ax, &ay, &aw, &ah ); + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + re.DrawStretchPic( ax, ay, aw, ah, + fcol, frow, + fcol + size, frow + size, + cls.charSetShader ); +} + +/* +** SCR_DrawSmallChar +** small chars are drawn at native screen resolution +*/ +void SCR_DrawSmallChar( int x, int y, int ch ) { + int row, col; + float frow, fcol; + float size; + + ch &= 255; + + if ( ch == ' ' ) { + return; + } + + if ( y < -SMALLCHAR_HEIGHT ) { + return; + } + + row = ch>>4; + col = ch&15; + + frow = row*0.0625; + fcol = col*0.0625; + size = 0.0625; + + re.DrawStretchPic( x, y, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, + fcol, frow, + fcol + size, frow + size, + cls.charSetShader ); +} + + +/* +================== +SCR_DrawBigString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. + +Coordinates are at 640 by 480 virtual resolution +================== +*/ +void SCR_DrawStringExt( int x, int y, float size, const char *string, float *setColor, qboolean forceColor, + qboolean noColorEscape ) { + vec4_t color; + const char *s; + int xx; + + // draw the drop shadow + color[0] = color[1] = color[2] = 0; + color[3] = setColor[3]; + re.SetColor( color ); + s = string; + xx = x; + while ( *s ) { + if ( !noColorEscape && Q_IsColorString( s ) ) { + s += 2; + continue; + } + SCR_DrawChar( xx+2, y+2, size, *s ); + xx += size; + s++; + } + + + // draw the colored text + s = string; + xx = x; + re.SetColor( setColor ); + while ( *s ) { + if ( !noColorEscape && Q_IsColorString( s ) ) { + if ( !forceColor ) { + Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + re.SetColor( color ); + } + s += 2; + continue; + } + SCR_DrawChar( xx, y, size, *s ); + xx += size; + s++; + } + re.SetColor( NULL ); +} + + +void SCR_DrawBigString( int x, int y, const char *s, float alpha, qboolean noColorEscape ) { + float color[4]; + + color[0] = color[1] = color[2] = 1.0; + color[3] = alpha; + SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qfalse, noColorEscape ); +} + +void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color, qboolean noColorEscape ) { + SCR_DrawStringExt( x, y, BIGCHAR_WIDTH, s, color, qtrue, noColorEscape ); +} + + +/* +================== +SCR_DrawSmallString[Color] + +Draws a multi-colored string with a drop shadow, optionally forcing +to a fixed color. +================== +*/ +void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor, + qboolean noColorEscape ) { + vec4_t color; + const char *s; + int xx; + + // draw the colored text + s = string; + xx = x; + re.SetColor( setColor ); + while ( *s ) { + if ( Q_IsColorString( s ) ) { + if ( !forceColor ) { + Com_Memcpy( color, g_color_table[ColorIndex(*(s+1))], sizeof( color ) ); + color[3] = setColor[3]; + re.SetColor( color ); + } + if ( !noColorEscape ) { + s += 2; + continue; + } + } + SCR_DrawSmallChar( xx, y, *s ); + xx += SMALLCHAR_WIDTH; + s++; + } + re.SetColor( NULL ); +} + + + +/* +** SCR_Strlen -- skips color escape codes +*/ +static int SCR_Strlen( const char *str ) { + const char *s = str; + int count = 0; + + while ( *s ) { + if ( Q_IsColorString( s ) ) { + s += 2; + } else { + count++; + s++; + } + } + + return count; +} + +/* +** SCR_GetBigStringWidth +*/ +int SCR_GetBigStringWidth( const char *str ) { + return SCR_Strlen( str ) * 16; +} + + +//=============================================================================== + +/* +================= +SCR_DrawDemoRecording +================= +*/ +void SCR_DrawDemoRecording( void ) { + char string[1024]; + int pos; + + if ( !clc.demorecording ) { + return; + } + if ( clc.spDemoRecording ) { + return; + } + + pos = FS_FTell( clc.demofile ); + sprintf( string, "RECORDING %s: %ik", clc.demoName, pos / 1024 ); + + SCR_DrawStringExt( 320 - strlen( string ) * 4, 20, 8, string, g_color_table[7], qtrue, qfalse ); +} + + +#ifdef USE_VOIP +/* +================= +SCR_DrawVoipMeter +================= +*/ +void SCR_DrawVoipMeter( void ) { + char buffer[16]; + char string[256]; + int limit, i; + + if (!cl_voipShowMeter->integer) + return; // player doesn't want to show meter at all. + else if (!cl_voipSend->integer) + return; // not recording at the moment. + else if (clc.state != CA_ACTIVE) + return; // not connected to a server. + else if (!clc.voipEnabled) + return; // server doesn't support VoIP. + else if (clc.demoplaying) + return; // playing back a demo. + else if (!cl_voip->integer) + return; // client has VoIP support disabled. + + limit = (int) (clc.voipPower * 10.0f); + if (limit > 10) + limit = 10; + + for (i = 0; i < limit; i++) + buffer[i] = '*'; + while (i < 10) + buffer[i++] = ' '; + buffer[i] = '\0'; + + sprintf( string, "VoIP: [%s]", buffer ); + SCR_DrawStringExt( 320 - strlen( string ) * 4, 10, 8, string, g_color_table[7], qtrue, qfalse ); +} +#endif + + + + +/* +=============================================================================== + +DEBUG GRAPH + +=============================================================================== +*/ + +static int current; +static float values[1024]; + +/* +============== +SCR_DebugGraph +============== +*/ +void SCR_DebugGraph (float value) +{ + values[current] = value; + current = (current + 1) % ARRAY_LEN(values); +} + +/* +============== +SCR_DrawDebugGraph +============== +*/ +void SCR_DrawDebugGraph (void) +{ + int a, x, y, w, i, h; + float v; + + // + // draw the graph + // + w = cls.glconfig.vidWidth; + x = 0; + y = cls.glconfig.vidHeight; + re.SetColor( g_color_table[0] ); + re.DrawStretchPic(x, y - cl_graphheight->integer, + w, cl_graphheight->integer, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + + for (a=0 ; ainteger + cl_graphshift->integer; + + if (v < 0) + v += cl_graphheight->integer * (1+(int)(-v / cl_graphheight->integer)); + h = (int)v % cl_graphheight->integer; + re.DrawStretchPic( x+w-1-a, y - h, 1, h, 0, 0, 0, 0, cls.whiteShader ); + } +} + +//============================================================================= + +/* +================== +SCR_Init +================== +*/ +void SCR_Init( void ) { + cl_timegraph = Cvar_Get ("timegraph", "0", CVAR_CHEAT); + cl_debuggraph = Cvar_Get ("debuggraph", "0", CVAR_CHEAT); + cl_graphheight = Cvar_Get ("graphheight", "32", CVAR_CHEAT); + cl_graphscale = Cvar_Get ("graphscale", "1", CVAR_CHEAT); + cl_graphshift = Cvar_Get ("graphshift", "0", CVAR_CHEAT); + + scr_initialized = qtrue; +} + + +//======================================================= + +/* +================== +SCR_DrawScreenField + +This will be called twice if rendering in stereo mode +================== +*/ +void SCR_DrawScreenField( stereoFrame_t stereoFrame ) { + qboolean uiFullscreen; + + re.BeginFrame( stereoFrame ); + + uiFullscreen = (uivm && VM_Call( uivm, UI_IS_FULLSCREEN )); + + // wide aspect ratio screens need to have the sides cleared + // unless they are displaying game renderings + if ( uiFullscreen || (clc.state != CA_ACTIVE && clc.state != CA_CINEMATIC) ) { + if ( cls.glconfig.vidWidth * 480 > cls.glconfig.vidHeight * 640 ) { + re.SetColor( g_color_table[0] ); + re.DrawStretchPic( 0, 0, cls.glconfig.vidWidth, cls.glconfig.vidHeight, 0, 0, 0, 0, cls.whiteShader ); + re.SetColor( NULL ); + } + } + + // if the menu is going to cover the entire screen, we + // don't need to render anything under it + if ( uivm && !uiFullscreen ) { + switch( clc.state ) { + default: + Com_Error( ERR_FATAL, "SCR_DrawScreenField: bad clc.state" ); + break; + case CA_CINEMATIC: + SCR_DrawCinematic(); + break; + case CA_DISCONNECTED: + // force menu up + S_StopAllSounds(); + VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN ); + break; + case CA_CONNECTING: + case CA_CHALLENGING: + case CA_CONNECTED: + // connecting clients will only show the connection dialog + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qfalse ); + break; + case CA_LOADING: + case CA_PRIMED: + // draw the game information screen and loading progress + CL_CGameRendering(stereoFrame); + + // also draw the connection information, so it doesn't + // flash away too briefly on local or lan games + // refresh to update the time + VM_Call( uivm, UI_REFRESH, cls.realtime ); + VM_Call( uivm, UI_DRAW_CONNECT_SCREEN, qtrue ); + break; + case CA_ACTIVE: + // always supply STEREO_CENTER as vieworg offset is now done by the engine. + CL_CGameRendering(stereoFrame); + SCR_DrawDemoRecording(); +#ifdef USE_VOIP + SCR_DrawVoipMeter(); +#endif + break; + } + } + + // the menu draws next + if ( Key_GetCatcher( ) & KEYCATCH_UI && uivm ) { + VM_Call( uivm, UI_REFRESH, cls.realtime ); + } + + // console draws next + Con_DrawConsole (); + + // debug graph can be drawn on top of anything + if ( cl_debuggraph->integer || cl_timegraph->integer || cl_debugMove->integer ) { + SCR_DrawDebugGraph (); + } +} + +/* +================== +SCR_UpdateScreen + +This is called every frame, and can also be called explicitly to flush +text to the screen. +================== +*/ +void SCR_UpdateScreen( void ) { + static int recursive; + + if ( !scr_initialized ) { + return; // not initialized yet + } + + if ( ++recursive > 2 ) { + Com_Error( ERR_FATAL, "SCR_UpdateScreen: recursively called" ); + } + recursive = 1; + + // If there is no VM, there are also no rendering commands issued. Stop the renderer in + // that case. + if( uivm || com_dedicated->integer ) + { + // XXX + int in_anaglyphMode = Cvar_VariableIntegerValue("r_anaglyphMode"); + // if running in stereo, we need to draw the frame twice + if ( cls.glconfig.stereoEnabled || in_anaglyphMode) { + SCR_DrawScreenField( STEREO_LEFT ); + SCR_DrawScreenField( STEREO_RIGHT ); + } else { + SCR_DrawScreenField( STEREO_CENTER ); + } + + if ( com_speeds->integer ) { + re.EndFrame( &time_frontend, &time_backend ); + } else { + re.EndFrame( NULL, NULL ); + } + } + + recursive = 0; +} + diff --git a/code/client/cl_ui.c b/code/client/cl_ui.c new file mode 100644 index 0000000..a4d680d --- /dev/null +++ b/code/client/cl_ui.c @@ -0,0 +1,1229 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" + +#include "../botlib/botlib.h" + +extern botlib_export_t *botlib_export; + +vm_t *uivm; + +/* +==================== +GetClientState +==================== +*/ +static void GetClientState( uiClientState_t *state ) { + state->connectPacketCount = clc.connectPacketCount; + state->connState = clc.state; + Q_strncpyz( state->servername, clc.servername, sizeof( state->servername ) ); + Q_strncpyz( state->updateInfoString, cls.updateInfoString, sizeof( state->updateInfoString ) ); + Q_strncpyz( state->messageString, clc.serverMessage, sizeof( state->messageString ) ); + state->clientNum = cl.snap.ps.clientNum; +} + +/* +==================== +LAN_LoadCachedServers +==================== +*/ +void LAN_LoadCachedServers( void ) { + int size; + fileHandle_t fileIn; + cls.numglobalservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + if (FS_SV_FOpenFileRead("servercache.dat", &fileIn)) { + FS_Read(&cls.numglobalservers, sizeof(int), fileIn); + FS_Read(&cls.numfavoriteservers, sizeof(int), fileIn); + FS_Read(&size, sizeof(int), fileIn); + if (size == sizeof(cls.globalServers) + sizeof(cls.favoriteServers)) { + FS_Read(&cls.globalServers, sizeof(cls.globalServers), fileIn); + FS_Read(&cls.favoriteServers, sizeof(cls.favoriteServers), fileIn); + } else { + cls.numglobalservers = cls.numfavoriteservers = 0; + cls.numGlobalServerAddresses = 0; + } + FS_FCloseFile(fileIn); + } +} + +/* +==================== +LAN_SaveServersToCache +==================== +*/ +void LAN_SaveServersToCache( void ) { + int size; + fileHandle_t fileOut = FS_SV_FOpenFileWrite("servercache.dat"); + FS_Write(&cls.numglobalservers, sizeof(int), fileOut); + FS_Write(&cls.numfavoriteservers, sizeof(int), fileOut); + size = sizeof(cls.globalServers) + sizeof(cls.favoriteServers); + FS_Write(&size, sizeof(int), fileOut); + FS_Write(&cls.globalServers, sizeof(cls.globalServers), fileOut); + FS_Write(&cls.favoriteServers, sizeof(cls.favoriteServers), fileOut); + FS_FCloseFile(fileOut); +} + + +/* +==================== +LAN_ResetPings +==================== +*/ +static void LAN_ResetPings(int source) { + int count,i; + serverInfo_t *servers = NULL; + count = 0; + + switch (source) { + case AS_LOCAL : + servers = &cls.localServers[0]; + count = MAX_OTHER_SERVERS; + break; + case AS_MPLAYER: + case AS_GLOBAL : + servers = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES : + servers = &cls.favoriteServers[0]; + count = MAX_OTHER_SERVERS; + break; + } + if (servers) { + for (i = 0; i < count; i++) { + servers[i].ping = -1; + } + } +} + +/* +==================== +LAN_AddServer +==================== +*/ +static int LAN_AddServer(int source, const char *name, const char *address) { + int max, *count, i; + netadr_t adr; + serverInfo_t *servers = NULL; + max = MAX_OTHER_SERVERS; + count = NULL; + + switch (source) { + case AS_LOCAL : + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER: + case AS_GLOBAL : + max = MAX_GLOBAL_SERVERS; + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES : + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if (servers && *count < max) { + NET_StringToAdr( address, &adr, NA_IP ); + for ( i = 0; i < *count; i++ ) { + if (NET_CompareAdr(servers[i].adr, adr)) { + break; + } + } + if (i >= *count) { + servers[*count].adr = adr; + Q_strncpyz(servers[*count].hostName, name, sizeof(servers[*count].hostName)); + servers[*count].visible = qtrue; + (*count)++; + return 1; + } + return 0; + } + return -1; +} + +/* +==================== +LAN_RemoveServer +==================== +*/ +static void LAN_RemoveServer(int source, const char *addr) { + int *count, i; + serverInfo_t *servers = NULL; + count = NULL; + switch (source) { + case AS_LOCAL : + count = &cls.numlocalservers; + servers = &cls.localServers[0]; + break; + case AS_MPLAYER: + case AS_GLOBAL : + count = &cls.numglobalservers; + servers = &cls.globalServers[0]; + break; + case AS_FAVORITES : + count = &cls.numfavoriteservers; + servers = &cls.favoriteServers[0]; + break; + } + if (servers) { + netadr_t comp; + NET_StringToAdr( addr, &comp, NA_IP ); + for (i = 0; i < *count; i++) { + if (NET_CompareAdr( comp, servers[i].adr)) { + int j = i; + while (j < *count - 1) { + Com_Memcpy(&servers[j], &servers[j+1], sizeof(servers[j])); + j++; + } + (*count)--; + break; + } + } + } +} + + +/* +==================== +LAN_GetServerCount +==================== +*/ +static int LAN_GetServerCount( int source ) { + switch (source) { + case AS_LOCAL : + return cls.numlocalservers; + break; + case AS_MPLAYER: + case AS_GLOBAL : + return cls.numglobalservers; + break; + case AS_FAVORITES : + return cls.numfavoriteservers; + break; + } + return 0; +} + +/* +==================== +LAN_GetLocalServerAddressString +==================== +*/ +static void LAN_GetServerAddressString( int source, int n, char *buf, int buflen ) { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + Q_strncpyz(buf, NET_AdrToStringwPort( cls.localServers[n].adr) , buflen ); + return; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + Q_strncpyz(buf, NET_AdrToStringwPort( cls.globalServers[n].adr) , buflen ); + return; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + Q_strncpyz(buf, NET_AdrToStringwPort( cls.favoriteServers[n].adr) , buflen ); + return; + } + break; + } + buf[0] = '\0'; +} + +/* +==================== +LAN_GetServerInfo +==================== +*/ +static void LAN_GetServerInfo( int source, int n, char *buf, int buflen ) { + char info[MAX_STRING_CHARS]; + serverInfo_t *server = NULL; + info[0] = '\0'; + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.favoriteServers[n]; + } + break; + } + if (server && buf) { + buf[0] = '\0'; + Info_SetValueForKey( info, "hostname", server->hostName); + Info_SetValueForKey( info, "mapname", server->mapName); + Info_SetValueForKey( info, "clients", va("%i",server->clients)); + Info_SetValueForKey( info, "sv_maxclients", va("%i",server->maxClients)); + Info_SetValueForKey( info, "ping", va("%i",server->ping)); + Info_SetValueForKey( info, "minping", va("%i",server->minPing)); + Info_SetValueForKey( info, "maxping", va("%i",server->maxPing)); + Info_SetValueForKey( info, "game", server->game); + Info_SetValueForKey( info, "gametype", va("%i",server->gameType)); + Info_SetValueForKey( info, "nettype", va("%i",server->netType)); + Info_SetValueForKey( info, "addr", NET_AdrToStringwPort(server->adr)); + Info_SetValueForKey( info, "punkbuster", va("%i", server->punkbuster)); + Info_SetValueForKey( info, "g_needpass", va("%i", server->g_needpass)); + Info_SetValueForKey( info, "g_humanplayers", va("%i", server->g_humanplayers)); + Q_strncpyz(buf, info, buflen); + } else { + if (buf) { + buf[0] = '\0'; + } + } +} + +/* +==================== +LAN_GetServerPing +==================== +*/ +static int LAN_GetServerPing( int source, int n ) { + serverInfo_t *server = NULL; + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.localServers[n]; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + server = &cls.globalServers[n]; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + server = &cls.favoriteServers[n]; + } + break; + } + if (server) { + return server->ping; + } + return -1; +} + +/* +==================== +LAN_GetServerPtr +==================== +*/ +static serverInfo_t *LAN_GetServerPtr( int source, int n ) { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return &cls.localServers[n]; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + return &cls.globalServers[n]; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return &cls.favoriteServers[n]; + } + break; + } + return NULL; +} + +/* +==================== +LAN_CompareServers +==================== +*/ +static int LAN_CompareServers( int source, int sortKey, int sortDir, int s1, int s2 ) { + int res; + serverInfo_t *server1, *server2; + + server1 = LAN_GetServerPtr(source, s1); + server2 = LAN_GetServerPtr(source, s2); + if (!server1 || !server2) { + return 0; + } + + res = 0; + switch( sortKey ) { + case SORT_HOST: + res = Q_stricmp( server1->hostName, server2->hostName ); + break; + + case SORT_MAP: + res = Q_stricmp( server1->mapName, server2->mapName ); + break; + case SORT_CLIENTS: + if (server1->clients < server2->clients) { + res = -1; + } + else if (server1->clients > server2->clients) { + res = 1; + } + else { + res = 0; + } + break; + case SORT_GAME: + if (server1->gameType < server2->gameType) { + res = -1; + } + else if (server1->gameType > server2->gameType) { + res = 1; + } + else { + res = 0; + } + break; + case SORT_PING: + if (server1->ping < server2->ping) { + res = -1; + } + else if (server1->ping > server2->ping) { + res = 1; + } + else { + res = 0; + } + break; + } + + if (sortDir) { + if (res < 0) + return 1; + if (res > 0) + return -1; + return 0; + } + return res; +} + +/* +==================== +LAN_GetPingQueueCount +==================== +*/ +static int LAN_GetPingQueueCount( void ) { + return (CL_GetPingQueueCount()); +} + +/* +==================== +LAN_ClearPing +==================== +*/ +static void LAN_ClearPing( int n ) { + CL_ClearPing( n ); +} + +/* +==================== +LAN_GetPing +==================== +*/ +static void LAN_GetPing( int n, char *buf, int buflen, int *pingtime ) { + CL_GetPing( n, buf, buflen, pingtime ); +} + +/* +==================== +LAN_GetPingInfo +==================== +*/ +static void LAN_GetPingInfo( int n, char *buf, int buflen ) { + CL_GetPingInfo( n, buf, buflen ); +} + +/* +==================== +LAN_MarkServerVisible +==================== +*/ +static void LAN_MarkServerVisible(int source, int n, qboolean visible ) { + if (n == -1) { + int count = MAX_OTHER_SERVERS; + serverInfo_t *server = NULL; + switch (source) { + case AS_LOCAL : + server = &cls.localServers[0]; + break; + case AS_MPLAYER: + case AS_GLOBAL : + server = &cls.globalServers[0]; + count = MAX_GLOBAL_SERVERS; + break; + case AS_FAVORITES : + server = &cls.favoriteServers[0]; + break; + } + if (server) { + for (n = 0; n < count; n++) { + server[n].visible = visible; + } + } + + } else { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + cls.localServers[n].visible = visible; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + cls.globalServers[n].visible = visible; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + cls.favoriteServers[n].visible = visible; + } + break; + } + } +} + + +/* +======================= +LAN_ServerIsVisible +======================= +*/ +static int LAN_ServerIsVisible(int source, int n ) { + switch (source) { + case AS_LOCAL : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return cls.localServers[n].visible; + } + break; + case AS_MPLAYER: + case AS_GLOBAL : + if (n >= 0 && n < MAX_GLOBAL_SERVERS) { + return cls.globalServers[n].visible; + } + break; + case AS_FAVORITES : + if (n >= 0 && n < MAX_OTHER_SERVERS) { + return cls.favoriteServers[n].visible; + } + break; + } + return qfalse; +} + +/* +======================= +LAN_UpdateVisiblePings +======================= +*/ +qboolean LAN_UpdateVisiblePings(int source ) { + return CL_UpdateVisiblePings_f(source); +} + +/* +==================== +LAN_GetServerStatus +==================== +*/ +int LAN_GetServerStatus( char *serverAddress, char *serverStatus, int maxLen ) { + return CL_ServerStatus( serverAddress, serverStatus, maxLen ); +} + +/* +==================== +CL_GetGlConfig +==================== +*/ +static void CL_GetGlconfig( glconfig_t *config ) { + *config = cls.glconfig; +} + +/* +==================== +CL_GetClipboardData +==================== +*/ +static void CL_GetClipboardData( char *buf, int buflen ) { + char *cbd; + + cbd = Sys_GetClipboardData(); + + if ( !cbd ) { + *buf = 0; + return; + } + + Q_strncpyz( buf, cbd, buflen ); + + Z_Free( cbd ); +} + +/* +==================== +Key_KeynumToStringBuf +==================== +*/ +static void Key_KeynumToStringBuf( int keynum, char *buf, int buflen ) { + Q_strncpyz( buf, Key_KeynumToString( keynum ), buflen ); +} + +/* +==================== +Key_GetBindingBuf +==================== +*/ +static void Key_GetBindingBuf( int keynum, char *buf, int buflen ) { + char *value; + + value = Key_GetBinding( keynum ); + if ( value ) { + Q_strncpyz( buf, value, buflen ); + } + else { + *buf = 0; + } +} + +/* +==================== +CLUI_GetCDKey +==================== +*/ +static void CLUI_GetCDKey( char *buf, int buflen ) { +#ifndef STANDALONE + cvar_t *fs; + fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { + Com_Memcpy( buf, &cl_cdkey[16], 16); + buf[16] = 0; + } else { + Com_Memcpy( buf, cl_cdkey, 16); + buf[16] = 0; + } +#else + *buf = 0; +#endif +} + + +/* +==================== +CLUI_SetCDKey +==================== +*/ +#ifndef STANDALONE +static void CLUI_SetCDKey( char *buf ) { + cvar_t *fs; + fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); + if (UI_usesUniqueCDKey() && fs && fs->string[0] != 0) { + Com_Memcpy( &cl_cdkey[16], buf, 16 ); + cl_cdkey[32] = 0; + // set the flag so the fle will be written at the next opportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } else { + Com_Memcpy( cl_cdkey, buf, 16 ); + // set the flag so the fle will be written at the next opportunity + cvar_modifiedFlags |= CVAR_ARCHIVE; + } +} +#endif + +/* +==================== +GetConfigString +==================== +*/ +static int GetConfigString(int index, char *buf, int size) +{ + int offset; + + if (index < 0 || index >= MAX_CONFIGSTRINGS) + return qfalse; + + offset = cl.gameState.stringOffsets[index]; + if (!offset) { + if( size ) { + buf[0] = 0; + } + return qfalse; + } + + Q_strncpyz( buf, cl.gameState.stringData+offset, size); + + return qtrue; +} + +/* +==================== +FloatAsInt +==================== +*/ +static int FloatAsInt( float f ) { + floatint_t fi; + fi.f = f; + return fi.i; +} + +/* +==================== +CL_UISystemCalls + +The ui module is making a system call +==================== +*/ +intptr_t CL_UISystemCalls( intptr_t *args ) { + switch( args[0] ) { + case UI_ERROR: + Com_Error( ERR_DROP, "%s", (const char*)VMA(1) ); + return 0; + + case UI_PRINT: + Com_Printf( "%s", (const char*)VMA(1) ); + return 0; + + case UI_MILLISECONDS: + return Sys_Milliseconds(); + + case UI_CVAR_REGISTER: + Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); + return 0; + + case UI_CVAR_UPDATE: + Cvar_Update( VMA(1) ); + return 0; + + case UI_CVAR_SET: + Cvar_SetSafe( VMA(1), VMA(2) ); + return 0; + + case UI_CVAR_VARIABLEVALUE: + return FloatAsInt( Cvar_VariableValue( VMA(1) ) ); + + case UI_CVAR_VARIABLESTRINGBUFFER: + Cvar_VariableStringBuffer( VMA(1), VMA(2), args[3] ); + return 0; + + case UI_CVAR_SETVALUE: + Cvar_SetValueSafe( VMA(1), VMF(2) ); + return 0; + + case UI_CVAR_RESET: + Cvar_Reset( VMA(1) ); + return 0; + + case UI_CVAR_CREATE: + Cvar_Get( VMA(1), VMA(2), args[3] ); + return 0; + + case UI_CVAR_INFOSTRINGBUFFER: + Cvar_InfoStringBuffer( args[1], VMA(2), args[3] ); + return 0; + + case UI_ARGC: + return Cmd_Argc(); + + case UI_ARGV: + Cmd_ArgvBuffer( args[1], VMA(2), args[3] ); + return 0; + + case UI_CMD_EXECUTETEXT: + if(args[1] == 0 + && (!strncmp(VMA(2), "snd_restart", 11) + || !strncmp(VMA(2), "vid_restart", 11) + || !strncmp(VMA(2), "quit", 5) + || !strncmp(VMA(2), "game_restart", 12))) + { + Com_Printf (S_COLOR_YELLOW "turning EXEC_NOW '%.11s' into EXEC_INSERT\n", (const char*)VMA(2)); + args[1] = EXEC_INSERT; + } + Cbuf_ExecuteText( args[1], VMA(2) ); + return 0; + + case UI_FS_FOPENFILE: + return FS_FOpenFileByMode( VMA(1), VMA(2), args[3] ); + + case UI_FS_READ: + FS_Read2( VMA(1), args[2], args[3] ); + return 0; + + case UI_FS_WRITE: + FS_Write( VMA(1), args[2], args[3] ); + return 0; + + case UI_FS_FCLOSEFILE: + FS_FCloseFile( args[1] ); + return 0; + + case UI_FS_GETFILELIST: + return FS_GetFileList( VMA(1), VMA(2), VMA(3), args[4] ); + +// case UI_FS_SEEK: +// return FS_Seek( args[1], args[2], args[3] ); + + case UI_R_REGISTERMODEL: + return re.RegisterModel( VMA(1) ); + + case UI_R_REGISTERSKIN: + return re.RegisterSkin( VMA(1) ); + + case UI_R_REGISTERSHADERNOMIP: + return re.RegisterShaderNoMip( VMA(1) ); + + case UI_R_CLEARSCENE: + re.ClearScene(); + return 0; + + case UI_R_ADDREFENTITYTOSCENE: + re.AddRefEntityToScene( VMA(1) ); + return 0; + + case UI_R_ADDPOLYTOSCENE: + re.AddPolyToScene( args[1], args[2], VMA(3), 1 ); + return 0; + + case UI_R_ADDLIGHTTOSCENE: + re.AddLightToScene( VMA(1), VMF(2), VMF(3), VMF(4), VMF(5) ); + return 0; + + case UI_R_RENDERSCENE: + re.RenderScene( VMA(1) ); + return 0; + + case UI_R_SETCOLOR: + re.SetColor( VMA(1) ); + return 0; + + case UI_R_DRAWSTRETCHPIC: + re.DrawStretchPic( VMF(1), VMF(2), VMF(3), VMF(4), VMF(5), VMF(6), VMF(7), VMF(8), args[9] ); + return 0; + + case UI_R_MODELBOUNDS: + re.ModelBounds( args[1], VMA(2), VMA(3) ); + return 0; + + case UI_UPDATESCREEN: + SCR_UpdateScreen(); + return 0; + + case UI_CM_LERPTAG: + re.LerpTag( VMA(1), args[2], args[3], args[4], VMF(5), VMA(6) ); + return 0; + + case UI_S_REGISTERSOUND: + return S_RegisterSound( VMA(1), qfalse ); + + case UI_S_STARTLOCALSOUND: + S_StartLocalSound( args[1], args[2] ); + return 0; + + case UI_KEY_KEYNUMTOSTRINGBUF: + Key_KeynumToStringBuf( args[1], VMA(2), args[3] ); + return 0; + + case UI_KEY_GETBINDINGBUF: + Key_GetBindingBuf( args[1], VMA(2), args[3] ); + return 0; + + case UI_KEY_SETBINDING: + Key_SetBinding( args[1], VMA(2) ); + return 0; + + case UI_KEY_ISDOWN: + return Key_IsDown( args[1] ); + + case UI_KEY_GETOVERSTRIKEMODE: + return Key_GetOverstrikeMode(); + + case UI_KEY_SETOVERSTRIKEMODE: + Key_SetOverstrikeMode( args[1] ); + return 0; + + case UI_KEY_CLEARSTATES: + Key_ClearStates(); + return 0; + + case UI_KEY_GETCATCHER: + return Key_GetCatcher(); + + case UI_KEY_SETCATCHER: + // Don't allow the ui module to close the console + Key_SetCatcher( args[1] | ( Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ); + return 0; + + case UI_GETCLIPBOARDDATA: + CL_GetClipboardData( VMA(1), args[2] ); + return 0; + + case UI_GETCLIENTSTATE: + GetClientState( VMA(1) ); + return 0; + + case UI_GETGLCONFIG: + CL_GetGlconfig( VMA(1) ); + return 0; + + case UI_GETCONFIGSTRING: + return GetConfigString( args[1], VMA(2), args[3] ); + + case UI_LAN_LOADCACHEDSERVERS: + LAN_LoadCachedServers(); + return 0; + + case UI_LAN_SAVECACHEDSERVERS: + LAN_SaveServersToCache(); + return 0; + + case UI_LAN_ADDSERVER: + return LAN_AddServer(args[1], VMA(2), VMA(3)); + + case UI_LAN_REMOVESERVER: + LAN_RemoveServer(args[1], VMA(2)); + return 0; + + case UI_LAN_GETPINGQUEUECOUNT: + return LAN_GetPingQueueCount(); + + case UI_LAN_CLEARPING: + LAN_ClearPing( args[1] ); + return 0; + + case UI_LAN_GETPING: + LAN_GetPing( args[1], VMA(2), args[3], VMA(4) ); + return 0; + + case UI_LAN_GETPINGINFO: + LAN_GetPingInfo( args[1], VMA(2), args[3] ); + return 0; + + case UI_LAN_GETSERVERCOUNT: + return LAN_GetServerCount(args[1]); + + case UI_LAN_GETSERVERADDRESSSTRING: + LAN_GetServerAddressString( args[1], args[2], VMA(3), args[4] ); + return 0; + + case UI_LAN_GETSERVERINFO: + LAN_GetServerInfo( args[1], args[2], VMA(3), args[4] ); + return 0; + + case UI_LAN_GETSERVERPING: + return LAN_GetServerPing( args[1], args[2] ); + + case UI_LAN_MARKSERVERVISIBLE: + LAN_MarkServerVisible( args[1], args[2], args[3] ); + return 0; + + case UI_LAN_SERVERISVISIBLE: + return LAN_ServerIsVisible( args[1], args[2] ); + + case UI_LAN_UPDATEVISIBLEPINGS: + return LAN_UpdateVisiblePings( args[1] ); + + case UI_LAN_RESETPINGS: + LAN_ResetPings( args[1] ); + return 0; + + case UI_LAN_SERVERSTATUS: + return LAN_GetServerStatus( VMA(1), VMA(2), args[3] ); + + case UI_LAN_COMPARESERVERS: + return LAN_CompareServers( args[1], args[2], args[3], args[4], args[5] ); + + case UI_MEMORY_REMAINING: + return Hunk_MemoryRemaining(); + + case UI_GET_CDKEY: + CLUI_GetCDKey( VMA(1), args[2] ); + return 0; + + case UI_SET_CDKEY: +#ifndef STANDALONE + CLUI_SetCDKey( VMA(1) ); +#endif + return 0; + +// case UI_SET_PBCLSTATUS: +// return 0; + + case UI_R_REGISTERFONT: + re.RegisterFont( VMA(1) ); + return 0; + + case UI_R_FONT_STRLENPIXELS: + return re.Font_StrLenPixels( VMA(1), args[2], VMF(3) ); + + case UI_R_FONT_STRLENCHARS: + return re.Font_StrLenChars( VMA(1) ); + + case UI_R_FONT_STRHEIGHTPIXELS: + return re.Font_HeightPixels( args[1], VMF(2) ); + + case UI_R_FONT_DRAWSTRING: + re.Font_DrawString( args[1], args[2], VMA(3), VMA(4), args[5], args[6], VMF(7) ); + return 0; + + case UI_LANGUAGE_ISASIAN: + return 0; + + case UI_LANGUAGE_USESSPACES: + return 1; + + case UI_ANYLANGUAGE_READCHARFROMSTRING: + return 0;// AnyLanguage_ReadCharFromString( const char *psText, int *piAdvanceCount, qboolean *pbIsTrailingPunctuation ) + + case UI_SP_GETNUMLANGUAGES: + return 1; + + case UI_SP_GETLANGUAGENAME: + Q_strncpyz( VMA(2), "English", 128 ); // We assume that the buffer size used is only ever 128 + return 0; + + case UI_SP_GETSTRINGTEXTSTRING: + Q_strncpyz( VMA(2), VMA(1), args[3] ); + return strlen( VMA(1) ); + + case UI_MEMSET: + Com_Memset( VMA(1), args[2], args[3] ); + return 0; + + case UI_MEMCPY: + Com_Memcpy( VMA(1), VMA(2), args[3] ); + return 0; + + case UI_STRNCPY: + strncpy( VMA(1), VMA(2), args[3] ); + return args[1]; + + case UI_SIN: + return FloatAsInt( sin( VMF(1) ) ); + + case UI_COS: + return FloatAsInt( cos( VMF(1) ) ); + + case UI_ATAN2: + return FloatAsInt( atan2( VMF(1), VMF(2) ) ); + + case UI_SQRT: + return FloatAsInt( sqrt( VMF(1) ) ); + + case UI_FLOOR: + return FloatAsInt( floor( VMF(1) ) ); + + case UI_CEIL: + return FloatAsInt( ceil( VMF(1) ) ); + + case UI_PC_ADD_GLOBAL_DEFINE: + return botlib_export->PC_AddGlobalDefine( VMA(1) ); + case UI_PC_LOAD_SOURCE: + return botlib_export->PC_LoadSourceHandle( VMA(1) ); + case UI_PC_FREE_SOURCE: + return botlib_export->PC_FreeSourceHandle( args[1] ); + case UI_PC_READ_TOKEN: + return botlib_export->PC_ReadTokenHandle( args[1], VMA(2) ); + case UI_PC_SOURCE_FILE_AND_LINE: + return botlib_export->PC_SourceFileAndLine( args[1], VMA(2), VMA(3) ); + case UI_PC_LOAD_GLOBAL_DEFINES: + return botlib_export->PC_LoadGlobalDefines( VMA(1) ); + case UI_PC_REMOVE_ALL_GLOBAL_DEFINES: + botlib_export->PC_RemoveAllGlobalDefines(); + return 0; + + case UI_S_STOPBACKGROUNDTRACK: + S_StopBackgroundTrack(); + return 0; + case UI_S_STARTBACKGROUNDTRACK: + S_StartBackgroundTrack( VMA(1), VMA(2)); // will need 3rd arg for cgame + return 0; + + case UI_REAL_TIME: + return Com_RealTime( VMA(1) ); + + case UI_CIN_PLAYCINEMATIC: + Com_DPrintf("UI_CIN_PlayCinematic\n"); + return CIN_PlayCinematic(VMA(1), args[2], args[3], args[4], args[5], args[6]); + + case UI_CIN_STOPCINEMATIC: + return CIN_StopCinematic(args[1]); + + case UI_CIN_RUNCINEMATIC: + return CIN_RunCinematic(args[1]); + + case UI_CIN_DRAWCINEMATIC: + CIN_DrawCinematic(args[1]); + return 0; + + case UI_CIN_SETEXTENTS: + CIN_SetExtents(args[1], args[2], args[3], args[4], args[5]); + return 0; + + case UI_R_REMAP_SHADER: + re.RemapShader( VMA(1), VMA(2), VMA(3) ); + return 0; + + case UI_VERIFY_CDKEY: + return CL_CDKeyValidate(VMA(1), VMA(2)); + + case UI_G2_LISTSURFACES: + case UI_G2_LISTBONES: + case UI_G2_SETMODELS: + case UI_G2_HAVEWEGHOULMODELS: + case UI_G2_GETBOLT: + case UI_G2_GETBOLT_NOREC: + case UI_G2_GETBOLT_NOREC_NOROT: + case UI_G2_INITGHOUL2MODEL: + case UI_G2_COLLISIONDETECT: + case UI_G2_COLLISIONDETECTCACHE: + case UI_G2_CLEANMODELS: + case UI_G2_ANGLEOVERRIDE: + case UI_G2_PLAYANIM: + case UI_G2_GETBONEANIM: + case UI_G2_GETBONEFRAME: + case UI_G2_GETGLANAME: + case UI_G2_COPYGHOUL2INSTANCE: + case UI_G2_COPYSPECIFICGHOUL2MODEL: + case UI_G2_DUPLICATEGHOUL2INSTANCE: + case UI_G2_HASGHOUL2MODELONINDEX: + case UI_G2_REMOVEGHOUL2MODEL: + case UI_G2_ADDBOLT: + case UI_G2_SETBOLTON: + case UI_G2_SETROOTSURFACE: + case UI_G2_SETSURFACEONOFF: + case UI_G2_SETNEWORIGIN: + case UI_G2_GETTIME: + case UI_G2_SETTIME: + case UI_G2_SETRAGDOLL: + case UI_G2_ANIMATEG2MODELS: + case UI_G2_SETBONEIKSTATE: + case UI_G2_IKMOVE: + case UI_G2_GETSURFACENAME: + case UI_G2_SETSKIN: + case UI_G2_ATTACHG2MODEL: + return 0; + + default: + Com_Error( ERR_DROP, "Bad UI system trap: %ld", (long int) args[0] ); + + } + + return 0; +} + +/* +==================== +CL_ShutdownUI +==================== +*/ +void CL_ShutdownUI( void ) { + Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_UI ); + cls.uiStarted = qfalse; + if ( !uivm ) { + return; + } + VM_Call( uivm, UI_SHUTDOWN ); + VM_Free( uivm ); + uivm = NULL; +} + +/* +==================== +CL_InitUI +==================== +*/ +#define UI_OLD_API_VERSION 4 + +void CL_InitUI( void ) { + int v; + vmInterpret_t interpret; + + // load the dll or bytecode + if ( cl_connectedToPureServer != 0 ) { + // if sv_pure is set we only allow qvms to be loaded + interpret = VMI_COMPILED; + } + else { + interpret = Cvar_VariableValue( "vm_ui" ); + } + uivm = VM_Create( "ui", CL_UISystemCalls, interpret ); + if ( !uivm ) { + Com_Error( ERR_FATAL, "VM_Create on UI failed" ); + } + + // sanity check + v = VM_Call( uivm, UI_GETAPIVERSION ); + if (v == UI_OLD_API_VERSION) { +// Com_Printf(S_COLOR_YELLOW "WARNING: loading old Quake III Arena User Interface version %d\n", v ); + // init for this gamestate + VM_Call( uivm, UI_INIT, (clc.state >= CA_AUTHORIZING && clc.state < CA_ACTIVE)); + } + else if (v != UI_API_VERSION) { + Com_Error( ERR_DROP, "User Interface is version %d, expected %d", v, UI_API_VERSION ); + cls.uiStarted = qfalse; + } + else { + // init for this gamestate + VM_Call( uivm, UI_INIT, (clc.state >= CA_AUTHORIZING && clc.state < CA_ACTIVE) ); + } +} + +#ifndef STANDALONE +qboolean UI_usesUniqueCDKey( void ) { + if (uivm) { + return (VM_Call( uivm, UI_HASUNIQUECDKEY) == qtrue); + } else { + return qfalse; + } +} +#endif + +/* +==================== +UI_GameCommand + +See if the current console command is claimed by the ui +==================== +*/ +qboolean UI_GameCommand( void ) { + if ( !uivm ) { + return qfalse; + } + + return VM_Call( uivm, UI_CONSOLE_COMMAND, cls.realtime ); +} diff --git a/code/client/client.h b/code/client/client.h new file mode 100644 index 0000000..c740efb --- /dev/null +++ b/code/client/client.h @@ -0,0 +1,646 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// client.h -- primary header for client + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "../renderer/tr_public.h" +#include "../ui/ui_public.h" +#include "keys.h" +#include "snd_public.h" +#include "../cgame/cg_public.h" +#include "../game/bg_public.h" + +#ifdef USE_CURL +#include "cl_curl.h" +#endif /* USE_CURL */ + +#ifdef USE_VOIP +#include "speex/speex.h" +#include "speex/speex_preprocess.h" +#endif + +// file full of random crap that gets used to create cl_guid +#define QKEY_FILE "qkey" +#define QKEY_SIZE 2048 + +#define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits + +// snapshots are a view of the server at a given time +typedef struct { + qboolean valid; // cleared if delta parsing was invalid + int snapFlags; // rate delayed and dropped commands + + int serverTime; // server time the message is valid for (in msec) + + int messageNum; // copied from netchan->incoming_sequence + int deltaNum; // messageNum the delta is from + int ping; // time from when cmdNum-1 was sent to time packet was reeceived + byte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + int cmdNum; // the next cmdNum the server is expecting + playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + int parseEntitiesNum; // at the time of this snapshot + + int serverCommandNum; // execute all commands up to this before + // making the snapshot current +} clSnapshot_t; + + + +/* +============================================================================= + +the clientActive_t structure is wiped completely at every +new gamestate_t, potentially several times during an established connection + +============================================================================= +*/ + +typedef struct { + int p_cmdNumber; // cl.cmdNumber when packet was sent + int p_serverTime; // usercmd->serverTime when packet was sent + int p_realtime; // cls.realtime when packet was sent +} outPacket_t; + +// the parseEntities array must be large enough to hold PACKET_BACKUP frames of +// entities, so that when a delta compressed message arives from the server +// it can be un-deltad from the original +#define MAX_PARSE_ENTITIES 2048 + +extern int g_console_field_width; + +typedef struct { + int timeoutcount; // it requres several frames in a timeout condition + // to disconnect, preventing debugging breaks from + // causing immediate disconnects on continue + clSnapshot_t snap; // latest received from server + + int serverTime; // may be paused during play + int oldServerTime; // to prevent time from flowing bakcwards + int oldFrameServerTime; // to check tournament restarts + int serverTimeDelta; // cl.serverTime = cls.realtime + cl.serverTimeDelta + // this value changes as net lag varies + qboolean extrapolatedSnapshot; // set if any cgame frame has been forced to extrapolate + // cleared when CL_AdjustTimeDelta looks at it + qboolean newSnapshots; // set on parse of any valid packet + + gameState_t gameState; // configstrings + char mapname[MAX_QPATH]; // extracted from CS_SERVERINFO + + int parseEntitiesNum; // index (not anded off) into cl_parse_entities[] + + int mouseDx[2], mouseDy[2]; // added to by mouse events + int mouseIndex; + int joystickAxis[MAX_JOYSTICK_AXIS]; // set by joystick events + + // cgame communicates a few values to the client system + int cgameUserCmdValue; // current weapon to add to usercmd_t + int cgameUserCmdForce; // current force to add to usercmd_t + int cgameUserCmdInv; // current inv to add to usercmd_t + int cgameUserCmdGenCmd; // current gencmd to add to usercmd_t + float cgameSensitivity; + + // cmds[cmdNumber] is the predicted command, [cmdNumber-1] is the last + // properly generated command + usercmd_t cmds[CMD_BACKUP]; // each mesage will send several old cmds + int cmdNumber; // incremented each frame, because multiple + // frames may need to be packed into a single packet + + outPacket_t outPackets[PACKET_BACKUP]; // information about each packet we have sent out + + // the client maintains its own idea of view angles, which are + // sent to the server each frame. It is cleared to 0 upon entering each level. + // the server sends a delta each frame which is added to the locally + // tracked view angles to account for standing on rotating objects, + // and teleport direction changes + vec3_t viewangles; + + int serverId; // included in each client message so the server + // can tell if it is for a prior map_restart + // big stuff at end of structure so most offsets are 15 bits or less + clSnapshot_t snapshots[PACKET_BACKUP]; + + entityState_t entityBaselines[MAX_GENTITIES]; // for delta compression when not in previous frame + + entityState_t parseEntities[MAX_PARSE_ENTITIES]; +} clientActive_t; + +extern clientActive_t cl; + +/* +============================================================================= + +the clientConnection_t structure is wiped when disconnecting from a server, +either to go to a full screen console, play a demo, or connect to a different server + +A connection can be to either a server through the network layer or a +demo through a file. + +============================================================================= +*/ + +#define MAX_TIMEDEMO_DURATIONS 4096 + +typedef struct { + + connstate_t state; // connection status + + int clientNum; + int lastPacketSentTime; // for retransmits during connection + int lastPacketTime; // for timeouts + + char servername[MAX_OSPATH]; // name of server from original connect (used by reconnect) + netadr_t serverAddress; + int connectTime; // for connection retransmits + int connectPacketCount; // for display on connection dialog + char serverMessage[MAX_STRING_TOKENS]; // for display on connection dialog + + int challenge; // from the server to use for connecting + int checksumFeed; // from the server for checksum calculations + + // these are our reliable messages that go to the server + int reliableSequence; + int reliableAcknowledge; // the last one the server has executed + char reliableCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + + // server message (unreliable) and command (reliable) sequence + // numbers are NOT cleared at level changes, but continue to + // increase as long as the connection is valid + + // message sequence is used by both the network layer and the + // delta compression layer + int serverMessageSequence; + + // reliable messages received from server + int serverCommandSequence; + int lastExecutedServerCommand; // last server command grabbed or executed with CL_GetServerCommand + char serverCommands[MAX_RELIABLE_COMMANDS][MAX_STRING_CHARS]; + + // file transfer from server + fileHandle_t download; + char downloadTempName[MAX_OSPATH]; + char downloadName[MAX_OSPATH]; +#ifdef USE_CURL + qboolean cURLEnabled; + qboolean cURLUsed; + qboolean cURLDisconnected; + char downloadURL[MAX_OSPATH]; + CURL *downloadCURL; + CURLM *downloadCURLM; +#endif /* USE_CURL */ + int sv_allowDownload; + char sv_dlURL[MAX_CVAR_VALUE_STRING]; + int downloadNumber; + int downloadBlock; // block we are waiting for + int downloadCount; // how many bytes we got + int downloadSize; // how many bytes we got + char downloadList[MAX_INFO_STRING]; // list of paks we need to download + qboolean downloadRestart; // if true, we need to do another FS_Restart because we downloaded a pak + + // demo information + char demoName[MAX_QPATH]; + qboolean spDemoRecording; + qboolean demorecording; + qboolean demoplaying; + qboolean demowaiting; // don't record until a non-delta message is received + qboolean firstDemoFrameSkipped; + fileHandle_t demofile; + + int timeDemoFrames; // counter of rendered frames + int timeDemoStart; // cls.realtime before first frame + int timeDemoBaseTime; // each frame will be at this time + frameNum * 50 + int timeDemoLastFrame;// time the last frame was rendered + int timeDemoMinDuration; // minimum frame duration + int timeDemoMaxDuration; // maximum frame duration + unsigned char timeDemoDurations[ MAX_TIMEDEMO_DURATIONS ]; // log of frame durations + +#ifdef USE_VOIP + qboolean voipEnabled; + qboolean speexInitialized; + int speexFrameSize; + int speexSampleRate; + + // incoming data... + // !!! FIXME: convert from parallel arrays to array of a struct. + SpeexBits speexDecoderBits[MAX_CLIENTS]; + void *speexDecoder[MAX_CLIENTS]; + byte voipIncomingGeneration[MAX_CLIENTS]; + int voipIncomingSequence[MAX_CLIENTS]; + float voipGain[MAX_CLIENTS]; + qboolean voipIgnore[MAX_CLIENTS]; + qboolean voipMuteAll; + + // outgoing data... + // if voipTargets[i / 8] & (1 << (i % 8)), + // then we are sending to clientnum i. + uint8_t voipTargets[(MAX_CLIENTS + 7) / 8]; + uint8_t voipFlags; + SpeexPreprocessState *speexPreprocessor; + SpeexBits speexEncoderBits; + void *speexEncoder; + int voipOutgoingDataSize; + int voipOutgoingDataFrames; + int voipOutgoingSequence; + byte voipOutgoingGeneration; + byte voipOutgoingData[1024]; + float voipPower; +#endif + +#ifdef LEGACY_PROTOCOL + qboolean compat; +#endif + + // big stuff at end of structure so most offsets are 15 bits or less + netchan_t netchan; +} clientConnection_t; + +extern clientConnection_t clc; + +/* +================================================================== + +the clientStatic_t structure is never wiped, and is used even when +no client connection is active at all + +================================================================== +*/ + +typedef struct { + netadr_t adr; + int start; + int time; + char info[MAX_INFO_STRING]; +} ping_t; + +typedef struct { + netadr_t adr; + char hostName[MAX_NAME_LENGTH]; + char mapName[MAX_NAME_LENGTH]; + char game[MAX_NAME_LENGTH]; + int netType; + int gameType; + int clients; + int maxClients; + int minPing; + int maxPing; + int ping; + qboolean visible; + int punkbuster; + int g_humanplayers; + int g_needpass; +} serverInfo_t; + +typedef struct { + qboolean cddialog; // bring up the cd needed dialog next frame + + // when the server clears the hunk, all of these must be restarted + qboolean rendererStarted; + qboolean soundStarted; + qboolean soundRegistered; + qboolean uiStarted; + qboolean cgameStarted; + + int framecount; + int frametime; // msec since last frame + + int realtime; // ignores pause + int realFrametime; // ignoring pause, so console always works + + int numlocalservers; + serverInfo_t localServers[MAX_OTHER_SERVERS]; + + int numglobalservers; + serverInfo_t globalServers[MAX_GLOBAL_SERVERS]; + // additional global servers + int numGlobalServerAddresses; + netadr_t globalServerAddresses[MAX_GLOBAL_SERVERS]; + + int numfavoriteservers; + serverInfo_t favoriteServers[MAX_OTHER_SERVERS]; + + int pingUpdateSource; // source currently pinging or updating + + char oldGame[MAX_QPATH]; + qboolean oldGameSet; + + // update server info + netadr_t updateServer; + char updateChallenge[MAX_TOKEN_CHARS]; + char updateInfoString[MAX_INFO_STRING]; + + netadr_t authorizeServer; + + // rendering info + glconfig_t glconfig; + qhandle_t charSetShader; + qhandle_t whiteShader; + qhandle_t consoleShader; + qhandle_t splashShader; +} clientStatic_t; + +extern clientStatic_t cls; + +//============================================================================= + +extern vm_t *cgvm; // interface to cgame dll or vm +extern vm_t *uivm; // interface to ui dll or vm +extern refexport_t re; // interface to refresh .dll + + +// +// cvars +// +extern cvar_t *cl_nodelta; +extern cvar_t *cl_debugMove; +extern cvar_t *cl_noprint; +extern cvar_t *cl_timegraph; +extern cvar_t *cl_maxpackets; +extern cvar_t *cl_packetdup; +extern cvar_t *cl_shownet; +extern cvar_t *cl_showSend; +extern cvar_t *cl_timeNudge; +extern cvar_t *cl_showTimeDelta; +extern cvar_t *cl_freezeDemo; + +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_run; +extern cvar_t *cl_anglespeedkey; + +extern cvar_t *cl_sensitivity; +extern cvar_t *cl_freelook; + +extern cvar_t *cl_mouseAccel; +extern cvar_t *cl_mouseAccelOffset; +extern cvar_t *cl_mouseAccelStyle; +extern cvar_t *cl_showMouseRate; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; +extern cvar_t *m_filter; + +extern cvar_t *j_pitch; +extern cvar_t *j_yaw; +extern cvar_t *j_forward; +extern cvar_t *j_side; +extern cvar_t *j_pitch_axis; +extern cvar_t *j_yaw_axis; +extern cvar_t *j_forward_axis; +extern cvar_t *j_side_axis; + +extern cvar_t *cl_timedemo; +extern cvar_t *cl_aviFrameRate; +extern cvar_t *cl_aviMotionJpeg; + +extern cvar_t *cl_activeAction; + +extern cvar_t *cl_allowDownload; +extern cvar_t *cl_downloadMethod; +extern cvar_t *cl_inGameVideo; + +extern cvar_t *cl_lanForcePackets; +extern cvar_t *cl_autoRecordDemo; + +extern cvar_t *cl_consoleKeys; + +#ifdef USE_MUMBLE +extern cvar_t *cl_useMumble; +extern cvar_t *cl_mumbleScale; +#endif + +#ifdef USE_VOIP +// cl_voipSendTarget is a string: "all" to broadcast to everyone, "none" to +// send to no one, or a comma-separated list of client numbers: +// "0,7,2,23" ... an empty string is treated like "all". +extern cvar_t *cl_voipUseVAD; +extern cvar_t *cl_voipVADThreshold; +extern cvar_t *cl_voipSend; +extern cvar_t *cl_voipSendTarget; +extern cvar_t *cl_voipGainDuringCapture; +extern cvar_t *cl_voipCaptureMult; +extern cvar_t *cl_voipShowMeter; +extern cvar_t *cl_voip; +#endif + +//================================================= + +// +// cl_main +// + +void CL_Init (void); +void CL_AddReliableCommand(const char *cmd, qboolean isDisconnectCmd); + +void CL_StartHunkUsers( qboolean rendererOnly ); + +void CL_Disconnect_f (void); +void CL_GetChallengePacket (void); +void CL_Vid_Restart_f( void ); +void CL_Snd_Restart_f (void); +void CL_StartDemoLoop( void ); +void CL_NextDemo( void ); +void CL_ReadDemoMessage( void ); +void CL_StopRecord_f(void); + +void CL_InitDownloads(void); +void CL_NextDownload(void); + +void CL_GetPing( int n, char *buf, int buflen, int *pingtime ); +void CL_GetPingInfo( int n, char *buf, int buflen ); +void CL_ClearPing( int n ); +int CL_GetPingQueueCount( void ); + +void CL_ShutdownRef( void ); +void CL_InitRef( void ); +qboolean CL_CDKeyValidate( const char *key, const char *checksum ); +int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ); + +qboolean CL_CheckPaused(void); + +// +// cl_input +// +typedef struct { + int down[2]; // key nums holding it down + unsigned downtime; // msec timestamp + unsigned msec; // msec down this frame if both a down and up happened + qboolean active; // current state + qboolean wasPressed; // set when down, not cleared when up +} kbutton_t; + +extern kbutton_t in_mlook, in_klook; +extern kbutton_t in_strafe; +extern kbutton_t in_speed; + +#ifdef USE_VOIP +extern kbutton_t in_voiprecord; +#endif + +void CL_InitInput(void); +void CL_ShutdownInput(void); +void CL_SendCmd (void); +void CL_ClearState (void); +void CL_ReadPackets (void); + +void CL_WritePacket( void ); +void IN_CenterView (void); + +void CL_VerifyCode( void ); + +float CL_KeyState (kbutton_t *key); +int Key_StringToKeynum( char *str ); +char *Key_KeynumToString (int keynum); + +// +// cl_parse.c +// +extern int cl_connectedToPureServer; +extern int cl_connectedToCheatServer; + +#ifdef USE_VOIP +void CL_Voip_f( void ); +#endif + +void CL_SystemInfoChanged( void ); +void CL_ParseServerMessage( msg_t *msg ); + +//==================================================================== + +void CL_ServerInfoPacket( netadr_t from, msg_t *msg ); +void CL_LocalServers_f( void ); +void CL_GlobalServers_f( void ); +void CL_FavoriteServers_f( void ); +void CL_Ping_f( void ); +qboolean CL_UpdateVisiblePings_f( int source ); + + +// +// console +// +void Con_DrawCharacter (int cx, int line, int num); + +void Con_CheckResize (void); +void Con_Init(void); +void Con_Shutdown(void); +void Con_Clear_f (void); +void Con_ToggleConsole_f (void); +void Con_DrawNotify (void); +void Con_ClearNotify (void); +void Con_RunConsole (void); +void Con_DrawConsole (void); +void Con_PageUp( void ); +void Con_PageDown( void ); +void Con_Top( void ); +void Con_Bottom( void ); +void Con_Close( void ); + +void CL_LoadConsoleHistory( void ); +void CL_SaveConsoleHistory( void ); + +// +// cl_scrn.c +// +void SCR_Init (void); +void SCR_UpdateScreen (void); + +void SCR_DebugGraph (float value); + +int SCR_GetBigStringWidth( const char *str ); // returns in virtual 640x480 coordinates + +void SCR_AdjustFrom640( float *x, float *y, float *w, float *h ); +void SCR_FillRect( float x, float y, float width, float height, + const float *color ); +void SCR_DrawPic( float x, float y, float width, float height, qhandle_t hShader ); +void SCR_DrawNamedPic( float x, float y, float width, float height, const char *picname ); + +void SCR_DrawBigString( int x, int y, const char *s, float alpha, qboolean noColorEscape ); // draws a string with embedded color control characters with fade +void SCR_DrawBigStringColor( int x, int y, const char *s, vec4_t color, qboolean noColorEscape ); // ignores embedded color control characters +void SCR_DrawSmallStringExt( int x, int y, const char *string, float *setColor, qboolean forceColor, qboolean noColorEscape ); +void SCR_DrawSmallChar( int x, int y, int ch ); + + +// +// cl_cin.c +// + +void CL_CompleteCinematicName( char *args, int argNum ); +void CL_PlayCinematic_f( void ); +void SCR_DrawCinematic (void); +void SCR_RunCinematic (void); +void SCR_StopCinematic (void); +int CIN_PlayCinematic( const char *arg0, int xpos, int ypos, int width, int height, int bits); +e_status CIN_StopCinematic(int handle); +e_status CIN_RunCinematic (int handle); +void CIN_DrawCinematic (int handle); +void CIN_SetExtents (int handle, int x, int y, int w, int h); +void CIN_SetLooping (int handle, qboolean loop); +void CIN_UploadCinematic(int handle); +void CIN_CloseAllVideos(void); + +// +// cl_cgame.c +// +void CL_InitCGame( void ); +void CL_ShutdownCGame( void ); +qboolean CL_GameCommand( void ); +void CL_CGameRendering( stereoFrame_t stereo ); +void CL_SetCGameTime( void ); +void CL_FirstSnapshot( void ); +void CL_ShaderStateChanged(void); + +// +// cl_ui.c +// +void CL_InitUI( void ); +void CL_ShutdownUI( void ); +int Key_GetCatcher( void ); +void Key_SetCatcher( int catcher ); +void LAN_LoadCachedServers( void ); +void LAN_SaveServersToCache( void ); + + +// +// cl_net_chan.c +// +void CL_Netchan_Transmit( netchan_t *chan, msg_t* msg); //int length, const byte *data ); +qboolean CL_Netchan_Process( netchan_t *chan, msg_t *msg ); + +// +// cl_avi.c +// +qboolean CL_OpenAVIForWriting( const char *filename ); +void CL_TakeVideoFrame( void ); +void CL_WriteAVIVideoFrame( const byte *imageBuffer, int size ); +void CL_WriteAVIAudioFrame( const byte *pcmBuffer, int size ); +qboolean CL_CloseAVI( void ); +qboolean CL_VideoRecording( void ); + +// +// cl_main.c +// +void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ); + diff --git a/code/client/keycodes.h b/code/client/keycodes.h new file mode 100644 index 0000000..706ca1b --- /dev/null +++ b/code/client/keycodes.h @@ -0,0 +1,279 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// +#ifndef __KEYCODES_H__ +#define __KEYCODES_H__ + +// +// these are the key numbers that should be passed to KeyEvent +// + +// normal keys should be passed as lowercased ascii + +typedef enum { + K_TAB = 9, + K_ENTER = 13, + K_ESCAPE = 27, + K_SPACE = 32, + + K_BACKSPACE = 127, + + K_COMMAND = 128, + K_CAPSLOCK, + K_POWER, + K_PAUSE, + + K_UPARROW, + K_DOWNARROW, + K_LEFTARROW, + K_RIGHTARROW, + + K_ALT, + K_CTRL, + K_SHIFT, + K_INS, + K_DEL, + K_PGDN, + K_PGUP, + K_HOME, + K_END, + + K_F1, + K_F2, + K_F3, + K_F4, + K_F5, + K_F6, + K_F7, + K_F8, + K_F9, + K_F10, + K_F11, + K_F12, + K_F13, + K_F14, + K_F15, + + K_KP_HOME, + K_KP_UPARROW, + K_KP_PGUP, + K_KP_LEFTARROW, + K_KP_5, + K_KP_RIGHTARROW, + K_KP_END, + K_KP_DOWNARROW, + K_KP_PGDN, + K_KP_ENTER, + K_KP_INS, + K_KP_DEL, + K_KP_SLASH, + K_KP_MINUS, + K_KP_PLUS, + K_KP_NUMLOCK, + K_KP_STAR, + K_KP_EQUALS, + + K_MOUSE1, + K_MOUSE2, + K_MOUSE3, + K_MOUSE4, + K_MOUSE5, + + K_MWHEELDOWN, + K_MWHEELUP, + + K_JOY1, + K_JOY2, + K_JOY3, + K_JOY4, + K_JOY5, + K_JOY6, + K_JOY7, + K_JOY8, + K_JOY9, + K_JOY10, + K_JOY11, + K_JOY12, + K_JOY13, + K_JOY14, + K_JOY15, + K_JOY16, + K_JOY17, + K_JOY18, + K_JOY19, + K_JOY20, + K_JOY21, + K_JOY22, + K_JOY23, + K_JOY24, + K_JOY25, + K_JOY26, + K_JOY27, + K_JOY28, + K_JOY29, + K_JOY30, + K_JOY31, + K_JOY32, + + K_AUX1, + K_AUX2, + K_AUX3, + K_AUX4, + K_AUX5, + K_AUX6, + K_AUX7, + K_AUX8, + K_AUX9, + K_AUX10, + K_AUX11, + K_AUX12, + K_AUX13, + K_AUX14, + K_AUX15, + K_AUX16, + + K_WORLD_0, + K_WORLD_1, + K_WORLD_2, + K_WORLD_3, + K_WORLD_4, + K_WORLD_5, + K_WORLD_6, + K_WORLD_7, + K_WORLD_8, + K_WORLD_9, + K_WORLD_10, + K_WORLD_11, + K_WORLD_12, + K_WORLD_13, + K_WORLD_14, + K_WORLD_15, + K_WORLD_16, + K_WORLD_17, + K_WORLD_18, + K_WORLD_19, + K_WORLD_20, + K_WORLD_21, + K_WORLD_22, + K_WORLD_23, + K_WORLD_24, + K_WORLD_25, + K_WORLD_26, + K_WORLD_27, + K_WORLD_28, + K_WORLD_29, + K_WORLD_30, + K_WORLD_31, + K_WORLD_32, + K_WORLD_33, + K_WORLD_34, + K_WORLD_35, + K_WORLD_36, + K_WORLD_37, + K_WORLD_38, + K_WORLD_39, + K_WORLD_40, + K_WORLD_41, + K_WORLD_42, + K_WORLD_43, + K_WORLD_44, + K_WORLD_45, + K_WORLD_46, + K_WORLD_47, + K_WORLD_48, + K_WORLD_49, + K_WORLD_50, + K_WORLD_51, + K_WORLD_52, + K_WORLD_53, + K_WORLD_54, + K_WORLD_55, + K_WORLD_56, + K_WORLD_57, + K_WORLD_58, + K_WORLD_59, + K_WORLD_60, + K_WORLD_61, + K_WORLD_62, + K_WORLD_63, + K_WORLD_64, + K_WORLD_65, + K_WORLD_66, + K_WORLD_67, + K_WORLD_68, + K_WORLD_69, + K_WORLD_70, + K_WORLD_71, + K_WORLD_72, + K_WORLD_73, + K_WORLD_74, + K_WORLD_75, + K_WORLD_76, + K_WORLD_77, + K_WORLD_78, + K_WORLD_79, + K_WORLD_80, + K_WORLD_81, + K_WORLD_82, + K_WORLD_83, + K_WORLD_84, + K_WORLD_85, + K_WORLD_86, + K_WORLD_87, + K_WORLD_88, + K_WORLD_89, + K_WORLD_90, + K_WORLD_91, + K_WORLD_92, + K_WORLD_93, + K_WORLD_94, + K_WORLD_95, + + K_SUPER, + K_COMPOSE, + K_MODE, + K_HELP, + K_PRINT, + K_SYSREQ, + K_SCROLLOCK, + K_BREAK, + K_MENU, + K_EURO, + K_UNDO, + + // Pseudo-key that brings the console down + K_CONSOLE, + + MAX_KEYS +} keyNum_t; + +// MAX_KEYS replaces K_LAST_KEY, however some mods may have used K_LAST_KEY +// in detecting binds, so we leave it defined to the old hardcoded value +// of maxiumum keys to prevent mods from crashing older versions of the engine +#define K_LAST_KEY 256 + +// The menu code needs to get both key and char events, but +// to avoid duplicating the paths, the char events are just +// distinguished by or'ing in K_CHAR_FLAG (ugly) +#define K_CHAR_FLAG 1024 + +#endif diff --git a/code/client/keys.h b/code/client/keys.h new file mode 100644 index 0000000..0168468 --- /dev/null +++ b/code/client/keys.h @@ -0,0 +1,55 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +#include "keycodes.h" + +typedef struct { + qboolean down; + int repeats; // if > 1, it is autorepeating + char *binding; +} qkey_t; + +extern qboolean key_overstrikeMode; +extern qkey_t keys[MAX_KEYS]; + +// NOTE TTimo the declaration of field_t and Field_Clear is now in qcommon/qcommon.h +void Field_KeyDownEvent( field_t *edit, int key ); +void Field_CharEvent( field_t *edit, int ch ); +void Field_Draw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ); +void Field_BigDraw( field_t *edit, int x, int y, int width, qboolean showCursor, qboolean noColorEscape ); + +#define COMMAND_HISTORY 32 +extern field_t historyEditLines[COMMAND_HISTORY]; + +extern field_t g_consoleField; +extern field_t chatField; +extern int anykeydown; +extern qboolean chat_team; +extern int chat_playerNum; + +void Key_WriteBindings( fileHandle_t f ); +void Key_SetBinding( int keynum, const char *binding ); +char *Key_GetBinding( int keynum ); +qboolean Key_IsDown( int keynum ); +qboolean Key_GetOverstrikeMode( void ); +void Key_SetOverstrikeMode( qboolean state ); +void Key_ClearStates( void ); +int Key_GetKey(const char *binding); diff --git a/code/client/libmumblelink.c b/code/client/libmumblelink.c new file mode 100644 index 0000000..40d1eb1 --- /dev/null +++ b/code/client/libmumblelink.c @@ -0,0 +1,188 @@ +/* libmumblelink.c -- mumble link interface + + Copyright (C) 2008 Ludwig Nussel + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +#ifdef WIN32 +#include +#define uint32_t UINT32 +#else +#include +#ifdef __sun +#define _POSIX_C_SOURCE 199309L +#endif +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include "libmumblelink.h" + +#ifndef MIN +#define MIN(a, b) ((a)<(b)?(a):(b)) +#endif + +typedef struct +{ + uint32_t uiVersion; + uint32_t uiTick; + float fAvatarPosition[3]; + float fAvatarFront[3]; + float fAvatarTop[3]; + wchar_t name[256]; + /* new in mumble 1.2 */ + float fCameraPosition[3]; + float fCameraFront[3]; + float fCameraTop[3]; + wchar_t identity[256]; + uint32_t context_len; + unsigned char context[256]; + wchar_t description[2048]; +} LinkedMem; + +static LinkedMem *lm = NULL; + +#ifdef WIN32 +static HANDLE hMapObject = NULL; +#else +static int32_t GetTickCount(void) +{ + struct timeval tv; + gettimeofday(&tv,NULL); + + return tv.tv_usec / 1000 + tv.tv_sec * 1000; +} +#endif + +int mumble_link(const char* name) +{ +#ifdef WIN32 + if(lm) + return 0; + + hMapObject = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"MumbleLink"); + if (hMapObject == NULL) + return -1; + + lm = (LinkedMem *) MapViewOfFile(hMapObject, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(LinkedMem)); + if (lm == NULL) { + CloseHandle(hMapObject); + hMapObject = NULL; + return -1; + } +#else + char file[256]; + int shmfd; + if(lm) + return 0; + + snprintf(file, sizeof (file), "/MumbleLink.%d", getuid()); + shmfd = shm_open(file, O_RDWR, S_IRUSR | S_IWUSR); + if(shmfd < 0) { + return -1; + } + + lm = (LinkedMem *) (mmap(NULL, sizeof(LinkedMem), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd,0)); + if (lm == (void *) (-1)) { + lm = NULL; + close(shmfd); + return -1; + } + close(shmfd); +#endif + memset(lm, 0, sizeof(LinkedMem)); + mbstowcs(lm->name, name, sizeof(lm->name)); + + return 0; +} + +void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3]) +{ + mumble_update_coordinates2(fPosition, fFront, fTop, fPosition, fFront, fTop); +} + +void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3], + float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3]) +{ + if (!lm) + return; + + memcpy(lm->fAvatarPosition, fAvatarPosition, sizeof(lm->fAvatarPosition)); + memcpy(lm->fAvatarFront, fAvatarFront, sizeof(lm->fAvatarFront)); + memcpy(lm->fAvatarTop, fAvatarTop, sizeof(lm->fAvatarTop)); + memcpy(lm->fCameraPosition, fCameraPosition, sizeof(lm->fCameraPosition)); + memcpy(lm->fCameraFront, fCameraFront, sizeof(lm->fCameraFront)); + memcpy(lm->fCameraTop, fCameraTop, sizeof(lm->fCameraTop)); + lm->uiVersion = 2; + lm->uiTick = GetTickCount(); +} + +void mumble_set_identity(const char* identity) +{ + size_t len; + if (!lm) + return; + len = MIN(sizeof(lm->identity), strlen(identity)+1); + mbstowcs(lm->identity, identity, len); +} + +void mumble_set_context(const unsigned char* context, size_t len) +{ + if (!lm) + return; + len = MIN(sizeof(lm->context), len); + lm->context_len = len; + memcpy(lm->context, context, len); +} + +void mumble_set_description(const char* description) +{ + size_t len; + if (!lm) + return; + len = MIN(sizeof(lm->description), strlen(description)+1); + mbstowcs(lm->description, description, len); +} + +void mumble_unlink() +{ + if(!lm) + return; +#ifdef WIN32 + UnmapViewOfFile(lm); + CloseHandle(hMapObject); + hMapObject = NULL; +#else + munmap(lm, sizeof(LinkedMem)); +#endif + lm = NULL; +} + +int mumble_islinked(void) +{ + return lm != NULL; +} diff --git a/code/client/libmumblelink.h b/code/client/libmumblelink.h new file mode 100644 index 0000000..7824a1f --- /dev/null +++ b/code/client/libmumblelink.h @@ -0,0 +1,35 @@ +/* libmumblelink.h -- mumble link interface + + Copyright (C) 2008 Ludwig Nussel + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + +*/ + +int mumble_link(const char* name); +int mumble_islinked(void); +void mumble_update_coordinates(float fPosition[3], float fFront[3], float fTop[3]); + +/* new for mumble 1.2: also set camera position */ +void mumble_update_coordinates2(float fAvatarPosition[3], float fAvatarFront[3], float fAvatarTop[3], + float fCameraPosition[3], float fCameraFront[3], float fCameraTop[3]); + +void mumble_set_description(const char* description); +void mumble_set_context(const unsigned char* context, size_t len); +void mumble_set_identity(const char* identity); + +void mumble_unlink(void); diff --git a/code/client/qal.c b/code/client/qal.c new file mode 100644 index 0000000..eb22551 --- /dev/null +++ b/code/client/qal.c @@ -0,0 +1,336 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// Dynamically loads OpenAL + +#ifdef USE_OPENAL + +#include "qal.h" + +#ifdef USE_OPENAL_DLOPEN + +#include "../sys/sys_loadlib.h" + +LPALENABLE qalEnable; +LPALDISABLE qalDisable; +LPALISENABLED qalIsEnabled; +LPALGETSTRING qalGetString; +LPALGETBOOLEANV qalGetBooleanv; +LPALGETINTEGERV qalGetIntegerv; +LPALGETFLOATV qalGetFloatv; +LPALGETDOUBLEV qalGetDoublev; +LPALGETBOOLEAN qalGetBoolean; +LPALGETINTEGER qalGetInteger; +LPALGETFLOAT qalGetFloat; +LPALGETDOUBLE qalGetDouble; +LPALGETERROR qalGetError; +LPALISEXTENSIONPRESENT qalIsExtensionPresent; +LPALGETPROCADDRESS qalGetProcAddress; +LPALGETENUMVALUE qalGetEnumValue; +LPALLISTENERF qalListenerf; +LPALLISTENER3F qalListener3f; +LPALLISTENERFV qalListenerfv; +LPALLISTENERI qalListeneri; +LPALGETLISTENERF qalGetListenerf; +LPALGETLISTENER3F qalGetListener3f; +LPALGETLISTENERFV qalGetListenerfv; +LPALGETLISTENERI qalGetListeneri; +LPALGENSOURCES qalGenSources; +LPALDELETESOURCES qalDeleteSources; +LPALISSOURCE qalIsSource; +LPALSOURCEF qalSourcef; +LPALSOURCE3F qalSource3f; +LPALSOURCEFV qalSourcefv; +LPALSOURCEI qalSourcei; +LPALGETSOURCEF qalGetSourcef; +LPALGETSOURCE3F qalGetSource3f; +LPALGETSOURCEFV qalGetSourcefv; +LPALGETSOURCEI qalGetSourcei; +LPALSOURCEPLAYV qalSourcePlayv; +LPALSOURCESTOPV qalSourceStopv; +LPALSOURCEREWINDV qalSourceRewindv; +LPALSOURCEPAUSEV qalSourcePausev; +LPALSOURCEPLAY qalSourcePlay; +LPALSOURCESTOP qalSourceStop; +LPALSOURCEREWIND qalSourceRewind; +LPALSOURCEPAUSE qalSourcePause; +LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers; +LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers; +LPALGENBUFFERS qalGenBuffers; +LPALDELETEBUFFERS qalDeleteBuffers; +LPALISBUFFER qalIsBuffer; +LPALBUFFERDATA qalBufferData; +LPALGETBUFFERF qalGetBufferf; +LPALGETBUFFERI qalGetBufferi; +LPALDOPPLERFACTOR qalDopplerFactor; +LPALDOPPLERVELOCITY qalDopplerVelocity; +LPALDISTANCEMODEL qalDistanceModel; + +LPALCCREATECONTEXT qalcCreateContext; +LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent; +LPALCPROCESSCONTEXT qalcProcessContext; +LPALCSUSPENDCONTEXT qalcSuspendContext; +LPALCDESTROYCONTEXT qalcDestroyContext; +LPALCGETCURRENTCONTEXT qalcGetCurrentContext; +LPALCGETCONTEXTSDEVICE qalcGetContextsDevice; +LPALCOPENDEVICE qalcOpenDevice; +LPALCCLOSEDEVICE qalcCloseDevice; +LPALCGETERROR qalcGetError; +LPALCISEXTENSIONPRESENT qalcIsExtensionPresent; +LPALCGETPROCADDRESS qalcGetProcAddress; +LPALCGETENUMVALUE qalcGetEnumValue; +LPALCGETSTRING qalcGetString; +LPALCGETINTEGERV qalcGetIntegerv; +LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice; +LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice; +LPALCCAPTURESTART qalcCaptureStart; +LPALCCAPTURESTOP qalcCaptureStop; +LPALCCAPTURESAMPLES qalcCaptureSamples; + +static void *OpenALLib = NULL; + +static qboolean alinit_fail = qfalse; + +/* +================= +GPA +================= +*/ +static void *GPA(char *str) +{ + void *rv; + + rv = Sys_LoadFunction(OpenALLib, str); + if(!rv) + { + Com_Printf( " Can't load symbol %s\n", str); + alinit_fail = qtrue; + return NULL; + } + else + { + Com_DPrintf( " Loaded symbol %s (%p)\n", str, rv); + return rv; + } +} + +/* +================= +QAL_Init +================= +*/ +qboolean QAL_Init(const char *libname) +{ + if(OpenALLib) + return qtrue; + + if(!(OpenALLib = Sys_LoadDll(libname, qtrue))) + return qfalse; + + alinit_fail = qfalse; + + qalEnable = GPA("alEnable"); + qalDisable = GPA("alDisable"); + qalIsEnabled = GPA("alIsEnabled"); + qalGetString = GPA("alGetString"); + qalGetBooleanv = GPA("alGetBooleanv"); + qalGetIntegerv = GPA("alGetIntegerv"); + qalGetFloatv = GPA("alGetFloatv"); + qalGetDoublev = GPA("alGetDoublev"); + qalGetBoolean = GPA("alGetBoolean"); + qalGetInteger = GPA("alGetInteger"); + qalGetFloat = GPA("alGetFloat"); + qalGetDouble = GPA("alGetDouble"); + qalGetError = GPA("alGetError"); + qalIsExtensionPresent = GPA("alIsExtensionPresent"); + qalGetProcAddress = GPA("alGetProcAddress"); + qalGetEnumValue = GPA("alGetEnumValue"); + qalListenerf = GPA("alListenerf"); + qalListener3f = GPA("alListener3f"); + qalListenerfv = GPA("alListenerfv"); + qalListeneri = GPA("alListeneri"); + qalGetListenerf = GPA("alGetListenerf"); + qalGetListener3f = GPA("alGetListener3f"); + qalGetListenerfv = GPA("alGetListenerfv"); + qalGetListeneri = GPA("alGetListeneri"); + qalGenSources = GPA("alGenSources"); + qalDeleteSources = GPA("alDeleteSources"); + qalIsSource = GPA("alIsSource"); + qalSourcef = GPA("alSourcef"); + qalSource3f = GPA("alSource3f"); + qalSourcefv = GPA("alSourcefv"); + qalSourcei = GPA("alSourcei"); + qalGetSourcef = GPA("alGetSourcef"); + qalGetSource3f = GPA("alGetSource3f"); + qalGetSourcefv = GPA("alGetSourcefv"); + qalGetSourcei = GPA("alGetSourcei"); + qalSourcePlayv = GPA("alSourcePlayv"); + qalSourceStopv = GPA("alSourceStopv"); + qalSourceRewindv = GPA("alSourceRewindv"); + qalSourcePausev = GPA("alSourcePausev"); + qalSourcePlay = GPA("alSourcePlay"); + qalSourceStop = GPA("alSourceStop"); + qalSourceRewind = GPA("alSourceRewind"); + qalSourcePause = GPA("alSourcePause"); + qalSourceQueueBuffers = GPA("alSourceQueueBuffers"); + qalSourceUnqueueBuffers = GPA("alSourceUnqueueBuffers"); + qalGenBuffers = GPA("alGenBuffers"); + qalDeleteBuffers = GPA("alDeleteBuffers"); + qalIsBuffer = GPA("alIsBuffer"); + qalBufferData = GPA("alBufferData"); + qalGetBufferf = GPA("alGetBufferf"); + qalGetBufferi = GPA("alGetBufferi"); + qalDopplerFactor = GPA("alDopplerFactor"); + qalDopplerVelocity = GPA("alDopplerVelocity"); + qalDistanceModel = GPA("alDistanceModel"); + + qalcCreateContext = GPA("alcCreateContext"); + qalcMakeContextCurrent = GPA("alcMakeContextCurrent"); + qalcProcessContext = GPA("alcProcessContext"); + qalcSuspendContext = GPA("alcSuspendContext"); + qalcDestroyContext = GPA("alcDestroyContext"); + qalcGetCurrentContext = GPA("alcGetCurrentContext"); + qalcGetContextsDevice = GPA("alcGetContextsDevice"); + qalcOpenDevice = GPA("alcOpenDevice"); + qalcCloseDevice = GPA("alcCloseDevice"); + qalcGetError = GPA("alcGetError"); + qalcIsExtensionPresent = GPA("alcIsExtensionPresent"); + qalcGetProcAddress = GPA("alcGetProcAddress"); + qalcGetEnumValue = GPA("alcGetEnumValue"); + qalcGetString = GPA("alcGetString"); + qalcGetIntegerv = GPA("alcGetIntegerv"); + qalcCaptureOpenDevice = GPA("alcCaptureOpenDevice"); + qalcCaptureCloseDevice = GPA("alcCaptureCloseDevice"); + qalcCaptureStart = GPA("alcCaptureStart"); + qalcCaptureStop = GPA("alcCaptureStop"); + qalcCaptureSamples = GPA("alcCaptureSamples"); + + if(alinit_fail) + { + QAL_Shutdown(); + Com_Printf( " One or more symbols not found\n"); + return qfalse; + } + + return qtrue; +} + +/* +================= +QAL_Shutdown +================= +*/ +void QAL_Shutdown( void ) +{ + if(OpenALLib) + { + Sys_UnloadLibrary(OpenALLib); + OpenALLib = NULL; + } + + qalEnable = NULL; + qalDisable = NULL; + qalIsEnabled = NULL; + qalGetString = NULL; + qalGetBooleanv = NULL; + qalGetIntegerv = NULL; + qalGetFloatv = NULL; + qalGetDoublev = NULL; + qalGetBoolean = NULL; + qalGetInteger = NULL; + qalGetFloat = NULL; + qalGetDouble = NULL; + qalGetError = NULL; + qalIsExtensionPresent = NULL; + qalGetProcAddress = NULL; + qalGetEnumValue = NULL; + qalListenerf = NULL; + qalListener3f = NULL; + qalListenerfv = NULL; + qalListeneri = NULL; + qalGetListenerf = NULL; + qalGetListener3f = NULL; + qalGetListenerfv = NULL; + qalGetListeneri = NULL; + qalGenSources = NULL; + qalDeleteSources = NULL; + qalIsSource = NULL; + qalSourcef = NULL; + qalSource3f = NULL; + qalSourcefv = NULL; + qalSourcei = NULL; + qalGetSourcef = NULL; + qalGetSource3f = NULL; + qalGetSourcefv = NULL; + qalGetSourcei = NULL; + qalSourcePlayv = NULL; + qalSourceStopv = NULL; + qalSourceRewindv = NULL; + qalSourcePausev = NULL; + qalSourcePlay = NULL; + qalSourceStop = NULL; + qalSourceRewind = NULL; + qalSourcePause = NULL; + qalSourceQueueBuffers = NULL; + qalSourceUnqueueBuffers = NULL; + qalGenBuffers = NULL; + qalDeleteBuffers = NULL; + qalIsBuffer = NULL; + qalBufferData = NULL; + qalGetBufferf = NULL; + qalGetBufferi = NULL; + qalDopplerFactor = NULL; + qalDopplerVelocity = NULL; + qalDistanceModel = NULL; + + qalcCreateContext = NULL; + qalcMakeContextCurrent = NULL; + qalcProcessContext = NULL; + qalcSuspendContext = NULL; + qalcDestroyContext = NULL; + qalcGetCurrentContext = NULL; + qalcGetContextsDevice = NULL; + qalcOpenDevice = NULL; + qalcCloseDevice = NULL; + qalcGetError = NULL; + qalcIsExtensionPresent = NULL; + qalcGetProcAddress = NULL; + qalcGetEnumValue = NULL; + qalcGetString = NULL; + qalcGetIntegerv = NULL; + qalcCaptureOpenDevice = NULL; + qalcCaptureCloseDevice = NULL; + qalcCaptureStart = NULL; + qalcCaptureStop = NULL; + qalcCaptureSamples = NULL; +} +#else +qboolean QAL_Init(const char *libname) +{ + return qtrue; +} +void QAL_Shutdown( void ) +{ +} +#endif +#endif diff --git a/code/client/qal.h b/code/client/qal.h new file mode 100644 index 0000000..8234497 --- /dev/null +++ b/code/client/qal.h @@ -0,0 +1,252 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +#ifndef __QAL_H__ +#define __QAL_H__ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +#ifdef USE_OPENAL_DLOPEN +#define AL_NO_PROTOTYPES +#define ALC_NO_PROTOTYPES +#endif + +#ifdef USE_LOCAL_HEADERS +#include "../AL/al.h" +#include "../AL/alc.h" +#else +#ifdef _MSC_VER + // MSVC users must install the OpenAL SDK which doesn't use the AL/*.h scheme. + #include + #include +#else + #include + #include +#endif +#endif + +/* Hack to enable compiling both on OpenAL SDK and OpenAL-soft. */ +#ifndef ALC_ENUMERATE_ALL_EXT +# define ALC_ENUMERATE_ALL_EXT 1 +# define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012 +# define ALC_ALL_DEVICES_SPECIFIER 0x1013 +#endif + +#ifdef USE_OPENAL_DLOPEN +extern LPALENABLE qalEnable; +extern LPALDISABLE qalDisable; +extern LPALISENABLED qalIsEnabled; +extern LPALGETSTRING qalGetString; +extern LPALGETBOOLEANV qalGetBooleanv; +extern LPALGETINTEGERV qalGetIntegerv; +extern LPALGETFLOATV qalGetFloatv; +extern LPALGETDOUBLEV qalGetDoublev; +extern LPALGETBOOLEAN qalGetBoolean; +extern LPALGETINTEGER qalGetInteger; +extern LPALGETFLOAT qalGetFloat; +extern LPALGETDOUBLE qalGetDouble; +extern LPALGETERROR qalGetError; +extern LPALISEXTENSIONPRESENT qalIsExtensionPresent; +extern LPALGETPROCADDRESS qalGetProcAddress; +extern LPALGETENUMVALUE qalGetEnumValue; +extern LPALLISTENERF qalListenerf; +extern LPALLISTENER3F qalListener3f; +extern LPALLISTENERFV qalListenerfv; +extern LPALLISTENERI qalListeneri; +extern LPALLISTENER3I qalListener3i; +extern LPALLISTENERIV qalListeneriv; +extern LPALGETLISTENERF qalGetListenerf; +extern LPALGETLISTENER3F qalGetListener3f; +extern LPALGETLISTENERFV qalGetListenerfv; +extern LPALGETLISTENERI qalGetListeneri; +extern LPALGETLISTENER3I qalGetListener3i; +extern LPALGETLISTENERIV qalGetListeneriv; +extern LPALGENSOURCES qalGenSources; +extern LPALDELETESOURCES qalDeleteSources; +extern LPALISSOURCE qalIsSource; +extern LPALSOURCEF qalSourcef; +extern LPALSOURCE3F qalSource3f; +extern LPALSOURCEFV qalSourcefv; +extern LPALSOURCEI qalSourcei; +extern LPALSOURCE3I qalSource3i; +extern LPALSOURCEIV qalSourceiv; +extern LPALGETSOURCEF qalGetSourcef; +extern LPALGETSOURCE3F qalGetSource3f; +extern LPALGETSOURCEFV qalGetSourcefv; +extern LPALGETSOURCEI qalGetSourcei; +extern LPALGETSOURCE3I qalGetSource3i; +extern LPALGETSOURCEIV qalGetSourceiv; +extern LPALSOURCEPLAYV qalSourcePlayv; +extern LPALSOURCESTOPV qalSourceStopv; +extern LPALSOURCEREWINDV qalSourceRewindv; +extern LPALSOURCEPAUSEV qalSourcePausev; +extern LPALSOURCEPLAY qalSourcePlay; +extern LPALSOURCESTOP qalSourceStop; +extern LPALSOURCEREWIND qalSourceRewind; +extern LPALSOURCEPAUSE qalSourcePause; +extern LPALSOURCEQUEUEBUFFERS qalSourceQueueBuffers; +extern LPALSOURCEUNQUEUEBUFFERS qalSourceUnqueueBuffers; +extern LPALGENBUFFERS qalGenBuffers; +extern LPALDELETEBUFFERS qalDeleteBuffers; +extern LPALISBUFFER qalIsBuffer; +extern LPALBUFFERDATA qalBufferData; +extern LPALBUFFERF qalBufferf; +extern LPALBUFFER3F qalBuffer3f; +extern LPALBUFFERFV qalBufferfv; +extern LPALBUFFERF qalBufferi; +extern LPALBUFFER3F qalBuffer3i; +extern LPALBUFFERFV qalBufferiv; +extern LPALGETBUFFERF qalGetBufferf; +extern LPALGETBUFFER3F qalGetBuffer3f; +extern LPALGETBUFFERFV qalGetBufferfv; +extern LPALGETBUFFERI qalGetBufferi; +extern LPALGETBUFFER3I qalGetBuffer3i; +extern LPALGETBUFFERIV qalGetBufferiv; +extern LPALDOPPLERFACTOR qalDopplerFactor; +extern LPALDOPPLERVELOCITY qalDopplerVelocity; +extern LPALSPEEDOFSOUND qalSpeedOfSound; +extern LPALDISTANCEMODEL qalDistanceModel; + +extern LPALCCREATECONTEXT qalcCreateContext; +extern LPALCMAKECONTEXTCURRENT qalcMakeContextCurrent; +extern LPALCPROCESSCONTEXT qalcProcessContext; +extern LPALCSUSPENDCONTEXT qalcSuspendContext; +extern LPALCDESTROYCONTEXT qalcDestroyContext; +extern LPALCGETCURRENTCONTEXT qalcGetCurrentContext; +extern LPALCGETCONTEXTSDEVICE qalcGetContextsDevice; +extern LPALCOPENDEVICE qalcOpenDevice; +extern LPALCCLOSEDEVICE qalcCloseDevice; +extern LPALCGETERROR qalcGetError; +extern LPALCISEXTENSIONPRESENT qalcIsExtensionPresent; +extern LPALCGETPROCADDRESS qalcGetProcAddress; +extern LPALCGETENUMVALUE qalcGetEnumValue; +extern LPALCGETSTRING qalcGetString; +extern LPALCGETINTEGERV qalcGetIntegerv; +extern LPALCCAPTUREOPENDEVICE qalcCaptureOpenDevice; +extern LPALCCAPTURECLOSEDEVICE qalcCaptureCloseDevice; +extern LPALCCAPTURESTART qalcCaptureStart; +extern LPALCCAPTURESTOP qalcCaptureStop; +extern LPALCCAPTURESAMPLES qalcCaptureSamples; +#else +#define qalEnable alEnable +#define qalDisable alDisable +#define qalIsEnabled alIsEnabled +#define qalGetString alGetString +#define qalGetBooleanv alGetBooleanv +#define qalGetIntegerv alGetIntegerv +#define qalGetFloatv alGetFloatv +#define qalGetDoublev alGetDoublev +#define qalGetBoolean alGetBoolean +#define qalGetInteger alGetInteger +#define qalGetFloat alGetFloat +#define qalGetDouble alGetDouble +#define qalGetError alGetError +#define qalIsExtensionPresent alIsExtensionPresent +#define qalGetProcAddress alGetProcAddress +#define qalGetEnumValue alGetEnumValue +#define qalListenerf alListenerf +#define qalListener3f alListener3f +#define qalListenerfv alListenerfv +#define qalListeneri alListeneri +#define qalListener3i alListener3i +#define qalListeneriv alListeneriv +#define qalGetListenerf alGetListenerf +#define qalGetListener3f alGetListener3f +#define qalGetListenerfv alGetListenerfv +#define qalGetListeneri alGetListeneri +#define qalGetListener3i alGetListener3i +#define qalGetListeneriv alGetListeneriv +#define qalGenSources alGenSources +#define qalDeleteSources alDeleteSources +#define qalIsSource alIsSource +#define qalSourcef alSourcef +#define qalSource3f alSource3f +#define qalSourcefv alSourcefv +#define qalSourcei alSourcei +#define qalSource3i alSource3i +#define qalSourceiv alSourceiv +#define qalGetSourcef alGetSourcef +#define qalGetSource3f alGetSource3f +#define qalGetSourcefv alGetSourcefv +#define qalGetSourcei alGetSourcei +#define qalGetSource3i alGetSource3i +#define qalGetSourceiv alGetSourceiv +#define qalSourcePlayv alSourcePlayv +#define qalSourceStopv alSourceStopv +#define qalSourceRewindv alSourceRewindv +#define qalSourcePausev alSourcePausev +#define qalSourcePlay alSourcePlay +#define qalSourceStop alSourceStop +#define qalSourceRewind alSourceRewind +#define qalSourcePause alSourcePause +#define qalSourceQueueBuffers alSourceQueueBuffers +#define qalSourceUnqueueBuffers alSourceUnqueueBuffers +#define qalGenBuffers alGenBuffers +#define qalDeleteBuffers alDeleteBuffers +#define qalIsBuffer alIsBuffer +#define qalBufferData alBufferData +#define qalBufferf alBufferf +#define qalBuffer3f alBuffer3f +#define qalBufferfv alBufferfv +#define qalBufferi alBufferi +#define qalBuffer3i alBuffer3i +#define qalBufferiv alBufferiv +#define qalGetBufferf alGetBufferf +#define qalGetBuffer3f alGetBuffer3f +#define qalGetBufferfv alGetBufferfv +#define qalGetBufferi alGetBufferi +#define qalGetBuffer3i alGetBuffer3i +#define qalGetBufferiv alGetBufferiv +#define qalDopplerFactor alDopplerFactor +#define qalDopplerVelocity alDopplerVelocity +#define qalSpeedOfSound alSpeedOfSound +#define qalDistanceModel alDistanceModel + +#define qalcCreateContext alcCreateContext +#define qalcMakeContextCurrent alcMakeContextCurrent +#define qalcProcessContext alcProcessContext +#define qalcSuspendContext alcSuspendContext +#define qalcDestroyContext alcDestroyContext +#define qalcGetCurrentContext alcGetCurrentContext +#define qalcGetContextsDevice alcGetContextsDevice +#define qalcOpenDevice alcOpenDevice +#define qalcCloseDevice alcCloseDevice +#define qalcGetError alcGetError +#define qalcIsExtensionPresent alcIsExtensionPresent +#define qalcGetProcAddress alcGetProcAddress +#define qalcGetEnumValue alcGetEnumValue +#define qalcGetString alcGetString +#define qalcGetIntegerv alcGetIntegerv +#define qalcCaptureOpenDevice alcCaptureOpenDevice +#define qalcCaptureCloseDevice alcCaptureCloseDevice +#define qalcCaptureStart alcCaptureStart +#define qalcCaptureStop alcCaptureStop +#define qalcCaptureSamples alcCaptureSamples +#endif + +qboolean QAL_Init(const char *libname); +void QAL_Shutdown( void ); + +#endif // __QAL_H__ diff --git a/code/client/snd_adpcm.c b/code/client/snd_adpcm.c new file mode 100644 index 0000000..89e68f4 --- /dev/null +++ b/code/client/snd_adpcm.c @@ -0,0 +1,330 @@ +/*********************************************************** +Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The +Netherlands. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the names of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +******************************************************************/ + +/* +** Intel/DVI ADPCM coder/decoder. +** +** The algorithm for this coder was taken from the IMA Compatability Project +** proceedings, Vol 2, Number 2; May 1992. +** +** Version 1.2, 18-Dec-92. +*/ + +#include "snd_local.h" + + +/* Intel ADPCM step variation table */ +static int indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +}; + +static int stepsizeTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + + +void S_AdpcmEncode( short indata[], char outdata[], int len, struct adpcm_state *state ) { + short *inp; /* Input buffer pointer */ + signed char *outp; /* output buffer pointer */ + int val; /* Current input sample value */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int diff; /* Difference between val and sample */ + int step; /* Stepsize */ + int valpred; /* Predicted output value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int outputbuffer; /* place to keep previous 4-bit value */ + int bufferstep; /* toggle between outputbuffer/output */ + + outp = (signed char *)outdata; + inp = indata; + + valpred = state->sample; + index = state->index; + step = stepsizeTable[index]; + + outputbuffer = 0; // quiet a compiler warning + bufferstep = 1; + + for ( ; len > 0 ; len-- ) { + val = *inp++; + + /* Step 1 - compute difference with previous value */ + diff = val - valpred; + sign = (diff < 0) ? 8 : 0; + if ( sign ) diff = (-diff); + + /* Step 2 - Divide and clamp */ + /* Note: + ** This code *approximately* computes: + ** delta = diff*4/step; + ** vpdiff = (delta+0.5)*step/4; + ** but in shift step bits are dropped. The net result of this is + ** that even if you have fast mul/div hardware you cannot put it to + ** good use since the fixup would be too expensive. + */ + delta = 0; + vpdiff = (step >> 3); + + if ( diff >= step ) { + delta = 4; + diff -= step; + vpdiff += step; + } + step >>= 1; + if ( diff >= step ) { + delta |= 2; + diff -= step; + vpdiff += step; + } + step >>= 1; + if ( diff >= step ) { + delta |= 1; + vpdiff += step; + } + + /* Step 3 - Update previous value */ + if ( sign ) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 4 - Clamp previous value to 16 bits */ + if ( valpred > 32767 ) + valpred = 32767; + else if ( valpred < -32768 ) + valpred = -32768; + + /* Step 5 - Assemble value, update index and step values */ + delta |= sign; + + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + step = stepsizeTable[index]; + + /* Step 6 - Output value */ + if ( bufferstep ) { + outputbuffer = (delta << 4) & 0xf0; + } else { + *outp++ = (delta & 0x0f) | outputbuffer; + } + bufferstep = !bufferstep; + } + + /* Output last step, if needed */ + if ( !bufferstep ) + *outp++ = outputbuffer; + + state->sample = valpred; + state->index = index; +} + + +/* static */ void S_AdpcmDecode( const char indata[], short *outdata, int len, struct adpcm_state *state ) { + signed char *inp; /* Input buffer pointer */ + int outp; /* output buffer pointer */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int step; /* Stepsize */ + int valpred; /* Predicted value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int inputbuffer; /* place to keep next 4-bit value */ + int bufferstep; /* toggle between inputbuffer/input */ + + outp = 0; + inp = (signed char *)indata; + + valpred = state->sample; + index = state->index; + step = stepsizeTable[index]; + + bufferstep = 0; + inputbuffer = 0; // quiet a compiler warning + for ( ; len > 0 ; len-- ) { + + /* Step 1 - get the delta value */ + if ( bufferstep ) { + delta = inputbuffer & 0xf; + } else { + inputbuffer = *inp++; + delta = (inputbuffer >> 4) & 0xf; + } + bufferstep = !bufferstep; + + /* Step 2 - Find new index value (for later) */ + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + + /* Step 3 - Separate sign and magnitude */ + sign = delta & 8; + delta = delta & 7; + + /* Step 4 - Compute difference and new predicted value */ + /* + ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment + ** in adpcm_coder. + */ + vpdiff = step >> 3; + if ( delta & 4 ) vpdiff += step; + if ( delta & 2 ) vpdiff += step>>1; + if ( delta & 1 ) vpdiff += step>>2; + + if ( sign ) + valpred -= vpdiff; + else + valpred += vpdiff; + + /* Step 5 - clamp output value */ + if ( valpred > 32767 ) + valpred = 32767; + else if ( valpred < -32768 ) + valpred = -32768; + + /* Step 6 - Update step value */ + step = stepsizeTable[index]; + + /* Step 7 - Output value */ + outdata[outp] = valpred; + outp++; + } + + state->sample = valpred; + state->index = index; +} + + +/* +==================== +S_AdpcmMemoryNeeded + +Returns the amount of memory (in bytes) needed to store the samples in out internal adpcm format +==================== +*/ +int S_AdpcmMemoryNeeded( const wavinfo_t *info ) { + float scale; + int scaledSampleCount; + int sampleMemory; + int blockCount; + int headerMemory; + + // determine scale to convert from input sampling rate to desired sampling rate + scale = (float)info->rate / dma.speed; + + // calc number of samples at playback sampling rate + scaledSampleCount = info->samples / scale; + + // calc memory need to store those samples using ADPCM at 4 bits per sample + sampleMemory = scaledSampleCount / 2; + + // calc number of sample blocks needed of PAINTBUFFER_SIZE + blockCount = scaledSampleCount / PAINTBUFFER_SIZE; + if( scaledSampleCount % PAINTBUFFER_SIZE ) { + blockCount++; + } + + // calc memory needed to store the block headers + headerMemory = blockCount * sizeof(adpcm_state_t); + + return sampleMemory + headerMemory; +} + + +/* +==================== +S_AdpcmGetSamples +==================== +*/ +void S_AdpcmGetSamples(sndBuffer *chunk, short *to) { + adpcm_state_t state; + byte *out; + + // get the starting state from the block header + state.index = chunk->adpcm.index; + state.sample = chunk->adpcm.sample; + + out = (byte *)chunk->sndChunk; + // get samples + S_AdpcmDecode((char *) out, to, SND_CHUNK_SIZE_BYTE*2, &state ); +} + + +/* +==================== +S_AdpcmEncodeSound +==================== +*/ +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ) { + adpcm_state_t state; + int inOffset; + int count; + int n; + sndBuffer *newchunk, *chunk; + byte *out; + + inOffset = 0; + count = sfx->soundLength; + state.index = 0; + state.sample = samples[0]; + + chunk = NULL; + while( count ) { + n = count; + if( n > SND_CHUNK_SIZE_BYTE*2 ) { + n = SND_CHUNK_SIZE_BYTE*2; + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + + // output the header + chunk->adpcm.index = state.index; + chunk->adpcm.sample = state.sample; + + out = (byte *)chunk->sndChunk; + + // encode the samples + S_AdpcmEncode( samples + inOffset, (char *) out, n, &state ); + + inOffset += n; + count -= n; + } +} diff --git a/code/client/snd_codec.c b/code/client/snd_codec.c new file mode 100644 index 0000000..a96842b --- /dev/null +++ b/code/client/snd_codec.c @@ -0,0 +1,235 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" + +static snd_codec_t *codecs; + +/* +================= +S_CodecGetSound + +Opens/loads a sound, tries codec based on the sound's file extension +then tries all supported codecs. +================= +*/ +static void *S_CodecGetSound(const char *filename, snd_info_t *info) +{ + snd_codec_t *codec; + snd_codec_t *orgCodec = NULL; + qboolean orgNameFailed = qfalse; + char localName[ MAX_QPATH ]; + const char *ext; + char altName[ MAX_QPATH ]; + void *rtn = NULL; + + Q_strncpyz(localName, filename, MAX_QPATH); + + ext = COM_GetExtension(localName); + + if( *ext ) + { + // Look for the correct loader and use it + for( codec = codecs; codec; codec = codec->next ) + { + if( !Q_stricmp( ext, codec->ext ) ) + { + // Load + if( info ) + rtn = codec->load(localName, info); + else + rtn = codec->open(localName); + break; + } + } + + // A loader was found + if( codec ) + { + if( !rtn ) + { + // Loader failed, most likely because the file isn't there; + // try again without the extension + orgNameFailed = qtrue; + orgCodec = codec; + COM_StripExtension( filename, localName, MAX_QPATH ); + } + else + { + // Something loaded + return rtn; + } + } + } + + // Try and find a suitable match using all + // the sound codecs supported + for( codec = codecs; codec; codec = codec->next ) + { + if( codec == orgCodec ) + continue; + + Com_sprintf( altName, sizeof (altName), "%s.%s", localName, codec->ext ); + + // Load + if( info ) + rtn = codec->load(altName, info); + else + rtn = codec->open(altName); + + if( rtn ) + { + if( orgNameFailed ) + { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s not present, using %s instead\n", + filename, altName ); + } + + return rtn; + } + } + + Com_Printf(S_COLOR_YELLOW "WARNING: Failed to %s sound %s!\n", info ? "load" : "open", filename); + + return NULL; +} + +/* +================= +S_CodecInit +================= +*/ +void S_CodecInit() +{ + codecs = NULL; + +#ifdef USE_CODEC_VORBIS + S_CodecRegister(&ogg_codec); +#endif + + S_CodecRegister(&mp3_codec); + +// Register wav codec last so that it is always tried first when a file extension was not found + S_CodecRegister(&wav_codec); +} + +/* +================= +S_CodecShutdown +================= +*/ +void S_CodecShutdown() +{ + codecs = NULL; +} + +/* +================= +S_CodecRegister +================= +*/ +void S_CodecRegister(snd_codec_t *codec) +{ + codec->next = codecs; + codecs = codec; +} + +/* +================= +S_CodecLoad +================= +*/ +void *S_CodecLoad(const char *filename, snd_info_t *info) +{ + return S_CodecGetSound(filename, info); +} + +/* +================= +S_CodecOpenStream +================= +*/ +snd_stream_t *S_CodecOpenStream(const char *filename) +{ + return S_CodecGetSound(filename, NULL); +} + +void S_CodecCloseStream(snd_stream_t *stream) +{ + stream->codec->close(stream); +} + +int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + return stream->codec->read(stream, bytes, buffer); +} + +//======================================================================= +// Util functions (used by codecs) + +/* +================= +S_CodecUtilOpen +================= +*/ +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec) +{ + snd_stream_t *stream; + fileHandle_t hnd; + int length; + + // Try to open the file + length = FS_FOpenFileRead(filename, &hnd, qtrue); + if(!hnd) + { + Com_DPrintf("Can't read sound file %s\n", filename); + return NULL; + } + + // Allocate a stream + stream = Z_Malloc(sizeof(snd_stream_t)); + if(!stream) + { + FS_FCloseFile(hnd); + return NULL; + } + + // Copy over, return + stream->codec = codec; + stream->file = hnd; + stream->length = length; + return stream; +} + +/* +================= +S_CodecUtilClose +================= +*/ +void S_CodecUtilClose(snd_stream_t **stream) +{ + FS_FCloseFile((*stream)->file); + Z_Free(*stream); + *stream = NULL; +} diff --git a/code/client/snd_codec.h b/code/client/snd_codec.h new file mode 100644 index 0000000..723ca91 --- /dev/null +++ b/code/client/snd_codec.h @@ -0,0 +1,104 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#ifndef _SND_CODEC_H_ +#define _SND_CODEC_H_ + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" + +typedef struct snd_info_s +{ + int rate; + int width; + int channels; + int samples; + int size; + int dataofs; +} snd_info_t; + +typedef struct snd_codec_s snd_codec_t; + +typedef struct snd_stream_s +{ + snd_codec_t *codec; + fileHandle_t file; + snd_info_t info; + int length; + int pos; + void *ptr; +} snd_stream_t; + +// Codec functions +typedef void *(*CODEC_LOAD)(const char *filename, snd_info_t *info); +typedef snd_stream_t *(*CODEC_OPEN)(const char *filename); +typedef int (*CODEC_READ)(snd_stream_t *stream, int bytes, void *buffer); +typedef void (*CODEC_CLOSE)(snd_stream_t *stream); + +// Codec data structure +struct snd_codec_s +{ + char *ext; + CODEC_LOAD load; + CODEC_OPEN open; + CODEC_READ read; + CODEC_CLOSE close; + snd_codec_t *next; +}; + +// Codec management +void S_CodecInit( void ); +void S_CodecShutdown( void ); +void S_CodecRegister(snd_codec_t *codec); +void *S_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_CodecOpenStream(const char *filename); +void S_CodecCloseStream(snd_stream_t *stream); +int S_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); + +// Util functions (used by codecs) +snd_stream_t *S_CodecUtilOpen(const char *filename, snd_codec_t *codec); +void S_CodecUtilClose(snd_stream_t **stream); + +// WAV Codec +extern snd_codec_t wav_codec; +void *S_WAV_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_WAV_CodecOpenStream(const char *filename); +void S_WAV_CodecCloseStream(snd_stream_t *stream); +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); + +// Ogg Vorbis codec +#ifdef USE_CODEC_VORBIS +extern snd_codec_t ogg_codec; +void *S_OGG_CodecLoad(const char *filename, snd_info_t *info); +snd_stream_t *S_OGG_CodecOpenStream(const char *filename); +void S_OGG_CodecCloseStream(snd_stream_t *stream); +int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer); +#endif // USE_CODEC_VORBIS + +extern snd_codec_t mp3_codec; +void *S_MP3_CodecLoad( const char *filename, snd_info_t *info ); +snd_stream_t *S_MP3_CodecOpenStream( const char *filename ); +void S_MP3_CodecCloseStream( snd_stream_t *stream ); +int S_MP3_CodecReadStream( snd_stream_t *stream, int bytes, void *buffer ); + +#endif // !_SND_CODEC_H_ diff --git a/code/client/snd_codec_mp3.c b/code/client/snd_codec_mp3.c new file mode 100644 index 0000000..3f47841 --- /dev/null +++ b/code/client/snd_codec_mp3.c @@ -0,0 +1,707 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2005-2006 Joerg Dietrich +Copyright (C) 2006 Thilo Schulz + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// includes for the Q3 sound system +#include "client.h" +#include "snd_codec.h" + +// includes for the MP3 codec +#include + +#define MP3_SAMPLE_WIDTH 2 +#define MP3_PCMSAMPLES_PERSLICE 32 + +// buffer size used when reading through the mp3 +#define MP3_DATA_BUFSIZ 128*1024 + +// undefine this if you don't want any dithering. +#define MP3_DITHERING + +// Q3 MP3 codec +snd_codec_t mp3_codec = +{ + "mp3", + S_MP3_CodecLoad, + S_MP3_CodecOpenStream, + S_MP3_CodecReadStream, + S_MP3_CodecCloseStream, + NULL +}; + +// structure used for info purposes +struct snd_codec_mp3_info +{ + byte encbuf[MP3_DATA_BUFSIZ]; // left over bytes not consumed + // by the decoder. + struct mad_stream madstream; // uses encbuf as buffer. + struct mad_frame madframe; // control structures for libmad. + struct mad_synth madsynth; + + byte *pcmbuf; // buffer for not-used samples. + int buflen; // length of buffer data. + int pcmbufsize; // amount of allocated memory for + // pcmbuf. This should have at least + // the size of a decoded mp3 frame. + + byte *dest; // copy decoded data here. + int destlen; // amount of already copied data. + int destsize; // amount of bytes we must decode. +}; + +/*************** MP3 utility functions ***************/ + +/* +================= +S_MP3_ReadData +================= +*/ + +// feed libmad with data +int S_MP3_ReadData(snd_stream_t *stream, struct mad_stream *madstream, byte *encbuf, int encbufsize) +{ + int retval; + int leftover; + + if(!stream) + return -1; + + leftover = madstream->bufend - madstream->next_frame; + if(leftover > 0) + memmove(encbuf, madstream->this_frame, leftover); + + retval = FS_Read(&encbuf[leftover], encbufsize - leftover, stream->file); + + if(retval <= 0) { + // EOF reached, that's ok. + return 0; + } + + mad_stream_buffer(madstream, encbuf, retval + leftover); + + return retval; +} + + +/* +================= +S_MP3_Scanfile + +to determine the samplecount, we apparently must get *all* headers :( +I basically used the xmms-mad plugin source to see how this stuff works. + +returns a value < 0 on error. +================= +*/ + +int S_MP3_Scanfile(snd_stream_t *stream) +{ + struct mad_stream madstream; + struct mad_header madheader; + int retval; + int samplecount; + byte encbuf[MP3_DATA_BUFSIZ]; + + // error out on invalid input. + if(!stream) + return -1; + + mad_stream_init(&madstream); + mad_header_init(&madheader); + + while(1) + { + retval = S_MP3_ReadData(stream, &madstream, encbuf, sizeof(encbuf)); + if(retval < 0) + return -1; + else if(retval == 0) + break; + + // Start decoding the headers. + while(1) + { + if((retval = mad_header_decode(&madheader, &madstream)) < 0) + { + if(madstream.error == MAD_ERROR_BUFLEN) + { + // We need to read more data + break; + } + + if(!MAD_RECOVERABLE (madstream.error)) + { + // unrecoverable error... we must bail out. + return retval; + } + + mad_stream_skip(&madstream, madstream.skiplen); + continue; + } + + // we got a valid header. + + if(madheader.layer != MAD_LAYER_III) + { + // we don't support non-mp3s + return -1; + } + + if(!stream->info.samples) + { + // This here is the very first frame. Set initial values now, + // that we expect to stay constant throughout the whole mp3. + + stream->info.rate = madheader.samplerate; + stream->info.width = MP3_SAMPLE_WIDTH; + stream->info.channels = MAD_NCHANNELS(&madheader); + stream->info.samples = 0; + stream->info.size = 0; // same here. + stream->info.dataofs = 0; + } + else + { + // Check whether something changed that shouldn't. + + if(stream->info.rate != madheader.samplerate || + stream->info.channels != MAD_NCHANNELS(&madheader)) + return -1; + } + + // Update the counters + samplecount = MAD_NSBSAMPLES(&madheader) * MP3_PCMSAMPLES_PERSLICE; + stream->info.samples += samplecount; + stream->info.size += samplecount * stream->info.channels * stream->info.width; + } + } + + // Reset the file pointer so we can do the real decoding. + FS_Seek(stream->file, 0, FS_SEEK_SET); + + return 0; +} + +/************************ dithering functions ***************************/ + +#ifdef MP3_DITHERING + +// All dithering done here is taken from the GPL'ed xmms-mad plugin. + +/* Copyright (C) 1997 Makoto Matsumoto and Takuji Nishimura. */ +/* Any feedback is very welcome. For any question, comments, */ +/* see http://www.math.keio.ac.jp/matumoto/emt.html or email */ +/* matumoto@math.keio.ac.jp */ + +/* Period parameters */ +#define MP3_DITH_N 624 +#define MP3_DITH_M 397 +#define MATRIX_A 0x9908b0df /* constant vector a */ +#define UPPER_MASK 0x80000000 /* most significant w-r bits */ +#define LOWER_MASK 0x7fffffff /* least significant r bits */ + +/* Tempering parameters */ +#define TEMPERING_MASK_B 0x9d2c5680 +#define TEMPERING_MASK_C 0xefc60000 +#define TEMPERING_SHIFT_U(y) (y >> 11) +#define TEMPERING_SHIFT_S(y) (y << 7) +#define TEMPERING_SHIFT_T(y) (y << 15) +#define TEMPERING_SHIFT_L(y) (y >> 18) + +static unsigned long mt[MP3_DITH_N]; /* the array for the state vector */ +static int mti=MP3_DITH_N+1; /* mti==MP3_DITH_N+1 means mt[MP3_DITH_N] is not initialized */ + +/* initializing the array with a NONZERO seed */ +void sgenrand(unsigned long seed) +{ + /* setting initial seeds to mt[MP3_DITH_N] using */ + /* the generator Line 25 of Table 1 in */ + /* [KNUTH 1981, The Art of Computer Programming */ + /* Vol. 2 (2nd Ed.), pp102] */ + mt[0]= seed & 0xffffffff; + for (mti=1; mti= MP3_DITH_N) { /* generate MP3_DITH_N words at one time */ + int kk; + + if (mti == MP3_DITH_N+1) /* if sgenrand() has not been called, */ + sgenrand(4357); /* a default initial seed is used */ + + for (kk=0;kk> 1) ^ mag01[y & 0x1]; + } + for (;kk> 1) ^ mag01[y & 0x1]; + } + y = (mt[MP3_DITH_N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); + mt[MP3_DITH_N-1] = mt[MP3_DITH_M-1] ^ (y >> 1) ^ mag01[y & 0x1]; + + mti = 0; + } + + y = mt[mti++]; + y ^= TEMPERING_SHIFT_U(y); + y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B; + y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C; + y ^= TEMPERING_SHIFT_L(y); + + return y; +} + +long triangular_dither_noise(int nbits) { + // parameter nbits : the peak-to-peak amplitude desired (in bits) + // use with nbits set to 2 + nber of bits to be trimmed. + // (because triangular is made from two uniformly distributed processes, + // it starts at 2 bits peak-to-peak amplitude) + // see The Theory of Dithered Quantization by Robert Alexander Wannamaker + // for complete proof of why that's optimal + + long v = (genrand()/2 - genrand()/2); // in ]-2^31, 2^31[ + //int signe = (v>0) ? 1 : -1; + long P = 1 << (32 - nbits); // the power of 2 + v /= P; + // now v in ]-2^(nbits-1), 2^(nbits-1) [ + + return v; +} + +#endif // MP3_DITHERING + +/************************ decoder functions ***************************/ + +/* +================= +S_MP3_Scale + +Converts the signal to 16 bit LE-PCM data and does dithering. + +- borrowed from xmms-mad plugin source. +================= +*/ + +/* + * xmms-mad - mp3 plugin for xmms + * Copyright (C) 2001-2002 Sam Clegg + */ + +signed int S_MP3_Scale(mad_fixed_t sample) +{ + int n_bits_to_loose = MAD_F_FRACBITS + 1 - 16; +#ifdef MP3_DITHERING + int dither; +#endif + + // round + sample += (1L << (n_bits_to_loose - 1)); + +#ifdef MP3_DITHERING + dither = triangular_dither_noise(n_bits_to_loose + 1); + sample += dither; +#endif + + /* clip */ + if (sample >= MAD_F_ONE) + sample = MAD_F_ONE - 1; + else if (sample < -MAD_F_ONE) + sample = -MAD_F_ONE; + + /* quantize */ + return sample >> n_bits_to_loose; +} + + +/* +================= +S_MP3_PCMCopy + +Copy and convert pcm data until bytecount bytes have been written. +return the position in pcm->samples. +indicate the amount of actually written bytes in wrotecnt. +================= +*/ + +int S_MP3_PCMCopy(byte *buf, struct mad_pcm *pcm, int bufofs, + int sampleofs, int bytecount, int *wrotecnt) +{ + int written = 0; + signed int sample; + int framesize = pcm->channels * MP3_SAMPLE_WIDTH; + + // add new pcm data. + while(written < bytecount && sampleofs < pcm->length) + { + sample = S_MP3_Scale(pcm->samples[0][sampleofs]); + +#ifdef Q3_BIG_ENDIAN + // output to 16 bit big endian PCM + buf[bufofs++] = (sample >> 8) & 0xff; + buf[bufofs++] = sample & 0xff; +#else + // output to 16 bit little endian PCM + buf[bufofs++] = sample & 0xff; + buf[bufofs++] = (sample >> 8) & 0xff; +#endif + + if(pcm->channels == 2) + { + sample = S_MP3_Scale(pcm->samples[1][sampleofs]); + +#ifdef Q3_BIG_ENDIAN + buf[bufofs++] = (sample >> 8) & 0xff; + buf[bufofs++] = sample & 0xff; +#else + buf[bufofs++] = sample & 0xff; + buf[bufofs++] = (sample >> 8) & 0xff; +#endif + } + + sampleofs++; + written += framesize; + } + + if(wrotecnt) + *wrotecnt = written; + + return sampleofs; +} + + +/* +================= +S_MP3_Decode +================= +*/ + +// gets executed for every decoded frame. +int S_MP3_Decode(snd_stream_t *stream) +{ + struct snd_codec_mp3_info *mp3info; + struct mad_stream *madstream; + struct mad_frame *madframe; + struct mad_synth *madsynth; + struct mad_pcm *pcm; + int cursize; + int samplecount; + int needcount; + int wrote; + int retval; + + if(!stream) + return -1; + + mp3info = (struct snd_codec_mp3_info *)stream->ptr; + madstream = &mp3info->madstream; + madframe = &mp3info->madframe; + + if(mad_frame_decode(madframe, madstream)) + { + if(madstream->error == MAD_ERROR_BUFLEN) + { + // we need more data. Read another chunk. + retval = S_MP3_ReadData(stream, madstream, mp3info->encbuf, sizeof(mp3info->encbuf)); + + // call myself again now that buffer is full. + if(retval > 0) + retval = S_MP3_Decode(stream); + } + else if(MAD_RECOVERABLE(madstream->error)) + { + mad_stream_skip(madstream, madstream->skiplen); + return S_MP3_Decode(stream); + } + else + retval = -1; + + return retval; + } + + // check whether this really is an mp3 + if(madframe->header.layer != MAD_LAYER_III) + return -1; + + // generate pcm data + madsynth = &mp3info->madsynth; + mad_synth_frame(madsynth, madframe); + + pcm = &madsynth->pcm; + + // perform a few checks to see whether something changed that shouldn't. + + if(stream->info.rate != pcm->samplerate || + stream->info.channels != pcm->channels) + { + return -1; + } + // see whether we have got enough data now. + cursize = pcm->length * pcm->channels * stream->info.width; + needcount = mp3info->destsize - mp3info->destlen; + + // Copy exactly as many samples as required. + samplecount = S_MP3_PCMCopy(mp3info->dest, pcm, + mp3info->destlen, 0, needcount, &wrote); + mp3info->destlen += wrote; + + if(samplecount < pcm->length) + { + // Not all samples got copied. Copy the rest into the pcm buffer. + samplecount = S_MP3_PCMCopy(mp3info->pcmbuf, pcm, + mp3info->buflen, + samplecount, + mp3info->pcmbufsize - mp3info->buflen, + &wrote); + mp3info->buflen += wrote; + + + if(samplecount < pcm->length) + { + // The pcm buffer was not large enough. Make it bigger. + byte *newbuf = (byte *)Hunk_AllocateTempMemory(cursize); + + if(mp3info->pcmbuf) + { + memcpy(newbuf, mp3info->pcmbuf, mp3info->buflen); + Hunk_FreeTempMemory(mp3info->pcmbuf); + } + + mp3info->pcmbuf = newbuf; + mp3info->pcmbufsize = cursize; + + samplecount = S_MP3_PCMCopy(mp3info->pcmbuf, pcm, + mp3info->buflen, + samplecount, + mp3info->pcmbufsize - mp3info->buflen, + &wrote); + mp3info->buflen += wrote; + } + + // we're definitely done. + retval = 0; + } + else if(mp3info->destlen >= mp3info->destsize) + retval = 0; + else + retval = 1; + + return retval; +} + +/*************** Callback functions for quake3 ***************/ + +/* +================= +S_MP3_CodecOpenStream +================= +*/ + +snd_stream_t *S_MP3_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + struct snd_codec_mp3_info *mp3info; + + // Open the stream + stream = S_CodecUtilOpen(filename, &mp3_codec); + if(!stream || stream->length <= 0) + return NULL; + + // We have to scan through the MP3 to determine the important mp3 info. + if(S_MP3_Scanfile(stream) < 0) + { + // scanning didn't work out... + S_CodecUtilClose(&stream); + return NULL; + } + + // Initialize the mp3 info structure we need for streaming + mp3info = (struct snd_codec_mp3_info *)Z_Malloc(sizeof(*mp3info)); + if(!mp3info) + { + S_CodecUtilClose(&stream); + return NULL; + } + + stream->ptr = mp3info; + + // initialize the libmad control structures. + mad_stream_init(&mp3info->madstream); + mad_frame_init(&mp3info->madframe); + mad_synth_init(&mp3info->madsynth); + + if(S_MP3_ReadData(stream, &mp3info->madstream, mp3info->encbuf, sizeof(mp3info->encbuf)) <= 0) + { + // we didnt read anything, that's bad. + S_MP3_CodecCloseStream(stream); + return NULL; + } + + return stream; +} + +/* +================= +S_MP3_CodecCloseStream +================= +*/ + +// free all memory we allocated. +void S_MP3_CodecCloseStream(snd_stream_t *stream) +{ + struct snd_codec_mp3_info *mp3info; + + if(!stream) + return; + + // free all data in our mp3info tree + + if(stream->ptr) + { + mp3info = (struct snd_codec_mp3_info *)stream->ptr; + + if(mp3info->pcmbuf) + Hunk_FreeTempMemory(mp3info->pcmbuf); + + mad_synth_finish(&mp3info->madsynth); + mad_frame_finish(&mp3info->madframe); + mad_stream_finish(&mp3info->madstream); + + Z_Free(stream->ptr); + } + + S_CodecUtilClose(&stream); +} + +/* +================= +S_MP3_CodecReadStream +================= +*/ +int S_MP3_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + struct snd_codec_mp3_info *mp3info; + int retval; + + if(!stream) + return -1; + + mp3info = (struct snd_codec_mp3_info *)stream->ptr; + + // Make sure we get complete frames all the way through. + bytes -= bytes % (stream->info.channels * stream->info.width); + + if(mp3info->buflen) + { + if(bytes < mp3info->buflen) + { + // we still have enough bytes in our decoded pcm buffer + memcpy(buffer, mp3info->pcmbuf, bytes); + + // remove the portion from our buffer. + mp3info->buflen -= bytes; + memmove(mp3info->pcmbuf, &mp3info->pcmbuf[bytes], mp3info->buflen); + return bytes; + } + else + { + // copy over the samples we already have. + memcpy(buffer, mp3info->pcmbuf, mp3info->buflen); + mp3info->destlen = mp3info->buflen; + mp3info->buflen = 0; + } + } + else + mp3info->destlen = 0; + + mp3info->dest = (byte *)buffer; + mp3info->destsize = bytes; + + do + { + retval = S_MP3_Decode(stream); + } while(retval > 0); + + // if there was an error return nothing. + if(retval < 0) + return 0; + + return mp3info->destlen; +} + +/* +===================================================================== +S_MP3_CodecLoad + +We handle S_MP3_CodecLoad as a special case of the streaming functions +where we read the whole stream at once. ++====================================================================== +*/ +void *S_MP3_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_stream_t *stream; + byte *pcmbuffer; + + // check if input is valid + if(!filename) + return NULL; + + stream = S_MP3_CodecOpenStream(filename); + + if(!stream) + return NULL; + + // copy over the info + info->rate = stream->info.rate; + info->width = stream->info.width; + info->channels = stream->info.channels; + info->samples = stream->info.samples; + info->dataofs = stream->info.dataofs; + + // allocate enough buffer for all pcm data + pcmbuffer = (byte *)Hunk_AllocateTempMemory( stream->info.size ); + if(!pcmbuffer) + { + S_MP3_CodecCloseStream(stream); + return NULL; + } + + info->size = S_MP3_CodecReadStream(stream, stream->info.size, pcmbuffer); + + if(info->size <= 0) + { + // we didn't read anything at all. darn. + Hunk_FreeTempMemory( pcmbuffer ); + pcmbuffer = NULL; + } + + S_MP3_CodecCloseStream(stream); + + return pcmbuffer; +} + diff --git a/code/client/snd_codec_ogg.c b/code/client/snd_codec_ogg.c new file mode 100644 index 0000000..c424649 --- /dev/null +++ b/code/client/snd_codec_ogg.c @@ -0,0 +1,478 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) +Copyright (C) 2005-2006 Joerg Dietrich + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// OGG support is enabled by this define +#ifdef USE_CODEC_VORBIS + +// includes for the Q3 sound system +#include "client.h" +#include "snd_codec.h" + +// includes for the OGG codec +#include +#define OV_EXCLUDE_STATIC_CALLBACKS +#include + +// The OGG codec can return the samples in a number of different formats, +// we use the standard signed short format. +#define OGG_SAMPLEWIDTH 2 + +// Q3 OGG codec +snd_codec_t ogg_codec = +{ + "ogg", + S_OGG_CodecLoad, + S_OGG_CodecOpenStream, + S_OGG_CodecReadStream, + S_OGG_CodecCloseStream, + NULL +}; + +// callbacks for vobisfile + +// fread() replacement +size_t S_OGG_Callback_read(void *ptr, size_t size, size_t nmemb, void *datasource) +{ + snd_stream_t *stream; + int byteSize = 0; + int bytesRead = 0; + size_t nMembRead = 0; + + // check if input is valid + if(!ptr) + { + errno = EFAULT; + return 0; + } + + if(!(size && nmemb)) + { + // It's not an error, caller just wants zero bytes! + errno = 0; + return 0; + } + + if(!datasource) + { + errno = EBADF; + return 0; + } + + // we use a snd_stream_t in the generic pointer to pass around + stream = (snd_stream_t *) datasource; + + // FS_Read does not support multi-byte elements + byteSize = nmemb * size; + + // read it with the Q3 function FS_Read() + bytesRead = FS_Read(ptr, byteSize, stream->file); + + // update the file position + stream->pos += bytesRead; + + // this function returns the number of elements read not the number of bytes + nMembRead = bytesRead / size; + + // even if the last member is only read partially + // it is counted as a whole in the return value + if(bytesRead % size) + { + nMembRead++; + } + + return nMembRead; +} + +// fseek() replacement +int S_OGG_Callback_seek(void *datasource, ogg_int64_t offset, int whence) +{ + snd_stream_t *stream; + int retVal = 0; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + // we must map the whence to its Q3 counterpart + switch(whence) + { + case SEEK_SET : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = (int) offset; + break; + } + + case SEEK_CUR : + { + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) offset, FS_SEEK_CUR); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos += (int) offset; + break; + } + + case SEEK_END : + { + // Quake 3 seems to have trouble with FS_SEEK_END + // so we use the file length and FS_SEEK_SET + + // set the file position in the actual file with the Q3 function + retVal = FS_Seek(stream->file, (long) stream->length + (long) offset, FS_SEEK_SET); + + // something has gone wrong, so we return here + if(retVal < 0) + { + return retVal; + } + + // keep track of file position + stream->pos = stream->length + (int) offset; + break; + } + + default : + { + // unknown whence, so we return an error + errno = EINVAL; + return -1; + } + } + + // stream->pos shouldn't be smaller than zero or bigger than the filesize + stream->pos = (stream->pos < 0) ? 0 : stream->pos; + stream->pos = (stream->pos > stream->length) ? stream->length : stream->pos; + + return 0; +} + +// fclose() replacement +int S_OGG_Callback_close(void *datasource) +{ + // we do nothing here and close all things manually in S_OGG_CodecCloseStream() + return 0; +} + +// ftell() replacement +long S_OGG_Callback_tell(void *datasource) +{ + snd_stream_t *stream; + + // check if input is valid + if(!datasource) + { + errno = EBADF; + return -1; + } + + // snd_stream_t in the generic pointer + stream = (snd_stream_t *) datasource; + + return (long) FS_FTell(stream->file); +} + +// the callback structure +const ov_callbacks S_OGG_Callbacks = +{ + &S_OGG_Callback_read, + &S_OGG_Callback_seek, + &S_OGG_Callback_close, + &S_OGG_Callback_tell +}; + +/* +================= +S_OGG_CodecOpenStream +================= +*/ +snd_stream_t *S_OGG_CodecOpenStream(const char *filename) +{ + snd_stream_t *stream; + + // OGG codec control structure + OggVorbis_File *vf; + + // some variables used to get informations about the OGG + vorbis_info *OGGInfo; + ogg_int64_t numSamples; + + // check if input is valid + if(!filename) + { + return NULL; + } + + // Open the stream + stream = S_CodecUtilOpen(filename, &ogg_codec); + if(!stream) + { + return NULL; + } + + // alloctate the OggVorbis_File + vf = Z_Malloc(sizeof(OggVorbis_File)); + if(!vf) + { + S_CodecUtilClose(&stream); + + return NULL; + } + + // open the codec with our callbacks and stream as the generic pointer + if(ov_open_callbacks(stream, vf, NULL, 0, S_OGG_Callbacks) != 0) + { + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // the stream must be seekable + if(!ov_seekable(vf)) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // we only support OGGs with one substream + if(ov_streams(vf) != 1) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the info about channels and rate + OGGInfo = ov_info(vf, 0); + if(!OGGInfo) + { + ov_clear(vf); + + Z_Free(vf); + + S_CodecUtilClose(&stream); + + return NULL; + } + + // get the number of sample-frames in the OGG + numSamples = ov_pcm_total(vf, 0); + + // fill in the info-structure in the stream + stream->info.rate = OGGInfo->rate; + stream->info.width = OGG_SAMPLEWIDTH; + stream->info.channels = OGGInfo->channels; + stream->info.samples = numSamples; + stream->info.size = stream->info.samples * stream->info.channels * stream->info.width; + stream->info.dataofs = 0; + + // We use stream->pos for the file pointer in the compressed ogg file + stream->pos = 0; + + // We use the generic pointer in stream for the OGG codec control structure + stream->ptr = vf; + + return stream; +} + +/* +================= +S_OGG_CodecCloseStream +================= +*/ +void S_OGG_CodecCloseStream(snd_stream_t *stream) +{ + // check if input is valid + if(!stream) + { + return; + } + + // let the OGG codec cleanup its stuff + ov_clear((OggVorbis_File *) stream->ptr); + + // free the OGG codec control struct + Z_Free(stream->ptr); + + // close the stream + S_CodecUtilClose(&stream); +} + +/* +================= +S_OGG_CodecReadStream +================= +*/ +int S_OGG_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + // buffer handling + int bytesRead, bytesLeft, c; + char *bufPtr; + + // Bitstream for the decoder + int BS = 0; + + // big endian machines want their samples in big endian order + int IsBigEndian = 0; + +# ifdef Q3_BIG_ENDIAN + IsBigEndian = 1; +# endif // Q3_BIG_ENDIAN + + // check if input is valid + if(!(stream && buffer)) + { + return 0; + } + + if(bytes <= 0) + { + return 0; + } + + bytesRead = 0; + bytesLeft = bytes; + bufPtr = buffer; + + // cycle until we have the requested or all available bytes read + while(-1) + { + // read some bytes from the OGG codec + c = ov_read((OggVorbis_File *) stream->ptr, bufPtr, bytesLeft, IsBigEndian, OGG_SAMPLEWIDTH, 1, &BS); + + // no more bytes are left + if(c <= 0) + { + break; + } + + bytesRead += c; + bytesLeft -= c; + bufPtr += c; + + // we have enough bytes + if(bytesLeft <= 0) + { + break; + } + } + + return bytesRead; +} + +/* +===================================================================== +S_OGG_CodecLoad + +We handle S_OGG_CodecLoad as a special case of the streaming functions +where we read the whole stream at once. +====================================================================== +*/ +void *S_OGG_CodecLoad(const char *filename, snd_info_t *info) +{ + snd_stream_t *stream; + byte *buffer; + int bytesRead; + + // check if input is valid + if(!(filename && info)) + { + return NULL; + } + + // open the file as a stream + stream = S_OGG_CodecOpenStream(filename); + if(!stream) + { + return NULL; + } + + // copy over the info + info->rate = stream->info.rate; + info->width = stream->info.width; + info->channels = stream->info.channels; + info->samples = stream->info.samples; + info->size = stream->info.size; + info->dataofs = stream->info.dataofs; + + // allocate a buffer + // this buffer must be free-ed by the caller of this function + buffer = Hunk_AllocateTempMemory(info->size); + if(!buffer) + { + S_OGG_CodecCloseStream(stream); + + return NULL; + } + + // fill the buffer + bytesRead = S_OGG_CodecReadStream(stream, info->size, buffer); + + // we don't even have read a single byte + if(bytesRead <= 0) + { + Hunk_FreeTempMemory(buffer); + S_OGG_CodecCloseStream(stream); + + return NULL; + } + + S_OGG_CodecCloseStream(stream); + + return buffer; +} + +#endif // USE_CODEC_VORBIS diff --git a/code/client/snd_codec_wav.c b/code/client/snd_codec_wav.c new file mode 100644 index 0000000..2fd6aa5 --- /dev/null +++ b/code/client/snd_codec_wav.c @@ -0,0 +1,291 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" + +/* +================= +FGetLittleLong +================= +*/ +static int FGetLittleLong( fileHandle_t f ) { + int v; + + FS_Read( &v, sizeof(v), f ); + + return LittleLong( v); +} + +/* +================= +FGetLittleShort +================= +*/ +static short FGetLittleShort( fileHandle_t f ) { + short v; + + FS_Read( &v, sizeof(v), f ); + + return LittleShort( v); +} + +/* +================= +S_ReadChunkInfo +================= +*/ +static int S_ReadChunkInfo(fileHandle_t f, char *name) +{ + int len, r; + + name[4] = 0; + + r = FS_Read(name, 4, f); + if(r != 4) + return -1; + + len = FGetLittleLong(f); + if( len < 0 ) { + Com_Printf( S_COLOR_YELLOW "WARNING: Negative chunk length\n" ); + return -1; + } + + return len; +} + +/* +================= +S_FindRIFFChunk + +Returns the length of the data in the chunk, or -1 if not found +================= +*/ +static int S_FindRIFFChunk( fileHandle_t f, char *chunk ) { + char name[5]; + int len; + + while( ( len = S_ReadChunkInfo(f, name) ) >= 0 ) + { + // If this is the right chunk, return + if( !Q_strncmp( name, chunk, 4 ) ) + return len; + + len = PAD( len, 2 ); + + // Not the right chunk - skip it + FS_Seek( f, len, FS_SEEK_CUR ); + } + + return -1; +} + +/* +================= +S_ByteSwapRawSamples +================= +*/ +static void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { + int i; + + if ( width != 2 ) { + return; + } + if ( LittleShort( 256 ) == 256 ) { + return; + } + + if ( s_channels == 2 ) { + samples <<= 1; + } + for ( i = 0 ; i < samples ; i++ ) { + ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } +} + +/* +================= +S_ReadRIFFHeader +================= +*/ +static qboolean S_ReadRIFFHeader(fileHandle_t file, snd_info_t *info) +{ + char dump[16]; + int bits; + int fmtlen = 0; + + // skip the riff wav header + FS_Read(dump, 12, file); + + // Scan for the format chunk + if((fmtlen = S_FindRIFFChunk(file, "fmt ")) < 0) + { + Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"fmt\" chunk\n"); + return qfalse; + } + + // Save the parameters + FGetLittleShort(file); // wav_format + info->channels = FGetLittleShort(file); + info->rate = FGetLittleLong(file); + FGetLittleLong(file); + FGetLittleShort(file); + bits = FGetLittleShort(file); + + if( bits < 8 ) + { + Com_Printf( S_COLOR_RED "ERROR: Less than 8 bit sound is not supported\n"); + return qfalse; + } + + info->width = bits / 8; + info->dataofs = 0; + + // Skip the rest of the format chunk if required + if(fmtlen > 16) + { + fmtlen -= 16; + FS_Seek( file, fmtlen, FS_SEEK_CUR ); + } + + // Scan for the data chunk + if( (info->size = S_FindRIFFChunk(file, "data")) < 0) + { + Com_Printf( S_COLOR_RED "ERROR: Couldn't find \"data\" chunk\n"); + return qfalse; + } + info->samples = (info->size / info->width) / info->channels; + + return qtrue; +} + +// WAV codec +snd_codec_t wav_codec = +{ + "wav", + S_WAV_CodecLoad, + S_WAV_CodecOpenStream, + S_WAV_CodecReadStream, + S_WAV_CodecCloseStream, + NULL +}; + +/* +================= +S_WAV_CodecLoad +================= +*/ +void *S_WAV_CodecLoad(const char *filename, snd_info_t *info) +{ + fileHandle_t file; + void *buffer; + + // Try to open the file + FS_FOpenFileRead(filename, &file, qtrue); + if(!file) + { + return NULL; + } + + // Read the RIFF header + if(!S_ReadRIFFHeader(file, info)) + { + FS_FCloseFile(file); + Com_Printf( S_COLOR_RED "ERROR: Incorrect/unsupported format in \"%s\"\n", + filename); + return NULL; + } + + // Allocate some memory + buffer = Hunk_AllocateTempMemory(info->size); + if(!buffer) + { + FS_FCloseFile(file); + Com_Printf( S_COLOR_RED "ERROR: Out of memory reading \"%s\"\n", + filename); + return NULL; + } + + // Read, byteswap + FS_Read(buffer, info->size, file); + S_ByteSwapRawSamples(info->samples, info->width, info->channels, (byte *)buffer); + + // Close and return + FS_FCloseFile(file); + return buffer; +} + +/* +================= +S_WAV_CodecOpenStream +================= +*/ +snd_stream_t *S_WAV_CodecOpenStream(const char *filename) +{ + snd_stream_t *rv; + + // Open + rv = S_CodecUtilOpen(filename, &wav_codec); + if(!rv) + return NULL; + + // Read the RIFF header + if(!S_ReadRIFFHeader(rv->file, &rv->info)) + { + S_CodecUtilClose(&rv); + return NULL; + } + + return rv; +} + +/* +================= +S_WAV_CodecCloseStream +================= +*/ +void S_WAV_CodecCloseStream(snd_stream_t *stream) +{ + S_CodecUtilClose(&stream); +} + +/* +================= +S_WAV_CodecReadStream +================= +*/ +int S_WAV_CodecReadStream(snd_stream_t *stream, int bytes, void *buffer) +{ + int remaining = stream->info.size - stream->pos; + int samples; + + if(remaining <= 0) + return 0; + if(bytes > remaining) + bytes = remaining; + stream->pos += bytes; + samples = (bytes / stream->info.width) / stream->info.channels; + FS_Read(buffer, bytes, stream->file); + S_ByteSwapRawSamples(samples, stream->info.width, stream->info.channels, buffer); + return bytes; +} diff --git a/code/client/snd_dma.c b/code/client/snd_dma.c new file mode 100644 index 0000000..a7c771f --- /dev/null +++ b/code/client/snd_dma.c @@ -0,0 +1,1550 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: snd_dma.c + * + * desc: main control for any streaming sound output device + * + * $Archive: /MissionPack/code/client/snd_dma.c $ + * + *****************************************************************************/ + +#include "snd_local.h" +#include "snd_codec.h" +#include "client.h" + +void S_Update_( void ); +void S_Base_StopAllSounds(void); +void S_Base_StopBackgroundTrack( void ); + +snd_stream_t *s_backgroundStream = NULL; +static char s_backgroundLoop[MAX_QPATH]; +//static char s_backgroundMusic[MAX_QPATH]; //TTimo: unused + + +// ======================================================================= +// Internal sound data & structures +// ======================================================================= + +// only begin attenuating sound volumes when outside the FULLVOLUME range +#define SOUND_FULLVOLUME 80 + +#define SOUND_ATTENUATE 0.0008f + +channel_t s_channels[MAX_CHANNELS]; +channel_t loop_channels[MAX_CHANNELS]; +int numLoopChannels; + +static int s_soundStarted; +static qboolean s_soundMuted; + +dma_t dma; + +static int listener_number; +static vec3_t listener_origin; +static vec3_t listener_axis[3]; + +int s_soundtime; // sample PAIRS +int s_paintedtime; // sample PAIRS + +// MAX_SFX may be larger than MAX_SOUNDS because +// of custom player sounds +#define MAX_SFX 4096 +sfx_t s_knownSfx[MAX_SFX]; +int s_numSfx = 0; + +#define LOOP_HASH 128 +static sfx_t *sfxHash[LOOP_HASH]; + +cvar_t *s_testsound; +cvar_t *s_khz; +cvar_t *s_show; +cvar_t *s_mixahead; +cvar_t *s_mixPreStep; + +static loopSound_t loopSounds[MAX_GENTITIES]; +static channel_t *freelist = NULL; + +int s_rawend[MAX_RAW_STREAMS]; +portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; + + +// ==================================================================== +// User-setable variables +// ==================================================================== + + +void S_Base_SoundInfo(void) { + Com_Printf("----- Sound Info -----\n" ); + if (!s_soundStarted) { + Com_Printf ("sound system not started\n"); + } else { + Com_Printf("%5d stereo\n", dma.channels - 1); + Com_Printf("%5d samples\n", dma.samples); + Com_Printf("%5d samplebits\n", dma.samplebits); + Com_Printf("%5d submission_chunk\n", dma.submission_chunk); + Com_Printf("%5d speed\n", dma.speed); + Com_Printf("%p dma buffer\n", dma.buffer); + if ( s_backgroundStream ) { + Com_Printf("Background file: %s\n", s_backgroundLoop ); + } else { + Com_Printf("No background file.\n" ); + } + + } + Com_Printf("----------------------\n" ); +} + + +#ifdef USE_VOIP +static +void S_Base_StartCapture( void ) +{ + // !!! FIXME: write me. +} + +static +int S_Base_AvailableCaptureSamples( void ) +{ + // !!! FIXME: write me. + return 0; +} + +static +void S_Base_Capture( int samples, byte *data ) +{ + // !!! FIXME: write me. +} + +static +void S_Base_StopCapture( void ) +{ + // !!! FIXME: write me. +} + +static +void S_Base_MasterGain( float val ) +{ + // !!! FIXME: write me. +} +#endif + + + +/* +================= +S_Base_SoundList +================= +*/ +void S_Base_SoundList( void ) { + int i; + sfx_t *sfx; + int size, total; + char type[4][16]; + char mem[2][16]; + + strcpy(type[0], "16bit"); + strcpy(type[1], "adpcm"); + strcpy(type[2], "daub4"); + strcpy(type[3], "mulaw"); + strcpy(mem[0], "paged out"); + strcpy(mem[1], "resident "); + total = 0; + for (sfx=s_knownSfx, i=0 ; isoundLength; + total += size; + Com_Printf("%6i[%s] : %s[%s]\n", size, type[sfx->soundCompressionMethod], + sfx->soundName, mem[sfx->inMemory] ); + } + Com_Printf ("Total resident: %i\n", total); + S_DisplayFreeMemory(); +} + + + +void S_ChannelFree(channel_t *v) { + v->thesfx = NULL; + *(channel_t **)v = freelist; + freelist = (channel_t*)v; +} + +channel_t* S_ChannelMalloc( void ) { + channel_t *v; + if (freelist == NULL) { + return NULL; + } + v = freelist; + freelist = *(channel_t **)freelist; + v->allocTime = Com_Milliseconds(); + return v; +} + +void S_ChannelSetup( void ) { + channel_t *p, *q; + + // clear all the sounds so they don't + Com_Memset( s_channels, 0, sizeof( s_channels ) ); + + p = s_channels;; + q = p + MAX_CHANNELS; + while (--q > p) { + *(channel_t **)q = q-1; + } + + *(channel_t **)q = NULL; + freelist = p + MAX_CHANNELS - 1; + Com_DPrintf("Channel memory manager started\n"); +} + + + +// ======================================================================= +// Load a sound +// ======================================================================= + +/* +================ +return a hash value for the sfx name +================ +*/ +static long S_HashSFXName(const char *name) { + int i; + long hash; + char letter; + + hash = 0; + i = 0; + while (name[i] != '\0') { + letter = tolower(name[i]); + if (letter =='.') break; // don't include extension + if (letter =='\\') letter = '/'; // damn path names + hash+=(long)(letter)*(i+119); + i++; + } + hash &= (LOOP_HASH-1); + return hash; +} + +/* +================== +S_FindName + +Will allocate a new sfx if it isn't found +================== +*/ +static sfx_t *S_FindName( const char *name ) { + int i; + int hash; + + sfx_t *sfx; + + if (!name) { + Com_Error (ERR_FATAL, "S_FindName: NULL"); + } + if (!name[0]) { + Com_Error (ERR_FATAL, "S_FindName: empty name"); + } + + if (strlen(name) >= MAX_QPATH) { + Com_Error (ERR_FATAL, "Sound name too long: %s", name); + } + + hash = S_HashSFXName(name); + + sfx = sfxHash[hash]; + // see if already loaded + while (sfx) { + if (!Q_stricmp(sfx->soundName, name) ) { + return sfx; + } + sfx = sfx->next; + } + + // find a free sfx + for (i=0 ; i < s_numSfx ; i++) { + if (!s_knownSfx[i].soundName[0]) { + break; + } + } + + if (i == s_numSfx) { + if (s_numSfx == MAX_SFX) { + Com_Error (ERR_FATAL, "S_FindName: out of sfx_t"); + } + s_numSfx++; + } + + sfx = &s_knownSfx[i]; + Com_Memset (sfx, 0, sizeof(*sfx)); + strcpy (sfx->soundName, name); + + sfx->next = sfxHash[hash]; + sfxHash[hash] = sfx; + + return sfx; +} + +/* +================= +S_DefaultSound +================= +*/ +void S_DefaultSound( sfx_t *sfx ) { + + int i; + + sfx->soundLength = 512; + sfx->soundData = SND_malloc(); + sfx->soundData->next = NULL; + + + for ( i = 0 ; i < sfx->soundLength ; i++ ) { + sfx->soundData->sndChunk[i] = i; + } +} + +/* +=================== +S_DisableSounds + +Disables sounds until the next S_BeginRegistration. +This is called when the hunk is cleared and the sounds +are no longer valid. +=================== +*/ +void S_Base_DisableSounds( void ) { + S_Base_StopAllSounds(); + s_soundMuted = qtrue; +} + +/* +================== +S_RegisterSound + +Creates a default buzz sound if the file can't be loaded +================== +*/ +sfxHandle_t S_Base_RegisterSound( const char *name, qboolean compressed ) { + sfx_t *sfx; + + compressed = qfalse; + if (!s_soundStarted) { + return 0; + } + + if ( strlen( name ) >= MAX_QPATH ) { + Com_Printf( "Sound name exceeds MAX_QPATH\n" ); + return 0; + } + + sfx = S_FindName( name ); + if ( sfx->soundData ) { + if ( sfx->defaultSound ) { + Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + return 0; + } + return sfx - s_knownSfx; + } + + sfx->inMemory = qfalse; + sfx->soundCompressed = compressed; + + S_memoryLoad(sfx); + + if ( sfx->defaultSound ) { + Com_Printf( S_COLOR_YELLOW "WARNING: could not find %s - using default\n", sfx->soundName ); + return 0; + } + + return sfx - s_knownSfx; +} + +/* +===================== +S_BeginRegistration + +===================== +*/ +void S_Base_BeginRegistration( void ) { + s_soundMuted = qfalse; // we can play again + + if (s_numSfx == 0) { + SND_setup(); + + Com_Memset(s_knownSfx, '\0', sizeof(s_knownSfx)); + Com_Memset(sfxHash, '\0', sizeof(sfx_t *) * LOOP_HASH); + + S_Base_RegisterSound("sound/null.wav", qfalse); // changed to a sound in baseq3 + } +} + +void S_memoryLoad(sfx_t *sfx) { + // load the sound file + if ( !S_LoadSound ( sfx ) ) { +// Com_Printf( S_COLOR_YELLOW "WARNING: couldn't load sound: %s\n", sfx->soundName ); + sfx->defaultSound = qtrue; + } + sfx->inMemory = qtrue; +} + +//============================================================================= + +/* +================= +S_SpatializeOrigin + +Used for spatializing s_channels +================= +*/ +void S_SpatializeOrigin (vec3_t origin, int master_vol, int *left_vol, int *right_vol) +{ + vec_t dot; + vec_t dist; + vec_t lscale, rscale, scale; + vec3_t source_vec; + vec3_t vec; + + const float dist_mult = SOUND_ATTENUATE; + + // calculate stereo seperation and distance attenuation + VectorSubtract(origin, listener_origin, source_vec); + + dist = VectorNormalize(source_vec); + dist -= SOUND_FULLVOLUME; + if (dist < 0) + dist = 0; // close enough to be at full volume + dist *= dist_mult; // different attenuation levels + + VectorRotate( source_vec, listener_axis, vec ); + + dot = -vec[1]; + + if (dma.channels == 1) + { // no attenuation = no spatialization + rscale = 1.0; + lscale = 1.0; + } + else + { + rscale = 0.5 * (1.0 + dot); + lscale = 0.5 * (1.0 - dot); + if ( rscale < 0 ) { + rscale = 0; + } + if ( lscale < 0 ) { + lscale = 0; + } + } + + // add in distance effect + scale = (1.0 - dist) * rscale; + *right_vol = (master_vol * scale); + if (*right_vol < 0) + *right_vol = 0; + + scale = (1.0 - dist) * lscale; + *left_vol = (master_vol * scale); + if (*left_vol < 0) + *left_vol = 0; +} + +// ======================================================================= +// Start a sound effect +// ======================================================================= + +/* +==================== +S_StartSound + +Validates the parms and ques the sound up +if pos is NULL, the sound will be dynamically sourced from the entity +Entchannel 0 will never override a playing sound +==================== +*/ +void S_Base_StartSound(vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfxHandle ) { + channel_t *ch; + sfx_t *sfx; + int i, oldest, chosen, time; + int inplay, allowed; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( !origin && ( entityNum < 0 || entityNum > MAX_GENTITIES ) ) { + Com_Error( ERR_DROP, "S_StartSound: bad entitynum %i", entityNum ); + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_StartSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == qfalse) { + S_memoryLoad(sfx); + } + + if ( s_show->integer == 1 ) { + Com_Printf( "%i : %s\n", s_paintedtime, sfx->soundName ); + } + + time = Com_Milliseconds(); + +// Com_Printf("playing %s\n", sfx->soundName); + // pick a channel to play on + + allowed = 4; + if (entityNum == listener_number) { + allowed = 8; + } + + ch = s_channels; + inplay = 0; + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum == entityNum && ch->thesfx == sfx) { + if (time - ch->allocTime < 50) { +// if (Cvar_VariableValue( "cg_showmiss" )) { +// Com_Printf("double sound start\n"); +// } + return; + } + inplay++; + } + } + + if (inplay>allowed) { + return; + } + + sfx->lastTimeUsed = time; + + ch = S_ChannelMalloc(); // entityNum, entchannel); + if (!ch) { + ch = s_channels; + + oldest = sfx->lastTimeUsed; + chosen = -1; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum != listener_number && ch->entnum == entityNum && ch->allocTimeentchannel != CHAN_ANNOUNCER) { + oldest = ch->allocTime; + chosen = i; + } + } + if (chosen == -1) { + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->entnum != listener_number && ch->allocTimeentchannel != CHAN_ANNOUNCER) { + oldest = ch->allocTime; + chosen = i; + } + } + if (chosen == -1) { + ch = s_channels; + if (ch->entnum == listener_number) { + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if (ch->allocTimeallocTime; + chosen = i; + } + } + } + if (chosen == -1) { + Com_Printf("dropping sound\n"); + return; + } + } + } + ch = &s_channels[chosen]; + ch->allocTime = sfx->lastTimeUsed; + } + + if (origin) { + VectorCopy (origin, ch->origin); + ch->fixed_origin = qtrue; + } else { + ch->fixed_origin = qfalse; + } + + ch->master_vol = 127; + ch->entnum = entityNum; + ch->thesfx = sfx; + ch->startSample = START_SAMPLE_IMMEDIATE; + ch->entchannel = entchannel; + ch->leftvol = ch->master_vol; // these will get calced at next spatialize + ch->rightvol = ch->master_vol; // unless the game isn't running + ch->doppler = qfalse; +} + + +/* +================== +S_StartLocalSound +================== +*/ +void S_Base_StartLocalSound( sfxHandle_t sfxHandle, int channelNum ) { + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_StartLocalSound: handle %i out of range\n", sfxHandle ); + return; + } + + S_Base_StartSound (NULL, listener_number, channelNum, sfxHandle ); +} + + +/* +================== +S_ClearSoundBuffer + +If we are about to perform file access, clear the buffer +so sound doesn't stutter. +================== +*/ +void S_Base_ClearSoundBuffer( void ) { + int clear; + + if (!s_soundStarted) + return; + + // stop looping sounds + Com_Memset(loopSounds, 0, MAX_GENTITIES*sizeof(loopSound_t)); + Com_Memset(loop_channels, 0, MAX_CHANNELS*sizeof(channel_t)); + numLoopChannels = 0; + + S_ChannelSetup(); + + Com_Memset(s_rawend, '\0', sizeof (s_rawend)); + + if (dma.samplebits == 8) + clear = 0x80; + else + clear = 0; + + SNDDMA_BeginPainting (); + if (dma.buffer) + Com_Memset(dma.buffer, clear, dma.samples * dma.samplebits/8); + SNDDMA_Submit (); +} + +/* +================== +S_StopAllSounds +================== +*/ +void S_Base_StopAllSounds(void) { + if ( !s_soundStarted ) { + return; + } + + // stop the background music + S_Base_StopBackgroundTrack(); + + S_Base_ClearSoundBuffer (); +} + +/* +============================================================== + +continuous looping sounds are added each frame + +============================================================== +*/ + +void S_Base_StopLoopingSound(int entityNum) { + loopSounds[entityNum].active = qfalse; +// loopSounds[entityNum].sfx = 0; + loopSounds[entityNum].kill = qfalse; +} + +/* +================== +S_ClearLoopingSounds + +================== +*/ +void S_Base_ClearLoopingSounds( qboolean killall ) { + int i; + for ( i = 0 ; i < MAX_GENTITIES ; i++) { + if (killall || loopSounds[i].kill == qtrue || (loopSounds[i].sfx && loopSounds[i].sfx->soundLength == 0)) { + loopSounds[i].kill = qfalse; + S_Base_StopLoopingSound(i); + } + } + numLoopChannels = 0; +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +Include velocity in case I get around to doing doppler... +================== +*/ +void S_Base_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { + sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_AddLoopingSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == qfalse) { + S_memoryLoad(sfx); + } + + if ( !sfx->soundLength ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); + } + + VectorCopy( origin, loopSounds[entityNum].origin ); + VectorCopy( velocity, loopSounds[entityNum].velocity ); + loopSounds[entityNum].active = qtrue; + loopSounds[entityNum].kill = qtrue; + loopSounds[entityNum].doppler = qfalse; + loopSounds[entityNum].oldDopplerScale = 1.0; + loopSounds[entityNum].dopplerScale = 1.0; + loopSounds[entityNum].sfx = sfx; + + if (s_doppler->integer && VectorLengthSquared(velocity)>0.0) { + vec3_t out; + float lena, lenb; + + loopSounds[entityNum].doppler = qtrue; + lena = DistanceSquared(loopSounds[listener_number].origin, loopSounds[entityNum].origin); + VectorAdd(loopSounds[entityNum].origin, loopSounds[entityNum].velocity, out); + lenb = DistanceSquared(loopSounds[listener_number].origin, out); + if ((loopSounds[entityNum].framenum+1) != cls.framecount) { + loopSounds[entityNum].oldDopplerScale = 1.0; + } else { + loopSounds[entityNum].oldDopplerScale = loopSounds[entityNum].dopplerScale; + } + loopSounds[entityNum].dopplerScale = lenb/(lena*100); + if (loopSounds[entityNum].dopplerScale<=1.0) { + loopSounds[entityNum].doppler = qfalse; // don't bother doing the math + } else if (loopSounds[entityNum].dopplerScale>MAX_DOPPLER_SCALE) { + loopSounds[entityNum].dopplerScale = MAX_DOPPLER_SCALE; + } + } + + loopSounds[entityNum].framenum = cls.framecount; +} + +/* +================== +S_AddLoopingSound + +Called during entity generation for a frame +Include velocity in case I get around to doing doppler... +================== +*/ +void S_Base_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfxHandle ) { + sfx_t *sfx; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if ( sfxHandle < 0 || sfxHandle >= s_numSfx ) { + Com_Printf( S_COLOR_YELLOW "S_AddRealLoopingSound: handle %i out of range\n", sfxHandle ); + return; + } + + sfx = &s_knownSfx[ sfxHandle ]; + + if (sfx->inMemory == qfalse) { + S_memoryLoad(sfx); + } + + if ( !sfx->soundLength ) { + Com_Error( ERR_DROP, "%s has length 0", sfx->soundName ); + } + VectorCopy( origin, loopSounds[entityNum].origin ); + VectorCopy( velocity, loopSounds[entityNum].velocity ); + loopSounds[entityNum].sfx = sfx; + loopSounds[entityNum].active = qtrue; + loopSounds[entityNum].kill = qfalse; + loopSounds[entityNum].doppler = qfalse; +} + + + +/* +================== +S_AddLoopSounds + +Spatialize all of the looping sounds. +All sounds are on the same cycle, so any duplicates can just +sum up the channel multipliers. +================== +*/ +void S_AddLoopSounds (void) { + int i, j, time; + int left_total, right_total, left, right; + channel_t *ch; + loopSound_t *loop, *loop2; + static int loopFrame; + + + numLoopChannels = 0; + + time = Com_Milliseconds(); + + loopFrame++; + for ( i = 0 ; i < MAX_GENTITIES ; i++) { + loop = &loopSounds[i]; + if ( !loop->active || loop->mergeFrame == loopFrame ) { + continue; // already merged into an earlier sound + } + + if (loop->kill) { + S_SpatializeOrigin( loop->origin, 127, &left_total, &right_total); // 3d + } else { + S_SpatializeOrigin( loop->origin, 90, &left_total, &right_total); // sphere + } + + loop->sfx->lastTimeUsed = time; + + for (j=(i+1); j< MAX_GENTITIES ; j++) { + loop2 = &loopSounds[j]; + if ( !loop2->active || loop2->doppler || loop2->sfx != loop->sfx) { + continue; + } + loop2->mergeFrame = loopFrame; + + if (loop2->kill) { + S_SpatializeOrigin( loop2->origin, 127, &left, &right); // 3d + } else { + S_SpatializeOrigin( loop2->origin, 90, &left, &right); // sphere + } + + loop2->sfx->lastTimeUsed = time; + left_total += left; + right_total += right; + } + if (left_total == 0 && right_total == 0) { + continue; // not audible + } + + // allocate a channel + ch = &loop_channels[numLoopChannels]; + + if (left_total > 255) { + left_total = 255; + } + if (right_total > 255) { + right_total = 255; + } + + ch->master_vol = 127; + ch->leftvol = left_total; + ch->rightvol = right_total; + ch->thesfx = loop->sfx; + ch->doppler = loop->doppler; + ch->dopplerScale = loop->dopplerScale; + ch->oldDopplerScale = loop->oldDopplerScale; + numLoopChannels++; + if (numLoopChannels == MAX_CHANNELS) { + return; + } + } +} + +//============================================================================= + +/* +================= +S_ByteSwapRawSamples + +If raw data has been loaded in little endien binary form, this must be done. +If raw data was calculated, as with ADPCM, this should not be called. +================= +*/ +void S_ByteSwapRawSamples( int samples, int width, int s_channels, const byte *data ) { + int i; + + if ( width != 2 ) { + return; + } + if ( LittleShort( 256 ) == 256 ) { + return; + } + + if ( s_channels == 2 ) { + samples <<= 1; + } + for ( i = 0 ; i < samples ; i++ ) { + ((short *)data)[i] = LittleShort( ((short *)data)[i] ); + } +} + +/* +============ +S_Base_RawSamples + +Music streaming +============ +*/ +void S_Base_RawSamples( int stream, int samples, int rate, int width, int s_channels, const byte *data, float volume, int entityNum) +{ + int i; + int src, dst; + float scale; + int intVolume; + portable_samplepair_t *rawsamples; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + if(entityNum >= 0) + { + // FIXME: support spatialized raw streams, e.g. for VoIP + return; + } + + if ( (stream < 0) || (stream >= MAX_RAW_STREAMS) ) { + return; + } + + rawsamples = s_rawsamples[stream]; + + if(s_muted->integer) + intVolume = 0; + else + intVolume = 256 * volume * s_volume->value; + + if ( s_rawend[stream] < s_soundtime ) { + Com_DPrintf( "S_Base_RawSamples: resetting minimum: %i < %i\n", s_rawend[stream], s_soundtime ); + s_rawend[stream] = s_soundtime; + } + + scale = (float)rate / dma.speed; + +//Com_Printf ("%i < %i < %i\n", s_soundtime, s_paintedtime, s_rawend[stream]); + if (s_channels == 2 && width == 2) + { + if (scale == 1.0) + { // optimized case + for (i=0 ; i= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src*2] * intVolume; + rawsamples[dst].right = ((short *)data)[src*2+1] * intVolume; + } + } + } + else if (s_channels == 1 && width == 2) + { + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((short *)data)[src] * intVolume; + rawsamples[dst].right = ((short *)data)[src] * intVolume; + } + } + else if (s_channels == 2 && width == 1) + { + intVolume *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = ((char *)data)[src*2] * intVolume; + rawsamples[dst].right = ((char *)data)[src*2+1] * intVolume; + } + } + else if (s_channels == 1 && width == 1) + { + intVolume *= 256; + + for (i=0 ; ; i++) + { + src = i*scale; + if (src >= samples) + break; + dst = s_rawend[stream]&(MAX_RAW_SAMPLES-1); + s_rawend[stream]++; + rawsamples[dst].left = (((byte *)data)[src]-128) * intVolume; + rawsamples[dst].right = (((byte *)data)[src]-128) * intVolume; + } + } + + if ( s_rawend[stream] > s_soundtime + MAX_RAW_SAMPLES ) { + Com_DPrintf( "S_Base_RawSamples: overflowed %i > %i\n", s_rawend[stream], s_soundtime ); + } +} + +//============================================================================= + +/* +===================== +S_UpdateEntityPosition + +let the sound system know where an entity currently is +====================== +*/ +void S_Base_UpdateEntityPosition( int entityNum, const vec3_t origin ) { + if ( entityNum < 0 || entityNum > MAX_GENTITIES ) { + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + } + VectorCopy( origin, loopSounds[entityNum].origin ); +} + + +/* +============ +S_Respatialize + +Change the volumes of all the playing sounds for changes in their positions +============ +*/ +void S_Base_Respatialize( int entityNum, const vec3_t head, vec3_t axis[3], int inwater ) { + int i; + channel_t *ch; + vec3_t origin; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + listener_number = entityNum; + VectorCopy(head, listener_origin); + VectorCopy(axis[0], listener_axis[0]); + VectorCopy(axis[1], listener_axis[1]); + VectorCopy(axis[2], listener_axis[2]); + + // update spatialization for dynamic sounds + ch = s_channels; + for ( i = 0 ; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx ) { + continue; + } + // anything coming from the view entity will always be full volume + if (ch->entnum == listener_number) { + ch->leftvol = ch->master_vol; + ch->rightvol = ch->master_vol; + } else { + if (ch->fixed_origin) { + VectorCopy( ch->origin, origin ); + } else { + VectorCopy( loopSounds[ ch->entnum ].origin, origin ); + } + + S_SpatializeOrigin (origin, ch->master_vol, &ch->leftvol, &ch->rightvol); + } + } + + // add loopsounds + S_AddLoopSounds (); +} + + +/* +======================== +S_ScanChannelStarts + +Returns qtrue if any new sounds were started since the last mix +======================== +*/ +qboolean S_ScanChannelStarts( void ) { + channel_t *ch; + int i; + qboolean newSamples; + + newSamples = qfalse; + ch = s_channels; + + for (i=0; ithesfx ) { + continue; + } + // if this channel was just started this frame, + // set the sample count to it begins mixing + // into the very first sample + if ( ch->startSample == START_SAMPLE_IMMEDIATE ) { + ch->startSample = s_paintedtime; + newSamples = qtrue; + continue; + } + + // if it is completely finished by now, clear it + if ( ch->startSample + (ch->thesfx->soundLength) <= s_paintedtime ) { + S_ChannelFree(ch); + } + } + + return newSamples; +} + +/* +============ +S_Update + +Called once each time through the main loop +============ +*/ +void S_Base_Update( void ) { + int i; + int total; + channel_t *ch; + + if ( !s_soundStarted || s_soundMuted ) { +// Com_DPrintf ("not started or muted\n"); + return; + } + + // + // debugging output + // + if ( s_show->integer == 2 ) { + total = 0; + ch = s_channels; + for (i=0 ; ithesfx && (ch->leftvol || ch->rightvol) ) { + Com_Printf ("%d %d %s\n", ch->leftvol, ch->rightvol, ch->thesfx->soundName); + total++; + } + } + + Com_Printf ("----(%i)---- painted: %i\n", total, s_paintedtime); + } + + // add raw data from streamed samples + S_UpdateBackgroundTrack(); + + // mix some sound + S_Update_(); +} + +void S_GetSoundtime(void) +{ + int samplepos; + static int buffers; + static int oldsamplepos; + int fullsamples; + + fullsamples = dma.samples / dma.channels; + + if( CL_VideoRecording( ) ) + { + s_soundtime += (int)ceil( dma.speed / cl_aviFrameRate->value ); + return; + } + + // it is possible to miscount buffers if it has wrapped twice between + // calls to S_Update. Oh well. + samplepos = SNDDMA_GetDMAPos(); + if (samplepos < oldsamplepos) + { + buffers++; // buffer wrapped + + if (s_paintedtime > 0x40000000) + { // time to chop things off to avoid 32 bit limits + buffers = 0; + s_paintedtime = fullsamples; + S_Base_StopAllSounds (); + } + } + oldsamplepos = samplepos; + + s_soundtime = buffers*fullsamples + samplepos/dma.channels; + +#if 0 +// check to make sure that we haven't overshot + if (s_paintedtime < s_soundtime) + { + Com_DPrintf ("S_Update_ : overflow\n"); + s_paintedtime = s_soundtime; + } +#endif + + if ( dma.submission_chunk < 256 ) { + s_paintedtime = s_soundtime + s_mixPreStep->value * dma.speed; + } else { + s_paintedtime = s_soundtime + dma.submission_chunk; + } +} + + +void S_Update_(void) { + unsigned endtime; + int samps; + static float lastTime = 0.0f; + float ma, op; + float thisTime, sane; + static int ot = -1; + + if ( !s_soundStarted || s_soundMuted ) { + return; + } + + thisTime = Com_Milliseconds(); + + // Updates s_soundtime + S_GetSoundtime(); + + if (s_soundtime == ot) { + return; + } + ot = s_soundtime; + + // clear any sound effects that end before the current time, + // and start any new sounds + S_ScanChannelStarts(); + + sane = thisTime - lastTime; + if (sane<11) { + sane = 11; // 85hz + } + + ma = s_mixahead->value * dma.speed; + op = s_mixPreStep->value + sane*dma.speed*0.01; + + if (op < ma) { + ma = op; + } + + // mix ahead of current position + endtime = s_soundtime + ma; + + // mix to an even submission block size + endtime = (endtime + dma.submission_chunk-1) + & ~(dma.submission_chunk-1); + + // never mix more than the complete buffer + samps = dma.samples >> (dma.channels-1); + if (endtime - s_soundtime > samps) + endtime = s_soundtime + samps; + + + + SNDDMA_BeginPainting (); + + S_PaintChannels (endtime); + + SNDDMA_Submit (); + + lastTime = thisTime; +} + + + +/* +=============================================================================== + +background music functions + +=============================================================================== +*/ + +/* +====================== +S_StopBackgroundTrack +====================== +*/ +void S_Base_StopBackgroundTrack( void ) { + if(!s_backgroundStream) + return; + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + s_rawend[0] = 0; +} + +/* +====================== +S_StartBackgroundTrack +====================== +*/ +void S_Base_StartBackgroundTrack( const char *intro, const char *loop ){ + if ( !intro ) { + intro = ""; + } + if ( !loop || !loop[0] ) { + loop = intro; + } + Com_DPrintf( "S_StartBackgroundTrack( %s, %s )\n", intro, loop ); + + if(!*intro) + { + S_Base_StopBackgroundTrack(); + return; + } + + if( !loop ) { + s_backgroundLoop[0] = 0; + } else { + Q_strncpyz( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); + } + + // close the background track, but DON'T reset s_rawend + // if restarting the same back ground track + if(s_backgroundStream) + { + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + } + + // Open stream + s_backgroundStream = S_CodecOpenStream(intro); + if(!s_backgroundStream) { + Com_Printf( S_COLOR_YELLOW "WARNING: couldn't open music file %s\n", intro ); + return; + } + + if(s_backgroundStream->info.channels != 2 || s_backgroundStream->info.rate != 22050) { + Com_Printf(S_COLOR_YELLOW "WARNING: music file %s is not 22k stereo\n", intro ); + } +} + +/* +====================== +S_UpdateBackgroundTrack +====================== +*/ +void S_UpdateBackgroundTrack( void ) { + int bufferSamples; + int fileSamples; + byte raw[30000]; // just enough to fit in a mac stack frame + int fileBytes; + int r; + + if(!s_backgroundStream) { + return; + } + + // don't bother playing anything if musicvolume is 0 + if ( s_musicVolume->value <= 0 ) { + return; + } + + // see how many samples should be copied into the raw buffer + if ( s_rawend[0] < s_soundtime ) { + s_rawend[0] = s_soundtime; + } + + while ( s_rawend[0] < s_soundtime + MAX_RAW_SAMPLES ) { + bufferSamples = MAX_RAW_SAMPLES - (s_rawend[0] - s_soundtime); + + // decide how much data needs to be read from the file + fileSamples = bufferSamples * s_backgroundStream->info.rate / dma.speed; + + if (!fileSamples) + return; + + // our max buffer size + fileBytes = fileSamples * (s_backgroundStream->info.width * s_backgroundStream->info.channels); + if ( fileBytes > sizeof(raw) ) { + fileBytes = sizeof(raw); + fileSamples = fileBytes / (s_backgroundStream->info.width * s_backgroundStream->info.channels); + } + + // Read + r = S_CodecReadStream(s_backgroundStream, fileBytes, raw); + if(r < fileBytes) + { + fileBytes = r; + fileSamples = r / (s_backgroundStream->info.width * s_backgroundStream->info.channels); + } + + if(r > 0) + { + // add to raw buffer + S_Base_RawSamples(0, fileSamples, s_backgroundStream->info.rate, + s_backgroundStream->info.width, s_backgroundStream->info.channels, raw, s_musicVolume->value, -1); + } + else + { + // loop + if(s_backgroundLoop[0]) + { + S_CodecCloseStream(s_backgroundStream); + s_backgroundStream = NULL; + S_Base_StartBackgroundTrack( s_backgroundLoop, s_backgroundLoop ); + if(!s_backgroundStream) + return; + } + else + { + S_Base_StopBackgroundTrack(); + return; + } + } + + } +} + + +/* +====================== +S_FreeOldestSound +====================== +*/ + +void S_FreeOldestSound( void ) { + int i, oldest, used; + sfx_t *sfx; + sndBuffer *buffer, *nbuffer; + + oldest = Com_Milliseconds(); + used = 0; + + for (i=1 ; i < s_numSfx ; i++) { + sfx = &s_knownSfx[i]; + if (sfx->inMemory && sfx->lastTimeUsedlastTimeUsed; + } + } + + sfx = &s_knownSfx[used]; + + Com_DPrintf("S_FreeOldestSound: freeing sound %s\n", sfx->soundName); + + buffer = sfx->soundData; + while(buffer != NULL) { + nbuffer = buffer->next; + SND_free(buffer); + buffer = nbuffer; + } + sfx->inMemory = qfalse; + sfx->soundData = NULL; +} + +// ======================================================================= +// Shutdown sound engine +// ======================================================================= + +void S_Base_Shutdown( void ) { + if ( !s_soundStarted ) { + return; + } + + SNDDMA_Shutdown(); + SND_shutdown(); + + s_soundStarted = 0; + s_numSfx = 0; + + Cmd_RemoveCommand("s_info"); +} + +/* +================ +S_Init +================ +*/ +qboolean S_Base_Init( soundInterface_t *si ) { + qboolean r; + + if( !si ) { + return qfalse; + } + + s_khz = Cvar_Get ("s_khz", "22", CVAR_ARCHIVE); + s_mixahead = Cvar_Get ("s_mixahead", "0.2", CVAR_ARCHIVE); + s_mixPreStep = Cvar_Get ("s_mixPreStep", "0.05", CVAR_ARCHIVE); + s_show = Cvar_Get ("s_show", "0", CVAR_CHEAT); + s_testsound = Cvar_Get ("s_testsound", "0", CVAR_CHEAT); + + r = SNDDMA_Init(); + + if ( r ) { + s_soundStarted = 1; + s_soundMuted = 1; +// s_numSfx = 0; + + Com_Memset(sfxHash, 0, sizeof(sfx_t *)*LOOP_HASH); + + s_soundtime = 0; + s_paintedtime = 0; + + S_Base_StopAllSounds( ); + } else { + return qfalse; + } + + si->Shutdown = S_Base_Shutdown; + si->StartSound = S_Base_StartSound; + si->StartLocalSound = S_Base_StartLocalSound; + si->StartBackgroundTrack = S_Base_StartBackgroundTrack; + si->StopBackgroundTrack = S_Base_StopBackgroundTrack; + si->RawSamples = S_Base_RawSamples; + si->StopAllSounds = S_Base_StopAllSounds; + si->ClearLoopingSounds = S_Base_ClearLoopingSounds; + si->AddLoopingSound = S_Base_AddLoopingSound; + si->AddRealLoopingSound = S_Base_AddRealLoopingSound; + si->StopLoopingSound = S_Base_StopLoopingSound; + si->Respatialize = S_Base_Respatialize; + si->UpdateEntityPosition = S_Base_UpdateEntityPosition; + si->Update = S_Base_Update; + si->DisableSounds = S_Base_DisableSounds; + si->BeginRegistration = S_Base_BeginRegistration; + si->RegisterSound = S_Base_RegisterSound; + si->ClearSoundBuffer = S_Base_ClearSoundBuffer; + si->SoundInfo = S_Base_SoundInfo; + si->SoundList = S_Base_SoundList; + +#ifdef USE_VOIP + si->StartCapture = S_Base_StartCapture; + si->AvailableCaptureSamples = S_Base_AvailableCaptureSamples; + si->Capture = S_Base_Capture; + si->StopCapture = S_Base_StopCapture; + si->MasterGain = S_Base_MasterGain; +#endif + + return qtrue; +} diff --git a/code/client/snd_local.h b/code/client/snd_local.h new file mode 100644 index 0000000..29783c1 --- /dev/null +++ b/code/client/snd_local.h @@ -0,0 +1,252 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// snd_local.h -- private sound definations + + +#include "../qcommon/q_shared.h" +#include "../qcommon/qcommon.h" +#include "snd_public.h" + +#define PAINTBUFFER_SIZE 4096 // this is in samples + +#define SND_CHUNK_SIZE 1024 // samples +#define SND_CHUNK_SIZE_FLOAT (SND_CHUNK_SIZE/2) // floats +#define SND_CHUNK_SIZE_BYTE (SND_CHUNK_SIZE*2) // floats + +typedef struct { + int left; // the final values will be clamped to +/- 0x00ffff00 and shifted down + int right; +} portable_samplepair_t; + +typedef struct adpcm_state { + short sample; /* Previous output value */ + char index; /* Index into stepsize table */ +} adpcm_state_t; + +typedef struct sndBuffer_s { + short sndChunk[SND_CHUNK_SIZE]; + struct sndBuffer_s *next; + int size; + adpcm_state_t adpcm; +} sndBuffer; + +typedef struct sfx_s { + sndBuffer *soundData; + qboolean defaultSound; // couldn't be loaded, so use buzz + qboolean inMemory; // not in Memory + qboolean soundCompressed; // not in Memory + int soundCompressionMethod; + int soundLength; + char soundName[MAX_QPATH]; + int lastTimeUsed; + struct sfx_s *next; +} sfx_t; + +typedef struct { + int channels; + int samples; // mono samples in buffer + int submission_chunk; // don't mix less than this # + int samplebits; + int speed; + byte *buffer; +} dma_t; + +#define START_SAMPLE_IMMEDIATE 0x7fffffff + +#define MAX_DOPPLER_SCALE 50.0f //arbitrary + +typedef struct loopSound_s { + vec3_t origin; + vec3_t velocity; + sfx_t *sfx; + int mergeFrame; + qboolean active; + qboolean kill; + qboolean doppler; + float dopplerScale; + float oldDopplerScale; + int framenum; +} loopSound_t; + +typedef struct +{ + int allocTime; + int startSample; // START_SAMPLE_IMMEDIATE = set immediately on next mix + int entnum; // to allow overriding a specific sound + int entchannel; // to allow overriding a specific sound + int leftvol; // 0-255 volume after spatialization + int rightvol; // 0-255 volume after spatialization + int master_vol; // 0-255 volume before spatialization + float dopplerScale; + float oldDopplerScale; + vec3_t origin; // only use if fixed_origin is set + qboolean fixed_origin; // use origin instead of fetching entnum's origin + sfx_t *thesfx; // sfx structure + qboolean doppler; +} channel_t; + + +#define WAV_FORMAT_PCM 1 + + +typedef struct { + int format; + int rate; + int width; + int channels; + int samples; + int dataofs; // chunk starts this many bytes from file start +} wavinfo_t; + +// Interface between Q3 sound "api" and the sound backend +typedef struct +{ + void (*Shutdown)(void); + void (*StartSound)( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); + void (*StartLocalSound)( sfxHandle_t sfx, int channelNum ); + void (*StartBackgroundTrack)( const char *intro, const char *loop ); + void (*StopBackgroundTrack)( void ); + void (*RawSamples)(int stream, int samples, int rate, int width, int channels, const byte *data, float volume, int entityNum); + void (*StopAllSounds)( void ); + void (*ClearLoopingSounds)( qboolean killall ); + void (*AddLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); + void (*AddRealLoopingSound)( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); + void (*StopLoopingSound)(int entityNum ); + void (*Respatialize)( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + void (*UpdateEntityPosition)( int entityNum, const vec3_t origin ); + void (*Update)( void ); + void (*DisableSounds)( void ); + void (*BeginRegistration)( void ); + sfxHandle_t (*RegisterSound)( const char *sample, qboolean compressed ); + void (*ClearSoundBuffer)( void ); + void (*SoundInfo)( void ); + void (*SoundList)( void ); +#ifdef USE_VOIP + void (*StartCapture)( void ); + int (*AvailableCaptureSamples)( void ); + void (*Capture)( int samples, byte *data ); + void (*StopCapture)( void ); + void (*MasterGain)( float gain ); +#endif +} soundInterface_t; + + +/* +==================================================================== + + SYSTEM SPECIFIC FUNCTIONS + +==================================================================== +*/ + +// initializes cycling through a DMA buffer and returns information on it +qboolean SNDDMA_Init(void); + +// gets the current DMA position +int SNDDMA_GetDMAPos(void); + +// shutdown the DMA xfer. +void SNDDMA_Shutdown(void); + +void SNDDMA_BeginPainting (void); + +void SNDDMA_Submit(void); + +//==================================================================== + +#define MAX_CHANNELS 96 + +extern channel_t s_channels[MAX_CHANNELS]; +extern channel_t loop_channels[MAX_CHANNELS]; +extern int numLoopChannels; + +extern int s_paintedtime; +extern vec3_t listener_forward; +extern vec3_t listener_right; +extern vec3_t listener_up; +extern dma_t dma; + +#define MAX_RAW_SAMPLES 16384 +#define MAX_RAW_STREAMS (MAX_CLIENTS * 2 + 1) +extern portable_samplepair_t s_rawsamples[MAX_RAW_STREAMS][MAX_RAW_SAMPLES]; +extern int s_rawend[MAX_RAW_STREAMS]; + +extern cvar_t *s_volume; +extern cvar_t *s_musicVolume; +extern cvar_t *s_muted; +extern cvar_t *s_doppler; + +extern cvar_t *s_testsound; + +qboolean S_LoadSound( sfx_t *sfx ); + +void SND_free(sndBuffer *v); +sndBuffer* SND_malloc( void ); +void SND_setup( void ); +void SND_shutdown(void); + +void S_PaintChannels(int endtime); + +void S_memoryLoad(sfx_t *sfx); + +// spatializes a channel +void S_Spatialize(channel_t *ch); + +// adpcm functions +int S_AdpcmMemoryNeeded( const wavinfo_t *info ); +void S_AdpcmEncodeSound( sfx_t *sfx, short *samples ); +void S_AdpcmGetSamples(sndBuffer *chunk, short *to); + +// wavelet function + +#define SENTINEL_MULAW_ZERO_RUN 127 +#define SENTINEL_MULAW_FOUR_BIT_RUN 126 + +void S_FreeOldestSound( void ); + +#define NXStream byte + +void encodeWavelet(sfx_t *sfx, short *packets); +void decodeWavelet( sndBuffer *stream, short *packets); + +void encodeMuLaw( sfx_t *sfx, short *packets); +extern short mulawToShort[256]; + +extern short *sfxScratchBuffer; +extern sfx_t *sfxScratchPointer; +extern int sfxScratchIndex; + +qboolean S_Base_Init( soundInterface_t *si ); + +// OpenAL stuff +typedef enum +{ + SRCPRI_AMBIENT = 0, // Ambient sound effects + SRCPRI_ENTITY, // Entity sound effects + SRCPRI_ONESHOT, // One-shot sounds + SRCPRI_LOCAL, // Local sounds + SRCPRI_STREAM // Streams (music, cutscenes) +} alSrcPriority_t; + +typedef int srcHandle_t; + +qboolean S_AL_Init( soundInterface_t *si ); diff --git a/code/client/snd_main.c b/code/client/snd_main.c new file mode 100644 index 0000000..08c3dd0 --- /dev/null +++ b/code/client/snd_main.c @@ -0,0 +1,568 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "client.h" +#include "snd_codec.h" +#include "snd_local.h" +#include "snd_public.h" + +cvar_t *s_volume; +cvar_t *s_muted; +cvar_t *s_musicVolume; +cvar_t *s_doppler; +cvar_t *s_backend; +cvar_t *s_muteWhenMinimized; +cvar_t *s_muteWhenUnfocused; + +static soundInterface_t si; + +/* +================= +S_ValidateInterface +================= +*/ +static qboolean S_ValidSoundInterface( soundInterface_t *si ) +{ + if( !si->Shutdown ) return qfalse; + if( !si->StartSound ) return qfalse; + if( !si->StartLocalSound ) return qfalse; + if( !si->StartBackgroundTrack ) return qfalse; + if( !si->StopBackgroundTrack ) return qfalse; + if( !si->RawSamples ) return qfalse; + if( !si->StopAllSounds ) return qfalse; + if( !si->ClearLoopingSounds ) return qfalse; + if( !si->AddLoopingSound ) return qfalse; + if( !si->AddRealLoopingSound ) return qfalse; + if( !si->StopLoopingSound ) return qfalse; + if( !si->Respatialize ) return qfalse; + if( !si->UpdateEntityPosition ) return qfalse; + if( !si->Update ) return qfalse; + if( !si->DisableSounds ) return qfalse; + if( !si->BeginRegistration ) return qfalse; + if( !si->RegisterSound ) return qfalse; + if( !si->ClearSoundBuffer ) return qfalse; + if( !si->SoundInfo ) return qfalse; + if( !si->SoundList ) return qfalse; + +#ifdef USE_VOIP + if( !si->StartCapture ) return qfalse; + if( !si->AvailableCaptureSamples ) return qfalse; + if( !si->Capture ) return qfalse; + if( !si->StopCapture ) return qfalse; + if( !si->MasterGain ) return qfalse; +#endif + + return qtrue; +} + +/* +================= +S_StartSound +================= +*/ +void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ) +{ + if( si.StartSound ) { + si.StartSound( origin, entnum, entchannel, sfx ); + } +} + +/* +================= +S_StartLocalSound +================= +*/ +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ) +{ + if( si.StartLocalSound ) { + si.StartLocalSound( sfx, channelNum ); + } +} + +/* +================= +S_StartBackgroundTrack +================= +*/ +void S_StartBackgroundTrack( const char *intro, const char *loop ) +{ + if( si.StartBackgroundTrack ) { + si.StartBackgroundTrack( intro, loop ); + } +} + +/* +================= +S_StopBackgroundTrack +================= +*/ +void S_StopBackgroundTrack( void ) +{ + if( si.StopBackgroundTrack ) { + si.StopBackgroundTrack( ); + } +} + +/* +================= +S_RawSamples +================= +*/ +void S_RawSamples (int stream, int samples, int rate, int width, int channels, + const byte *data, float volume, int entityNum) +{ + if(si.RawSamples) + si.RawSamples(stream, samples, rate, width, channels, data, volume, entityNum); +} + +/* +================= +S_StopAllSounds +================= +*/ +void S_StopAllSounds( void ) +{ + if( si.StopAllSounds ) { + si.StopAllSounds( ); + } +} + +/* +================= +S_ClearLoopingSounds +================= +*/ +void S_ClearLoopingSounds( qboolean killall ) +{ + if( si.ClearLoopingSounds ) { + si.ClearLoopingSounds( killall ); + } +} + +/* +================= +S_AddLoopingSound +================= +*/ +void S_AddLoopingSound( int entityNum, const vec3_t origin, + const vec3_t velocity, sfxHandle_t sfx ) +{ + if( si.AddLoopingSound ) { + si.AddLoopingSound( entityNum, origin, velocity, sfx ); + } +} + +/* +================= +S_AddRealLoopingSound +================= +*/ +void S_AddRealLoopingSound( int entityNum, const vec3_t origin, + const vec3_t velocity, sfxHandle_t sfx ) +{ + if( si.AddRealLoopingSound ) { + si.AddRealLoopingSound( entityNum, origin, velocity, sfx ); + } +} + +/* +================= +S_StopLoopingSound +================= +*/ +void S_StopLoopingSound( int entityNum ) +{ + if( si.StopLoopingSound ) { + si.StopLoopingSound( entityNum ); + } +} + +/* +================= +S_Respatialize +================= +*/ +void S_Respatialize( int entityNum, const vec3_t origin, + vec3_t axis[3], int inwater ) +{ + if( si.Respatialize ) { + si.Respatialize( entityNum, origin, axis, inwater ); + } +} + +/* +================= +S_UpdateEntityPosition +================= +*/ +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + if( si.UpdateEntityPosition ) { + si.UpdateEntityPosition( entityNum, origin ); + } +} + +/* +================= +S_Update +================= +*/ +void S_Update( void ) +{ + if(s_muted->integer) + { + if(!(s_muteWhenMinimized->integer && com_minimized->integer) && + !(s_muteWhenUnfocused->integer && com_unfocused->integer)) + { + s_muted->integer = qfalse; + s_muted->modified = qtrue; + } + } + else + { + if((s_muteWhenMinimized->integer && com_minimized->integer) || + (s_muteWhenUnfocused->integer && com_unfocused->integer)) + { + s_muted->integer = qtrue; + s_muted->modified = qtrue; + } + } + + if( si.Update ) { + si.Update( ); + } +} + +/* +================= +S_DisableSounds +================= +*/ +void S_DisableSounds( void ) +{ + if( si.DisableSounds ) { + si.DisableSounds( ); + } +} + +/* +================= +S_BeginRegistration +================= +*/ +void S_BeginRegistration( void ) +{ + if( si.BeginRegistration ) { + si.BeginRegistration( ); + } +} + +/* +================= +S_RegisterSound +================= +*/ +sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ) +{ + if( si.RegisterSound ) { + return si.RegisterSound( sample, compressed ); + } else { + return 0; + } +} + +/* +================= +S_ClearSoundBuffer +================= +*/ +void S_ClearSoundBuffer( void ) +{ + if( si.ClearSoundBuffer ) { + si.ClearSoundBuffer( ); + } +} + +/* +================= +S_SoundInfo +================= +*/ +void S_SoundInfo( void ) +{ + if( si.SoundInfo ) { + si.SoundInfo( ); + } +} + +/* +================= +S_SoundList +================= +*/ +void S_SoundList( void ) +{ + if( si.SoundList ) { + si.SoundList( ); + } +} + + +#ifdef USE_VOIP +/* +================= +S_StartCapture +================= +*/ +void S_StartCapture( void ) +{ + if( si.StartCapture ) { + si.StartCapture( ); + } +} + +/* +================= +S_AvailableCaptureSamples +================= +*/ +int S_AvailableCaptureSamples( void ) +{ + if( si.AvailableCaptureSamples ) { + return si.AvailableCaptureSamples( ); + } + return 0; +} + +/* +================= +S_Capture +================= +*/ +void S_Capture( int samples, byte *data ) +{ + if( si.Capture ) { + si.Capture( samples, data ); + } +} + +/* +================= +S_StopCapture +================= +*/ +void S_StopCapture( void ) +{ + if( si.StopCapture ) { + si.StopCapture( ); + } +} + +/* +================= +S_MasterGain +================= +*/ +void S_MasterGain( float gain ) +{ + if( si.MasterGain ) { + si.MasterGain( gain ); + } +} +#endif + +//============================================================================= + +/* +================== +S_CompleteSoundName +================== +*/ +static void S_CompleteSoundName( char *args, int argNum ) { + if( argNum >= 2 ) { + Field_CompleteFilename( "sound", "", qfalse, qfalse ); + } +} +/* +================= +S_Play_f +================= +*/ +void S_Play_f( void ) { + int i; + sfxHandle_t h; + char name[256]; + + if( !si.RegisterSound || !si.StartLocalSound ) { + return; + } + + i = 1; + while ( i [loopfile]\n"); + return; + } + +} + +/* +================= +S_StopMusic_f +================= +*/ +void S_StopMusic_f( void ) +{ + if(!si.StopBackgroundTrack) + return; + + si.StopBackgroundTrack(); +} + + +//============================================================================= + +/* +================= +S_Init +================= +*/ +void S_Init( void ) +{ + cvar_t *cv; + qboolean started = qfalse; + + Com_Printf( "------ Initializing Sound ------\n" ); + + s_volume = Cvar_Get( "s_volume", "0.8", CVAR_ARCHIVE ); + s_musicVolume = Cvar_Get( "s_musicvolume", "0.25", CVAR_ARCHIVE ); + s_muted = Cvar_Get("s_muted", "0", CVAR_ROM); + s_doppler = Cvar_Get( "s_doppler", "1", CVAR_ARCHIVE ); + s_backend = Cvar_Get( "s_backend", "", CVAR_ROM ); + s_muteWhenMinimized = Cvar_Get( "s_muteWhenMinimized", "0", CVAR_ARCHIVE ); + s_muteWhenUnfocused = Cvar_Get( "s_muteWhenUnfocused", "0", CVAR_ARCHIVE ); + + cv = Cvar_Get( "s_initsound", "1", 0 ); + if( !cv->integer ) { + Com_Printf( "Sound disabled.\n" ); + } else { + + S_CodecInit( ); + + Cmd_AddCommand( "play", S_Play_f ); + Cmd_SetCommandCompletionFunc( "play", S_CompleteSoundName ); + Cmd_AddCommand( "music", S_Music_f ); + Cmd_SetCommandCompletionFunc( "music", S_CompleteMusicName ); + Cmd_AddCommand( "stopmusic", S_StopMusic_f ); + Cmd_AddCommand( "s_list", S_SoundList ); + Cmd_AddCommand( "s_stop", S_StopAllSounds ); + Cmd_AddCommand( "s_info", S_SoundInfo ); + + cv = Cvar_Get( "s_useOpenAL", "1", CVAR_ARCHIVE ); + if( cv->integer ) { + //OpenAL + started = S_AL_Init( &si ); + Cvar_Set( "s_backend", "OpenAL" ); + } + + if( !started ) { + started = S_Base_Init( &si ); + Cvar_Set( "s_backend", "base" ); + } + + if( started ) { + if( !S_ValidSoundInterface( &si ) ) { + Com_Error( ERR_FATAL, "Sound interface invalid" ); + } + + S_SoundInfo( ); + Com_Printf( "Sound initialization successful.\n" ); + } else { + Com_Printf( "Sound initialization failed.\n" ); + } + } + + Com_Printf( "--------------------------------\n"); +} + +/* +================= +S_Shutdown +================= +*/ +void S_Shutdown( void ) +{ + if( si.Shutdown ) { + si.Shutdown( ); + } + + Com_Memset( &si, 0, sizeof( soundInterface_t ) ); + + Cmd_RemoveCommand( "play" ); + Cmd_RemoveCommand( "music"); + Cmd_RemoveCommand( "stopmusic"); + Cmd_RemoveCommand( "s_list" ); + Cmd_RemoveCommand( "s_stop" ); + Cmd_RemoveCommand( "s_info" ); + + S_CodecShutdown( ); +} + diff --git a/code/client/snd_mem.c b/code/client/snd_mem.c new file mode 100644 index 0000000..ee40a75 --- /dev/null +++ b/code/client/snd_mem.c @@ -0,0 +1,271 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +/***************************************************************************** + * name: snd_mem.c + * + * desc: sound caching + * + * $Archive: /MissionPack/code/client/snd_mem.c $ + * + *****************************************************************************/ + +#include "snd_local.h" +#include "snd_codec.h" + +#define DEF_COMSOUNDMEGS "8" + +/* +=============================================================================== + +memory management + +=============================================================================== +*/ + +static sndBuffer *buffer = NULL; +static sndBuffer *freelist = NULL; +static int inUse = 0; +static int totalInUse = 0; + +short *sfxScratchBuffer = NULL; +sfx_t *sfxScratchPointer = NULL; +int sfxScratchIndex = 0; + +void SND_free(sndBuffer *v) { + *(sndBuffer **)v = freelist; + freelist = (sndBuffer*)v; + inUse += sizeof(sndBuffer); +} + +sndBuffer* SND_malloc(void) { + sndBuffer *v; +redo: + if (freelist == NULL) { + S_FreeOldestSound(); + goto redo; + } + + inUse -= sizeof(sndBuffer); + totalInUse += sizeof(sndBuffer); + + v = freelist; + freelist = *(sndBuffer **)freelist; + v->next = NULL; + return v; +} + +void SND_setup(void) { + sndBuffer *p, *q; + cvar_t *cv; + int scs; + + cv = Cvar_Get( "com_soundMegs", DEF_COMSOUNDMEGS, CVAR_LATCH | CVAR_ARCHIVE ); + + scs = (cv->integer*1536); + + buffer = malloc(scs*sizeof(sndBuffer) ); + // allocate the stack based hunk allocator + sfxScratchBuffer = malloc(SND_CHUNK_SIZE * sizeof(short) * 4); //Hunk_Alloc(SND_CHUNK_SIZE * sizeof(short) * 4); + sfxScratchPointer = NULL; + + inUse = scs*sizeof(sndBuffer); + p = buffer;; + q = p + scs; + while (--q > p) + *(sndBuffer **)q = q-1; + + *(sndBuffer **)q = NULL; + freelist = p + scs - 1; + + Com_Printf("Sound memory manager started\n"); +} + +void SND_shutdown(void) +{ + free(sfxScratchBuffer); + free(buffer); +} + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +static void ResampleSfx( sfx_t *sfx, int inrate, int inwidth, byte *data, qboolean compressed ) { + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + int part; + sndBuffer *chunk; + + stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + + outcount = sfx->soundLength / stepscale; + sfx->soundLength = outcount; + + samplefrac = 0; + fracstep = stepscale * 256; + chunk = sfx->soundData; + + for (i=0 ; i> 8; + samplefrac += fracstep; + if( inwidth == 2 ) { + sample = ( ((short *)data)[srcsample] ); + } else { + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + } + part = (i&(SND_CHUNK_SIZE-1)); + if (part == 0) { + sndBuffer *newchunk; + newchunk = SND_malloc(); + if (chunk == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + } + + chunk->sndChunk[part] = sample; + } +} + +/* +================ +ResampleSfx + +resample / decimate to the current source rate +================ +*/ +static int ResampleSfxRaw( short *sfx, int inrate, int inwidth, int samples, byte *data ) { + int outcount; + int srcsample; + float stepscale; + int i; + int sample, samplefrac, fracstep; + + stepscale = (float)inrate / dma.speed; // this is usually 0.5, 1, or 2 + + outcount = samples / stepscale; + + samplefrac = 0; + fracstep = stepscale * 256; + + for (i=0 ; i> 8; + samplefrac += fracstep; + if( inwidth == 2 ) { + sample = LittleShort ( ((short *)data)[srcsample] ); + } else { + sample = (int)( (unsigned char)(data[srcsample]) - 128) << 8; + } + sfx[i] = sample; + } + return outcount; +} + +//============================================================================= + +/* +============== +S_LoadSound + +The filename may be different than sfx->name in the case +of a forced fallback of a player specific sound +============== +*/ +qboolean S_LoadSound( sfx_t *sfx ) +{ + byte *data; + short *samples; + snd_info_t info; +// int size; + + // player specific sounds are never directly loaded + if ( sfx->soundName[0] == '*') { + return qfalse; + } + + // load it in + data = S_CodecLoad(sfx->soundName, &info); + if(!data) + return qfalse; + + if ( info.width == 1 ) { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is a 8 bit wav file\n", sfx->soundName); + } + + if ( info.rate != 22050 ) { + Com_DPrintf(S_COLOR_YELLOW "WARNING: %s is not a 22kHz wav file\n", sfx->soundName); + } + + samples = Hunk_AllocateTempMemory(info.samples * sizeof(short) * 2); + + sfx->lastTimeUsed = Com_Milliseconds()+1; + + // each of these compression schemes works just fine + // but the 16bit quality is much nicer and with a local + // install assured we can rely upon the sound memory + // manager to do the right thing for us and page + // sound in as needed + + if( sfx->soundCompressed == qtrue) { + sfx->soundCompressionMethod = 1; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, data + info.dataofs ); + S_AdpcmEncodeSound(sfx, samples); +#if 0 + } else if (info.samples>(SND_CHUNK_SIZE*16) && info.width >1) { + sfx->soundCompressionMethod = 3; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + encodeMuLaw( sfx, samples); + } else if (info.samples>(SND_CHUNK_SIZE*6400) && info.width >1) { + sfx->soundCompressionMethod = 2; + sfx->soundData = NULL; + sfx->soundLength = ResampleSfxRaw( samples, info.rate, info.width, info.samples, (data + info.dataofs) ); + encodeWavelet( sfx, samples); +#endif + } else { + sfx->soundCompressionMethod = 0; + sfx->soundLength = info.samples; + sfx->soundData = NULL; + ResampleSfx( sfx, info.rate, info.width, data + info.dataofs, qfalse ); + } + + Hunk_FreeTempMemory(samples); + Hunk_FreeTempMemory(data); + + return qtrue; +} + +void S_DisplayFreeMemory(void) { + Com_Printf("%d bytes free sound buffer memory, %d total used\n", inUse, totalInUse); +} diff --git a/code/client/snd_mix.c b/code/client/snd_mix.c new file mode 100644 index 0000000..11de66d --- /dev/null +++ b/code/client/snd_mix.c @@ -0,0 +1,741 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ +// snd_mix.c -- portable code to mix sounds for snd_dma.c + +#include "client.h" +#include "snd_local.h" +#if idppc_altivec && !defined(MACOS_X) +#include +#endif + +static portable_samplepair_t paintbuffer[PAINTBUFFER_SIZE]; +static int snd_vol; + +int* snd_p; +int snd_linear_count; +short* snd_out; + +#if !id386 // if configured not to use asm + +void S_WriteLinearBlastStereo16 (void) +{ + int i; + int val; + + for (i=0 ; i>8; + if (val > 0x7fff) + snd_out[i] = 0x7fff; + else if (val < -32768) + snd_out[i] = -32768; + else + snd_out[i] = val; + + val = snd_p[i+1]>>8; + if (val > 0x7fff) + snd_out[i+1] = 0x7fff; + else if (val < -32768) + snd_out[i+1] = -32768; + else + snd_out[i+1] = val; + } +} +#elif defined(__GNUC__) +// uses snd_mixa.s +void S_WriteLinearBlastStereo16 (void); +#else + +__declspec( naked ) void S_WriteLinearBlastStereo16 (void) +{ + __asm { + + push edi + push ebx + mov ecx,ds:dword ptr[snd_linear_count] + mov ebx,ds:dword ptr[snd_p] + mov edi,ds:dword ptr[snd_out] +LWLBLoopTop: + mov eax,ds:dword ptr[-8+ebx+ecx*4] + sar eax,8 + cmp eax,07FFFh + jg LClampHigh + cmp eax,0FFFF8000h + jnl LClampDone + mov eax,0FFFF8000h + jmp LClampDone +LClampHigh: + mov eax,07FFFh +LClampDone: + mov edx,ds:dword ptr[-4+ebx+ecx*4] + sar edx,8 + cmp edx,07FFFh + jg LClampHigh2 + cmp edx,0FFFF8000h + jnl LClampDone2 + mov edx,0FFFF8000h + jmp LClampDone2 +LClampHigh2: + mov edx,07FFFh +LClampDone2: + shl edx,16 + and eax,0FFFFh + or edx,eax + mov ds:dword ptr[-4+edi+ecx*2],edx + sub ecx,2 + jnz LWLBLoopTop + pop ebx + pop edi + ret + } +} + +#endif + +void S_TransferStereo16 (unsigned long *pbuf, int endtime) +{ + int lpos; + int ls_paintedtime; + + snd_p = (int *) paintbuffer; + ls_paintedtime = s_paintedtime; + + while (ls_paintedtime < endtime) + { + // handle recirculating buffer issues + lpos = ls_paintedtime & ((dma.samples>>1)-1); + + snd_out = (short *) pbuf + (lpos<<1); + + snd_linear_count = (dma.samples>>1) - lpos; + if (ls_paintedtime + snd_linear_count > endtime) + snd_linear_count = endtime - ls_paintedtime; + + snd_linear_count <<= 1; + + // write a linear blast of samples + S_WriteLinearBlastStereo16 (); + + snd_p += snd_linear_count; + ls_paintedtime += (snd_linear_count>>1); + + if( CL_VideoRecording( ) ) + CL_WriteAVIAudioFrame( (byte *)snd_out, snd_linear_count << 1 ); + } +} + +/* +=================== +S_TransferPaintBuffer + +=================== +*/ +void S_TransferPaintBuffer(int endtime) +{ + int out_idx; + int count; + int out_mask; + int *p; + int step; + int val; + unsigned long *pbuf; + + pbuf = (unsigned long *)dma.buffer; + + + if ( s_testsound->integer ) { + int i; + int count; + + // write a fixed sine wave + count = (endtime - s_paintedtime); + for (i=0 ; i> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < -32768) + val = -32768; + out[out_idx] = val; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (dma.samplebits == 8) + { + unsigned char *out = (unsigned char *) pbuf; + while (count--) + { + val = *p >> 8; + p+= step; + if (val > 0x7fff) + val = 0x7fff; + else if (val < -32768) + val = -32768; + out[out_idx] = (val>>8) + 128; + out_idx = (out_idx + 1) & out_mask; + } + } + } +} + + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + +#if idppc_altivec +static void S_PaintChannelFrom16_altivec( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data, aoff, boff; + int leftvol, rightvol; + int i, j; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + float ooff, fdata, fdiv, fleftvol, frightvol; + + samp = &paintbuffer[ bufferOffset ]; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + chunk = sc->soundData; + while (sampleOffset>=SND_CHUNK_SIZE) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler || ch->dopplerScale==1.0f) { + vector signed short volume_vec; + vector unsigned int volume_shift; + int vectorCount, samplesLeft, chunkSamplesLeft; + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + samples = chunk->sndChunk; + ((short *)&volume_vec)[0] = leftvol; + ((short *)&volume_vec)[1] = leftvol; + ((short *)&volume_vec)[4] = leftvol; + ((short *)&volume_vec)[5] = leftvol; + ((short *)&volume_vec)[2] = rightvol; + ((short *)&volume_vec)[3] = rightvol; + ((short *)&volume_vec)[6] = rightvol; + ((short *)&volume_vec)[7] = rightvol; + volume_shift = vec_splat_u32(8); + i = 0; + + while(i < count) { + /* Try to align destination to 16-byte boundary */ + while(i < count && (((unsigned long)&samp[i] & 0x1f) || ((count-i) < 8) || ((SND_CHUNK_SIZE - sampleOffset) < 8))) { + data = samples[sampleOffset++]; + samp[i].left += (data * leftvol)>>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + i++; + } + /* Destination is now aligned. Process as many 8-sample + chunks as we can before we run out of room from the current + sound chunk. We do 8 per loop to avoid extra source data reads. */ + samplesLeft = count - i; + chunkSamplesLeft = SND_CHUNK_SIZE - sampleOffset; + if(samplesLeft > chunkSamplesLeft) + samplesLeft = chunkSamplesLeft; + + vectorCount = samplesLeft / 8; + + if(vectorCount) + { + vector unsigned char tmp; + vector short s0, s1, sampleData0, sampleData1; + vector signed int merge0, merge1; + vector signed int d0, d1, d2, d3; + vector unsigned char samplePermute0 = + VECCONST_UINT8(0, 1, 4, 5, 0, 1, 4, 5, 2, 3, 6, 7, 2, 3, 6, 7); + vector unsigned char samplePermute1 = + VECCONST_UINT8(8, 9, 12, 13, 8, 9, 12, 13, 10, 11, 14, 15, 10, 11, 14, 15); + vector unsigned char loadPermute0, loadPermute1; + + // Rather than permute the vectors after we load them to do the sample + // replication and rearrangement, we permute the alignment vector so + // we do everything in one step below and avoid data shuffling. + tmp = vec_lvsl(0,&samples[sampleOffset]); + loadPermute0 = vec_perm(tmp,tmp,samplePermute0); + loadPermute1 = vec_perm(tmp,tmp,samplePermute1); + + s0 = *(vector short *)&samples[sampleOffset]; + while(vectorCount) + { + /* Load up source (16-bit) sample data */ + s1 = *(vector short *)&samples[sampleOffset+7]; + + /* Load up destination sample data */ + d0 = *(vector signed int *)&samp[i]; + d1 = *(vector signed int *)&samp[i+2]; + d2 = *(vector signed int *)&samp[i+4]; + d3 = *(vector signed int *)&samp[i+6]; + + sampleData0 = vec_perm(s0,s1,loadPermute0); + sampleData1 = vec_perm(s0,s1,loadPermute1); + + merge0 = vec_mule(sampleData0,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData0,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d0 = vec_add(merge0,d0); + d1 = vec_add(merge1,d1); + + merge0 = vec_mule(sampleData1,volume_vec); + merge0 = vec_sra(merge0,volume_shift); /* Shift down to proper range */ + + merge1 = vec_mulo(sampleData1,volume_vec); + merge1 = vec_sra(merge1,volume_shift); + + d2 = vec_add(merge0,d2); + d3 = vec_add(merge1,d3); + + /* Store destination sample data */ + *(vector signed int *)&samp[i] = d0; + *(vector signed int *)&samp[i+2] = d1; + *(vector signed int *)&samp[i+4] = d2; + *(vector signed int *)&samp[i+6] = d3; + + i += 8; + vectorCount--; + s0 = s1; + sampleOffset += 8; + } + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + } + } + } else { + fleftvol = ch->leftvol*snd_vol; + frightvol = ch->rightvol*snd_vol; + + ooff = sampleOffset; + samples = chunk->sndChunk; + + for ( i=0 ; idopplerScale; + boff = ooff; + fdata = 0; + for (j=aoff; jnext; + if (!chunk) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + ooff -= SND_CHUNK_SIZE; + } + fdata += samples[j&(SND_CHUNK_SIZE-1)]; + } + fdiv = 256 * (boff-aoff); + samp[i].left += (fdata * fleftvol)/fdiv; + samp[i].right += (fdata * frightvol)/fdiv; + } + } +} +#endif + +static void S_PaintChannelFrom16_scalar( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data, aoff, boff; + int leftvol, rightvol; + int i, j; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + float ooff, fdata, fdiv, fleftvol, frightvol; + + samp = &paintbuffer[ bufferOffset ]; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + chunk = sc->soundData; + while (sampleOffset>=SND_CHUNK_SIZE) { + chunk = chunk->next; + sampleOffset -= SND_CHUNK_SIZE; + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler || ch->dopplerScale==1.0f) { + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + samples = chunk->sndChunk; + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE) { + chunk = chunk->next; + samples = chunk->sndChunk; + sampleOffset = 0; + } + } + } else { + fleftvol = ch->leftvol*snd_vol; + frightvol = ch->rightvol*snd_vol; + + ooff = sampleOffset; + samples = chunk->sndChunk; + + + + + for ( i=0 ; idopplerScale; + boff = ooff; + fdata = 0; + for (j=aoff; jnext; + if (!chunk) { + chunk = sc->soundData; + } + samples = chunk->sndChunk; + ooff -= SND_CHUNK_SIZE; + } + fdata += samples[j&(SND_CHUNK_SIZE-1)]; + } + fdiv = 256 * (boff-aoff); + samp[i].left += (fdata * fleftvol)/fdiv; + samp[i].right += (fdata * frightvol)/fdiv; + } + } +} + +static void S_PaintChannelFrom16( channel_t *ch, const sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { +#if idppc_altivec + if (com_altivec->integer) { + // must be in a seperate function or G3 systems will crash. + S_PaintChannelFrom16_altivec( ch, sc, count, sampleOffset, bufferOffset ); + return; + } +#endif + S_PaintChannelFrom16_scalar( ch, sc, count, sampleOffset, bufferOffset ); +} + +void S_PaintChannelFromWavelet( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while (sampleOffset>=(SND_CHUNK_SIZE_FLOAT*4)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE_FLOAT*4); + i++; + } + + if (i!=sfxScratchIndex || sfxScratchPointer != sc) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE*2) { + chunk = chunk->next; + decodeWavelet(chunk, sfxScratchBuffer); + sfxScratchIndex++; + sampleOffset = 0; + } + } +} + +void S_PaintChannelFromADPCM( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + short *samples; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + i = 0; + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + + if (ch->doppler) { + sampleOffset = sampleOffset*ch->oldDopplerScale; + } + + while (sampleOffset>=(SND_CHUNK_SIZE*4)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE*4); + i++; + } + + if (i!=sfxScratchIndex || sfxScratchPointer != sc) { + S_AdpcmGetSamples( chunk, sfxScratchBuffer ); + sfxScratchIndex = i; + sfxScratchPointer = sc; + } + + samples = sfxScratchBuffer; + + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + + if (sampleOffset == SND_CHUNK_SIZE*4) { + chunk = chunk->next; + S_AdpcmGetSamples( chunk, sfxScratchBuffer); + sampleOffset = 0; + sfxScratchIndex++; + } + } +} + +void S_PaintChannelFromMuLaw( channel_t *ch, sfx_t *sc, int count, int sampleOffset, int bufferOffset ) { + int data; + int leftvol, rightvol; + int i; + portable_samplepair_t *samp; + sndBuffer *chunk; + byte *samples; + float ooff; + + leftvol = ch->leftvol*snd_vol; + rightvol = ch->rightvol*snd_vol; + + samp = &paintbuffer[ bufferOffset ]; + chunk = sc->soundData; + while (sampleOffset>=(SND_CHUNK_SIZE*2)) { + chunk = chunk->next; + sampleOffset -= (SND_CHUNK_SIZE*2); + if (!chunk) { + chunk = sc->soundData; + } + } + + if (!ch->doppler) { + samples = (byte *)chunk->sndChunk + sampleOffset; + for ( i=0 ; i>8; + samp[i].right += (data * rightvol)>>8; + samples++; + if (samples == (byte *)chunk->sndChunk+(SND_CHUNK_SIZE*2)) { + chunk = chunk->next; + samples = (byte *)chunk->sndChunk; + } + } + } else { + ooff = sampleOffset; + samples = (byte *)chunk->sndChunk; + for ( i=0 ; idopplerScale; + samp[i].left += (data * leftvol)>>8; + samp[i].right += (data * rightvol)>>8; + if (ooff >= SND_CHUNK_SIZE*2) { + chunk = chunk->next; + if (!chunk) { + chunk = sc->soundData; + } + samples = (byte *)chunk->sndChunk; + ooff = 0.0; + } + } + } +} + +/* +=================== +S_PaintChannels +=================== +*/ +void S_PaintChannels( int endtime ) { + int i; + int end; + int stream; + channel_t *ch; + sfx_t *sc; + int ltime, count; + int sampleOffset; + + if(s_muted->integer) + snd_vol = 0; + else + snd_vol = s_volume->value*255; + +//Com_Printf ("%i to %i\n", s_paintedtime, endtime); + while ( s_paintedtime < endtime ) { + // if paintbuffer is smaller than DMA buffer + // we may need to fill it multiple times + end = endtime; + if ( endtime - s_paintedtime > PAINTBUFFER_SIZE ) { + end = s_paintedtime + PAINTBUFFER_SIZE; + } + + // clear the paint buffer and mix any raw samples... + Com_Memset(paintbuffer, 0, sizeof (paintbuffer)); + for (stream = 0; stream < MAX_RAW_STREAMS; stream++) { + if ( s_rawend[stream] >= s_paintedtime ) { + // copy from the streaming sound source + const portable_samplepair_t *rawsamples = s_rawsamples[stream]; + const int stop = (end < s_rawend[stream]) ? end : s_rawend[stream]; + for ( i = s_paintedtime ; i < stop ; i++ ) { + const int s = i&(MAX_RAW_SAMPLES-1); + paintbuffer[i-s_paintedtime].left += rawsamples[s].left; + paintbuffer[i-s_paintedtime].right += rawsamples[s].right; + } + } + } + + // paint in the channels. + ch = s_channels; + for ( i = 0; i < MAX_CHANNELS ; i++, ch++ ) { + if ( !ch->thesfx || (ch->leftvol<0.25 && ch->rightvol<0.25 )) { + continue; + } + + ltime = s_paintedtime; + sc = ch->thesfx; + + sampleOffset = ltime - ch->startSample; + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { + if( sc->soundCompressionMethod == 1) { + S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 2) { + S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 3) { + S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else { + S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } + } + } + + // paint in the looped channels. + ch = loop_channels; + for ( i = 0; i < numLoopChannels ; i++, ch++ ) { + if ( !ch->thesfx || (!ch->leftvol && !ch->rightvol )) { + continue; + } + + ltime = s_paintedtime; + sc = ch->thesfx; + + if (sc->soundData==NULL || sc->soundLength==0) { + continue; + } + // we might have to make two passes if it + // is a looping sound effect and the end of + // the sample is hit + do { + sampleOffset = (ltime % sc->soundLength); + + count = end - ltime; + if ( sampleOffset + count > sc->soundLength ) { + count = sc->soundLength - sampleOffset; + } + + if ( count > 0 ) { + if( sc->soundCompressionMethod == 1) { + S_PaintChannelFromADPCM (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 2) { + S_PaintChannelFromWavelet (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else if( sc->soundCompressionMethod == 3) { + S_PaintChannelFromMuLaw (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } else { + S_PaintChannelFrom16 (ch, sc, count, sampleOffset, ltime - s_paintedtime); + } + ltime += count; + } + } while ( ltime < end); + } + + // transfer out according to DMA format + S_TransferPaintBuffer( end ); + s_paintedtime = end; + } +} diff --git a/code/client/snd_openal.c b/code/client/snd_openal.c new file mode 100644 index 0000000..d53cf0f --- /dev/null +++ b/code/client/snd_openal.c @@ -0,0 +1,2628 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2005 Stuart Dalton (badcdev@gmail.com) + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "snd_local.h" +#include "snd_codec.h" +#include "client.h" + +#ifdef USE_OPENAL + +#include "qal.h" + +// Console variables specific to OpenAL +cvar_t *s_alPrecache; +cvar_t *s_alGain; +cvar_t *s_alSources; +cvar_t *s_alDopplerFactor; +cvar_t *s_alDopplerSpeed; +cvar_t *s_alMinDistance; +cvar_t *s_alMaxDistance; +cvar_t *s_alRolloff; +cvar_t *s_alGraceDistance; +cvar_t *s_alDriver; +cvar_t *s_alDevice; +cvar_t *s_alInputDevice; +cvar_t *s_alAvailableDevices; +cvar_t *s_alAvailableInputDevices; + +static qboolean enumeration_ext = qfalse; +static qboolean enumeration_all_ext = qfalse; +#ifdef USE_VOIP +static qboolean capture_ext = qfalse; +#endif + +/* +================= +S_AL_Format +================= +*/ +static +ALuint S_AL_Format(int width, int channels) +{ + ALuint format = AL_FORMAT_MONO16; + + // Work out format + if(width == 1) + { + if(channels == 1) + format = AL_FORMAT_MONO8; + else if(channels == 2) + format = AL_FORMAT_STEREO8; + } + else if(width == 2) + { + if(channels == 1) + format = AL_FORMAT_MONO16; + else if(channels == 2) + format = AL_FORMAT_STEREO16; + } + + return format; +} + +/* +================= +S_AL_ErrorMsg +================= +*/ +static const char *S_AL_ErrorMsg(ALenum error) +{ + switch(error) + { + case AL_NO_ERROR: + return "No error"; + case AL_INVALID_NAME: + return "Invalid name"; + case AL_INVALID_ENUM: + return "Invalid enumerator"; + case AL_INVALID_VALUE: + return "Invalid value"; + case AL_INVALID_OPERATION: + return "Invalid operation"; + case AL_OUT_OF_MEMORY: + return "Out of memory"; + default: + return "Unknown error"; + } +} + +/* +================= +S_AL_ClearError +================= +*/ +static void S_AL_ClearError( qboolean quiet ) +{ + int error = qalGetError(); + + if( quiet ) + return; + if(error != AL_NO_ERROR) + { + Com_Printf(S_COLOR_YELLOW "WARNING: unhandled AL error: %s\n", + S_AL_ErrorMsg(error)); + } +} + + +//=========================================================================== + + +typedef struct alSfx_s +{ + char filename[MAX_QPATH]; + ALuint buffer; // OpenAL buffer + snd_info_t info; // information for this sound like rate, sample count.. + + qboolean isDefault; // Couldn't be loaded - use default FX + qboolean isDefaultChecked; // Sound has been check if it isDefault + qboolean inMemory; // Sound is stored in memory + qboolean isLocked; // Sound is locked (can not be unloaded) + int lastUsedTime; // Time last used + + int loopCnt; // number of loops using this sfx + int loopActiveCnt; // number of playing loops using this sfx + int masterLoopSrc; // All other sources looping this buffer are synced to this master src +} alSfx_t; + +static qboolean alBuffersInitialised = qfalse; + +// Sound effect storage, data structures +#define MAX_SFX 4096 +static alSfx_t knownSfx[MAX_SFX]; +static sfxHandle_t numSfx = 0; + +static sfxHandle_t default_sfx; + +/* +================= +S_AL_BufferFindFree + +Find a free handle +================= +*/ +static sfxHandle_t S_AL_BufferFindFree( void ) +{ + int i; + + for(i = 0; i < MAX_SFX; i++) + { + // Got one + if(knownSfx[i].filename[0] == '\0') + { + if(i >= numSfx) + numSfx = i + 1; + return i; + } + } + + // Shit... + Com_Error(ERR_FATAL, "S_AL_BufferFindFree: No free sound handles"); + return -1; +} + +/* +================= +S_AL_BufferFind + +Find a sound effect if loaded, set up a handle otherwise +================= +*/ +static sfxHandle_t S_AL_BufferFind(const char *filename) +{ + // Look it up in the table + sfxHandle_t sfx = -1; + int i; + + for(i = 0; i < numSfx; i++) + { + if(!Q_stricmp(knownSfx[i].filename, filename)) + { + sfx = i; + break; + } + } + + // Not found in table? + if(sfx == -1) + { + alSfx_t *ptr; + + sfx = S_AL_BufferFindFree(); + + // Clear and copy the filename over + ptr = &knownSfx[sfx]; + memset(ptr, 0, sizeof(*ptr)); + ptr->masterLoopSrc = -1; + strcpy(ptr->filename, filename); + } + + // Return the handle + return sfx; +} + +/* +================= +S_AL_BufferUseDefault +================= +*/ +static void S_AL_BufferUseDefault(sfxHandle_t sfx) +{ + if(sfx == default_sfx) + Com_Error(ERR_FATAL, "Can't load default sound effect %s", knownSfx[sfx].filename); + + Com_Printf( S_COLOR_YELLOW "WARNING: Using default sound for %s\n", knownSfx[sfx].filename); + knownSfx[sfx].isDefault = qtrue; + knownSfx[sfx].buffer = knownSfx[default_sfx].buffer; +} + +/* +================= +S_AL_BufferUnload +================= +*/ +static void S_AL_BufferUnload(sfxHandle_t sfx) +{ + if(knownSfx[sfx].filename[0] == '\0') + return; + + if(!knownSfx[sfx].inMemory) + return; + + // Delete it + S_AL_ClearError( qfalse ); + qalDeleteBuffers(1, &knownSfx[sfx].buffer); + if(qalGetError() != AL_NO_ERROR) + Com_Printf( S_COLOR_RED "ERROR: Can't delete sound buffer for %s\n", + knownSfx[sfx].filename); + + knownSfx[sfx].inMemory = qfalse; +} + +/* +================= +S_AL_BufferEvict +================= +*/ +static qboolean S_AL_BufferEvict( void ) +{ + int i, oldestBuffer = -1; + int oldestTime = Sys_Milliseconds( ); + + for( i = 0; i < numSfx; i++ ) + { + if( !knownSfx[ i ].filename[ 0 ] ) + continue; + + if( !knownSfx[ i ].inMemory ) + continue; + + if( knownSfx[ i ].lastUsedTime < oldestTime ) + { + oldestTime = knownSfx[ i ].lastUsedTime; + oldestBuffer = i; + } + } + + if( oldestBuffer >= 0 ) + { + S_AL_BufferUnload( oldestBuffer ); + return qtrue; + } + else + return qfalse; +} + +/* +================= +S_AL_BufferLoad +================= +*/ +static void S_AL_BufferLoad(sfxHandle_t sfx, qboolean cache) +{ + ALenum error; + ALuint format; + + void *data; + snd_info_t info; + alSfx_t *curSfx = &knownSfx[sfx]; + + // Nothing? + if(curSfx->filename[0] == '\0') + return; + + // Player SFX + if(curSfx->filename[0] == '*') + return; + + // Already done? + if((curSfx->inMemory) || (curSfx->isDefault) || (!cache && curSfx->isDefaultChecked)) + return; + + // Try to load + data = S_CodecLoad(curSfx->filename, &info); + if(!data) + { + S_AL_BufferUseDefault(sfx); + return; + } + + curSfx->isDefaultChecked = qtrue; + + if (!cache) + { + // Don't create AL cache + Hunk_FreeTempMemory(data); + return; + } + + format = S_AL_Format(info.width, info.channels); + + // Create a buffer + S_AL_ClearError( qfalse ); + qalGenBuffers(1, &curSfx->buffer); + if((error = qalGetError()) != AL_NO_ERROR) + { + S_AL_BufferUseDefault(sfx); + Hunk_FreeTempMemory(data); + Com_Printf( S_COLOR_RED "ERROR: Can't create a sound buffer for %s - %s\n", + curSfx->filename, S_AL_ErrorMsg(error)); + return; + } + + // Fill the buffer + if( info.size == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData(curSfx->buffer, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050); + } + else + qalBufferData(curSfx->buffer, format, data, info.size, info.rate); + + error = qalGetError(); + + // If we ran out of memory, start evicting the least recently used sounds + while(error == AL_OUT_OF_MEMORY) + { + if( !S_AL_BufferEvict( ) ) + { + S_AL_BufferUseDefault(sfx); + Hunk_FreeTempMemory(data); + Com_Printf( S_COLOR_RED "ERROR: Out of memory loading %s\n", curSfx->filename); + return; + } + + // Try load it again + qalBufferData(curSfx->buffer, format, data, info.size, info.rate); + error = qalGetError(); + } + + // Some other error condition + if(error != AL_NO_ERROR) + { + S_AL_BufferUseDefault(sfx); + Hunk_FreeTempMemory(data); + Com_Printf( S_COLOR_RED "ERROR: Can't fill sound buffer for %s - %s\n", + curSfx->filename, S_AL_ErrorMsg(error)); + return; + } + + curSfx->info = info; + + // Free the memory + Hunk_FreeTempMemory(data); + + // Woo! + curSfx->inMemory = qtrue; +} + +/* +================= +S_AL_BufferUse +================= +*/ +static +void S_AL_BufferUse(sfxHandle_t sfx) +{ + if(knownSfx[sfx].filename[0] == '\0') + return; + + if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx, qtrue); + knownSfx[sfx].lastUsedTime = Sys_Milliseconds(); +} + +/* +================= +S_AL_BufferInit +================= +*/ +static +qboolean S_AL_BufferInit( void ) +{ + if(alBuffersInitialised) + return qtrue; + + // Clear the hash table, and SFX table + memset(knownSfx, 0, sizeof(knownSfx)); + numSfx = 0; + + // Load the default sound, and lock it + default_sfx = S_AL_BufferFind("sound/feedback/hit.wav"); + S_AL_BufferUse(default_sfx); + knownSfx[default_sfx].isLocked = qtrue; + + // All done + alBuffersInitialised = qtrue; + return qtrue; +} + +/* +================= +S_AL_BufferShutdown +================= +*/ +static +void S_AL_BufferShutdown( void ) +{ + int i; + + if(!alBuffersInitialised) + return; + + // Unlock the default sound effect + knownSfx[default_sfx].isLocked = qfalse; + + // Free all used effects + for(i = 0; i < numSfx; i++) + S_AL_BufferUnload(i); + + // Clear the tables + numSfx = 0; + + // All undone + alBuffersInitialised = qfalse; +} + +/* +================= +S_AL_RegisterSound +================= +*/ +static +sfxHandle_t S_AL_RegisterSound( const char *sample, qboolean compressed ) +{ + sfxHandle_t sfx = S_AL_BufferFind(sample); + + if((!knownSfx[sfx].inMemory) && (!knownSfx[sfx].isDefault)) + S_AL_BufferLoad(sfx, s_alPrecache->integer); + knownSfx[sfx].lastUsedTime = Com_Milliseconds(); + + if (knownSfx[sfx].isDefault) { + return 0; + } + + return sfx; +} + +/* +================= +S_AL_BufferGet + +Return's an sfx's buffer +================= +*/ +static +ALuint S_AL_BufferGet(sfxHandle_t sfx) +{ + return knownSfx[sfx].buffer; +} + + +//=========================================================================== + + +typedef struct src_s +{ + ALuint alSource; // OpenAL source object + sfxHandle_t sfx; // Sound effect in use + + int lastUsedTime; // Last time used + alSrcPriority_t priority; // Priority + int entity; // Owning entity (-1 if none) + int channel; // Associated channel (-1 if none) + + qboolean isActive; // Is this source currently in use? + qboolean isPlaying; // Is this source currently playing, or stopped? + qboolean isLocked; // This is locked (un-allocatable) + qboolean isLooping; // Is this a looping effect (attached to an entity) + qboolean isTracking; // Is this object tracking its owner + qboolean isStream; // Is this source a stream + + float curGain; // gain employed if source is within maxdistance. + float scaleGain; // Last gain value for this source. 0 if muted. + + float lastTimePos; // On stopped loops, the last position in the buffer + int lastSampleTime; // Time when this was stopped + vec3_t loopSpeakerPos; // Origin of the loop speaker + + qboolean local; // Is this local (relative to the cam) +} src_t; + +#ifdef MACOS_X + #define MAX_SRC 64 +#else + #define MAX_SRC 128 +#endif +static src_t srcList[MAX_SRC]; +static int srcCount = 0; +static int srcActiveCnt = 0; +static qboolean alSourcesInitialised = qfalse; +static vec3_t lastListenerOrigin = { 0.0f, 0.0f, 0.0f }; + +typedef struct sentity_s +{ + vec3_t origin; + + qboolean srcAllocated; // If a src_t has been allocated to this entity + int srcIndex; + + qboolean loopAddedThisFrame; + alSrcPriority_t loopPriority; + sfxHandle_t loopSfx; + qboolean startLoopingSound; +} sentity_t; + +static sentity_t entityList[MAX_GENTITIES]; + +/* +================= +S_AL_SanitiseVector +================= +*/ +#define S_AL_SanitiseVector(v) _S_AL_SanitiseVector(v,__LINE__) +static void _S_AL_SanitiseVector( vec3_t v, int line ) +{ + if( Q_isnan( v[ 0 ] ) || Q_isnan( v[ 1 ] ) || Q_isnan( v[ 2 ] ) ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: vector with one or more NaN components " + "being passed to OpenAL at %s:%d -- zeroing\n", __FILE__, line ); + VectorClear( v ); + } +} + + +#define AL_THIRD_PERSON_THRESHOLD_SQ (48.0f*48.0f) + +/* +================= +S_AL_Gain +Set gain to 0 if muted, otherwise set it to given value. +================= +*/ + +static void S_AL_Gain(ALuint source, float gainval) +{ + if(s_muted->integer) + qalSourcef(source, AL_GAIN, 0.0f); + else + qalSourcef(source, AL_GAIN, gainval); +} + +/* +================= +S_AL_ScaleGain +Adapt the gain if necessary to get a quicker fadeout when the source is too far away. +================= +*/ + +static void S_AL_ScaleGain(src_t *chksrc, vec3_t origin) +{ + float distance; + + if(!chksrc->local) + distance = Distance(origin, lastListenerOrigin); + + // If we exceed a certain distance, scale the gain linearly until the sound + // vanishes into nothingness. + if(!chksrc->local && (distance -= s_alMaxDistance->value) > 0) + { + float scaleFactor; + + if(distance >= s_alGraceDistance->value) + scaleFactor = 0.0f; + else + scaleFactor = 1.0f - distance / s_alGraceDistance->value; + + scaleFactor *= chksrc->curGain; + + if(chksrc->scaleGain != scaleFactor) + { + chksrc->scaleGain = scaleFactor; + S_AL_Gain(chksrc->alSource, chksrc->scaleGain); + } + } + else if(chksrc->scaleGain != chksrc->curGain) + { + chksrc->scaleGain = chksrc->curGain; + S_AL_Gain(chksrc->alSource, chksrc->scaleGain); + } +} + +/* +================= +S_AL_HearingThroughEntity +================= +*/ +static qboolean S_AL_HearingThroughEntity( int entityNum ) +{ + float distanceSq; + + if( clc.clientNum == entityNum ) + { + // FIXME: 28/02/06 This is an outrageous hack to detect + // whether or not the player is rendering in third person or not. We can't + // ask the renderer because the renderer has no notion of entities and we + // can't ask cgame since that would involve changing the API and hence mod + // compatibility. I don't think there is any way around this, but I'll leave + // the FIXME just in case anyone has a bright idea. + distanceSq = DistanceSquared( + entityList[ entityNum ].origin, + lastListenerOrigin ); + + if( distanceSq > AL_THIRD_PERSON_THRESHOLD_SQ ) + return qfalse; //we're the player, but third person + else + return qtrue; //we're the player + } + else + return qfalse; //not the player +} + +/* +================= +S_AL_SrcInit +================= +*/ +static +qboolean S_AL_SrcInit( void ) +{ + int i; + int limit; + + // Clear the sources data structure + memset(srcList, 0, sizeof(srcList)); + srcCount = 0; + srcActiveCnt = 0; + + // Cap s_alSources to MAX_SRC + limit = s_alSources->integer; + if(limit > MAX_SRC) + limit = MAX_SRC; + else if(limit < 16) + limit = 16; + + S_AL_ClearError( qfalse ); + // Allocate as many sources as possible + for(i = 0; i < limit; i++) + { + qalGenSources(1, &srcList[i].alSource); + if(qalGetError() != AL_NO_ERROR) + break; + srcCount++; + } + + // All done. Print this for informational purposes + Com_Printf( "Allocated %d sources.\n", srcCount); + alSourcesInitialised = qtrue; + return qtrue; +} + +/* +================= +S_AL_SrcShutdown +================= +*/ +static +void S_AL_SrcShutdown( void ) +{ + int i; + src_t *curSource; + + if(!alSourcesInitialised) + return; + + // Destroy all the sources + for(i = 0; i < srcCount; i++) + { + curSource = &srcList[i]; + + if(curSource->isLocked) + Com_DPrintf( S_COLOR_YELLOW "WARNING: Source %d is locked\n", i); + + if(curSource->entity > 0) + entityList[curSource->entity].srcAllocated = qfalse; + + qalSourceStop(srcList[i].alSource); + qalDeleteSources(1, &srcList[i].alSource); + } + + memset(srcList, 0, sizeof(srcList)); + + alSourcesInitialised = qfalse; +} + +/* +================= +S_AL_SrcSetup +================= +*/ +static void S_AL_SrcSetup(srcHandle_t src, sfxHandle_t sfx, alSrcPriority_t priority, + int entity, int channel, qboolean local) +{ + src_t *curSource; + + // Set up src struct + curSource = &srcList[src]; + + curSource->lastUsedTime = Sys_Milliseconds(); + curSource->sfx = sfx; + curSource->priority = priority; + curSource->entity = entity; + curSource->channel = channel; + curSource->isPlaying = qfalse; + curSource->isLocked = qfalse; + curSource->isLooping = qfalse; + curSource->isTracking = qfalse; + curSource->isStream = qfalse; + curSource->curGain = s_alGain->value * s_volume->value; + curSource->scaleGain = curSource->curGain; + curSource->local = local; + + // Set up OpenAL source + if(sfx >= 0) + { + // Mark the SFX as used, and grab the raw AL buffer + S_AL_BufferUse(sfx); + qalSourcei(curSource->alSource, AL_BUFFER, S_AL_BufferGet(sfx)); + } + + qalSourcef(curSource->alSource, AL_PITCH, 1.0f); + S_AL_Gain(curSource->alSource, curSource->curGain); + qalSourcefv(curSource->alSource, AL_POSITION, vec3_origin); + qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin); + qalSourcei(curSource->alSource, AL_LOOPING, AL_FALSE); + qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(local) + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f); + } + else + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + } +} + +/* +================= +S_AL_NewLoopMaster +Remove given source as loop master if it is the master and hand off master status to another source in this case. +================= +*/ + +static void S_AL_SaveLoopPos(src_t *dest, ALuint alSource) +{ + int error; + + S_AL_ClearError(qfalse); + + qalGetSourcef(alSource, AL_SEC_OFFSET, &dest->lastTimePos); + if((error = qalGetError()) != AL_NO_ERROR) + { + // Old OpenAL implementations don't support AL_SEC_OFFSET + + if(error != AL_INVALID_ENUM) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Could not get time offset for alSource %d: %s\n", + alSource, S_AL_ErrorMsg(error)); + } + + dest->lastTimePos = -1; + } + else + dest->lastSampleTime = Sys_Milliseconds(); +} + +/* +================= +S_AL_NewLoopMaster +Remove given source as loop master if it is the master and hand off master status to another source in this case. +================= +*/ + +static void S_AL_NewLoopMaster(src_t *rmSource, qboolean iskilled) +{ + int index; + src_t *curSource = NULL; + alSfx_t *curSfx; + + curSfx = &knownSfx[rmSource->sfx]; + + if(rmSource->isPlaying) + curSfx->loopActiveCnt--; + if(iskilled) + curSfx->loopCnt--; + + if(curSfx->loopCnt) + { + if(rmSource->priority == SRCPRI_ENTITY) + { + if(!iskilled && rmSource->isPlaying) + { + // only sync ambient loops... + // It makes more sense to have sounds for weapons/projectiles unsynced + S_AL_SaveLoopPos(rmSource, rmSource->alSource); + } + } + else if(rmSource == &srcList[curSfx->masterLoopSrc]) + { + int firstInactive = -1; + + // Only if rmSource was the master and if there are still playing loops for + // this sound will we need to find a new master. + + if(iskilled || curSfx->loopActiveCnt) + { + for(index = 0; index < srcCount; index++) + { + curSource = &srcList[index]; + + if(curSource->sfx == rmSource->sfx && curSource != rmSource && + curSource->isActive && curSource->isLooping && curSource->priority == SRCPRI_AMBIENT) + { + if(curSource->isPlaying) + { + curSfx->masterLoopSrc = index; + break; + } + else if(firstInactive < 0) + firstInactive = index; + } + } + } + + if(!curSfx->loopActiveCnt) + { + if(firstInactive < 0) + { + if(iskilled) + { + curSfx->masterLoopSrc = -1; + return; + } + else + curSource = rmSource; + } + else + curSource = &srcList[firstInactive]; + + if(rmSource->isPlaying) + { + // this was the last not stopped source, save last sample position + time + S_AL_SaveLoopPos(curSource, rmSource->alSource); + } + else + { + // second case: all loops using this sound have stopped due to listener being of of range, + // and now the inactive master gets deleted. Just move over the soundpos settings to the + // new master. + curSource->lastTimePos = rmSource->lastTimePos; + curSource->lastSampleTime = rmSource->lastSampleTime; + } + } + } + } + else + curSfx->masterLoopSrc = -1; +} + +/* +================= +S_AL_SrcKill +================= +*/ +static void S_AL_SrcKill(srcHandle_t src) +{ + src_t *curSource = &srcList[src]; + + // I'm not touching it. Unlock it first. + if(curSource->isLocked) + return; + + // Remove the entity association and loop master status + if(curSource->isLooping) + { + curSource->isLooping = qfalse; + + if(curSource->entity != -1) + { + sentity_t *curEnt = &entityList[curSource->entity]; + + curEnt->srcAllocated = qfalse; + curEnt->srcIndex = -1; + curEnt->loopAddedThisFrame = qfalse; + curEnt->startLoopingSound = qfalse; + } + + S_AL_NewLoopMaster(curSource, qtrue); + } + + // Stop it if it's playing + if(curSource->isPlaying) + { + qalSourceStop(curSource->alSource); + curSource->isPlaying = qfalse; + } + + // Remove the buffer + qalSourcei(curSource->alSource, AL_BUFFER, 0); + + curSource->sfx = 0; + curSource->lastUsedTime = 0; + curSource->priority = 0; + curSource->entity = -1; + curSource->channel = -1; + if(curSource->isActive) + { + curSource->isActive = qfalse; + srcActiveCnt--; + } + curSource->isLocked = qfalse; + curSource->isTracking = qfalse; + curSource->local = qfalse; +} + +/* +================= +S_AL_SrcAlloc +================= +*/ +static +srcHandle_t S_AL_SrcAlloc( alSrcPriority_t priority, int entnum, int channel ) +{ + int i; + int empty = -1; + int weakest = -1; + int weakest_time = Sys_Milliseconds(); + int weakest_pri = 999; + float weakest_gain = 1000.0; + qboolean weakest_isplaying = qtrue; + int weakest_numloops = 0; + src_t *curSource; + + for(i = 0; i < srcCount; i++) + { + curSource = &srcList[i]; + + // If it's locked, we aren't even going to look at it + if(curSource->isLocked) + continue; + + // Is it empty or not? + if(!curSource->isActive) + { + empty = i; + break; + } + + if(curSource->isPlaying) + { + if(weakest_isplaying && curSource->priority < priority && + (curSource->priority < weakest_pri || + (!curSource->isLooping && (curSource->scaleGain < weakest_gain || curSource->lastUsedTime < weakest_time)))) + { + // If it has lower priority, is fainter or older, flag it as weak + // the last two values are only compared if it's not a looping sound, because we want to prevent two + // loops (loops are added EVERY frame) fighting for a slot + weakest_pri = curSource->priority; + weakest_time = curSource->lastUsedTime; + weakest_gain = curSource->scaleGain; + weakest = i; + } + } + else + { + weakest_isplaying = qfalse; + + if(weakest < 0 || + knownSfx[curSource->sfx].loopCnt > weakest_numloops || + curSource->priority < weakest_pri || + curSource->lastUsedTime < weakest_time) + { + // Sources currently not playing of course have lowest priority + // also try to always keep at least one loop master for every loop sound + weakest_pri = curSource->priority; + weakest_time = curSource->lastUsedTime; + weakest_numloops = knownSfx[curSource->sfx].loopCnt; + weakest = i; + } + } + + // The channel system is not actually adhered to by baseq3, and not + // implemented in snd_dma.c, so while the following is strictly correct, it + // causes incorrect behaviour versus defacto baseq3 +#if 0 + // Is it an exact match, and not on channel 0? + if((curSource->entity == entnum) && (curSource->channel == channel) && (channel != 0)) + { + S_AL_SrcKill(i); + return i; + } +#endif + } + + if(empty == -1) + empty = weakest; + + if(empty >= 0) + { + S_AL_SrcKill(empty); + srcList[empty].isActive = qtrue; + srcActiveCnt++; + } + + return empty; +} + +/* +================= +S_AL_SrcFind + +Finds an active source with matching entity and channel numbers +Returns -1 if there isn't one +================= +*/ +#if 0 +static +srcHandle_t S_AL_SrcFind(int entnum, int channel) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if(!srcList[i].isActive) + continue; + if((srcList[i].entity == entnum) && (srcList[i].channel == channel)) + return i; + } + return -1; +} +#endif + +/* +================= +S_AL_SrcLock + +Locked sources will not be automatically reallocated or managed +================= +*/ +static +void S_AL_SrcLock(srcHandle_t src) +{ + srcList[src].isLocked = qtrue; +} + +/* +================= +S_AL_SrcUnlock + +Once unlocked, the source may be reallocated again +================= +*/ +static +void S_AL_SrcUnlock(srcHandle_t src) +{ + srcList[src].isLocked = qfalse; +} + +/* +================= +S_AL_UpdateEntityPosition +================= +*/ +static +void S_AL_UpdateEntityPosition( int entityNum, const vec3_t origin ) +{ + vec3_t sanOrigin; + + VectorCopy( origin, sanOrigin ); + S_AL_SanitiseVector( sanOrigin ); + if ( entityNum < 0 || entityNum > MAX_GENTITIES ) + Com_Error( ERR_DROP, "S_UpdateEntityPosition: bad entitynum %i", entityNum ); + VectorCopy( sanOrigin, entityList[entityNum].origin ); +} + +/* +================= +S_AL_CheckInput +Check whether input values from mods are out of range. +Necessary for i.g. Western Quake3 mod which is buggy. +================= +*/ +static qboolean S_AL_CheckInput(int entityNum, sfxHandle_t sfx) +{ + if (entityNum < 0 || entityNum > MAX_GENTITIES) + Com_Error(ERR_DROP, "ERROR: S_AL_CheckInput: bad entitynum %i", entityNum); + + if (sfx < 0 || sfx >= numSfx) + { + Com_Printf(S_COLOR_RED "ERROR: S_AL_CheckInput: handle %i out of range\n", sfx); + return qtrue; + } + + return qfalse; +} + +/* +================= +S_AL_StartLocalSound + +Play a local (non-spatialized) sound effect +================= +*/ +static +void S_AL_StartLocalSound(sfxHandle_t sfx, int channel) +{ + srcHandle_t src; + + if(S_AL_CheckInput(0, sfx)) + return; + + // Try to grab a source + src = S_AL_SrcAlloc(SRCPRI_LOCAL, -1, channel); + + if(src == -1) + return; + + // Set up the effect + S_AL_SrcSetup(src, sfx, SRCPRI_LOCAL, -1, channel, qtrue); + + // Start it playing + srcList[src].isPlaying = qtrue; + qalSourcePlay(srcList[src].alSource); +} + +/* +================= +S_AL_StartSound + +Play a one-shot sound effect +================= +*/ +static void S_AL_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ) +{ + vec3_t sorigin; + srcHandle_t src; + src_t *curSource; + + if(origin) + { + if(S_AL_CheckInput(0, sfx)) + return; + + VectorCopy(origin, sorigin); + } + else + { + if(S_AL_CheckInput(entnum, sfx)) + return; + + if(S_AL_HearingThroughEntity(entnum)) + { + S_AL_StartLocalSound(sfx, entchannel); + return; + } + + VectorCopy(entityList[entnum].origin, sorigin); + } + + S_AL_SanitiseVector(sorigin); + + if((srcActiveCnt > 5 * srcCount / 3) && + (DistanceSquared(sorigin, lastListenerOrigin) >= + (s_alMaxDistance->value + s_alGraceDistance->value) * (s_alMaxDistance->value + s_alGraceDistance->value))) + { + // We're getting tight on sources and source is not within hearing distance so don't add it + return; + } + + // Try to grab a source + src = S_AL_SrcAlloc(SRCPRI_ONESHOT, entnum, entchannel); + if(src == -1) + return; + + S_AL_SrcSetup(src, sfx, SRCPRI_ONESHOT, entnum, entchannel, qfalse); + + curSource = &srcList[src]; + + if(!origin) + curSource->isTracking = qtrue; + + qalSourcefv(curSource->alSource, AL_POSITION, sorigin ); + S_AL_ScaleGain(curSource, sorigin); + + // Start it playing + curSource->isPlaying = qtrue; + qalSourcePlay(curSource->alSource); +} + +/* +================= +S_AL_ClearLoopingSounds +================= +*/ +static +void S_AL_ClearLoopingSounds( qboolean killall ) +{ + int i; + for(i = 0; i < srcCount; i++) + { + if((srcList[i].isLooping) && (srcList[i].entity != -1)) + entityList[srcList[i].entity].loopAddedThisFrame = qfalse; + } +} + +/* +================= +S_AL_SrcLoop +================= +*/ +static void S_AL_SrcLoop( alSrcPriority_t priority, sfxHandle_t sfx, + const vec3_t origin, const vec3_t velocity, int entityNum ) +{ + int src; + sentity_t *sent = &entityList[ entityNum ]; + src_t *curSource; + vec3_t sorigin, svelocity; + + if(S_AL_CheckInput(entityNum, sfx)) + return; + + // Do we need to allocate a new source for this entity + if( !sent->srcAllocated ) + { + // Try to get a channel + src = S_AL_SrcAlloc( priority, entityNum, -1 ); + if( src == -1 ) + { + Com_DPrintf( S_COLOR_YELLOW "WARNING: Failed to allocate source " + "for loop sfx %d on entity %d\n", sfx, entityNum ); + return; + } + + curSource = &srcList[src]; + + sent->startLoopingSound = qtrue; + + curSource->lastTimePos = -1.0; + curSource->lastSampleTime = Sys_Milliseconds(); + } + else + { + src = sent->srcIndex; + curSource = &srcList[src]; + } + + sent->srcAllocated = qtrue; + sent->srcIndex = src; + + sent->loopPriority = priority; + sent->loopSfx = sfx; + + // If this is not set then the looping sound is stopped. + sent->loopAddedThisFrame = qtrue; + + // UGH + // These lines should be called via S_AL_SrcSetup, but we + // can't call that yet as it buffers sfxes that may change + // with subsequent calls to S_AL_SrcLoop + curSource->entity = entityNum; + curSource->isLooping = qtrue; + + if( S_AL_HearingThroughEntity( entityNum ) ) + { + curSource->local = qtrue; + + VectorClear(sorigin); + + qalSourcefv(curSource->alSource, AL_POSITION, sorigin); + qalSourcefv(curSource->alSource, AL_VELOCITY, vec3_origin); + } + else + { + curSource->local = qfalse; + + if(origin) + VectorCopy(origin, sorigin); + else + VectorCopy(sent->origin, sorigin); + + S_AL_SanitiseVector(sorigin); + + VectorCopy(sorigin, curSource->loopSpeakerPos); + + if(velocity) + { + VectorCopy(velocity, svelocity); + S_AL_SanitiseVector(svelocity); + } + else + VectorClear(svelocity); + + qalSourcefv(curSource->alSource, AL_POSITION, (ALfloat *) sorigin); + qalSourcefv(curSource->alSource, AL_VELOCITY, (ALfloat *) svelocity); + } +} + +/* +================= +S_AL_AddLoopingSound +================= +*/ +static void S_AL_AddLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + S_AL_SrcLoop(SRCPRI_ENTITY, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_AddRealLoopingSound +================= +*/ +static void S_AL_AddRealLoopingSound(int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx) +{ + S_AL_SrcLoop(SRCPRI_AMBIENT, sfx, origin, velocity, entityNum); +} + +/* +================= +S_AL_StopLoopingSound +================= +*/ +static +void S_AL_StopLoopingSound(int entityNum ) +{ + if(entityList[entityNum].srcAllocated) + S_AL_SrcKill(entityList[entityNum].srcIndex); +} + +/* +================= +S_AL_SrcUpdate + +Update state (move things around, manage sources, and so on) +================= +*/ +static +void S_AL_SrcUpdate( void ) +{ + int i; + int entityNum; + ALint state; + src_t *curSource; + + for(i = 0; i < srcCount; i++) + { + entityNum = srcList[i].entity; + curSource = &srcList[i]; + + if(curSource->isLocked) + continue; + + if(!curSource->isActive) + continue; + + // Update source parameters + if((s_alGain->modified) || (s_volume->modified)) + curSource->curGain = s_alGain->value * s_volume->value; + if((s_alRolloff->modified) && (!curSource->local)) + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + if(s_alMinDistance->modified) + qalSourcef(curSource->alSource, AL_REFERENCE_DISTANCE, s_alMinDistance->value); + + if(curSource->isLooping) + { + sentity_t *sent = &entityList[ entityNum ]; + + // If a looping effect hasn't been touched this frame, pause or kill it + if(sent->loopAddedThisFrame) + { + alSfx_t *curSfx; + + // The sound has changed without an intervening removal + if(curSource->isActive && !sent->startLoopingSound && + curSource->sfx != sent->loopSfx) + { + S_AL_NewLoopMaster(curSource, qtrue); + + curSource->isPlaying = qfalse; + qalSourceStop(curSource->alSource); + qalSourcei(curSource->alSource, AL_BUFFER, 0); + sent->startLoopingSound = qtrue; + } + + // The sound hasn't been started yet + if(sent->startLoopingSound) + { + S_AL_SrcSetup(i, sent->loopSfx, sent->loopPriority, + entityNum, -1, curSource->local); + curSource->isLooping = qtrue; + + knownSfx[curSource->sfx].loopCnt++; + sent->startLoopingSound = qfalse; + } + + curSfx = &knownSfx[curSource->sfx]; + + S_AL_ScaleGain(curSource, curSource->loopSpeakerPos); + if(!curSource->scaleGain) + { + if(curSource->isPlaying) + { + // Sound is mute, stop playback until we are in range again + S_AL_NewLoopMaster(curSource, qfalse); + qalSourceStop(curSource->alSource); + curSource->isPlaying = qfalse; + } + else if(!curSfx->loopActiveCnt && curSfx->masterLoopSrc < 0) + curSfx->masterLoopSrc = i; + + continue; + } + + if(!curSource->isPlaying) + { + if(curSource->priority == SRCPRI_AMBIENT) + { + // If there are other ambient looping sources with the same sound, + // make sure the sound of these sources are in sync. + + if(curSfx->loopActiveCnt) + { + int offset, error; + + // we already have a master loop playing, get buffer position. + S_AL_ClearError(qfalse); + qalGetSourcei(srcList[curSfx->masterLoopSrc].alSource, AL_SAMPLE_OFFSET, &offset); + if((error = qalGetError()) != AL_NO_ERROR) + { + if(error != AL_INVALID_ENUM) + { + Com_Printf(S_COLOR_YELLOW "WARNING: Cannot get sample offset from source %d: " + "%s\n", i, S_AL_ErrorMsg(error)); + } + } + else + qalSourcei(curSource->alSource, AL_SAMPLE_OFFSET, offset); + } + else if(curSfx->loopCnt && curSfx->masterLoopSrc >= 0) + { + float secofs; + + src_t *master = &srcList[curSfx->masterLoopSrc]; + // This loop sound used to be played, but all sources are stopped. Use last sample position/time + // to calculate offset so the player thinks the sources continued playing while they were inaudible. + + if(master->lastTimePos >= 0) + { + secofs = master->lastTimePos + (Sys_Milliseconds() - master->lastSampleTime) / 1000.0f; + secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate); + + qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs); + } + + // I be the master now + curSfx->masterLoopSrc = i; + } + else + curSfx->masterLoopSrc = i; + } + else if(curSource->lastTimePos >= 0) + { + float secofs; + + // For unsynced loops (SRCPRI_ENTITY) just carry on playing as if the sound was never stopped + + secofs = curSource->lastTimePos + (Sys_Milliseconds() - curSource->lastSampleTime) / 1000.0f; + secofs = fmodf(secofs, (float) curSfx->info.samples / curSfx->info.rate); + qalSourcef(curSource->alSource, AL_SEC_OFFSET, secofs); + } + + curSfx->loopActiveCnt++; + + qalSourcei(curSource->alSource, AL_LOOPING, AL_TRUE); + curSource->isPlaying = qtrue; + qalSourcePlay(curSource->alSource); + } + + // Update locality + if(curSource->local) + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_TRUE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, 0.0f); + } + else + { + qalSourcei(curSource->alSource, AL_SOURCE_RELATIVE, AL_FALSE); + qalSourcef(curSource->alSource, AL_ROLLOFF_FACTOR, s_alRolloff->value); + } + + } + else if(curSource->priority == SRCPRI_AMBIENT) + { + if(curSource->isPlaying) + { + S_AL_NewLoopMaster(curSource, qfalse); + qalSourceStop(curSource->alSource); + curSource->isPlaying = qfalse; + } + } + else + S_AL_SrcKill(i); + + continue; + } + + if(!curSource->isStream) + { + // Check if it's done, and flag it + qalGetSourcei(curSource->alSource, AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + curSource->isPlaying = qfalse; + S_AL_SrcKill(i); + continue; + } + } + + // Query relativity of source, don't move if it's true + qalGetSourcei(curSource->alSource, AL_SOURCE_RELATIVE, &state); + + // See if it needs to be moved + if(curSource->isTracking && !state) + { + qalSourcefv(curSource->alSource, AL_POSITION, entityList[entityNum].origin); + S_AL_ScaleGain(curSource, entityList[entityNum].origin); + } + } +} + +/* +================= +S_AL_SrcShutup +================= +*/ +static +void S_AL_SrcShutup( void ) +{ + int i; + for(i = 0; i < srcCount; i++) + S_AL_SrcKill(i); +} + +/* +================= +S_AL_SrcGet +================= +*/ +static +ALuint S_AL_SrcGet(srcHandle_t src) +{ + return srcList[src].alSource; +} + + +//=========================================================================== + +static srcHandle_t streamSourceHandles[MAX_RAW_STREAMS]; +static qboolean streamPlaying[MAX_RAW_STREAMS]; +static ALuint streamSources[MAX_RAW_STREAMS]; + +/* +================= +S_AL_AllocateStreamChannel +================= +*/ +static void S_AL_AllocateStreamChannel(int stream, int entityNum) +{ + srcHandle_t cursrc; + ALuint alsrc; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(entityNum >= 0) + { + // This is a stream that tracks an entity + // Allocate a streamSource at normal priority + cursrc = S_AL_SrcAlloc(SRCPRI_ENTITY, entityNum, 0); + if(cursrc < 0) + return; + + S_AL_SrcSetup(cursrc, -1, SRCPRI_ENTITY, entityNum, 0, qfalse); + alsrc = S_AL_SrcGet(cursrc); + srcList[cursrc].isTracking = qtrue; + srcList[cursrc].isStream = qtrue; + } + else + { + // Unspatialized stream source + + // Allocate a streamSource at high priority + cursrc = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(cursrc < 0) + return; + + alsrc = S_AL_SrcGet(cursrc); + + // Lock the streamSource so nobody else can use it, and get the raw streamSource + S_AL_SrcLock(cursrc); + + // make sure that after unmuting the S_AL_Gain in S_Update() does not turn + // volume up prematurely for this source + srcList[cursrc].scaleGain = 0.0f; + + // Set some streamSource parameters + qalSourcei (alsrc, AL_BUFFER, 0 ); + qalSourcei (alsrc, AL_LOOPING, AL_FALSE ); + qalSource3f(alsrc, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(alsrc, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(alsrc, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (alsrc, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (alsrc, AL_SOURCE_RELATIVE, AL_TRUE ); + } + + streamSourceHandles[stream] = cursrc; + streamSources[stream] = alsrc; +} + +/* +================= +S_AL_FreeStreamChannel +================= +*/ +static void S_AL_FreeStreamChannel( int stream ) +{ + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + // Release the output streamSource + S_AL_SrcUnlock(streamSourceHandles[stream]); + S_AL_SrcKill(streamSourceHandles[stream]); + streamSources[stream] = 0; + streamSourceHandles[stream] = -1; +} + +/* +================= +S_AL_RawSamples +================= +*/ +static +void S_AL_RawSamples(int stream, int samples, int rate, int width, int channels, const byte *data, float volume, int entityNum) +{ + ALuint buffer; + ALuint format; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + format = S_AL_Format( width, channels ); + + // Create the streamSource if necessary + if(streamSourceHandles[stream] == -1) + { + S_AL_AllocateStreamChannel(stream, entityNum); + + // Failed? + if(streamSourceHandles[stream] == -1) + { + Com_Printf( S_COLOR_RED "ERROR: Can't allocate streaming streamSource\n"); + return; + } + } + + // Create a buffer, and stuff the data into it + qalGenBuffers(1, &buffer); + qalBufferData(buffer, format, (ALvoid *)data, (samples * width * channels), rate); + + // Shove the data onto the streamSource + qalSourceQueueBuffers(streamSources[stream], 1, &buffer); + + if(entityNum < 0) + { + // Volume + S_AL_Gain (streamSources[stream], volume * s_volume->value * s_alGain->value); + } +} + +/* +================= +S_AL_StreamUpdate +================= +*/ +static +void S_AL_StreamUpdate( int stream ) +{ + int numBuffers; + ALint state; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(streamSourceHandles[stream] == -1) + return; + + // Un-queue any buffers, and delete them + qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint buffer; + qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer); + qalDeleteBuffers(1, &buffer); + } + + // Start the streamSource playing if necessary + qalGetSourcei( streamSources[stream], AL_BUFFERS_QUEUED, &numBuffers ); + + qalGetSourcei(streamSources[stream], AL_SOURCE_STATE, &state); + if(state == AL_STOPPED) + { + streamPlaying[stream] = qfalse; + + // If there are no buffers queued up, release the streamSource + if( !numBuffers ) + S_AL_FreeStreamChannel( stream ); + } + + if( !streamPlaying[stream] && numBuffers ) + { + qalSourcePlay( streamSources[stream] ); + streamPlaying[stream] = qtrue; + } +} + +/* +================= +S_AL_StreamDie +================= +*/ +static +void S_AL_StreamDie( int stream ) +{ + int numBuffers; + + if ((stream < 0) || (stream >= MAX_RAW_STREAMS)) + return; + + if(streamSourceHandles[stream] == -1) + return; + + streamPlaying[stream] = qfalse; + qalSourceStop(streamSources[stream]); + + // Un-queue any buffers, and delete them + qalGetSourcei( streamSources[stream], AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint buffer; + qalSourceUnqueueBuffers(streamSources[stream], 1, &buffer); + qalDeleteBuffers(1, &buffer); + } + + S_AL_FreeStreamChannel(stream); +} + + +//=========================================================================== + + +#define NUM_MUSIC_BUFFERS 4 +#define MUSIC_BUFFER_SIZE 4096 + +static qboolean musicPlaying = qfalse; +static srcHandle_t musicSourceHandle = -1; +static ALuint musicSource; +static ALuint musicBuffers[NUM_MUSIC_BUFFERS]; + +static snd_stream_t *mus_stream; +static snd_stream_t *intro_stream; +static char s_backgroundLoop[MAX_QPATH]; + +static byte decode_buffer[MUSIC_BUFFER_SIZE]; + +/* +================= +S_AL_MusicSourceGet +================= +*/ +static void S_AL_MusicSourceGet( void ) +{ + // Allocate a musicSource at high priority + musicSourceHandle = S_AL_SrcAlloc(SRCPRI_STREAM, -2, 0); + if(musicSourceHandle == -1) + return; + + // Lock the musicSource so nobody else can use it, and get the raw musicSource + S_AL_SrcLock(musicSourceHandle); + musicSource = S_AL_SrcGet(musicSourceHandle); + + // make sure that after unmuting the S_AL_Gain in S_Update() does not turn + // volume up prematurely for this source + srcList[musicSourceHandle].scaleGain = 0.0f; + + // Set some musicSource parameters + qalSource3f(musicSource, AL_POSITION, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_VELOCITY, 0.0, 0.0, 0.0); + qalSource3f(musicSource, AL_DIRECTION, 0.0, 0.0, 0.0); + qalSourcef (musicSource, AL_ROLLOFF_FACTOR, 0.0 ); + qalSourcei (musicSource, AL_SOURCE_RELATIVE, AL_TRUE ); +} + +/* +================= +S_AL_MusicSourceFree +================= +*/ +static void S_AL_MusicSourceFree( void ) +{ + // Release the output musicSource + S_AL_SrcUnlock(musicSourceHandle); + musicSource = 0; + musicSourceHandle = -1; +} + +/* +================= +S_AL_CloseMusicFiles +================= +*/ +static void S_AL_CloseMusicFiles(void) +{ + if(intro_stream) + { + S_CodecCloseStream(intro_stream); + intro_stream = NULL; + } + + if(mus_stream) + { + S_CodecCloseStream(mus_stream); + mus_stream = NULL; + } +} + +/* +================= +S_AL_StopBackgroundTrack +================= +*/ +static +void S_AL_StopBackgroundTrack( void ) +{ + if(!musicPlaying) + return; + + // Stop playing + qalSourceStop(musicSource); + + // De-queue the musicBuffers + qalSourceUnqueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers); + + // Destroy the musicBuffers + qalDeleteBuffers(NUM_MUSIC_BUFFERS, musicBuffers); + + // Free the musicSource + S_AL_MusicSourceFree(); + + // Unload the stream + S_AL_CloseMusicFiles(); + + musicPlaying = qfalse; +} + +/* +================= +S_AL_MusicProcess +================= +*/ +static +void S_AL_MusicProcess(ALuint b) +{ + ALenum error; + int l; + ALuint format; + snd_stream_t *curstream; + + S_AL_ClearError( qfalse ); + + if(intro_stream) + curstream = intro_stream; + else + curstream = mus_stream; + + if(!curstream) + return; + + l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer); + + // Run out data to read, start at the beginning again + if(l == 0) + { + S_CodecCloseStream(curstream); + + // the intro stream just finished playing so we don't need to reopen + // the music stream. + if(intro_stream) + intro_stream = NULL; + else + mus_stream = S_CodecOpenStream(s_backgroundLoop); + + curstream = mus_stream; + + if(!curstream) + { + S_AL_StopBackgroundTrack(); + return; + } + + l = S_CodecReadStream(curstream, MUSIC_BUFFER_SIZE, decode_buffer); + } + + format = S_AL_Format(curstream->info.width, curstream->info.channels); + + if( l == 0 ) + { + // We have no data to buffer, so buffer silence + byte dummyData[ 2 ] = { 0 }; + + qalBufferData( b, AL_FORMAT_MONO16, (void *)dummyData, 2, 22050 ); + } + else + qalBufferData(b, format, decode_buffer, l, curstream->info.rate); + + if( ( error = qalGetError( ) ) != AL_NO_ERROR ) + { + S_AL_StopBackgroundTrack( ); + Com_Printf( S_COLOR_RED "ERROR: while buffering data for music stream - %s\n", + S_AL_ErrorMsg( error ) ); + return; + } +} + +/* +================= +S_AL_StartBackgroundTrack +================= +*/ +static +void S_AL_StartBackgroundTrack( const char *intro, const char *loop ) +{ + int i; + qboolean issame; + + // Stop any existing music that might be playing + S_AL_StopBackgroundTrack(); + + if((!intro || !*intro) && (!loop || !*loop)) + return; + + // Allocate a musicSource + S_AL_MusicSourceGet(); + if(musicSourceHandle == -1) + return; + + if (!loop || !*loop) + { + loop = intro; + issame = qtrue; + } + else if(intro && *intro && !strcmp(intro, loop)) + issame = qtrue; + else + issame = qfalse; + + // Copy the loop over + strncpy( s_backgroundLoop, loop, sizeof( s_backgroundLoop ) ); + + if(!issame) + { + // Open the intro and don't mind whether it succeeds. + // The important part is the loop. + intro_stream = S_CodecOpenStream(intro); + } + else + intro_stream = NULL; + + mus_stream = S_CodecOpenStream(s_backgroundLoop); + if(!mus_stream) + { + S_AL_CloseMusicFiles(); + S_AL_MusicSourceFree(); + return; + } + + // Generate the musicBuffers + qalGenBuffers(NUM_MUSIC_BUFFERS, musicBuffers); + + // Queue the musicBuffers up + for(i = 0; i < NUM_MUSIC_BUFFERS; i++) + { + S_AL_MusicProcess(musicBuffers[i]); + } + + qalSourceQueueBuffers(musicSource, NUM_MUSIC_BUFFERS, musicBuffers); + + // Set the initial gain property + S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value); + + // Start playing + qalSourcePlay(musicSource); + + musicPlaying = qtrue; +} + +/* +================= +S_AL_MusicUpdate +================= +*/ +static +void S_AL_MusicUpdate( void ) +{ + int numBuffers; + ALint state; + + if(!musicPlaying) + return; + + qalGetSourcei( musicSource, AL_BUFFERS_PROCESSED, &numBuffers ); + while( numBuffers-- ) + { + ALuint b; + qalSourceUnqueueBuffers(musicSource, 1, &b); + S_AL_MusicProcess(b); + qalSourceQueueBuffers(musicSource, 1, &b); + } + + // Hitches can cause OpenAL to be starved of buffers when streaming. + // If this happens, it will stop playback. This restarts the source if + // it is no longer playing, and if there are buffers available + qalGetSourcei( musicSource, AL_SOURCE_STATE, &state ); + qalGetSourcei( musicSource, AL_BUFFERS_QUEUED, &numBuffers ); + if( state == AL_STOPPED && numBuffers ) + { + Com_DPrintf( S_COLOR_YELLOW "Restarted OpenAL music\n" ); + qalSourcePlay(musicSource); + } + + // Set the gain property + S_AL_Gain(musicSource, s_alGain->value * s_musicVolume->value); +} + + +//=========================================================================== + + +// Local state variables +static ALCdevice *alDevice; +static ALCcontext *alContext; + +#ifdef USE_VOIP +static ALCdevice *alCaptureDevice; +static cvar_t *s_alCapture; +#endif + +#ifdef _WIN32 +#define ALDRIVER_DEFAULT "OpenAL32.dll" +#elif defined(MACOS_X) +#define ALDRIVER_DEFAULT "/System/Library/Frameworks/OpenAL.framework/OpenAL" +#else +#define ALDRIVER_DEFAULT "libopenal.so.1" +#endif + +/* +================= +S_AL_StopAllSounds +================= +*/ +static +void S_AL_StopAllSounds( void ) +{ + int i; + S_AL_SrcShutup(); + S_AL_StopBackgroundTrack(); + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamDie(i); +} + +/* +================= +S_AL_Respatialize +================= +*/ +static +void S_AL_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ) +{ + float orientation[6]; + vec3_t sorigin; + + VectorCopy( origin, sorigin ); + S_AL_SanitiseVector( sorigin ); + + S_AL_SanitiseVector( axis[ 0 ] ); + S_AL_SanitiseVector( axis[ 1 ] ); + S_AL_SanitiseVector( axis[ 2 ] ); + + orientation[0] = axis[0][0]; orientation[1] = axis[0][1]; orientation[2] = axis[0][2]; + orientation[3] = axis[2][0]; orientation[4] = axis[2][1]; orientation[5] = axis[2][2]; + + VectorCopy( sorigin, lastListenerOrigin ); + + // Set OpenAL listener paramaters + qalListenerfv(AL_POSITION, (ALfloat *)sorigin); + qalListenerfv(AL_VELOCITY, vec3_origin); + qalListenerfv(AL_ORIENTATION, orientation); +} + +/* +================= +S_AL_Update +================= +*/ +static +void S_AL_Update( void ) +{ + int i; + + if(s_muted->modified) + { + // muted state changed. Let S_AL_Gain turn up all sources again. + for(i = 0; i < srcCount; i++) + { + if(srcList[i].isActive) + S_AL_Gain(srcList[i].alSource, srcList[i].scaleGain); + } + + s_muted->modified = qfalse; + } + + // Update SFX channels + S_AL_SrcUpdate(); + + // Update streams + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamUpdate(i); + S_AL_MusicUpdate(); + + // Doppler + if(s_doppler->modified) + { + s_alDopplerFactor->modified = qtrue; + s_doppler->modified = qfalse; + } + + // Doppler parameters + if(s_alDopplerFactor->modified) + { + if(s_doppler->integer) + qalDopplerFactor(s_alDopplerFactor->value); + else + qalDopplerFactor(0.0f); + s_alDopplerFactor->modified = qfalse; + } + if(s_alDopplerSpeed->modified) + { + qalDopplerVelocity(s_alDopplerSpeed->value); + s_alDopplerSpeed->modified = qfalse; + } + + // Clear the modified flags on the other cvars + s_alGain->modified = qfalse; + s_volume->modified = qfalse; + s_musicVolume->modified = qfalse; + s_alMinDistance->modified = qfalse; + s_alRolloff->modified = qfalse; +} + +/* +================= +S_AL_DisableSounds +================= +*/ +static +void S_AL_DisableSounds( void ) +{ + S_AL_StopAllSounds(); +} + +/* +================= +S_AL_BeginRegistration +================= +*/ +static +void S_AL_BeginRegistration( void ) +{ + if(!numSfx) + S_AL_BufferInit(); +} + +/* +================= +S_AL_ClearSoundBuffer +================= +*/ +static +void S_AL_ClearSoundBuffer( void ) +{ +} + +/* +================= +S_AL_SoundList +================= +*/ +static +void S_AL_SoundList( void ) +{ +} + +#ifdef USE_VOIP +static +void S_AL_StartCapture( void ) +{ + if (alCaptureDevice != NULL) + qalcCaptureStart(alCaptureDevice); +} + +static +int S_AL_AvailableCaptureSamples( void ) +{ + int retval = 0; + if (alCaptureDevice != NULL) + { + ALint samples = 0; + qalcGetIntegerv(alCaptureDevice, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples); + retval = (int) samples; + } + return retval; +} + +static +void S_AL_Capture( int samples, byte *data ) +{ + if (alCaptureDevice != NULL) + qalcCaptureSamples(alCaptureDevice, data, samples); +} + +void S_AL_StopCapture( void ) +{ + if (alCaptureDevice != NULL) + qalcCaptureStop(alCaptureDevice); +} + +void S_AL_MasterGain( float gain ) +{ + qalListenerf(AL_GAIN, gain); +} +#endif + + +/* +================= +S_AL_SoundInfo +================= +*/ +static void S_AL_SoundInfo(void) +{ + Com_Printf( "OpenAL info:\n" ); + Com_Printf( " Vendor: %s\n", qalGetString( AL_VENDOR ) ); + Com_Printf( " Version: %s\n", qalGetString( AL_VERSION ) ); + Com_Printf( " Renderer: %s\n", qalGetString( AL_RENDERER ) ); + Com_Printf( " AL Extensions: %s\n", qalGetString( AL_EXTENSIONS ) ); + Com_Printf( " ALC Extensions: %s\n", qalcGetString( alDevice, ALC_EXTENSIONS ) ); + + if(enumeration_all_ext) + Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_ALL_DEVICES_SPECIFIER)); + else if(enumeration_ext) + Com_Printf(" Device: %s\n", qalcGetString(alDevice, ALC_DEVICE_SPECIFIER)); + + if(enumeration_all_ext || enumeration_ext) + Com_Printf(" Available Devices:\n%s", s_alAvailableDevices->string); + +#ifdef USE_VOIP + if(capture_ext) + { + Com_Printf(" Input Device: %s\n", qalcGetString(alCaptureDevice, ALC_CAPTURE_DEVICE_SPECIFIER)); + Com_Printf(" Available Input Devices:\n%s", s_alAvailableInputDevices->string); + } +#endif +} + + + +/* +================= +S_AL_Shutdown +================= +*/ +static +void S_AL_Shutdown( void ) +{ + // Shut down everything + int i; + for (i = 0; i < MAX_RAW_STREAMS; i++) + S_AL_StreamDie(i); + S_AL_StopBackgroundTrack( ); + S_AL_SrcShutdown( ); + S_AL_BufferShutdown( ); + + qalcDestroyContext(alContext); + qalcCloseDevice(alDevice); + +#ifdef USE_VOIP + if (alCaptureDevice != NULL) { + qalcCaptureStop(alCaptureDevice); + qalcCaptureCloseDevice(alCaptureDevice); + alCaptureDevice = NULL; + Com_Printf( "OpenAL capture device closed.\n" ); + } +#endif + + for (i = 0; i < MAX_RAW_STREAMS; i++) { + streamSourceHandles[i] = -1; + streamPlaying[i] = qfalse; + streamSources[i] = 0; + } + + QAL_Shutdown(); +} + +#endif + +/* +================= +S_AL_Init +================= +*/ +qboolean S_AL_Init( soundInterface_t *si ) +{ +#ifdef USE_OPENAL + const char* device = NULL; + const char* inputdevice = NULL; + int i; + + if( !si ) { + return qfalse; + } + + for (i = 0; i < MAX_RAW_STREAMS; i++) { + streamSourceHandles[i] = -1; + streamPlaying[i] = qfalse; + streamSources[i] = 0; + } + + // New console variables + s_alPrecache = Cvar_Get( "s_alPrecache", "1", CVAR_ARCHIVE ); + s_alGain = Cvar_Get( "s_alGain", "1.0", CVAR_ARCHIVE ); + s_alSources = Cvar_Get( "s_alSources", "96", CVAR_ARCHIVE ); + s_alDopplerFactor = Cvar_Get( "s_alDopplerFactor", "1.0", CVAR_ARCHIVE ); + s_alDopplerSpeed = Cvar_Get( "s_alDopplerSpeed", "2200", CVAR_ARCHIVE ); + s_alMinDistance = Cvar_Get( "s_alMinDistance", "120", CVAR_CHEAT ); + s_alMaxDistance = Cvar_Get("s_alMaxDistance", "1024", CVAR_CHEAT); + s_alRolloff = Cvar_Get( "s_alRolloff", "2", CVAR_CHEAT); + s_alGraceDistance = Cvar_Get("s_alGraceDistance", "512", CVAR_CHEAT); + + s_alDriver = Cvar_Get( "s_alDriver", ALDRIVER_DEFAULT, CVAR_ARCHIVE | CVAR_LATCH ); + + s_alInputDevice = Cvar_Get( "s_alInputDevice", "", CVAR_ARCHIVE | CVAR_LATCH ); + s_alDevice = Cvar_Get("s_alDevice", "", CVAR_ARCHIVE | CVAR_LATCH); + + // Load QAL + if( !QAL_Init( s_alDriver->string ) ) + { + Com_Printf( "Failed to load library: \"%s\".\n", s_alDriver->string ); + return qfalse; + } + + device = s_alDevice->string; + if(device && !*device) + device = NULL; + + inputdevice = s_alInputDevice->string; + if(inputdevice && !*inputdevice) + inputdevice = NULL; + + + // Device enumeration support + enumeration_all_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT"); + enumeration_ext = qalcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT"); + + if(enumeration_ext || enumeration_all_ext) + { + char devicenames[16384] = ""; + const char *devicelist; +#ifdef _WIN32 + const char *defaultdevice; +#endif + int curlen; + + // get all available devices + the default device name. + if(enumeration_all_ext) + { + devicelist = qalcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER); +#ifdef _WIN32 + defaultdevice = qalcGetString(NULL, ALC_DEFAULT_ALL_DEVICES_SPECIFIER); +#endif + } + else + { + // We don't have ALC_ENUMERATE_ALL_EXT but normal enumeration. + devicelist = qalcGetString(NULL, ALC_DEVICE_SPECIFIER); +#ifdef _WIN32 + defaultdevice = qalcGetString(NULL, ALC_DEFAULT_DEVICE_SPECIFIER); +#endif + enumeration_ext = qtrue; + } + +#ifdef _WIN32 + // check whether the default device is generic hardware. If it is, change to + // Generic Software as that one works more reliably with various sound systems. + // If it's not, use OpenAL's default selection as we don't want to ignore + // native hardware acceleration. + if(!device && defaultdevice && !strcmp(defaultdevice, "Generic Hardware")) + device = "Generic Software"; +#endif + + // dump a list of available devices to a cvar for the user to see. + + if(devicelist) + { + while((curlen = strlen(devicelist))) + { + Q_strcat(devicenames, sizeof(devicenames), devicelist); + Q_strcat(devicenames, sizeof(devicenames), "\n"); + + devicelist += curlen + 1; + } + } + else + devicelist = ""; + + s_alAvailableDevices = Cvar_Get("s_alAvailableDevices", devicenames, CVAR_ROM | CVAR_NORESTART); + } + + alDevice = qalcOpenDevice(device); + if( !alDevice && device ) + { + Com_Printf( "Failed to open OpenAL device '%s', trying default.\n", device ); + alDevice = qalcOpenDevice(NULL); + } + + if( !alDevice ) + { + QAL_Shutdown( ); + Com_Printf( "Failed to open OpenAL device.\n" ); + return qfalse; + } + + // Create OpenAL context + alContext = qalcCreateContext( alDevice, NULL ); + if( !alContext ) + { + QAL_Shutdown( ); + qalcCloseDevice( alDevice ); + Com_Printf( "Failed to create OpenAL context.\n" ); + return qfalse; + } + qalcMakeContextCurrent( alContext ); + + // Initialize sources, buffers, music + S_AL_BufferInit( ); + S_AL_SrcInit( ); + + // Set up OpenAL parameters (doppler, etc) + qalDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + qalDopplerFactor( s_alDopplerFactor->value ); + qalDopplerVelocity( s_alDopplerSpeed->value ); + +#ifdef USE_VOIP + // !!! FIXME: some of these alcCaptureOpenDevice() values should be cvars. + // !!! FIXME: add support for capture device enumeration. + // !!! FIXME: add some better error reporting. + s_alCapture = Cvar_Get( "s_alCapture", "1", CVAR_ARCHIVE | CVAR_LATCH ); + if (!s_alCapture->integer) + { + Com_Printf("OpenAL capture support disabled by user ('+set s_alCapture 1' to enable)\n"); + } +#if USE_MUMBLE + else if (cl_useMumble->integer) + { + Com_Printf("OpenAL capture support disabled for Mumble support\n"); + } +#endif + else + { +#ifdef MACOS_X + // !!! FIXME: Apple has a 1.1-compliant OpenAL, which includes + // !!! FIXME: capture support, but they don't list it in the + // !!! FIXME: extension string. We need to check the version string, + // !!! FIXME: then the extension string, but that's too much trouble, + // !!! FIXME: so we'll just check the function pointer for now. + if (qalcCaptureOpenDevice == NULL) +#else + if (!qalcIsExtensionPresent(NULL, "ALC_EXT_capture")) +#endif + { + Com_Printf("No ALC_EXT_capture support, can't record audio.\n"); + } + else + { + char inputdevicenames[16384] = ""; + const char *inputdevicelist; + const char *defaultinputdevice; + int curlen; + + capture_ext = qtrue; + + // get all available input devices + the default input device name. + inputdevicelist = qalcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER); + defaultinputdevice = qalcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER); + + // dump a list of available devices to a cvar for the user to see. + while((curlen = strlen(inputdevicelist))) + { + Q_strcat(inputdevicenames, sizeof(inputdevicenames), inputdevicelist); + Q_strcat(inputdevicenames, sizeof(inputdevicenames), "\n"); + inputdevicelist += curlen + 1; + } + + s_alAvailableInputDevices = Cvar_Get("s_alAvailableInputDevices", inputdevicenames, CVAR_ROM | CVAR_NORESTART); + + // !!! FIXME: 8000Hz is what Speex narrowband mode needs, but we + // !!! FIXME: should probably open the capture device after + // !!! FIXME: initializing Speex so we can change to wideband + // !!! FIXME: if we like. + Com_Printf("OpenAL default capture device is '%s'\n", defaultinputdevice); + alCaptureDevice = qalcCaptureOpenDevice(inputdevice, 8000, AL_FORMAT_MONO16, 4096); + if( !alCaptureDevice && inputdevice ) + { + Com_Printf( "Failed to open OpenAL Input device '%s', trying default.\n", inputdevice ); + alCaptureDevice = qalcCaptureOpenDevice(NULL, 8000, AL_FORMAT_MONO16, 4096); + } + Com_Printf( "OpenAL capture device %s.\n", + (alCaptureDevice == NULL) ? "failed to open" : "opened"); + } + } +#endif + + si->Shutdown = S_AL_Shutdown; + si->StartSound = S_AL_StartSound; + si->StartLocalSound = S_AL_StartLocalSound; + si->StartBackgroundTrack = S_AL_StartBackgroundTrack; + si->StopBackgroundTrack = S_AL_StopBackgroundTrack; + si->RawSamples = S_AL_RawSamples; + si->StopAllSounds = S_AL_StopAllSounds; + si->ClearLoopingSounds = S_AL_ClearLoopingSounds; + si->AddLoopingSound = S_AL_AddLoopingSound; + si->AddRealLoopingSound = S_AL_AddRealLoopingSound; + si->StopLoopingSound = S_AL_StopLoopingSound; + si->Respatialize = S_AL_Respatialize; + si->UpdateEntityPosition = S_AL_UpdateEntityPosition; + si->Update = S_AL_Update; + si->DisableSounds = S_AL_DisableSounds; + si->BeginRegistration = S_AL_BeginRegistration; + si->RegisterSound = S_AL_RegisterSound; + si->ClearSoundBuffer = S_AL_ClearSoundBuffer; + si->SoundInfo = S_AL_SoundInfo; + si->SoundList = S_AL_SoundList; + +#ifdef USE_VOIP + si->StartCapture = S_AL_StartCapture; + si->AvailableCaptureSamples = S_AL_AvailableCaptureSamples; + si->Capture = S_AL_Capture; + si->StopCapture = S_AL_StopCapture; + si->MasterGain = S_AL_MasterGain; +#endif + + return qtrue; +#else + return qfalse; +#endif +} + diff --git a/code/client/snd_public.h b/code/client/snd_public.h new file mode 100644 index 0000000..aeb9b2e --- /dev/null +++ b/code/client/snd_public.h @@ -0,0 +1,82 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + + +void S_Init( void ); +void S_Shutdown( void ); + +// if origin is NULL, the sound will be dynamically sourced from the entity +void S_StartSound( vec3_t origin, int entnum, int entchannel, sfxHandle_t sfx ); +void S_StartLocalSound( sfxHandle_t sfx, int channelNum ); + +void S_StartBackgroundTrack( const char *intro, const char *loop ); +void S_StopBackgroundTrack( void ); + +// cinematics and voice-over-network will send raw samples +// 1.0 volume will be direct output of source samples +void S_RawSamples(int stream, int samples, int rate, int width, int channels, + const byte *data, float volume, int entityNum); + +// stop all sounds and the background track +void S_StopAllSounds( void ); + +// all continuous looping sounds must be added before calling S_Update +void S_ClearLoopingSounds( qboolean killall ); +void S_AddLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void S_AddRealLoopingSound( int entityNum, const vec3_t origin, const vec3_t velocity, sfxHandle_t sfx ); +void S_StopLoopingSound(int entityNum ); + +// recompute the reletive volumes for all running sounds +// reletive to the given entityNum / orientation +void S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + +// let the sound system know where an entity currently is +void S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + +void S_Update( void ); + +void S_DisableSounds( void ); + +void S_BeginRegistration( void ); + +// RegisterSound will allways return a valid sample, even if it +// has to create a placeholder. This prevents continuous filesystem +// checks for missing files +sfxHandle_t S_RegisterSound( const char *sample, qboolean compressed ); + +void S_DisplayFreeMemory(void); + +void S_ClearSoundBuffer( void ); + +void SNDDMA_Activate( void ); + +void S_UpdateBackgroundTrack( void ); + + +#ifdef USE_VOIP +void S_StartCapture( void ); +int S_AvailableCaptureSamples( void ); +void S_Capture( int samples, byte *data ); +void S_StopCapture( void ); +void S_MasterGain( float gain ); +#endif + diff --git a/code/client/snd_wavelet.c b/code/client/snd_wavelet.c new file mode 100644 index 0000000..9940b4f --- /dev/null +++ b/code/client/snd_wavelet.c @@ -0,0 +1,251 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake III Arena source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "snd_local.h" + +#define C0 0.4829629131445341 +#define C1 0.8365163037378079 +#define C2 0.2241438680420134 +#define C3 -0.1294095225512604 + +void daub4(float b[], unsigned long n, int isign) +{ + float wksp[4097]; + float *a=b-1; // numerical recipies so a[1] = b[0] + + unsigned long nh,nh1,i,j; + + if (n < 4) return; + + nh1=(nh=n >> 1)+1; + if (isign >= 0) { + for (i=1,j=1;j<=n-3;j+=2,i++) { + wksp[i] = C0*a[j]+C1*a[j+1]+C2*a[j+2]+C3*a[j+3]; + wksp[i+nh] = C3*a[j]-C2*a[j+1]+C1*a[j+2]-C0*a[j+3]; + } + wksp[i ] = C0*a[n-1]+C1*a[n]+C2*a[1]+C3*a[2]; + wksp[i+nh] = C3*a[n-1]-C2*a[n]+C1*a[1]-C0*a[2]; + } else { + wksp[1] = C2*a[nh]+C1*a[n]+C0*a[1]+C3*a[nh1]; + wksp[2] = C3*a[nh]-C0*a[n]+C1*a[1]-C2*a[nh1]; + for (i=1,j=3;i= 0) { + for (nn=n;nn>=inverseStartLength;nn>>=1) daub4(a,nn,isign); + } else { + for (nn=inverseStartLength;nn<=n;nn<<=1) daub4(a,nn,isign); + } +} + +/* The number of bits required by each value */ +static unsigned char numBits[] = { + 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, +}; + +byte MuLawEncode(short s) { + unsigned long adjusted; + byte sign, exponent, mantissa; + + sign = (s<0)?0:0x80; + + if (s<0) s=-s; + adjusted = (long)s << (16-sizeof(short)*8); + adjusted += 128L + 4L; + if (adjusted > 32767) adjusted = 32767; + exponent = numBits[(adjusted>>7)&0xff] - 1; + mantissa = (adjusted>>(exponent+3))&0xf; + return ~(sign | (exponent<<4) | mantissa); +} + +short MuLawDecode(byte uLaw) { + signed long adjusted; + byte exponent, mantissa; + + uLaw = ~uLaw; + exponent = (uLaw>>4) & 0x7; + mantissa = (uLaw&0xf) + 16; + adjusted = (mantissa << (exponent +3)) - 128 - 4; + + return (uLaw & 0x80)? adjusted : -adjusted; +} + +short mulawToShort[256]; +static qboolean madeTable = qfalse; + +static int NXStreamCount; + +void NXPutc(NXStream *stream, char out) { + stream[NXStreamCount++] = out; +} + + +void encodeWavelet( sfx_t *sfx, short *packets) { + float wksp[4097], temp; + int i, samples, size; + sndBuffer *newchunk, *chunk; + byte *out; + + if (!madeTable) { + for (i=0;i<256;i++) { + mulawToShort[i] = (float)MuLawDecode((byte)i); + } + madeTable = qtrue; + } + chunk = NULL; + + samples = sfx->soundLength; + while(samples>0) { + size = samples; + if (size>(SND_CHUNK_SIZE*2)) { + size = (SND_CHUNK_SIZE*2); + } + + if (size<4) { + size = 4; + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + for(i=0; isndChunk; + + for(i=0;i 32767) temp = 32767; else if (temp<-32768) temp = -32768; + out[i] = MuLawEncode((short)temp); + } + + chunk->size = size; + samples -= size; + } +} + +void decodeWavelet(sndBuffer *chunk, short *to) { + float wksp[4097]; + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for(i=0;isoundLength; + grade = 0; + + while(samples>0) { + size = samples; + if (size>(SND_CHUNK_SIZE*2)) { + size = (SND_CHUNK_SIZE*2); + } + + newchunk = SND_malloc(); + if (sfx->soundData == NULL) { + sfx->soundData = newchunk; + } else { + chunk->next = newchunk; + } + chunk = newchunk; + out = (byte *)chunk->sndChunk; + for(i=0; i32767) { + poop = 32767; + } else if (poop<-32768) { + poop = -32768; + } + out[i] = MuLawEncode((short)poop); + grade = poop - mulawToShort[out[i]]; + packets++; + } + chunk->size = size; + samples -= size; + } +} + +void decodeMuLaw(sndBuffer *chunk, short *to) { + int i; + byte *out; + + int size = chunk->size; + + out = (byte *)chunk->sndChunk; + for(i=0;i= pVeh->m_iDieTime ) + { + // If the vehicle is not empty. + if ( pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) + { + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + else + { + // Waste this sucker. + } + + // Die now... +/* else + { + vec3_t mins, maxs, bottom; + trace_t trace; + + if ( pVeh->m_pVehicleInfo->explodeFX ) + { + G_PlayEffect( pVeh->m_pVehicleInfo->explodeFX, parent->currentOrigin ); + //trace down and place mark + VectorCopy( parent->currentOrigin, bottom ); + bottom[2] -= 80; + gi.trace( &trace, parent->currentOrigin, vec3_origin, vec3_origin, bottom, parent->s.number, CONTENTS_SOLID ); + if ( trace.fraction < 1.0f ) + { + VectorCopy( trace.endpos, bottom ); + bottom[2] += 2; + G_PlayEffect( "ships/ship_explosion_mark", trace.endpos ); + } + } + + parent->takedamage = qfalse;//so we don't recursively damage ourselves + if ( pVeh->m_pVehicleInfo->explosionRadius > 0 && pVeh->m_pVehicleInfo->explosionDamage > 0 ) + { + VectorCopy( parent->mins, mins ); + mins[2] = -4;//to keep it off the ground a *little* + VectorCopy( parent->maxs, maxs ); + VectorCopy( parent->currentOrigin, bottom ); + bottom[2] += parent->mins[2] - 32; + gi.trace( &trace, parent->currentOrigin, mins, maxs, bottom, parent->s.number, CONTENTS_SOLID ); + G_RadiusDamage( trace.endpos, NULL, pVeh->m_pVehicleInfo->explosionDamage, pVeh->m_pVehicleInfo->explosionRadius, NULL, MOD_EXPLOSIVE );//FIXME: extern damage and radius or base on fuel + } + + parent->e_ThinkFunc = thinkF_G_FreeEntity; + parent->nextthink = level.time + FRAMETIME; + }*/ + } +} + +// Like a think or move command, this updates various vehicle properties. +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + return g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ); +} +#endif //QAGAME + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + float fWalkSpeedMax; + int curTime; + bgEntity_t *parent = pVeh->m_pParentEntity; +#ifdef _JK2MP + playerState_t *parentPS = parent->playerState; +#else + playerState_t *parentPS = &parent->client->ps; +#endif + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + +#ifndef _JK2MP //bad for prediction - fixme + // Bucking so we can't do anything. + if ( pVeh->m_ulFlags & VEH_BUCKING || pVeh->m_ulFlags & VEH_FLYING || pVeh->m_ulFlags & VEH_CRASHING ) + { +//#ifdef QAGAME //this was in Update above +// ((gentity_t *)parent)->client->ps.speed = 0; +//#endif + parentPS->speed = 0; + return; + } +#endif + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + speedMax = pVeh->m_pVehicleInfo->speedMax; + + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + + + + if ( pVeh->m_pPilot /*&& (pilotPS->weapon == WP_NONE || pilotPS->weapon == WP_MELEE )*/ && + (pVeh->m_ucmd.buttons & BUTTON_ALT_ATTACK) && pVeh->m_pVehicleInfo->turboSpeed ) + { + if ((curTime - pVeh->m_iTurboTime)>pVeh->m_pVehicleInfo->turboRecharge) + { + pVeh->m_iTurboTime = (curTime + pVeh->m_pVehicleInfo->turboDuration); +#ifndef _JK2MP //kill me now + if (pVeh->m_pVehicleInfo->soundTurbo) + { + G_SoundIndexOnEnt(pVeh->m_pParentEntity, CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo); + } +#endif + parentPS->speed = pVeh->m_pVehicleInfo->turboSpeed; // Instantly Jump To Turbo Speed + } + } + + if ( curTime < pVeh->m_iTurboTime ) + { + speedMax = pVeh->m_pVehicleInfo->turboSpeed; + } + else + { + speedMax = pVeh->m_pVehicleInfo->speedMax; + } + +#ifdef _JK2MP + if ( !parentPS->m_iVehicleNum ) +#else + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//drifts to a stop + speedInc = speedIdle * pVeh->m_fTimeModifier; + VectorClear( parentPS->moveDir ); + //m_ucmd.forwardmove = 127; + parentPS->speed = 0; + } + else + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + + if ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + parentPS->speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( parentPS->speed > 0.0f ) + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < 0.0f ) + { + parentPS->speed = 0.0f; + } + } + else if ( parentPS->speed < 0.0f ) + { + parentPS->speed += speedIdleDec; + if ( parentPS->speed > 0.0f ) + { + parentPS->speed = 0.0f; + } + } + } + else + { + if ( pVeh->m_ucmd.forwardmove < 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + if ( pVeh->m_ucmd.upmove < 0 ) + { + pVeh->m_ucmd.upmove = 0; + } + + //pVeh->m_ucmd.rightmove = 0; + + /*if ( !pVeh->m_pVehicleInfo->strafePerc + || (!g_speederControlScheme->value && !parent->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + pVeh->m_ucmd.rightmove = 0; + }*/ + } + + fWalkSpeedMax = speedMax * 0.275f; + if ( curTime>pVeh->m_iTurboTime && (pVeh->m_ucmd.buttons & BUTTON_WALKING) && parentPS->speed > fWalkSpeedMax ) + { + parentPS->speed = fWalkSpeedMax; + } + else if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessOrientCommands the Vehicle. +static void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + bgEntity_t *parent = pVeh->m_pParentEntity; + playerState_t *parentPS, *riderPS; + +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } +#else + gentity_t *rider = parent->owner; +#endif + + // Bucking so we can't do anything. +#ifndef _JK2MP //bad for prediction - fixme + if ( pVeh->m_ulFlags & VEH_BUCKING || pVeh->m_ulFlags & VEH_FLYING || pVeh->m_ulFlags & VEH_CRASHING ) + { + return; + } +#endif + +#ifdef _JK2MP + if ( !rider ) +#else + if ( !rider || !rider->client ) +#endif + { + rider = parent; + } + + + +#ifdef _JK2MP + parentPS = parent->playerState; + riderPS = rider->playerState; +#else + parentPS = &parent->client->ps; + riderPS = &rider->client->ps; +#endif + + if (rider) + { +#ifdef _JK2MP + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*4.0f; //magic number hackery + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; +#endif + } + + +/* speed = VectorLength( parentPS->velocity ); + + // If the player is the rider... + if ( rider->s.number < MAX_CLIENTS ) + {//FIXME: use the vehicle's turning stat in this calc + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + } + else + { + float turnSpeed = pVeh->m_pVehicleInfo->turningSpeed; + if ( !pVeh->m_pVehicleInfo->turnWhenStopped + && !parentPS->speed )//FIXME: or !pVeh->m_ucmd.forwardmove? + {//can't turn when not moving + //FIXME: or ramp up to max turnSpeed? + turnSpeed = 0.0f; + } +#ifdef _JK2MP + if (rider->s.eType == ET_NPC) +#else + if ( !rider || rider->NPC ) +#endif + {//help NPCs out some + turnSpeed *= 2.0f; +#ifdef _JK2MP + if (parentPS->speed > 200.0f) +#else + if ( parent->client->ps.speed > 200.0f ) +#endif + { + turnSpeed += turnSpeed * parentPS->speed/200.0f*0.05f; + } + } + turnSpeed *= pVeh->m_fTimeModifier; + + //default control scheme: strafing turns, mouselook aims + if ( pVeh->m_ucmd.rightmove < 0 ) + { + pVeh->m_vOrientation[YAW] += turnSpeed; + } + else if ( pVeh->m_ucmd.rightmove > 0 ) + { + pVeh->m_vOrientation[YAW] -= turnSpeed; + } + + if ( pVeh->m_pVehicleInfo->malfunctionArmorLevel && pVeh->m_iArmor <= pVeh->m_pVehicleInfo->malfunctionArmorLevel ) + {//damaged badly + } + }*/ + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef _JK2MP //temp hack til mp speeder controls are sorted -rww +void AnimalProcessOri(Vehicle_t *pVeh) +{ + ProcessOrientCommands(pVeh); +} +#endif + +#ifdef QAGAME //back to our game-only functions +static void AnimateVehicle( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_VT_IDLE; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + gentity_t * pilot = (gentity_t *)pVeh->m_pPilot; + gentity_t * parent = (gentity_t *)pVeh->m_pParentEntity; + playerState_t * pilotPS; + playerState_t * parentPS; + float fSpeedPercToMax; + +#ifdef _JK2MP + pilotPS = (pilot)?(pilot->playerState):(0); + parentPS = parent->playerState; +#else + pilotPS = (pilot)?(&pilot->client->ps):(0); + parentPS = &parent->client->ps; +#endif + + // We're dead (boarding is reused here so I don't have to make another variable :-). + if ( parent->health <= 0 ) + { + /* + if ( pVeh->m_iBoarding != -999 ) // Animate the death just once! + { + pVeh->m_iBoarding = -999; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + // FIXME! Why do you keep repeating over and over!!?!?!? Bastard! + //Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_DEATH1, iFlags, iBlend ); + } + */ + return; + } + + // If they're bucking, play the animation and leave... + if ( parent->client->ps.legsAnim == BOTH_VT_BUCK ) + { + // Done with animation? Erase the flag. + if ( parent->client->ps.legsAnimTimer <= 0 ) + { + pVeh->m_ulFlags &= ~VEH_BUCKING; + } + else + { + return; + } + } + else if ( pVeh->m_ulFlags & VEH_BUCKING ) + { + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + Anim = BOTH_VT_BUCK; + iBlend = 500; + Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_BUCK, iFlags, iBlend ); + return; + } + + // Boarding animation. + if ( pVeh->m_iBoarding != 0 ) + { + // We've just started boarding, set the amount of time it will take to finish boarding. + if ( pVeh->m_iBoarding < 0 ) + { + int iAnimLen; + + // Boarding from left... + if ( pVeh->m_iBoarding == -1 ) + { + Anim = BOTH_VT_MOUNT_L; + } + else if ( pVeh->m_iBoarding == -2 ) + { + Anim = BOTH_VT_MOUNT_R; + } + else if ( pVeh->m_iBoarding == -3 ) + { + Anim = BOTH_VT_MOUNT_B; + } + + // Set the delay time (which happens to be the time it takes for the animation to complete). + // NOTE: Here I made it so the delay is actually 70% (0.7f) of the animation time. +#ifdef _JK2MP + iAnimLen = BG_AnimLength( parent->localAnimIndex, Anim ) * 0.7f; +#else + iAnimLen = PM_AnimLength( parent->client->clientInfo.animFileIndex, Anim ) * 0.7f; +#endif + pVeh->m_iBoarding = level.time + iAnimLen; + + // Set the animation, which won't be interrupted until it's completed. + // TODO: But what if he's killed? Should the animation remain persistant??? + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); + if (pilot) + { + Vehicle_SetAnim(pilot, SETANIM_BOTH, Anim, iFlags, iBlend ); + } + return; + } + // Otherwise we're done. + else if ( pVeh->m_iBoarding <= level.time ) + { + pVeh->m_iBoarding = 0; + } + } + + // Percentage of maximum speed relative to current speed. + //float fSpeed = VectorLength( client->ps.velocity ); + fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; + + + // Going in reverse... + if ( fSpeedPercToMax < -0.01f ) + { + Anim = BOTH_VT_WALK_REV; + iBlend = 600; + } + else + { + bool Turbo = (fSpeedPercToMax>0.0f && level.timem_iTurboTime); + bool Walking = (fSpeedPercToMax>0.0f && ((pVeh->m_ucmd.buttons&BUTTON_WALKING) || fSpeedPercToMax<=0.275f)); + bool Running = (fSpeedPercToMax>0.275f); + + + // Remove Crashing Flag + //---------------------- + pVeh->m_ulFlags &= ~VEH_CRASHING; + + if (Turbo) + {// Kicked In Turbo + iBlend = 50; + iFlags = SETANIM_FLAG_OVERRIDE; + Anim = BOTH_VT_TURBO; + } + else + {// No Special Moves + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + Anim = (Walking)?(BOTH_VT_WALK_FWD ):((Running)?(BOTH_VT_RUN_FWD ):(BOTH_VT_IDLE1)); + } + } + Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); +} + +//rwwFIXMEFIXME: This is all going to have to be predicted I think, or it will feel awful +//and lagged +// This function makes sure that the rider's in this vehicle are properly animated. +static void AnimateRiders( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_VT_IDLE; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 500; + gentity_t *pilot = (gentity_t *)pVeh->m_pPilot; + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + playerState_t *pilotPS; + playerState_t *parentPS; + float fSpeedPercToMax; + +#ifdef _JK2MP + pilotPS = pVeh->m_pPilot->playerState; + parentPS = pVeh->m_pPilot->playerState; +#else + pilotPS = &pVeh->m_pPilot->client->ps; + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + + // Boarding animation. + if ( pVeh->m_iBoarding != 0 ) + { + return; + } + + // Percentage of maximum speed relative to current speed. + fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; + + // Going in reverse... +#ifdef _JK2MP //handled in pmove in mp + if (0) +#else + if ( fSpeedPercToMax < -0.01f ) +#endif + { + Anim = BOTH_VT_WALK_REV; + iBlend = 600; + } + else + { + bool HasWeapon = ((pilotPS->weapon != WP_NONE) && (pilotPS->weapon != WP_MELEE)); + bool Attacking = (HasWeapon && !!(pVeh->m_ucmd.buttons&BUTTON_ATTACK)); + bool Right = (pVeh->m_ucmd.rightmove>0); + bool Left = (pVeh->m_ucmd.rightmove<0); + bool Turbo = (fSpeedPercToMax>0.0f && level.timem_iTurboTime); + bool Walking = (fSpeedPercToMax>0.0f && ((pVeh->m_ucmd.buttons&BUTTON_WALKING) || fSpeedPercToMax<=0.275f)); + bool Running = (fSpeedPercToMax>0.275f); + EWeaponPose WeaponPose = WPOSE_NONE; + + + // Remove Crashing Flag + //---------------------- + pVeh->m_ulFlags &= ~VEH_CRASHING; + + + // Put Away Saber When It Is Not Active + //-------------------------------------- +#ifndef _JK2MP + if (HasWeapon && (Turbo || (pilotPS->weapon==WP_SABER && !pilotPS->SaberActive()))) + { + if (pVeh->m_pPilot->s.numberm_pPilot->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels(pVeh->m_pPilot); + } +#endif + + // Don't Interrupt Attack Anims + //------------------------------ +#ifdef _JK2MP + if (pilotPS->weaponTime>0) + { + return; + } +#else + if (pilotPS->torsoAnim>=BOTH_VT_ATL_S && pilotPS->torsoAnim<=BOTH_VT_ATF_G) + { + float bodyCurrent = 0.0f; + int bodyEnd = 0; + if (!!gi.G2API_GetBoneAnimIndex(&pVeh->m_pPilot->ghoul2[pVeh->m_pPilot->playerModel], pVeh->m_pPilot->rootBone, level.time, &bodyCurrent, NULL, &bodyEnd, NULL, NULL, NULL)) + { + if (bodyCurrent<=((float)(bodyEnd)-1.5f)) + { + return; + } + } + } +#endif + + // Compute The Weapon Pose + //-------------------------- + if (pilotPS->weapon==WP_BLASTER) + { + WeaponPose = WPOSE_BLASTER; + } + else if (pilotPS->weapon==WP_SABER) + { + if ( (pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VT_ATL_TO_R_S) + { + pVeh->m_ulFlags &= ~VEH_SABERINLEFTHAND; + } + if (!(pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VT_ATR_TO_L_S) + { + pVeh->m_ulFlags |= VEH_SABERINLEFTHAND; + } + WeaponPose = (pVeh->m_ulFlags&VEH_SABERINLEFTHAND)?(WPOSE_SABERLEFT):(WPOSE_SABERRIGHT); + } + + + if (Attacking && WeaponPose) + {// Attack! + iBlend = 100; + iFlags = SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART; + + if (Turbo) + { + Right = true; + Left = false; + } + + // Auto Aiming + //=============================================== + if (!Left && !Right) // Allow player strafe keys to override + { +#ifndef _JK2MP + if (pVeh->m_pPilot->enemy) + { + vec3_t toEnemy; + float toEnemyDistance; + vec3_t actorRight; + float actorRightDot; + + VectorSubtract(pVeh->m_pPilot->currentOrigin, pVeh->m_pPilot->enemy->currentOrigin, toEnemy); + toEnemyDistance = VectorNormalize(toEnemy); + + AngleVectors(pVeh->m_pParentEntity->currentAngles, 0, actorRight, 0); + actorRightDot = DotProduct(toEnemy, actorRight); + + if (fabsf(actorRightDot)>0.5f || pilotPS->weapon==WP_SABER) + { + Left = (actorRightDot>0.0f); + Right = !Left; + } + else + { + Right = Left = false; + } + } + else +#endif + if (pilotPS->weapon==WP_SABER && !Left && !Right) + { + Left = (WeaponPose==WPOSE_SABERLEFT); + Right = !Left; + } + } + + + if (Left) + {// Attack Left + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_ATL_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VT_ATL_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VT_ATR_TO_L_S; break; + default: assert(0); + } + } + else if (Right) + {// Attack Right + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_ATR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VT_ATL_TO_R_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VT_ATR_S; break; + default: assert(0); + } + } + else + {// Attack Ahead + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_ATF_G; break; + default: assert(0); + } + } + } + else if (Turbo) + {// Kicked In Turbo + iBlend = 50; + iFlags = SETANIM_FLAG_OVERRIDE; + Anim = BOTH_VT_TURBO; + } + else + {// No Special Moves + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + + if (WeaponPose==WPOSE_NONE) + { + if (Walking) + { + Anim = BOTH_VT_WALK_FWD; + } + else if (Running) + { + Anim = BOTH_VT_RUN_FWD; + } + else + { + Anim = BOTH_VT_IDLE1;//(Q_irand(0,1)==0)?(BOTH_VT_IDLE):(BOTH_VT_IDLE1); + } + } + else + { + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VT_IDLE_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VT_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VT_IDLE_SR; break; + default: assert(0); + } + } + }// No Special Moves + } + + Vehicle_SetAnim( pilot, SETANIM_BOTH, Anim, iFlags, iBlend ); +} +#endif //QAGAME + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +//on the client this function will only set up the process command funcs +void G_SetAnimalVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + pVehInfo->AnimateVehicle = AnimateVehicle; + pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; +// pVehInfo->Board = Board; +// pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; + pVehInfo->DeathUpdate = DeathUpdate; +// pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif //QAGAME + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +//this is a BG function too in MP so don't un-bg-compatibilify it -rww +void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strAnimalType ) +{ + // Allocate the Vehicle. +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#else + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#endif +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/code/game/FighterNPC.c b/code/game/FighterNPC.c new file mode 100644 index 0000000..c7eba98 --- /dev/null +++ b/code/game/FighterNPC.c @@ -0,0 +1,2039 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +#endif + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +#ifdef QAGAME //SP or gameside MP +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern void G_VehicleTrace( trace_t *results, const vec3_t start, const vec3_t tMins, const vec3_t tMaxs, const vec3_t end, int passEntityNum, int contentmask ); +#endif + +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); + +#ifdef _JK2MP + +#include "../namespace_begin.h" + +extern void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int BG_GetTime(void); +#endif + +extern void BG_ExternThisSoICanRecompileInDebug( Vehicle_t *pVeh, playerState_t *riderPS ); + +//this stuff has got to be predicted, so.. +bool BG_FighterUpdate(Vehicle_t *pVeh, const usercmd_t *pUcmd, vec3_t trMins, vec3_t trMaxs, float gravity, + void (*traceFunc)( trace_t *results, const vec3_t start, const vec3_t lmins, const vec3_t lmaxs, const vec3_t end, int passEntityNum, int contentMask )) +{ + vec3_t bottom; + playerState_t *parentPS; + qboolean isDead = qfalse; +#ifdef QAGAME //don't do this on client + int i; + + // Make sure the riders are not visible or collidable. + pVeh->m_pVehicleInfo->Ghost( pVeh, pVeh->m_pPilot ); + for ( i = 0; i < pVeh->m_pVehicleInfo->maxPassengers; i++ ) + { + pVeh->m_pVehicleInfo->Ghost( pVeh, pVeh->m_ppPassengers[i] ); + } +#endif + + +#ifdef _JK2MP + parentPS = pVeh->m_pParentEntity->playerState; +#else + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + + if (!parentPS) + { + Com_Error(ERR_DROP, "NULL PS in BG_FighterUpdate (%s)", pVeh->m_pVehicleInfo->name); + return false; + } + + // If we have a pilot, take out gravity (it's a flying craft...). + if ( pVeh->m_pPilot ) + { + parentPS->gravity = 0; +#ifndef _JK2MP //don't need this flag in mp, I.. guess + pVeh->m_pParentEntity->svFlags |= SVF_CUSTOM_GRAVITY; +#endif + } + else + { +#ifndef _JK2MP //don't need this flag in mp, I.. guess + pVeh->m_pParentEntity->svFlags &= ~SVF_CUSTOM_GRAVITY; +#else //in MP set grav back to normal gravity + if (pVeh->m_pVehicleInfo->gravity) + { + parentPS->gravity = pVeh->m_pVehicleInfo->gravity; + } + else + { //it doesn't have gravity specified apparently + parentPS->gravity = gravity; + } +#endif + } + +#ifdef _JK2MP + isDead = (qboolean)((parentPS->eFlags&EF_DEAD)!=0); +#else + isDead = (parentPS->stats[STAT_HEALTH] <= 0 ); +#endif + + /* + if ( isDead || + (pVeh->m_pVehicleInfo->surfDestruction && + pVeh->m_iRemovedSurfaces ) ) + {//can't land if dead or spiralling out of control + pVeh->m_LandTrace.fraction = 1.0f; + pVeh->m_LandTrace.contents = pVeh->m_LandTrace.surfaceFlags = 0; + VectorClear( pVeh->m_LandTrace.plane.normal ); + pVeh->m_LandTrace.allsolid = qfalse; + pVeh->m_LandTrace.startsolid = qfalse; + } + else + { + */ + //argh, no, I need to have a way to see when they impact the ground while damaged. -rww + + // Check to see if the fighter has taken off yet (if it's a certain height above ground). + VectorCopy( parentPS->origin, bottom ); + bottom[2] -= pVeh->m_pVehicleInfo->landingHeight; + + traceFunc( &pVeh->m_LandTrace, parentPS->origin, trMins, trMaxs, bottom, pVeh->m_pParentEntity->s.number, (MASK_NPCSOLID&~CONTENTS_BODY) ); + //} + + return true; +} + +#ifdef QAGAME //ONLY in SP or on server, not cgame + +// Like a think or move command, this updates various vehicle properties. +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + assert(pVeh->m_pParentEntity); + if (!BG_FighterUpdate(pVeh, pUcmd, ((gentity_t *)pVeh->m_pParentEntity)->mins, + ((gentity_t *)pVeh->m_pParentEntity)->maxs, +#ifdef _JK2MP + g_gravity.value, +#else + g_gravity->value, +#endif + G_VehicleTrace)) + { + return false; + } + + if ( !g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ) ) + { + return false; + } + + return true; +} + +// Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. +static bool Board( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + if ( !g_vehicleInfo[VEHICLE_BASE].Board( pVeh, pEnt ) ) + return false; + + // Set the board wait time (they won't be able to do anything, including getting off, for this amount of time). + pVeh->m_iBoarding = level.time + 1500; + + return true; +} + +// Eject an entity from the vehicle. +static bool Eject( Vehicle_t *pVeh, bgEntity_t *pEnt, qboolean forceEject ) +{ + if ( g_vehicleInfo[VEHICLE_BASE].Eject( pVeh, pEnt, forceEject ) ) + { + return true; + } + + return false; +} + +#endif //end game-side only + +//method of decrementing the given angle based on the given taking variable frame times into account +static float PredictedAngularDecrement(float scale, float timeMod, float originalAngle) +{ + float fixedBaseDec = originalAngle*0.05f; + float r = 0.0f; + + if (fixedBaseDec < 0.0f) + { + fixedBaseDec = -fixedBaseDec; + } + + fixedBaseDec *= (1.0f+(1.0f-scale)); + + if (fixedBaseDec < 0.1f) + { //don't increment in incredibly small fractions, it would eat up unnecessary bandwidth. + fixedBaseDec = 0.1f; + } + + fixedBaseDec *= (timeMod*0.1f); + if (originalAngle > 0.0f) + { //subtract + r = (originalAngle-fixedBaseDec); + if (r < 0.0f) + { + r = 0.0f; + } + } + else if (originalAngle < 0.0f) + { //add + r = (originalAngle+fixedBaseDec); + if (r > 0.0f) + { + r = 0.0f; + } + } + + return r; +} + +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver +qboolean FighterIsInSpace( gentity_t *gParent ) +{ + if ( gParent + && gParent->client + && gParent->client->inSpaceIndex + && gParent->client->inSpaceIndex < ENTITYNUM_WORLD ) + { + return qtrue; + } + return qfalse; +} +#endif + +qboolean FighterOverValidLandingSurface( Vehicle_t *pVeh ) +{ + if ( pVeh->m_LandTrace.fraction < 1.0f //ground present + && pVeh->m_LandTrace.plane.normal[2] >= MIN_LANDING_SLOPE )//flat enough + //FIXME: also check for a certain surface flag ... "landing zones"? + { + return qtrue; + } + return qfalse; +} + +qboolean FighterIsLanded( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + if ( FighterOverValidLandingSurface( pVeh ) + && !parentPS->speed )//stopped + { + return qtrue; + } + return qfalse; +} + +qboolean FighterIsLanding( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + + if ( FighterOverValidLandingSurface( pVeh ) +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + && pVeh->m_pVehicleInfo->Inhabited( pVeh )//has to have a driver in order to be capable of landing +#endif + && (pVeh->m_ucmd.forwardmove < 0||pVeh->m_ucmd.upmove<0) //decelerating or holding crouch button + && parentPS->speed <= MIN_LANDING_SPEED )//going slow enough to start landing - was using pVeh->m_pVehicleInfo->speedIdle, but that's still too fast + { + return qtrue; + } + return qfalse; +} + +qboolean FighterIsLaunching( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + + if ( FighterOverValidLandingSurface( pVeh ) +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + && pVeh->m_pVehicleInfo->Inhabited( pVeh )//has to have a driver in order to be capable of landing +#endif + && pVeh->m_ucmd.upmove > 0 //trying to take off + && parentPS->speed <= 200.0f )//going slow enough to start landing - was using pVeh->m_pVehicleInfo->speedIdle, but that's still too fast + { + return qtrue; + } + return qfalse; +} + +qboolean FighterSuspended( Vehicle_t *pVeh, playerState_t *parentPS ) +{ +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + if (!pVeh->m_pPilot//empty + && !parentPS->speed//not moving + && pVeh->m_ucmd.forwardmove <= 0//not trying to go forward for whatever reason + && pVeh->m_pParentEntity != NULL + && (((gentity_t *)pVeh->m_pParentEntity)->spawnflags&2) )//SUSPENDED spawnflag is on + { + return qtrue; + } + return qfalse; +#elif CGAME + return qfalse; +#endif +} + +#ifdef CGAME +extern void trap_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); //cg_syscalls.c +extern sfxHandle_t trap_S_RegisterSound( const char *sample); //cg_syscalls.c +#endif + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +#define FIGHTER_MIN_TAKEOFF_FRACTION 0.7f +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + bgEntity_t *parent = pVeh->m_pParentEntity; + qboolean isLandingOrLaunching = qfalse; + /* +#ifndef _JK2MP//SP + int curTime = level.time; +#elif QAGAME//MP GAME + int curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + int curTime = pm->cmd.serverTime; +#endif + */ + //this function should only be called from pmove.. if it gets called elsehwere, + //obviously this will explode. + int curTime = pm->cmd.serverTime; + +#ifdef _JK2MP + playerState_t *parentPS = parent->playerState; +#else + playerState_t *parentPS = &parent->client->ps; +#endif + +#ifdef _JK2MP + if ( parentPS->hyperSpaceTime + && curTime - parentPS->hyperSpaceTime < HYPERSPACE_TIME ) + {//Going to Hyperspace + //totally override movement + float timeFrac = ((float)(curTime-parentPS->hyperSpaceTime))/HYPERSPACE_TIME; + if ( timeFrac < HYPERSPACE_TELEPORT_FRAC ) + {//for first half, instantly jump to top speed! + if ( !(parentPS->eFlags2&EF2_HYPERSPACE) ) + {//waiting to face the right direction, do nothing + parentPS->speed = 0.0f; + } + else + { + if ( parentPS->speed < HYPERSPACE_SPEED ) + {//just started hyperspace +//MIKE: This is going to play the sound twice for the predicting client, I suggest using +//a predicted event or only doing it game-side. -rich +#ifdef QAGAME//MP GAME-side + //G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_LOCAL, pVeh->m_pVehicleInfo->soundHyper ); +#elif CGAME//MP CGAME-side + trap_S_StartSound( NULL, pm->ps->clientNum, CHAN_LOCAL, pVeh->m_pVehicleInfo->soundHyper ); +#endif + } + + parentPS->speed = HYPERSPACE_SPEED; + } + } + else + {//slow from top speed to 200... + parentPS->speed = 200.0f + ((1.0f-timeFrac)*(1.0f/HYPERSPACE_TELEPORT_FRAC)*(HYPERSPACE_SPEED-200.0f)); + //don't mess with acceleration, just pop to the high velocity + if ( VectorLength( parentPS->velocity ) < parentPS->speed ) + { + VectorScale( parentPS->moveDir, parentPS->speed, parentPS->velocity ); + } + } + return; + } +#endif + + if ( pVeh->m_iDropTime >= curTime ) + {//no speed, just drop + parentPS->speed = 0.0f; + parentPS->gravity = 800; + return; + } + + isLandingOrLaunching = (FighterIsLanding( pVeh, parentPS )||FighterIsLaunching( pVeh, parentPS )); + + // If we are hitting the ground, just allow the fighter to go up and down. + if ( isLandingOrLaunching//going slow enough to start landing + && (pVeh->m_ucmd.forwardmove<=0||pVeh->m_LandTrace.fraction<=FIGHTER_MIN_TAKEOFF_FRACTION) )//not trying to accelerate away already (or: you are trying to, but not high enough off the ground yet) + {//FIXME: if start to move forward and fly over something low while still going relatively slow, you may try to land even though you don't mean to... + //float fInvFrac = 1.0f - pVeh->m_LandTrace.fraction; + + if ( pVeh->m_ucmd.upmove > 0 ) + { +#ifdef _JK2MP + if ( parentPS->velocity[2] <= 0 + && pVeh->m_pVehicleInfo->soundTakeOff ) + {//taking off for the first time +#ifdef QAGAME//MP GAME-side + G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_AUTO, pVeh->m_pVehicleInfo->soundTakeOff ); +#endif + } +#endif + parentPS->velocity[2] += pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier;// * ( /*fInvFrac **/ 1.5f ); + } + else if ( pVeh->m_ucmd.upmove < 0 ) + { + parentPS->velocity[2] -= pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier;// * ( /*fInvFrac **/ 1.8f ); + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( pVeh->m_LandTrace.fraction != 0.0f ) + { + parentPS->velocity[2] -= pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + + if ( pVeh->m_LandTrace.fraction <= FIGHTER_MIN_TAKEOFF_FRACTION ) + { + //pVeh->m_pParentEntity->client->ps.velocity[0] *= pVeh->m_LandTrace.fraction; + //pVeh->m_pParentEntity->client->ps.velocity[1] *= pVeh->m_LandTrace.fraction; + + //remember to always base this stuff on the time modifier! otherwise, you create + //framerate-dependancy issues and break prediction in MP -rww + //parentPS->velocity[2] *= pVeh->m_LandTrace.fraction; + //it's not an angle, but hey + parentPS->velocity[2] = PredictedAngularDecrement(pVeh->m_LandTrace.fraction, pVeh->m_fTimeModifier*5.0f, parentPS->velocity[2]); + + parentPS->speed = 0; + } + } + + // Make sure they don't pitch as they near the ground. + //pVeh->m_vOrientation[PITCH] *= 0.7f; + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.7f, pVeh->m_fTimeModifier*10.0f, pVeh->m_vOrientation[PITCH]); + + return; + } + + if ( (pVeh->m_ucmd.upmove > 0) && pVeh->m_pVehicleInfo->turboSpeed ) + { + if ((curTime - pVeh->m_iTurboTime)>pVeh->m_pVehicleInfo->turboRecharge) + { + pVeh->m_iTurboTime = (curTime + pVeh->m_pVehicleInfo->turboDuration); + +#ifdef QAGAME//MP GAME-side + //NOTE: turbo sound can't be part of effect if effect is played on every muzzle! + if ( pVeh->m_pVehicleInfo->soundTurbo ) + { + G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_AUTO, pVeh->m_pVehicleInfo->soundTurbo ); + } +#endif + } + } + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + if ( curTime < pVeh->m_iTurboTime ) + {//going turbo speed + speedMax = pVeh->m_pVehicleInfo->turboSpeed; + //double our acceleration + //speedInc *= 2.0f; + //no no no! this would el breako el predictiono! we want the following... -rww + speedInc = (pVeh->m_pVehicleInfo->acceleration*2.0f) * pVeh->m_fTimeModifier; + + //force us to move forward + pVeh->m_ucmd.forwardmove = 127; +#ifdef _JK2MP//SP can cheat and just check m_iTurboTime directly... :) + //add flag to let cgame know to draw the iTurboFX effect + parentPS->eFlags |= EF_JETPACK_ACTIVE; +#endif + } + /* + //FIXME: if turbotime is up and we're waiting for it to recharge, should our max speed drop while we recharge? + else if ( (curTime - pVeh->m_iTurboTime)<3000 ) + {//still waiting for the recharge + speedMax = pVeh->m_pVehicleInfo->speedMax*0.75; + } + */ + else + {//normal max speed + speedMax = pVeh->m_pVehicleInfo->speedMax; +#ifdef _JK2MP//SP can cheat and just check m_iTurboTime directly... :) + if ( (parentPS->eFlags&EF_JETPACK_ACTIVE) ) + {//stop cgame from playing the turbo exhaust effect + parentPS->eFlags &= ~EF_JETPACK_ACTIVE; + } +#endif + } + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + + if ( (parentPS->brokenLimbs&(1<brokenLimbs&(1<m_iRemovedSurfaces + || parentPS->electrifyTime>=curTime) + { //go out of control + parentPS->speed += speedInc; + //Why set forwardmove? PMove code doesn't use it... does it? + pVeh->m_ucmd.forwardmove = 127; + } +#ifdef QAGAME //well, the thing is always going to be inhabited if it's being predicted! + else if ( FighterSuspended( pVeh, parentPS ) ) + { + parentPS->speed = 0; + pVeh->m_ucmd.forwardmove = 0; + } + else if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) + && parentPS->speed > 0 ) + {//pilot jumped out while we were moving forward (not landing or landed) so just keep the throttle locked + //Why set forwardmove? PMove code doesn't use it... does it? + pVeh->m_ucmd.forwardmove = 127; + } +#endif + else if ( ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) && pVeh->m_LandTrace.fraction >= 0.05f ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + pVeh->m_ucmd.forwardmove = 127; + } + else if ( pVeh->m_ucmd.forwardmove < 0 + || pVeh->m_ucmd.upmove < 0 ) + {//decelerating or braking + if ( pVeh->m_ucmd.upmove < 0 ) + {//braking (trying to land?), slow down faster + if ( pVeh->m_ucmd.forwardmove ) + {//decelerator + brakes + speedInc += pVeh->m_pVehicleInfo->braking; + speedIdleDec += pVeh->m_pVehicleInfo->braking; + } + else + {//just brakes + speedInc = speedIdleDec = pVeh->m_pVehicleInfo->braking; + } + } + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + if ( FighterOverValidLandingSurface( pVeh ) ) + {//there's ground below us and we're trying to slow down, slow down faster + parentPS->speed -= speedInc; + } + else + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < MIN_LANDING_SPEED ) + {//unless you can land, don't drop below the landing speed!!! This way you can't come to a dead stop in mid-air + parentPS->speed = MIN_LANDING_SPEED; + } + } + } + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + pVeh->m_ucmd.forwardmove = 127; + } + else if ( speedMin >= 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + } + //else not accel, decel or braking + else if ( pVeh->m_pVehicleInfo->throttleSticks ) + {//we're using a throttle that sticks at current speed + if ( parentPS->speed <= MIN_LANDING_SPEED ) + {//going less than landing speed + if ( FighterOverValidLandingSurface( pVeh ) ) + {//close to ground and not going very fast + //slow to a stop if within landing height and not accel/decel/braking + if ( parentPS->speed > 0 ) + {//slow down + parentPS->speed -= speedIdleDec; + } + else if ( parentPS->speed < 0 ) + {//going backwards, slow down + parentPS->speed += speedIdleDec; + } + } + else + {//not over a valid landing surf, but going too slow + //speed up to idle speed if not over a valid landing surf and not accel/decel/braking + if ( parentPS->speed < speedIdle ) + { + parentPS->speed += speedIdleAccel; + if ( parentPS->speed > speedIdle ) + { + parentPS->speed = speedIdle; + } + } + } + } + } + else + {//then speed up or slow down to idle speed + //accelerate to cruising speed only, otherwise, just coast + // If they've launched, apply some constant motion. + if ( (pVeh->m_LandTrace.fraction >= 1.0f //no ground + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE )//or can't land on ground below us + && speedIdle > 0 ) + {//not above ground and have an idle speed + //float fSpeed = pVeh->m_pParentEntity->client->ps.speed; + if ( parentPS->speed < speedIdle ) + { + parentPS->speed += speedIdleAccel; + if ( parentPS->speed > speedIdle ) + { + parentPS->speed = speedIdle; + } + } + else if ( parentPS->speed > 0 ) + {//slow down + parentPS->speed -= speedIdleDec; + + if ( parentPS->speed < speedIdle ) + { + parentPS->speed = speedIdle; + } + } + } + else//either close to ground or no idle speed + {//slow to a stop if no idle speed or within landing height and not accel/decel/braking + if ( parentPS->speed > 0 ) + {//slow down + parentPS->speed -= speedIdleDec; + } + else if ( parentPS->speed < 0 ) + {//going backwards, slow down + parentPS->speed += speedIdleDec; + } + } + } + } + else + { + if ( pVeh->m_ucmd.forwardmove < 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + if ( pVeh->m_ucmd.upmove < 0 ) + { + pVeh->m_ucmd.upmove = 0; + } + +#ifndef _JK2MP + if ( !pVeh->m_pVehicleInfo->strafePerc || (!g_speederControlScheme->value && !pVeh->m_pParentEntity->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + pVeh->m_ucmd.rightmove = 0; + } +#endif + } + +#if 1//This is working now, but there are some transitional jitters... Rich? +//STRAFING============================================================================== + if ( pVeh->m_pVehicleInfo->strafePerc +#ifdef QAGAME//only do this check on GAME side, because if it's CGAME, it's being predicted, and it's only predicted if the local client is the driver + && pVeh->m_pVehicleInfo->Inhabited( pVeh )//has to have a driver in order to be capable of landing +#endif + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTimem_LandTrace.fraction >= 1.0f//no grounf + ||pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE//can't land here + ||parentPS->speed>MIN_LANDING_SPEED)//going too fast to land + && pVeh->m_ucmd.rightmove ) + {//strafe + vec3_t vAngles, vRight; + float strafeSpeed = (pVeh->m_pVehicleInfo->strafePerc*speedMax)*5.0f; + VectorCopy( pVeh->m_vOrientation, vAngles ); + vAngles[PITCH] = vAngles[ROLL] = 0; + AngleVectors( vAngles, NULL, vRight, NULL ); + + if ( pVeh->m_ucmd.rightmove > 0 ) + {//strafe right + //FIXME: this will probably make it possible to cheat and + // go faster than max speed if you keep turning and strafing... + if ( parentPS->hackingTime > -MAX_STRAFE_TIME ) + {//can strafe right for 2 seconds + float curStrafeSpeed = DotProduct( parentPS->velocity, vRight ); + if ( curStrafeSpeed > 0.0f ) + {//if > 0, already strafing right + strafeSpeed -= curStrafeSpeed;//so it doesn't add up + } + if ( strafeSpeed > 0 ) + { + VectorMA( parentPS->velocity, strafeSpeed*pVeh->m_fTimeModifier, vRight, parentPS->velocity ); + } + parentPS->hackingTime -= 50*pVeh->m_fTimeModifier; + } + } + else + {//strafe left + if ( parentPS->hackingTime < MAX_STRAFE_TIME ) + {//can strafe left for 2 seconds + float curStrafeSpeed = DotProduct( parentPS->velocity, vRight ); + if ( curStrafeSpeed < 0.0f ) + {//if < 0, already strafing left + strafeSpeed += curStrafeSpeed;//so it doesn't add up + } + if ( strafeSpeed > 0 ) + { + VectorMA( parentPS->velocity, -strafeSpeed*pVeh->m_fTimeModifier, vRight, parentPS->velocity ); + } + parentPS->hackingTime += 50*pVeh->m_fTimeModifier; + } + } + //strafing takes away from forward speed? If so, strafePerc above should use speedMax + //parentPS->speed *= (1.0f-pVeh->m_pVehicleInfo->strafePerc); + } + else//if ( parentPS->hackingTimef ) + { + if ( parentPS->hackingTime > 0 ) + { + parentPS->hackingTime -= 50*pVeh->m_fTimeModifier; + if ( parentPS->hackingTime < 0 ) + { + parentPS->hackingTime = 0.0f; + } + } + else if ( parentPS->hackingTime < 0 ) + { + parentPS->hackingTime += 50*pVeh->m_fTimeModifier; + if ( parentPS->hackingTime > 0 ) + { + parentPS->hackingTime = 0.0f; + } + } + } +//STRAFING============================================================================== +#endif + + if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + +#ifdef QAGAME//FIXME: get working in GAME and CGAME + if ((pVeh->m_vOrientation[PITCH]*0.1f) > 10.0f) + { //pitched downward, increase speed more and more based on our tilt + if ( FighterIsInSpace( (gentity_t *)parent ) ) + {//in space, do nothing with speed base on pitch... + } + else + { + //really should only do this when on a planet + float mult = pVeh->m_vOrientation[PITCH]*0.1f; + if (mult < 1.0f) + { + mult = 1.0f; + } + parentPS->speed = PredictedAngularDecrement(mult, pVeh->m_fTimeModifier*10.0f, parentPS->speed); + } + } + + if (pVeh->m_iRemovedSurfaces + || parentPS->electrifyTime>=curTime) + { //going down + if ( FighterIsInSpace( (gentity_t *)parent ) ) + {//we're in a valid trigger_space brush + //simulate randomness + if ( !(parent->s.number&3) ) + {//even multiple of 3, don't do anything + parentPS->gravity = 0; + } + else if ( !(parent->s.number&2) ) + {//even multiple of 2, go up + parentPS->gravity = -500.0f; + parentPS->velocity[2] = 80.0f; + } + else + {//odd number, go down + parentPS->gravity = 500.0f; + parentPS->velocity[2] = -80.0f; + } + } + else + {//over a planet + parentPS->gravity = 500.0f; + parentPS->velocity[2] = -80.0f; + } + } + else if ( FighterSuspended( pVeh, parentPS ) ) + { + parentPS->gravity = 0; + } + else if ( (!parentPS->speed||parentPS->speed < speedIdle) && pVeh->m_ucmd.upmove <= 0 ) + {//slowing down or stopped and not trying to take off + if ( FighterIsInSpace( (gentity_t *)parent ) ) + {//we're in space, stopping doesn't make us drift downward + if ( FighterOverValidLandingSurface( pVeh ) ) + {//well, there's something below us to land on, so go ahead and lower us down to it + parentPS->gravity = (speedIdle - parentPS->speed)/4; + } + } + else + {//over a planet + parentPS->gravity = (speedIdle - parentPS->speed)/4; + } + } + else + { + parentPS->gravity = 0; + } +#else//FIXME: get above checks working in GAME and CGAME + parentPS->gravity = 0; +#endif + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +extern void BG_VehicleTurnRateForSpeed( Vehicle_t *pVeh, float speed, float *mPitchOverride, float *mYawOverride ); +static void FighterWingMalfunctionCheck( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + float mPitchOverride = 1.0f; + float mYawOverride = 1.0f; + BG_VehicleTurnRateForSpeed( pVeh, parentPS->speed, &mPitchOverride, &mYawOverride ); + //check right wing damage + if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] += (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*50.0f; + } + else if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] += (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*12.5f; + } + + //check left wing damage + if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] -= (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*50.0f; + } + else if ( (parentPS->brokenLimbs&(1<m_vOrientation[ROLL] -= (sin( pVeh->m_ucmd.serverTime*0.001 )+1.0f)*pVeh->m_fTimeModifier*mYawOverride*12.5f; + } + +} + +static void FighterNoseMalfunctionCheck( Vehicle_t *pVeh, playerState_t *parentPS ) +{ + float mPitchOverride = 1.0f; + float mYawOverride = 1.0f; + BG_VehicleTurnRateForSpeed( pVeh, parentPS->speed, &mPitchOverride, &mYawOverride ); + //check nose damage + if ( (parentPS->brokenLimbs&(1<m_vOrientation[PITCH] += sin( pVeh->m_ucmd.serverTime*0.001 )*pVeh->m_fTimeModifier*mPitchOverride*50.0f; + } + else if ( (parentPS->brokenLimbs&(1<m_vOrientation[PITCH] += sin( pVeh->m_ucmd.serverTime*0.001 )*pVeh->m_fTimeModifier*mPitchOverride*20.0f; + } +} + +static void FighterDamageRoutine( Vehicle_t *pVeh, bgEntity_t *parent, playerState_t *parentPS, playerState_t *riderPS, qboolean isDead ) +{ + if ( !pVeh->m_iRemovedSurfaces ) + {//still in one piece + if ( pVeh->m_pParentEntity && isDead ) + {//death spiral + pVeh->m_ucmd.upmove = 0; + //FIXME: don't bias toward pitching down when not in space + /* + if ( FighterIsInSpace( pVeh->m_pParentEntity ) ) + { + } + else + */ + if ( !(pVeh->m_pParentEntity->s.number%3) ) + {//NOT everyone should do this + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + } + } + else if ( !(pVeh->m_pParentEntity->s.number%2) ) + { + pVeh->m_vOrientation[PITCH] -= pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > -60.0f ) + { + pVeh->m_vOrientation[PITCH] = -60.0f; + } + } + } + if ( (pVeh->m_pParentEntity->s.number%2) ) + { + pVeh->m_vOrientation[YAW] += pVeh->m_fTimeModifier; + pVeh->m_vOrientation[ROLL] += pVeh->m_fTimeModifier*4.0f; + } + else + { + pVeh->m_vOrientation[YAW] -= pVeh->m_fTimeModifier; + pVeh->m_vOrientation[ROLL] -= pVeh->m_fTimeModifier*4.0f; + } + } + return; + } + + //if we get into here we have at least one broken piece + pVeh->m_ucmd.upmove = 0; + + //if you're off the ground and not suspended, pitch down + //FIXME: not in space! + if ( pVeh->m_LandTrace.fraction >= 0.1f ) + { + if ( !FighterSuspended( pVeh, parentPS ) ) + { + //pVeh->m_ucmd.forwardmove = 0; + //FIXME: don't bias towards pitching down when in space... + if ( !(pVeh->m_pParentEntity->s.number%2) ) + {//NOT everyone should do this + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + } + } + else if ( !(pVeh->m_pParentEntity->s.number%3) ) + { + pVeh->m_vOrientation[PITCH] -= pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > -60.0f ) + { + pVeh->m_vOrientation[PITCH] = -60.0f; + } + } + } + //else: just keep going forward + } + } +#ifdef QAGAME + if ( pVeh->m_LandTrace.fraction < 1.0f ) + { //if you land at all when pieces of your ship are missing, then die + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + gentity_t *killer = parent; +#ifdef _JK2MP//only have this info in MP... + if (parent->client->ps.otherKiller < ENTITYNUM_WORLD && + parent->client->ps.otherKillerTime > level.time) + { + gentity_t *potentialKiller = &g_entities[parent->client->ps.otherKiller]; + + if (potentialKiller->inuse && potentialKiller->client) + { //he's valid I guess + killer = potentialKiller; + } + } +#endif + G_Damage(parent, killer, killer, vec3_origin, parent->client->ps.origin, 99999, DAMAGE_NO_ARMOR, MOD_SUICIDE); + } +#endif + + if ( ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) && + ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) ) + { //wings on both side broken + float factor = 2.0f; + if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { //all wings broken + factor *= 2.0f; + } + + if ( !(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5) ) + {//won't yaw, so increase roll factor + factor *= 4.0f; + } + + pVeh->m_vOrientation[ROLL] += (pVeh->m_fTimeModifier*factor); //do some spiralling + } + else if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { //left wing broken + float factor = 2.0f; + if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D)) + { //if both are broken.. + factor *= 2.0f; + } + + if ( !(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5) ) + {//won't yaw, so increase roll factor + factor *= 4.0f; + } + + pVeh->m_vOrientation[ROLL] += factor*pVeh->m_fTimeModifier; + } + else if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) || + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) + { //right wing broken + float factor = 2.0f; + if ((pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) + { //if both are broken.. + factor *= 2.0f; + } + + if ( !(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5) ) + {//won't yaw, so increase roll factor + factor *= 4.0f; + } + + pVeh->m_vOrientation[ROLL] -= factor*pVeh->m_fTimeModifier; + } +} + +#ifdef _JK2MP + +#ifdef VEH_CONTROL_SCHEME_4 + +#define FIGHTER_TURNING_MULTIPLIER 0.8f//was 1.6f //magic number hackery +#define FIGHTER_TURNING_DEADZONE 0.25f//no turning if offset is this much +void FighterRollAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ +/* + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); +*/ + float angDif = AngleSubtract(pVeh->m_vPrevRiderViewAngles[YAW],riderPS->viewangles[YAW]);///2.0f;//AngleSubtract(pVeh->m_vPrevRiderViewAngles[YAW], riderPS->viewangles[YAW]); + /* + if ( fabs( angDif ) < FIGHTER_TURNING_DEADZONE ) + { + angDif = 0.0f; + } + else if ( angDif >= FIGHTER_TURNING_DEADZONE ) + { + angDif -= FIGHTER_TURNING_DEADZONE; + } + else if ( angDif <= -FIGHTER_TURNING_DEADZONE ) + { + angDif += FIGHTER_TURNING_DEADZONE; + } + */ + + angDif *= 0.5f; + if ( angDif > 0.0f ) + { + angDif *= angDif; + } + else if ( angDif < 0.0f ) + { + angDif *= -angDif; + } + + if (parentPS && parentPS->speed) + { + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*FIGHTER_TURNING_MULTIPLIER; + + if ( pVeh->m_pVehicleInfo->speedDependantTurning ) + { + float speedFrac = 1.0f; + if ( pVeh->m_LandTrace.fraction >= 1.0f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + { + float s = parentPS->speed; + if (s < 0.0f) + { + s = -s; + } + speedFrac = (s/(pVeh->m_pVehicleInfo->speedMax*0.75f)); + if ( speedFrac < 0.25f ) + { + speedFrac = 0.25f; + } + else if ( speedFrac > 1.0f ) + { + speedFrac = 1.0f; + } + } + angDif *= speedFrac; + } + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[ROLL] = AngleNormalize180(pVeh->m_vOrientation[ROLL] + angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} + +void FighterYawAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(pVeh->m_vPrevRiderViewAngles[YAW],riderPS->viewangles[YAW]);///2.0f;//AngleSubtract(pVeh->m_vPrevRiderViewAngles[YAW], riderPS->viewangles[YAW]); + if ( fabs( angDif ) < FIGHTER_TURNING_DEADZONE ) + { + angDif = 0.0f; + } + else if ( angDif >= FIGHTER_TURNING_DEADZONE ) + { + angDif -= FIGHTER_TURNING_DEADZONE; + } + else if ( angDif <= -FIGHTER_TURNING_DEADZONE ) + { + angDif += FIGHTER_TURNING_DEADZONE; + } + + angDif *= 0.5f; + if ( angDif > 0.0f ) + { + angDif *= angDif; + } + else if ( angDif < 0.0f ) + { + angDif *= -angDif; + } + + if (parentPS && parentPS->speed) + { + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*FIGHTER_TURNING_MULTIPLIER; + + if ( pVeh->m_pVehicleInfo->speedDependantTurning ) + { + float speedFrac = 1.0f; + if ( pVeh->m_LandTrace.fraction >= 1.0f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + { + float s = parentPS->speed; + if (s < 0.0f) + { + s = -s; + } + speedFrac = (s/(pVeh->m_pVehicleInfo->speedMax*0.75f)); + if ( speedFrac < 0.25f ) + { + speedFrac = 0.25f; + } + else if ( speedFrac > 1.0f ) + { + speedFrac = 1.0f; + } + } + angDif *= speedFrac; + } + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - (angDif*(pVeh->m_fTimeModifier*0.2f)) ); + } +} + +void FighterPitchAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(0,riderPS->viewangles[PITCH]);//AngleSubtract(pVeh->m_vPrevRiderViewAngles[PITCH], riderPS->viewangles[PITCH]); + if ( fabs( angDif ) < FIGHTER_TURNING_DEADZONE ) + { + angDif = 0.0f; + } + else if ( angDif >= FIGHTER_TURNING_DEADZONE ) + { + angDif -= FIGHTER_TURNING_DEADZONE; + } + else if ( angDif <= -FIGHTER_TURNING_DEADZONE ) + { + angDif += FIGHTER_TURNING_DEADZONE; + } + + angDif *= 0.5f; + if ( angDif > 0.0f ) + { + angDif *= angDif; + } + else if ( angDif < 0.0f ) + { + angDif *= -angDif; + } + + if (parentPS && parentPS->speed) + { + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*FIGHTER_TURNING_MULTIPLIER; + + if ( pVeh->m_pVehicleInfo->speedDependantTurning ) + { + float speedFrac = 1.0f; + if ( pVeh->m_LandTrace.fraction >= 1.0f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + { + float s = parentPS->speed; + if (s < 0.0f) + { + s = -s; + } + speedFrac = (s/(pVeh->m_pVehicleInfo->speedMax*0.75f)); + if ( speedFrac < 0.25f ) + { + speedFrac = 0.25f; + } + else if ( speedFrac > 1.0f ) + { + speedFrac = 1.0f; + } + } + angDif *= speedFrac; + } + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[PITCH] = AngleNormalize180(pVeh->m_vOrientation[PITCH] - (angDif*(pVeh->m_fTimeModifier*0.2f)) ); + } +} + +#else// VEH_CONTROL_SCHEME_4 + +void FighterYawAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*0.8f; //magic number hackery + + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} + +void FighterPitchAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(pVeh->m_vOrientation[PITCH], riderPS->viewangles[PITCH]); + + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*0.8f; //magic number hackery + + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[PITCH] = AngleNormalize360(pVeh->m_vOrientation[PITCH] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} +#endif// VEH_CONTROL_SCHEME_4 + +void FighterPitchClamp(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS, int curTime ) +{ + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + {//cap pitch reasonably + if ( pVeh->m_pVehicleInfo->pitchLimit != -1 + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTime < curTime ) + { + if (pVeh->m_vOrientation[PITCH] > pVeh->m_pVehicleInfo->pitchLimit ) + { + pVeh->m_vOrientation[PITCH] = pVeh->m_pVehicleInfo->pitchLimit; + } + else if (pVeh->m_vOrientation[PITCH] < -pVeh->m_pVehicleInfo->pitchLimit) + { + pVeh->m_vOrientation[PITCH] = -pVeh->m_pVehicleInfo->pitchLimit; + } + } + } +} + +#endif// _JK2MP + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessOrientCommands the Vehicle. +static void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + + bgEntity_t *parent = pVeh->m_pParentEntity; + playerState_t *parentPS, *riderPS; + float angleTimeMod; +#ifdef QAGAME + const float groundFraction = 0.1f; +#endif + float curRoll = 0.0f; + qboolean isDead = qfalse; + qboolean isLandingOrLanded = qfalse; +#ifndef _JK2MP//SP + int curTime = level.time; +#elif QAGAME//MP GAME + int curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + int curTime = pm->cmd.serverTime; +#endif + +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } +#else + gentity_t *rider = parent->owner; +#endif + +#ifdef _JK2MP + if ( !rider ) +#else + if ( !rider || !rider->client ) +#endif + { + rider = parent; + } + +#ifdef _JK2MP + parentPS = parent->playerState; + riderPS = rider->playerState; + isDead = (qboolean)((parentPS->eFlags&EF_DEAD)!=0); +#else + parentPS = &parent->client->ps; + riderPS = &rider->client->ps; + isDead = (parentPS->stats[STAT_HEALTH] <= 0 ); +#endif + +#ifdef _JK2MP + +#ifdef VEH_CONTROL_SCHEME_4 + if ( parentPS->hyperSpaceTime + && (curTime - parentPS->hyperSpaceTime) < HYPERSPACE_TIME ) + {//Going to Hyperspace + //VectorCopy( riderPS->viewangles, pVeh->m_vOrientation ); + //VectorCopy( riderPS->viewangles, parentPS->viewangles ); + VectorCopy( parentPS->viewangles, pVeh->m_vOrientation ); + VectorClear( pVeh->m_vPrevRiderViewAngles ); + pVeh->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(riderPS->viewangles[YAW]); + FighterPitchClamp( pVeh, riderPS, parentPS, curTime ); + return; + } + else if ( parentPS->vehTurnaroundIndex + && parentPS->vehTurnaroundTime > curTime ) + {//in turn-around brush + //VectorCopy( riderPS->viewangles, pVeh->m_vOrientation ); + //VectorCopy( riderPS->viewangles, parentPS->viewangles ); + VectorCopy( parentPS->viewangles, pVeh->m_vOrientation ); + VectorClear( pVeh->m_vPrevRiderViewAngles ); + pVeh->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(riderPS->viewangles[YAW]); + FighterPitchClamp( pVeh, riderPS, parentPS, curTime ); + return; + } + +#else// VEH_CONTROL_SCHEME_4 + + if ( parentPS->hyperSpaceTime + && (curTime - parentPS->hyperSpaceTime) < HYPERSPACE_TIME ) + {//Going to Hyperspace + VectorCopy( riderPS->viewangles, pVeh->m_vOrientation ); + VectorCopy( riderPS->viewangles, parentPS->viewangles ); + return; + } +#endif// VEH_CONTROL_SCHEME_4 + +#endif//_JK2MP + + if ( pVeh->m_iDropTime >= curTime ) + {//you can only YAW during this + parentPS->viewangles[YAW] = pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; +#ifdef VEH_CONTROL_SCHEME_4 + VectorClear( pVeh->m_vPrevRiderViewAngles ); + pVeh->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(riderPS->viewangles[YAW]); +#endif// VEH_CONTROL_SCHEME_4 + return; + } + + angleTimeMod = pVeh->m_fTimeModifier; + + if ( isDead || parentPS->electrifyTime>=curTime || + (pVeh->m_pVehicleInfo->surfDestruction && + pVeh->m_iRemovedSurfaces && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_C) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_D) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_E) && + (pVeh->m_iRemovedSurfaces & SHIPSURF_BROKEN_F)) ) + { //do some special stuff for when all the wings are torn off + FighterDamageRoutine(pVeh, parent, parentPS, riderPS, isDead); + pVeh->m_vOrientation[ROLL] = AngleNormalize180( pVeh->m_vOrientation[ROLL] ); + return; + } + + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + pVeh->m_vOrientation[ROLL] = PredictedAngularDecrement(0.95f, angleTimeMod*2.0f, pVeh->m_vOrientation[ROLL]); + } + + isLandingOrLanded = (FighterIsLanding( pVeh, parentPS )||FighterIsLanded( pVeh, parentPS )); + + if (!isLandingOrLanded) + { //don't do this stuff while landed.. I guess. I don't want ships spinning in place, looks silly. + int m = 0; + float aVelDif; + float dForVel; + + FighterWingMalfunctionCheck( pVeh, parentPS ); + + while (m < 3) + { + aVelDif = pVeh->m_vFullAngleVelocity[m]; + + if (aVelDif != 0.0f) + { + dForVel = (aVelDif*0.1f)*pVeh->m_fTimeModifier; + if (dForVel > 1.0f || dForVel < -1.0f) + { + pVeh->m_vOrientation[m] += dForVel; + pVeh->m_vOrientation[m] = AngleNormalize180(pVeh->m_vOrientation[m]); + if (m == PITCH) + { //don't pitch downward into ground even more. + if (pVeh->m_vOrientation[m] > 90.0f && (pVeh->m_vOrientation[m]-dForVel) < 90.0f) + { + pVeh->m_vOrientation[m] = 90.0f; + pVeh->m_vFullAngleVelocity[m] = -pVeh->m_vFullAngleVelocity[m]; + } + } + pVeh->m_vFullAngleVelocity[m] -= dForVel; + } + else + { + pVeh->m_vFullAngleVelocity[m] = 0.0f; + } + } + + m++; + } + } + else + { //clear decr/incr angles once landed. + VectorClear(pVeh->m_vFullAngleVelocity); + } + + curRoll = pVeh->m_vOrientation[ROLL]; + + // If we're landed, we shouldn't be able to do anything but take off. + if ( isLandingOrLanded //going slow enough to start landing + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTimespeed > 0.0f ) + {//Uh... what? Why? + if ( pVeh->m_LandTrace.fraction < 0.3f ) + { + pVeh->m_vOrientation[PITCH] = 0.0f; + } + else + { + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.83f, angleTimeMod*10.0f, pVeh->m_vOrientation[PITCH]); + } + } + if ( pVeh->m_LandTrace.fraction > 0.1f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + {//off the ground, at least (or not on a valid landing surf) + // Dampen the turn rate based on the current height. +#ifdef _JK2MP + FighterYawAdjust(pVeh, riderPS, parentPS); +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW];//*pVeh->m_LandTrace.fraction; +#endif + } +#ifdef VEH_CONTROL_SCHEME_4 + else + { + VectorClear( pVeh->m_vPrevRiderViewAngles ); + pVeh->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(riderPS->viewangles[YAW]); + } +#endif// VEH_CONTROL_SCHEME_4 + } + else if ( (pVeh->m_iRemovedSurfaces||parentPS->electrifyTime>=curTime)//spiralling out of control + && (!(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5)) ) + {//no yaw control + } + else if ( pVeh->m_pPilot && pVeh->m_pPilot->s.number < MAX_CLIENTS && parentPS->speed > 0.0f )//&& !( pVeh->m_ucmd.forwardmove > 0 && pVeh->m_LandTrace.fraction != 1.0f ) ) + { +#ifdef VEH_CONTROL_SCHEME_4 + if ( 0 ) +#else// VEH_CONTROL_SCHEME_4 + if ( BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) +#endif// VEH_CONTROL_SCHEME_4 + { + VectorCopy( riderPS->viewangles, pVeh->m_vOrientation ); + VectorCopy( riderPS->viewangles, parentPS->viewangles ); +#ifdef _JK2MP + //BG_ExternThisSoICanRecompileInDebug( pVeh, riderPS ); +#endif + + curRoll = pVeh->m_vOrientation[ROLL]; + + FighterNoseMalfunctionCheck( pVeh, parentPS ); + + //VectorCopy( pVeh->m_vOrientation, parentPS->viewangles ); + } + else + { + /* + float fTurnAmt[3]; + //PITCH + fTurnAmt[PITCH] = riderPS->viewangles[PITCH] * 0.08f; + //YAW + fTurnAmt[YAW] = riderPS->viewangles[YAW] * 0.065f; + fTurnAmt[YAW] *= fTurnAmt[YAW]; + // Dampen the turn rate based on the current height. + if ( riderPS->viewangles[YAW] < 0 ) + {//must keep it negative because squaring a negative makes it positive + fTurnAmt[YAW] = -fTurnAmt[YAW]; + } + fTurnAmt[YAW] *= pVeh->m_LandTrace.fraction; + //ROLL + fTurnAmt[2] = 0.0f; + */ + + //Actal YAW +#ifdef _JK2MP + /* + pVeh->m_vOrientation[ROLL] = curRoll; + FighterRollAdjust(pVeh, riderPS, parentPS); + curRoll = pVeh->m_vOrientation[ROLL]; + */ + FighterYawAdjust(pVeh, riderPS, parentPS); +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; +#endif + + // If we are not hitting the ground, allow the fighter to pitch up and down. + if ( !FighterOverValidLandingSurface( pVeh ) + || parentPS->speed > MIN_LANDING_SPEED ) + //if ( ( pVeh->m_LandTrace.fraction >= 1.0f || pVeh->m_ucmd.forwardmove != 0 ) && pVeh->m_LandTrace.fraction >= 0.0f ) + { + float fYawDelta; + +#ifdef _JK2MP + FighterPitchAdjust(pVeh, riderPS, parentPS); +#ifdef VEH_CONTROL_SCHEME_4 + FighterPitchClamp( pVeh, riderPS, parentPS, curTime ); +#endif// VEH_CONTROL_SCHEME_4 +#else + pVeh->m_vOrientation[PITCH] = riderPS->viewangles[PITCH]; +#endif + + FighterNoseMalfunctionCheck( pVeh, parentPS ); + + // Adjust the roll based on the turn amount and dampen it a little. + fYawDelta = AngleSubtract(pVeh->m_vOrientation[YAW], pVeh->m_vPrevOrientation[YAW]); //pVeh->m_vOrientation[YAW] - pVeh->m_vPrevOrientation[YAW]; + if ( fYawDelta > 8.0f ) + { + fYawDelta = 8.0f; + } + else if ( fYawDelta < -8.0f ) + { + fYawDelta = -8.0f; + } + curRoll -= fYawDelta; + curRoll = PredictedAngularDecrement(0.93f, angleTimeMod*2.0f, curRoll); + + //cap it reasonably + //NOTE: was hardcoded to 40.0f, now using extern data +#ifdef VEH_CONTROL_SCHEME_4 + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_pVehicleInfo->rollLimit != -1 ) + { + if (curRoll > pVeh->m_pVehicleInfo->rollLimit ) + { + curRoll = pVeh->m_pVehicleInfo->rollLimit; + } + else if (curRoll < -pVeh->m_pVehicleInfo->rollLimit) + { + curRoll = -pVeh->m_pVehicleInfo->rollLimit; + } + } + } +#else// VEH_CONTROL_SCHEME_4 + if ( pVeh->m_pVehicleInfo->rollLimit != -1 ) + { + if (curRoll > pVeh->m_pVehicleInfo->rollLimit ) + { + curRoll = pVeh->m_pVehicleInfo->rollLimit; + } + else if (curRoll < -pVeh->m_pVehicleInfo->rollLimit) + { + curRoll = -pVeh->m_pVehicleInfo->rollLimit; + } + } +#endif// VEH_CONTROL_SCHEME_4 + } + } + } + + // If you are directly impacting the ground, even out your pitch. + if ( isLandingOrLanded ) + {//only if capable of landing + if ( !isDead + && parentPS->electrifyTimem_pVehicleInfo->surfDestruction || !pVeh->m_iRemovedSurfaces ) ) + {//not crashing or spiralling out of control... + if ( pVeh->m_vOrientation[PITCH] > 0 ) + { + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.2f, angleTimeMod*10.0f, pVeh->m_vOrientation[PITCH]); + } + else + { + pVeh->m_vOrientation[PITCH] = PredictedAngularDecrement(0.75f, angleTimeMod*10.0f, pVeh->m_vOrientation[PITCH]); + } + } + } + + +/* +//NOTE: all this is redundant now since we have the FighterDamageRoutine func... +#ifdef _JK2MP //...yeah. Need to send armor across net for prediction to work. + if ( isDead ) +#else + if ( pVeh->m_iArmor <= 0 ) +#endif + {//going to explode + //FIXME: maybe make it erratically jerk or spin or start and stop? +#ifndef _JK2MP + if ( g_speederControlScheme->value > 0 || !rider || rider->s.number ) +#else + if (1) +#endif + { + pVeh->m_ucmd.rightmove = Q_irand( -64, 64 ); + } + else + { + pVeh->m_ucmd.rightmove = 0; + } + pVeh->m_ucmd.forwardmove = Q_irand( -32, 127 ); + pVeh->m_ucmd.upmove = Q_irand( -127, 127 ); + pVeh->m_vOrientation[YAW] += Q_flrand( -10, 10 ); + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + if ( pVeh->m_LandTrace.fraction != 0.0f ) + { + parentPS->velocity[2] -= pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + } +*/ + // If no one is in this vehicle and it's up in the sky, pitch it forward as it comes tumbling down. +#ifdef QAGAME //never gonna happen on client anyway, we can't be getting predicted unless the predicting client is boarded + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) + && pVeh->m_LandTrace.fraction >= groundFraction + && !FighterIsInSpace( (gentity_t *)parent ) + && !FighterSuspended( pVeh, parentPS ) ) + { + pVeh->m_ucmd.upmove = 0; + //pVeh->m_ucmd.forwardmove = 0; + pVeh->m_vOrientation[PITCH] += pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + if ( pVeh->m_vOrientation[PITCH] > 60.0f ) + { + pVeh->m_vOrientation[PITCH] = 60.0f; + } + } + } +#endif + + if ( !parentPS->hackingTime ) + {//use that roll + pVeh->m_vOrientation[ROLL] = curRoll; + //NOTE: this seems really backwards... + if ( pVeh->m_vOrientation[ROLL] ) + { //continually adjust the yaw based on the roll.. + if ( (pVeh->m_iRemovedSurfaces||parentPS->electrifyTime>=curTime)//spiralling out of control + && (!(pVeh->m_pParentEntity->s.number%4)||!(pVeh->m_pParentEntity->s.number%5)) ) + {//leave YAW alone + } + else + { + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + { + pVeh->m_vOrientation[YAW] -= ((pVeh->m_vOrientation[ROLL])*0.05f)*pVeh->m_fTimeModifier; + } + } + } + } + else + {//add in strafing roll + float strafeRoll = (parentPS->hackingTime/MAX_STRAFE_TIME)*pVeh->m_pVehicleInfo->rollLimit;//pVeh->m_pVehicleInfo->bankingSpeed* + float strafeDif = AngleSubtract(strafeRoll, pVeh->m_vOrientation[ROLL]); + pVeh->m_vOrientation[ROLL] += (strafeDif*0.1f)*pVeh->m_fTimeModifier; + if ( !BG_UnrestrainedPitchRoll( riderPS, pVeh ) ) + {//cap it reasonably + if ( pVeh->m_pVehicleInfo->rollLimit != -1 + && !pVeh->m_iRemovedSurfaces + && parentPS->electrifyTimem_vOrientation[ROLL] > pVeh->m_pVehicleInfo->rollLimit ) + { + pVeh->m_vOrientation[ROLL] = pVeh->m_pVehicleInfo->rollLimit; + } + else if (pVeh->m_vOrientation[ROLL] < -pVeh->m_pVehicleInfo->rollLimit) + { + pVeh->m_vOrientation[ROLL] = -pVeh->m_pVehicleInfo->rollLimit; + } + } + } + } + + if (pVeh->m_pVehicleInfo->surfDestruction) + { + FighterDamageRoutine(pVeh, parent, parentPS, riderPS, isDead); + } + pVeh->m_vOrientation[ROLL] = AngleNormalize180( pVeh->m_vOrientation[ROLL] ); + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef QAGAME //ONLY in SP or on server, not cgame + +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); + +// This function makes sure that the vehicle is properly animated. +static void AnimateVehicle( Vehicle_t *pVeh ) +{ + int Anim = -1; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + qboolean isLanding = qfalse, isLanded = qfalse; +#ifdef _JK2MP + playerState_t *parentPS = pVeh->m_pParentEntity->playerState; +#else + playerState_t *parentPS = &pVeh->m_pParentEntity->client->ps; +#endif +#ifndef _JK2MP//SP + //nothing +#elif QAGAME//MP GAME + int curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + int curTime = pm->cmd.serverTime; +#endif + + if ( parentPS->hyperSpaceTime + && curTime - parentPS->hyperSpaceTime < HYPERSPACE_TIME ) + {//Going to Hyperspace + //close the wings (FIXME: makes sense on X-Wing, not Shuttle?) + if ( pVeh->m_ulFlags & VEH_WINGSOPEN ) + { + pVeh->m_ulFlags &= ~VEH_WINGSOPEN; + Anim = BOTH_WINGS_CLOSE; + } + } + else + { + isLanding = FighterIsLanding( pVeh, parentPS ); + isLanded = FighterIsLanded( pVeh, parentPS ); + + // if we're above launch height (way up in the air)... + if ( !isLanding && !isLanded ) + { + if ( !( pVeh->m_ulFlags & VEH_WINGSOPEN ) ) + { + pVeh->m_ulFlags |= VEH_WINGSOPEN; + pVeh->m_ulFlags &= ~VEH_GEARSOPEN; + Anim = BOTH_WINGS_OPEN; + } + } + // otherwise we're below launch height and still taking off. + else + { + if ( (pVeh->m_ucmd.forwardmove < 0 || pVeh->m_ucmd.upmove < 0||isLanded) + && pVeh->m_LandTrace.fraction <= 0.4f + && pVeh->m_LandTrace.plane.normal[2] >= MIN_LANDING_SLOPE ) + {//already landed or trying to land and close to ground + // Open gears. + if ( !( pVeh->m_ulFlags & VEH_GEARSOPEN ) ) + { +#ifdef _JK2MP + if ( pVeh->m_pVehicleInfo->soundLand ) + {//just landed? +#ifdef QAGAME//MP GAME-side + G_EntitySound( ((gentity_t *)(pVeh->m_pParentEntity)), CHAN_AUTO, pVeh->m_pVehicleInfo->soundLand ); +#elif CGAME//MP CGAME-side + //trap_S_StartSound( NULL, pVeh->m_pParentEntity->s.number, CHAN_AUTO, pVeh->m_pVehicleInfo->soundLand ); +#endif + } +#endif + pVeh->m_ulFlags |= VEH_GEARSOPEN; + Anim = BOTH_GEARS_OPEN; + } + } + else + {//trying to take off and almost halfway off the ground + // Close gears (if they're open). + if ( pVeh->m_ulFlags & VEH_GEARSOPEN ) + { + pVeh->m_ulFlags &= ~VEH_GEARSOPEN; + Anim = BOTH_GEARS_CLOSE; + //iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + } + // If gears are closed, and we are below launch height, close the wings. + else + { + if ( pVeh->m_ulFlags & VEH_WINGSOPEN ) + { + pVeh->m_ulFlags &= ~VEH_WINGSOPEN; + Anim = BOTH_WINGS_CLOSE; + } + } + } + } + } + + if ( Anim != -1 ) + { + #ifdef _JK2MP + BG_SetAnim(pVeh->m_pParentEntity->playerState, bgAllAnims[pVeh->m_pParentEntity->localAnimIndex].anims, + SETANIM_BOTH, Anim, iFlags, iBlend); + #else + NPC_SetAnim( pVeh->m_pParentEntity, SETANIM_BOTH, Anim, iFlags, iBlend ); + #endif + } +} + +// This function makes sure that the rider's in this vehicle are properly animated. +static void AnimateRiders( Vehicle_t *pVeh ) +{ +} + +#endif //game-only + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +void G_SetFighterVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME //ONLY in SP or on server, not cgame + pVehInfo->AnimateVehicle = AnimateVehicle; + pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; + pVehInfo->Board = Board; + pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; +// pVehInfo->DeathUpdate = DeathUpdate; +// pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif //game-only + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType ) +{ + // Allocate the Vehicle. +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); +#else + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); +#endif + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strType )]; +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/code/game/NPC.c b/code/game/NPC.c new file mode 100644 index 0000000..0293b5b --- /dev/null +++ b/code/game/NPC.c @@ -0,0 +1,2110 @@ +// +// NPC.cpp - generic functions +// +#include "b_local.h" +#include "anims.h" +#include "say.h" +#include "../icarus/Q3_Interface.h" + +extern vec3_t playerMins; +extern vec3_t playerMaxs; +//extern void PM_SetAnimFinal(int *torsoAnim,int *legsAnim,int type,int anim,int priority,int *torsoAnimTimer,int *legsAnimTimer,gentity_t *gent); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); +extern void NPC_BSNoClip ( void ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void NPC_ApplyRoff (void); +extern void NPC_TempLookTarget ( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern void NPC_CheckPlayerAim ( void ); +extern void NPC_CheckAllClear ( void ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern qboolean NPC_CheckLookTarget( gentity_t *self ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern void Mark1_dying( gentity_t *self ); +extern void NPC_BSCinematic( void ); +extern int GetTime ( int lastTime ); +extern void NPC_BSGM_Default( void ); +extern void NPC_CheckCharmed( void ); +extern qboolean Boba_Flying( gentity_t *self ); + +extern vmCvar_t g_saberRealisticCombat; + +//Local Variables +gentity_t *NPC; +gNPC_t *NPCInfo; +gclient_t *client; +usercmd_t ucmd; +visibility_t enemyVisibility; + +void NPC_SetAnim(gentity_t *ent,int type,int anim,int priority); +void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope ); +extern void GM_Dying( gentity_t *self ); + +extern int eventClearTime; + +void CorpsePhysics( gentity_t *self ) +{ + // run the bot through the server like it was a real client + memset( &ucmd, 0, sizeof( ucmd ) ); + ClientThink( self->s.number, &ucmd ); + //VectorCopy( self->s.origin, self->s.origin2 ); + //rww - don't get why this is happening. + + if ( self->client->NPC_class == CLASS_GALAKMECH ) + { + GM_Dying( self ); + } + //FIXME: match my pitch and roll for the slope of my groundPlane + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE && !(self->s.eFlags&EF_DISINTEGRATION) ) + {//on the ground + //FIXME: check 4 corners + pitch_roll_for_slope( self, NULL ); + } + + if ( eventClearTime == level.time + ALERT_CLEAR_TIME ) + {//events were just cleared out so add me again + if ( !(self->client->ps.eFlags&EF_NODRAW) ) + { + AddSightEvent( self->enemy, self->r.currentOrigin, 384, AEL_DISCOVERED, 0.0f ); + } + } + + if ( level.time - self->s.time > 3000 ) + {//been dead for 3 seconds + if ( g_dismember.integer < 11381138 && !g_saberRealisticCombat.integer ) + {//can't be dismembered once dead + if ( self->client->NPC_class != CLASS_PROTOCOL ) + { + // self->client->dismembered = qtrue; + } + } + } + + //if ( level.time - self->s.time > 500 ) + if (self->client->respawnTime < (level.time+500)) + {//don't turn "nonsolid" until about 1 second after actual death + + if (self->client->ps.eFlags & EF_DISINTEGRATION) + { + self->r.contents = 0; + } + else if ((self->client->NPC_class != CLASS_MARK1) && (self->client->NPC_class != CLASS_INTERROGATOR)) // The Mark1 & Interrogator stays solid. + { + self->r.contents = CONTENTS_CORPSE; + //self->r.maxs[2] = -8; + } + + if ( self->message ) + { + self->r.contents |= CONTENTS_TRIGGER; + } + } +} + +/* +---------------------------------------- +NPC_RemoveBody + +Determines when it's ok to ditch the corpse +---------------------------------------- +*/ +#define REMOVE_DISTANCE 128 +#define REMOVE_DISTANCE_SQR (REMOVE_DISTANCE * REMOVE_DISTANCE) + +void NPC_RemoveBody( gentity_t *self ) +{ + CorpsePhysics( self ); + + self->nextthink = level.time + FRAMETIME; + + if ( self->NPC->nextBStateThink <= level.time ) + { + trap_ICARUS_MaintainTaskManager(self->s.number); + } + self->NPC->nextBStateThink = level.time + FRAMETIME; + + if ( self->message ) + {//I still have a key + return; + } + + // I don't consider this a hack, it's creative coding . . . + // I agree, very creative... need something like this for ATST and GALAKMECH too! + if (self->client->NPC_class == CLASS_MARK1) + { + Mark1_dying( self ); + } + + // Since these blow up, remove the bounding box. + if ( self->client->NPC_class == CLASS_REMOTE + || self->client->NPC_class == CLASS_SENTRY + || self->client->NPC_class == CLASS_PROBE + || self->client->NPC_class == CLASS_INTERROGATOR + || self->client->NPC_class == CLASS_PROBE + || self->client->NPC_class == CLASS_MARK2 ) + { + //if ( !self->taskManager || !self->taskManager->IsRunning() ) + if (!trap_ICARUS_IsRunning(self->s.number)) + { + if ( !self->activator || !self->activator->client || !(self->activator->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//not being held by a Rancor + G_FreeEntity( self ); + } + } + return; + } + + //FIXME: don't ever inflate back up? + self->r.maxs[2] = self->client->renderInfo.eyePoint[2] - self->r.currentOrigin[2] + 4; + if ( self->r.maxs[2] < -8 ) + { + self->r.maxs[2] = -8; + } + + if ( self->client->NPC_class == CLASS_GALAKMECH ) + {//never disappears + return; + } + if ( self->NPC && self->NPC->timeOfDeath <= level.time ) + { + self->NPC->timeOfDeath = level.time + 1000; + // Only do all of this nonsense for Scav boys ( and girls ) + /// if ( self->client->playerTeam == NPCTEAM_SCAVENGERS || self->client->playerTeam == NPCTEAM_KLINGON + // || self->client->playerTeam == NPCTEAM_HIROGEN || self->client->playerTeam == NPCTEAM_MALON ) + // should I check NPC_class here instead of TEAM ? - dmv + if( self->client->playerTeam == NPCTEAM_ENEMY || self->client->NPC_class == CLASS_PROTOCOL ) + { + self->nextthink = level.time + FRAMETIME; // try back in a second + + /* + if ( DistanceSquared( g_entities[0].r.currentOrigin, self->r.currentOrigin ) <= REMOVE_DISTANCE_SQR ) + { + return; + } + + if ( (InFOV( self, &g_entities[0], 110, 90 )) ) // generous FOV check + { + if ( (NPC_ClearLOS2( &g_entities[0], self->r.currentOrigin )) ) + { + return; + } + } + */ + //Don't care about this for MP I guess. + } + + //FIXME: there are some conditions - such as heavy combat - in which we want + // to remove the bodies... but in other cases it's just weird, like + // when they're right behind you in a closed room and when they've been + // placed as dead NPCs by a designer... + // For now we just assume that a corpse with no enemy was + // placed in the map as a corpse + if ( self->enemy ) + { + //if ( !self->taskManager || !self->taskManager->IsRunning() ) + if (!trap_ICARUS_IsRunning(self->s.number)) + { + if ( !self->activator || !self->activator->client || !(self->activator->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//not being held by a Rancor + if ( self->client && self->client->ps.saberEntityNum > 0 && self->client->ps.saberEntityNum < ENTITYNUM_WORLD ) + { + gentity_t *saberent = &g_entities[self->client->ps.saberEntityNum]; + if ( saberent ) + { + G_FreeEntity( saberent ); + } + } + G_FreeEntity( self ); + } + } + } + } +} + +/* +---------------------------------------- +NPC_RemoveBody + +Determines when it's ok to ditch the corpse +---------------------------------------- +*/ + +int BodyRemovalPadTime( gentity_t *ent ) +{ + int time; + + if ( !ent || !ent->client ) + return 0; +/* + switch ( ent->client->playerTeam ) + { + case NPCTEAM_KLINGON: // no effect, we just remove them when the player isn't looking + case NPCTEAM_SCAVENGERS: + case NPCTEAM_HIROGEN: + case NPCTEAM_MALON: + case NPCTEAM_IMPERIAL: + case NPCTEAM_STARFLEET: + time = 10000; // 15 secs. + break; + + case NPCTEAM_BORG: + time = 2000; + break; + + case NPCTEAM_STASIS: + return qtrue; + break; + + case NPCTEAM_FORGE: + time = 1000; + break; + + case NPCTEAM_BOTS: +// if (!Q_stricmp( ent->NPC_type, "mouse" )) +// { + time = 0; +// } +// else +// { +// time = 10000; +// } + break; + + case NPCTEAM_8472: + time = 2000; + break; + + default: + // never go away + time = Q3_INFINITE; + break; + } +*/ + // team no longer indicates species/race, so in this case we'd use NPC_class, but + switch( ent->client->NPC_class ) + { + case CLASS_MOUSE: + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + //case CLASS_PROTOCOL: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_PROBE: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_SENTRY: + case CLASS_INTERROGATOR: + time = 0; + break; + default: + // never go away + // time = Q3_INFINITE; + // for now I'm making default 10000 + time = 10000; + break; + + } + + + return time; +} + + +/* +---------------------------------------- +NPC_RemoveBodyEffect + +Effect to be applied when ditching the corpse +---------------------------------------- +*/ + +static void NPC_RemoveBodyEffect(void) +{ +// vec3_t org; +// gentity_t *tent; + + if ( !NPC || !NPC->client || (NPC->s.eFlags & EF_NODRAW) ) + return; +/* + switch(NPC->client->playerTeam) + { + case NPCTEAM_STARFLEET: + //FIXME: Starfleet beam out + break; + + case NPCTEAM_BOTS: +// VectorCopy( NPC->r.currentOrigin, org ); +// org[2] -= 16; +// tent = G_TempEntity( org, EV_BOT_EXPLODE ); +// tent->owner = NPC; + + break; + + default: + break; + } +*/ + + + // team no longer indicates species/race, so in this case we'd use NPC_class, but + + // stub code + switch(NPC->client->NPC_class) + { + case CLASS_PROBE: + case CLASS_SEEKER: + case CLASS_REMOTE: + case CLASS_SENTRY: + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + //case CLASS_PROTOCOL: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_INTERROGATOR: + case CLASS_ATST: // yeah, this is a little weird, but for now I'm listing all droids + // VectorCopy( NPC->r.currentOrigin, org ); + // org[2] -= 16; + // tent = G_TempEntity( org, EV_BOT_EXPLODE ); + // tent->owner = NPC; + break; + default: + break; + } + + +} + + +/* +==================================================================== +void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope ) + +MG + +This will adjust the pitch and roll of a monster to match +a given slope - if a non-'0 0 0' slope is passed, it will +use that value, otherwise it will use the ground underneath +the monster. If it doesn't find a surface, it does nothinh\g +and returns. +==================================================================== +*/ + +void pitch_roll_for_slope( gentity_t *forwhom, vec3_t pass_slope ) +{ + vec3_t slope; + vec3_t nvf, ovf, ovr, startspot, endspot, new_angles = { 0, 0, 0 }; + float pitch, mod, dot; + + //if we don't have a slope, get one + if( !pass_slope || VectorCompare( vec3_origin, pass_slope ) ) + { + trace_t trace; + + VectorCopy( forwhom->r.currentOrigin, startspot ); + startspot[2] += forwhom->r.mins[2] + 4; + VectorCopy( startspot, endspot ); + endspot[2] -= 300; + trap_Trace( &trace, forwhom->r.currentOrigin, vec3_origin, vec3_origin, endspot, forwhom->s.number, MASK_SOLID ); +// if(trace_fraction>0.05&&forwhom.movetype==MOVETYPE_STEP) +// forwhom.flags(-)FL_ONGROUND; + + if ( trace.fraction >= 1.0 ) + return; + + if( !( &trace.plane ) ) + return; + + if ( VectorCompare( vec3_origin, trace.plane.normal ) ) + return; + + VectorCopy( trace.plane.normal, slope ); + } + else + { + VectorCopy( pass_slope, slope ); + } + + + AngleVectors( forwhom->r.currentAngles, ovf, ovr, NULL ); + + vectoangles( slope, new_angles ); + pitch = new_angles[PITCH] + 90; + new_angles[ROLL] = new_angles[PITCH] = 0; + + AngleVectors( new_angles, nvf, NULL, NULL ); + + mod = DotProduct( nvf, ovr ); + + if ( mod<0 ) + mod = -1; + else + mod = 1; + + dot = DotProduct( nvf, ovf ); + + if ( forwhom->client ) + { + float oldmins2; + + forwhom->client->ps.viewangles[PITCH] = dot * pitch; + forwhom->client->ps.viewangles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + oldmins2 = forwhom->r.mins[2]; + forwhom->r.mins[2] = -24 + 12 * fabs(forwhom->client->ps.viewangles[PITCH])/180.0f; + //FIXME: if it gets bigger, move up + if ( oldmins2 > forwhom->r.mins[2] ) + {//our mins is now lower, need to move up + //FIXME: trace? + forwhom->client->ps.origin[2] += (oldmins2 - forwhom->r.mins[2]); + forwhom->r.currentOrigin[2] = forwhom->client->ps.origin[2]; + trap_LinkEntity( forwhom ); + } + } + else + { + forwhom->r.currentAngles[PITCH] = dot * pitch; + forwhom->r.currentAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + } +} + + +/* +---------------------------------------- +DeadThink +---------------------------------------- +*/ +static void DeadThink ( void ) +{ + trace_t trace; + + //HACKHACKHACKHACKHACK + //We should really have a seperate G2 bounding box (seperate from the physics bbox) for G2 collisions only + //FIXME: don't ever inflate back up? + NPC->r.maxs[2] = NPC->client->renderInfo.eyePoint[2] - NPC->r.currentOrigin[2] + 4; + if ( NPC->r.maxs[2] < -8 ) + { + NPC->r.maxs[2] = -8; + } + if ( VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//not flying through the air + if ( NPC->r.mins[0] > -32 ) + { + NPC->r.mins[0] -= 1; + trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->r.mins[0] += 1; + } + } + if ( NPC->r.maxs[0] < 32 ) + { + NPC->r.maxs[0] += 1; + trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->r.maxs[0] -= 1; + } + } + if ( NPC->r.mins[1] > -32 ) + { + NPC->r.mins[1] -= 1; + trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->r.mins[1] += 1; + } + } + if ( NPC->r.maxs[1] < 32 ) + { + NPC->r.maxs[1] += 1; + trap_Trace (&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid ) + { + NPC->r.maxs[1] -= 1; + } + } + } + //HACKHACKHACKHACKHACK + + //FIXME: tilt and fall off of ledges? + //NPC_PostDeathThink(); + + /* + if ( !NPCInfo->timeOfDeath && NPC->client != NULL && NPCInfo != NULL ) + { + //haven't finished death anim yet and were NOT given a specific amount of time to wait before removal + int legsAnim = NPC->client->ps.legsAnim; + animation_t *animations = knownAnimFileSets[NPC->client->clientInfo.animFileIndex].animations; + + NPC->bounceCount = -1; // This is a cheap hack for optimizing the pointcontents check below + + //ghoul doesn't tell us this anymore + //if ( NPC->client->renderInfo.legsFrame == animations[legsAnim].firstFrame + (animations[legsAnim].numFrames - 1) ) + { + //reached the end of the death anim + NPCInfo->timeOfDeath = level.time + BodyRemovalPadTime( NPC ); + } + } + else + */ + { + //death anim done (or were given a specific amount of time to wait before removal), wait the requisite amount of time them remove + if ( level.time >= NPCInfo->timeOfDeath + BodyRemovalPadTime( NPC ) ) + { + if ( NPC->client->ps.eFlags & EF_NODRAW ) + { + if (!trap_ICARUS_IsRunning(NPC->s.number)) + //if ( !NPC->taskManager || !NPC->taskManager->IsRunning() ) + { + NPC->think = G_FreeEntity; + NPC->nextthink = level.time + FRAMETIME; + } + } + else + { + class_t npc_class; + + // Start the body effect first, then delay 400ms before ditching the corpse + NPC_RemoveBodyEffect(); + + //FIXME: keep it running through physics somehow? + NPC->think = NPC_RemoveBody; + NPC->nextthink = level.time + FRAMETIME; + // if ( NPC->client->playerTeam == NPCTEAM_FORGE ) + // NPCInfo->timeOfDeath = level.time + FRAMETIME * 8; + // else if ( NPC->client->playerTeam == NPCTEAM_BOTS ) + npc_class = NPC->client->NPC_class; + // check for droids + if ( npc_class == CLASS_SEEKER || npc_class == CLASS_REMOTE || npc_class == CLASS_PROBE || npc_class == CLASS_MOUSE || + npc_class == CLASS_GONK || npc_class == CLASS_R2D2 || npc_class == CLASS_R5D2 || + npc_class == CLASS_MARK2 || npc_class == CLASS_SENTRY )//npc_class == CLASS_PROTOCOL || + { + NPC->client->ps.eFlags |= EF_NODRAW; + NPCInfo->timeOfDeath = level.time + FRAMETIME * 8; + } + else + NPCInfo->timeOfDeath = level.time + FRAMETIME * 4; + } + return; + } + } + + // If the player is on the ground and the resting position contents haven't been set yet...(BounceCount tracks the contents) + if ( NPC->bounceCount < 0 && NPC->s.groundEntityNum >= 0 ) + { + // if client is in a nodrop area, make him/her nodraw + int contents = NPC->bounceCount = trap_PointContents( NPC->r.currentOrigin, -1 ); + + if ( ( contents & CONTENTS_NODROP ) ) + { + NPC->client->ps.eFlags |= EF_NODRAW; + } + } + + CorpsePhysics( NPC ); +} + + +/* +=============== +SetNPCGlobals + +local function to set globals used throughout the AI code +=============== +*/ +void SetNPCGlobals( gentity_t *ent ) +{ + NPC = ent; + NPCInfo = ent->NPC; + client = ent->client; + memset( &ucmd, 0, sizeof( usercmd_t ) ); +} + +gentity_t *_saved_NPC; +gNPC_t *_saved_NPCInfo; +gclient_t *_saved_client; +usercmd_t _saved_ucmd; + +void SaveNPCGlobals(void) +{ + _saved_NPC = NPC; + _saved_NPCInfo = NPCInfo; + _saved_client = client; + memcpy( &_saved_ucmd, &ucmd, sizeof( usercmd_t ) ); +} + +void RestoreNPCGlobals(void) +{ + NPC = _saved_NPC; + NPCInfo = _saved_NPCInfo; + client = _saved_client; + memcpy( &ucmd, &_saved_ucmd, sizeof( usercmd_t ) ); +} + +//We MUST do this, other funcs were using NPC illegally when "self" wasn't the global NPC +void ClearNPCGlobals( void ) +{ + NPC = NULL; + NPCInfo = NULL; + client = NULL; +} +//=============== + +extern qboolean showBBoxes; +vec3_t NPCDEBUG_RED = {1.0, 0.0, 0.0}; +vec3_t NPCDEBUG_GREEN = {0.0, 1.0, 0.0}; +vec3_t NPCDEBUG_BLUE = {0.0, 0.0, 1.0}; +vec3_t NPCDEBUG_LIGHT_BLUE = {0.3f, 0.7f, 1.0}; +extern void G_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ); +extern void G_Line( vec3_t start, vec3_t end, vec3_t color, float alpha ); +extern void G_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color ); + +void NPC_ShowDebugInfo (void) +{ + if ( showBBoxes ) + { + gentity_t *found = NULL; + vec3_t mins, maxs; + + while( (found = G_Find( found, FOFS(classname), "NPC" ) ) != NULL ) + { + if ( trap_InPVS( found->r.currentOrigin, g_entities[0].r.currentOrigin ) ) + { + VectorAdd( found->r.currentOrigin, found->r.mins, mins ); + VectorAdd( found->r.currentOrigin, found->r.maxs, maxs ); + G_Cube( mins, maxs, NPCDEBUG_RED, 0.25 ); + } + } + } +} + +void NPC_ApplyScriptFlags (void) +{ + if ( NPCInfo->scriptFlags & SCF_CROUCHED ) + { + if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) ) + {//ugh, if charmed and moving, ignore the crouched command + } + else + { + ucmd.upmove = -127; + } + } + + if(NPCInfo->scriptFlags & SCF_RUNNING) + { + ucmd.buttons &= ~BUTTON_WALKING; + } + else if(NPCInfo->scriptFlags & SCF_WALKING) + { + if ( NPCInfo->charmedTime > level.time && (ucmd.forwardmove || ucmd.rightmove) ) + {//ugh, if charmed and moving, ignore the walking command + } + else + { + ucmd.buttons |= BUTTON_WALKING; + } + } +/* + if(NPCInfo->scriptFlags & SCF_CAREFUL) + { + ucmd.buttons |= BUTTON_CAREFUL; + } +*/ + if(NPCInfo->scriptFlags & SCF_LEAN_RIGHT) + { + ucmd.buttons |= BUTTON_USE; + ucmd.rightmove = 127; + ucmd.forwardmove = 0; + ucmd.upmove = 0; + } + else if(NPCInfo->scriptFlags & SCF_LEAN_LEFT) + { + ucmd.buttons |= BUTTON_USE; + ucmd.rightmove = -127; + ucmd.forwardmove = 0; + ucmd.upmove = 0; + } + + if ( (NPCInfo->scriptFlags & SCF_ALT_FIRE) && (ucmd.buttons & BUTTON_ATTACK) ) + {//Use altfire instead + ucmd.buttons |= BUTTON_ALT_ATTACK; + } +} + +void Q3_DebugPrint( int level, const char *format, ... ); +void NPC_HandleAIFlags (void) +{ + //FIXME: make these flags checks a function call like NPC_CheckAIFlagsAndTimers + if ( NPCInfo->aiFlags & NPCAI_LOST ) + {//Print that you need help! + //FIXME: shouldn't remove this just yet if cg_draw needs it + NPCInfo->aiFlags &= ~NPCAI_LOST; + + /* + if ( showWaypoints ) + { + Q3_DebugPrint(WL_WARNING, "%s can't navigate to target %s (my wp: %d, goal wp: %d)\n", NPC->targetname, NPCInfo->goalEntity->targetname, NPC->waypoint, NPCInfo->goalEntity->waypoint ); + } + */ + + if ( NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) + {//We can't nav to our enemy + //Drop enemy and see if we should search for him + NPC_LostEnemyDecideChase(); + } + } + + //MRJ Request: + /* + if ( NPCInfo->aiFlags & NPCAI_GREET_ALLIES && !NPC->enemy )//what if "enemy" is the greetEnt? + {//If no enemy, look for teammates to greet + //FIXME: don't say hi to the same guy over and over again. + if ( NPCInfo->greetingDebounceTime < level.time ) + {//Has been at least 2 seconds since we greeted last + if ( !NPCInfo->greetEnt ) + {//Find a teammate whom I'm facing and who is facing me and within 128 + NPCInfo->greetEnt = NPC_PickAlly( qtrue, 128, qtrue, qtrue ); + } + + if ( NPCInfo->greetEnt && !Q_irand(0, 5) ) + {//Start greeting someone + qboolean greeted = qfalse; + + //TODO: If have a greetscript, run that instead? + + //FIXME: make them greet back? + if( !Q_irand( 0, 2 ) ) + {//Play gesture anim (press gesture button?) + greeted = qtrue; + NPC_SetAnim( NPC, SETANIM_TORSO, Q_irand( BOTH_GESTURE1, BOTH_GESTURE3 ), SETANIM_FLAG_NORMAL|SETANIM_FLAG_HOLD ); + //NOTE: play full-body gesture if not moving? + } + + if( !Q_irand( 0, 2 ) ) + {//Play random voice greeting sound + greeted = qtrue; + //FIXME: need NPC sound sets + + //G_AddVoiceEvent( NPC, Q_irand(EV_GREET1, EV_GREET3), 2000 ); + } + + if( !Q_irand( 0, 1 ) ) + {//set looktarget to them for a second or two + greeted = qtrue; + NPC_TempLookTarget( NPC, NPCInfo->greetEnt->s.number, 1000, 3000 ); + } + + if ( greeted ) + {//Did at least one of the things above + //Don't greet again for 2 - 4 seconds + NPCInfo->greetingDebounceTime = level.time + Q_irand( 2000, 4000 ); + NPCInfo->greetEnt = NULL; + } + } + } + } + */ + //been told to play a victory sound after a delay + if ( NPCInfo->greetingDebounceTime && NPCInfo->greetingDebounceTime < level.time ) + { + G_AddVoiceEvent( NPC, Q_irand(EV_VICTORY1, EV_VICTORY3), Q_irand( 2000, 4000 ) ); + NPCInfo->greetingDebounceTime = 0; + } + + if ( NPCInfo->ffireCount > 0 ) + { + if ( NPCInfo->ffireFadeDebounce < level.time ) + { + NPCInfo->ffireCount--; + //Com_Printf( "drop: %d < %d\n", NPCInfo->ffireCount, 3+((2-g_spskill.integer)*2) ); + NPCInfo->ffireFadeDebounce = level.time + 3000; + } + } + if ( d_patched.integer ) + {//use patch-style navigation + if ( NPCInfo->consecutiveBlockedMoves > 20 ) + {//been stuck for a while, try again? + NPCInfo->consecutiveBlockedMoves = 0; + } + } +} + +void NPC_AvoidWallsAndCliffs (void) +{ + //... +} + +void NPC_CheckAttackScript(void) +{ + if(!(ucmd.buttons & BUTTON_ATTACK)) + { + return; + } + + G_ActivateBehavior(NPC, BSET_ATTACK); +} + +float NPC_MaxDistSquaredForWeapon (void); +void NPC_CheckAttackHold(void) +{ + vec3_t vec; + + // If they don't have an enemy they shouldn't hold their attack anim. + if ( !NPC->enemy ) + { + NPCInfo->attackHoldTime = 0; + return; + } + +/* if ( ( NPC->client->ps.weapon == WP_BORG_ASSIMILATOR ) || ( NPC->client->ps.weapon == WP_BORG_DRILL ) ) + {//FIXME: don't keep holding this if can't hit enemy? + + // If they don't have shields ( been disabled) they shouldn't hold their attack anim. + if ( !(NPC->NPC->aiFlags & NPCAI_SHIELDS) ) + { + NPCInfo->attackHoldTime = 0; + return; + } + + VectorSubtract(NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec); + if( VectorLengthSquared(vec) > NPC_MaxDistSquaredForWeapon() ) + { + NPCInfo->attackHoldTime = 0; + PM_SetTorsoAnimTimer(NPC, &NPC->client->ps.torsoAnimTimer, 0); + } + else if( NPCInfo->attackHoldTime && NPCInfo->attackHoldTime > level.time ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + else if ( ( NPCInfo->attackHold ) && ( ucmd.buttons & BUTTON_ATTACK ) ) + { + NPCInfo->attackHoldTime = level.time + NPCInfo->attackHold; + PM_SetTorsoAnimTimer(NPC, &NPC->client->ps.torsoAnimTimer, NPCInfo->attackHold); + } + else + { + NPCInfo->attackHoldTime = 0; + PM_SetTorsoAnimTimer(NPC, &NPC->client->ps.torsoAnimTimer, 0); + } + } + else*/ + {//everyone else...? FIXME: need to tie this into AI somehow? + VectorSubtract(NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec); + if( VectorLengthSquared(vec) > NPC_MaxDistSquaredForWeapon() ) + { + NPCInfo->attackHoldTime = 0; + } + else if( NPCInfo->attackHoldTime && NPCInfo->attackHoldTime > level.time ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + else if ( ( NPCInfo->attackHold ) && ( ucmd.buttons & BUTTON_ATTACK ) ) + { + NPCInfo->attackHoldTime = level.time + NPCInfo->attackHold; + } + else + { + NPCInfo->attackHoldTime = 0; + } + } +} + +/* +void NPC_KeepCurrentFacing(void) + +Fills in a default ucmd to keep current angles facing +*/ +void NPC_KeepCurrentFacing(void) +{ + if(!ucmd.angles[YAW]) + { + ucmd.angles[YAW] = ANGLE2SHORT( client->ps.viewangles[YAW] ) - client->ps.delta_angles[YAW]; + } + + if(!ucmd.angles[PITCH]) + { + ucmd.angles[PITCH] = ANGLE2SHORT( client->ps.viewangles[PITCH] ) - client->ps.delta_angles[PITCH]; + } +} + +/* +------------------------- +NPC_BehaviorSet_Charmed +------------------------- +*/ + +void NPC_BehaviorSet_Charmed( int bState ) +{ + switch( bState ) + { + case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across + NPC_BSFollowLeader(); + break; + case BS_REMOVE: + NPC_BSRemove(); + break; + case BS_SEARCH: //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies + NPC_BSSearch(); + break; + case BS_WANDER: //# 46: Wander down random waypoint paths + NPC_BSWander(); + break; + case BS_FLEE: + NPC_BSFlee(); + break; + default: + case BS_DEFAULT://whatever + NPC_BSDefault(); + break; + } +} +/* +------------------------- +NPC_BehaviorSet_Default +------------------------- +*/ + +void NPC_BehaviorSet_Default( int bState ) +{ + switch( bState ) + { + case BS_ADVANCE_FIGHT://head toward captureGoal, shoot anything that gets in the way + NPC_BSAdvanceFight (); + break; + case BS_SLEEP://Follow a path, looking for enemies + NPC_BSSleep (); + break; + case BS_FOLLOW_LEADER://# 40: Follow your leader and shoot any enemies you come across + NPC_BSFollowLeader(); + break; + case BS_JUMP: //41: Face navgoal and jump to it. + NPC_BSJump(); + break; + case BS_REMOVE: + NPC_BSRemove(); + break; + case BS_SEARCH: //# 43: Using current waypoint as a base, search the immediate branches of waypoints for enemies + NPC_BSSearch(); + break; + case BS_NOCLIP: + NPC_BSNoClip(); + break; + case BS_WANDER: //# 46: Wander down random waypoint paths + NPC_BSWander(); + break; + case BS_FLEE: + NPC_BSFlee(); + break; + case BS_WAIT: + NPC_BSWait(); + break; + case BS_CINEMATIC: + NPC_BSCinematic(); + break; + default: + case BS_DEFAULT://whatever + NPC_BSDefault(); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Interrogator +------------------------- +*/ +void NPC_BehaviorSet_Interrogator( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSInterrogator_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +void NPC_BSImperialProbe_Attack( void ); +void NPC_BSImperialProbe_Patrol( void ); +void NPC_BSImperialProbe_Wait(void); + +/* +------------------------- +NPC_BehaviorSet_ImperialProbe +------------------------- +*/ +void NPC_BehaviorSet_ImperialProbe( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSImperialProbe_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + + +void NPC_BSSeeker_Default( void ); + +/* +------------------------- +NPC_BehaviorSet_Seeker +------------------------- +*/ +void NPC_BehaviorSet_Seeker( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSSeeker_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +void NPC_BSRemote_Default( void ); + +/* +------------------------- +NPC_BehaviorSet_Remote +------------------------- +*/ +void NPC_BehaviorSet_Remote( int bState ) +{ + NPC_BSRemote_Default(); +} + +void NPC_BSSentry_Default( void ); + +/* +------------------------- +NPC_BehaviorSet_Sentry +------------------------- +*/ +void NPC_BehaviorSet_Sentry( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSSentry_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Grenadier +------------------------- +*/ +void NPC_BehaviorSet_Grenadier( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSGrenadier_Default(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} +/* +------------------------- +NPC_BehaviorSet_Sniper +------------------------- +*/ +void NPC_BehaviorSet_Sniper( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSSniper_Default(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} +/* +------------------------- +NPC_BehaviorSet_Stormtrooper +------------------------- +*/ + +void NPC_BehaviorSet_Stormtrooper( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSST_Default(); + break; + + case BS_INVESTIGATE: + NPC_BSST_Investigate(); + break; + + case BS_SLEEP: + NPC_BSST_Sleep(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Jedi +------------------------- +*/ + +void NPC_BehaviorSet_Jedi( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSJedi_Default(); + break; + + case BS_FOLLOW_LEADER: + NPC_BSJedi_FollowLeader(); + break; + + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Droid +------------------------- +*/ +void NPC_BehaviorSet_Droid( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_STAND_GUARD: + case BS_PATROL: + NPC_BSDroid_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Mark1 +------------------------- +*/ +void NPC_BehaviorSet_Mark1( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_STAND_GUARD: + case BS_PATROL: + NPC_BSMark1_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Mark2 +------------------------- +*/ +void NPC_BehaviorSet_Mark2( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + NPC_BSMark2_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_ATST +------------------------- +*/ +void NPC_BehaviorSet_ATST( int bState ) +{ + switch( bState ) + { + case BS_DEFAULT: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + NPC_BSATST_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_MineMonster +------------------------- +*/ +void NPC_BehaviorSet_MineMonster( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSMineMonster_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Howler +------------------------- +*/ +void NPC_BehaviorSet_Howler( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSHowler_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_BehaviorSet_Rancor +------------------------- +*/ +void NPC_BehaviorSet_Rancor( int bState ) +{ + switch( bState ) + { + case BS_STAND_GUARD: + case BS_PATROL: + case BS_STAND_AND_SHOOT: + case BS_HUNT_AND_KILL: + case BS_DEFAULT: + NPC_BSRancor_Default(); + break; + default: + NPC_BehaviorSet_Default( bState ); + break; + } +} + +/* +------------------------- +NPC_RunBehavior +------------------------- +*/ +extern void NPC_BSEmplaced( void ); +extern qboolean NPC_CheckSurrender( void ); +extern void Boba_FlyStop( gentity_t *self ); +extern void NPC_BSWampa_Default( void ); +void NPC_RunBehavior( int team, int bState ) +{ + qboolean dontSetAim = qfalse; + + if (NPC->s.NPC_class == CLASS_VEHICLE && + NPC->m_pVehicle) + { //vehicles don't do AI! + return; + } + + if ( bState == BS_CINEMATIC ) + { + NPC_BSCinematic(); + } + else if ( NPC->client->ps.weapon == WP_EMPLACED_GUN ) + { + NPC_BSEmplaced(); + NPC_CheckCharmed(); + return; + } + else if ( NPC->client->ps.weapon == WP_SABER ) + {//jedi + NPC_BehaviorSet_Jedi( bState ); + dontSetAim = qtrue; + } + else if ( NPC->client->NPC_class == CLASS_WAMPA ) + {//wampa + NPC_BSWampa_Default(); + } + else if ( NPC->client->NPC_class == CLASS_RANCOR ) + {//rancor + NPC_BehaviorSet_Rancor( bState ); + } + else if ( NPC->client->NPC_class == CLASS_REMOTE ) + { + NPC_BehaviorSet_Remote( bState ); + } + else if ( NPC->client->NPC_class == CLASS_SEEKER ) + { + NPC_BehaviorSet_Seeker( bState ); + } + else if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + {//bounty hunter + if ( Boba_Flying( NPC ) ) + { + NPC_BehaviorSet_Seeker(bState); + } + else + { + NPC_BehaviorSet_Jedi( bState ); + } + dontSetAim = qtrue; + } + else if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) + {//being forced to march + NPC_BSDefault(); + } + else + { + switch( team ) + { + + // case NPCTEAM_SCAVENGERS: + // case NPCTEAM_IMPERIAL: + // case NPCTEAM_KLINGON: + // case NPCTEAM_HIROGEN: + // case NPCTEAM_MALON: + // not sure if TEAM_ENEMY is appropriate here, I think I should be using NPC_class to check for behavior - dmv + case NPCTEAM_ENEMY: + // special cases for enemy droids + switch( NPC->client->NPC_class) + { + case CLASS_ATST: + NPC_BehaviorSet_ATST( bState ); + return; + case CLASS_PROBE: + NPC_BehaviorSet_ImperialProbe(bState); + return; + case CLASS_REMOTE: + NPC_BehaviorSet_Remote( bState ); + return; + case CLASS_SENTRY: + NPC_BehaviorSet_Sentry(bState); + return; + case CLASS_INTERROGATOR: + NPC_BehaviorSet_Interrogator( bState ); + return; + case CLASS_MINEMONSTER: + NPC_BehaviorSet_MineMonster( bState ); + return; + case CLASS_HOWLER: + NPC_BehaviorSet_Howler( bState ); + return; + case CLASS_MARK1: + NPC_BehaviorSet_Mark1( bState ); + return; + case CLASS_MARK2: + NPC_BehaviorSet_Mark2( bState ); + return; + case CLASS_GALAKMECH: + NPC_BSGM_Default(); + return; + + } + + if ( NPC->enemy && NPC->s.weapon == WP_NONE && bState != BS_HUNT_AND_KILL && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//if in battle and have no weapon, run away, fixme: when in BS_HUNT_AND_KILL, they just stand there + if ( bState != BS_FLEE ) + { + NPC_StartFlee( NPC->enemy, NPC->enemy->r.currentOrigin, AEL_DANGER_GREAT, 5000, 10000 ); + } + else + { + NPC_BSFlee(); + } + return; + } + if ( NPC->client->ps.weapon == WP_SABER ) + {//special melee exception + NPC_BehaviorSet_Default( bState ); + return; + } + if ( NPC->client->ps.weapon == WP_DISRUPTOR && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//a sniper + NPC_BehaviorSet_Sniper( bState ); + return; + } + if ( NPC->client->ps.weapon == WP_THERMAL || NPC->client->ps.weapon == WP_STUN_BATON )//FIXME: separate AI for melee fighters + {//a grenadier + NPC_BehaviorSet_Grenadier( bState ); + return; + } + if ( NPC_CheckSurrender() ) + { + return; + } + NPC_BehaviorSet_Stormtrooper( bState ); + break; + + case NPCTEAM_NEUTRAL: + + // special cases for enemy droids + if ( NPC->client->NPC_class == CLASS_PROTOCOL || NPC->client->NPC_class == CLASS_UGNAUGHT || + NPC->client->NPC_class == CLASS_JAWA) + { + NPC_BehaviorSet_Default(bState); + } + else if ( NPC->client->NPC_class == CLASS_VEHICLE ) + { + // TODO: Add vehicle behaviors here. + NPC_UpdateAngles( qtrue, qtrue );//just face our spawn angles for now + } + else + { + // Just one of the average droids + NPC_BehaviorSet_Droid( bState ); + } + break; + + default: + if ( NPC->client->NPC_class == CLASS_SEEKER ) + { + NPC_BehaviorSet_Seeker(bState); + } + else + { + if ( NPCInfo->charmedTime > level.time ) + { + NPC_BehaviorSet_Charmed( bState ); + } + else + { + NPC_BehaviorSet_Default( bState ); + } + NPC_CheckCharmed(); + dontSetAim = qtrue; + } + break; + } + } +} + +/* +=============== +NPC_ExecuteBState + + MCG + +NPC Behavior state thinking + +=============== +*/ +void NPC_ExecuteBState ( gentity_t *self)//, int msec ) +{ + bState_t bState; + + NPC_HandleAIFlags(); + + //FIXME: these next three bits could be a function call, some sort of setup/cleanup func + //Lookmode must be reset every think cycle + if(NPC->delayScriptTime && NPC->delayScriptTime <= level.time) + { + G_ActivateBehavior( NPC, BSET_DELAYED); + NPC->delayScriptTime = 0; + } + + //Clear this and let bState set it itself, so it automatically handles changing bStates... but we need a set bState wrapper func + NPCInfo->combatMove = qfalse; + + //Execute our bState + if(NPCInfo->tempBehavior) + {//Overrides normal behavior until cleared + bState = NPCInfo->tempBehavior; + } + else + { + if(!NPCInfo->behaviorState) + NPCInfo->behaviorState = NPCInfo->defaultBehavior; + + bState = NPCInfo->behaviorState; + } + + //Pick the proper bstate for us and run it + NPC_RunBehavior( self->client->playerTeam, bState ); + + +// if(bState != BS_POINT_COMBAT && NPCInfo->combatPoint != -1) +// { + //level.combatPoints[NPCInfo->combatPoint].occupied = qfalse; + //NPCInfo->combatPoint = -1; +// } + + //Here we need to see what the scripted stuff told us to do +//Only process snapshot if independant and in combat mode- this would pick enemies and go after needed items +// ProcessSnapshot(); + +//Ignore my needs if I'm under script control- this would set needs for items +// CheckSelf(); + + //Back to normal? All decisions made? + + //FIXME: don't walk off ledges unless we can get to our goal faster that way, or that's our goal's surface + //NPCPredict(); + + if ( NPC->enemy ) + { + if ( !NPC->enemy->inuse ) + {//just in case bState doesn't catch this + G_ClearEnemy( NPC ); + } + } + + if ( NPC->client->ps.saberLockTime && NPC->client->ps.saberLockEnemy != ENTITYNUM_NONE ) + { + NPC_SetLookTarget( NPC, NPC->client->ps.saberLockEnemy, level.time+1000 ); + } + else if ( !NPC_CheckLookTarget( NPC ) ) + { + if ( NPC->enemy ) + { + NPC_SetLookTarget( NPC, NPC->enemy->s.number, 0 ); + } + } + + if ( NPC->enemy ) + { + if(NPC->enemy->flags & FL_DONT_SHOOT) + { + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons &= ~BUTTON_ALT_ATTACK; + } + else if ( NPC->client->playerTeam != NPCTEAM_ENEMY && NPC->enemy->NPC && (NPC->enemy->NPC->surrenderTime > level.time || (NPC->enemy->NPC->scriptFlags&SCF_FORCED_MARCH)) ) + {//don't shoot someone who's surrendering if you're a good guy + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons &= ~BUTTON_ALT_ATTACK; + } + + if(client->ps.weaponstate == WEAPON_IDLE) + { + client->ps.weaponstate = WEAPON_READY; + } + } + else + { + if(client->ps.weaponstate == WEAPON_READY) + { + client->ps.weaponstate = WEAPON_IDLE; + } + } + + if(!(ucmd.buttons & BUTTON_ATTACK) && NPC->attackDebounceTime > level.time) + {//We just shot but aren't still shooting, so hold the gun up for a while + if(client->ps.weapon == WP_SABER ) + {//One-handed + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY1,SETANIM_FLAG_NORMAL); + } + else if(client->ps.weapon == WP_BRYAR_PISTOL) + {//Sniper pose + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + /*//FIXME: What's the proper solution here? + else + {//heavy weapon + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONREADY3,SETANIM_FLAG_NORMAL); + } + */ + } + else if ( !NPC->enemy )//HACK! + { +// if(client->ps.weapon != WP_TRICORDER) + { + if( NPC->s.torsoAnim == TORSO_WEAPONREADY1 || NPC->s.torsoAnim == TORSO_WEAPONREADY3 ) + {//we look ready for action, using one of the first 2 weapon, let's rest our weapon on our shoulder + NPC_SetAnim(NPC,SETANIM_TORSO,TORSO_WEAPONIDLE3,SETANIM_FLAG_NORMAL); + } + } + } + + NPC_CheckAttackHold(); + NPC_ApplyScriptFlags(); + + //cliff and wall avoidance + NPC_AvoidWallsAndCliffs(); + + // run the bot through the server like it was a real client +//=== Save the ucmd for the second no-think Pmove ============================ + ucmd.serverTime = level.time - 50; + memcpy( &NPCInfo->last_ucmd, &ucmd, sizeof( usercmd_t ) ); + if ( !NPCInfo->attackHoldTime ) + { + NPCInfo->last_ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK);//so we don't fire twice in one think + } +//============================================================================ + NPC_CheckAttackScript(); + NPC_KeepCurrentFacing(); + + if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) + {//If we were following a roff, we don't do normal pmoves. + ClientThink( NPC->s.number, &ucmd ); + } + else + { + NPC_ApplyRoff(); + } + + // end of thinking cleanup + NPCInfo->touchedByPlayer = NULL; + + NPC_CheckPlayerAim(); + NPC_CheckAllClear(); + + /*if( ucmd.forwardmove || ucmd.rightmove ) + { + int i, la = -1, ta = -1; + + for(i = 0; i < MAX_ANIMATIONS; i++) + { + if( NPC->client->ps.legsAnim == i ) + { + la = i; + } + + if( NPC->client->ps.torsoAnim == i ) + { + ta = i; + } + + if(la != -1 && ta != -1) + { + break; + } + } + + if(la != -1 && ta != -1) + {//FIXME: should never play same frame twice or restart an anim before finishing it + Com_Printf("LegsAnim: %s(%d) TorsoAnim: %s(%d)\n", animTable[la].name, NPC->renderInfo.legsFrame, animTable[ta].name, NPC->client->renderInfo.torsoFrame); + } + }*/ +} + +void NPC_CheckInSolid(void) +{ + trace_t trace; + vec3_t point; + VectorCopy(NPC->r.currentOrigin, point); + point[2] -= 0.25; + + trap_Trace(&trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, point, NPC->s.number, NPC->clipmask); + if(!trace.startsolid && !trace.allsolid) + { + VectorCopy(NPC->r.currentOrigin, NPCInfo->lastClearOrigin); + } + else + { + if(VectorLengthSquared(NPCInfo->lastClearOrigin)) + { +// Com_Printf("%s stuck in solid at %s: fixing...\n", NPC->script_targetname, vtos(NPC->r.currentOrigin)); + G_SetOrigin(NPC, NPCInfo->lastClearOrigin); + trap_LinkEntity(NPC); + } + } +} + +void G_DroidSounds( gentity_t *self ) +{ + if ( self->client ) + {//make the noises + if ( TIMER_Done( self, "patrolNoise" ) && !Q_irand( 0, 20 ) ) + { + switch( self->client->NPC_class ) + { + case CLASS_R2D2: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)) ); + break; + case CLASS_R5D2: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)) ); + break; + case CLASS_PROBE: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d.wav",Q_irand(1, 3)) ); + break; + case CLASS_MOUSE: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)) ); + break; + case CLASS_GONK: // droid + G_SoundOnEnt(self, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)) ); + break; + } + TIMER_Set( self, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } +} + +/* +=============== +NPC_Think + +Main NPC AI - called once per frame +=============== +*/ +#if AI_TIMERS +extern int AITime; +#endif// AI_TIMERS +void NPC_Think ( gentity_t *self)//, int msec ) +{ + vec3_t oldMoveDir; + int i = 0; + gentity_t *player; + + self->nextthink = level.time + FRAMETIME; + + SetNPCGlobals( self ); + + memset( &ucmd, 0, sizeof( ucmd ) ); + + VectorCopy( self->client->ps.moveDir, oldMoveDir ); + if (self->s.NPC_class != CLASS_VEHICLE) + { //YOU ARE BREAKING MY PREDICTION. Bad clear. + VectorClear( self->client->ps.moveDir ); + } + + if(!self || !self->NPC || !self->client) + { + return; + } + + // dead NPCs have a special think, don't run scripts (for now) + //FIXME: this breaks deathscripts + if ( self->health <= 0 ) + { + DeadThink(); + if ( NPCInfo->nextBStateThink <= level.time ) + { + trap_ICARUS_MaintainTaskManager(self->s.number); + } + VectorCopy(self->r.currentOrigin, self->client->ps.origin); + return; + } + + // see if NPC ai is frozen + if ( debugNPCFreeze.value || (NPC->r.svFlags&SVF_ICARUS_FREEZE) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + ClientThink(self->s.number, &ucmd); + //VectorCopy(self->s.origin, self->s.origin2 ); + VectorCopy(self->r.currentOrigin, self->client->ps.origin); + return; + } + + self->nextthink = level.time + FRAMETIME/2; + + + while (i < MAX_CLIENTS) + { + player = &g_entities[i]; + + if (player->inuse && player->client && player->client->sess.sessionTeam != TEAM_SPECTATOR && + !(player->client->ps.pm_flags & PMF_FOLLOW)) + { + //if ( player->client->ps.viewEntity == self->s.number ) + if (0) //rwwFIXMEFIXME: Allow controlling ents + {//being controlled by player + G_DroidSounds( self ); + //FIXME: might want to at least make sounds or something? + //NPC_UpdateAngles(qtrue, qtrue); + //Which ucmd should we send? Does it matter, since it gets overridden anyway? + NPCInfo->last_ucmd.serverTime = level.time - 50; + ClientThink( NPC->s.number, &ucmd ); + //VectorCopy(self->s.origin, self->s.origin2 ); + VectorCopy(self->r.currentOrigin, self->client->ps.origin); + return; + } + } + i++; + } + + if ( self->client->NPC_class == CLASS_VEHICLE) + { + if (self->client->ps.m_iVehicleNum) + {//we don't think on our own + //well, run scripts, though... + trap_ICARUS_MaintainTaskManager(self->s.number); + return; + } + else + { + VectorClear(self->client->ps.moveDir); + self->client->pers.cmd.forwardmove = 0; + self->client->pers.cmd.rightmove = 0; + self->client->pers.cmd.upmove = 0; + self->client->pers.cmd.buttons = 0; + memcpy(&self->m_pVehicle->m_ucmd, &self->client->pers.cmd, sizeof(usercmd_t)); + } + } + else if ( NPC->s.m_iVehicleNum ) + {//droid in a vehicle? + G_DroidSounds( self ); + } + + if ( NPCInfo->nextBStateThink <= level.time + && !NPC->s.m_iVehicleNum )//NPCs sitting in Vehicles do NOTHING + { +#if AI_TIMERS + int startTime = GetTime(0); +#endif// AI_TIMERS + if ( NPC->s.eType != ET_NPC ) + {//Something drastic happened in our script + return; + } + + if ( NPC->s.weapon == WP_SABER && g_spskill.integer >= 2 && NPCInfo->rank > RANK_LT_JG ) + {//Jedi think faster on hard difficulty, except low-rank (reborn) + NPCInfo->nextBStateThink = level.time + FRAMETIME/2; + } + else + {//Maybe even 200 ms? + NPCInfo->nextBStateThink = level.time + FRAMETIME; + } + + //nextthink is set before this so something in here can override it + if (self->s.NPC_class != CLASS_VEHICLE || + !self->m_pVehicle) + { //ok, let's not do this at all for vehicles. + NPC_ExecuteBState( self ); + } + +#if AI_TIMERS + int addTime = GetTime( startTime ); + if ( addTime > 50 ) + { + Com_Printf( S_COLOR_RED"ERROR: NPC number %d, %s %s at %s, weaponnum: %d, using %d of AI time!!!\n", NPC->s.number, NPC->NPC_type, NPC->targetname, vtos(NPC->r.currentOrigin), NPC->s.weapon, addTime ); + } + AITime += addTime; +#endif// AI_TIMERS + } + else + { + VectorCopy( oldMoveDir, self->client->ps.moveDir ); + //or use client->pers.lastCommand? + NPCInfo->last_ucmd.serverTime = level.time - 50; + if ( !NPC->next_roff_time || NPC->next_roff_time < level.time ) + {//If we were following a roff, we don't do normal pmoves. + //FIXME: firing angles (no aim offset) or regular angles? + NPC_UpdateAngles(qtrue, qtrue); + memcpy( &ucmd, &NPCInfo->last_ucmd, sizeof( usercmd_t ) ); + ClientThink(NPC->s.number, &ucmd); + } + else + { + NPC_ApplyRoff(); + } + //VectorCopy(self->s.origin, self->s.origin2 ); + } + //must update icarus *every* frame because of certain animation completions in the pmove stuff that can leave a 50ms gap between ICARUS animation commands + trap_ICARUS_MaintainTaskManager(self->s.number); + VectorCopy(self->r.currentOrigin, self->client->ps.origin); +} + +void NPC_InitAI ( void ) +{ + /* + trap_Cvar_Register(&g_saberRealisticCombat, "g_saberRealisticCombat", "0", CVAR_CHEAT); + + trap_Cvar_Register(&debugNoRoam, "d_noroam", "0", CVAR_CHEAT); + trap_Cvar_Register(&debugNPCAimingBeam, "d_npcaiming", "0", CVAR_CHEAT); + trap_Cvar_Register(&debugBreak, "d_break", "0", CVAR_CHEAT); + trap_Cvar_Register(&debugNPCAI, "d_npcai", "0", CVAR_CHEAT); + trap_Cvar_Register(&debugNPCFreeze, "d_npcfreeze", "0", CVAR_CHEAT); + trap_Cvar_Register(&d_JediAI, "d_JediAI", "0", CVAR_CHEAT); + trap_Cvar_Register(&d_noGroupAI, "d_noGroupAI", "0", CVAR_CHEAT); + trap_Cvar_Register(&d_asynchronousGroupAI, "d_asynchronousGroupAI", "0", CVAR_CHEAT); + + //0 = never (BORING) + //1 = kyle only + //2 = kyle and last enemy jedi + //3 = kyle and any enemy jedi + //4 = kyle and last enemy in a group + //5 = kyle and any enemy + //6 = also when kyle takes pain or enemy jedi dodges player saber swing or does an acrobatic evasion + + trap_Cvar_Register(&d_slowmodeath, "d_slowmodeath", "0", CVAR_CHEAT); + + trap_Cvar_Register(&d_saberCombat, "d_saberCombat", "0", CVAR_CHEAT); + + trap_Cvar_Register(&g_spskill, "g_npcspskill", "0", CVAR_ARCHIVE | CVAR_USERINFO); + */ +} + +/* +================================== +void NPC_InitAnimTable( void ) + + Need to initialize this table. + If someone tried to play an anim + before table is filled in with + values, causes tasks that wait for + anim completion to never finish. + (frameLerp of 0 * numFrames of 0 = 0) +================================== +*/ +/* +void NPC_InitAnimTable( void ) +{ + int i; + + for ( i = 0; i < MAX_ANIM_FILES; i++ ) + { + for ( int j = 0; j < MAX_ANIMATIONS; j++ ) + { + level.knownAnimFileSets[i].animations[j].firstFrame = 0; + level.knownAnimFileSets[i].animations[j].frameLerp = 100; + level.knownAnimFileSets[i].animations[j].initialLerp = 100; + level.knownAnimFileSets[i].animations[j].numFrames = 0; + } + } +} +*/ + +void NPC_InitGame( void ) +{ +// globals.NPCs = (gNPC_t *) gi.TagMalloc(game.maxclients * sizeof(game.bots[0]), TAG_GAME); +// trap_Cvar_Register(&debugNPCName, "d_npc", "0", CVAR_CHEAT); + + NPC_LoadParms(); + NPC_InitAI(); +// NPC_InitAnimTable(); + /* + ResetTeamCounters(); + for ( int team = NPCTEAM_FREE; team < NPCTEAM_NUM_TEAMS; team++ ) + { + teamLastEnemyTime[team] = -10000; + } + */ +} + +void NPC_SetAnim(gentity_t *ent, int setAnimParts, int anim, int setAnimFlags) +{ // FIXME : once torsoAnim and legsAnim are in the same structure for NCP and Players + // rename PM_SETAnimFinal to PM_SetAnim and have both NCP and Players call PM_SetAnim + G_SetAnim(ent, NULL, setAnimParts, anim, setAnimFlags, 0); +/* + if(ent->client) + {//Players, NPCs + if (setAnimFlags&SETANIM_FLAG_OVERRIDE) + { + if (setAnimParts & SETANIM_TORSO) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.torsoAnim != anim ) + { + PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoTimer, 0 ); + } + } + if (setAnimParts & SETANIM_LEGS) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->client->ps.legsAnim != anim ) + { + PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, 0 ); + } + } + } + + PM_SetAnimFinal(&ent->client->ps.torsoAnim,&ent->client->ps.legsAnim,setAnimParts,anim,setAnimFlags, + &ent->client->ps.torsoAnimTimer,&ent->client->ps.legsAnimTimer,ent); + } + else + {//bodies, etc. + if (setAnimFlags&SETANIM_FLAG_OVERRIDE) + { + if (setAnimParts & SETANIM_TORSO) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.torsoAnim != anim ) + { + PM_SetTorsoAnimTimer( ent, &ent->s.torsoAnimTimer, 0 ); + } + } + if (setAnimParts & SETANIM_LEGS) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || ent->s.legsAnim != anim ) + { + PM_SetLegsAnimTimer( ent, &ent->s.legsAnimTimer, 0 ); + } + } + } + + PM_SetAnimFinal(&ent->s.torsoAnim,&ent->s.legsAnim,setAnimParts,anim,setAnimFlags, + &ent->s.torsoAnimTimer,&ent->s.legsAnimTimer,ent); + } + */ +} diff --git a/code/game/NPC_AI_Atst.c b/code/game/NPC_AI_Atst.c new file mode 100644 index 0000000..bf8f544 --- /dev/null +++ b/code/game/NPC_AI_Atst.c @@ -0,0 +1,328 @@ +#include "b_local.h" + +#define MIN_MELEE_RANGE 640 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define TURN_OFF 0x00000100//G2SURFACEFLAG_NODESCENDANTS + +#define LEFT_ARM_HEALTH 40 +#define RIGHT_ARM_HEALTH 40 + +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +/* +------------------------- +NPC_ATST_Precache +------------------------- +*/ +void NPC_ATST_Precache(void) +{ + G_SoundIndex( "sound/chars/atst/atst_damaged1" ); + G_SoundIndex( "sound/chars/atst/atst_damaged2" ); + +// RegisterItem( BG_FindItemForWeapon( WP_ATST_MAIN )); //precache the weapon + //rwwFIXMEFIXME: add this weapon + RegisterItem( BG_FindItemForWeapon( WP_BOWCASTER )); //precache the weapon + RegisterItem( BG_FindItemForWeapon( WP_ROCKET_LAUNCHER )); //precache the weapon + + G_EffectIndex( "env/med_explode2" ); +// G_EffectIndex( "smaller_chunks" ); + G_EffectIndex( "blaster/smoke_bolton" ); + G_EffectIndex( "explosions/droidexplosion1" ); +} + +//----------------------------------------------------------------- +#if 0 +static void ATST_PlayEffect( gentity_t *self, const int boltID, const char *fx ) +{ + if ( boltID >=0 && fx && fx[0] ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir; + + trap_G2API_GetBoltMatrix( self->ghoul2, 0, + boltID, + &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time, + NULL, self->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + + G_PlayEffectID( G_EffectIndex((char *)fx), org, dir ); + } +} +#endif + +/* +------------------------- +G_ATSTCheckPain + +Called by NPC's and player in an ATST +------------------------- +*/ + +void G_ATSTCheckPain( gentity_t *self, gentity_t *other, int damage ) +{ + //int newBolt; + //int hitLoc = gPainHitLoc; + + if ( rand() & 1 ) + { + G_SoundOnEnt( self, CHAN_LESS_ATTEN, "sound/chars/atst/atst_damaged1" ); + } + else + { + G_SoundOnEnt( self, CHAN_LESS_ATTEN, "sound/chars/atst/atst_damaged2" ); + } + + /* + if ((hitLoc==HL_ARM_LT) && (self->locationDamage[HL_ARM_LT] > LEFT_ARM_HEALTH)) + { + if (self->locationDamage[hitLoc] >= LEFT_ARM_HEALTH) // Blow it up? + { + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*flash3" ); + if ( newBolt != -1 ) + { +// G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt1, self->s.number); + ATST_PlayEffect( self, trap_G2API_AddBolt(self->ghoul2, 0, "*flash3"), "env/med_explode2" ); + //G_PlayEffectID( G_EffectIndex("blaster/smoke_bolton"), self->playerModel, newBolt, self->s.number); + //Maybe bother with this some other time. + } + + NPC_SetSurfaceOnOff( self, "head_light_blaster_cann", TURN_OFF ); + } + } + else if ((hitLoc==HL_ARM_RT) && (self->locationDamage[HL_ARM_RT] > RIGHT_ARM_HEALTH)) // Blow it up? + { + if (self->locationDamage[hitLoc] >= RIGHT_ARM_HEALTH) + { + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*flash4" ); + if ( newBolt != -1 ) + { +// G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt2, self->s.number); + ATST_PlayEffect( self, trap_G2API_AddBolt(self->ghoul2, 0, "*flash4"), "env/med_explode2" ); + //G_PlayEffect( "blaster/smoke_bolton", self->playerModel, newBolt, self->s.number); + } + + NPC_SetSurfaceOnOff( self, "head_concussion_charger", TURN_OFF ); + } + } + */ +} +/* +------------------------- +NPC_ATST_Pain +------------------------- +*/ +void NPC_ATST_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + G_ATSTCheckPain( self, attacker, damage ); + NPC_Pain( self, attacker, damage ); +} + +/* +------------------------- +ATST_Hunt +-------------------------` +*/ +void ATST_Hunt( qboolean visible, qboolean advance ) +{ + + if ( NPCInfo->goalEntity == NULL ) + {//hunt + NPCInfo->goalEntity = NPC->enemy; + } + + NPCInfo->combatMove = qtrue; + + NPC_MoveToGoal( qtrue ); + +} + +/* +------------------------- +ATST_Ranged +------------------------- +*/ +void ATST_Ranged( qboolean visible, qboolean advance, qboolean altAttack ) +{ + + if ( TIMER_Done( NPC, "atkDelay" ) && visible ) // Attack? + { + TIMER_Set( NPC, "atkDelay", Q_irand( 500, 3000 ) ); + + if (altAttack) + { + ucmd.buttons |= BUTTON_ATTACK|BUTTON_ALT_ATTACK; + } + else + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ATST_Hunt( visible, advance ); + } +} + +/* +------------------------- +ATST_Attack +------------------------- +*/ +void ATST_Attack( void ) +{ + qboolean altAttack=qfalse; + int blasterTest,chargerTest,weapon; + float distance; + distance_e distRate; + qboolean visible; + qboolean advance; + + if ( NPC_CheckEnemyExt(qfalse) == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + return; + } + + NPC_FaceEnemy( qtrue ); + + // Rate our distance to the target, and our visibilty + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ATST_Hunt( visible, advance ); + return; + } + } + + // Decide what type of attack to do + switch ( distRate ) + { + case DIST_MELEE: +// NPC_ChangeWeapon( WP_ATST_MAIN ); + break; + + case DIST_LONG: + +// NPC_ChangeWeapon( WP_ATST_SIDE ); + //rwwFIXMEFIXME: make atst weaps work. + + // See if the side weapons are there + blasterTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "head_light_blaster_cann" ); + chargerTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "head_concussion_charger" ); + + // It has both side weapons + if ( blasterTest != -1 + && !(blasterTest&TURN_OFF) + && chargerTest != -1 + && !(chargerTest&TURN_OFF)) + { + weapon = Q_irand( 0, 1); // 0 is blaster, 1 is charger (ALT SIDE) + + if (weapon) // Fire charger + { + altAttack = qtrue; + } + else + { + altAttack = qfalse; + } + + } + else if (blasterTest != -1 + && !(blasterTest & TURN_OFF)) // Blaster is on + { + altAttack = qfalse; + } + else if (chargerTest != -1 + &&!(chargerTest & TURN_OFF)) // Blaster is on + { + altAttack = qtrue; + } + else + { + NPC_ChangeWeapon( WP_NONE ); + } + break; + } + + NPC_FaceEnemy( qtrue ); + + ATST_Ranged( visible, advance,altAttack ); +} + +/* +------------------------- +ATST_Patrol +------------------------- +*/ +void ATST_Patrol( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + } + } + +} + +/* +------------------------- +ATST_Idle +------------------------- +*/ +void ATST_Idle( void ) +{ + + NPC_BSIdle(); + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_NORMAL ); +} + +/* +------------------------- +NPC_BSDroid_Default +------------------------- +*/ +void NPC_BSATST_Default( void ) +{ + if ( NPC->enemy ) + { + if( (NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) ) + { + NPCInfo->goalEntity = NPC->enemy; + } + ATST_Attack(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + ATST_Patrol(); + } + else + { + ATST_Idle(); + } +} diff --git a/code/game/NPC_AI_Default.c b/code/game/NPC_AI_Default.c new file mode 100644 index 0000000..d65392d --- /dev/null +++ b/code/game/NPC_AI_Default.c @@ -0,0 +1,957 @@ +#include "b_local.h" +#include "g_nav.h" +#include "../icarus/Q3_Interface.h" + +//#include "anims.h" +//extern int PM_AnimLength( int index, animNumber_t anim ); +//extern int PM_AnimLength( int index, animNumber_t anim ); +//#define MAX_IDLE_ANIMS 8 + +extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent); + +/* +void NPC_LostEnemyDecideChase(void) + + We lost our enemy and want to drop him but see if we should chase him if we are in the proper bState +*/ + +void NPC_LostEnemyDecideChase(void) +{ + switch( NPCInfo->behaviorState ) + { + case BS_HUNT_AND_KILL: + //We were chasing him and lost him, so try to find him + if ( NPC->enemy == NPCInfo->goalEntity && NPC->enemy->lastWaypoint != WAYPOINT_NONE ) + {//Remember his last valid Wp, then check it out + //FIXME: Should we only do this if there's no other enemies or we've got LOCKED_ENEMY on? + NPC_BSSearchStart( NPC->enemy->lastWaypoint, BS_SEARCH ); + } + //If he's not our goalEntity, we're running somewhere else, so lose him + break; + default: + break; + } + G_ClearEnemy( NPC ); +} +/* +------------------------- +NPC_StandIdle +------------------------- +*/ + +void NPC_StandIdle( void ) +{ +/* + //Must be done with any other animations + if ( NPC->client->ps.legsAnimTimer != 0 ) + return; + + //Not ready to do another one + if ( TIMER_Done( NPC, "idleAnim" ) == false ) + return; + + int anim = NPC->client->ps.legsAnim; + + if ( anim != BOTH_STAND1 && anim != BOTH_STAND2 ) + return; + + //FIXME: Account for STAND1 or STAND2 here and set the base anim accordingly + int baseSeq = ( anim == BOTH_STAND1 ) ? BOTH_STAND1_RANDOM1 : BOTH_STAND2_RANDOM1; + + //Must have at least one random idle animation + //NOTENOTE: This relies on proper ordering of animations, which SHOULD be okay + if ( PM_HasAnimation( NPC, baseSeq ) == false ) + return; + + int newIdle = Q_irand( 0, MAX_IDLE_ANIMS-1 ); + + //FIXME: Technically this could never complete.. but that's not really too likely + while( 1 ) + { + if ( PM_HasAnimation( NPC, baseSeq + newIdle ) ) + break; + + newIdle = Q_irand( 0, MAX_IDLE_ANIMS ); + } + + //Start that animation going + NPC_SetAnim( NPC, SETANIM_BOTH, baseSeq + newIdle, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + int newTime = PM_AnimLength( NPC->client->clientInfo.animFileIndex, (animNumber_t) (baseSeq + newIdle) ); + + //Don't do this again for a random amount of time + TIMER_Set( NPC, "idleAnim", newTime + Q_irand( 2000, 10000 ) ); +*/ +} + +qboolean NPC_StandTrackAndShoot (gentity_t *NPC, qboolean canDuck) +{ + qboolean attack_ok = qfalse; + qboolean duck_ok = qfalse; + qboolean faced = qfalse; + float attack_scale = 1.0; + + //First see if we're hurt bad- if so, duck + //FIXME: if even when ducked, we can shoot someone, we should. + //Maybe is can be shot even when ducked, we should run away to the nearest cover? + if ( canDuck ) + { + if ( NPC->health < 20 ) + { + // if( NPC->svFlags&SVF_HEALING || random() ) + if( random() ) + { + duck_ok = qtrue; + } + } + else if ( NPC->health < 40 ) + { +// if ( NPC->svFlags&SVF_HEALING ) +// {//Medic is on the way, get down! +// duck_ok = qtrue; +// } + // no more borg +/// if ( NPC->client->playerTeam!= TEAM_BORG ) +// {//Borg don't care if they're about to die + //attack_scale will be a max of .66 +// attack_scale = NPC->health/60; +// } + } + } + + //NPC_CheckEnemy( qtrue, qfalse, qtrue ); + + if ( !duck_ok ) + {//made this whole part a function call + attack_ok = NPC_CheckCanAttack( attack_scale, qtrue ); + faced = qtrue; + } + + if ( canDuck && (duck_ok || (!attack_ok && client->ps.weaponTime <= 0)) && ucmd.upmove != -127 ) + {//if we didn't attack check to duck if we're not already + if( !duck_ok ) + { + if ( NPC->enemy->client ) + { + if ( NPC->enemy->enemy == NPC ) + { + if ( NPC->enemy->client->buttons & BUTTON_ATTACK ) + {//FIXME: determine if enemy fire angles would hit me or get close + if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation? Health? + { + duck_ok = qtrue; + } + } + } + } + } + + if ( duck_ok ) + {//duck and don't shoot + attack_ok = qfalse; + ucmd.upmove = -127; + NPCInfo->duckDebounceTime = level.time + 1000;//duck for a full second + } + } + + return faced; +} + + +void NPC_BSIdle( void ) +{ + //FIXME if there is no nav data, we need to do something else + // if we're stuck, try to move around it + if ( UpdateGoal() ) + { + NPC_MoveToGoal( qtrue ); + } + + if ( ( ucmd.forwardmove == 0 ) && ( ucmd.rightmove == 0 ) && ( ucmd.upmove == 0 ) ) + { +// NPC_StandIdle(); + } + + NPC_UpdateAngles( qtrue, qtrue ); + ucmd.buttons |= BUTTON_WALKING; +} + +void NPC_BSRun (void) +{ + //FIXME if there is no nav data, we need to do something else + // if we're stuck, try to move around it + if ( UpdateGoal() ) + { + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +void NPC_BSStandGuard (void) +{ + //FIXME: Use Snapshot info + if ( NPC->enemy == NULL ) + {//Possible to pick one up by being shot + if( random() < 0.5 ) + { + if(NPC->client->enemyTeam) + { + gentity_t *newenemy = NPC_PickEnemy(NPC, NPC->client->enemyTeam, (NPC->cantHitEnemyCounter < 10), (NPC->client->enemyTeam == NPCTEAM_PLAYER), qtrue); + //only checks for vis if couldn't hit last enemy + if(newenemy) + { + G_SetEnemy( NPC, newenemy ); + } + } + } + } + + if ( NPC->enemy != NULL ) + { + if( NPCInfo->tempBehavior == BS_STAND_GUARD ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + + if( NPCInfo->behaviorState == BS_STAND_GUARD ) + { + NPCInfo->behaviorState = BS_STAND_AND_SHOOT; + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSHuntAndKill +------------------------- +*/ + +void NPC_BSHuntAndKill( void ) +{ + qboolean turned = qfalse; + vec3_t vec; + float enemyDist; + visibility_t oEVis; + int curAnim; + + NPC_CheckEnemy( NPCInfo->tempBehavior != BS_HUNT_AND_KILL, qfalse, qtrue );//don't find new enemy if this is tempbehav + + if ( NPC->enemy ) + { + oEVis = enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|//CHECK_PVS| + if(enemyVisibility > VIS_PVS) + { + if ( !NPC_EnemyTooFar( NPC->enemy, 0, qtrue ) ) + {//Enemy is close enough to shoot - FIXME: this next func does this also, but need to know here for info on whether ot not to turn later + NPC_CheckCanAttack( 1.0, qfalse ); + turned = qtrue; + } + } + + curAnim = NPC->client->ps.legsAnim; + if(curAnim != BOTH_ATTACK1 && curAnim != BOTH_ATTACK2 && curAnim != BOTH_ATTACK3 && curAnim != BOTH_MELEE1 && curAnim != BOTH_MELEE2 ) + {//Don't move toward enemy if we're in a full-body attack anim + //FIXME, use IdealDistance to determin if we need to close distance + VectorSubtract(NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec); + enemyDist = VectorLength(vec); + if( enemyDist > 48 && ((enemyDist*1.5)*(enemyDist*1.5) >= NPC_MaxDistSquaredForWeapon() || + oEVis != VIS_SHOOT || + //!(ucmd.buttons & BUTTON_ATTACK) || + enemyDist > IdealDistance(NPC)*3 ) ) + {//We should close in? + NPCInfo->goalEntity = NPC->enemy; + + NPC_MoveToGoal( qtrue ); + } + else if(enemyDist < IdealDistance(NPC)) + {//We should back off? + //if(ucmd.buttons & BUTTON_ATTACK) + { + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + NPC_MoveToGoal( qtrue ); + + ucmd.forwardmove *= -1; + ucmd.rightmove *= -1; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + + ucmd.buttons |= BUTTON_WALKING; + } + }//otherwise, stay where we are + } + } + else + {//ok, stand guard until we find an enemy + if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + { + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + } + return; + } + + if(!turned) + { + NPC_UpdateAngles(qtrue, qtrue); + } +} + +void NPC_BSStandAndShoot (void) +{ + //FIXME: + //When our numbers outnumber enemies 3 to 1, or only one of them, + //go into hunt and kill mode + + //FIXME: + //When they're all dead, go to some script or wander off to sickbay? + + if(NPC->client->playerTeam && NPC->client->enemyTeam) + { + //FIXME: don't realize this right away- or else enemies show up and we're standing around + /* + if( teamNumbers[NPC->enemyTeam] == 0 ) + {//ok, stand guard until we find another enemy + //reset our rush counter + teamCounter[NPC->playerTeam] = 0; + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + return; + }*/ + /* + //FIXME: whether to do this or not should be settable + else if( NPC->playerTeam != TEAM_BORG )//Borg don't rush + { + //FIXME: In case reinforcements show up, we should wait a few seconds + //and keep checking before rushing! + //Also: what if not everyone on our team is going after playerTeam? + //Also: our team count includes medics! + if(NPC->health > 25) + {//Can we rush the enemy? + if(teamNumbers[NPC->enemyTeam] == 1 || + teamNumbers[NPC->playerTeam] >= teamNumbers[NPC->enemyTeam]*3) + {//Only one of them or we outnumber 3 to 1 + if(teamStrength[NPC->playerTeam] >= 75 || + (teamStrength[NPC->playerTeam] >= 50 && teamStrength[NPC->playerTeam] > teamStrength[NPC->enemyTeam])) + {//Our team is strong enough to rush + teamCounter[NPC->playerTeam]++; + if(teamNumbers[NPC->playerTeam] * 17 <= teamCounter[NPC->playerTeam]) + {//ok, we waited 1.7 think cycles on average and everyone is go, let's do it! + //FIXME: Should we do this to everyone on our team? + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + //FIXME: if the tide changes, we should retreat! + //FIXME: when do we reset the counter? + NPC_BSHuntAndKill (); + return; + } + } + else//Oops! Something's wrong, reset the counter to rush + teamCounter[NPC->playerTeam] = 0; + } + else//Oops! Something's wrong, reset the counter to rush + teamCounter[NPC->playerTeam] = 0; + } + } + */ + } + + NPC_CheckEnemy(qtrue, qfalse, qtrue); + + if(NPCInfo->duckDebounceTime > level.time && NPC->client->ps.weapon != WP_SABER ) + { + ucmd.upmove = -127; + if(NPC->enemy) + { + NPC_CheckCanAttack(1.0, qtrue); + } + return; + } + + if(NPC->enemy) + { + if(!NPC_StandTrackAndShoot( NPC, qtrue )) + {//That func didn't update our angles + NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH]; + NPC_UpdateAngles(qtrue, qtrue); + } + } + else + { + NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH]; + NPC_UpdateAngles(qtrue, qtrue); +// NPC_BSIdle();//only moves if we have a goal + } +} + +void NPC_BSRunAndShoot (void) +{ + /*if(NPC->playerTeam && NPC->enemyTeam) + { + //FIXME: don't realize this right away- or else enemies show up and we're standing around + if( teamNumbers[NPC->enemyTeam] == 0 ) + {//ok, stand guard until we find another enemy + //reset our rush counter + teamCounter[NPC->playerTeam] = 0; + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + return; + } + }*/ + + //NOTE: are we sure we want ALL run and shoot people to move this way? + //Shouldn't it check to see if we have an enemy and our enemy is our goal?! + //Moved that check into NPC_MoveToGoal + //NPCInfo->combatMove = qtrue; + + NPC_CheckEnemy( qtrue, qfalse, qtrue ); + + if ( NPCInfo->duckDebounceTime > level.time ) // && NPCInfo->hidingGoal ) + { + ucmd.upmove = -127; + if ( NPC->enemy ) + { + NPC_CheckCanAttack( 1.0, qfalse ); + } + return; + } + + if ( NPC->enemy ) + { + int monitor = NPC->cantHitEnemyCounter; + NPC_StandTrackAndShoot( NPC, qfalse );//(NPCInfo->hidingGoal != NULL) ); + + if ( !(ucmd.buttons & BUTTON_ATTACK) && ucmd.upmove >= 0 && NPC->cantHitEnemyCounter > monitor ) + {//not crouching and not firing + vec3_t vec; + + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, vec ); + vec[2] = 0; + if ( VectorLength( vec ) > 128 || NPC->cantHitEnemyCounter >= 10 ) + {//run at enemy if too far away + //The cantHitEnemyCounter getting high has other repercussions + //100 (10 seconds) will make you try to pick a new enemy... + //But we're chasing, so we clamp it at 50 here + if ( NPC->cantHitEnemyCounter > 60 ) + { + NPC->cantHitEnemyCounter = 60; + } + + if ( NPC->cantHitEnemyCounter >= (NPCInfo->stats.aggression+1) * 10 ) + { + NPC_LostEnemyDecideChase(); + } + + //chase and face + ucmd.angles[YAW] = 0; + ucmd.angles[PITCH] = 0; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + //NAV_ClearLastRoute(NPC); + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles(qtrue, qtrue); + } + else + { + //FIXME: this could happen if they're just on the other side + //of a thin wall or something else blocking out shot. That + //would make us just stand there and not go around it... + //but maybe it's okay- might look like we're waiting for + //him to come out...? + //Current solution: runs around if cantHitEnemyCounter gets + //to 10 (1 second). + } + } + else + {//Clear the can't hit enemy counter here + NPC->cantHitEnemyCounter = 0; + } + } + else + { + if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + {//lost him, go back to what we were doing before + NPCInfo->tempBehavior = BS_DEFAULT; + return; + } + +// NPC_BSRun();//only moves if we have a goal + } +} + +//Simply turn until facing desired angles +void NPC_BSFace (void) +{ + //FIXME: once you stop sending turning info, they reset to whatever their delta_angles was last???? + //Once this is over, it snaps back to what it was facing before- WHY??? + if( NPC_UpdateAngles ( qtrue, qtrue ) ) + { + trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE ); + + NPCInfo->desiredYaw = client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = client->ps.viewangles[PITCH]; + + NPCInfo->aimTime = 0;//ok to turn normally now + } +} + +void NPC_BSPointShoot (qboolean shoot) +{//FIXME: doesn't check for clear shot... + vec3_t muzzle, dir, angles, org; + + if ( !NPC->enemy || !NPC->enemy->inuse || (NPC->enemy->NPC && NPC->enemy->health <= 0) ) + {//FIXME: should still keep shooting for a second or two after they actually die... + trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE ); + goto finished; + return; + } + + CalcEntitySpot(NPC, SPOT_WEAPON, muzzle); + CalcEntitySpot(NPC->enemy, SPOT_HEAD, org);//Was spot_org + //Head is a little high, so let's aim for the chest: + if ( NPC->enemy->client ) + { + org[2] -= 12;//NOTE: is this enough? + } + + VectorSubtract(org, muzzle, dir); + vectoangles(dir, angles); + + switch( NPC->client->ps.weapon ) + { + case WP_NONE: +// case WP_TRICORDER: + case WP_STUN_BATON: + case WP_SABER: + //don't do any pitch change if not holding a firing weapon + break; + default: + NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]); + break; + } + + NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]); + + if ( NPC_UpdateAngles ( qtrue, qtrue ) ) + {//FIXME: if angles clamped, this may never work! + //NPCInfo->shotTime = NPC->attackDebounceTime = 0; + + if ( shoot ) + {//FIXME: needs to hold this down if using a weapon that requires it, like phaser... + ucmd.buttons |= BUTTON_ATTACK; + } + + //if ( !shoot || !(NPC->svFlags & SVF_LOCKEDENEMY) ) + if (1) + {//If locked_enemy is on, dont complete until it is destroyed... + trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE ); + goto finished; + } + } + //else if ( shoot && (NPC->svFlags & SVF_LOCKEDENEMY) ) + if (0) + {//shooting them till their dead, not aiming right at them yet... + /* + qboolean movingTarget = qfalse; + + if ( NPC->enemy->client ) + { + if ( VectorLengthSquared( NPC->enemy->client->ps.velocity ) ) + { + movingTarget = qtrue; + } + } + else if ( VectorLengthSquared( NPC->enemy->s.pos.trDelta ) ) + { + movingTarget = qtrue; + } + + if (movingTarget ) + */ + { + float dist = VectorLength( dir ); + float yawMiss, yawMissAllow = NPC->enemy->r.maxs[0]; + float pitchMiss, pitchMissAllow = (NPC->enemy->r.maxs[2] - NPC->enemy->r.mins[2])/2; + + if ( yawMissAllow < 8.0f ) + { + yawMissAllow = 8.0f; + } + + if ( pitchMissAllow < 8.0f ) + { + pitchMissAllow = 8.0f; + } + + yawMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ))) * dist; + pitchMiss = tan(DEG2RAD(AngleDelta ( NPC->client->ps.viewangles[PITCH], NPCInfo->desiredPitch))) * dist; + + if ( yawMissAllow >= yawMiss && pitchMissAllow > pitchMiss ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + } + + return; + +finished: + NPCInfo->desiredYaw = client->ps.viewangles[YAW]; + NPCInfo->desiredPitch = client->ps.viewangles[PITCH]; + + NPCInfo->aimTime = 0;//ok to turn normally now +} + +/* +void NPC_BSMove(void) +Move in a direction, face another +*/ +void NPC_BSMove(void) +{ + gentity_t *goal = NULL; + + NPC_CheckEnemy(qtrue, qfalse, qtrue); + if(NPC->enemy) + { + NPC_CheckCanAttack(1.0, qfalse); + } + else + { + NPC_UpdateAngles(qtrue, qtrue); + } + + goal = UpdateGoal(); + if(goal) + { +// NPCInfo->moveToGoalMod = 1.0; + + NPC_SlideMoveToGoal(); + } +} + +/* +void NPC_BSShoot(void) +Move in a direction, face another +*/ + +void NPC_BSShoot(void) +{ +// NPC_BSMove(); + + enemyVisibility = VIS_SHOOT; + + if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING ) + { + client->ps.weaponstate = WEAPON_READY; + } + + WeaponThink(qtrue); +} + +/* +void NPC_BSPatrol( void ) + + Same as idle, but you look for enemies every "vigilance" + using your angles, HFOV, VFOV and visrange, and listen for sounds within earshot... +*/ +void NPC_BSPatrol( void ) +{ + //int alertEventNum; + + if(level.time > NPCInfo->enemyCheckDebounceTime) + { + NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 1000); + NPC_CheckEnemy(qtrue, qfalse, qtrue); + if(NPC->enemy) + {//FIXME: do anger script + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + //NPC_AngerSound(); + return; + } + } + + //FIXME: Implement generic sound alerts + /* + alertEventNum = NPC_CheckAlertEvents( qtrue, qtrue ); + if( alertEventNum != -1 ) + {//If we heard something, see if we should check it out + if ( NPC_CheckInvestigate( alertEventNum ) ) + { + return; + } + } + */ + + NPCInfo->investigateSoundDebounceTime = 0; + //FIXME if there is no nav data, we need to do something else + // if we're stuck, try to move around it + if ( UpdateGoal() ) + { + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); + + ucmd.buttons |= BUTTON_WALKING; +} + +/* +void NPC_BSDefault(void) + uses various scriptflags to determine how an npc should behave +*/ +extern void NPC_CheckGetNewWeapon( void ); +extern void NPC_BSST_Attack( void ); + +void NPC_BSDefault( void ) +{ +// vec3_t enemyDir; +// float enemyDist; +// float shootDist; +// qboolean enemyFOV = qfalse; +// qboolean enemyShotFOV = qfalse; +// qboolean enemyPVS = qfalse; +// vec3_t enemyHead; +// vec3_t muzzle; +// qboolean enemyLOS = qfalse; +// qboolean enemyCS = qfalse; + qboolean move = qtrue; +// qboolean shoot = qfalse; + + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) + {//being forced to walk + if( NPC->client->ps.torsoAnim != TORSO_SURRENDER_START ) + { + NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD ); + } + } + //look for a new enemy if don't have one and are allowed to look, validate current enemy if have one + NPC_CheckEnemy( (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES), qfalse, qtrue ); + if ( !NPC->enemy ) + {//still don't have an enemy + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + {//check for alert events + //FIXME: Check Alert events, see if we should investigate or just look at it + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED ); + + //There is an event to look at + if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + {//heard/saw something + if ( level.alertEvents[alertEvent].level >= AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + {//was a big event + if ( level.alertEvents[alertEvent].owner && level.alertEvents[alertEvent].owner->client && level.alertEvents[alertEvent].owner->health >= 0 && level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + } + } + else + {//FIXME: investigate lesser events + } + } + //FIXME: also check our allies' condition? + } + } + + if ( NPC->enemy && !(NPCInfo->scriptFlags&SCF_FORCED_MARCH) ) + { + // just use the stormtrooper attack AI... + NPC_CheckGetNewWeapon(); + if ( NPC->client->leader + && NPCInfo->goalEntity == NPC->client->leader + && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + { + NPC_ClearGoal(); + } + NPC_BSST_Attack(); + return; +/* + //have an enemy + //FIXME: if one of these fails, meaning we can't shoot, do we really need to do the rest? + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir ); + enemyDist = VectorNormalize( enemyDir ); + enemyDist *= enemyDist; + shootDist = NPC_MaxDistSquaredForWeapon(); + + enemyFOV = InFOV( NPC->enemy, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ); + enemyShotFOV = InFOV( NPC->enemy, NPC, 20, 20 ); + enemyPVS = gi.inPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ); + + if ( enemyPVS ) + {//in the pvs + trace_t tr; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemyHead ); + enemyHead[2] -= Q_flrand( 0.0f, NPC->enemy->maxs[2]*0.5f ); + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + enemyLOS = NPC_ClearLOS( muzzle, enemyHead ); + + gi.trace ( &tr, muzzle, vec3_origin, vec3_origin, enemyHead, NPC->s.number, MASK_SHOT ); + enemyCS = NPC_EvaluateShot( tr.entityNum, qtrue ); + } + else + {//skip thr 2 traces since they would have to fail + enemyLOS = qfalse; + enemyCS = qfalse; + } + + if ( enemyCS && enemyShotFOV ) + {//can hit enemy if we want + NPC->cantHitEnemyCounter = 0; + } + else + {//can't hit + NPC->cantHitEnemyCounter++; + } + + if ( enemyCS && enemyShotFOV && enemyDist < shootDist ) + {//can shoot + shoot = qtrue; + if ( NPCInfo->goalEntity == NPC->enemy ) + {//my goal is my enemy and I have a clear shot, no need to chase right now + move = qfalse; + } + } + else + {//don't shoot yet, keep chasing + shoot = qfalse; + move = qtrue; + } + + //shoot decision + if ( !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//try to shoot + if ( NPC->enemy ) + { + if ( shoot ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + } + } + } + + //chase decision + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + {//go after him + NPCInfo->goalEntity = NPC->enemy; + //FIXME: don't need to chase when have a clear shot and in range? + if ( !enemyCS && NPC->cantHitEnemyCounter > 60 ) + {//haven't been able to shoot enemy for about 6 seconds, need to do something + //FIXME: combat points? Just chase? + if ( enemyPVS ) + {//in my PVS, just pick a combat point + //FIXME: implement + } + else + {//just chase him + } + } + //FIXME: in normal behavior, should we use combat Points? Do we care? Is anyone actually going to ever use this AI? + } + else if ( NPC->cantHitEnemyCounter > 60 ) + {//pick a new one + NPC_CheckEnemy( qtrue, qfalse, qtrue ); + } + + if ( enemyPVS && enemyLOS )//&& !enemyShotFOV ) + {//have a clear LOS to him//, but not looking at him + //Find the desired angles + vec3_t angles; + + GetAnglesForDirection( muzzle, enemyHead, angles ); + + NPCInfo->desiredYaw = AngleNormalize180( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize180( angles[PITCH] ); + } + */ + } + + if ( UpdateGoal() ) + {//have a goal + if ( !NPC->enemy + && NPC->client->leader + && NPCInfo->goalEntity == NPC->client->leader + && !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + { + NPC_BSFollowLeader(); + } + else + { + //set angles + if ( (NPCInfo->scriptFlags & SCF_FACE_MOVE_DIR) || NPCInfo->goalEntity != NPC->enemy ) + {//face direction of movement, NOTE: default behavior when not chasing enemy + NPCInfo->combatMove = qfalse; + } + else + {//face goal.. FIXME: what if have a navgoal but want to face enemy while moving? Will this do that? + vec3_t dir, angles; + + NPCInfo->combatMove = qfalse; + + VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir ); + vectoangles( dir, angles ); + NPCInfo->desiredYaw = angles[YAW]; + if ( NPCInfo->goalEntity == NPC->enemy ) + { + NPCInfo->desiredPitch = angles[PITCH]; + } + } + + //set movement + //override default walk/run behavior + //NOTE: redundant, done in NPC_ApplyScriptFlags + if ( NPCInfo->scriptFlags & SCF_RUNNING ) + { + ucmd.buttons &= ~BUTTON_WALKING; + } + else if ( NPCInfo->scriptFlags & SCF_WALKING ) + { + ucmd.buttons |= BUTTON_WALKING; + } + else if ( NPCInfo->goalEntity == NPC->enemy ) + { + ucmd.buttons &= ~BUTTON_WALKING; + } + else + { + ucmd.buttons |= BUTTON_WALKING; + } + + if ( NPCInfo->scriptFlags & SCF_FORCED_MARCH ) + {//being forced to walk + //if ( g_crosshairEntNum != NPC->s.number ) + if (!NPC_SomeoneLookingAtMe(NPC)) + {//don't walk if player isn't aiming at me + move = qfalse; + } + } + + if ( move ) + { + //move toward goal + NPC_MoveToGoal( qtrue ); + } + } + } + else if ( !NPC->enemy && NPC->client->leader ) + { + NPC_BSFollowLeader(); + } + + //update angles + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/code/game/NPC_AI_Droid.c b/code/game/NPC_AI_Droid.c new file mode 100644 index 0000000..f738ed0 --- /dev/null +++ b/code/game/NPC_AI_Droid.c @@ -0,0 +1,621 @@ +#include "b_local.h" + +//static void R5D2_LookAround( void ); +float NPC_GetPainChance( gentity_t *self, int damage ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); + +#define TURN_OFF 0x00000100 + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_BACKINGUP, + LSTATE_SPINNING, + LSTATE_PAIN, + LSTATE_DROP +}; + +/* +------------------------- +R2D2_PartsMove +------------------------- +*/ +void R2D2_PartsMove(void) +{ + // Front 'eye' lense + if ( TIMER_Done(NPC,"eyeDelay") ) + { + NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); + + NPC->pos1[0]+=Q_irand( -20, 20 ); // Roll + NPC->pos1[1]=Q_irand( -20, 20 ); + NPC->pos1[2]=Q_irand( -20, 20 ); + + /* + if (NPC->genericBone1) + { + gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + } + */ + NPC_SetBoneAngles(NPC, "f_eye", NPC->pos1); + + + TIMER_Set( NPC, "eyeDelay", Q_irand( 100, 1000 ) ); + } +} + +/* +------------------------- +NPC_BSDroid_Idle +------------------------- +*/ +void Droid_Idle( void ) +{ +// VectorCopy( NPCInfo->investigateGoal, lookPos ); + +// NPC_FacePosition( lookPos ); +} + +/* +------------------------- +R2D2_TurnAnims +------------------------- +*/ +void R2D2_TurnAnims ( void ) +{ + float turndelta; + int anim; + + turndelta = AngleDelta(NPC->r.currentAngles[YAW], NPCInfo->desiredYaw); + + if ((fabs(turndelta) > 20) && ((NPC->client->NPC_class == CLASS_R2D2) || (NPC->client->NPC_class == CLASS_R5D2))) + { + anim = NPC->client->ps.legsAnim; + if (turndelta<0) + { + if (anim != BOTH_TURN_LEFT1) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_LEFT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + else + { + if (anim != BOTH_TURN_RIGHT1) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TURN_RIGHT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + +} + +/* +------------------------- +Droid_Patrol +------------------------- +*/ +void Droid_Patrol( void ) +{ + + NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); + + if ( NPC->client && NPC->client->NPC_class != CLASS_GONK ) + { + if (NPC->client->NPC_class != CLASS_R5D2) + { //he doesn't have an eye. + R2D2_PartsMove(); // Get his eye moving. + } + R2D2_TurnAnims(); + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + + if( NPC->client && NPC->client->NPC_class == CLASS_MOUSE ) + { + NPCInfo->desiredYaw += sin(level.time*.5) * 25; // Weaves side to side a little + + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/mouse/misc/mousego%d.wav", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + else if( NPC->client && NPC->client->NPC_class == CLASS_R2D2 ) + { + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r2d2/misc/r2d2talk0%d.wav", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + else if( NPC->client && NPC->client->NPC_class == CLASS_R5D2 ) + { + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/r5d2/misc/r5talk%d.wav", Q_irand(1, 4)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + if( NPC->client && NPC->client->NPC_class == CLASS_GONK ) + { + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/gonk/misc/gonktalk%d.wav", Q_irand(1, 2)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } +// else +// { +// R5D2_LookAround(); +// } + } + + NPC_UpdateAngles( qtrue, qtrue ); + +} + +/* +------------------------- +Droid_Run +------------------------- +*/ +void Droid_Run( void ) +{ + R2D2_PartsMove(); + + if ( NPCInfo->localState == LSTATE_BACKINGUP ) + { + ucmd.forwardmove = -127; + NPCInfo->desiredYaw += 5; + + NPCInfo->localState = LSTATE_NONE; // So he doesn't constantly backup. + } + else + { + ucmd.forwardmove = 64; + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + if (NPC_MoveToGoal( qfalse )) + { + NPCInfo->desiredYaw += sin(level.time*.5) * 5; // Weaves side to side a little + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +void Droid_Spin( void ) +------------------------- +*/ +void Droid_Spin( void ) +{ + vec3_t dir = {0,0,1}; + + R2D2_TurnAnims(); + + + // Head is gone, spin and spark + if ( NPC->client->NPC_class == CLASS_R5D2 + || NPC->client->NPC_class == CLASS_R2D2 ) + { + // No head? + if (trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "head" )>0) + { + if (TIMER_Done(NPC,"smoke") && !TIMER_Done(NPC,"droidsmoketotal")) + { + TIMER_Set( NPC, "smoke", 100); + G_PlayEffectID( G_EffectIndex("volumetric/droid_smoke") , NPC->r.currentOrigin,dir); + } + + if (TIMER_Done(NPC,"droidspark")) + { + TIMER_Set( NPC, "droidspark", Q_irand(100,500)); + G_PlayEffectID( G_EffectIndex("sparks/spark"), NPC->r.currentOrigin,dir); + } + + ucmd.forwardmove = Q_irand( -64, 64); + + if (TIMER_Done(NPC,"roam")) + { + TIMER_Set( NPC, "roam", Q_irand( 250, 1000 ) ); + NPCInfo->desiredYaw = Q_irand( 0, 360 ); // Go in random directions + } + } + else + { + if (TIMER_Done(NPC,"roam")) + { + NPCInfo->localState = LSTATE_NONE; + } + else + { + NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around + } + } + } + else + { + if (TIMER_Done(NPC,"roam")) + { + NPCInfo->localState = LSTATE_NONE; + } + else + { + NPCInfo->desiredYaw = AngleNormalize360(NPCInfo->desiredYaw + 40); // Spin around + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSDroid_Pain +------------------------- +*/ +void NPC_Droid_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + gentity_t *other = attacker; + int anim; + int mod = gPainMOD; + float pain_chance; + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( self->client->NPC_class == CLASS_R5D2 ) + { + pain_chance = NPC_GetPainChance( self, damage ); + + // Put it in pain + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this + { + // Health is between 0-30 or was hit by a DEMP2 so pop his head + if ( !self->s.m_iVehicleNum + && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) ) + { + if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE + { + if ((self->NPC->localState != LSTATE_SPINNING) && + (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ))) + { + NPC_SetSurfaceOnOff( self, "head", TURN_OFF ); + + if ( self->client->ps.m_iVehicleNum ) + { + vec3_t up; + AngleVectors( self->r.currentAngles, NULL, NULL, up ); + G_PlayEffectID( G_EffectIndex("chunks/r5d2head_veh"), self->r.currentOrigin, up ); + } + else + { + G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin ); + G_PlayEffectID( G_EffectIndex("chunks/r5d2head"), self->r.currentOrigin, vec3_origin ); + } + + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + self->client->ps.electrifyTime = level.time + 3000; + + TIMER_Set( self, "droidsmoketotal", 5000); + TIMER_Set( self, "droidspark", 100); + self->NPC->localState = LSTATE_SPINNING; + } + } + } + // Just give him normal pain for a little while + else + { + anim = self->client->ps.legsAnim; + + if ( anim == BOTH_STAND2 ) // On two legs? + { + anim = BOTH_PAIN1; + } + else // On three legs + { + anim = BOTH_PAIN2; + } + + NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + // Spin around in pain + self->NPC->localState = LSTATE_SPINNING; + TIMER_Set( self, "roam", Q_irand(1000,2000)); + } + } + } + else if (self->client->NPC_class == CLASS_MOUSE) + { + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) + { + self->NPC->localState = LSTATE_SPINNING; + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + self->client->ps.electrifyTime = level.time + 3000; + } + else + { + self->NPC->localState = LSTATE_BACKINGUP; + } + + self->NPC->scriptFlags &= ~SCF_LOOK_FOR_ENEMIES; + } + else if ((self->client->NPC_class == CLASS_R2D2)) + { + + pain_chance = NPC_GetPainChance( self, damage ); + + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT || random() < pain_chance ) // Spin around in pain? Demp2 always does this + { + // Health is between 0-30 or was hit by a DEMP2 so pop his head + if ( !self->s.m_iVehicleNum + && ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) ) + { + if (!(self->spawnflags & 2)) // Doesn't have to ALWAYSDIE + { + if ((self->NPC->localState != LSTATE_SPINNING) && + (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "head" ))) + { + NPC_SetSurfaceOnOff( self, "head", TURN_OFF ); + + if ( self->client->ps.m_iVehicleNum ) + { + vec3_t up; + AngleVectors( self->r.currentAngles, NULL, NULL, up ); + G_PlayEffectID( G_EffectIndex("chunks/r2d2head_veh"), self->r.currentOrigin, up ); + } + else + { + G_PlayEffectID( G_EffectIndex("small_chunks") , self->r.currentOrigin, vec3_origin ); + G_PlayEffectID( G_EffectIndex("chunks/r2d2head"), self->r.currentOrigin, vec3_origin ); + } + + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + self->client->ps.electrifyTime = level.time + 3000; + + TIMER_Set( self, "droidsmoketotal", 5000); + TIMER_Set( self, "droidspark", 100); + self->NPC->localState = LSTATE_SPINNING; + } + } + } + // Just give him normal pain for a little while + else + { + anim = self->client->ps.legsAnim; + + if ( anim == BOTH_STAND2 ) // On two legs? + { + anim = BOTH_PAIN1; + } + else // On three legs + { + anim = BOTH_PAIN2; + } + + NPC_SetAnim( self, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + // Spin around in pain + self->NPC->localState = LSTATE_SPINNING; + TIMER_Set( self, "roam", Q_irand(1000,2000)); + } + } + } + else if ( self->client->NPC_class == CLASS_INTERROGATOR && ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) && other ) + { + vec3_t dir; + + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); + VectorNormalize( dir ); + + VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); + self->client->ps.velocity[2] -= 127; + } + + NPC_Pain( self, attacker, damage); +} + + +/* +------------------------- +Droid_Pain +------------------------- +*/ +void Droid_Pain(void) +{ + if (TIMER_Done(NPC,"droidpain")) //He's done jumping around + { + NPCInfo->localState = LSTATE_NONE; + } +} + +/* +------------------------- +NPC_Mouse_Precache +------------------------- +*/ +void NPC_Mouse_Precache( void ) +{ + int i; + + for (i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/mouse/misc/mousego%d.wav", i ) ); + } + + G_EffectIndex( "env/small_explode" ); + G_SoundIndex( "sound/chars/mouse/misc/death1" ); + G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" ); +} + +/* +------------------------- +NPC_R5D2_Precache +------------------------- +*/ +void NPC_R5D2_Precache(void) +{ + int i; + + for ( i = 1; i < 5; i++) + { + G_SoundIndex( va( "sound/chars/r5d2/misc/r5talk%d.wav", i ) ); + } + //G_SoundIndex( "sound/chars/r5d2/misc/falling1.wav" ); + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ?? + G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" ); + G_EffectIndex( "env/med_explode"); + G_EffectIndex( "volumetric/droid_smoke" ); + G_EffectIndex("sparks/spark"); + G_EffectIndex( "chunks/r5d2head"); + G_EffectIndex( "chunks/r5d2head_veh"); +} + +/* +------------------------- +NPC_R2D2_Precache +------------------------- +*/ +void NPC_R2D2_Precache(void) +{ + int i; + + for ( i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/r2d2/misc/r2d2talk0%d.wav", i ) ); + } + //G_SoundIndex( "sound/chars/r2d2/misc/falling1.wav" ); + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); // ?? + G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" ); + G_EffectIndex( "env/med_explode"); + G_EffectIndex( "volumetric/droid_smoke" ); + G_EffectIndex("sparks/spark"); + G_EffectIndex( "chunks/r2d2head"); + G_EffectIndex( "chunks/r2d2head_veh"); +} + +/* +------------------------- +NPC_Gonk_Precache +------------------------- +*/ +void NPC_Gonk_Precache( void ) +{ + G_SoundIndex("sound/chars/gonk/misc/gonktalk1.wav"); + G_SoundIndex("sound/chars/gonk/misc/gonktalk2.wav"); + + G_SoundIndex("sound/chars/gonk/misc/death1.wav"); + G_SoundIndex("sound/chars/gonk/misc/death2.wav"); + G_SoundIndex("sound/chars/gonk/misc/death3.wav"); + + G_EffectIndex( "env/med_explode"); +} + +/* +------------------------- +NPC_Protocol_Precache +------------------------- +*/ +void NPC_Protocol_Precache( void ) +{ + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" ); + G_EffectIndex( "env/med_explode"); +} + +/* +static void R5D2_OffsetLook( float offset, vec3_t out ) +{ + vec3_t angles, forward, temp; + + GetAnglesForDirection( NPC->r.currentOrigin, NPCInfo->investigateGoal, angles ); + angles[YAW] += offset; + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( NPC->r.currentOrigin, 64, forward, out ); + + CalcEntitySpot( NPC, SPOT_HEAD, temp ); + out[2] = temp[2]; +} +*/ + +/* +------------------------- +R5D2_LookAround +------------------------- +*/ +/* +static void R5D2_LookAround( void ) +{ + vec3_t lookPos; + float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime; + + //Keep looking at the spot + if ( perc < 0.25 ) + { + VectorCopy( NPCInfo->investigateGoal, lookPos ); + } + else if ( perc < 0.5f ) //Look up but straight ahead + { + R5D2_OffsetLook( 0.0f, lookPos ); + } + else if ( perc < 0.75f ) //Look right + { + R5D2_OffsetLook( 45.0f, lookPos ); + } + else //Look left + { + R5D2_OffsetLook( -45.0f, lookPos ); + } + + NPC_FacePosition( lookPos ); +} + +*/ + +/* +------------------------- +NPC_BSDroid_Default +------------------------- +*/ +void NPC_BSDroid_Default( void ) +{ + + if ( NPCInfo->localState == LSTATE_SPINNING ) + { + Droid_Spin(); + } + else if ( NPCInfo->localState == LSTATE_PAIN ) + { + Droid_Pain(); + } + else if ( NPCInfo->localState == LSTATE_DROP ) + { + NPC_UpdateAngles( qtrue, qtrue ); + ucmd.upmove = crandom() * 64; + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Droid_Patrol(); + } + else + { + Droid_Run(); + } +} diff --git a/code/game/NPC_AI_GalakMech.c b/code/game/NPC_AI_GalakMech.c new file mode 100644 index 0000000..8f9cc28 --- /dev/null +++ b/code/game/NPC_AI_GalakMech.c @@ -0,0 +1,1297 @@ +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +#include "w_saber.h" + +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void NPC_AimAdjust( int change ); +extern qboolean WP_LobFire( gentity_t *self, vec3_t start, vec3_t target, vec3_t mins, vec3_t maxs, int clipmask, + vec3_t velocity, qboolean tracePath, int ignoreEntNum, int enemyNum, + float minSpeed, float maxSpeed, float idealSpeed, qboolean mustHit ); +extern void G_SoundOnEnt (gentity_t *ent, soundChannel_t channel, const char *soundPath); + +#include "../namespace_begin.h" +extern qboolean BG_CrouchAnim( int anim ); +#include "../namespace_end.h" + +//extern void NPC_Mark1_Part_Explode(gentity_t *self,int bolt); + +#define MELEE_DIST_SQUARED 6400//80*80 +#define MIN_LOB_DIST_SQUARED 65536//256*256 +#define MAX_LOB_DIST_SQUARED 200704//448*448 +#define REPEATER_ALT_SIZE 3 // half of bbox size +#define GENERATOR_HEALTH 25 +#define TURN_ON 0x00000000 +#define TURN_OFF 0x00000100 +#define GALAK_SHIELD_HEALTH 500 + +static vec3_t shieldMins = {-60, -60, -24 }; +static vec3_t shieldMaxs = {60, 60, 80}; + +extern qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS4; +static qboolean enemyCS4; +static qboolean hitAlly4; +static qboolean faceEnemy4; +static qboolean move4; +static qboolean shoot4; +static float enemyDist4; +static vec3_t impactPos4; + +void NPC_GalakMech_Precache( void ) +{ + G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ); + G_SoundIndex( "sound/weapons/galak/lasercharge.wav" ); + G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); + G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ); + + G_EffectIndex( "galak/trace_beam" ); + G_EffectIndex( "galak/beam_warmup" ); +// G_EffectIndex( "small_chunks"); + G_EffectIndex( "env/med_explode2"); + G_EffectIndex( "env/small_explode2"); + G_EffectIndex( "galak/explode"); + G_EffectIndex( "blaster/smoke_bolton"); +// G_EffectIndex( "env/exp_trail_comp"); +} + +void NPC_GalakMech_Init( gentity_t *ent ) +{ + if (ent->NPC->behaviorState != BS_CINEMATIC) + { + ent->client->ps.stats[STAT_ARMOR] = GALAK_SHIELD_HEALTH; + ent->NPC->investigateCount = ent->NPC->investigateDebounceTime = 0; + ent->flags |= FL_SHIELDED;//reflect normal shots + //rwwFIXMEFIXME: Support PW_GALAK_SHIELD + //ent->client->ps.powerups[PW_GALAK_SHIELD] = Q3_INFINITE;//temp, for effect + //ent->fx_time = level.time; + VectorSet( ent->r.mins, -60, -60, -24 ); + VectorSet( ent->r.maxs, 60, 60, 80 ); + ent->flags |= FL_NO_KNOCKBACK;//don't get pushed + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "flee", 0 ); + TIMER_Set( ent, "smackTime", 0 ); + TIMER_Set( ent, "beamDelay", 0 ); + TIMER_Set( ent, "noLob", 0 ); + TIMER_Set( ent, "noRapid", 0 ); + TIMER_Set( ent, "talkDebounce", 0 ); + + NPC_SetSurfaceOnOff( ent, "torso_shield", TURN_ON ); + NPC_SetSurfaceOnOff( ent, "torso_galakface", TURN_OFF ); + NPC_SetSurfaceOnOff( ent, "torso_galakhead", TURN_OFF ); + NPC_SetSurfaceOnOff( ent, "torso_eyes_mouth", TURN_OFF ); + NPC_SetSurfaceOnOff( ent, "torso_collar", TURN_OFF ); + NPC_SetSurfaceOnOff( ent, "torso_galaktorso", TURN_OFF ); + } + else + { +// NPC_SetSurfaceOnOff( ent, "helmet", TURN_OFF ); + NPC_SetSurfaceOnOff( ent, "torso_shield", TURN_OFF ); + NPC_SetSurfaceOnOff( ent, "torso_galakface", TURN_ON ); + NPC_SetSurfaceOnOff( ent, "torso_galakhead", TURN_ON ); + NPC_SetSurfaceOnOff( ent, "torso_eyes_mouth", TURN_ON ); + NPC_SetSurfaceOnOff( ent, "torso_collar", TURN_ON ); + NPC_SetSurfaceOnOff( ent, "torso_galaktorso", TURN_ON ); + } + +} + +//----------------------------------------------------------------- +static void GM_CreateExplosion( gentity_t *self, const int boltID, qboolean doSmall ) //doSmall = qfalse +{ + if ( boltID >=0 ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir; + + trap_G2API_GetBoltMatrix( self->ghoul2, 0, + boltID, + &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time, + NULL, self->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + + if ( doSmall ) + { + G_PlayEffectID( G_EffectIndex("env/small_explode2"), org, dir ); + } + else + { + G_PlayEffectID( G_EffectIndex("env/med_explode2"), org, dir ); + } + } +} + +/* +------------------------- +GM_Dying +------------------------- +*/ + +void GM_Dying( gentity_t *self ) +{ + if ( level.time - self->s.time < 4000 ) + {//FIXME: need a real effect + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + 1000; + self->client->ps.electrifyTime = level.time + 1000; + if ( TIMER_Done( self, "dyingExplosion" ) ) + { + int newBolt; + switch ( Q_irand( 1, 14 ) ) + { + // Find place to generate explosion + case 1: + if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_hand" )) + {//r_hand still there + GM_CreateExplosion( self, trap_G2API_AddBolt(self->ghoul2, 0, "*flasha"), qtrue ); + NPC_SetSurfaceOnOff( self, "r_hand", TURN_OFF ); + } + else if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_arm_middle" )) + {//r_arm_middle still there + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*r_arm_elbow" ); + NPC_SetSurfaceOnOff( self, "r_arm_middle", TURN_OFF ); + } + break; + case 2: + //FIXME: do only once? + if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_hand" )) + {//l_hand still there + GM_CreateExplosion( self, trap_G2API_AddBolt(self->ghoul2, 0, "*flashc"), qfalse ); + NPC_SetSurfaceOnOff( self, "l_hand", TURN_OFF ); + } + else if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm_wrist" )) + {//l_arm_wrist still there + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_arm_cap_l_hand" ); + NPC_SetSurfaceOnOff( self, "l_arm_wrist", TURN_OFF ); + } + else if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm_middle" )) + {//l_arm_middle still there + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_arm_cap_l_hand" ); + NPC_SetSurfaceOnOff( self, "l_arm_middle", TURN_OFF ); + } + else if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm_augment" )) + {//l_arm_augment still there + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_arm_elbow" ); + NPC_SetSurfaceOnOff( self, "l_arm_augment", TURN_OFF ); + } + break; + case 3: + case 4: + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*hip_fr" ); + GM_CreateExplosion( self, newBolt, qfalse ); + break; + case 5: + case 6: + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*shldr_l" ); + GM_CreateExplosion( self, newBolt, qfalse ); + break; + case 7: + case 8: + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*uchest_r" ); + GM_CreateExplosion( self, newBolt, qfalse ); + break; + case 9: + case 10: + GM_CreateExplosion( self, self->client->renderInfo.headBolt, qfalse ); + break; + case 11: + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_leg_knee" ); + GM_CreateExplosion( self, newBolt, qtrue ); + break; + case 12: + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*r_leg_knee" ); + GM_CreateExplosion( self, newBolt, qtrue ); + break; + case 13: + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_leg_foot" ); + GM_CreateExplosion( self, newBolt, qtrue ); + break; + case 14: + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*r_leg_foot" ); + GM_CreateExplosion( self, newBolt, qtrue ); + break; + } + + TIMER_Set( self, "dyingExplosion", Q_irand( 300, 1100 ) ); + } + } + else + {//one final, huge explosion + G_PlayEffectID( G_EffectIndex("galak/explode"), self->r.currentOrigin, vec3_origin ); +// G_PlayEffect( "small_chunks", self->r.currentOrigin ); +// G_PlayEffect( "env/exp_trail_comp", self->r.currentOrigin, self->currentAngles ); + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + } +} + +/* +------------------------- +NPC_GM_Pain +------------------------- +*/ + +extern void NPC_SetPainEvent( gentity_t *self ); +void NPC_GM_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + vec3_t point; + gentity_t *inflictor = attacker; + int hitLoc = 1; + int mod = gPainMOD; + + VectorCopy(gPainPoint, point); + + //if ( self->client->ps.powerups[PW_GALAK_SHIELD] == 0 ) + if (0) //rwwFIXMEFIXME: do all of this + {//shield is currently down + //FIXME: allow for radius damage? + /* + if ( (hitLoc==HL_GENERIC1) && (self->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH) ) + { + int newBolt = gi.G2API_AddBolt( &self->ghoul2[self->playerModel], "*antenna_base" ); + if ( newBolt != -1 ) + { + GM_CreateExplosion( self, newBolt, qfalse ); + } + + NPC_SetSurfaceOnOff( self, "torso_shield", TURN_OFF ); + NPC_SetSurfaceOnOff( self, "torso_antenna", TURN_OFF ); + NPC_SetSurfaceOnOff( self, "torso_antenna_base_cap", TURN_ON ); + self->client->ps.powerups[PW_GALAK_SHIELD] = 0;//temp, for effect + self->client->ps.stats[STAT_ARMOR] = 0;//no more armor + self->NPC->investigateDebounceTime = 0;//stop recharging + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_ALERT1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( self, "attackDelay", self->client->ps.torsoTimer ); + G_AddEvent( self, Q_irand( EV_DEATH1, EV_DEATH3 ), self->health ); + } + */ + } + else + {//store the point for shield impact + if ( point ) + { + // VectorCopy( point, self->pos4 ); + // self->client->poisonTime = level.time; + //rwwFIXMEFIXME: ..do this is as well. + } + } + + if ( !self->lockCount && self->client->ps.torsoTimer <= 0 ) + {//don't interrupt laser sweep attack or other special attacks/moves + if ( self->count < 4 && self->health > 100 && hitLoc != HL_GENERIC1 ) + { + if ( self->delay < level.time ) + { + int speech; + switch( self->count ) + { + default: + case 0: + speech = EV_PUSHED1; + break; + case 1: + speech = EV_PUSHED2; + break; + case 2: + speech = EV_PUSHED3; + break; + case 3: + speech = EV_DETECTED1; + break; + } + self->count++; + self->NPC->blockedSpeechDebounceTime = 0; + G_AddVoiceEvent( self, speech, Q_irand( 3000, 5000 ) ); + self->delay = level.time + Q_irand( 5000, 7000 ); + } + } + else + { + NPC_Pain(self, attacker, damage); + } + } + else if ( hitLoc == HL_GENERIC1 ) + { + NPC_SetPainEvent( self ); + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); + self->client->ps.electrifyTime = level.time + Q_irand(500, 2500); + } + + if ( inflictor && inflictor->lastEnemy == self ) + {//He force-pushed my own lobfires back at me + if ( mod == MOD_REPEATER_ALT && !Q_irand( 0, 2 ) ) + { + if ( TIMER_Done( self, "noRapid" ) ) + { + self->NPC->scriptFlags &= ~SCF_ALT_FIRE; + self->alt_fire = qfalse; + TIMER_Set( self, "noLob", Q_irand( 2000, 6000 ) ); + } + else + {//hopefully this will make us fire the laser + TIMER_Set( self, "noLob", Q_irand( 1000, 2000 ) ); + } + } + else if ( mod == MOD_REPEATER && !Q_irand( 0, 5 ) ) + { + if ( TIMER_Done( self, "noLob" ) ) + { + self->NPC->scriptFlags |= SCF_ALT_FIRE; + self->alt_fire = qtrue; + TIMER_Set( self, "noRapid", Q_irand( 2000, 6000 ) ); + } + else + {//hopefully this will make us fire the laser + TIMER_Set( self, "noRapid", Q_irand( 1000, 2000 ) ); + } + } + } +} + +/* +------------------------- +GM_HoldPosition +------------------------- +*/ + +static void GM_HoldPosition( void ) +{ + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//don't have a script waiting for me to get to my point, okay to stop trying and stand + NPCInfo->goalEntity = NULL; + } +} + +/* +------------------------- +GM_Move +------------------------- +*/ +static qboolean GM_Move( void ) +{ + qboolean moved; + navInfo_t info; + + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + moved = NPC_MoveToGoal( qtrue ); + + //Get the move info + NAV_GetLastMove( &info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! + if ( info.flags & NIF_COLLISION ) + { + if ( info.blocker == NPC->enemy ) + { + GM_HoldPosition(); + } + } + + //If our move failed, then reset + if ( moved == qfalse ) + {//FIXME: if we're going to a combat point, need to pick a different one + if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//can't transfer movegoal or stop when a script we're running is waiting to complete + GM_HoldPosition(); + } + } + + return moved; +} + +/* +------------------------- +NPC_BSGM_Patrol +------------------------- +*/ + +void NPC_BSGM_Patrol( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +GM_CheckMoveState +------------------------- +*/ + +static void GM_CheckMoveState( void ) +{ + if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//moving toward a goal that a script is waiting on, so don't stop for anything! + move4 = qtrue; + } + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, qfalse ) || + ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) && enemyLOS4 && enemyDist4 <= 10000 ) ) + {//either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels + return; + } + } +} + +/* +------------------------- +GM_CheckFireState +------------------------- +*/ + +static void GM_CheckFireState( void ) +{ + if ( enemyCS4 ) + {//if have a clear shot, always try + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //See if we should continue to fire on their last position + if ( !hitAlly4 && NPCInfo->enemyLastSeenTime > 0 ) + { + if ( level.time - NPCInfo->enemyLastSeenTime < 10000 ) + { + if ( !Q_irand( 0, 10 ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + qboolean tooClose = qfalse; + qboolean tooFar = qfalse; + float distThreshold; + float dist; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + if ( VectorCompare( impactPos4, vec3_origin ) ) + {//never checked ShotEntity this frame, so must do a trace... + trace_t tr; + //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; + vec3_t forward, end; + AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 8192, forward, end ); + trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, impactPos4 ); + } + + //see if impact would be too close to me + distThreshold = 16384/*128*128*/;//default + if ( NPC->s.weapon == WP_REPEATER ) + { + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 65536/*256*256*/; + } + } + + dist = DistanceSquared( impactPos4, muzzle ); + + if ( dist < distThreshold ) + {//impact would be too close to me + tooClose = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 ) + {//we've haven't seen them in the last 5 seconds + //see if it's too far from where he is + distThreshold = 65536/*256*256*/;//default + if ( NPC->s.weapon == WP_REPEATER ) + { + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 262144/*512*512*/; + } + } + dist = DistanceSquared( impactPos4, NPCInfo->enemyLastSeenLocation ); + if ( dist > distThreshold ) + {//impact would be too far from enemy + tooFar = qtrue; + } + } + + if ( !tooClose && !tooFar ) + {//okay too shoot at last pos + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + VectorNormalize( dir ); + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot4 = qtrue; + faceEnemy4 = qfalse; + return; + } + } + } + } +} + +void NPC_GM_StartLaser( void ) +{ + if ( !NPC->lockCount ) + {//haven't already started a laser attack + //warm up for the beam attack +#if 0 + NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_RAISEWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +#endif + TIMER_Set( NPC, "beamDelay", NPC->client->ps.torsoTimer ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer+3000 ); + NPC->lockCount = 1; + //turn on warmup effect + G_PlayEffectID( G_EffectIndex("galak/beam_warmup"), NPC->r.currentOrigin, vec3_origin ); + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/weapons/galak/lasercharge.wav" ); + } +} + +void GM_StartGloat( void ) +{ + NPC->wait = 0; + NPC_SetSurfaceOnOff( NPC, "torso_galakface", TURN_ON ); + NPC_SetSurfaceOnOff( NPC, "torso_galakhead", TURN_ON ); + NPC_SetSurfaceOnOff( NPC, "torso_eyes_mouth", TURN_ON ); + NPC_SetSurfaceOnOff( NPC, "torso_collar", TURN_ON ); + NPC_SetSurfaceOnOff( NPC, "torso_galaktorso", TURN_ON ); + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND2TO1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsTimer += 500; + NPC->client->ps.torsoTimer += 500; +} +/* +------------------------- +NPC_BSGM_Attack +------------------------- +*/ + +void NPC_BSGM_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + +#if 0 + //FIXME: if killed enemy, use victory anim + if ( NPC->enemy && NPC->enemy->health <= 0 + && !NPC->enemy->s.number ) + {//my enemy is dead + if ( NPC->client->ps.torsoAnim == BOTH_STAND2TO1 ) + { + if ( NPC->client->ps.torsoTimer <= 500 ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1START, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsTimer += 500; + NPC->client->ps.torsoTimer += 500; + } + } + else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1START ) + { + if ( NPC->client->ps.torsoTimer <= 500 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STARTGESTURE, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsTimer += 500; + NPC->client->ps.torsoTimer += 500; + } + } + else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STARTGESTURE ) + { + if ( NPC->client->ps.torsoTimer <= 500 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_TRIUMPHANT1STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsTimer += 500; + NPC->client->ps.torsoTimer += 500; + } + } + else if ( NPC->client->ps.torsoAnim == BOTH_TRIUMPHANT1STOP ) + { + if ( NPC->client->ps.torsoTimer <= 500 ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.legsTimer = -1; + NPC->client->ps.torsoTimer = -1; + } + } + else if ( NPC->wait ) + { + if ( TIMER_Done( NPC, "gloatTime" ) ) + { + GM_StartGloat(); + } + else if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared + { + NPCInfo->goalEntity = NPC->enemy; + GM_Move(); + } + else + {//got there + GM_StartGloat(); + } + } + NPC_FaceEnemy( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } +#endif + + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse || !NPC->enemy ) + { + NPC->enemy = NULL; + NPC_BSGM_Patrol(); + return; + } + + enemyLOS4 = enemyCS4 = qfalse; + move4 = qtrue; + faceEnemy4 = qfalse; + shoot4 = qfalse; + hitAlly4 = qfalse; + VectorClear( impactPos4 ); + enemyDist4 = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + + //if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 || + // NPC->client->ps.torsoAnim == BOTH_ATTACK5 ) + if (0) + { + shoot4 = qfalse; + if ( TIMER_Done( NPC, "smackTime" ) && !NPCInfo->blockedDebounceTime ) + {//time to smack + //recheck enemyDist4 and InFront + if ( enemyDist4 < MELEE_DIST_SQUARED && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) ) + { + vec3_t smackDir; + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, smackDir ); + smackDir[2] += 30; + VectorNormalize( smackDir ); + //hurt them + G_Sound( NPC->enemy, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/skewerhit.wav" ) ); + G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->r.currentOrigin, (g_spskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); + if ( NPC->client->ps.torsoAnim == BOTH_ATTACK4 ) + {//smackdown + int knockAnim = BOTH_KNOCKDOWN1; + if ( BG_CrouchAnim( NPC->enemy->client->ps.legsAnim ) ) + {//knockdown from crouch + knockAnim = BOTH_KNOCKDOWN4; + } + //throw them + smackDir[2] = 1; + VectorNormalize( smackDir ); + G_Throw( NPC->enemy, smackDir, 50 ); + NPC_SetAnim( NPC->enemy, SETANIM_BOTH, knockAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + {//uppercut + //throw them + G_Throw( NPC->enemy, smackDir, 100 ); + //make them backflip + NPC_SetAnim( NPC->enemy, SETANIM_BOTH, BOTH_KNOCKDOWN5, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + //done with the damage + NPCInfo->blockedDebounceTime = 1; + } + } + } + else if ( NPC->lockCount ) //already shooting laser + {//sometimes use the laser beam attack, but only after he's taken down our generator + shoot4 = qfalse; + if ( NPC->lockCount == 1 ) + {//charging up + if ( TIMER_Done( NPC, "beamDelay" ) ) + {//time to start the beam + int laserAnim; + //if ( Q_irand( 0, 1 ) ) + if (1) + { + laserAnim = BOTH_ATTACK2; + } + /* + else + { + laserAnim = BOTH_ATTACK7; + } + */ + NPC_SetAnim( NPC, SETANIM_BOTH, laserAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) ); + //turn on beam effect + NPC->lockCount = 2; + G_PlayEffectID( G_EffectIndex("galak/trace_beam"), NPC->r.currentOrigin, vec3_origin ); + NPC->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); + if ( !NPCInfo->coverTarg ) + {//for moving looping sound at end of trace + NPCInfo->coverTarg = G_Spawn(); + if ( NPCInfo->coverTarg ) + { + G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); + NPCInfo->coverTarg->r.svFlags |= SVF_BROADCAST; + NPCInfo->coverTarg->s.loopSound = G_SoundIndex( "sound/weapons/galak/lasercutting.wav" ); + } + } + } + } + else + {//in the actual attack now + if ( NPC->client->ps.torsoTimer <= 0 ) + {//attack done! + NPC->lockCount = 0; + G_FreeEntity( NPCInfo->coverTarg ); + NPC->s.loopSound = 0; +#if 0 + NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_DROPWEAP2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +#endif + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer ); + } + else + {//attack still going + //do the trace and damage + trace_t trace; + vec3_t end, mins={-3,-3,-3}, maxs={3,3,3}; + VectorMA( NPC->client->renderInfo.muzzlePoint, 1024, NPC->client->renderInfo.muzzleDir, end ); + trap_Trace( &trace, NPC->client->renderInfo.muzzlePoint, mins, maxs, end, NPC->s.number, MASK_SHOT ); + if ( trace.allsolid || trace.startsolid ) + {//oops, in a wall + if ( NPCInfo->coverTarg ) + { + G_SetOrigin( NPCInfo->coverTarg, NPC->client->renderInfo.muzzlePoint ); + } + } + else + {//clear + if ( trace.fraction < 1.0f ) + {//hit something + gentity_t *traceEnt = &g_entities[trace.entityNum]; + if ( traceEnt && traceEnt->takedamage ) + {//damage it + G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); + G_Damage( traceEnt, NPC, NPC, NPC->client->renderInfo.muzzleDir, trace.endpos, 10, 0, MOD_UNKNOWN ); + } + } + if ( NPCInfo->coverTarg ) + { + G_SetOrigin( NPCInfo->coverTarg, trace.endpos ); + } + if ( !Q_irand( 0, 5 ) ) + { + G_SoundAtLoc( trace.endpos, CHAN_AUTO, G_SoundIndex( "sound/weapons/galak/laserdamage.wav" ) ); + } + } + } + } + } + else + {//Okay, we're not in a special attack, see if we should switch weapons or start a special attack + /* + if ( NPC->s.weapon == WP_REPEATER + && !(NPCInfo->scriptFlags & SCF_ALT_FIRE)//using rapid-fire + && NPC->enemy->s.weapon == WP_SABER //enemy using saber + && NPC->client && (NPC->client->ps.saberEventFlags&SEF_DEFLECTED) + && !Q_irand( 0, 50 ) ) + {//he's deflecting my shots, switch to the laser or the lob fire for a while + TIMER_Set( NPC, "noRapid", Q_irand( 2000, 6000 ) ); + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + NPC->alt_fire = qtrue; + if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH && (Q_irand( 0, 1 )||enemyDist4 < MAX_LOB_DIST_SQUARED) ) + {//shield down, use laser + NPC_GM_StartLaser(); + } + } + else*/ + if (// !NPC->client->ps.powerups[PW_GALAK_SHIELD] + 1 //rwwFIXMEFIXME: just act like the shield is down til the effects and stuff are done + && enemyDist4 < MELEE_DIST_SQUARED + && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) + && NPC->enemy->localAnimIndex <= 1 )//within 80 and in front + {//our shield is down, and enemy within 80, if very close, use melee attack to slap away + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + //animate me + int swingAnim = BOTH_ATTACK1; +#if 0 + if ( NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH ) + {//generator down, use random melee + swingAnim = Q_irand( BOTH_ATTACK4, BOTH_ATTACK5 );//smackdown or uppercut + } + else + {//always knock-away + swingAnim = BOTH_ATTACK5;//uppercut + } +#endif + //FIXME: swing sound + NPC_SetAnim( NPC, SETANIM_BOTH, swingAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer + Q_irand( 1000, 3000 ) ); + //delay the hurt until the proper point in the anim + TIMER_Set( NPC, "smackTime", 600 ); + NPCInfo->blockedDebounceTime = 0; + //FIXME: say something? + } + } + else if ( !NPC->lockCount && NPC->locationDamage[HL_GENERIC1] > GENERATOR_HEALTH + && TIMER_Done( NPC, "attackDelay" ) + && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.3f ) + && ((!Q_irand( 0, 10*(2-g_spskill.integer))&& enemyDist4 > MIN_LOB_DIST_SQUARED&& enemyDist4 < MAX_LOB_DIST_SQUARED) + ||(!TIMER_Done( NPC, "noLob" )&&!TIMER_Done( NPC, "noRapid" ))) + && NPC->enemy->s.weapon != WP_TURRET ) + {//sometimes use the laser beam attack, but only after he's taken down our generator + shoot4 = qfalse; + NPC_GM_StartLaser(); + } + else if ( enemyDist4 < MIN_LOB_DIST_SQUARED + && (NPC->enemy->s.weapon != WP_TURRET || Q_stricmp( "PAS", NPC->enemy->classname )) + && TIMER_Done( NPC, "noRapid" ) )//256 + {//enemy within 256 + if ( (NPC->client->ps.weapon == WP_REPEATER) && (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//shooting an explosive, but enemy too close, switch to primary fire + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + NPC->alt_fire = qfalse; + //FIXME: use weap raise & lower anims + NPC_ChangeWeapon( WP_REPEATER ); + } + } + else if ( (enemyDist4 > MAX_LOB_DIST_SQUARED || (NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ))) + && TIMER_Done( NPC, "noLob" ) )//448 + {//enemy more than 448 away and we are ready to try lob fire again + if ( (NPC->client->ps.weapon == WP_REPEATER) && !(NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//enemy far enough away to use lobby explosives + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + NPC->alt_fire = qtrue; + //FIXME: use weap raise & lower anims + NPC_ChangeWeapon( WP_REPEATER ); + } + } + } + + //can we see our target? + if ( NPC_ClearLOS4( NPC->enemy ) ) + { + NPCInfo->enemyLastSeenTime = level.time;//used here for aim debouncing, not always a clear LOS + enemyLOS4 = qtrue; + + if ( NPC->client->ps.weapon == WP_NONE ) + { + enemyCS4 = qfalse;//not true, but should stop us from firing + NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon + } + else + {//can we shoot our target? + if ( ((NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist4 < MIN_LOB_DIST_SQUARED )//256 + { + enemyCS4 = qfalse;//not true, but should stop us from firing + hitAlly4 = qtrue;//us! + //FIXME: if too close, run away! + } + else + { + int hit = NPC_ShotEntity( NPC->enemy, impactPos4 ); + gentity_t *hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage ) ) + {//can hit enemy or will hit glass or other breakable, so shoot anyway + enemyCS4 = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + else + {//Hmm, have to get around this bastard + NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) + {//would hit an ally, don't fire!!! + hitAlly4 = qtrue; + } + else + {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire + } + } + } + } + } + else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + { + int hit; + gentity_t *hitEnt; + + if ( TIMER_Done( NPC, "talkDebounce" ) && !Q_irand( 0, 10 ) ) + { + if ( NPCInfo->enemyCheckDebounceTime < 8 ) + { + int speech = -1; + switch( NPCInfo->enemyCheckDebounceTime ) + { + case 0: + case 1: + case 2: + speech = EV_CHASE1 + NPCInfo->enemyCheckDebounceTime; + break; + case 3: + case 4: + case 5: + speech = EV_COVER1 + NPCInfo->enemyCheckDebounceTime-3; + break; + case 6: + case 7: + speech = EV_ESCAPING1 + NPCInfo->enemyCheckDebounceTime-6; + break; + } + NPCInfo->enemyCheckDebounceTime++; + if ( speech != -1 ) + { + G_AddVoiceEvent( NPC, speech, Q_irand( 3000, 5000 ) ); + TIMER_Set( NPC, "talkDebounce", Q_irand( 5000, 7000 ) ); + } + } + } + + NPCInfo->enemyLastSeenTime = level.time; + + hit = NPC_ShotEntity( NPC->enemy, impactPos4 ); + hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage ) ) + {//can hit enemy or will hit glass or other breakable, so shoot anyway + enemyCS4 = qtrue; + } + else + { + faceEnemy4 = qtrue; + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + } + + if ( enemyLOS4 ) + { + faceEnemy4 = qtrue; + } + else + { + if ( !NPCInfo->goalEntity ) + { + NPCInfo->goalEntity = NPC->enemy; + } + if ( NPCInfo->goalEntity == NPC->enemy ) + {//for now, always chase the enemy + move4 = qtrue; + } + } + if ( enemyCS4 ) + { + shoot4 = qtrue; + //NPCInfo->enemyCheckDebounceTime = level.time;//actually used here as a last actual LOS + } + else + { + if ( !NPCInfo->goalEntity ) + { + NPCInfo->goalEntity = NPC->enemy; + } + if ( NPCInfo->goalEntity == NPC->enemy ) + {//for now, always chase the enemy + move4 = qtrue; + } + } + + //Check for movement to take care of + GM_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + GM_CheckFireState(); + + if ( NPC->client->ps.weapon == WP_REPEATER && (NPCInfo->scriptFlags&SCF_ALT_FIRE) && shoot4 && TIMER_Done( NPC, "attackDelay" ) ) + { + vec3_t muzzle; + vec3_t angles; + vec3_t target; + vec3_t velocity = {0,0,0}; + vec3_t mins = {-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE,-REPEATER_ALT_SIZE}, maxs = {REPEATER_ALT_SIZE,REPEATER_ALT_SIZE,REPEATER_ALT_SIZE}; + qboolean clearshot; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorCopy( NPC->enemy->r.currentOrigin, target ); + + target[0] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); + target[1] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); + target[2] += flrand( -5, 5 )+(crandom()*(6-NPCInfo->currentAim)*2); + + //Find the desired angles + clearshot = WP_LobFire( NPC, muzzle, target, mins, maxs, MASK_SHOT|CONTENTS_LIGHTSABER, + velocity, qtrue, NPC->s.number, NPC->enemy->s.number, + 300, 1100, 1500, qtrue ); + if ( VectorCompare( vec3_origin, velocity ) || (!clearshot&&enemyLOS4&&enemyCS4) ) + {//no clear lob shot and no lob shot that will hit something breakable + if ( enemyLOS4 && enemyCS4 && TIMER_Done( NPC, "noRapid" ) ) + {//have a clear straight shot, so switch to primary + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + NPC->alt_fire = qfalse; + NPC_ChangeWeapon( WP_REPEATER ); + //keep this weap for a bit + TIMER_Set( NPC, "noLob", Q_irand( 500, 1000 ) ); + } + else + { + shoot4 = qfalse; + } + } + else + { + vectoangles( velocity, angles ); + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + + VectorCopy( velocity, NPC->client->hiddenDir ); + NPC->client->hiddenDist = VectorNormalize ( NPC->client->hiddenDir ); + } + } + else if ( faceEnemy4 ) + {//face the enemy + NPC_FaceEnemy( qtrue ); + } + + if ( !TIMER_Done( NPC, "standTime" ) ) + { + move4 = qfalse; + } + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not supposed to chase my enemies + if ( NPCInfo->goalEntity == NPC->enemy ) + {//goal is my entity, so don't move + move4 = qfalse; + } + } + + if ( move4 && !NPC->lockCount ) + {//move toward goal + if ( NPCInfo->goalEntity + /*&& NPC->client->ps.legsAnim != BOTH_ALERT1 + && NPC->client->ps.legsAnim != BOTH_ATTACK2 + && NPC->client->ps.legsAnim != BOTH_ATTACK4 + && NPC->client->ps.legsAnim != BOTH_ATTACK5 + && NPC->client->ps.legsAnim != BOTH_ATTACK7*/ ) + { + move4 = GM_Move(); + } + else + { + move4 = qfalse; + } + } + + if ( !TIMER_Done( NPC, "flee" ) ) + {//running away + faceEnemy4 = qfalse; + } + + //FIXME: check scf_face_move_dir here? + + if ( !faceEnemy4 ) + {//we want to face in the dir we're running + if ( !move4 ) + {//if we haven't moved, we should look in the direction we last looked? + VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); + } + if ( move4 ) + {//don't run away and shoot + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + shoot4 = qfalse; + } + } + NPC_UpdateAngles( qtrue, qtrue ); + + if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + { + shoot4 = qfalse; + } + + if ( NPC->enemy && NPC->enemy->enemy ) + { + if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) + {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) + shoot4 = qfalse; + } + } + //FIXME: don't shoot right away! + if ( shoot4 ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + } + } + + //also: + if ( NPC->enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) + {//crush turrets + if ( G_BoundsOverlap( NPC->r.absmin, NPC->r.absmax, NPC->enemy->r.absmin, NPC->enemy->r.absmax ) ) + {//have to do this test because placed turrets are not solid to NPCs (so they don't obstruct navigation) + //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) + if (0) + { + NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; + G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); + } + else + { + G_Damage( NPC->enemy, NPC, NPC, NULL, NPC->r.currentOrigin, 100, DAMAGE_NO_KNOCKBACK, MOD_CRUSH ); + } + } + } + else if ( NPCInfo->touchedByPlayer != NULL && NPCInfo->touchedByPlayer == NPC->enemy ) + {//touched enemy + //if ( NPC->client->ps.powerups[PW_GALAK_SHIELD] > 0 ) + if (0) + {//zap him! + vec3_t smackDir; + + //animate me +#if 0 + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK6, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +#endif + TIMER_Set( NPC, "attackDelay", NPC->client->ps.torsoTimer ); + TIMER_Set( NPC, "standTime", NPC->client->ps.legsTimer ); + //FIXME: debounce this? + NPCInfo->touchedByPlayer = NULL; + //FIXME: some shield effect? + NPC->client->ps.powerups[PW_BATTLESUIT] = level.time + ARMOR_EFFECT_TIME; + + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, smackDir ); + smackDir[2] += 30; + VectorNormalize( smackDir ); + G_Damage( NPC->enemy, NPC, NPC, smackDir, NPC->r.currentOrigin, (g_spskill.integer+1)*Q_irand( 5, 10), DAMAGE_NO_KNOCKBACK, MOD_UNKNOWN ); + //throw them + G_Throw( NPC->enemy, smackDir, 100 ); + //NPC->enemy->s.powerups |= ( 1 << PW_SHOCKED ); + if ( NPC->enemy->client ) + { + // NPC->enemy->client->ps.powerups[PW_SHOCKED] = level.time + 1000; + NPC->enemy->client->ps.electrifyTime = level.time + 1000; + } + //stop any attacks + ucmd.buttons = 0; + } + } + + if ( NPCInfo->movementSpeech < 3 && NPCInfo->blockedSpeechDebounceTime <= level.time ) + { + if ( NPC->enemy && NPC->enemy->health > 0 && NPC->enemy->painDebounceTime > level.time ) + { + if ( NPC->enemy->health < 50 && NPCInfo->movementSpeech == 2 ) + { + G_AddVoiceEvent( NPC, EV_ANGER2, Q_irand( 2000, 4000 ) ); + NPCInfo->movementSpeech = 3; + } + else if ( NPC->enemy->health < 75 && NPCInfo->movementSpeech == 1 ) + { + G_AddVoiceEvent( NPC, EV_ANGER1, Q_irand( 2000, 4000 ) ); + NPCInfo->movementSpeech = 2; + } + else if ( NPC->enemy->health < 100 && NPCInfo->movementSpeech == 0 ) + { + G_AddVoiceEvent( NPC, EV_ANGER3, Q_irand( 2000, 4000 ) ); + NPCInfo->movementSpeech = 1; + } + } + } +} + +void NPC_BSGM_Default( void ) +{ + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( NPC->client->ps.stats[STAT_ARMOR] <= 0 ) + {//armor gone + // if ( !NPCInfo->investigateDebounceTime ) + if (0) + {//start regenerating the armor + NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_OFF ); + NPC->flags &= ~FL_SHIELDED;//no more reflections + VectorSet( NPC->r.mins, -20, -20, -24 ); + VectorSet( NPC->r.maxs, 20, 20, 64 ); + NPC->client->ps.crouchheight = NPC->client->ps.standheight = 64; + if ( NPC->locationDamage[HL_GENERIC1] < GENERATOR_HEALTH ) + {//still have the generator bolt-on + if ( NPCInfo->investigateCount < 12 ) + { + NPCInfo->investigateCount++; + } + NPCInfo->investigateDebounceTime = level.time + (NPCInfo->investigateCount * 5000); + } + } + else if ( NPCInfo->investigateDebounceTime < level.time ) + {//armor regenerated, turn shield back on + //do a trace and make sure we can turn this back on? + trace_t tr; + trap_Trace( &tr, NPC->r.currentOrigin, shieldMins, shieldMaxs, NPC->r.currentOrigin, NPC->s.number, NPC->clipmask ); + if ( !tr.startsolid ) + { + VectorCopy( shieldMins, NPC->r.mins ); + VectorCopy( shieldMaxs, NPC->r.maxs ); + NPC->client->ps.crouchheight = NPC->client->ps.standheight = shieldMaxs[2]; + NPC->client->ps.stats[STAT_ARMOR] = GALAK_SHIELD_HEALTH; + NPCInfo->investigateDebounceTime = 0; + NPC->flags |= FL_SHIELDED;//reflect normal shots + // NPC->fx_time = level.time; + NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_ON ); + } + } + } + /* + if ( NPC->client->ps.stats[STAT_ARMOR] > 0 ) + {//armor present + NPC->client->ps.powerups[PW_GALAK_SHIELD] = Q3_INFINITE;//temp, for effect + NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_ON ); + } + else + { + NPC_SetSurfaceOnOff( NPC, "torso_shield", TURN_OFF ); + } + */ + //rwwFIXMEFIXME: Allow this stuff, and again, going to have to let the client know about it. + //Maybe a surface-off bitflag of some sort in the entity state? + + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSGM_Patrol(); + } + else //if ( NPC->enemy ) + {//have an enemy + NPC_BSGM_Attack(); + } +} diff --git a/code/game/NPC_AI_Grenadier.c b/code/game/NPC_AI_Grenadier.c new file mode 100644 index 0000000..e8147c7 --- /dev/null +++ b/code/game/NPC_AI_Grenadier.c @@ -0,0 +1,679 @@ +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +//#include "g_navigator.h" + +#include "../namespace_begin.h" +extern qboolean BG_SabersOff( playerState_t *ps ); +#include "../namespace_end.h" + +extern void CG_DrawAlert( vec3_t origin, float rating ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern void NPC_AimAdjust( int change ); +extern qboolean FlyingCreature( gentity_t *ent ); + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 + +#define DISTANCE_SCALE 0.25f +#define DISTANCE_THRESHOLD 0.075f +#define SPEED_SCALE 0.25f +#define FOV_SCALE 0.5f +#define LIGHT_SCALE 0.25f + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS3; +static qboolean enemyCS3; +static qboolean faceEnemy3; +static qboolean move3; +static qboolean shoot3; +static float enemyDist3; + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void Grenadier_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); +} + +void NPC_Grenadier_PlayConfusionSound( gentity_t *self ) +{//FIXME: make this a custom sound in sound set + if ( self->health > 0 ) + { + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + TIMER_Set( self, "flee", 0 ); + self->NPC->squadState = SQUAD_IDLE; + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} + + +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_Grenadier_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, attacker, damage ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void Grenadier_HoldPosition( void ) +{ + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + NPCInfo->goalEntity = NULL; + + /*if ( TIMER_Done( NPC, "stand" ) ) + {//FIXME: what if can't shoot from this pos? + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +/* +------------------------- +ST_Move +------------------------- +*/ + +static qboolean Grenadier_Move( void ) +{ + qboolean moved; + navInfo_t info; + + NPCInfo->combatMove = qtrue;//always move straight toward our goal + moved = NPC_MoveToGoal( qtrue ); + + //Get the move info + NAV_GetLastMove( &info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! + if ( info.flags & NIF_COLLISION ) + { + if ( info.blocker == NPC->enemy ) + { + Grenadier_HoldPosition(); + } + } + + //If our move failed, then reset + if ( moved == qfalse ) + {//couldn't get to enemy + if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPC->client->ps.weapon == WP_THERMAL && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) + {//we were running after enemy + //Try to find a combat point that can hit the enemy + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); + int cp; + + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 ); + if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + {//okay, try one by the enemy + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 ); + } + //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... + if ( cp != -1 ) + {//found a combat point that has a clear shot to enemy + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + return moved; + } + } + //just hang here + Grenadier_HoldPosition(); + } + + return moved; +} + +/* +------------------------- +NPC_BSGrenadier_Patrol +------------------------- +*/ + +void NPC_BSGrenadier_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be automatic now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + //Is there danger nearby + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); + if ( NPC_CheckForDanger( alertEvent ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + {//check for other alert events + //There is an event to look at + if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED ) + { + if ( level.alertEvents[alertEvent].owner && + level.alertEvents[alertEvent].owner->client && + level.alertEvents[alertEvent].owner->health >= 0 && + level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + //NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + } + } + else + {//FIXME: get more suspicious over time? + //Save the position for movement (if necessary) + VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); + NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); + if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) + {//suspicious looks longer + NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); + } + } + } + } + + if ( NPCInfo->investigateDebounceTime > level.time ) + {//FIXME: walk over to it, maybe? Not if not chase enemies + //NOTE: stops walking or doing anything else below + vec3_t dir, angles; + float o_yaw, o_pitch; + + VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); + vectoangles( dir, angles ); + + o_yaw = NPCInfo->desiredYaw; + o_pitch = NPCInfo->desiredPitch; + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + NPC_UpdateAngles( qtrue, qtrue ); + + NPCInfo->desiredYaw = o_yaw; + NPCInfo->desiredPitch = o_pitch; + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSGrenadier_Idle +------------------------- +*/ +/* +void NPC_BSGrenadier_Idle( void ) +{ + //FIXME: check for other alert events? + + //Is there danger nearby? + if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) ); + + NPC_UpdateAngles( qtrue, qtrue ); +} +*/ +/* +------------------------- +ST_CheckMoveState +------------------------- +*/ + +static void Grenadier_CheckMoveState( void ) +{ + //See if we're a scout + if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//behaviorState == BS_STAND_AND_SHOOT ) + { + if ( NPCInfo->goalEntity == NPC->enemy ) + { + move3 = qfalse; + return; + } + } + //See if we're running away + else if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + if ( TIMER_Done( NPC, "flee" ) ) + { + NPCInfo->squadState = SQUAD_IDLE; + } + else + { + faceEnemy3 = qfalse; + } + } + /* + else if ( NPCInfo->squadState == SQUAD_IDLE ) + { + if ( !NPCInfo->goalEntity ) + { + move3 = qfalse; + return; + } + //Should keep moving toward player when we're out of range... right? + } + */ + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) || + ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS3 && enemyDist3 <= 10000 ) ) + { + int newSquadState = SQUAD_STAND_AND_SHOOT; + //we got where we wanted to go, set timers based on why we were running + switch ( NPCInfo->squadState ) + { + case SQUAD_RETREAT://was running away + TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 ); + TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); + newSquadState = SQUAD_COVER; + break; + case SQUAD_TRANSITION://was heading for a combat point + TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); + break; + case SQUAD_SCOUT://was running after player + break; + default: + break; + } + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels + //don't do something else just yet + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); + //stop fleeing + if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + TIMER_Set( NPC, "flee", -level.time ); + NPCInfo->squadState = SQUAD_IDLE; + } + return; + } + + //keep going, hold of roamTimer until we get there + TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); + } + + if ( !NPCInfo->goalEntity ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + NPCInfo->goalEntity = NPC->enemy; + } + } +} + +/* +------------------------- +ST_CheckFireState +------------------------- +*/ + +static void Grenadier_CheckFireState( void ) +{ + if ( enemyCS3 ) + {//if have a clear shot, always try + return; + } + + if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT ) + {//runners never try to fire at the last pos + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //continue to fire on their last position + /* + if ( !Q_irand( 0, 1 ) && NPCInfo->enemyLastSeenTime && level.time - NPCInfo->enemyLastSeenTime < 4000 ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + + VectorNormalize( dir ); + + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + //FIXME: they always throw toward enemy, so this will be very odd... + shoot3 = qtrue; + faceEnemy3 = qfalse; + + return; + } + */ +} + +qboolean Grenadier_EvaluateShot( int hit ) +{ + if ( !NPC->enemy ) + { + return qfalse; + } + + if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].r.svFlags&SVF_GLASS_BRUSH)) ) + {//can hit enemy or will hit glass, so shoot anyway + return qtrue; + } + return qfalse; +} + +/* +------------------------- +NPC_BSGrenadier_Attack +------------------------- +*/ + +void NPC_BSGrenadier_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + NPC_BSGrenadier_Patrol();//FIXME: or patrol? + return; + } + + if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//going to run + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSGrenadier_Patrol();//FIXME: or patrol? + return; + } + + enemyLOS3 = enemyCS3 = qfalse; + move3 = qtrue; + faceEnemy3 = qfalse; + shoot3 = qfalse; + enemyDist3 = DistanceSquared( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ); + + //See if we should switch to melee attack + if ( enemyDist3 < 16384 //128 + && (!NPC->enemy->client + || NPC->enemy->client->ps.weapon != WP_SABER + || BG_SabersOff( &NPC->enemy->client->ps ) + ) + ) + {//enemy is close and not using saber + if ( NPC->client->ps.weapon == WP_THERMAL ) + {//grenadier + trace_t trace; + trap_Trace ( &trace, NPC->r.currentOrigin, NPC->enemy->r.mins, NPC->enemy->r.maxs, NPC->enemy->r.currentOrigin, NPC->s.number, NPC->enemy->clipmask ); + if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->enemy->s.number ) ) + {//I can get right to him + //reset fire-timing variables + NPC_ChangeWeapon( WP_STUN_BATON ); + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT ) + {//FIXME: should we be overriding scriptFlags? + NPCInfo->scriptFlags |= SCF_CHASE_ENEMIES;//NPCInfo->behaviorState = BS_HUNT_AND_KILL; + } + } + } + } + else if ( enemyDist3 > 65536 || (NPC->enemy->client && NPC->enemy->client->ps.weapon == WP_SABER && !NPC->enemy->client->ps.saberHolstered) )//256 + {//enemy is far or using saber + if ( NPC->client->ps.weapon == WP_STUN_BATON && (NPC->client->ps.stats[STAT_WEAPONS]&(1<enemy ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS3 = qtrue; + + if ( NPC->client->ps.weapon == WP_STUN_BATON ) + { + if ( enemyDist3 <= 4096 && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 90, 45 ) )//within 64 & infront + { + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + enemyCS3 = qtrue; + } + } + else if ( InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 45, 90 ) ) + {//in front of me + //can we shoot our target? + //FIXME: how accurate/necessary is this check? + int hit = NPC_ShotEntity( NPC->enemy, NULL ); + gentity_t *hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) ) + { + float enemyHorzDist; + + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + enemyHorzDist = DistanceHorizontalSquared( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ); + if ( enemyHorzDist < 1048576 ) + {//within 1024 + enemyCS3 = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + } + else + { + NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + } + } + } + } + else + { + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + /* + else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy3 = qtrue; + } + */ + + if ( enemyLOS3 ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy3 = qtrue; + } + + if ( enemyCS3 ) + { + shoot3 = qtrue; + if ( NPC->client->ps.weapon == WP_THERMAL ) + {//don't chase and throw + move3 = qfalse; + } + else if ( NPC->client->ps.weapon == WP_STUN_BATON && enemyDist3 < (NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+16)*(NPC->r.maxs[0]+NPC->enemy->r.maxs[0]+16) ) + {//close enough + move3 = qfalse; + } + }//this should make him chase enemy when out of range...? + + //Check for movement to take care of + Grenadier_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + Grenadier_CheckFireState(); + + if ( move3 ) + {//move toward goal + if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist3 > 10000 ) )//100 squared + { + move3 = Grenadier_Move(); + } + else + { + move3 = qfalse; + } + } + + if ( !move3 ) + { + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + //FIXME: what about leaning? + } + else + {//stop ducking! + TIMER_Set( NPC, "duck", -1 ); + } + + if ( !faceEnemy3 ) + {//we want to face in the dir we're running + if ( move3 ) + {//don't run away and shoot + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + shoot3 = qfalse; + } + NPC_UpdateAngles( qtrue, qtrue ); + } + else// if ( faceEnemy3 ) + {//face the enemy + NPC_FaceEnemy(qtrue); + } + + if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) + { + shoot3 = qfalse; + } + + //FIXME: don't shoot right away! + if ( shoot3 ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time ); + } + + } + } +} + +void NPC_BSGrenadier_Default( void ) +{ + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSGrenadier_Patrol(); + } + else//if ( NPC->enemy ) + {//have an enemy + NPC_BSGrenadier_Attack(); + } +} diff --git a/code/game/NPC_AI_Howler.c b/code/game/NPC_AI_Howler.c new file mode 100644 index 0000000..b25c3f0 --- /dev/null +++ b/code/game/NPC_AI_Howler.c @@ -0,0 +1,218 @@ +#include "b_local.h" + +// These define the working combat range for these suckers +#define MIN_DISTANCE 54 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 128 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +/* +------------------------- +NPC_Howler_Precache +------------------------- +*/ +void NPC_Howler_Precache( void ) +{ +} + + +/* +------------------------- +Howler_Idle +------------------------- +*/ +void Howler_Idle( void ) +{ +} + + +/* +------------------------- +Howler_Patrol +------------------------- +*/ +void Howler_Patrol( void ) +{ + vec3_t dif; + + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + else + { + if ( TIMER_Done( NPC, "patrolTime" )) + { + TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 ); + } + } + + //rwwFIXMEFIXME: Care about all clients, not just client 0 + VectorSubtract( g_entities[0].r.currentOrigin, NPC->r.currentOrigin, dif ); + + if ( VectorLengthSquared( dif ) < 256 * 256 ) + { + G_SetEnemy( NPC, &g_entities[0] ); + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Howler_Idle(); + return; + } +} + +/* +------------------------- +Howler_Move +------------------------- +*/ +void Howler_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + NPC_MoveToGoal( qtrue ); + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + } +} + +//--------------------------------------------------------- +void Howler_TryDamage( gentity_t *enemy, int damage ) +{ + vec3_t end, dir; + trace_t tr; + + if ( !enemy ) + { + return; + } + + AngleVectors( NPC->client->ps.viewangles, dir, NULL, NULL ); + VectorMA( NPC->r.currentOrigin, MIN_DISTANCE, dir, end ); + + // Should probably trace from the mouth, but, ah well. + trap_Trace( &tr, NPC->r.currentOrigin, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + + if ( tr.entityNum != ENTITYNUM_WORLD ) + { + G_Damage( &g_entities[tr.entityNum], NPC, NPC, dir, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } +} + +//------------------------------ +void Howler_Attack( void ) +{ + if ( !TIMER_Exists( NPC, "attacking" )) + { + // Going to do ATTACK1 + TIMER_Set( NPC, "attacking", 1700 + random() * 200 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack_dmg", 200 ); // level two damage + } + + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + if ( TIMER_Done2( NPC, "attack_dmg", qtrue )) + { + Howler_TryDamage( NPC->enemy, 5 ); + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); +} + +//---------------------------------- +void Howler_Combat( void ) +{ + float distance; + qboolean advance; + + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS4( NPC->enemy ) || UpdateGoal( )) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + NPC_MoveToGoal( qtrue ); + return; + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + distance = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + advance = (qboolean)( distance > MIN_DISTANCE_SQR ? qtrue : qfalse ); + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Howler_Move( 1 ); + } + } + else + { + Howler_Attack(); + } +} + +/* +------------------------- +NPC_Howler_Pain +------------------------- +*/ +void NPC_Howler_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + if ( damage >= 10 ) + { + TIMER_Remove( self, "attacking" ); + TIMER_Set( self, "takingPain", 2900 ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } +} + + +/* +------------------------- +NPC_BSHowler_Default +------------------------- +*/ +void NPC_BSHowler_Default( void ) +{ + if ( NPC->enemy ) + { + Howler_Combat(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Howler_Patrol(); + } + else + { + Howler_Idle(); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/code/game/NPC_AI_ImperialProbe.c b/code/game/NPC_AI_ImperialProbe.c new file mode 100644 index 0000000..2b8d81c --- /dev/null +++ b/code/game/NPC_AI_ImperialProbe.c @@ -0,0 +1,609 @@ +#include "b_local.h" +#include "g_nav.h" + +#include "../namespace_begin.h" +gitem_t *BG_FindItemForAmmo( ammo_t ammo ); +#include "../namespace_end.h" +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_BACKINGUP, + LSTATE_SPINNING, + LSTATE_PAIN, + LSTATE_DROP +}; + +void ImperialProbe_Idle( void ); + +void NPC_Probe_Precache(void) +{ + int i; + + for ( i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/probe/misc/probetalk%d", i ) ); + } + G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); + G_SoundIndex("sound/chars/probe/misc/anger1"); + G_SoundIndex("sound/chars/probe/misc/fire"); + + G_EffectIndex( "chunks/probehead" ); + G_EffectIndex( "env/med_explode2" ); + G_EffectIndex( "explosions/probeexplosion1"); + G_EffectIndex( "bryar/muzzle_flash" ); + + RegisterItem( BG_FindItemForAmmo( AMMO_BLASTER )); + RegisterItem( BG_FindItemForWeapon( WP_BRYAR_PISTOL ) ); +} +/* +------------------------- +Hunter_MaintainHeight +------------------------- +*/ + +#define VELOCITY_DECAY 0.85f + +void ImperialProbe_MaintainHeight( void ) +{ + float dif; +// vec3_t endPos; +// trace_t trace; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at about enemy eye level + if ( NPC->enemy ) + { + // Find the height difference + dif = NPC->enemy->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 8 ) + { + if ( fabs( dif ) > 16 ) + { + dif = ( dif < 0 ? -16 : 16 ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + // Apply friction + else if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + + // Stay at a given height until we take on an enemy +/* VectorSet( endPos, NPC->r.currentOrigin[0], NPC->r.currentOrigin[1], NPC->r.currentOrigin[2] - 512 ); + trap_Trace( &trace, NPC->r.currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID ); + + if ( trace.fraction != 1.0f ) + { + float length = ( trace.fraction * 512 ); + + if ( length < 80 ) + { + ucmd.upmove = 32; + } + else if ( length > 120 ) + { + ucmd.upmove = -32; + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } */ + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +/* +------------------------- +ImperialProbe_Strafe +------------------------- +*/ + +#define HUNTER_STRAFE_VEL 256 +#define HUNTER_STRAFE_DIS 200 +#define HUNTER_UPWARD_PUSH 32 + +void ImperialProbe_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->r.currentOrigin, HUNTER_STRAFE_DIS * dir, right, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, HUNTER_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + // Add a slight upward push + NPC->client->ps.velocity[2] += HUNTER_UPWARD_PUSH; + + // Set the strafe start time so we can do a controlled roll + //NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +/* +------------------------- +ImperialProbe_Hunt +-------------------------` +*/ + +#define HUNTER_FORWARD_BASE_SPEED 10 +#define HUNTER_FORWARD_MULTIPLIER 5 + +void ImperialProbe_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + ImperialProbe_Strafe(); + return; + } + } + + //If we don't want to advance, stop here + if ( advance == qfalse ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + //Get our direction from the navigator if we can't see our target + if ( NPC_GetMoveDirection( forward, &distance ) == qfalse ) + return; + } + else + { + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = HUNTER_FORWARD_BASE_SPEED + HUNTER_FORWARD_MULTIPLIER * g_spskill.integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +/* +------------------------- +ImperialProbe_FireBlaster +------------------------- +*/ +void ImperialProbe_FireBlaster(void) +{ + vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + int genBolt1; + gentity_t *missile; + mdxaBone_t boltMatrix; + + genBolt1 = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash"); + + //FIXME: use {0, NPC->client->ps.legsYaw, 0} + trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, + genBolt1, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time, + NULL, NPC->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle1 ); + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle1, vec3_origin ); + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/probe/misc/fire" )); + + if (NPC->health) + { + CalcEntitySpot( NPC->enemy, SPOT_CHEST, enemy_org1 ); + enemy_org1[0]+= Q_irand(0,10); + enemy_org1[1]+= Q_irand(0,10); + VectorSubtract (enemy_org1, muzzle1, delta1); + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + } + else + { + AngleVectors (NPC->r.currentAngles, forward, vright, up); + } + + missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC, qfalse ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + if ( g_spskill.integer <= 1 ) + { + missile->damage = 5; + } + else + { + missile->damage = 10; + } + + + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_UNKNOWN; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +ImperialProbe_Ranged +------------------------- +*/ +void ImperialProbe_Ranged( qboolean visible, qboolean advance ) +{ + int delay_min,delay_max; + + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + + if ( g_spskill.integer == 0 ) + { + delay_min = 500; + delay_max = 3000; + } + else if ( g_spskill.integer > 1 ) + { + delay_min = 500; + delay_max = 2000; + } + else + { + delay_min = 300; + delay_max = 1500; + } + + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) ); + ImperialProbe_FireBlaster(); +// ucmd.buttons |= BUTTON_ATTACK; + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ImperialProbe_Hunt( visible, advance ); + } +} + +/* +------------------------- +ImperialProbe_AttackDecision +------------------------- +*/ + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +void ImperialProbe_AttackDecision( void ) +{ + float distance; + qboolean visible; + qboolean advance; + + // Always keep a good height off the ground + ImperialProbe_MaintainHeight(); + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse ) + { + ImperialProbe_Idle(); + return; + } + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL); + + // Rate our distance to the target, and our visibilty + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + ImperialProbe_Hunt( visible, advance ); + return; + } + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + // Decide what type of attack to do + ImperialProbe_Ranged( visible, advance ); +} + +/* +------------------------- +NPC_BSDroid_Pain +------------------------- +*/ +void NPC_Probe_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + float pain_chance; + gentity_t *other = attacker; + int mod = gPainMOD; + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( self->health < 30 || mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) // demp2 always messes them up real good + { + vec3_t endPos; + trace_t trace; + + VectorSet( endPos, self->r.currentOrigin[0], self->r.currentOrigin[1], self->r.currentOrigin[2] - 128 ); + trap_Trace( &trace, self->r.currentOrigin, NULL, NULL, endPos, self->s.number, MASK_SOLID ); + + if ( trace.fraction == 1.0f || mod == MOD_DEMP2 ) // demp2 always does this + { + /* + if (self->client->clientInfo.headModel != 0) + { + vec3_t origin; + + VectorCopy(self->r.currentOrigin,origin); + origin[2] +=50; +// G_PlayEffect( "small_chunks", origin ); + G_PlayEffect( "chunks/probehead", origin ); + G_PlayEffect( "env/med_explode2", origin ); + self->client->clientInfo.headModel = 0; + self->client->moveType = MT_RUNJUMP; + self->client->ps.gravity = g_gravity->value*.1; + } + */ + + if ( (mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT) && other ) + { + vec3_t dir; + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + + VectorSubtract( self->r.currentOrigin, other->r.currentOrigin, dir ); + VectorNormalize( dir ); + + VectorMA( self->client->ps.velocity, 550, dir, self->client->ps.velocity ); + self->client->ps.velocity[2] -= 127; + } + + //self->s.powerups |= ( 1 << PW_SHOCKED ); + //self->client->ps.powerups[PW_SHOCKED] = level.time + 3000; + self->client->ps.electrifyTime = level.time + 3000; + + self->NPC->localState = LSTATE_DROP; + } + } + else + { + pain_chance = NPC_GetPainChance( self, damage ); + + if ( random() < pain_chance ) // Spin around in pain? + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE); + } + } + + NPC_Pain( self, attacker, damage ); +} + +/* +------------------------- +ImperialProbe_Idle +------------------------- +*/ + +void ImperialProbe_Idle( void ) +{ + ImperialProbe_MaintainHeight(); + + NPC_BSIdle(); +} + +/* +------------------------- +NPC_BSImperialProbe_Patrol +------------------------- +*/ +void ImperialProbe_Patrol( void ) +{ + ImperialProbe_MaintainHeight(); + + if ( NPC_CheckPlayerTeamStealth() ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1, SETANIM_FLAG_NORMAL ); + + if ( UpdateGoal() ) + { + //start loop sound once we move + NPC->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + //randomly talk + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/probetalk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + else // He's got an enemy. Make him angry. + { + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/probe/misc/anger1" ); + TIMER_Set( NPC, "angerNoise", Q_irand( 2000, 4000 ) ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL; + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +ImperialProbe_Wait +------------------------- +*/ +void ImperialProbe_Wait(void) +{ + if ( NPCInfo->localState == LSTATE_DROP ) + { + vec3_t endPos; + trace_t trace; + + NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->desiredYaw + 25 ); + + VectorSet( endPos, NPC->r.currentOrigin[0], NPC->r.currentOrigin[1], NPC->r.currentOrigin[2] - 32 ); + trap_Trace( &trace, NPC->r.currentOrigin, NULL, NULL, endPos, NPC->s.number, MASK_SOLID ); + + if ( trace.fraction != 1.0f ) + { + G_Damage(NPC, NPC->enemy, NPC->enemy, NULL, NULL, 2000, 0,MOD_UNKNOWN); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSImperialProbe_Default +------------------------- +*/ +void NPC_BSImperialProbe_Default( void ) +{ + + if ( NPC->enemy ) + { + NPCInfo->goalEntity = NPC->enemy; + ImperialProbe_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + ImperialProbe_Patrol(); + } + else if ( NPCInfo->localState == LSTATE_DROP ) + { + ImperialProbe_Wait(); + } + else + { + ImperialProbe_Idle(); + } +} diff --git a/code/game/NPC_AI_Interrogator.c b/code/game/NPC_AI_Interrogator.c new file mode 100644 index 0000000..e04e4e4 --- /dev/null +++ b/code/game/NPC_AI_Interrogator.c @@ -0,0 +1,467 @@ +#include "b_local.h" +#include "g_nav.h" + +void Interrogator_Idle( void ); +void DeathFX( gentity_t *ent ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); + +enum +{ +LSTATE_BLADESTOP=0, +LSTATE_BLADEUP, +LSTATE_BLADEDOWN, +}; + +/* +------------------------- +NPC_Interrogator_Precache +------------------------- +*/ +void NPC_Interrogator_Precache(gentity_t *self) +{ + G_SoundIndex( "sound/chars/interrogator/misc/torture_droid_lp" ); + G_SoundIndex("sound/chars/mark1/misc/anger.wav"); + G_SoundIndex( "sound/chars/probe/misc/talk"); + G_SoundIndex( "sound/chars/interrogator/misc/torture_droid_inject" ); + G_SoundIndex( "sound/chars/interrogator/misc/int_droid_explo" ); + G_EffectIndex( "explosions/droidexplosion1" ); +} +/* +------------------------- +Interrogator_die +------------------------- +*/ +void Interrogator_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +{ + self->client->ps.velocity[2] = -100; + /* + self->locationDamage[HL_NONE] += damage; + if (self->locationDamage[HL_NONE] > 40) + { + DeathFX(self); + self->client->ps.eFlags |= EF_NODRAW; + self->contents = CONTENTS_CORPSE; + } + else + */ + { + self->client->ps.eFlags2 &= ~EF2_FLYING;//moveType = MT_WALK; + self->client->ps.velocity[0] = Q_irand( -10, -20 ); + self->client->ps.velocity[1] = Q_irand( -10, -20 ); + self->client->ps.velocity[2] = -100; + } + //self->takedamage = qfalse; + //self->client->ps.eFlags |= EF_NODRAW; + //self->contents = 0; + return; +} + +/* +------------------------- +Interrogator_PartsMove +------------------------- +*/ +void Interrogator_PartsMove(void) +{ + // Syringe + if ( TIMER_Done(NPC,"syringeDelay") ) + { + NPC->pos1[1] = AngleNormalize360( NPC->pos1[1]); + + if ((NPC->pos1[1] < 60) || (NPC->pos1[1] > 300)) + { + NPC->pos1[1]+=Q_irand( -20, 20 ); // Pitch + } + else if (NPC->pos1[1] > 180) + { + NPC->pos1[1]=Q_irand( 300, 360 ); // Pitch + } + else + { + NPC->pos1[1]=Q_irand( 0, 60 ); // Pitch + } + + // gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone1, NPC->pos1, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + NPC_SetBoneAngles(NPC, "left_arm", NPC->pos1); + + TIMER_Set( NPC, "syringeDelay", Q_irand( 100, 1000 ) ); + } + + // Scalpel + if ( TIMER_Done(NPC,"scalpelDelay") ) + { + // Change pitch + if ( NPCInfo->localState == LSTATE_BLADEDOWN ) // Blade is moving down + { + NPC->pos2[0]-= 30; + if (NPC->pos2[0] < 180) + { + NPC->pos2[0] = 180; + NPCInfo->localState = LSTATE_BLADEUP; // Make it move up + } + } + else // Blade is coming back up + { + NPC->pos2[0]+= 30; + if (NPC->pos2[0] >= 360) + { + NPC->pos2[0] = 360; + NPCInfo->localState = LSTATE_BLADEDOWN; // Make it move down + TIMER_Set( NPC, "scalpelDelay", Q_irand( 100, 1000 ) ); + } + } + + NPC->pos2[0] = AngleNormalize360( NPC->pos2[0]); + // gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone2, NPC->pos2, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + + NPC_SetBoneAngles(NPC, "right_arm", NPC->pos2); + } + + // Claw + NPC->pos3[1] += Q_irand( 10, 30 ); + NPC->pos3[1] = AngleNormalize360( NPC->pos3[1]); + //gi.G2API_SetBoneAnglesIndex( &NPC->ghoul2[NPC->playerModel], NPC->genericBone3, NPC->pos3, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL ); + + NPC_SetBoneAngles(NPC, "claw", NPC->pos3); + +} + +#define VELOCITY_DECAY 0.85f +#define HUNTER_UPWARD_PUSH 2 + +/* +------------------------- +Interrogator_MaintainHeight +------------------------- +*/ +void Interrogator_MaintainHeight( void ) +{ + float dif; +// vec3_t endPos; +// trace_t trace; + + NPC->s.loopSound = G_SoundIndex( "sound/chars/interrogator/misc/torture_droid_lp" ); + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at about enemy eye level + if ( NPC->enemy ) + { + // Find the height difference + dif = (NPC->enemy->r.currentOrigin[2] + NPC->enemy->r.maxs[2]) - NPC->r.currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 2 ) + { + if ( fabs( dif ) > 16 ) + { + dif = ( dif < 0 ? -16 : 16 ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + // Apply friction + else if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +#define HUNTER_STRAFE_VEL 32 +#define HUNTER_STRAFE_DIS 200 +/* +------------------------- +Interrogator_Strafe +------------------------- +*/ +void Interrogator_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + float dif; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->r.currentOrigin, HUNTER_STRAFE_DIS * dir, right, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, HUNTER_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + // Add a slight upward push + if ( NPC->enemy ) + { + // Find the height difference + dif = (NPC->enemy->r.currentOrigin[2] + 32) - NPC->r.currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 8 ) + { + dif = ( dif < 0 ? -HUNTER_UPWARD_PUSH : HUNTER_UPWARD_PUSH ); + } + + NPC->client->ps.velocity[2] += dif; + + } + + // Set the strafe start time + //NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +/* +------------------------- +Interrogator_Hunt +-------------------------` +*/ + +#define HUNTER_FORWARD_BASE_SPEED 10 +#define HUNTER_FORWARD_MULTIPLIER 2 + +void Interrogator_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + Interrogator_PartsMove(); + + NPC_FaceEnemy(qfalse); + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Interrogator_Strafe(); + if ( NPCInfo->standTime > level.time ) + {//successfully strafed + return; + } + } + } + + //If we don't want to advance, stop here + if ( advance == qfalse ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + //Get our direction from the navigator if we can't see our target + if ( NPC_GetMoveDirection( forward, &distance ) == qfalse ) + return; + } + else + { + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = HUNTER_FORWARD_BASE_SPEED + HUNTER_FORWARD_MULTIPLIER * g_spskill.integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +#define MIN_DISTANCE 64 + +/* +------------------------- +Interrogator_Melee +------------------------- +*/ +void Interrogator_Melee( qboolean visible, qboolean advance ) +{ + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + // Make sure that we are within the height range before we allow any damage to happen + if ( NPC->r.currentOrigin[2] >= NPC->enemy->r.currentOrigin[2]+NPC->enemy->r.mins[2] && NPC->r.currentOrigin[2]+NPC->r.mins[2]+8 < NPC->enemy->r.currentOrigin[2]+NPC->enemy->r.maxs[2] ) + { + //gentity_t *tent; + + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) ); + G_Damage( NPC->enemy, NPC, NPC, 0, 0, 2, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + + // NPC->enemy->client->poisonDamage = 18; + // NPC->enemy->client->poisonTime = level.time + 1000; + + // Drug our enemy up and do the wonky vision thing +// tent = G_TempEntity( NPC->enemy->r.currentOrigin, EV_DRUGGED ); +// tent->owner = NPC->enemy; + + //rwwFIXMEFIXME: poison damage + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/interrogator/misc/torture_droid_inject.mp3" )); + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Interrogator_Hunt( visible, advance ); + } +} + +/* +------------------------- +Interrogator_Attack +------------------------- +*/ +void Interrogator_Attack( void ) +{ + float distance; + qboolean visible; + qboolean advance; + + // Always keep a good height off the ground + Interrogator_MaintainHeight(); + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/probe/misc/talk.wav", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse ) + { + Interrogator_Idle(); + return; + } + + // Rate our distance to the target, and our visibilty + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE*MIN_DISTANCE ); + + if ( !visible ) + { + advance = qtrue; + } + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Interrogator_Hunt( visible, advance ); + } + + NPC_FaceEnemy( qtrue ); + + if (!advance) + { + Interrogator_Melee( visible, advance ); + } +} + +/* +------------------------- +Interrogator_Idle +------------------------- +*/ +void Interrogator_Idle( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/mark1/misc/anger.wav" ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + Interrogator_MaintainHeight(); + + NPC_BSIdle(); +} + +/* +------------------------- +NPC_BSInterrogator_Default +------------------------- +*/ +void NPC_BSInterrogator_Default( void ) +{ + //NPC->e_DieFunc = dieF_Interrogator_die; + + if ( NPC->enemy ) + { + Interrogator_Attack(); + } + else + { + Interrogator_Idle(); + } + +} diff --git a/code/game/NPC_AI_Jedi.c b/code/game/NPC_AI_Jedi.c new file mode 100644 index 0000000..fb832fc --- /dev/null +++ b/code/game/NPC_AI_Jedi.c @@ -0,0 +1,6220 @@ +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" +#include "w_saber.h" + +#include "../namespace_begin.h" +extern qboolean BG_SabersOff( playerState_t *ps ); +#include "../namespace_end.h" + +extern void CG_DrawAlert( vec3_t origin, float rating ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void ForceJump( gentity_t *self, usercmd_t *ucmd ); +extern vmCvar_t g_saberRealisticCombat; +extern vmCvar_t d_slowmodeath; + +void G_StartMatrixEffect( gentity_t *ent ) +{ //perhaps write this at some point? + +} + +#define MAX_VIEW_DIST 2048 +#define MAX_VIEW_SPEED 100 +#define JEDI_MAX_LIGHT_INTENSITY 64 +#define JEDI_MIN_LIGHT_THRESHOLD 10 +#define JEDI_MAX_LIGHT_THRESHOLD 50 + +#define DISTANCE_SCALE 0.25f +//#define DISTANCE_THRESHOLD 0.075f +#define SPEED_SCALE 0.25f +#define FOV_SCALE 0.5f +#define LIGHT_SCALE 0.25f + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.3 ) + +#define MAX_CHECK_THRESHOLD 1 + +extern void NPC_ClearLookTarget( gentity_t *self ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern qboolean NPC_CheckEnemyStealth( void ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); + +#include "../namespace_begin.h" +extern gitem_t *BG_FindItemForAmmo( ammo_t ammo ); +#include "../namespace_end.h" + +extern void ForceThrow( gentity_t *self, qboolean pull ); +extern void ForceLightning( gentity_t *self ); +extern void ForceHeal( gentity_t *self ); +extern void ForceRage( gentity_t *self ); +extern void ForceProtect( gentity_t *self ); +extern void ForceAbsorb( gentity_t *self ); +extern int WP_MissileBlockForBlock( int saberBlock ); +extern qboolean G_GetHitLocFromSurfName( gentity_t *ent, const char *surfName, int *hitLoc, vec3_t point, vec3_t dir, vec3_t bladeDir, int mod ); +extern qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern qboolean WP_ForcePowerAvailable( gentity_t *self, forcePowers_t forcePower, int overrideAmt ); +extern void WP_ForcePowerStop( gentity_t *self, forcePowers_t forcePower ); +extern void WP_DeactivateSaber( gentity_t *self, qboolean clearLength ); //clearLength = qfalse +extern void WP_ActivateSaber( gentity_t *self ); +//extern void WP_SaberBlock(gentity_t *saber, vec3_t hitloc); + +#include "../namespace_begin.h" +extern qboolean PM_SaberInStart( int move ); +extern qboolean BG_SaberInSpecialAttack( int anim ); +extern qboolean BG_SaberInAttack( int move ); +extern qboolean PM_SaberInBounce( int move ); +extern qboolean PM_SaberInParry( int move ); +extern qboolean PM_SaberInKnockaway( int move ); +extern qboolean PM_SaberInBrokenParry( int move ); +extern qboolean PM_SaberInDeflect( int move ); +extern qboolean BG_SpinningSaberAnim( int anim ); +extern qboolean BG_FlippingAnim( int anim ); +extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean BG_InRoll( playerState_t *ps, int anim ); +extern qboolean BG_CrouchAnim( int anim ); +#include "../namespace_end.h" + +extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent); + +extern int WP_GetVelocityForForceJump( gentity_t *self, vec3_t jumpVel, usercmd_t *ucmd ); + +extern void G_TestLine(vec3_t start, vec3_t end, int color, int time); + +static void Jedi_Aggression( gentity_t *self, int change ); +qboolean Jedi_WaitingAmbush( gentity_t *self ); + +#include "../namespace_begin.h" +extern int bg_parryDebounce[]; +#include "../namespace_end.h" + +static int jediSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several jedi from speaking all at once +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void NPC_ShadowTrooper_Precache( void ) +{ + RegisterItem( BG_FindItemForAmmo( AMMO_FORCE ) ); + G_SoundIndex( "sound/chars/shadowtrooper/cloak.wav" ); + G_SoundIndex( "sound/chars/shadowtrooper/decloak.wav" ); +} + +void Jedi_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "strafeLeft", 0 ); + TIMER_Set( ent, "strafeRight", 0 ); + TIMER_Set( ent, "noStrafe", 0 ); + TIMER_Set( ent, "walking", 0 ); + TIMER_Set( ent, "taunting", 0 ); + TIMER_Set( ent, "parryTime", 0 ); + TIMER_Set( ent, "parryReCalcTime", 0 ); + TIMER_Set( ent, "forceJumpChasing", 0 ); + TIMER_Set( ent, "jumpChaseDebounce", 0 ); + TIMER_Set( ent, "moveforward", 0 ); + TIMER_Set( ent, "moveback", 0 ); + TIMER_Set( ent, "movenone", 0 ); + TIMER_Set( ent, "moveright", 0 ); + TIMER_Set( ent, "moveleft", 0 ); + TIMER_Set( ent, "movecenter", 0 ); + TIMER_Set( ent, "saberLevelDebounce", 0 ); + TIMER_Set( ent, "noRetreat", 0 ); + TIMER_Set( ent, "holdLightning", 0 ); + TIMER_Set( ent, "gripping", 0 ); + TIMER_Set( ent, "draining", 0 ); + TIMER_Set( ent, "noturn", 0 ); +} + +void Jedi_PlayBlockedPushSound( gentity_t *self ) +{ + if ( !self->s.number ) + { + G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 ); + } + else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( self, EV_PUSHFAIL, 3000 ); + self->NPC->blockedSpeechDebounceTime = level.time + 3000; + } +} + +void Jedi_PlayDeflectSound( gentity_t *self ) +{ + if ( !self->s.number ) + { + G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 ); + } + else if ( self->health > 0 && self->NPC && self->NPC->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( self, Q_irand( EV_DEFLECT1, EV_DEFLECT3 ), 3000 ); + self->NPC->blockedSpeechDebounceTime = level.time + 3000; + } +} + +void NPC_Jedi_PlayConfusionSound( gentity_t *self ) +{ + if ( self->health > 0 ) + { + if ( self->client && ( self->client->NPC_class == CLASS_TAVION || self->client->NPC_class == CLASS_DESANN ) ) + { + G_AddVoiceEvent( self, Q_irand( EV_CONFUSE1, EV_CONFUSE3 ), 2000 ); + } + else if ( Q_irand( 0, 1 ) ) + { + G_AddVoiceEvent( self, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 2000 ); + } + else + { + G_AddVoiceEvent( self, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 2000 ); + } + } +} + +void Boba_Precache( void ) +{ + G_SoundIndex( "sound/boba/jeton.wav" ); + G_SoundIndex( "sound/boba/jethover.wav" ); + G_SoundIndex( "sound/effects/combustfire.mp3" ); + G_EffectIndex( "boba/jet" ); + G_EffectIndex( "boba/fthrw" ); +} + +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +void Boba_ChangeWeapon( int wp ) +{ + if ( NPC->s.weapon == wp ) + { + return; + } + NPC_ChangeWeapon( wp ); + G_AddEvent( NPC, EV_GENERAL_SOUND, G_SoundIndex( "sound/weapons/change.wav" )); +} + +void WP_ResistForcePush( gentity_t *self, gentity_t *pusher, qboolean noPenalty ) +{ + int parts; + qboolean runningResist = qfalse; + + if ( !self || self->health <= 0 || !self->client || !pusher || !pusher->client ) + { + return; + } + if ( (!self->s.number || self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) || self->client->NPC_class == CLASS_LUKE) + && (VectorLengthSquared( self->client->ps.velocity ) > 10000 || self->client->ps.fd.forcePowerLevel[FP_PUSH] >= FORCE_LEVEL_3 || self->client->ps.fd.forcePowerLevel[FP_PULL] >= FORCE_LEVEL_3 ) ) + { + runningResist = qtrue; + } + if ( !runningResist + && self->client->ps.groundEntityNum != ENTITYNUM_NONE + && !BG_SpinningSaberAnim( self->client->ps.legsAnim ) + && !BG_FlippingAnim( self->client->ps.legsAnim ) + && !PM_RollingAnim( self->client->ps.legsAnim ) + && !PM_InKnockDown( &self->client->ps ) + && !BG_CrouchAnim( self->client->ps.legsAnim )) + {//if on a surface and not in a spin or flip, play full body resist + parts = SETANIM_BOTH; + } + else + {//play resist just in torso + parts = SETANIM_TORSO; + } + NPC_SetAnim( self, parts, BOTH_RESISTPUSH, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( !noPenalty ) + { + char buf[128]; + float tFVal = 0; + + trap_Cvar_VariableStringBuffer("timescale", buf, sizeof(buf)); + + tFVal = atof(buf); + + if ( !runningResist ) + { + VectorClear( self->client->ps.velocity ); + //still stop them from attacking or moving for a bit, though + //FIXME: maybe push just a little (like, slide)? + self->client->ps.weaponTime = 1000; + if ( self->client->ps.fd.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * tFVal ); + } + self->client->ps.pm_time = self->client->ps.weaponTime; + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + //play the full body push effect on me + //self->forcePushTime = level.time + 600; // let the push effect last for 600 ms + //rwwFIXMEFIXME: Do this? + } + else + { + self->client->ps.weaponTime = 600; + if ( self->client->ps.fd.forcePowersActive&(1<client->ps.weaponTime = floor( self->client->ps.weaponTime * tFVal ); + } + } + } + //play my force push effect on my hand + self->client->ps.powerups[PW_DISINT_4] = level.time + self->client->ps.torsoTimer + 500; + self->client->ps.powerups[PW_PULL] = 0; + Jedi_PlayBlockedPushSound( self ); +} + +qboolean Boba_StopKnockdown( gentity_t *self, gentity_t *pusher, vec3_t pushDir, qboolean forceKnockdown ) //forceKnockdown = qfalse +{ + vec3_t pDir, fwd, right, ang; + float fDot, rDot; + int strafeTime; + + if ( self->client->NPC_class != CLASS_BOBAFETT ) + { + return qfalse; + } + + if ( (self->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM ) + {//can't knock me down when I'm flying + return qtrue; + } + + VectorSet(ang, 0, self->r.currentAngles[YAW], 0); + strafeTime = Q_irand( 1000, 2000 ); + + AngleVectors( ang, fwd, right, NULL ); + VectorNormalize2( pushDir, pDir ); + fDot = DotProduct( pDir, fwd ); + rDot = DotProduct( pDir, right ); + + if ( Q_irand( 0, 2 ) ) + {//flip or roll with it + usercmd_t tempCmd; + if ( fDot >= 0.4f ) + { + tempCmd.forwardmove = 127; + TIMER_Set( self, "moveforward", strafeTime ); + } + else if ( fDot <= -0.4f ) + { + tempCmd.forwardmove = -127; + TIMER_Set( self, "moveback", strafeTime ); + } + else if ( rDot > 0 ) + { + tempCmd.rightmove = 127; + TIMER_Set( self, "strafeRight", strafeTime ); + TIMER_Set( self, "strafeLeft", -1 ); + } + else + { + tempCmd.rightmove = -127; + TIMER_Set( self, "strafeLeft", strafeTime ); + TIMER_Set( self, "strafeRight", -1 ); + } + G_AddEvent( self, EV_JUMP, 0 ); + if ( !Q_irand( 0, 1 ) ) + {//flip + self->client->ps.fd.forceJumpCharge = 280;//FIXME: calc this intelligently? + ForceJump( self, &tempCmd ); + } + else + {//roll + TIMER_Set( self, "duck", strafeTime ); + } + self->painDebounceTime = 0;//so we do something + } + else if ( !Q_irand( 0, 1 ) && forceKnockdown ) + {//resist + WP_ResistForcePush( self, pusher, qtrue ); + } + else + {//fall down + return qfalse; + } + + return qtrue; +} + +void Boba_FlyStart( gentity_t *self ) +{//switch to seeker AI for a while + if ( TIMER_Done( self, "jetRecharge" ) ) + { + self->client->ps.gravity = 0; + if ( self->NPC ) + { + self->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; + } + self->client->ps.eFlags2 |= EF2_FLYING;//moveType = MT_FLYSWIM; + self->client->jetPackTime = level.time + Q_irand( 3000, 10000 ); + //take-off sound + G_SoundOnEnt( self, CHAN_ITEM, "sound/boba/jeton.wav" ); + //jet loop sound + self->s.loopSound = G_SoundIndex( "sound/boba/jethover.wav" ); + if ( self->NPC ) + { + self->count = Q3_INFINITE; // SEEKER shot ammo count + } + } +} + +void Boba_FlyStop( gentity_t *self ) +{ + self->client->ps.gravity = g_gravity.value; + if ( self->NPC ) + { + self->NPC->aiFlags &= ~NPCAI_CUSTOM_GRAVITY; + } + self->client->ps.eFlags2 &= ~EF2_FLYING; + self->client->jetPackTime = 0; + //stop jet loop sound + self->s.loopSound = 0; + if ( self->NPC ) + { + self->count = 0; // SEEKER shot ammo count + TIMER_Set( self, "jetRecharge", Q_irand( 1000, 5000 ) ); + TIMER_Set( self, "jumpChaseDebounce", Q_irand( 500, 2000 ) ); + } +} + +qboolean Boba_Flying( gentity_t *self ) +{ + return ((qboolean)(self->client->ps.eFlags2&EF2_FLYING));//moveType==MT_FLYSWIM)); +} + +void Boba_FireFlameThrower( gentity_t *self ) +{ + int damage = Q_irand( 20, 30 ); + trace_t tr; + gentity_t *traceEnt = NULL; + mdxaBone_t boltMatrix; + vec3_t start, end, dir, traceMins = {-4, -4, -4}, traceMaxs = {4, 4, 4}; + + trap_G2API_GetBoltMatrix( self->ghoul2, 0, self->client->renderInfo.handLBolt, + &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time, + NULL, self->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, start ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + //G_PlayEffect( "boba/fthrw", start, dir ); + VectorMA( start, 128, dir, end ); + + trap_Trace( &tr, start, traceMins, traceMaxs, end, self->s.number, MASK_SHOT ); + + traceEnt = &g_entities[tr.entityNum]; + if ( tr.entityNum < ENTITYNUM_WORLD && traceEnt->takedamage ) + { + G_Damage( traceEnt, self, self, dir, tr.endpos, damage, DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|/*DAMAGE_NO_HIT_LOC|*/DAMAGE_IGNORE_TEAM, MOD_LAVA ); + //rwwFIXMEFIXME: add DAMAGE_NO_HIT_LOC? + } +} + +//extern void SP_fx_explosion_trail( gentity_t *ent ); +void Boba_StartFlameThrower( gentity_t *self ) +{ + int flameTime = 4000;//Q_irand( 1000, 3000 ); + mdxaBone_t boltMatrix; + vec3_t org, dir; + + self->client->ps.torsoTimer = flameTime;//+1000; + if ( self->NPC ) + { + TIMER_Set( self, "nextAttackDelay", flameTime ); + TIMER_Set( self, "walking", 0 ); + } + TIMER_Set( self, "flameTime", flameTime ); + /* + gentity_t *fire = G_Spawn(); + if ( fire != NULL ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir, ang; + gi.G2API_GetBoltMatrix( NPC->ghoul2, NPC->playerModel, NPC->handRBolt, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, (cg.time?cg.time:level.time), + NULL, NPC->s.modelScale ); + + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, ORIGIN, org ); + gi.G2API_GiveMeVectorFromMatrix( boltMatrix, NEGATIVE_Y, dir ); + vectoangles( dir, ang ); + + VectorCopy( org, fire->s.origin ); + VectorCopy( ang, fire->s.angles ); + + fire->targetname = "bobafire"; + SP_fx_explosion_trail( fire ); + fire->damage = 1; + fire->radius = 10; + fire->speed = 200; + fire->fxID = G_EffectIndex( "boba/fthrw" );//"env/exp_fire_trail" );//"env/small_fire" + fx_explosion_trail_link( fire ); + fx_explosion_trail_use( fire, NPC, NPC ); + + } + */ + G_SoundOnEnt( self, CHAN_WEAPON, "sound/effects/combustfire.mp3" ); + + trap_G2API_GetBoltMatrix(NPC->ghoul2, 0, NPC->client->renderInfo.handRBolt, &boltMatrix, NPC->r.currentAngles, + NPC->r.currentOrigin, level.time, NULL, NPC->modelScale); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + + G_PlayEffectID( G_EffectIndex("boba/fthrw"), org, dir); +} + +void Boba_DoFlameThrower( gentity_t *self ) +{ + NPC_SetAnim( self, SETANIM_TORSO, BOTH_FORCELIGHTNING_HOLD, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( TIMER_Done( self, "nextAttackDelay" ) && TIMER_Done( self, "flameTime" ) ) + { + Boba_StartFlameThrower( self ); + } + Boba_FireFlameThrower( self ); +} + +void Boba_FireDecide( void ) +{ + qboolean enemyLOS = qfalse; + qboolean enemyCS = qfalse; + qboolean enemyInFOV = qfalse; + //qboolean move = qtrue; + qboolean faceEnemy = qfalse; + qboolean shoot = qfalse; + qboolean hitAlly = qfalse; + vec3_t impactPos; + float enemyDist; + float dot; + vec3_t enemyDir, shootDir; + + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE + && NPC->client->ps.fd.forceJumpZStart + && !BG_FlippingAnim( NPC->client->ps.legsAnim ) + && !Q_irand( 0, 10 ) ) + {//take off + Boba_FlyStart( NPC ); + } + + if ( !NPC->enemy ) + { + return; + } + + /* + if ( NPC->enemy->enemy != NPC && NPC->health == NPC->client->pers.maxHealth ) + { + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_DISRUPTOR ); + } + else */if ( NPC->enemy->s.weapon == WP_SABER ) + { + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_ROCKET_LAUNCHER ); + } + else + { + if ( NPC->health < NPC->client->pers.maxHealth*0.5f ) + { + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_BLASTER ); + NPCInfo->burstMin = 3; + NPCInfo->burstMean = 12; + NPCInfo->burstMax = 20; + NPCInfo->burstSpacing = Q_irand( 300, 750 );//attack debounce + } + else + { + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_BLASTER ); + } + } + + VectorClear( impactPos ); + enemyDist = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir ); + VectorNormalize( enemyDir ); + AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL ); + dot = DotProduct( enemyDir, shootDir ); + if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 ) + {//enemy is in front of me or they're very close and not behind me + enemyInFOV = qtrue; + } + + if ( (enemyDist < (128*128)&&enemyInFOV) || !TIMER_Done( NPC, "flameTime" ) ) + {//flamethrower + Boba_DoFlameThrower( NPC ); + enemyCS = qfalse; + shoot = qfalse; + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); + } + else if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128 + {//enemy within 128 + if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && + (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//shooting an explosive, but enemy too close, switch to primary fire + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not... + } + } + else if ( enemyDist > 65536 )//256 squared + { + if ( NPC->client->ps.weapon == WP_DISRUPTOR ) + {//sniping... should be assumed + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + {//use primary fire + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( WP_DISRUPTOR ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + //can we see our target? + if ( TIMER_Done( NPC, "nextAttackDelay" ) && TIMER_Done( NPC, "flameTime" ) ) + { + if ( NPC_ClearLOS4( NPC->enemy ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS = qtrue; + + if ( NPC->client->ps.weapon == WP_NONE ) + { + enemyCS = qfalse;//not true, but should stop us from firing + } + else + {//can we shoot our target? + if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128 + { + enemyCS = qfalse;//not true, but should stop us from firing + hitAlly = qtrue;//us! + //FIXME: if too close, run away! + } + else if ( enemyInFOV ) + {//if enemy is FOV, go ahead and check for shooting + int hit = NPC_ShotEntity( NPC->enemy, impactPos ); + gentity_t *hitEnt = &g_entities[hit]; + + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) ) + {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway + enemyCS = qtrue; + //NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + else + {//Hmm, have to get around this bastard + //NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) + {//would hit an ally, don't fire!!! + hitAlly = qtrue; + } + else + {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire + } + } + } + else + { + enemyCS = qfalse;//not true, but should stop us from firing + } + } + } + else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + //NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + + if ( NPC->client->ps.weapon == WP_NONE ) + { + faceEnemy = qfalse; + shoot = qfalse; + } + else + { + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + if ( enemyCS ) + { + shoot = qtrue; + } + } + + if ( !enemyCS ) + {//if have a clear shot, always try + //See if we should continue to fire on their last position + //!TIMER_Done( NPC, "stick" ) || + if ( !hitAlly //we're not going to hit an ally + && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS? + && NPCInfo->enemyLastSeenTime > 0 )//we've seen the enemy + { + if ( level.time - NPCInfo->enemyLastSeenTime < 10000 )//we have seem the enemy in the last 10 seconds + { + if ( !Q_irand( 0, 10 ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + qboolean tooClose = qfalse; + qboolean tooFar = qfalse; + float distThreshold; + float dist; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + if ( VectorCompare( impactPos, vec3_origin ) ) + {//never checked ShotEntity this frame, so must do a trace... + trace_t tr; + //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; + vec3_t forward, end; + AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 8192, forward, end ); + trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, impactPos ); + } + + //see if impact would be too close to me + distThreshold = 16384/*128*128*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 65536/*256*256*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 65536/*256*256*/; + } + break; + default: + break; + } + + dist = DistanceSquared( impactPos, muzzle ); + + if ( dist < distThreshold ) + {//impact would be too close to me + tooClose = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 || + (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 )) + {//we've haven't seen them in the last 5 seconds + //see if it's too far from where he is + distThreshold = 65536/*256*256*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 262144/*512*512*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 262144/*512*512*/; + } + break; + default: + break; + } + dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation ); + if ( dist > distThreshold ) + {//impact would be too far from enemy + tooFar = qtrue; + } + } + + if ( !tooClose && !tooFar ) + {//okay too shoot at last pos + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + VectorNormalize( dir ); + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot = qtrue; + faceEnemy = qfalse; + } + } + } + } + } + + //FIXME: don't shoot right away! + if ( NPC->client->ps.weaponTime > 0 ) + { + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER ) + { + if ( !enemyLOS || !enemyCS ) + {//cancel it + NPC->client->ps.weaponTime = 0; + } + else + {//delay our next attempt + TIMER_Set( NPC, "nextAttackDelay", Q_irand( 500, 1000 ) ); + } + } + } + else if ( shoot ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "nextAttackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + //NASTY + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER + && (ucmd.buttons&BUTTON_ATTACK) + && !Q_irand( 0, 3 ) ) + {//every now and then, shoot a homing rocket + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + NPC->client->ps.weaponTime = Q_irand( 500, 1500 ); + } + } + } + } +} + +void Jedi_Cloak( gentity_t *self ) +{ + if ( self ) + { + self->flags |= FL_NOTARGET; + if ( self->client ) + { + if ( !self->client->ps.powerups[PW_CLOAKED] ) + {//cloak + self->client->ps.powerups[PW_CLOAKED] = Q3_INFINITE; + + //FIXME: debounce attacks? + //FIXME: temp sound + G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/chars/shadowtrooper/cloak.wav") ); + } + } + } +} + +void Jedi_Decloak( gentity_t *self ) +{ + if ( self ) + { + self->flags &= ~FL_NOTARGET; + if ( self->client ) + { + if ( self->client->ps.powerups[PW_CLOAKED] ) + {//Uncloak + self->client->ps.powerups[PW_CLOAKED] = 0; + + G_Sound( self, CHAN_ITEM, G_SoundIndex("sound/chars/shadowtrooper/decloak.wav") ); + } + } + } +} + +void Jedi_CheckCloak( void ) +{ + if ( NPC && NPC->client && NPC->client->NPC_class == CLASS_SHADOWTROOPER ) + { + if ( !NPC->client->ps.saberHolstered || + NPC->health <= 0 || + NPC->client->ps.saberInFlight || + // (NPC->client->ps.eFlags&EF_FORCE_GRIPPED) || + // (NPC->client->ps.eFlags&EF_FORCE_DRAINED) || + NPC->painDebounceTime > level.time ) + {//can't be cloaked if saber is on, or dead or saber in flight or taking pain or being gripped + Jedi_Decloak( NPC ); + } + else if ( NPC->health > 0 + && !NPC->client->ps.saberInFlight + // && !(NPC->client->ps.eFlags&EF_FORCE_GRIPPED) + // && !(NPC->client->ps.eFlags&EF_FORCE_DRAINED) + && NPC->painDebounceTime < level.time ) + {//still alive, have saber in hand, not taking pain and not being gripped + Jedi_Cloak( NPC ); + } + } +} +/* +========================================================================================== +AGGRESSION +========================================================================================== +*/ +static void Jedi_Aggression( gentity_t *self, int change ) +{ + int upper_threshold, lower_threshold; + + self->NPC->stats.aggression += change; + + //FIXME: base this on initial NPC stats + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + {//good guys are less aggressive + upper_threshold = 7; + lower_threshold = 1; + } + else + {//bad guys are more aggressive + if ( self->client->NPC_class == CLASS_DESANN ) + { + upper_threshold = 20; + lower_threshold = 5; + } + else + { + upper_threshold = 10; + lower_threshold = 3; + } + } + + if ( self->NPC->stats.aggression > upper_threshold ) + { + self->NPC->stats.aggression = upper_threshold; + } + else if ( self->NPC->stats.aggression < lower_threshold ) + { + self->NPC->stats.aggression = lower_threshold; + } + //Com_Printf( "(%d) %s agg %d change: %d\n", level.time, self->NPC_type, self->NPC->stats.aggression, change ); +} + +static void Jedi_AggressionErosion( int amt ) +{ + if ( TIMER_Done( NPC, "roamTime" ) ) + {//the longer we're not alerted and have no enemy, the more our aggression goes down + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + Jedi_Aggression( NPC, amt ); + } + + if ( NPCInfo->stats.aggression < 4 || (NPCInfo->stats.aggression < 6&&NPC->client->NPC_class == CLASS_DESANN)) + {//turn off the saber + WP_DeactivateSaber( NPC, qfalse ); + } +} + +void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy ) +{ + float healthAggression; + float weaponAggression; + int newAggression; + + switch( enemy->s.weapon ) + { + case WP_SABER: + healthAggression = (float)self->health/200.0f*6.0f; + weaponAggression = 7;//go after him + break; + case WP_BLASTER: + if ( DistanceSquared( self->r.currentOrigin, enemy->r.currentOrigin ) < 65536 )//256 squared + { + healthAggression = (float)self->health/200.0f*8.0f; + weaponAggression = 8;//go after him + } + else + { + healthAggression = 8.0f - ((float)self->health/200.0f*8.0f); + weaponAggression = 2;//hang back for a second + } + break; + default: + healthAggression = (float)self->health/200.0f*8.0f; + weaponAggression = 6;//approach + break; + } + //Average these with current aggression + newAggression = ceil( (healthAggression + weaponAggression + (float)self->NPC->stats.aggression )/3.0f); + //Com_Printf( "(%d) new agg %d - new enemy\n", level.time, newAggression ); + Jedi_Aggression( self, newAggression - self->NPC->stats.aggression ); + + //don't taunt right away + TIMER_Set( self, "chatter", Q_irand( 4000, 7000 ) ); +} + +static void Jedi_Rage( void ) +{ + Jedi_Aggression( NPC, 10 - NPCInfo->stats.aggression + Q_irand( -2, 2 ) ); + TIMER_Set( NPC, "roamTime", 0 ); + TIMER_Set( NPC, "chatter", 0 ); + TIMER_Set( NPC, "walking", 0 ); + TIMER_Set( NPC, "taunting", 0 ); + TIMER_Set( NPC, "jumpChaseDebounce", 0 ); + TIMER_Set( NPC, "movenone", 0 ); + TIMER_Set( NPC, "movecenter", 0 ); + TIMER_Set( NPC, "noturn", 0 ); + ForceRage( NPC ); +} + +void Jedi_RageStop( gentity_t *self ) +{ + if ( self->NPC ) + {//calm down and back off + TIMER_Set( self, "roamTime", 0 ); + Jedi_Aggression( self, Q_irand( -5, 0 ) ); + } +} +/* +========================================================================================== +SPEAKING +========================================================================================== +*/ + +static qboolean Jedi_BattleTaunt( void ) +{ + if ( TIMER_Done( NPC, "chatter" ) + && !Q_irand( 0, 3 ) + && NPCInfo->blockedSpeechDebounceTime < level.time + && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time ) + { + int event = -1; + if ( NPC->client->playerTeam == NPCTEAM_PLAYER + && NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI ) + {//a jedi fighting a jedi - training + if ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) + {//only trainer taunts + event = EV_TAUNT1; + } + } + else + {//reborn or a jedi fighting an enemy + event = Q_irand( EV_TAUNT1, EV_TAUNT3 ); + } + if ( event != -1 ) + { + G_AddVoiceEvent( NPC, event, 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 6000; + TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) ); + + if ( NPC->enemy && NPC->enemy->NPC && NPC->enemy->s.weapon == WP_SABER && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_JEDI ) + {//Have the enemy jedi say something in response when I'm done? + } + return qtrue; + } + } + return qfalse; +} + +/* +========================================================================================== +MOVEMENT +========================================================================================== +*/ +static qboolean Jedi_ClearPathToSpot( vec3_t dest, int impactEntNum ) +{ + trace_t trace; + vec3_t mins, start, end, dir; + float dist, drop; + float i; + + //Offset the step height + VectorSet( mins, NPC->r.mins[0], NPC->r.mins[1], NPC->r.mins[2] + STEPSIZE ); + + trap_Trace( &trace, NPC->r.currentOrigin, mins, NPC->r.maxs, dest, NPC->s.number, NPC->clipmask ); + + //Do a simple check + if ( trace.allsolid || trace.startsolid ) + {//inside solid + return qfalse; + } + + if ( trace.fraction < 1.0f ) + {//hit something + if ( impactEntNum != ENTITYNUM_NONE && trace.entityNum == impactEntNum ) + {//hit what we're going after + return qtrue; + } + else + { + return qfalse; + } + } + + //otherwise, clear path in a straight line. + //Now at intervals of my size, go along the trace and trace down STEPSIZE to make sure there is a solid floor. + VectorSubtract( dest, NPC->r.currentOrigin, dir ); + dist = VectorNormalize( dir ); + if ( dest[2] > NPC->r.currentOrigin[2] ) + {//going up, check for steps + drop = STEPSIZE; + } + else + {//going down or level, check for moderate drops + drop = 64; + } + for ( i = NPC->r.maxs[0]*2; i < dist; i += NPC->r.maxs[0]*2 ) + {//FIXME: does this check the last spot, too? We're assuming that should be okay since the enemy is there? + VectorMA( NPC->r.currentOrigin, i, dir, start ); + VectorCopy( start, end ); + end[2] -= drop; + trap_Trace( &trace, start, mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask );//NPC->r.mins? + if ( trace.fraction < 1.0f || trace.allsolid || trace.startsolid ) + {//good to go + continue; + } + //no floor here! (or a long drop?) + return qfalse; + } + //we made it! + return qtrue; +} + +qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset ) +{ + vec3_t forward, right, testPos, angles, mins; + trace_t trace; + float fwdDist, rtDist; + float bottom_max = -STEPSIZE*4 - 1; + + if ( !forwardmove && !rightmove ) + {//not even moving + //Com_Printf( "%d skipping walk-cliff check (not moving)\n", level.time ); + return qtrue; + } + + if ( ucmd.upmove > 0 || NPC->client->ps.fd.forceJumpCharge ) + {//Going to jump + //Com_Printf( "%d skipping walk-cliff check (going to jump)\n", level.time ); + return qtrue; + } + + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//in the air + //Com_Printf( "%d skipping walk-cliff check (in air)\n", level.time ); + return qtrue; + } + /* + if ( fabs( AngleDelta( NPC->r.currentAngles[YAW], NPCInfo->desiredYaw ) ) < 5.0 )//!ucmd.angles[YAW] ) + {//Not turning much, don't do this + //NOTE: Should this not happen only if you're not turning AT ALL? + // You could be turning slowly but moving fast, so that would + // still let you walk right off a cliff... + //NOTE: Or maybe it is a good idea to ALWAYS do this, regardless + // of whether ot not we're turning? But why would we be walking + // straight into a wall or off a cliff unless we really wanted to? + return; + } + */ + + //FIXME: to really do this right, we'd have to actually do a pmove to predict where we're + //going to be... maybe this should be a flag and pmove handles it and sets a flag so AI knows + //NEXT frame? Or just incorporate current velocity, runspeed and possibly friction? + VectorCopy( NPC->r.mins, mins ); + mins[2] += STEPSIZE; + angles[PITCH] = angles[ROLL] = 0; + angles[YAW] = NPC->client->ps.viewangles[YAW];//Add ucmd.angles[YAW]? + AngleVectors( angles, forward, right, NULL ); + fwdDist = ((float)forwardmove)/2.0f; + rtDist = ((float)rightmove)/2.0f; + VectorMA( NPC->r.currentOrigin, fwdDist, forward, testPos ); + VectorMA( testPos, rtDist, right, testPos ); + trap_Trace( &trace, NPC->r.currentOrigin, mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + if ( trace.allsolid || trace.startsolid ) + {//hmm, trace started inside this brush... how do we decide if we should continue? + //FIXME: what do we do if we start INSIDE a CONTENTS_BOTCLIP? Try the trace again without that in the clipmask? + if ( reset ) + { + trace.fraction = 1.0f; + } + VectorCopy( testPos, trace.endpos ); + //return qtrue; + } + if ( trace.fraction < 0.6 ) + {//Going to bump into something very close, don't move, just turn + if ( (NPC->enemy && trace.entityNum == NPC->enemy->s.number) || (NPCInfo->goalEntity && trace.entityNum == NPCInfo->goalEntity->s.number) ) + {//okay to bump into enemy or goal + //Com_Printf( "%d bump into enemy/goal okay\n", level.time ); + return qtrue; + } + else if ( reset ) + {//actually want to screw with the ucmd + //Com_Printf( "%d avoiding walk into wall (entnum %d)\n", level.time, trace.entityNum ); + ucmd.forwardmove = 0; + ucmd.rightmove = 0; + VectorClear( NPC->client->ps.moveDir ); + } + return qfalse; + } + + if ( NPCInfo->goalEntity ) + { + if ( NPCInfo->goalEntity->r.currentOrigin[2] < NPC->r.currentOrigin[2] ) + {//goal is below me, okay to step off at least that far plus stepheight + bottom_max += NPCInfo->goalEntity->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + } + } + VectorCopy( trace.endpos, testPos ); + testPos[2] += bottom_max; + + trap_Trace( &trace, trace.endpos, mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask ); + + //FIXME:Should we try to see if we can still get to our goal using the waypoint network from this trace.endpos? + //OR: just put NPC clip brushes on these edges (still fall through when die) + + if ( trace.allsolid || trace.startsolid ) + {//Not going off a cliff + //Com_Printf( "%d walk off cliff okay (droptrace in solid)\n", level.time ); + return qtrue; + } + + if ( trace.fraction < 1.0 ) + {//Not going off a cliff + //FIXME: what if plane.normal is sloped? We'll slide off, not land... plus this doesn't account for slide-movement... + //Com_Printf( "%d walk off cliff okay will hit entnum %d at dropdist of %4.2f\n", level.time, trace.entityNum, (trace.fraction*bottom_max) ); + return qtrue; + } + + //going to fall at least bottom_max, don't move, just turn... is this bad, though? What if we want them to drop off? + if ( reset ) + {//actually want to screw with the ucmd + //Com_Printf( "%d avoiding walk off cliff\n", level.time ); + ucmd.forwardmove *= -1.0;//= 0; + ucmd.rightmove *= -1.0;//= 0; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + } + return qfalse; +} +/* +------------------------- +Jedi_HoldPosition +------------------------- +*/ + +static void Jedi_HoldPosition( void ) +{ + //NPCInfo->squadState = SQUAD_STAND_AND_SHOOT; + NPCInfo->goalEntity = NULL; + + /* + if ( TIMER_Done( NPC, "stand" ) ) + { + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +/* +------------------------- +Jedi_Move +------------------------- +*/ + +static void Jedi_Move( gentity_t *goal, qboolean retreat ) +{ + qboolean moved; + navInfo_t info; + + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = goal; + + moved = NPC_MoveToGoal( qtrue ); + + //FIXME: temp retreat behavior- should really make this toward a safe spot or maybe to outflank enemy + if ( retreat ) + {//FIXME: should we trace and make sure we can go this way? Or somehow let NPC_MoveToGoal know we want to retreat and have it handle it? + ucmd.forwardmove *= -1; + ucmd.rightmove *= -1; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + } + + //Get the move info + NAV_GetLastMove( &info ); + + //If we hit our target, then stop and fire! + if ( ( info.flags & NIF_COLLISION ) && ( info.blocker == NPC->enemy ) ) + { + Jedi_HoldPosition(); + } + + //If our move failed, then reset + if ( moved == qfalse ) + { + Jedi_HoldPosition(); + } +} + +static qboolean Jedi_Hunt( void ) +{ + //Com_Printf( "Hunting\n" ); + //if we're at all interested in fighting, go after him + if ( NPCInfo->stats.aggression > 1 ) + {//approach enemy + NPCInfo->combatMove = qtrue; + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + else + { + if ( NPCInfo->goalEntity == NULL ) + {//hunt + NPCInfo->goalEntity = NPC->enemy; + } + //Jedi_Move( NPC->enemy, qfalse ); + if ( NPC_MoveToGoal( qfalse ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + } + return qfalse; +} + +/* +static qboolean Jedi_Track( void ) +{ + //if we're at all interested in fighting, go after him + if ( NPCInfo->stats.aggression > 1 ) + {//approach enemy + NPCInfo->combatMove = qtrue; + NPC_SetMoveGoal( NPC, NPCInfo->enemyLastSeenLocation, 16, qtrue ); + if ( NPC_MoveToGoal( qfalse ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + return qfalse; +} +*/ + +static void Jedi_Retreat( void ) +{ + if ( !TIMER_Done( NPC, "noRetreat" ) ) + {//don't actually move + return; + } + //FIXME: when retreating, we should probably see if we can retreat + //in the direction we want. If not...? Evade? + //Com_Printf( "Retreating\n" ); + Jedi_Move( NPC->enemy, qtrue ); +} + +static void Jedi_Advance( void ) +{ + if ( !NPC->client->ps.saberInFlight ) + { + //NPC->client->ps.SaberActivate(); + WP_ActivateSaber(NPC); + } + //Com_Printf( "Advancing\n" ); + Jedi_Move( NPC->enemy, qfalse ); + + //TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) ); + //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); + //TIMER_Set( NPC, "duck", 0 ); +} + +static void Jedi_AdjustSaberAnimLevel( gentity_t *self, int newLevel ) +{ + if ( !self || !self->client ) + { + return; + } + //FIXME: each NPC shold have a unique pattern of behavior for the order in which they + if ( self->client->NPC_class == CLASS_TAVION ) + {//special attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_5; + return; + } + else if ( self->client->NPC_class == CLASS_DESANN ) + {//special attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_4; + return; + } + if ( self->client->playerTeam == NPCTEAM_ENEMY ) + { + if ( self->NPC->rank == RANK_CIVILIAN || self->NPC->rank == RANK_LT_JG ) + {//grunt and fencer always uses quick attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1; + return; + } + if ( self->NPC->rank == RANK_CREWMAN + || self->NPC->rank == RANK_ENSIGN ) + {//acrobat & force-users always use medium attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_2; + return; + } + /* + if ( self->NPC->rank == RANK_LT ) + {//boss always uses strong attacks + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_3; + return; + } + */ + } + //use the different attacks, how often they switch and under what circumstances + if ( newLevel > self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] ) + {//cap it + self->client->ps.fd.saberAnimLevel = self->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]; + } + else if ( newLevel < FORCE_LEVEL_1 ) + { + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1; + } + else + {//go ahead and set it + self->client->ps.fd.saberAnimLevel = newLevel; + } + + if ( d_JediAI.integer ) + { + switch ( self->client->ps.fd.saberAnimLevel ) + { + case FORCE_LEVEL_1: + Com_Printf( S_COLOR_GREEN"%s Saber Attack Set: fast\n", self->NPC_type ); + break; + case FORCE_LEVEL_2: + Com_Printf( S_COLOR_YELLOW"%s Saber Attack Set: medium\n", self->NPC_type ); + break; + case FORCE_LEVEL_3: + Com_Printf( S_COLOR_RED"%s Saber Attack Set: strong\n", self->NPC_type ); + break; + } + } +} + +static void Jedi_CheckDecreaseSaberAnimLevel( void ) +{ + if ( !NPC->client->ps.weaponTime && !(ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + {//not attacking + if ( TIMER_Done( NPC, "saberLevelDebounce" ) && !Q_irand( 0, 10 ) ) + { + //Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );//drop + Jedi_AdjustSaberAnimLevel( NPC, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ));//random + TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 3000, 10000 ) ); + } + } + else + { + TIMER_Set( NPC, "saberLevelDebounce", Q_irand( 1000, 5000 ) ); + } +} + +static void Jedi_CombatDistance( int enemy_dist ) +{//FIXME: for many of these checks, what we really want is horizontal distance to enemy + if ( NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//when gripping, don't move + return; + } + else if ( !TIMER_Done( NPC, "gripping" ) ) + {//stopped gripping, clear timers just in case + TIMER_Set( NPC, "gripping", -level.time ); + TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) ); + } + + if ( NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_DRAIN] > FORCE_LEVEL_1 ) + {//when draining, don't move + return; + } + else if ( !TIMER_Done( NPC, "draining" ) ) + {//stopped draining, clear timers just in case + TIMER_Set( NPC, "draining", -level.time ); + TIMER_Set( NPC, "attackDelay", Q_irand( 0, 1000 ) ); + } + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( !TIMER_Done( NPC, "flameTime" ) ) + { + if ( enemy_dist > 50 ) + { + Jedi_Advance(); + } + else if ( enemy_dist <= 0 ) + { + Jedi_Retreat(); + } + } + else if ( enemy_dist < 200 ) + { + Jedi_Retreat(); + } + else if ( enemy_dist > 1024 ) + { + Jedi_Advance(); + } + } + else if ( NPC->client->ps.saberInFlight && + !PM_SaberInBrokenParry( NPC->client->ps.saberMove ) + && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//maintain distance + if ( enemy_dist < NPC->client->ps.saberEntityDist ) + { + Jedi_Retreat(); + } + else if ( enemy_dist > NPC->client->ps.saberEntityDist && enemy_dist > 100 ) + { + Jedi_Advance(); + } + if ( NPC->client->ps.weapon == WP_SABER //using saber + && NPC->client->ps.saberEntityState == SES_LEAVING //not returning yet + && NPC->client->ps.fd.forcePowerLevel[FP_SABERTHROW] > FORCE_LEVEL_1 //2nd or 3rd level lightsaber + && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//hold it out there + ucmd.buttons |= BUTTON_ALT_ATTACK; + //FIXME: time limit? + } + } + else if ( !TIMER_Done( NPC, "taunting" ) ) + { + if ( enemy_dist <= 64 ) + {//he's getting too close + ucmd.buttons |= BUTTON_ATTACK; + if ( !NPC->client->ps.saberInFlight ) + { + WP_ActivateSaber(NPC); + } + TIMER_Set( NPC, "taunting", -level.time ); + } + //else if ( NPC->client->ps.torsoAnim == BOTH_GESTURE1 && NPC->client->ps.torsoTimer < 2000 ) + else if (NPC->client->ps.forceHandExtend == HANDEXTEND_JEDITAUNT && (NPC->client->ps.forceHandExtendTime - level.time) < 200) + {//we're almost done with our special taunt + //FIXME: this doesn't always work, for some reason + if ( !NPC->client->ps.saberInFlight ) + { + WP_ActivateSaber(NPC); + } + } + } + else if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON ) + {//we won a saber lock, press the advantage + if ( enemy_dist > 0 ) + {//get closer so we can hit! + Jedi_Advance(); + } + if ( enemy_dist > 128 ) + {//lost 'em + NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON; + } + if ( NPC->enemy->painDebounceTime + 2000 < level.time ) + {//the window of opportunity is gone + NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON; + } + //don't strafe? + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + } + else if ( NPC->enemy->client + && NPC->enemy->s.weapon == WP_SABER + && NPC->enemy->client->ps.saberLockTime > level.time + && NPC->client->ps.saberLockTime < level.time ) + {//enemy is in a saberLock and we are not + if ( enemy_dist < 64 ) + {//FIXME: maybe just pick another enemy? + Jedi_Retreat(); + } + } + /* + else if ( NPC->enemy->s.weapon == WP_TURRET + && !Q_stricmp( "PAS", NPC->enemy->classname ) + && NPC->enemy->s.apos.trType == TR_STATIONARY ) + { + int testlevel; + if ( enemy_dist > forcePushPullRadius[FORCE_LEVEL_1] - 16 ) + { + Jedi_Advance(); + } + if ( NPC->client->ps.fd.forcePowerLevel[FP_PUSH] < FORCE_LEVEL_1 ) + {// + testlevel = FORCE_LEVEL_1; + } + else + { + testlevel = NPC->client->ps.fd.forcePowerLevel[FP_PUSH]; + } + if ( enemy_dist < forcePushPullRadius[testlevel] - 16 ) + {//close enough to push + if ( InFront( NPC->enemy->r.currentOrigin, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, 0.6f ) ) + {//knock it down + WP_KnockdownTurret( NPC, NPC->enemy ); + //do the forcethrow call just for effect + ForceThrow( NPC, qfalse ); + } + } + } + */ + //rwwFIXMEFIXME: Give them the ability to do this again (turret needs to be fixed up to allow it) + else if ( enemy_dist <= 64 + && ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||(!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,10))) ) + {//can't use saber and they're in striking range + if ( !Q_irand( 0, 5 ) && InFront( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.2f ) ) + { + if ( ((NPCInfo->scriptFlags&SCF_DONT_FIRE)||NPC->client->pers.maxHealth - NPC->health > NPC->client->pers.maxHealth*0.25f)//lost over 1/4 of our health or not firing + && NPC->client->ps.fd.forcePowersKnown&(1<client->pers.maxHealth - NPC->health > NPC->client->pers.maxHealth*0.25f//lost over 1/4 of our health + && NPC->client->ps.fd.forcePowersKnown&(1<enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, 0.2f ) ) + { + TIMER_Set( NPC, "draining", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + Jedi_Advance(); + return; + } + else if ( enemy_dist <= -16 ) + {//we're too damn close! + Jedi_Retreat(); + } + else if ( enemy_dist <= 0 ) + {//we're within striking range + //if we are attacking, see if we should stop + if ( NPCInfo->stats.aggression < 4 ) + {//back off and defend + Jedi_Retreat(); + } + } + else if ( enemy_dist > 256 ) + {//we're way out of range + qboolean usedForce = qfalse; + if ( NPCInfo->stats.aggression < Q_irand( 0, 20 ) + && NPC->health < NPC->client->pers.maxHealth*0.75f + && !Q_irand( 0, 2 ) ) + { + if ( (NPC->client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1< 384 ) + {//FIXME: check for enemy facing away and/or moving away + if ( !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time ) + { + if ( NPC_ClearLOS4( NPC->enemy ) ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_JCHASE1, EV_JCHASE3 ), 3000 ); + } + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + } + //Unless we're totally hiding, go after him + if ( NPCInfo->stats.aggression > 0 ) + {//approach enemy + if ( !usedForce ) + { + Jedi_Advance(); + } + } + } + /* + else if ( enemy_dist < 96 && NPC->enemy && NPC->enemy->client && NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//too close and in air, so retreat + Jedi_Retreat(); + } + */ + //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade.... + else if ( enemy_dist > 50 )//FIXME: not hardcoded- base on our reach (modelScale?) and saberLengthMax + {//we're out of striking range and we are allowed to attack + //first, check some tactical force power decisions + if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.fd.forceGripBeingGripped > level.time) ) + {//They're being gripped, rush them! + if ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//far away or allowed to use saber + Jedi_Advance(); + } + } + } + if ( NPCInfo->rank >= RANK_LT_JG + && !Q_irand( 0, 5 ) + && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//throw saber + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + } + else if ( NPC->enemy && NPC->enemy->client && //valid enemy + NPC->enemy->client->ps.saberInFlight && /*NPC->enemy->client->ps.saber[0].Active()*/ NPC->enemy->client->ps.saberEntityNum && //enemy throwing saber + NPC->client->ps.weaponTime <= 0 && //I'm not busy + WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) && //I can use the power + !Q_irand( 0, 10 ) && //don't do it all the time, averages to 1 check a second + Q_irand( 0, 6 ) < g_spskill.integer && //more likely on harder diff + Q_irand( RANK_CIVILIAN, RANK_CAPTAIN ) < NPCInfo->rank )//more likely against harder enemies + {//They're throwing their saber, grip them! + //taunt + if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + TIMER_Set( NPC, "chatter", 3000 ); + } + + //grip + TIMER_Set( NPC, "gripping", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + } + else + { + int chanceScale; + + if ( NPC->enemy && NPC->enemy->client && (NPC->enemy->client->ps.fd.forcePowersActive&(1<enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//far away or allowed to use saber + Jedi_Advance(); + } + } + } + } + chanceScale = 0; + if ( NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPC->NPC_type) ) + { + chanceScale = 1; + } + else if ( NPCInfo->rank == RANK_ENSIGN ) + { + chanceScale = 2; + } + else if ( NPCInfo->rank >= RANK_LT_JG ) + { + chanceScale = 5; + } + if ( chanceScale + && (enemy_dist > Q_irand( 100, 200 ) || (NPCInfo->scriptFlags&SCF_DONT_FIRE) || (!Q_stricmp("Yoda",NPC->NPC_type)&&!Q_irand(0,3)) ) + && enemy_dist < 500 + && (Q_irand( 0, chanceScale*10 )<5 || (NPC->enemy->client && NPC->enemy->client->ps.weapon != WP_SABER && !Q_irand( 0, chanceScale ) ) ) ) + {//else, randomly try some kind of attack every now and then + if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && !Q_irand( 0, 1 ) ) + { + if ( WP_ForcePowerAvailable( NPC, FP_PULL, 0 ) && !Q_irand( 0, 2 ) ) + { + //force pull the guy to me! + //FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]] + ForceThrow( NPC, qtrue ); + //maybe strafe too? + TIMER_Set( NPC, "duck", enemy_dist*3 ); + if ( Q_irand( 0, 1 ) ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + } + else if ( WP_ForcePowerAvailable( NPC, FP_LIGHTNING, 0 ) && Q_irand( 0, 1 ) ) + { + ForceLightning( NPC ); + if ( NPC->client->ps.fd.forcePowerLevel[FP_LIGHTNING] > FORCE_LEVEL_1 ) + { + NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill.integer*500) ); + TIMER_Set( NPC, "holdLightning", NPC->client->ps.weaponTime ); + } + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + } + /*else if ( NPC->health < NPC->client->pers.maxHealth * 0.75f + && Q_irand( FORCE_LEVEL_0, NPC->client->ps.fd.forcePowerLevel[FP_DRAIN] ) > FORCE_LEVEL_1 + && WP_ForcePowerAvailable( NPC, FP_DRAIN, 0 ) + && Q_irand( 0, 1 ) ) + { + ForceDrain2( NPC ); + NPC->client->ps.weaponTime = Q_irand( 1000, 3000+(g_spskill.integer*500) ); + TIMER_Set( NPC, "draining", NPC->client->ps.weaponTime ); + TIMER_Set( NPC, "attackDelay", NPC->client->ps.weaponTime ); + }*/ + //rwwFIXMEFIXME: After new drain stuff from SP is in re-enable this. + else if ( WP_ForcePowerAvailable( NPC, FP_GRIP, 0 ) ) + { + //taunt + if ( TIMER_Done( NPC, "chatter" ) && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && NPCInfo->blockedSpeechDebounceTime < level.time ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_TAUNT1, EV_TAUNT3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + TIMER_Set( NPC, "chatter", 3000 ); + } + + //grip + TIMER_Set( NPC, "gripping", 3000 ); + TIMER_Set( NPC, "attackDelay", 3000 ); + } + else + { + if ( WP_ForcePowerAvailable( NPC, FP_SABERTHROW, 0 ) + && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//throw saber + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + } + } + else + { + if ( NPCInfo->rank >= RANK_LT_JG + && !(NPC->client->ps.fd.forcePowersActive&(1 << FP_SPEED)) + && !(NPC->client->ps.saberEventFlags&SEF_INWATER) )//saber not in water + {//throw saber + ucmd.buttons |= BUTTON_ALT_ATTACK; + } + } + } + //see if we should advance now + else if ( NPCInfo->stats.aggression > 5 ) + {//approach enemy + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + if ( enemy_dist > 200 || !(NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//far away or allowed to use saber + Jedi_Advance(); + } + } + } + } + else + {//maintain this distance? + //walk? + } + } + } + else + {//we're not close enough to attack, but not far enough away to be safe + if ( NPCInfo->stats.aggression < 4 ) + {//back off and defend + Jedi_Retreat(); + } + else if ( NPCInfo->stats.aggression > 5 ) + {//try to get closer + if ( enemy_dist > 0 && !(NPCInfo->scriptFlags&SCF_DONT_FIRE)) + {//we're allowed to use our lightsaber, get closer + if ( TIMER_Done( NPC, "parryTime" ) || NPCInfo->rank > RANK_LT ) + {//not parrying + if ( !NPC->enemy->client || NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//they're on the ground, so advance + Jedi_Advance(); + } + } + } + } + else + {//agression is 4 or 5... somewhere in the middle + //what do we do here? Nothing? + //Move forward and back? + } + } + //if really really mad, rage! + if ( NPCInfo->stats.aggression > Q_irand( 5, 15 ) + && NPC->health < NPC->client->pers.maxHealth*0.75f + && !Q_irand( 0, 2 ) ) + { + if ( (NPC->client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<client->ps.saberEventFlags&SEF_LOCK_WON && NPC->enemy && NPC->enemy->painDebounceTime > level.time ) + {//don't strafe if pressing the advantage of winning a saberLock + return qfalse; + } + if ( TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) ) + { + qboolean strafed = qfalse; + //TODO: make left/right choice a tactical decision rather than random: + // try to keep own back away from walls and ledges, + // try to keep enemy's back to a ledge or wall + // Maybe try to strafe toward designer-placed "safe spots" or "goals"? + int strafeTime = Q_irand( strafeTimeMin, strafeTimeMax ); + + if ( Q_irand( 0, 1 ) ) + { + if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) ) + { + TIMER_Set( NPC, "strafeLeft", strafeTime ); + strafed = qtrue; + } + else if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) ) + { + TIMER_Set( NPC, "strafeRight", strafeTime ); + strafed = qtrue; + } + } + else + { + if ( NPC_MoveDirClear( ucmd.forwardmove, 127, qfalse ) ) + { + TIMER_Set( NPC, "strafeRight", strafeTime ); + strafed = qtrue; + } + else if ( NPC_MoveDirClear( ucmd.forwardmove, -127, qfalse ) ) + { + TIMER_Set( NPC, "strafeLeft", strafeTime ); + strafed = qtrue; + } + } + + if ( strafed ) + { + TIMER_Set( NPC, "noStrafe", strafeTime + Q_irand( nextStrafeTimeMin, nextStrafeTimeMax ) ); + if ( walking ) + {//should be a slow strafe + TIMER_Set( NPC, "walking", strafeTime ); + } + return qtrue; + } + } + return qfalse; +} + +/* +static void Jedi_FaceEntity( gentity_t *self, gentity_t *other, qboolean doPitch ) +{ + vec3_t entPos; + vec3_t muzzle; + + //Get the positions + CalcEntitySpot( other, SPOT_ORIGIN, entPos ); + + //Get the positions + CalcEntitySpot( self, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD + + //Find the desired angles + vec3_t angles; + + GetAnglesForDirection( muzzle, entPos, angles ); + + self->NPC->desiredYaw = AngleNormalize360( angles[YAW] ); + if ( doPitch ) + { + self->NPC->desiredPitch = AngleNormalize360( angles[PITCH] ); + } +} +*/ + +/* +qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ) + +Jedi will play a dodge anim, blur, and make the force speed noise. + +Right now used to dodge instant-hit weapons. + +FIXME: possibly call this for saber melee evasion and/or missile evasion? +FIXME: possibly let player do this too? +*/ +//rwwFIXMEFIXME: Going to use qboolean Jedi_DodgeEvasion( gentity_t *self, gentity_t *shooter, trace_t *tr, int hitLoc ) from +//w_saber.c.. maybe use seperate one for NPCs or add cases to that one? + +evasionType_t Jedi_CheckFlipEvasions( gentity_t *self, float rightdot, float zdiff ) +{ + if ( self->NPC && (self->NPC->scriptFlags&SCF_NO_ACROBATICS) ) + { + return EVASION_NONE; + } + if ( self->client + && (self->client->ps.fd.forceRageRecoveryTime > level.time || (self->client->ps.fd.forcePowersActive&(1<client->ps.legsAnim == BOTH_WALL_RUN_LEFT || self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT ) + {//already running on a wall + vec3_t right, fwdAngles; + int anim = -1; + float animLength; + + VectorSet(fwdAngles, 0, self->client->ps.viewangles[YAW], 0); + + AngleVectors( fwdAngles, NULL, right, NULL ); + + animLength = BG_AnimLength( self->localAnimIndex, (animNumber_t)self->client->ps.legsAnim ); + if ( self->client->ps.legsAnim == BOTH_WALL_RUN_LEFT && rightdot < 0 ) + {//I'm running on a wall to my left and the attack is on the left + if ( animLength - self->client->ps.legsTimer > 400 + && self->client->ps.legsTimer > 400 ) + {//not at the beginning or end of the anim + anim = BOTH_WALL_RUN_LEFT_FLIP; + } + } + else if ( self->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT && rightdot > 0 ) + {//I'm running on a wall to my right and the attack is on the right + if ( animLength - self->client->ps.legsTimer > 400 + && self->client->ps.legsTimer > 400 ) + {//not at the beginning or end of the anim + anim = BOTH_WALL_RUN_RIGHT_FLIP; + } + } + if ( anim != -1 ) + {//flip off the wall! + int parts; + //FIXME: check the direction we will flip towards for do-not-enter/walls/drops? + //NOTE: we presume there is still a wall there! + if ( anim == BOTH_WALL_RUN_LEFT_FLIP ) + { + self->client->ps.velocity[0] *= 0.5f; + self->client->ps.velocity[1] *= 0.5f; + VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity ); + } + else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP ) + { + self->client->ps.velocity[0] *= 0.5f; + self->client->ps.velocity[1] *= 0.5f; + VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity ); + } + parts = SETANIM_LEGS; + if ( !self->client->ps.weaponTime ) + { + parts = SETANIM_BOTH; + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + //rwwFIXMEFIXME: Add these pm flags? + G_AddEvent( self, EV_JUMP, 0 ); + return EVASION_OTHER; + } + } + else if ( self->client->NPC_class != CLASS_DESANN //desann doesn't do these kind of frilly acrobatics + && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT) + && Q_irand( 0, 1 ) + && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim ) + && !PM_InKnockDown( &self->client->ps ) + && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) ) + { + vec3_t fwd, right, traceto, mins, maxs, fwdAngles; + trace_t trace; + int parts, anim; + float speed, checkDist; + qboolean allowCartWheels = qtrue; + qboolean allowWallFlips = qtrue; + + if ( self->client->ps.weapon == WP_SABER ) + { + if ( self->client->saber[0].model + && self->client->saber[0].model[0] + && (self->client->saber[0].saberFlags&SFL_NO_CARTWHEELS) ) + { + allowCartWheels = qfalse; + } + else if ( self->client->saber[1].model + && self->client->saber[1].model[0] + && (self->client->saber[1].saberFlags&SFL_NO_CARTWHEELS) ) + { + allowCartWheels = qfalse; + } + if ( self->client->saber[0].model + && self->client->saber[0].model[0] + && (self->client->saber[0].saberFlags&SFL_NO_WALL_FLIPS) ) + { + allowWallFlips = qfalse; + } + else if ( self->client->saber[1].model + && self->client->saber[1].model[0] + && (self->client->saber[1].saberFlags&SFL_NO_WALL_FLIPS) ) + { + allowWallFlips = qfalse; + } + } + + VectorSet(mins, self->r.mins[0],self->r.mins[1],0); + VectorSet(maxs, self->r.maxs[0],self->r.maxs[1],24); + VectorSet(fwdAngles, 0, self->client->ps.viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, right, NULL ); + + parts = SETANIM_BOTH; + + if ( BG_SaberInAttack( self->client->ps.saberMove ) + || PM_SaberInStart( self->client->ps.saberMove ) ) + { + parts = SETANIM_LEGS; + } + if ( rightdot >= 0 ) + { + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_ARIAL_LEFT; + } + else + { + anim = BOTH_CARTWHEEL_LEFT; + } + checkDist = -128; + speed = -200; + } + else + { + if ( Q_irand( 0, 1 ) ) + { + anim = BOTH_ARIAL_RIGHT; + } + else + { + anim = BOTH_CARTWHEEL_RIGHT; + } + checkDist = 128; + speed = 200; + } + //trace in the dir that we want to go + VectorMA( self->r.currentOrigin, checkDist, right, traceto ); + trap_Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP ); + if ( trace.fraction >= 1.0f && allowCartWheels ) + {//it's clear, let's do it + //FIXME: check for drops? + vec3_t fwdAngles, jumpRt; + + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = self->client->ps.legsTimer;//don't attack again until this anim is done + VectorCopy( self->client->ps.viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + //do the flip + AngleVectors( fwdAngles, NULL, jumpRt, NULL ); + VectorScale( jumpRt, speed, self->client->ps.velocity ); + self->client->ps.fd.forceJumpCharge = 0;//so we don't play the force flip anim + self->client->ps.velocity[2] = 200; + self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height + //self->client->ps.pm_flags |= PMF_JUMPING; + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + //ucmd.upmove = 0; + return EVASION_CARTWHEEL; + } + else if ( !(trace.contents&CONTENTS_BOTCLIP) ) + {//hit a wall, not a do-not-enter brush + //FIXME: before we check any of these jump-type evasions, we should check for headroom, right? + //Okay, see if we can flip *off* the wall and go the other way + vec3_t idealNormal; + gentity_t *traceEnt; + + VectorSubtract( self->r.currentOrigin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + traceEnt = &g_entities[trace.entityNum]; + if ( (trace.entityNums.solid!=SOLID_BMODEL) || DotProduct( trace.plane.normal, idealNormal ) > 0.7f ) + {//it's a ent of some sort or it's a wall roughly facing us + float bestCheckDist = 0; + //hmm, see if we're moving forward + if ( DotProduct( self->client->ps.velocity, fwd ) < 200 ) + {//not running forward very fast + //check to see if it's okay to move the other way + if ( (trace.fraction*checkDist) <= 32 ) + {//wall on that side is close enough to wall-flip off of or wall-run on + bestCheckDist = checkDist; + checkDist *= -1.0f; + VectorMA( self->r.currentOrigin, checkDist, right, traceto ); + //trace in the dir that we want to go + trap_Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP ); + if ( trace.fraction >= 1.0f ) + {//it's clear, let's do it + if ( allowWallFlips ) + {//okay to do wall-flips with this saber + int parts; + + //FIXME: check for drops? + //turn the cartwheel into a wallflip in the other dir + if ( rightdot > 0 ) + { + anim = BOTH_WALL_FLIP_LEFT; + self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0; + VectorMA( self->client->ps.velocity, 150, right, self->client->ps.velocity ); + } + else + { + anim = BOTH_WALL_FLIP_RIGHT; + self->client->ps.velocity[0] = self->client->ps.velocity[1] = 0; + VectorMA( self->client->ps.velocity, -150, right, self->client->ps.velocity ); + } + self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + //animate me + parts = SETANIM_LEGS; + if ( !self->client->ps.weaponTime ) + { + parts = SETANIM_BOTH; + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height + //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + return EVASION_OTHER; + } + } + else + {//boxed in on both sides + if ( DotProduct( self->client->ps.velocity, fwd ) < 0 ) + {//moving backwards + return EVASION_NONE; + } + if ( (trace.fraction*checkDist) <= 32 && (trace.fraction*checkDist) < bestCheckDist ) + { + bestCheckDist = checkDist; + } + } + } + else + {//too far from that wall to flip or run off it, check other side + checkDist *= -1.0f; + VectorMA( self->r.currentOrigin, checkDist, right, traceto ); + //trace in the dir that we want to go + trap_Trace( &trace, self->r.currentOrigin, mins, maxs, traceto, self->s.number, CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BOTCLIP ); + if ( (trace.fraction*checkDist) <= 32 ) + {//wall on this side is close enough + bestCheckDist = checkDist; + } + else + {//neither side has a wall within 32 + return EVASION_NONE; + } + } + } + //Try wall run? + if ( bestCheckDist ) + {//one of the walls was close enough to wall-run on + qboolean allowWallRuns = qtrue; + if ( self->client->ps.weapon == WP_SABER ) + { + if ( self->client->saber[0].model + && self->client->saber[0].model[0] + && (self->client->saber[0].saberFlags&SFL_NO_WALL_RUNS) ) + { + allowWallRuns = qfalse; + } + else if ( self->client->saber[1].model + && self->client->saber[1].model[0] + && (self->client->saber[1].saberFlags&SFL_NO_WALL_RUNS) ) + { + allowWallRuns = qfalse; + } + } + if ( allowWallRuns ) + {//okay to do wallruns with this saber + int parts; + + //FIXME: check for long enough wall and a drop at the end? + if ( bestCheckDist > 0 ) + {//it was to the right + anim = BOTH_WALL_RUN_RIGHT; + } + else + {//it was to the left + anim = BOTH_WALL_RUN_LEFT; + } + self->client->ps.velocity[2] = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + //animate me + parts = SETANIM_LEGS; + if ( !self->client->ps.weaponTime ) + { + parts = SETANIM_BOTH; + } + NPC_SetAnim( self, parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height + //self->client->ps.pm_flags |= (PMF_JUMPING|PMF_SLOW_MO_FALL); + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_SoundOnEnt( self, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + return EVASION_OTHER; + } + } + //else check for wall in front, do backflip off wall + } + } + } + return EVASION_NONE; +} + +int Jedi_ReCalcParryTime( gentity_t *self, evasionType_t evasionType ) +{ + if ( !self->client ) + { + return 0; + } + if ( !self->s.number ) + {//player + return bg_parryDebounce[self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]]; + } + else if ( self->NPC ) + { + if ( !g_saberRealisticCombat.integer + && ( g_spskill.integer == 2 || (g_spskill.integer == 1 && self->client->NPC_class == CLASS_TAVION) ) ) + { + if ( self->client->NPC_class == CLASS_TAVION ) + { + return 0; + } + else + { + return Q_irand( 0, 150 ); + } + } + else + { + int baseTime; + if ( evasionType == EVASION_DODGE ) + { + baseTime = self->client->ps.torsoTimer; + } + else if ( evasionType == EVASION_CARTWHEEL ) + { + baseTime = self->client->ps.torsoTimer; + } + else if ( self->client->ps.saberInFlight ) + { + baseTime = Q_irand( 1, 3 ) * 50; + } + else + { + if ( g_saberRealisticCombat.integer ) + { + baseTime = 500; + + switch ( g_spskill.integer ) + { + case 0: + baseTime = 500; + break; + case 1: + baseTime = 300; + break; + case 2: + default: + baseTime = 100; + break; + } + } + else + { + baseTime = 150;//500; + + switch ( g_spskill.integer ) + { + case 0: + baseTime = 200;//500; + break; + case 1: + baseTime = 100;//300; + break; + case 2: + default: + baseTime = 50;//100; + break; + } + } + + if ( self->client->NPC_class == CLASS_TAVION ) + {//Tavion is faster + baseTime = ceil(baseTime/2.0f); + } + else if ( self->NPC->rank >= RANK_LT_JG ) + {//fencers, bosses, shadowtroopers, luke, desann, et al use the norm + if ( Q_irand( 0, 2 ) ) + {//medium speed parry + baseTime = baseTime; + } + else + {//with the occasional fast parry + baseTime = ceil(baseTime/2.0f); + } + } + else if ( self->NPC->rank == RANK_CIVILIAN ) + {//grunts are slowest + baseTime = baseTime*Q_irand(1,3); + } + else if ( self->NPC->rank == RANK_CREWMAN ) + {//acrobats aren't so bad + if ( evasionType == EVASION_PARRY + || evasionType == EVASION_DUCK_PARRY + || evasionType == EVASION_JUMP_PARRY ) + {//slower with parries + baseTime = baseTime*Q_irand(1,2); + } + else + {//faster with acrobatics + //baseTime = baseTime; + } + } + else + {//force users are kinda slow + baseTime = baseTime*Q_irand(1,2); + } + if ( evasionType == EVASION_DUCK || evasionType == EVASION_DUCK_PARRY ) + { + baseTime += 100; + } + else if ( evasionType == EVASION_JUMP || evasionType == EVASION_JUMP_PARRY ) + { + baseTime += 50; + } + else if ( evasionType == EVASION_OTHER ) + { + baseTime += 100; + } + else if ( evasionType == EVASION_FJUMP ) + { + baseTime += 100; + } + } + + return baseTime; + } + } + return 0; +} + +qboolean Jedi_QuickReactions( gentity_t *self ) +{ + if ( ( self->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) || + self->client->NPC_class == CLASS_TAVION || + (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_1&&g_spskill.integer>1) || + (self->client->ps.fd.forcePowerLevel[FP_SABER_DEFENSE]>FORCE_LEVEL_2&&g_spskill.integer>0) ) + { + return qtrue; + } + return qfalse; +} + +qboolean Jedi_SaberBusy( gentity_t *self ) +{ + if ( self->client->ps.torsoTimer > 300 + && ( (BG_SaberInAttack( self->client->ps.saberMove )&&self->client->ps.fd.saberAnimLevel==FORCE_LEVEL_3) + || BG_SpinningSaberAnim( self->client->ps.torsoAnim ) + || BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) + //|| PM_SaberInBounce( self->client->ps.saberMove ) + || PM_SaberInBrokenParry( self->client->ps.saberMove ) + //|| PM_SaberInDeflect( self->client->ps.saberMove ) + || BG_FlippingAnim( self->client->ps.torsoAnim ) + || PM_RollingAnim( self->client->ps.torsoAnim ) ) ) + {//my saber is not in a parrying position + return qtrue; + } + return qfalse; +} + +/* +------------------------- +Jedi_SaberBlock + +Pick proper block anim + +FIXME: Based on difficulty level/enemy saber combat skill, make this decision-making more/less effective + +NOTE: always blocking projectiles in this func! + +------------------------- +*/ +extern qboolean G_FindClosestPointOnLineSegment( const vec3_t start, const vec3_t end, const vec3_t from, vec3_t result ); +evasionType_t Jedi_SaberBlockGo( gentity_t *self, usercmd_t *cmd, vec3_t pHitloc, vec3_t phitDir, gentity_t *incoming, float dist ) //dist = 0.0f +{ + vec3_t hitloc, hitdir, diff, fwdangles={0,0,0}, right; + float rightdot; + float zdiff; + int duckChance = 0; + int dodgeAnim = -1; + qboolean saberBusy = qfalse, evaded = qfalse, doDodge = qfalse; + evasionType_t evasionType = EVASION_NONE; + + //FIXME: if we don't have our saber in hand, pick the force throw option or a jump or strafe! + //FIXME: reborn don't block enough anymore + if ( !incoming ) + { + VectorCopy( pHitloc, hitloc ); + VectorCopy( phitDir, hitdir ); + //FIXME: maybe base this on rank some? And/or g_spskill? + if ( self->client->ps.saberInFlight ) + {//DOH! do non-saber evasion! + saberBusy = qtrue; + } + else if ( Jedi_QuickReactions( self ) ) + {//jedi trainer and tavion are must faster at parrying and can do it whenever they like + //Also, on medium, all level 3 people can parry any time and on hard, all level 2 or 3 people can parry any time + } + else + { + saberBusy = Jedi_SaberBusy( self ); + } + } + else + { + if ( incoming->s.weapon == WP_SABER ) + {//flying lightsaber, face it! + //FIXME: for this to actually work, we'd need to call update angles too? + //Jedi_FaceEntity( self, incoming, qtrue ); + } + VectorCopy( incoming->r.currentOrigin, hitloc ); + VectorNormalize2( incoming->s.pos.trDelta, hitdir ); + } + if ( self->client && self->client->NPC_class == CLASS_BOBAFETT ) + { + saberBusy = qtrue; + } + + VectorSubtract( hitloc, self->client->renderInfo.eyePoint, diff ); + diff[2] = 0; + //VectorNormalize( diff ); + fwdangles[1] = self->client->ps.viewangles[1]; + // Ultimately we might care if the shot was ahead or behind, but for now, just quadrant is fine. + AngleVectors( fwdangles, NULL, right, NULL ); + + rightdot = DotProduct(right, diff);// + flrand(-0.10f,0.10f); + //totalHeight = self->client->renderInfo.eyePoint[2] - self->r.absmin[2]; + zdiff = hitloc[2] - self->client->renderInfo.eyePoint[2];// + Q_irand(-6,6); + + //see if we can dodge if need-be + if ( (dist>16&&(Q_irand( 0, 2 )||saberBusy)) + || self->client->ps.saberInFlight + || BG_SabersOff( &self->client->ps ) + || self->client->NPC_class == CLASS_BOBAFETT ) + {//either it will miss by a bit (and 25% chance) OR our saber is not in-hand OR saber is off + if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank >= RANK_LT_JG) ) + {//acrobat or fencer or above + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE &&//on the ground + !(self->client->ps.pm_flags&PMF_DUCKED)&&cmd->upmove>=0&&TIMER_Done( self, "duck" )//not ducking + && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim )//not rolling + && !PM_InKnockDown( &self->client->ps )//not knocked down + && ( self->client->ps.saberInFlight || + self->client->NPC_class == CLASS_BOBAFETT || + (!BG_SaberInAttack( self->client->ps.saberMove )//not attacking + && !PM_SaberInStart( self->client->ps.saberMove )//not starting an attack + && !BG_SpinningSaberAnim( self->client->ps.torsoAnim )//not in a saber spin + && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ))//not in a special attack + ) + ) + {//need to check all these because it overrides both torso and legs with the dodge + doDodge = qtrue; + } + } + } + // Figure out what quadrant the block was in. + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) evading attack from height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, hitloc[2]-self->r.absmin[2],zdiff,rightdot); + } + + //UL = > -1//-6 + //UR = > -6//-9 + //TOP = > +6//+4 + //FIXME: take FP_SABER_DEFENSE into account here somehow? + if ( zdiff >= -5 )//was 0 + { + if ( incoming || !saberBusy ) + { + if ( rightdot > 12 + || (rightdot > 3 && zdiff < 5) + || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, 0.3 + {//coming from right + if ( doDodge ) + { + if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) ) + {//roll! + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + evasionType = EVASION_DUCK; + evaded = qtrue; + } + else if ( Q_irand( 0, 1 ) ) + { + dodgeAnim = BOTH_DODGE_FL; + } + else + { + dodgeAnim = BOTH_DODGE_BL; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + evasionType = EVASION_PARRY; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + if ( zdiff > 5 ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK_PARRY; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + } + else + { + duckChance = 6; + } + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "UR block\n" ); + } + } + else if ( rightdot < -12 + || (rightdot < -3 && zdiff < 5) + || (!incoming&&fabs(hitdir[2])<0.25f) )//was normalized, -0.3 + {//coming from left + if ( doDodge ) + { + if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) ) + {//roll! + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeLeft", 0 ); + evasionType = EVASION_DUCK; + evaded = qtrue; + } + else if ( Q_irand( 0, 1 ) ) + { + dodgeAnim = BOTH_DODGE_FR; + } + else + { + dodgeAnim = BOTH_DODGE_BR; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + evasionType = EVASION_PARRY; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + if ( zdiff > 5 ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK_PARRY; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + } + else + { + duckChance = 6; + } + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "UL block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_TOP; + evasionType = EVASION_PARRY; + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + duckChance = 4; + } + if ( d_JediAI.integer ) + { + Com_Printf( "TOP block\n" ); + } + } + evaded = qtrue; + } + else + { + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + //duckChance = 2; + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + } + } + } + //LL = -22//= -18 to -39 + //LR = -23//= -20 to -41 + else if ( zdiff > -22 )//was-15 ) + { + if ( 1 )//zdiff < -10 ) + {//hmm, pretty low, but not low enough to use the low block, so we need to duck + if ( self->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + //duckChance = 2; + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + } + else + {//in air! Ducking does no good + } + } + if ( incoming || !saberBusy ) + { + if ( rightdot > 8 || (rightdot > 3 && zdiff < -11) )//was normalized, 0.2 + { + if ( doDodge ) + { + if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) ) + {//roll! + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + } + else + { + dodgeAnim = BOTH_DODGE_L; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_RIGHT; + if ( evasionType == EVASION_DUCK ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_PARRY; + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "mid-UR block\n" ); + } + } + else if ( rightdot < -8 || (rightdot < -3 && zdiff < -11) )//was normalized, -0.2 + { + if ( doDodge ) + { + if ( self->client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 2 ) ) + {//roll! + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + } + else + { + dodgeAnim = BOTH_DODGE_R; + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_UPPER_LEFT; + if ( evasionType == EVASION_DUCK ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_PARRY; + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "mid-UL block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_TOP; + if ( evasionType == EVASION_DUCK ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_PARRY; + } + if ( d_JediAI.integer ) + { + Com_Printf( "mid-TOP block\n" ); + } + } + evaded = qtrue; + } + } + else if ( saberBusy || (zdiff < -36 && ( zdiff < -44 || !Q_irand( 0, 2 ) ) ) )//was -30 and -40//2nd one was -46 + {//jump! + if ( self->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//already in air, duck to pull up legs + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + evasionType = EVASION_DUCK; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "legs up\n" ); + } + if ( incoming || !saberBusy ) + { + //since the jump may be cleared if not safe, set a lower block too + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + evasionType = EVASION_DUCK_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "LR block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + evasionType = EVASION_DUCK_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "LL block\n" ); + } + } + evaded = qtrue; + } + } + else + {//gotta jump! + if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) && + (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) ) + {//superjump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.fd.forceRageRecoveryTime < level.time + && !(self->client->ps.fd.forcePowersActive&(1<client->ps ) ) + { + self->client->ps.fd.forceJumpCharge = 320;//FIXME: calc this intelligently + evasionType = EVASION_FJUMP; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "force jump + " ); + } + } + } + else + {//normal jump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.fd.forceRageRecoveryTime < level.time + && !(self->client->ps.fd.forcePowersActive&(1<client->NPC_class == CLASS_BOBAFETT && !Q_irand( 0, 1 ) ) + {//roll! + if ( rightdot > 0 ) + { + TIMER_Start( self, "strafeLeft", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeRight", 0 ); + TIMER_Set( self, "walking", 0 ); + } + else + { + TIMER_Start( self, "strafeRight", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "strafeLeft", 0 ); + TIMER_Set( self, "walking", 0 ); + } + } + else + { + if ( self == NPC ) + { + cmd->upmove = 127; + } + else + { + self->client->ps.velocity[2] = JUMP_VELOCITY; + } + } + evasionType = EVASION_JUMP; + evaded = qtrue; + if ( d_JediAI.integer ) + { + Com_Printf( "jump + " ); + } + } + if ( self->client->NPC_class == CLASS_TAVION ) + { + if ( !incoming + && self->client->ps.groundEntityNum < ENTITYNUM_NONE + && !Q_irand( 0, 2 ) ) + { + if ( !BG_SaberInAttack( self->client->ps.saberMove ) + && !PM_SaberInStart( self->client->ps.saberMove ) + && !BG_InRoll( &self->client->ps, self->client->ps.legsAnim ) + && !PM_InKnockDown( &self->client->ps ) + && !BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) ) + {//do the butterfly! + int butterflyAnim; + if ( Q_irand( 0, 1 ) ) + { + butterflyAnim = BOTH_BUTTERFLY_LEFT; + } + else + { + butterflyAnim = BOTH_BUTTERFLY_RIGHT; + } + evasionType = EVASION_CARTWHEEL; + NPC_SetAnim( self, SETANIM_BOTH, butterflyAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.velocity[2] = 225; + self->client->ps.fd.forceJumpZStart = self->r.currentOrigin[2];//so we don't take damage if we land at same height + // self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + // self->client->ps.SaberActivateTrail( 300 );//FIXME: reset this when done! + //Ah well. No hacking from the server for now. + if ( self->client->NPC_class == CLASS_BOBAFETT ) + { + G_AddEvent( self, EV_JUMP, 0 ); + } + else + { + G_Sound( self, CHAN_BODY, G_SoundIndex("sound/weapons/force/jump.wav") ); + } + cmd->upmove = 0; + saberBusy = qtrue; + evaded = qtrue; + } + } + } + } + if ( ((evasionType = Jedi_CheckFlipEvasions( self, rightdot, zdiff ))!=EVASION_NONE) ) + { + if ( d_slowmodeath.integer > 5 && self->enemy && !self->enemy->s.number ) + { + G_StartMatrixEffect( self ); + } + saberBusy = qtrue; + evaded = qtrue; + } + else if ( incoming || !saberBusy ) + { + //since the jump may be cleared if not safe, set a lower block too + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + if ( evasionType == EVASION_JUMP ) + { + evasionType = EVASION_JUMP_PARRY; + } + else if ( evasionType == EVASION_NONE ) + { + evasionType = EVASION_PARRY; + } + if ( d_JediAI.integer ) + { + Com_Printf( "LR block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + if ( evasionType == EVASION_JUMP ) + { + evasionType = EVASION_JUMP_PARRY; + } + else if ( evasionType == EVASION_NONE ) + { + evasionType = EVASION_PARRY; + } + if ( d_JediAI.integer ) + { + Com_Printf( "LL block\n" ); + } + } + evaded = qtrue; + } + } + } + else + { + if ( incoming || !saberBusy ) + { + if ( rightdot >= 0 ) + { + self->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + evasionType = EVASION_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "LR block\n" ); + } + } + else + { + self->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + evasionType = EVASION_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "LL block\n" ); + } + } + if ( incoming && incoming->s.weapon == WP_SABER ) + {//thrown saber! + if ( self->NPC && (self->NPC->rank == RANK_CREWMAN || self->NPC->rank > RANK_LT_JG ) && + (!Q_irand( 0, 10 ) || (!Q_irand( 0, 2 ) && (cmd->forwardmove || cmd->rightmove))) ) + {//superjump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.fd.forceRageRecoveryTime < level.time + && !(self->client->ps.fd.forcePowersActive&(1<client->ps ) ) + { + self->client->ps.fd.forceJumpCharge = 320;//FIXME: calc this intelligently + evasionType = EVASION_FJUMP; + if ( d_JediAI.integer ) + { + Com_Printf( "force jump + " ); + } + } + } + else + {//normal jump + //FIXME: check the jump, if can't, then block + if ( self->NPC + && !(self->NPC->scriptFlags&SCF_NO_ACROBATICS) + && self->client->ps.fd.forceRageRecoveryTime < level.time + && !(self->client->ps.fd.forcePowersActive&(1<upmove = 127; + } + else + { + self->client->ps.velocity[2] = JUMP_VELOCITY; + } + evasionType = EVASION_JUMP_PARRY; + if ( d_JediAI.integer ) + { + Com_Printf( "jump + " ); + } + } + } + } + evaded = qtrue; + } + } + + if ( evasionType == EVASION_NONE ) + { + return EVASION_NONE; + } + //stop taunting + TIMER_Set( self, "taunting", 0 ); + //stop gripping + TIMER_Set( self, "gripping", -level.time ); + WP_ForcePowerStop( self, FP_GRIP ); + //stop draining + TIMER_Set( self, "draining", -level.time ); + WP_ForcePowerStop( self, FP_DRAIN ); + + if ( dodgeAnim != -1 ) + {//dodged + evasionType = EVASION_DODGE; + NPC_SetAnim( self, SETANIM_BOTH, dodgeAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = self->client->ps.torsoTimer; + //force them to stop moving in this case + self->client->ps.pm_time = self->client->ps.torsoTimer; + //FIXME: maybe make a sound? Like a grunt? EV_JUMP? + self->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + //dodged, not block + if ( d_slowmodeath.integer > 5 && self->enemy && !self->enemy->s.number ) + { + G_StartMatrixEffect( self ); + } + } + else + { + if ( duckChance ) + { + if ( !Q_irand( 0, duckChance ) ) + { + TIMER_Start( self, "duck", Q_irand( 500, 1500 ) ); + if ( evasionType == EVASION_PARRY ) + { + evasionType = EVASION_DUCK_PARRY; + } + else + { + evasionType = EVASION_DUCK; + } + /* + if ( d_JediAI.integer ) + { + Com_Printf( "duck " ); + } + */ + } + } + + if ( incoming ) + { + self->client->ps.saberBlocked = WP_MissileBlockForBlock( self->client->ps.saberBlocked ); + } + + } + //if ( self->client->ps.saberBlocked != BLOCKED_NONE ) + { + int parryReCalcTime = Jedi_ReCalcParryTime( self, evasionType ); + if ( self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] < level.time + parryReCalcTime ) + { + self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + parryReCalcTime; + } + } + return evasionType; +} + +extern float ShortestLineSegBewteen2LineSegs( vec3_t start1, vec3_t end1, vec3_t start2, vec3_t end2, vec3_t close_pnt1, vec3_t close_pnt2 ); +extern int WPDEBUG_SaberColor( saber_colors_t saberColor ); +static qboolean Jedi_SaberBlock( int saberNum, int bladeNum ) //saberNum = 0, bladeNum = 0 +{ + vec3_t hitloc, saberTipOld, saberTip, top, bottom, axisPoint, saberPoint, dir;//saberBase, + vec3_t pointDir, baseDir, tipDir, saberHitPoint, saberMins, saberMaxs; + float pointDist, baseDirPerc, dist; + float bladeLen = 0; + trace_t tr; + evasionType_t evasionType; + + //FIXME: reborn don't block enough anymore + /* + //maybe do this on easy only... or only on grunt-level reborn + if ( NPC->client->ps.weaponTime ) + {//i'm attacking right now + return qfalse; + } + */ + + if ( !TIMER_Done( NPC, "parryReCalcTime" ) ) + {//can't do our own re-think of which parry to use yet + return qfalse; + } + + if ( NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] > level.time ) + {//can't move the saber to another position yet + return qfalse; + } + + /* + if ( NPCInfo->rank < RANK_LT_JG && Q_irand( 0, (2 - g_spskill.integer) ) ) + {//lower rank reborn have a random chance of not doing it at all + NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 300; + return qfalse; + } + */ + + if ( NPC->enemy->health <= 0 || !NPC->enemy->client ) + {//don't keep blocking him once he's dead (or if not a client) + return qfalse; + } + /* + //VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip ); + //VectorMA( NPC->enemy->client->renderInfo.muzzlePointNext, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirNext, saberTipNext ); + VectorMA( NPC->enemy->client->renderInfo.muzzlePointOld, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDirOld, saberTipOld ); + VectorMA( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->ps.saberLength, NPC->enemy->client->renderInfo.muzzleDir, saberTip ); + + VectorSubtract( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->renderInfo.muzzlePointOld, dir );//get the dir + VectorAdd( dir, NPC->enemy->client->renderInfo.muzzlePoint, saberBase );//extrapolate + + VectorSubtract( saberTip, saberTipOld, dir );//get the dir + VectorAdd( dir, saberTip, saberTipOld );//extrapolate + + VectorCopy( NPC->r.currentOrigin, top ); + top[2] = NPC->r.absmax[2]; + VectorCopy( NPC->r.currentOrigin, bottom ); + bottom[2] = NPC->r.absmin[2]; + + float dist = ShortestLineSegBewteen2LineSegs( saberBase, saberTipOld, bottom, top, saberPoint, axisPoint ); + if ( 0 )//dist > NPC->r.maxs[0]*4 )//was *3 + {//FIXME: sometimes he reacts when you're too far away to actually hit him + if ( d_JediAI.integer ) + { + Com_Printf( "enemy saber dist: %4.2f\n", dist ); + } + TIMER_Set( NPC, "parryTime", -1 ); + return qfalse; + } + + //get the actual point of impact + trace_t tr; + trap_Trace( &tr, saberPoint, vec3_origin, vec3_origin, axisPoint, NPC->enemy->s.number, MASK_SHOT, G2_RETURNONHIT, 10 ); + if ( tr.allsolid || tr.startsolid ) + {//estimate + VectorSubtract( saberPoint, axisPoint, dir ); + VectorNormalize( dir ); + VectorMA( axisPoint, NPC->r.maxs[0]*1.22, dir, hitloc ); + } + else + { + VectorCopy( tr.endpos, hitloc ); + } + */ + VectorSet(saberMins,-4,-4,-4); + VectorSet(saberMaxs,4,4,4); + + VectorMA( NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzlePointOld, NPC->enemy->client->saber[saberNum].blade[bladeNum].length, NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzleDirOld, saberTipOld ); + VectorMA( NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzlePoint, NPC->enemy->client->saber[saberNum].blade[bladeNum].length, NPC->enemy->client->saber[saberNum].blade[bladeNum].muzzleDir, saberTip ); +// VectorCopy(NPC->enemy->client->lastSaberBase_Always, muzzlePoint); +// VectorMA(muzzlePoint, GAME_SABER_LENGTH, NPC->enemy->client->lastSaberDir_Always, saberTip); +// VectorCopy(saberTip, saberTipOld); + + VectorCopy( NPC->r.currentOrigin, top ); + top[2] = NPC->r.absmax[2]; + VectorCopy( NPC->r.currentOrigin, bottom ); + bottom[2] = NPC->r.absmin[2]; + + dist = ShortestLineSegBewteen2LineSegs( NPC->enemy->client->renderInfo.muzzlePoint, saberTip, bottom, top, saberPoint, axisPoint ); + if ( dist > NPC->r.maxs[0]*5 )//was *3 + {//FIXME: sometimes he reacts when you're too far away to actually hit him + if ( d_JediAI.integer ) + { + Com_Printf( S_COLOR_RED"enemy saber dist: %4.2f\n", dist ); + } + /* + if ( dist < 300 //close + && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves + && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me + {//he's swinging at me and close enough to be a threat, don't start an attack right now + TIMER_Set( NPC, "parryTime", 100 ); + } + else + */ + { + TIMER_Set( NPC, "parryTime", -1 ); + } + return qfalse; + } + if ( d_JediAI.integer ) + { + Com_Printf( S_COLOR_GREEN"enemy saber dist: %4.2f\n", dist ); + } + + VectorSubtract( saberPoint, NPC->enemy->client->renderInfo.muzzlePoint, pointDir ); + pointDist = VectorLength( pointDir ); + + bladeLen = NPC->enemy->client->saber[saberNum].blade[bladeNum].length; + + if ( bladeLen <= 0 ) + { + baseDirPerc = 0.5f; + } + else + { + baseDirPerc = pointDist/bladeLen; + } + VectorSubtract( NPC->enemy->client->renderInfo.muzzlePoint, NPC->enemy->client->renderInfo.muzzlePointOld, baseDir ); + VectorSubtract( saberTip, saberTipOld, tipDir ); + VectorScale( baseDir, baseDirPerc, baseDir ); + VectorMA( baseDir, 1.0f-baseDirPerc, tipDir, dir ); + VectorMA( saberPoint, 200, dir, hitloc ); + + //get the actual point of impact + trap_Trace( &tr, saberPoint, saberMins, saberMaxs, hitloc, NPC->enemy->s.number, CONTENTS_BODY );//, G2_RETURNONHIT, 10 ); + if ( tr.allsolid || tr.startsolid || tr.fraction >= 1.0f ) + {//estimate + vec3_t dir2Me; + VectorSubtract( axisPoint, saberPoint, dir2Me ); + dist = VectorNormalize( dir2Me ); + if ( DotProduct( dir, dir2Me ) < 0.2f ) + {//saber is not swinging in my direction + /* + if ( dist < 300 //close + && !Jedi_QuickReactions( NPC )//quick reaction people can interrupt themselves + && (PM_SaberInStart( NPC->enemy->client->ps.saberMove ) || BG_SaberInAttack( NPC->enemy->client->ps.saberMove )) )//enemy is swinging at me + {//he's swinging at me and close enough to be a threat, don't start an attack right now + TIMER_Set( NPC, "parryTime", 100 ); + } + else + */ + { + TIMER_Set( NPC, "parryTime", -1 ); + } + return qfalse; + } + ShortestLineSegBewteen2LineSegs( saberPoint, hitloc, bottom, top, saberHitPoint, hitloc ); + /* + VectorSubtract( saberPoint, axisPoint, dir ); + VectorNormalize( dir ); + VectorMA( axisPoint, NPC->r.maxs[0]*1.22, dir, hitloc ); + */ + } + else + { + VectorCopy( tr.endpos, hitloc ); + } + + if ( d_JediAI.integer ) + { + //G_DebugLine( saberPoint, hitloc, FRAMETIME, WPDEBUG_SaberColor( NPC->enemy->client->ps.saber[saberNum].blade[bladeNum].color ), qtrue ); + G_TestLine(saberPoint, hitloc, 0x0000ff, FRAMETIME); + } + + //FIXME: if saber is off and/or we have force speed and want to be really cocky, + // and the swing misses by some amount, we can use the dodges here... :) + if ( (evasionType=Jedi_SaberBlockGo( NPC, &ucmd, hitloc, dir, NULL, dist )) != EVASION_DODGE ) + {//we did block (not dodge) + int parryReCalcTime; + + if ( !NPC->client->ps.saberInFlight ) + {//make sure saber is on + WP_ActivateSaber(NPC); + } + + //debounce our parry recalc time + parryReCalcTime = Jedi_ReCalcParryTime( NPC, evasionType ); + TIMER_Set( NPC, "parryReCalcTime", Q_irand( 0, parryReCalcTime ) ); + if ( d_JediAI.integer ) + { + Com_Printf( "Keep parry choice until: %d\n", level.time + parryReCalcTime ); + } + + //determine how long to hold this anim + if ( TIMER_Done( NPC, "parryTime" ) ) + { + if ( NPC->client->NPC_class == CLASS_TAVION ) + { + TIMER_Set( NPC, "parryTime", Q_irand( parryReCalcTime/2, parryReCalcTime*1.5 ) ); + } + else if ( NPCInfo->rank >= RANK_LT_JG ) + {//fencers and higher hold a parry less + TIMER_Set( NPC, "parryTime", parryReCalcTime ); + } + else + {//others hold it longer + TIMER_Set( NPC, "parryTime", Q_irand( 1, 2 )*parryReCalcTime ); + } + } + } + else + { + int dodgeTime = NPC->client->ps.torsoTimer; + if ( NPCInfo->rank > RANK_LT_COMM && NPC->client->NPC_class != CLASS_DESANN ) + {//higher-level guys can dodge faster + dodgeTime -= 200; + } + TIMER_Set( NPC, "parryReCalcTime", dodgeTime ); + TIMER_Set( NPC, "parryTime", dodgeTime ); + } + return qtrue; +} +/* +------------------------- +Jedi_EvasionSaber + +defend if other is using saber and attacking me! +------------------------- +*/ +static void Jedi_EvasionSaber( vec3_t enemy_movedir, float enemy_dist, vec3_t enemy_dir ) +{ + vec3_t dirEnemy2Me; + int evasionChance = 30;//only step aside 30% if he's moving at me but not attacking + qboolean enemy_attacking = qfalse; + qboolean throwing_saber = qfalse; + qboolean shooting_lightning = qfalse; + + if ( !NPC->enemy->client ) + { + return; + } + else if ( NPC->enemy->client + && NPC->enemy->s.weapon == WP_SABER + && NPC->enemy->client->ps.saberLockTime > level.time ) + {//don't try to block/evade an enemy who is in a saberLock + return; + } + else if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON && NPC->enemy->painDebounceTime > level.time ) + {//pressing the advantage of winning a saber lock + return; + } + + if ( NPC->enemy->client->ps.saberInFlight && !TIMER_Done( NPC, "taunting" ) ) + {//if he's throwing his saber, stop taunting + TIMER_Set( NPC, "taunting", -level.time ); + if ( !NPC->client->ps.saberInFlight ) + { + WP_ActivateSaber(NPC); + } + } + + if ( TIMER_Done( NPC, "parryTime" ) ) + { + if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE && + NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//wasn't blocked myself + NPC->client->ps.saberBlocked = BLOCKED_NONE; + } + } + + if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING ) + { + if ( !NPC->client->ps.saberInFlight && Jedi_SaberBlock(0, 0) ) + { + return; + } + } + + VectorSubtract( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, dirEnemy2Me ); + VectorNormalize( dirEnemy2Me ); + + if ( NPC->enemy->client->ps.weaponTime && NPC->enemy->client->ps.weaponstate == WEAPON_FIRING ) + {//enemy is attacking + enemy_attacking = qtrue; + evasionChance = 90; + } + + if ( (NPC->enemy->client->ps.fd.forcePowersActive&(1<enemy->client->ps.saberInFlight && NPC->enemy->client->ps.saberEntityNum != ENTITYNUM_NONE && NPC->enemy->client->ps.saberEntityState != SES_RETURNING ) + {//enemy is shooting lightning + enemy_attacking = qtrue; + throwing_saber = qtrue; + } + + //FIXME: this needs to take skill and rank(reborn type) into account much more + if ( Q_irand( 0, 100 ) < evasionChance ) + {//check to see if he's coming at me + float facingAmt; + if ( VectorCompare( enemy_movedir, vec3_origin ) || shooting_lightning || throwing_saber ) + {//he's not moving (or he's using a ranged attack), see if he's facing me + vec3_t enemy_fwd; + AngleVectors( NPC->enemy->client->ps.viewangles, enemy_fwd, NULL, NULL ); + facingAmt = DotProduct( enemy_fwd, dirEnemy2Me ); + } + else + {//he's moving + facingAmt = DotProduct( enemy_movedir, dirEnemy2Me ); + } + + if ( flrand( 0.25, 1 ) < facingAmt ) + {//coming at/facing me! + int whichDefense = 0; + if ( NPC->client->ps.weaponTime || NPC->client->ps.saberInFlight || NPC->client->NPC_class == CLASS_BOBAFETT ) + {//I'm attacking or recovering from a parry, can only try to strafe/jump right now + if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression ) + { + return; + } + whichDefense = 100; + } + else + { + if ( shooting_lightning ) + {//check for lightning attack + //only valid defense is strafe and/or jump + whichDefense = 100; + } + else if ( throwing_saber ) + {//he's thrown his saber! See if it's coming at me + float saberDist; + vec3_t saberDir2Me; + vec3_t saberMoveDir; + gentity_t *saber = &g_entities[NPC->enemy->client->ps.saberEntityNum]; + VectorSubtract( NPC->r.currentOrigin, saber->r.currentOrigin, saberDir2Me ); + saberDist = VectorNormalize( saberDir2Me ); + VectorCopy( saber->s.pos.trDelta, saberMoveDir ); + VectorNormalize( saberMoveDir ); + if ( !Q_irand( 0, 3 ) ) + { + //Com_Printf( "(%d) raise agg - enemy threw saber\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 ) + {//it's heading towards me + if ( saberDist < 100 ) + {//it's close + whichDefense = Q_irand( 3, 6 ); + } + else if ( saberDist < 200 ) + {//got some time, yet, try pushing + whichDefense = Q_irand( 0, 8 ); + } + } + } + if ( whichDefense ) + {//already chose one + } + else if ( enemy_dist > 80 || !enemy_attacking ) + {//he's pretty far, or not swinging, just strafe + if ( VectorCompare( enemy_movedir, vec3_origin ) ) + {//if he's not moving, not swinging and far enough away, no evasion necc. + return; + } + if ( Q_irand( 0, 10 ) < NPCInfo->stats.aggression ) + { + return; + } + whichDefense = 100; + } + else + {//he's getting close and swinging at me + vec3_t fwd; + //see if I'm facing him + AngleVectors( NPC->client->ps.viewangles, fwd, NULL, NULL ); + if ( DotProduct( enemy_dir, fwd ) < 0.5 ) + {//I'm not really facing him, best option is to strafe + whichDefense = Q_irand( 5, 16 ); + } + else if ( enemy_dist < 56 ) + {//he's very close, maybe we should be more inclined to block or throw + whichDefense = Q_irand( NPCInfo->stats.aggression, 12 ); + } + else + { + whichDefense = Q_irand( 2, 16 ); + } + } + } + + if ( whichDefense >= 4 && whichDefense <= 12 ) + {//would try to block + if ( NPC->client->ps.saberInFlight ) + {//can't, saber in not in hand, so fall back to strafe/jump + whichDefense = 100; + } + } + + switch( whichDefense ) + { + case 0: + case 1: + case 2: + case 3: + //use jedi force push? + //FIXME: try to do this if health low or enemy back to a cliff? + if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && TIMER_Done( NPC, "parryTime" ) ) + {//FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]] + ForceThrow( NPC, qfalse ); + } + break; + case 4: + case 5: + case 6: + case 7: + case 8: + case 9: + case 10: + case 11: + case 12: + //try to parry the blow + //Com_Printf( "blocking\n" ); + Jedi_SaberBlock(0, 0); + break; + default: + //Evade! + //start a strafe left/right if not already + if ( !Q_irand( 0, 5 ) || !Jedi_Strafe( 300, 1000, 0, 1000, qfalse ) ) + {//certain chance they will pick an alternative evasion + //if couldn't strafe, try a different kind of evasion... + if ( shooting_lightning || throwing_saber || enemy_dist < 80 ) + { + //FIXME: force-jump+forward - jump over the guy! + if ( shooting_lightning || (!Q_irand( 0, 2 ) && NPCInfo->stats.aggression < 4 && TIMER_Done( NPC, "parryTime" ) ) ) + { + if ( (NPCInfo->rank == RANK_ENSIGN || NPCInfo->rank > RANK_LT_JG) && !shooting_lightning && Q_irand( 0, 2 ) ) + {//FIXME: check forcePushRadius[NPC->client->ps.fd.forcePowerLevel[FP_PUSH]] + ForceThrow( NPC, qfalse ); + } + else if ( (NPCInfo->rank==RANK_CREWMAN||NPCInfo->rank>RANK_LT_JG) + && !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS) + && NPC->client->ps.fd.forceRageRecoveryTime < level.time + && !(NPC->client->ps.fd.forcePowersActive&(1<client->ps ) ) + {//FIXME: make this a function call? + //FIXME: check for clearance, safety of landing spot? + NPC->client->ps.fd.forceJumpCharge = 480; + //Don't jump again for another 2 to 5 seconds + TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) ); + if ( Q_irand( 0, 2 ) ) + { + ucmd.forwardmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + else + { + ucmd.forwardmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + //FIXME: if this jump is cleared, we can't block... so pick a random lower block? + if ( Q_irand( 0, 1 ) )//FIXME: make intelligent + { + NPC->client->ps.saberBlocked = BLOCKED_LOWER_RIGHT; + } + else + { + NPC->client->ps.saberBlocked = BLOCKED_LOWER_LEFT; + } + } + } + else if ( enemy_attacking ) + { + Jedi_SaberBlock(0, 0); + } + } + } + else + {//strafed + if ( d_JediAI.integer ) + { + Com_Printf( "def strafe\n" ); + } + if ( !(NPCInfo->scriptFlags&SCF_NO_ACROBATICS) + && NPC->client->ps.fd.forceRageRecoveryTime < level.time + && !(NPC->client->ps.fd.forcePowersActive&(1<rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG ) + && !PM_InKnockDown( &NPC->client->ps ) + && !Q_irand( 0, 5 ) ) + {//FIXME: make this a function call? + //FIXME: check for clearance, safety of landing spot? + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + NPC->client->ps.fd.forceJumpCharge = 280;//FIXME: calc this intelligently? + } + else + { + NPC->client->ps.fd.forceJumpCharge = 320; + } + //Don't jump again for another 2 to 5 seconds + TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) ); + } + } + break; + } + + //turn off slow walking no matter what + TIMER_Set( NPC, "walking", -level.time ); + TIMER_Set( NPC, "taunting", -level.time ); + } + } +} +/* +------------------------- +Jedi_Flee +------------------------- +*/ +/* + +static qboolean Jedi_Flee( void ) +{ + return qfalse; +} +*/ + + +/* +========================================================================================== +INTERNAL AI ROUTINES +========================================================================================== +*/ +gentity_t *Jedi_FindEnemyInCone( gentity_t *self, gentity_t *fallback, float minDot ) +{ + vec3_t forward, mins, maxs, dir; + float dist, bestDist = Q3_INFINITE; + gentity_t *enemy = fallback; + gentity_t *check = NULL; + int entityList[MAX_GENTITIES]; + int e, numListedEntities; + trace_t tr; + + if ( !self->client ) + { + return enemy; + } + + AngleVectors( self->client->ps.viewangles, forward, NULL, NULL ); + + for ( e = 0 ; e < 3 ; e++ ) + { + mins[e] = self->r.currentOrigin[e] - 1024; + maxs[e] = self->r.currentOrigin[e] + 1024; + } + numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( e = 0 ; e < numListedEntities ; e++ ) + { + check = &g_entities[entityList[e]]; + if ( check == self ) + {//me + continue; + } + if ( !(check->inuse) ) + {//freed + continue; + } + if ( !check->client ) + {//not a client - FIXME: what about turrets? + continue; + } + if ( check->client->playerTeam != self->client->enemyTeam ) + {//not an enemy - FIXME: what about turrets? + continue; + } + if ( check->health <= 0 ) + {//dead + continue; + } + + if ( !trap_InPVS( check->r.currentOrigin, self->r.currentOrigin ) ) + {//can't potentially see them + continue; + } + + VectorSubtract( check->r.currentOrigin, self->r.currentOrigin, dir ); + dist = VectorNormalize( dir ); + + if ( DotProduct( dir, forward ) < minDot ) + {//not in front + continue; + } + + //really should have a clear LOS to this thing... + trap_Trace( &tr, self->r.currentOrigin, vec3_origin, vec3_origin, check->r.currentOrigin, self->s.number, MASK_SHOT ); + if ( tr.fraction < 1.0f && tr.entityNum != check->s.number ) + {//must have clear shot + continue; + } + + if ( dist < bestDist ) + {//closer than our last best one + dist = bestDist; + enemy = check; + } + } + return enemy; +} + +static void Jedi_SetEnemyInfo( vec3_t enemy_dest, vec3_t enemy_dir, float *enemy_dist, vec3_t enemy_movedir, float *enemy_movespeed, int prediction ) +{ + if ( !NPC || !NPC->enemy ) + {//no valid enemy + return; + } + if ( !NPC->enemy->client ) + { + VectorClear( enemy_movedir ); + *enemy_movespeed = 0; + VectorCopy( NPC->enemy->r.currentOrigin, enemy_dest ); + enemy_dest[2] += NPC->enemy->r.mins[2] + 24;//get it's origin to a height I can work with + VectorSubtract( enemy_dest, NPC->r.currentOrigin, enemy_dir ); + //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade.... + *enemy_dist = VectorNormalize( enemy_dir );// - (NPC->client->ps.saberLengthMax + NPC->r.maxs[0]*1.5 + 16); + } + else + {//see where enemy is headed + VectorCopy( NPC->enemy->client->ps.velocity, enemy_movedir ); + *enemy_movespeed = VectorNormalize( enemy_movedir ); + //figure out where he'll be, say, 3 frames from now + VectorMA( NPC->enemy->r.currentOrigin, *enemy_movespeed * 0.001 * prediction, enemy_movedir, enemy_dest ); + //figure out what dir the enemy's estimated position is from me and how far from the tip of my saber he is + VectorSubtract( enemy_dest, NPC->r.currentOrigin, enemy_dir );//NPC->client->renderInfo.muzzlePoint + //FIXME: enemy_dist calc needs to include all blade lengths, and include distance from hand to start of blade.... + *enemy_dist = VectorNormalize( enemy_dir ) - (NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5 + 16); //just use the blade 0 len I guess + //FIXME: keep a group of enemies around me and use that info to make decisions... + // For instance, if there are multiple enemies, evade more, push them away + // and use medium attacks. If enemies are using blasters, switch to fast. + // If one jedi enemy, use strong attacks. Use grip when fighting one or + // two enemies, use lightning spread when fighting multiple enemies, etc. + // Also, when kill one, check rest of group instead of walking up to victim. + } +} + +extern float WP_SpeedOfMissileForWeapon( int wp, qboolean alt_fire ); +static void Jedi_FaceEnemy( qboolean doPitch ) +{ + vec3_t enemy_eyes, eyes, angles; + + if ( NPC == NULL ) + return; + + if ( NPC->enemy == NULL ) + return; + + if ( NPC->client->ps.fd.forcePowersActive & (1<client->ps.fd.forcePowerLevel[FP_GRIP] > FORCE_LEVEL_1 ) + {//don't update? + NPCInfo->desiredPitch = NPC->client->ps.viewangles[PITCH]; + NPCInfo->desiredYaw = NPC->client->ps.viewangles[YAW]; + return; + } + CalcEntitySpot( NPC, SPOT_HEAD, eyes ); + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_eyes ); + + if ( NPC->client->NPC_class == CLASS_BOBAFETT + && TIMER_Done( NPC, "flameTime" ) + && NPC->s.weapon != WP_NONE + && NPC->s.weapon != WP_DISRUPTOR + && (NPC->s.weapon != WP_ROCKET_LAUNCHER||!(NPCInfo->scriptFlags&SCF_ALT_FIRE)) + && NPC->s.weapon != WP_THERMAL + && NPC->s.weapon != WP_TRIP_MINE + && NPC->s.weapon != WP_DET_PACK + && NPC->s.weapon != WP_STUN_BATON + /*&& NPC->s.weapon != WP_MELEE*/ ) + {//boba leads his enemy + if ( NPC->health < NPC->client->pers.maxHealth*0.5f ) + {//lead + float missileSpeed = WP_SpeedOfMissileForWeapon( NPC->s.weapon, ((qboolean)(NPCInfo->scriptFlags&SCF_ALT_FIRE)) ); + if ( missileSpeed ) + { + float eDist = Distance( eyes, enemy_eyes ); + eDist /= missileSpeed;//How many seconds it will take to get to the enemy + VectorMA( enemy_eyes, eDist*flrand(0.95f,1.25f), NPC->enemy->client->ps.velocity, enemy_eyes ); + } + } + } + + //Find the desired angles + if ( !NPC->client->ps.saberInFlight + && (NPC->client->ps.legsAnim == BOTH_A2_STABBACK1 + || NPC->client->ps.legsAnim == BOTH_CROUCHATTACKBACK1 + || NPC->client->ps.legsAnim == BOTH_ATTACK_BACK) + ) + {//point *away* + GetAnglesForDirection( enemy_eyes, eyes, angles ); + } + else + {//point towards him + GetAnglesForDirection( eyes, enemy_eyes, angles ); + } + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + /* + if ( NPC->client->ps.saberBlocked == BLOCKED_UPPER_LEFT ) + {//temp hack- to make up for poor coverage on left side + NPCInfo->desiredYaw += 30; + } + */ + + if ( doPitch ) + { + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + if ( NPC->client->ps.saberInFlight ) + {//tilt down a little + NPCInfo->desiredPitch += 10; + } + } + //FIXME: else desiredPitch = 0? Or keep previous? +} + +static void Jedi_DebounceDirectionChanges( void ) +{ + //FIXME: check these before making fwd/back & right/left decisions? + //Time-debounce changes in forward/back dir + if ( ucmd.forwardmove > 0 ) + { + if ( !TIMER_Done( NPC, "moveback" ) || !TIMER_Done( NPC, "movenone" ) ) + { + ucmd.forwardmove = 0; + //now we have to normalize the total movement again + if ( ucmd.rightmove > 0 ) + { + ucmd.rightmove = 127; + } + else if ( ucmd.rightmove < 0 ) + { + ucmd.rightmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveback", -level.time ); + if ( TIMER_Done( NPC, "movenone" ) ) + { + TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveforward" ) ) + {//FIXME: should be if it's zero? + TIMER_Set( NPC, "moveforward", Q_irand( 500, 2000 ) ); + } + } + else if ( ucmd.forwardmove < 0 ) + { + if ( !TIMER_Done( NPC, "moveforward" ) || !TIMER_Done( NPC, "movenone" ) ) + { + ucmd.forwardmove = 0; + //now we have to normalize the total movement again + if ( ucmd.rightmove > 0 ) + { + ucmd.rightmove = 127; + } + else if ( ucmd.rightmove < 0 ) + { + ucmd.rightmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveforward", -level.time ); + if ( TIMER_Done( NPC, "movenone" ) ) + { + TIMER_Set( NPC, "movenone", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveback" ) ) + {//FIXME: should be if it's zero? + TIMER_Set( NPC, "moveback", Q_irand( 250, 1000 ) ); + } + } + else if ( !TIMER_Done( NPC, "moveforward" ) ) + {//NOTE: edge checking should stop me if this is bad... but what if it sends us colliding into the enemy? + ucmd.forwardmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + else if ( !TIMER_Done( NPC, "moveback" ) ) + {//NOTE: edge checking should stop me if this is bad... + ucmd.forwardmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + //Time-debounce changes in right/left dir + if ( ucmd.rightmove > 0 ) + { + if ( !TIMER_Done( NPC, "moveleft" ) || !TIMER_Done( NPC, "movecenter" ) ) + { + ucmd.rightmove = 0; + //now we have to normalize the total movement again + if ( ucmd.forwardmove > 0 ) + { + ucmd.forwardmove = 127; + } + else if ( ucmd.forwardmove < 0 ) + { + ucmd.forwardmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveleft", -level.time ); + if ( TIMER_Done( NPC, "movecenter" ) ) + { + TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveright" ) ) + {//FIXME: should be if it's zero? + TIMER_Set( NPC, "moveright", Q_irand( 250, 1500 ) ); + } + } + else if ( ucmd.rightmove < 0 ) + { + if ( !TIMER_Done( NPC, "moveright" ) || !TIMER_Done( NPC, "movecenter" ) ) + { + ucmd.rightmove = 0; + //now we have to normalize the total movement again + if ( ucmd.forwardmove > 0 ) + { + ucmd.forwardmove = 127; + } + else if ( ucmd.forwardmove < 0 ) + { + ucmd.forwardmove = -127; + } + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "moveright", -level.time ); + if ( TIMER_Done( NPC, "movecenter" ) ) + { + TIMER_Set( NPC, "movecenter", Q_irand( 1000, 2000 ) ); + } + } + else if ( TIMER_Done( NPC, "moveleft" ) ) + {//FIXME: should be if it's zero? + TIMER_Set( NPC, "moveleft", Q_irand( 250, 1500 ) ); + } + } + else if ( !TIMER_Done( NPC, "moveright" ) ) + {//NOTE: edge checking should stop me if this is bad... + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + else if ( !TIMER_Done( NPC, "moveleft" ) ) + {//NOTE: edge checking should stop me if this is bad... + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } +} + +static void Jedi_TimersApply( void ) +{ + if ( !ucmd.rightmove ) + {//only if not already strafing + //FIXME: if enemy behind me and turning to face enemy, don't strafe in that direction, too + if ( !TIMER_Done( NPC, "strafeLeft" ) ) + { + if ( NPCInfo->desiredYaw > NPC->client->ps.viewangles[YAW] + 60 ) + {//we want to turn left, don't apply the strafing + } + else + {//go ahead and strafe left + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + } + else if ( !TIMER_Done( NPC, "strafeRight" ) ) + { + if ( NPCInfo->desiredYaw < NPC->client->ps.viewangles[YAW] - 60 ) + {//we want to turn right, don't apply the strafing + } + else + {//go ahead and strafe left + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + } + } + + Jedi_DebounceDirectionChanges(); + + //use careful anim/slower movement if not already moving + if ( !ucmd.forwardmove && !TIMER_Done( NPC, "walking" ) ) + { + ucmd.buttons |= (BUTTON_WALKING); + } + + if ( !TIMER_Done( NPC, "taunting" ) ) + { + ucmd.buttons |= (BUTTON_WALKING); + } + + if ( !TIMER_Done( NPC, "gripping" ) ) + {//FIXME: what do we do if we ran out of power? NPC's can't? + //FIXME: don't keep turning to face enemy or we'll end up spinning around + ucmd.buttons |= BUTTON_FORCEGRIP; + } + + if ( !TIMER_Done( NPC, "draining" ) ) + {//FIXME: what do we do if we ran out of power? NPC's can't? + //FIXME: don't keep turning to face enemy or we'll end up spinning around + ucmd.buttons |= BUTTON_FORCE_DRAIN; + } + + if ( !TIMER_Done( NPC, "holdLightning" ) ) + {//hold down the lightning key + ucmd.buttons |= BUTTON_FORCE_LIGHTNING; + } +} + +static void Jedi_CombatTimersUpdate( int enemy_dist ) +{ + if ( TIMER_Done( NPC, "roamTime" ) ) + { + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + //okay, now mess with agression + if ( NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forceRageRecoveryTime > level.time ) + {//recovering + Jedi_Aggression( NPC, Q_irand( 0, -2 ) ); + } + if ( NPC->enemy && NPC->enemy->client ) + { + switch( NPC->enemy->client->ps.weapon ) + { + case WP_SABER: + //If enemy has a lightsaber, always close in + if ( BG_SabersOff( &NPC->enemy->client->ps ) ) + {//fool! Standing around unarmed, charge! + //Com_Printf( "(%d) raise agg - enemy saber off\n", level.time ); + Jedi_Aggression( NPC, 2 ); + } + else + { + //Com_Printf( "(%d) raise agg - enemy saber\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + break; + case WP_BLASTER: + case WP_BRYAR_PISTOL: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_DEMP2: + case WP_FLECHETTE: + case WP_ROCKET_LAUNCHER: + //if he has a blaster, move in when: + //They're not shooting at me + if ( NPC->enemy->attackDebounceTime < level.time ) + {//does this apply to players? + //Com_Printf( "(%d) raise agg - enemy not shooting ranged weap\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + //He's closer than a dist that gives us time to deflect + if ( enemy_dist < 256 ) + { + //Com_Printf( "(%d) raise agg - enemy ranged weap- too close\n", level.time ); + Jedi_Aggression( NPC, 1 ); + } + break; + default: + break; + } + } + } + + if ( TIMER_Done( NPC, "noStrafe" ) && TIMER_Done( NPC, "strafeLeft" ) && TIMER_Done( NPC, "strafeRight" ) ) + { + //FIXME: Maybe more likely to do this if aggression higher? Or some other stat? + if ( !Q_irand( 0, 4 ) ) + {//start a strafe + if ( Jedi_Strafe( 1000, 3000, 0, 4000, qtrue ) ) + { + if ( d_JediAI.integer ) + { + Com_Printf( "off strafe\n" ); + } + } + } + else + {//postpone any strafing for a while + TIMER_Set( NPC, "noStrafe", Q_irand( 1000, 3000 ) ); + } + } + + if ( NPC->client->ps.saberEventFlags ) + {//some kind of saber combat event is still pending + int newFlags = NPC->client->ps.saberEventFlags; + if ( NPC->client->ps.saberEventFlags&SEF_PARRIED ) + {//parried + TIMER_Set( NPC, "parryTime", -1 ); + /* + if ( NPCInfo->rank >= RANK_LT_JG ) + { + NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 100; + } + else + { + NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + } + */ + if ( NPC->enemy && PM_SaberInKnockaway( NPC->enemy->client->ps.saberMove ) ) + {//advance! + Jedi_Aggression( NPC, 1 );//get closer + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) );//use a faster attack + } + else + { + if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff? + { + //Com_Printf( "(%d) drop agg - we parried\n", level.time ); + Jedi_Aggression( NPC, -1 ); + } + if ( !Q_irand( 0, 1 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) ); + } + } + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) PARRY: agg %d, no parry until %d\n", level.time, NPCInfo->stats.aggression, level.time + 100 ); + } + newFlags &= ~SEF_PARRIED; + } + if ( !NPC->client->ps.weaponTime && (NPC->client->ps.saberEventFlags&SEF_HITENEMY) )//hit enemy + {//we hit our enemy last time we swung, drop our aggression + if ( !Q_irand( 0, 1 ) )//FIXME: dependant on rank/diff? + { + //Com_Printf( "(%d) drop agg - we hit enemy\n", level.time ); + Jedi_Aggression( NPC, -1 ); + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) HIT: agg %d\n", level.time, NPCInfo->stats.aggression ); + } + if ( !Q_irand( 0, 3 ) + && NPCInfo->blockedSpeechDebounceTime < level.time + && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time + && NPC->painDebounceTime < level.time - 1000 ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_GLOAT1, EV_GLOAT3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + } + if ( !Q_irand( 0, 2 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) ); + } + newFlags &= ~SEF_HITENEMY; + } + if ( (NPC->client->ps.saberEventFlags&SEF_BLOCKED) ) + {//was blocked whilst attacking + if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove ) + || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN ) + { + //Com_Printf( "(%d) drop agg - we were knock-blocked\n", level.time ); + if ( NPC->client->ps.saberInFlight ) + {//lost our saber, too!!! + Jedi_Aggression( NPC, -5 );//really really really should back off!!! + } + else + { + Jedi_Aggression( NPC, -2 );//really should back off! + } + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) );//use a stronger attack + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) KNOCK-BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression ); + } + } + else + { + if ( !Q_irand( 0, 2 ) )//FIXME: dependant on rank/diff? + { + //Com_Printf( "(%d) drop agg - we were blocked\n", level.time ); + Jedi_Aggression( NPC, -1 ); + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) BLOCKED: agg %d\n", level.time, NPCInfo->stats.aggression ); + } + } + if ( !Q_irand( 0, 1 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel+1) ); + } + } + newFlags &= ~SEF_BLOCKED; + //FIXME: based on the type of parry the enemy is doing and my skill, + // choose an attack that is likely to get around the parry? + // right now that's generic in the saber animation code, auto-picks + // a next anim for me, but really should be AI-controlled. + } + if ( NPC->client->ps.saberEventFlags&SEF_DEFLECTED ) + {//deflected a shot + newFlags &= ~SEF_DEFLECTED; + if ( !Q_irand( 0, 3 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) ); + } + } + if ( NPC->client->ps.saberEventFlags&SEF_HITWALL ) + {//hit a wall + newFlags &= ~SEF_HITWALL; + } + if ( NPC->client->ps.saberEventFlags&SEF_HITOBJECT ) + {//hit some other damagable object + if ( !Q_irand( 0, 3 ) ) + { + Jedi_AdjustSaberAnimLevel( NPC, (NPC->client->ps.fd.saberAnimLevel-1) ); + } + newFlags &= ~SEF_HITOBJECT; + } + NPC->client->ps.saberEventFlags = newFlags; + } +} + +static void Jedi_CombatIdle( int enemy_dist ) +{ + if ( !TIMER_Done( NPC, "parryTime" ) ) + { + return; + } + if ( NPC->client->ps.saberInFlight ) + {//don't do this idle stuff if throwing saber + return; + } + if ( NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forceRageRecoveryTime > level.time ) + {//never taunt while raging or recovering from rage + return; + } + //FIXME: make these distance numbers defines? + if ( enemy_dist >= 64 ) + {//FIXME: only do this if standing still? + //based on aggression, flaunt/taunt + int chance = 20; + if ( NPC->client->NPC_class == CLASS_SHADOWTROOPER ) + { + chance = 10; + } + //FIXME: possibly throw local objects at enemy? + if ( Q_irand( 2, chance ) < NPCInfo->stats.aggression ) + { + if ( TIMER_Done( NPC, "chatter" ) && NPC->client->ps.forceHandExtend == HANDEXTEND_NONE ) + {//FIXME: add more taunt behaviors + //FIXME: sometimes he turns it off, then turns it right back on again??? + if ( enemy_dist > 200 + && NPC->client->NPC_class != CLASS_BOBAFETT + && !NPC->client->ps.saberHolstered + && !Q_irand( 0, 5 ) ) + {//taunt even more, turn off the saber + //FIXME: don't do this if health low? + WP_DeactivateSaber( NPC, qfalse ); + //Don't attack for a bit + NPCInfo->stats.aggression = 3; + //FIXME: maybe start strafing? + //debounce this + if ( NPC->client->playerTeam != NPCTEAM_PLAYER && !Q_irand( 0, 1 )) + { + //NPC->client->ps.taunting = level.time + 100; + NPC->client->ps.forceHandExtend = HANDEXTEND_JEDITAUNT; + NPC->client->ps.forceHandExtendTime = level.time + 5000; + + TIMER_Set( NPC, "chatter", Q_irand( 5000, 10000 ) ); + TIMER_Set( NPC, "taunting", 5500 ); + } + else + { + Jedi_BattleTaunt(); + TIMER_Set( NPC, "taunting", Q_irand( 5000, 10000 ) ); + } + } + else if ( Jedi_BattleTaunt() ) + {//FIXME: pick some anims + } + } + } + } +} + +static qboolean Jedi_AttackDecide( int enemy_dist ) +{ + if ( NPC->enemy->client + && NPC->enemy->s.weapon == WP_SABER + && NPC->enemy->client->ps.saberLockTime > level.time + && NPC->client->ps.saberLockTime < level.time ) + {//enemy is in a saberLock and we are not + return qfalse; + } + + if ( NPC->client->ps.saberEventFlags&SEF_LOCK_WON ) + {//we won a saber lock, press the advantage with an attack! + int chance = 0; + if ( NPC->client->NPC_class == CLASS_DESANN || NPC->client->NPC_class == CLASS_LUKE || !Q_stricmp("Yoda",NPC->NPC_type) ) + {//desann and luke + chance = 20; + } + else if ( NPC->client->NPC_class == CLASS_TAVION ) + {//tavion + chance = 10; + } + else if ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG ) + {//fencer + chance = 5; + } + else + { + chance = NPCInfo->rank; + } + if ( Q_irand( 0, 30 ) < chance ) + {//based on skill with some randomness + NPC->client->ps.saberEventFlags &= ~SEF_LOCK_WON;//clear this now that we are using the opportunity + TIMER_Set( NPC, "noRetreat", Q_irand( 500, 2000 ) ); + //FIXME: check enemy_dist? + NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0; + //NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + NPC->client->ps.saberBlocked = BLOCKED_NONE; + WeaponThink( qtrue ); + return qtrue; + } + } + + if ( NPC->client->NPC_class == CLASS_TAVION || + ( NPC->client->NPC_class == CLASS_REBORN && NPCInfo->rank == RANK_LT_JG ) || + ( NPC->client->NPC_class == CLASS_JEDI && NPCInfo->rank == RANK_COMMANDER ) ) + {//tavion, fencers, jedi trainer are all good at following up a parry with an attack + if ( ( PM_SaberInParry( NPC->client->ps.saberMove ) || PM_SaberInKnockaway( NPC->client->ps.saberMove ) ) + && NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//try to attack straight from a parry + NPC->client->ps.weaponTime = NPCInfo->shotTime = NPC->attackDebounceTime = 0; + //NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + NPC->client->ps.saberBlocked = BLOCKED_NONE; + Jedi_AdjustSaberAnimLevel( NPC, FORCE_LEVEL_1 );//try to follow-up with a quick attack + WeaponThink( qtrue ); + return qtrue; + } + } + + //try to hit them if we can + if ( enemy_dist >= 64 ) + { + return qfalse; + } + + if ( !TIMER_Done( NPC, "parryTime" ) ) + { + return qfalse; + } + + if ( (NPCInfo->scriptFlags&SCF_DONT_FIRE) ) + {//not allowed to attack + return qfalse; + } + + if ( !(ucmd.buttons&BUTTON_ATTACK) && !(ucmd.buttons&BUTTON_ALT_ATTACK) ) + {//not already attacking + //Try to attack + WeaponThink( qtrue ); + } + + //FIXME: Maybe try to push enemy off a ledge? + + //close enough to step forward + + //FIXME: an attack debounce timer other than the phaser debounce time? + // or base it on aggression? + + if ( ucmd.buttons&BUTTON_ATTACK ) + {//attacking + /* + if ( enemy_dist > 32 && NPCInfo->stats.aggression >= 4 ) + {//move forward if we're too far away and we're chasing him + ucmd.forwardmove = 127; + } + else if ( enemy_dist < 0 ) + {//move back if we're too close + ucmd.forwardmove = -127; + } + */ + //FIXME: based on the type of parry/attack the enemy is doing and my skill, + // choose an attack that is likely to get around the parry? + // right now that's generic in the saber animation code, auto-picks + // a next anim for me, but really should be AI-controlled. + //FIXME: have this interact with/override above strafing code? + if ( !ucmd.rightmove ) + {//not already strafing + if ( !Q_irand( 0, 3 ) ) + {//25% chance of doing this + vec3_t right, dir2enemy; + + AngleVectors( NPC->r.currentAngles, NULL, right, NULL ); + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentAngles, dir2enemy ); + if ( DotProduct( right, dir2enemy ) > 0 ) + {//he's to my right, strafe left + ucmd.rightmove = -127; + VectorClear( NPC->client->ps.moveDir ); + } + else + {//he's to my left, strafe right + ucmd.rightmove = 127; + VectorClear( NPC->client->ps.moveDir ); + } + } + } + return qtrue; + } + + return qfalse; +} + +#define APEX_HEIGHT 200.0f +#define PARA_WIDTH (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT)) +#define JUMP_SPEED 200.0f + +static qboolean Jedi_Jump( vec3_t dest, int goalEntNum ) +{//FIXME: if land on enemy, knock him down & jump off again + /* + if ( dest[2] - NPC->r.currentOrigin[2] < 64 && DistanceHorizontal( NPC->r.currentOrigin, dest ) > 256 ) + {//a pretty horizontal jump, easy to fake: + vec3_t enemy_diff; + + VectorSubtract( dest, NPC->r.currentOrigin, enemy_diff ); + float enemy_z_diff = enemy_diff[2]; + enemy_diff[2] = 0; + float enemy_xy_diff = VectorNormalize( enemy_diff ); + + VectorScale( enemy_diff, enemy_xy_diff*0.8, NPC->client->ps.velocity ); + if ( enemy_z_diff < 64 ) + { + NPC->client->ps.velocity[2] = enemy_xy_diff; + } + else + { + NPC->client->ps.velocity[2] = enemy_z_diff*2+enemy_xy_diff/2; + } + } + else + */ + if ( 1 ) + { + float targetDist, shotSpeed = 300, travelTime, impactDist, bestImpactDist = Q3_INFINITE;//fireSpeed, + vec3_t targetDir, shotVel, failCase; + trace_t trace; + trajectory_t tr; + qboolean blocked; + int elapsedTime, timeStep = 500, hitCount = 0, maxHits = 7; + vec3_t lastPos, testPos, bottom; + + while ( hitCount < maxHits ) + { + VectorSubtract( dest, NPC->r.currentOrigin, targetDir ); + targetDist = VectorNormalize( targetDir ); + + VectorScale( targetDir, shotSpeed, shotVel ); + travelTime = targetDist/shotSpeed; + shotVel[2] += travelTime * 0.5 * NPC->client->ps.gravity; + + if ( !hitCount ) + {//save the first one as the worst case scenario + VectorCopy( shotVel, failCase ); + } + + if ( 1 )//tracePath ) + {//do a rough trace of the path + blocked = qfalse; + + VectorCopy( NPC->r.currentOrigin, tr.trBase ); + VectorCopy( shotVel, tr.trDelta ); + tr.trType = TR_GRAVITY; + tr.trTime = level.time; + travelTime *= 1000.0f; + VectorCopy( NPC->r.currentOrigin, lastPos ); + + //This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down? + for ( elapsedTime = timeStep; elapsedTime < floor(travelTime)+timeStep; elapsedTime += timeStep ) + { + if ( (float)elapsedTime > travelTime ) + {//cap it + elapsedTime = floor( travelTime ); + } + BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos ); + if ( testPos[2] < lastPos[2] ) + {//going down, ignore botclip + trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask ); + } + else + {//going up, check for botclip + trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + } + + if ( trace.allsolid || trace.startsolid ) + { + blocked = qtrue; + break; + } + if ( trace.fraction < 1.0f ) + {//hit something + if ( trace.entityNum == goalEntNum ) + {//hit the enemy, that's perfect! + //Hmm, don't want to land on him, though... + break; + } + else + { + if ( trace.contents & CONTENTS_BOTCLIP ) + {//hit a do-not-enter brush + blocked = qtrue; + break; + } + if ( trace.plane.normal[2] > 0.7 && DistanceSquared( trace.endpos, dest ) < 4096 )//hit within 64 of desired location, should be okay + {//close enough! + break; + } + else + {//FIXME: maybe find the extents of this brush and go above or below it on next try somehow? + impactDist = DistanceSquared( trace.endpos, dest ); + if ( impactDist < bestImpactDist ) + { + bestImpactDist = impactDist; + VectorCopy( shotVel, failCase ); + } + blocked = qtrue; + break; + } + } + } + if ( elapsedTime == floor( travelTime ) ) + {//reached end, all clear + if ( trace.fraction >= 1.0f ) + {//hmm, make sure we'll land on the ground... + //FIXME: do we care how far below ourselves or our dest we'll land? + VectorCopy( trace.endpos, bottom ); + bottom[2] -= 128; + trap_Trace( &trace, trace.endpos, NPC->r.mins, NPC->r.maxs, bottom, NPC->s.number, NPC->clipmask ); + if ( trace.fraction >= 1.0f ) + {//would fall too far + blocked = qtrue; + } + } + break; + } + else + { + //all clear, try next slice + VectorCopy( testPos, lastPos ); + } + } + if ( blocked ) + {//hit something, adjust speed (which will change arc) + hitCount++; + shotSpeed = 300 + ((hitCount-2) * 100);//from 100 to 900 (skipping 300) + if ( hitCount >= 2 ) + {//skip 300 since that was the first value we tested + shotSpeed += 100; + } + } + else + {//made it! + break; + } + } + else + {//no need to check the path, go with first calc + break; + } + } + + if ( hitCount >= maxHits ) + {//NOTE: worst case scenario, use the one that impacted closest to the target (or just use the first try...?) + //NOTE: or try failcase? + VectorCopy( failCase, NPC->client->ps.velocity ); + } + VectorCopy( shotVel, NPC->client->ps.velocity ); + } + else + {//a more complicated jump + vec3_t dir, p1, p2, apex; + float time, height, forward, z, xy, dist, apexHeight; + + if ( NPC->r.currentOrigin[2] > dest[2] )//NPCInfo->goalEntity->r.currentOrigin + { + VectorCopy( NPC->r.currentOrigin, p1 ); + VectorCopy( dest, p2 );//NPCInfo->goalEntity->r.currentOrigin + } + else if ( NPC->r.currentOrigin[2] < dest[2] )//NPCInfo->goalEntity->r.currentOrigin + { + VectorCopy( dest, p1 );//NPCInfo->goalEntity->r.currentOrigin + VectorCopy( NPC->r.currentOrigin, p2 ); + } + else + { + VectorCopy( NPC->r.currentOrigin, p1 ); + VectorCopy( dest, p2 );//NPCInfo->goalEntity->r.currentOrigin + } + + //z = xy*xy + VectorSubtract( p2, p1, dir ); + dir[2] = 0; + + //Get xy and z diffs + xy = VectorNormalize( dir ); + z = p1[2] - p2[2]; + + apexHeight = APEX_HEIGHT/2; + + //Determine most desirable apex height + //FIXME: length of xy will change curve of parabola, need to account for this + //somewhere... PARA_WIDTH + /* + apexHeight = (APEX_HEIGHT * PARA_WIDTH/xy) + (APEX_HEIGHT * z/128); + if ( apexHeight < APEX_HEIGHT * 0.5 ) + { + apexHeight = APEX_HEIGHT*0.5; + } + else if ( apexHeight > APEX_HEIGHT * 2 ) + { + apexHeight = APEX_HEIGHT*2; + } + */ + + z = (sqrt(apexHeight + z) - sqrt(apexHeight)); + + assert(z >= 0); + +// Com_Printf("apex is %4.2f percent from p1: ", (xy-z)*0.5/xy*100.0f); + + xy -= z; + xy *= 0.5; + + assert(xy > 0); + + VectorMA( p1, xy, dir, apex ); + apex[2] += apexHeight; + + VectorCopy(apex, NPC->pos1); + + //Now we have the apex, aim for it + height = apex[2] - NPC->r.currentOrigin[2]; + time = sqrt( height / ( .5 * NPC->client->ps.gravity ) );//was 0.5, but didn't work well for very long jumps + if ( !time ) + { + //Com_Printf( S_COLOR_RED"ERROR: no time in jump\n" ); + return qfalse; + } + + VectorSubtract ( apex, NPC->r.currentOrigin, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 0; + dist = VectorNormalize( NPC->client->ps.velocity ); + + forward = dist / time * 1.25;//er... probably bad, but... + VectorScale( NPC->client->ps.velocity, forward, NPC->client->ps.velocity ); + + //FIXME: Uh.... should we trace/EvaluateTrajectory this to make sure we have clearance and we land where we want? + NPC->client->ps.velocity[2] = time * NPC->client->ps.gravity; + + //Com_Printf("Jump Velocity: %4.2f, %4.2f, %4.2f\n", NPC->client->ps.velocity[0], NPC->client->ps.velocity[1], NPC->client->ps.velocity[2] ); + } + return qtrue; +} + +static qboolean Jedi_TryJump( gentity_t *goal ) +{//FIXME: never does a simple short, regular jump... + //FIXME: I need to be on ground too! + if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) ) + { + return qfalse; + } + if ( TIMER_Done( NPC, "jumpChaseDebounce" ) ) + { + if ( (!goal->client || goal->client->ps.groundEntityNum != ENTITYNUM_NONE) ) + { + if ( !PM_InKnockDown( &NPC->client->ps ) && !BG_InRoll( &NPC->client->ps, NPC->client->ps.legsAnim ) ) + {//enemy is on terra firma + vec3_t goal_diff; + float goal_z_diff; + float goal_xy_dist; + VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, goal_diff ); + goal_z_diff = goal_diff[2]; + goal_diff[2] = 0; + goal_xy_dist = VectorNormalize( goal_diff ); + if ( goal_xy_dist < 550 && goal_z_diff > -400/*was -256*/ )//for now, jedi don't take falling damage && (NPC->health > 20 || goal_z_diff > 0 ) && (NPC->health >= 100 || goal_z_diff > -128 ))//closer than @512 + { + qboolean debounce = qfalse; + if ( NPC->health < 150 && ((NPC->health < 30 && goal_z_diff < 0) || goal_z_diff < -128 ) ) + {//don't jump, just walk off... doesn't help with ledges, though + debounce = qtrue; + } + else if ( goal_z_diff < 32 && goal_xy_dist < 200 ) + {//what is their ideal jump height? + ucmd.upmove = 127; + debounce = qtrue; + } + else + { + /* + //NO! All Jedi can jump-navigate now... + if ( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG ) + {//can't do acrobatics + return qfalse; + } + */ + if ( goal_z_diff > 0 || goal_xy_dist > 128 ) + {//Fake a force-jump + //Screw it, just do my own calc & throw + vec3_t dest; + VectorCopy( goal->r.currentOrigin, dest ); + if ( goal == NPC->enemy ) + { + int sideTry = 0; + while( sideTry < 10 ) + {//FIXME: make it so it doesn't try the same spot again? + trace_t trace; + vec3_t bottom; + + if ( Q_irand( 0, 1 ) ) + { + dest[0] += NPC->enemy->r.maxs[0]*1.25; + } + else + { + dest[0] += NPC->enemy->r.mins[0]*1.25; + } + if ( Q_irand( 0, 1 ) ) + { + dest[1] += NPC->enemy->r.maxs[1]*1.25; + } + else + { + dest[1] += NPC->enemy->r.mins[1]*1.25; + } + VectorCopy( dest, bottom ); + bottom[2] -= 128; + trap_Trace( &trace, dest, NPC->r.mins, NPC->r.maxs, bottom, goal->s.number, NPC->clipmask ); + if ( trace.fraction < 1.0f ) + {//hit floor, okay to land here + break; + } + sideTry++; + } + if ( sideTry >= 10 ) + {//screw it, just jump right at him? + VectorCopy( goal->r.currentOrigin, dest ); + } + } + if ( Jedi_Jump( dest, goal->s.number ) ) + { + //Com_Printf( "(%d) pre-checked force jump\n", level.time ); + + //FIXME: store the dir we;re going in in case something gets in the way of the jump? + //? = vectoyaw( NPC->client->ps.velocity ); + /* + if ( NPC->client->ps.velocity[2] < 320 ) + { + NPC->client->ps.velocity[2] = 320; + } + else + */ + {//FIXME: make this a function call + int jumpAnim; + //FIXME: this should be more intelligent, like the normal force jump anim logic + if ( NPC->client->NPC_class == CLASS_BOBAFETT + ||( NPCInfo->rank != RANK_CREWMAN && NPCInfo->rank <= RANK_LT_JG ) ) + {//can't do acrobatics + jumpAnim = BOTH_FORCEJUMP1; + } + else + { + jumpAnim = BOTH_FLIP_F; + } + NPC_SetAnim( NPC, SETANIM_BOTH, jumpAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + + NPC->client->ps.fd.forceJumpZStart = NPC->r.currentOrigin[2]; + //NPC->client->ps.pm_flags |= PMF_JUMPING; + + NPC->client->ps.weaponTime = NPC->client->ps.torsoTimer; + NPC->client->ps.fd.forcePowersActive |= ( 1 << FP_LEVITATION ); + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + G_SoundOnEnt( NPC, CHAN_ITEM, "sound/boba/jeton.wav" ); + NPC->client->jetPackTime = level.time + Q_irand( 1000, 3000 ); + } + else + { + G_SoundOnEnt( NPC, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + + TIMER_Set( NPC, "forceJumpChasing", Q_irand( 2000, 3000 ) ); + debounce = qtrue; + } + } + } + if ( debounce ) + { + //Don't jump again for another 2 to 5 seconds + TIMER_Set( NPC, "jumpChaseDebounce", Q_irand( 2000, 5000 ) ); + ucmd.forwardmove = 127; + VectorClear( NPC->client->ps.moveDir ); + TIMER_Set( NPC, "duck", -level.time ); + return qtrue; + } + } + } + } + } + return qfalse; +} + +static qboolean Jedi_Jumping( gentity_t *goal ) +{ + if ( !TIMER_Done( NPC, "forceJumpChasing" ) && goal ) + {//force-jumping at the enemy +// if ( !(NPC->client->ps.pm_flags & PMF_JUMPING )//forceJumpZStart ) +// && !(NPC->client->ps.pm_flags&PMF_TRIGGER_PUSHED)) + if (NPC->client->ps.groundEntityNum != ENTITYNUM_NONE) //rwwFIXMEFIXME: Not sure if this is gonna work, use the PM flags ideally. + {//landed + TIMER_Set( NPC, "forceJumpChasing", 0 ); + } + else + { + NPC_FaceEntity( goal, qtrue ); + //FIXME: push me torward where I was heading + //FIXME: if hit a ledge as soon as we jumped, we need to push toward our goal... must remember original jump dir and/or original jump dest + /* + vec3_t viewangles_xy={0,0,0}, goal_dir, goal_xy_dir, forward, right; + float goal_dist; + + //gert horz dir to goal + VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, goal_dir ); + VectorCopy( goal_dir, goal_xy_dir ); + goal_dist = VectorNormalize( goal_dir ); + goal_xy_dir[2] = 0; + VectorNormalize( goal_xy_dir ); + + //get horz facing + viewangles_xy[1] = NPC->client->ps.viewangles[1]; + AngleVectors( viewangles_xy, forward, right, NULL ); + + //get movement commands to push me toward enemy + float fDot = DotProduct( forward, goal_dir ) * 127; + float rDot = DotProduct( right, goal_dir ) * 127; + + ucmd.forwardmove = floor(fDot); + ucmd.rightmove = floor(rDot); + ucmd.upmove = 0;//don't duck + //Cheat: + if ( goal_dist < 128 && goal->r.currentOrigin[2] > NPC->r.currentOrigin[2] && NPC->client->ps.velocity[2] <= 0 ) + { + NPC->client->ps.velocity[2] += 320; + } + */ + return qtrue; + } + } + return qfalse; +} + +extern void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir ); +static void Jedi_CheckEnemyMovement( float enemy_dist ) +{ + if ( !NPC->enemy || !NPC->enemy->client ) + { + return; + } + + if ( NPC->client->NPC_class != CLASS_TAVION + && NPC->client->NPC_class != CLASS_DESANN + && NPC->client->NPC_class != CLASS_LUKE + && Q_stricmp("Yoda",NPC->NPC_type) ) + { + if ( NPC->enemy->enemy && NPC->enemy->enemy == NPC ) + {//enemy is mad at *me* + if ( NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSLASHDOWN1 || + NPC->enemy->client->ps.legsAnim == BOTH_JUMPFLIPSTABDOWN ) + {//enemy is flipping over me + if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT ) + {//be nice and stand still for him... + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.fd.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + } + } + else if ( NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_BACK1 + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_RIGHT + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_FLIP_LEFT + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_LEFT_FLIP + || NPC->enemy->client->ps.legsAnim == BOTH_WALL_RUN_RIGHT_FLIP ) + {//he's flipping off a wall + if ( NPC->enemy->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//still in air + if ( enemy_dist < 256 ) + {//close + if ( Q_irand( 0, NPCInfo->rank ) < RANK_LT ) + {//be nice and stand still for him... + vec3_t enemyFwd, dest, dir; + + /* + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.fd.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "noturn", Q_irand( 200, 500 ) ); + */ + //stop current movement + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.fd.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "noturn", Q_irand( 250, 500 )*(3-g_spskill.integer) ); + + VectorCopy( NPC->enemy->client->ps.velocity, enemyFwd ); + VectorNormalize( enemyFwd ); + VectorMA( NPC->enemy->r.currentOrigin, -64, enemyFwd, dest ); + VectorSubtract( dest, NPC->r.currentOrigin, dir ); + if ( VectorNormalize( dir ) > 32 ) + { + G_UcmdMoveForDir( NPC, &ucmd, dir ); + } + else + { + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + } + } + } + } + } + else if ( NPC->enemy->client->ps.legsAnim == BOTH_A2_STABBACK1 ) + {//he's stabbing backwards + if ( enemy_dist < 256 && enemy_dist > 64 ) + {//close + if ( !InFront( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, NPC->enemy->r.currentAngles, 0.0f ) ) + {//behind him + if ( !Q_irand( 0, NPCInfo->rank ) ) + {//be nice and stand still for him... + vec3_t enemyFwd, dest, dir; + + //stop current movement + ucmd.forwardmove = ucmd.rightmove = ucmd.upmove = 0; + VectorClear( NPC->client->ps.moveDir ); + NPC->client->ps.fd.forceJumpCharge = 0; + TIMER_Set( NPC, "strafeLeft", -1 ); + TIMER_Set( NPC, "strafeRight", -1 ); + TIMER_Set( NPC, "noStrafe", Q_irand( 500, 1000 ) ); + + AngleVectors( NPC->enemy->r.currentAngles, enemyFwd, NULL, NULL ); + VectorMA( NPC->enemy->r.currentOrigin, -32, enemyFwd, dest ); + VectorSubtract( dest, NPC->r.currentOrigin, dir ); + if ( VectorNormalize( dir ) > 64 ) + { + G_UcmdMoveForDir( NPC, &ucmd, dir ); + } + else + { + TIMER_Set( NPC, "movenone", Q_irand( 500, 1000 ) ); + TIMER_Set( NPC, "movecenter", Q_irand( 500, 1000 ) ); + } + } + } + } + } + } + } + //FIXME: also: + // If enemy doing wall flip, keep running forward + // If enemy doing back-attack and we're behind him keep running forward toward his back, don't strafe +} + +static void Jedi_CheckJumps( void ) +{ + vec3_t jumpVel; + trace_t trace; + trajectory_t tr; + vec3_t lastPos, testPos, bottom; + int elapsedTime; + + if ( (NPCInfo->scriptFlags&SCF_NO_ACROBATICS) ) + { + NPC->client->ps.fd.forceJumpCharge = 0; + ucmd.upmove = 0; + return; + } + //FIXME: should probably check this before AI decides that best move is to jump? Otherwise, they may end up just standing there and looking dumb + //FIXME: all this work and he still jumps off ledges... *sigh*... need CONTENTS_BOTCLIP do-not-enter brushes...? + VectorClear(jumpVel); + + if ( NPC->client->ps.fd.forceJumpCharge ) + { + //Com_Printf( "(%d) force jump\n", level.time ); + WP_GetVelocityForForceJump( NPC, jumpVel, &ucmd ); + } + else if ( ucmd.upmove > 0 ) + { + //Com_Printf( "(%d) regular jump\n", level.time ); + VectorCopy( NPC->client->ps.velocity, jumpVel ); + jumpVel[2] = JUMP_VELOCITY; + } + else + { + return; + } + + //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry... + if ( !jumpVel[0] && !jumpVel[1] )//FIXME: && !ucmd.forwardmove && !ucmd.rightmove? + {//we assume a jump straight up is safe + //Com_Printf( "(%d) jump straight up is safe\n", level.time ); + return; + } + //Now predict where this is going + //in steps, keep evaluating the trajectory until the new z pos is <= than current z pos, trace down from there + + VectorCopy( NPC->r.currentOrigin, tr.trBase ); + VectorCopy( jumpVel, tr.trDelta ); + tr.trType = TR_GRAVITY; + tr.trTime = level.time; + VectorCopy( NPC->r.currentOrigin, lastPos ); + + VectorClear(trace.endpos); //shut the compiler up + + //This may be kind of wasteful, especially on long throws... use larger steps? Divide the travelTime into a certain hard number of slices? Trace just to apex and down? + for ( elapsedTime = 500; elapsedTime <= 4000; elapsedTime += 500 ) + { + BG_EvaluateTrajectory( &tr, level.time + elapsedTime, testPos ); + //FIXME: account for PM_AirMove if ucmd.forwardmove and/or ucmd.rightmove is non-zero... + if ( testPos[2] < lastPos[2] ) + {//going down, don't check for BOTCLIP + trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask );//FIXME: include CONTENTS_BOTCLIP? + } + else + {//going up, check for BOTCLIP + trap_Trace( &trace, lastPos, NPC->r.mins, NPC->r.maxs, testPos, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + } + if ( trace.allsolid || trace.startsolid ) + {//WTF? + //FIXME: what do we do when we start INSIDE the CONTENTS_BOTCLIP? Do the trace again without that clipmask? + goto jump_unsafe; + return; + } + if ( trace.fraction < 1.0f ) + {//hit something + if ( trace.contents & CONTENTS_BOTCLIP ) + {//hit a do-not-enter brush + goto jump_unsafe; + return; + } + //FIXME: trace through func_glass? + break; + } + VectorCopy( testPos, lastPos ); + } + //okay, reached end of jump, now trace down from here for a floor + VectorCopy( trace.endpos, bottom ); + if ( bottom[2] > NPC->r.currentOrigin[2] ) + {//only care about dist down from current height or lower + bottom[2] = NPC->r.currentOrigin[2]; + } + else if ( NPC->r.currentOrigin[2] - bottom[2] > 400 ) + {//whoa, long drop, don't do it! + //probably no floor at end of jump, so don't jump + goto jump_unsafe; + return; + } + bottom[2] -= 128; + trap_Trace( &trace, trace.endpos, NPC->r.mins, NPC->r.maxs, bottom, NPC->s.number, NPC->clipmask ); + if ( trace.allsolid || trace.startsolid || trace.fraction < 1.0f ) + {//hit ground! + if ( trace.entityNum < ENTITYNUM_WORLD ) + {//landed on an ent + gentity_t *groundEnt = &g_entities[trace.entityNum]; + if ( groundEnt->r.svFlags&SVF_GLASS_BRUSH ) + {//don't land on breakable glass! + goto jump_unsafe; + return; + } + } + //Com_Printf( "(%d) jump is safe\n", level.time ); + return; + } +jump_unsafe: + //probably no floor at end of jump, so don't jump + //Com_Printf( "(%d) unsafe jump cleared\n", level.time ); + NPC->client->ps.fd.forceJumpCharge = 0; + ucmd.upmove = 0; +} + +static void Jedi_Combat( void ) +{ + vec3_t enemy_dir, enemy_movedir, enemy_dest; + float enemy_dist, enemy_movespeed; + qboolean enemy_lost = qfalse; + + //See where enemy will be 300 ms from now + Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 ); + + if ( Jedi_Jumping( NPC->enemy ) ) + {//I'm in the middle of a jump, so just see if I should attack + Jedi_AttackDecide( enemy_dist ); + return; + } + + if ( !(NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2 ) + {//not gripping + //If we can't get straight at him + if ( !Jedi_ClearPathToSpot( enemy_dest, NPC->enemy->s.number ) ) + {//hunt him down + //Com_Printf( "No Clear Path\n" ); + if ( (NPC_ClearLOS4( NPC->enemy )||NPCInfo->enemyLastSeenTime>level.time-500) && NPC_FaceEnemy( qtrue ) )//( NPCInfo->rank == RANK_CREWMAN || NPCInfo->rank > RANK_LT_JG ) && + { + //try to jump to him? + /* + vec3_t end; + VectorCopy( NPC->r.currentOrigin, end ); + end[2] += 36; + trap_Trace( &trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0 ) + { + vec3_t angles, forward; + VectorCopy( NPC->client->ps.viewangles, angles ); + angles[0] = 0; + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( end, 64, forward, end ); + trap_Trace( &trace, NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, end, NPC->s.number, NPC->clipmask|CONTENTS_BOTCLIP ); + if ( !trace.allsolid && !trace.startsolid ) + { + if ( trace.fraction >= 1.0 || trace.plane.normal[2] > 0 ) + { + ucmd.upmove = 127; + ucmd.forwardmove = 127; + return; + } + } + } + */ + //FIXME: about every 1 second calc a velocity, + //run a loop of traces with evaluate trajectory + //for gravity with my size, see if it makes it... + //this will also catch misacalculations that send you off ledges! + //Com_Printf( "Considering Jump\n" ); + if ( Jedi_TryJump( NPC->enemy ) ) + {//FIXME: what about jumping to his enemyLastSeenLocation? + return; + } + } + + //Check for evasion + if ( TIMER_Done( NPC, "parryTime" ) ) + {//finished parrying + if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE && + NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//wasn't blocked myself + NPC->client->ps.saberBlocked = BLOCKED_NONE; + } + } + if ( Jedi_Hunt() && !(NPCInfo->aiFlags&NPCAI_BLOCKED) )//FIXME: have to do this because they can ping-pong forever + {//can macro-navigate to him + if ( enemy_dist < 384 && !Q_irand( 0, 10 ) && NPCInfo->blockedSpeechDebounceTime < level.time && jediSpeechDebounceTime[NPC->client->playerTeam] < level.time && !NPC_ClearLOS4( NPC->enemy ) ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_JLOST1, EV_JLOST3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + + return; + } + //well, try to head for his last seen location + /* + else if ( Jedi_Track() ) + { + return; + } + */ else + {//FIXME: try to find a waypoint that can see enemy, jump from there + if ( NPCInfo->aiFlags & NPCAI_BLOCKED ) + {//try to jump to the blockedDest + gentity_t *tempGoal = G_Spawn();//ugh, this is NOT good...? + G_SetOrigin( tempGoal, NPCInfo->blockedDest ); + trap_LinkEntity( tempGoal ); + if ( Jedi_TryJump( tempGoal ) ) + {//going to jump to the dest + G_FreeEntity( tempGoal ); + return; + } + G_FreeEntity( tempGoal ); + } + + enemy_lost = qtrue; + } + } + } + //else, we can see him or we can't track him at all + + //every few seconds, decide if we should we advance or retreat? + Jedi_CombatTimersUpdate( enemy_dist ); + + //We call this even if lost enemy to keep him moving and to update the taunting behavior + //maintain a distance from enemy appropriate for our aggression level + Jedi_CombatDistance( enemy_dist ); + + //if ( !enemy_lost ) + { + //Update our seen enemy position + if ( !NPC->enemy->client || ( NPC->enemy->client->ps.groundEntityNum != ENTITYNUM_NONE && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE ) ) + { + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + NPCInfo->enemyLastSeenTime = level.time; + } + + //Turn to face the enemy + if ( TIMER_Done( NPC, "noturn" ) ) + { + Jedi_FaceEnemy( qtrue ); + } + NPC_UpdateAngles( qtrue, qtrue ); + + //Check for evasion + if ( TIMER_Done( NPC, "parryTime" ) ) + {//finished parrying + if ( NPC->client->ps.saberBlocked != BLOCKED_ATK_BOUNCE && + NPC->client->ps.saberBlocked != BLOCKED_PARRY_BROKEN ) + {//wasn't blocked myself + NPC->client->ps.saberBlocked = BLOCKED_NONE; + } + } + if ( NPC->enemy->s.weapon == WP_SABER ) + { + Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir ); + } + else + {//do we need to do any evasion for other kinds of enemies? + } + + //apply strafing/walking timers, etc. + Jedi_TimersApply(); + + if ( !NPC->client->ps.saberInFlight && (!(NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_GRIP] < FORCE_LEVEL_2) ) + {//not throwing saber or using force grip + //see if we can attack + if ( !Jedi_AttackDecide( enemy_dist ) ) + {//we're not attacking, decide what else to do + Jedi_CombatIdle( enemy_dist ); + //FIXME: lower aggression when actually strike offensively? Or just when do damage? + } + else + {//we are attacking + //stop taunting + TIMER_Set( NPC, "taunting", -level.time ); + } + } + else + { + } + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + Boba_FireDecide(); + } + + //Check for certain enemy special moves + Jedi_CheckEnemyMovement( enemy_dist ); + //Make sure that we don't jump off ledges over long drops + Jedi_CheckJumps(); + //Just make sure we don't strafe into walls or off cliffs + if ( !NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue ) ) + {//uh-oh, we are going to fall or hit something + navInfo_t info; + //Get the move info + NAV_GetLastMove( &info ); + if ( !(info.flags & NIF_MACRO_NAV) ) + {//micro-navigation told us to step off a ledge, try using macronav for now + NPC_MoveToGoal( qfalse ); + } + //reset the timers. + TIMER_Set( NPC, "strafeLeft", 0 ); + TIMER_Set( NPC, "strafeRight", 0 ); + } +} + +/* +========================================================================================== +EXTERNALLY CALLED BEHAVIOR STATES +========================================================================================== +*/ + +/* +------------------------- +NPC_Jedi_Pain +------------------------- +*/ + +void NPC_Jedi_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + gentity_t *other = attacker; + vec3_t point; + + VectorCopy(gPainPoint, point); + + //FIXME: base the actual aggression add/subtract on health? + //FIXME: don't do this more than once per frame? + //FIXME: when take pain, stop force gripping....? + if ( other->s.weapon == WP_SABER ) + {//back off + TIMER_Set( self, "parryTime", -1 ); + if ( self->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",self->NPC_type) ) + {//less for Desann + self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*50; + } + else if ( self->NPC->rank >= RANK_LT_JG ) + { + self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*100;//300 + } + else + { + self->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + (3-g_spskill.integer)*200;//500 + } + if ( !Q_irand( 0, 3 ) ) + {//ouch... maybe switch up which saber power level we're using + Jedi_AdjustSaberAnimLevel( self, Q_irand( FORCE_LEVEL_1, FORCE_LEVEL_3 ) ); + } + if ( !Q_irand( 0, 1 ) )//damage > 20 || self->health < 40 || + { + //Com_Printf( "(%d) drop agg - hit by saber\n", level.time ); + Jedi_Aggression( self, -1 ); + } + if ( d_JediAI.integer ) + { + Com_Printf( "(%d) PAIN: agg %d, no parry until %d\n", level.time, self->NPC->stats.aggression, level.time+500 ); + } + //for testing only + // Figure out what quadrant the hit was in. + if ( d_JediAI.integer ) + { + vec3_t diff, fwdangles, right; + float rightdot, zdiff; + + VectorSubtract( point, self->client->renderInfo.eyePoint, diff ); + diff[2] = 0; + fwdangles[1] = self->client->ps.viewangles[1]; + AngleVectors( fwdangles, NULL, right, NULL ); + rightdot = DotProduct(right, diff); + zdiff = point[2] - self->client->renderInfo.eyePoint[2]; + + Com_Printf( "(%d) saber hit at height %4.2f, zdiff: %4.2f, rightdot: %4.2f\n", level.time, point[2]-self->r.absmin[2],zdiff,rightdot); + } + } + else + {//attack + //Com_Printf( "(%d) raise agg - hit by ranged\n", level.time ); + Jedi_Aggression( self, 1 ); + } + + self->NPC->enemyCheckDebounceTime = 0; + + WP_ForcePowerStop( self, FP_GRIP ); + + //NPC_Pain( self, inflictor, other, point, damage, mod ); + NPC_Pain(self, attacker, damage); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } + + //drop me from the ceiling if I'm on it + if ( Jedi_WaitingAmbush( self ) ) + { + self->client->noclip = qfalse; + } + if ( self->client->ps.legsAnim == BOTH_CEILING_CLING ) + { + NPC_SetAnim( self, SETANIM_LEGS, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + if ( self->client->ps.torsoAnim == BOTH_CEILING_CLING ) + { + NPC_SetAnim( self, SETANIM_TORSO, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } +} + +qboolean Jedi_CheckDanger( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR ); + if ( level.alertEvents[alertEvent].level >= AEL_DANGER ) + {//run away! + if ( !level.alertEvents[alertEvent].owner + || !level.alertEvents[alertEvent].owner->client + || (level.alertEvents[alertEvent].owner!=NPC&&level.alertEvents[alertEvent].owner->client->playerTeam!=NPC->client->playerTeam) ) + {//no owner + return qfalse; + } + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + return qfalse; +} + +qboolean Jedi_CheckAmbushPlayer( void ) +{ + int i = 0; + gentity_t *player; + float target_dist; + float zDiff; + + for ( i = 0; i < MAX_CLIENTS; i++ ) + { + player = &g_entities[i]; + + if ( !player || !player->client ) + { + continue; + } + + if ( !NPC_ValidEnemy( player ) ) + { + continue; + } + +// if ( NPC->client->ps.powerups[PW_CLOAKED] || g_crosshairEntNum != NPC->s.number ) + if (NPC->client->ps.powerups[PW_CLOAKED] || !NPC_SomeoneLookingAtMe(NPC)) //rwwFIXMEFIXME: Need to pay attention to who is under crosshair for each player or something. + {//if I'm not cloaked and the player's crosshair is on me, I will wake up, otherwise do this stuff down here... + if ( !trap_InPVS( player->r.currentOrigin, NPC->r.currentOrigin ) ) + {//must be in same room + continue; + } + else + { + if ( !NPC->client->ps.powerups[PW_CLOAKED] ) + { + NPC_SetLookTarget( NPC, 0, 0 ); + } + } + zDiff = NPC->r.currentOrigin[2]-player->r.currentOrigin[2]; + if ( zDiff <= 0 || zDiff > 512 ) + {//never ambush if they're above me or way way below me + continue; + } + + //If the target is this close, then wake up regardless + if ( (target_dist = DistanceHorizontalSquared( player->r.currentOrigin, NPC->r.currentOrigin )) > 4096 ) + {//closer than 64 - always ambush + if ( target_dist > 147456 ) + {//> 384, not close enough to ambush + continue; + } + //Check FOV first + if ( NPC->client->ps.powerups[PW_CLOAKED] ) + { + if ( InFOV( player, NPC, 30, 90 ) == qfalse ) + { + continue; + } + } + else + { + if ( InFOV( player, NPC, 45, 90 ) == qfalse ) + { + continue; + } + } + } + + if ( !NPC_ClearLOS4( player ) ) + { + continue; + } + } + + //Got him, return true; + G_SetEnemy( NPC, player ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + //Didn't get anyone. + return qfalse; +} + +void Jedi_Ambush( gentity_t *self ) +{ + self->client->noclip = qfalse; +// self->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_CEILING_DROP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + self->client->ps.weaponTime = self->client->ps.torsoTimer; //NPC->client->ps.torsoTimer; //what the? + if ( self->client->NPC_class != CLASS_BOBAFETT ) + { + WP_ActivateSaber(self); + } + Jedi_Decloak( self ); + G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 1000 ); +} + +qboolean Jedi_WaitingAmbush( gentity_t *self ) +{ + if ( (self->spawnflags&JSF_AMBUSH) && self->client->noclip ) + { + return qtrue; + } + return qfalse; +} +/* +------------------------- +Jedi_Patrol +------------------------- +*/ + +static void Jedi_Patrol( void ) +{ + NPC->client->ps.saberBlocked = BLOCKED_NONE; + + if ( Jedi_WaitingAmbush( NPC ) ) + {//hiding on the ceiling + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_CEILING_CLING, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + {//look for enemies + if ( Jedi_CheckAmbushPlayer() || Jedi_CheckDanger() ) + {//found him! + Jedi_Ambush( NPC ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + else if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES )//NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + {//look for enemies + gentity_t *best_enemy = NULL; + float best_enemy_dist = Q3_INFINITE; + int i; + for ( i = 0; i < ENTITYNUM_WORLD; i++ ) + { + gentity_t *enemy = &g_entities[i]; + float enemy_dist; + if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam ) + { + if ( trap_InPVS( NPC->r.currentOrigin, enemy->r.currentOrigin ) ) + {//we could potentially see him + enemy_dist = DistanceSquared( NPC->r.currentOrigin, enemy->r.currentOrigin ); + if ( enemy->s.eType == ET_PLAYER || enemy_dist < best_enemy_dist ) + { + //if the enemy is close enough, or threw his saber, take him as the enemy + //FIXME: what if he throws a thermal detonator? + if ( enemy_dist < (220*220) || ( NPCInfo->investigateCount>= 3 && !NPC->client->ps.saberHolstered ) ) + { + G_SetEnemy( NPC, enemy ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + NPCInfo->stats.aggression = 3; + break; + } + else if ( enemy->client->ps.saberInFlight && !enemy->client->ps.saberHolstered ) + {//threw his saber, see if it's heading toward me and close enough to consider a threat + float saberDist; + vec3_t saberDir2Me; + vec3_t saberMoveDir; + gentity_t *saber = &g_entities[enemy->client->ps.saberEntityNum]; + VectorSubtract( NPC->r.currentOrigin, saber->r.currentOrigin, saberDir2Me ); + saberDist = VectorNormalize( saberDir2Me ); + VectorCopy( saber->s.pos.trDelta, saberMoveDir ); + VectorNormalize( saberMoveDir ); + if ( DotProduct( saberMoveDir, saberDir2Me ) > 0.5 ) + {//it's heading towards me + if ( saberDist < 200 ) + {//incoming! + G_SetEnemy( NPC, enemy ); + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + NPCInfo->stats.aggression = 3; + break; + } + } + } + best_enemy_dist = enemy_dist; + best_enemy = enemy; + } + } + } + } + if ( !NPC->enemy ) + {//still not mad + if ( !best_enemy ) + { + //Com_Printf( "(%d) drop agg - no enemy (patrol)\n", level.time ); + Jedi_AggressionErosion(-1); + //FIXME: what about alerts? But not if ignore alerts + } + else + {//have one to consider + if ( NPC_ClearLOS4( best_enemy ) ) + {//we have a clear (of architecture) LOS to him + if ( best_enemy->s.number ) + {//just attack + G_SetEnemy( NPC, best_enemy ); + NPCInfo->stats.aggression = 3; + } + else if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + {//the player, toy with him + //get progressively more interested over time + if ( TIMER_Done( NPC, "watchTime" ) ) + {//we want to pick him up in stages + if ( TIMER_Get( NPC, "watchTime" ) == -1 ) + {//this is the first time, we'll ignore him for a couple seconds + TIMER_Set( NPC, "watchTime", Q_irand( 3000, 5000 ) ); + goto finish; + } + else + {//okay, we've ignored him, now start to notice him + if ( !NPCInfo->investigateCount ) + { + G_AddVoiceEvent( NPC, Q_irand( EV_JDETECTED1, EV_JDETECTED3 ), 3000 ); + } + NPCInfo->investigateCount++; + TIMER_Set( NPC, "watchTime", Q_irand( 4000, 10000 ) ); + } + } + //while we're waiting, do what we need to do + if ( best_enemy_dist < (440*440) || NPCInfo->investigateCount >= 2 ) + {//stage three: keep facing him + NPC_FaceEntity( best_enemy, qtrue ); + if ( best_enemy_dist < (330*330) ) + {//stage four: turn on the saber + if ( !NPC->client->ps.saberInFlight ) + { + WP_ActivateSaber(NPC); + } + } + } + else if ( best_enemy_dist < (550*550) || NPCInfo->investigateCount == 1 ) + {//stage two: stop and face him every now and then + if ( TIMER_Done( NPC, "watchTime" ) ) + { + NPC_FaceEntity( best_enemy, qtrue ); + } + } + else + {//stage one: look at him. + NPC_SetLookTarget( NPC, best_enemy->s.number, 0 ); + } + } + } + else if ( TIMER_Done( NPC, "watchTime" ) ) + {//haven't seen him in a bit, clear the lookTarget + NPC_ClearLookTarget( NPC ); + } + } + } + } +finish: + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + //Jedi_Move( NPCInfo->goalEntity ); + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); + + if ( NPC->enemy ) + {//just picked one up + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + } +} + +qboolean Jedi_CanPullBackSaber( gentity_t *self ) +{ + if ( self->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN && !TIMER_Done( self, "parryTime" ) ) + { + return qfalse; + } + + if ( self->client->NPC_class == CLASS_SHADOWTROOPER + || self->client->NPC_class == CLASS_TAVION + || self->client->NPC_class == CLASS_LUKE + || self->client->NPC_class == CLASS_DESANN + || !Q_stricmp( "Yoda", self->NPC_type ) ) + { + return qtrue; + } + + if ( self->painDebounceTime > level.time )//|| self->client->ps.weaponTime > 0 ) + { + return qfalse; + } + + return qtrue; +} +/* +------------------------- +NPC_BSJedi_FollowLeader +------------------------- +*/ +void NPC_BSJedi_FollowLeader( void ) +{ + NPC->client->ps.saberBlocked = BLOCKED_NONE; + if ( !NPC->enemy ) + { + //Com_Printf( "(%d) drop agg - no enemy (follow)\n", level.time ); + Jedi_AggressionErosion(-1); + } + + //did we drop our saber? If so, go after it! + if ( NPC->client->ps.saberInFlight ) + {//saber is not in hand + if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0 + {// + if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY ) + {//fell to the ground, try to pick it up... + if ( Jedi_CanPullBackSaber( NPC ) ) + { + //FIXME: if it's on the ground and we just pulled it back to us, should we + // stand still for a bit to make sure it gets to us...? + // otherwise we could end up running away from it while it's on its + // way back to us and we could lose it again. + NPC->client->ps.saberBlocked = BLOCKED_NONE; + NPCInfo->goalEntity = &g_entities[NPC->client->ps.saberEntityNum]; + ucmd.buttons |= BUTTON_ATTACK; + if ( NPC->enemy && NPC->enemy->health > 0 ) + {//get our saber back NOW! + if ( !NPC_MoveToGoal( qtrue ) )//Jedi_Move( NPCInfo->goalEntity, qfalse ); + {//can't nav to it, try jumping to it + NPC_FaceEntity( NPCInfo->goalEntity, qtrue ); + Jedi_TryJump( NPCInfo->goalEntity ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + } + } + + if ( NPCInfo->goalEntity ) + { + trace_t trace; + + if ( Jedi_Jumping( NPCInfo->goalEntity ) ) + {//in mid-jump + return; + } + + if ( !NAV_CheckAhead( NPC, NPCInfo->goalEntity->r.currentOrigin, &trace, ( NPC->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) ) + {//can't get straight to him + if ( NPC_ClearLOS4( NPCInfo->goalEntity ) && NPC_FaceEntity( NPCInfo->goalEntity, qtrue ) ) + {//no line of sight + if ( Jedi_TryJump( NPCInfo->goalEntity ) ) + {//started a jump + return; + } + } + } + if ( NPCInfo->aiFlags & NPCAI_BLOCKED ) + {//try to jump to the blockedDest + if ( fabs(NPCInfo->blockedDest[2]-NPC->r.currentOrigin[2]) > 64 ) + { + gentity_t *tempGoal = G_Spawn();//ugh, this is NOT good...? + G_SetOrigin( tempGoal, NPCInfo->blockedDest ); + trap_LinkEntity( tempGoal ); + TIMER_Set( NPC, "jumpChaseDebounce", -1 ); + if ( Jedi_TryJump( tempGoal ) ) + {//going to jump to the dest + G_FreeEntity( tempGoal ); + return; + } + G_FreeEntity( tempGoal ); + } + } + } + //try normal movement + NPC_BSFollowLeader(); +} + + +/* +------------------------- +Jedi_Attack +------------------------- +*/ + +static void Jedi_Attack( void ) +{ + //Don't do anything if we're in a pain anim + if ( NPC->painDebounceTime > level.time ) + { + if ( Q_irand( 0, 1 ) ) + { + Jedi_FaceEnemy( qtrue ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( NPC->client->ps.saberLockTime > level.time ) + { + //FIXME: maybe if I'm losing I should try to force-push out of it? Very rarely, though... + if ( NPC->client->ps.fd.forcePowerLevel[FP_PUSH] > FORCE_LEVEL_2 + && NPC->client->ps.saberLockTime < level.time + 5000 + && !Q_irand( 0, 10 )) + { + ForceThrow( NPC, qfalse ); + } + //based on my skill, hit attack button every other to every several frames in order to push enemy back + else + { + float chance; + + if ( NPC->client->NPC_class == CLASS_DESANN || !Q_stricmp("Yoda",NPC->NPC_type) ) + { + if ( g_spskill.integer ) + { + chance = 4.0f;//he pushes *hard* + } + else + { + chance = 3.0f;//he pushes *hard* + } + } + else if ( NPC->client->NPC_class == CLASS_TAVION ) + { + chance = 2.0f+g_spskill.value;//from 2 to 4 + } + else + {//the escalation in difficulty is nice, here, but cap it so it doesn't get *impossible* on hard + float maxChance = (float)(RANK_LT)/2.0f+3.0f;//5? + if ( !g_spskill.value ) + { + chance = (float)(NPCInfo->rank)/2.0f; + } + else + { + chance = (float)(NPCInfo->rank)/2.0f+1.0f; + } + if ( chance > maxChance ) + { + chance = maxChance; + } + } + // if ( flrand( -4.0f, chance ) >= 0.0f && !(NPC->client->ps.pm_flags&PMF_ATTACK_HELD) ) + // { + // ucmd.buttons |= BUTTON_ATTACK; + // } + if ( flrand( -4.0f, chance ) >= 0.0f ) + { + ucmd.buttons |= BUTTON_ATTACK; + } + //rwwFIXMEFIXME: support for PMF_ATTACK_HELD + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + //did we drop our saber? If so, go after it! + if ( NPC->client->ps.saberInFlight ) + {//saber is not in hand + // if ( NPC->client->ps.saberEntityNum < ENTITYNUM_NONE && NPC->client->ps.saberEntityNum > 0 )//player is 0 + if (!NPC->client->ps.saberEntityNum && NPC->client->saberStoredIndex) //this is valid, it's 0 when our saber is gone -rww (mp-specific) + {// + //if ( g_entities[NPC->client->ps.saberEntityNum].s.pos.trType == TR_STATIONARY ) + if (1) //no matter + {//fell to the ground, try to pick it up + // if ( Jedi_CanPullBackSaber( NPC ) ) + if (1) //no matter + { + NPC->client->ps.saberBlocked = BLOCKED_NONE; + NPCInfo->goalEntity = &g_entities[NPC->client->saberStoredIndex]; + ucmd.buttons |= BUTTON_ATTACK; + if ( NPC->enemy && NPC->enemy->health > 0 ) + {//get our saber back NOW! + Jedi_Move( NPCInfo->goalEntity, qfalse ); + NPC_UpdateAngles( qtrue, qtrue ); + if ( NPC->enemy->s.weapon == WP_SABER ) + {//be sure to continue evasion + vec3_t enemy_dir, enemy_movedir, enemy_dest; + float enemy_dist, enemy_movespeed; + Jedi_SetEnemyInfo( enemy_dest, enemy_dir, &enemy_dist, enemy_movedir, &enemy_movespeed, 300 ); + Jedi_EvasionSaber( enemy_movedir, enemy_dist, enemy_dir ); + } + return; + } + } + } + } + } + //see if our enemy was killed by us, gloat and turn off saber after cool down. + //FIXME: don't do this if we have other enemies to fight...? + if ( NPC->enemy ) + { + if ( NPC->enemy->health <= 0 && NPC->enemy->enemy == NPC && NPC->client->playerTeam != NPCTEAM_PLAYER )//good guys don't gloat + {//my enemy is dead and I killed him + NPCInfo->enemyCheckDebounceTime = 0;//keep looking for others + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( NPCInfo->walkDebounceTime < level.time && NPCInfo->walkDebounceTime >= 0 ) + { + TIMER_Set( NPC, "gloatTime", 10000 ); + NPCInfo->walkDebounceTime = -1; + } + if ( !TIMER_Done( NPC, "gloatTime" ) ) + { + if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared + { + NPCInfo->goalEntity = NPC->enemy; + Jedi_Move( NPC->enemy, qfalse ); + ucmd.buttons |= BUTTON_WALKING; + } + else + { + TIMER_Set( NPC, "gloatTime", 0 ); + } + } + else if ( NPCInfo->walkDebounceTime == -1 ) + { + NPCInfo->walkDebounceTime = -2; + G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000; + NPCInfo->desiredPitch = 0; + NPCInfo->goalEntity = NULL; + } + Jedi_FaceEnemy( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + { + if ( !TIMER_Done( NPC, "parryTime" ) ) + { + TIMER_Set( NPC, "parryTime", -1 ); + NPC->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] = level.time + 500; + } + NPC->client->ps.saberBlocked = BLOCKED_NONE; + if ( !NPC->client->ps.saberHolstered && NPC->client->ps.saberInFlight ) + {//saber is still on (or we're trying to pull it back), count down erosion and keep facing the enemy + //FIXME: need to stop this from happening over and over again when they're blocking their victim's saber + //FIXME: turn off saber sooner so we get cool walk anim? + //Com_Printf( "(%d) drop agg - enemy dead\n", level.time ); + Jedi_AggressionErosion(-3); + if ( BG_SabersOff( &NPC->client->ps ) && !NPC->client->ps.saberInFlight ) + {//turned off saber (in hand), gloat + G_AddVoiceEvent( NPC, Q_irand( EV_VICTORY1, EV_VICTORY3 ), 3000 ); + jediSpeechDebounceTime[NPC->client->playerTeam] = level.time + 3000; + NPCInfo->desiredPitch = 0; + NPCInfo->goalEntity = NULL; + } + TIMER_Set( NPC, "gloatTime", 10000 ); + } + if ( !NPC->client->ps.saberHolstered || NPC->client->ps.saberInFlight || !TIMER_Done( NPC, "gloatTime" ) ) + {//keep walking + if ( DistanceHorizontalSquared( NPC->client->renderInfo.eyePoint, NPC->enemy->r.currentOrigin ) > 4096 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) )//64 squared + { + NPCInfo->goalEntity = NPC->enemy; + Jedi_Move( NPC->enemy, qfalse ); + ucmd.buttons |= BUTTON_WALKING; + } + else + {//got there + if ( NPC->health < NPC->client->pers.maxHealth + && (NPC->client->ps.fd.forcePowersKnown&(1<client->ps.fd.forcePowersActive&(1<enemy->s.weapon == WP_TURRET && !Q_stricmp( "PAS", NPC->enemy->classname ) ) + { + if ( NPC->enemy->count <= 0 ) + {//it's out of ammo + if ( NPC->enemy->activator && NPC_ValidEnemy( NPC->enemy->activator ) ) + { + gentity_t *turretOwner = NPC->enemy->activator; + G_ClearEnemy( NPC ); + G_SetEnemy( NPC, turretOwner ); + } + else + { + G_ClearEnemy( NPC ); + } + } + } + NPC_CheckEnemy( qtrue, qtrue, qtrue ); + + if ( !NPC->enemy ) + { + NPC->client->ps.saberBlocked = BLOCKED_NONE; + if ( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + {//lost him, go back to what we were doing before + NPCInfo->tempBehavior = BS_DEFAULT; + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + Jedi_Patrol();//was calling Idle... why? + return; + } + + //always face enemy if have one + NPCInfo->combatMove = qtrue; + + //Track the player and kill them if possible + Jedi_Combat(); + + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) + || ((NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_HEAL] 0 ) + { + ucmd.upmove = 0; + } + NPC->client->ps.fd.forceJumpCharge = 0; + VectorClear( NPC->client->ps.moveDir ); + } + + //NOTE: for now, we clear ucmd.forwardmove & ucmd.rightmove while in air to avoid jumps going awry... + if ( NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) + {//don't push while in air, throws off jumps! + //FIXME: if we are in the air over a drop near a ledge, should we try to push back towards the ledge? + ucmd.forwardmove = 0; + ucmd.rightmove = 0; + VectorClear( NPC->client->ps.moveDir ); + } + + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( PM_SaberInBrokenParry( NPC->client->ps.saberMove ) || NPC->client->ps.saberBlocked == BLOCKED_PARRY_BROKEN ) + {//just make sure they don't pull their saber to them if they're being blocked + ucmd.buttons &= ~BUTTON_ATTACK; + } + } + + if( (NPCInfo->scriptFlags&SCF_DONT_FIRE) //not allowed to attack + || ((NPC->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_HEAL]client->ps.saberEventFlags&SEF_INWATER)&&!NPC->client->ps.saberInFlight) )//saber in water + { + ucmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); + } + + if ( NPCInfo->scriptFlags&SCF_NO_ACROBATICS ) + { + ucmd.upmove = 0; + NPC->client->ps.fd.forceJumpCharge = 0; + } + + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + Jedi_CheckDecreaseSaberAnimLevel(); + } + + if ( ucmd.buttons & BUTTON_ATTACK && NPC->client->playerTeam == NPCTEAM_ENEMY ) + { + if ( Q_irand( 0, NPC->client->ps.fd.saberAnimLevel ) > 0 + && Q_irand( 0, NPC->client->pers.maxHealth+10 ) > NPC->health + && !Q_irand( 0, 3 )) + {//the more we're hurt and the stronger the attack we're using, the more likely we are to make a anger noise when we swing + G_AddVoiceEvent( NPC, Q_irand( EV_COMBAT1, EV_COMBAT3 ), 1000 ); + } + } + + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( NPC->client->NPC_class == CLASS_TAVION + || (g_spskill.integer && ( NPC->client->NPC_class == CLASS_DESANN || NPCInfo->rank >= Q_irand( RANK_CREWMAN, RANK_CAPTAIN )))) + {//Tavion will kick in force speed if the player does... + if ( NPC->enemy + && !NPC->enemy->s.number + && NPC->enemy->client + && (NPC->enemy->client->ps.fd.forcePowersActive & (1<client->ps.fd.forcePowersActive & (1<enemy ) + {//don't have an enemy, look for one + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + NPC_BSST_Patrol(); + } + else + { + Jedi_Patrol(); + } + } + else//if ( NPC->enemy ) + {//have an enemy + if ( Jedi_WaitingAmbush( NPC ) ) + {//we were still waiting to drop down - must have had enemy set on me outside my AI + Jedi_Ambush( NPC ); + } + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( NPC->enemy->enemy != NPC && NPC->health == NPC->client->pers.maxHealth && DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin )>(800*800) ) + { + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + Boba_ChangeWeapon( WP_DISRUPTOR ); + NPC_BSSniper_Default(); + return; + } + } + Jedi_Attack(); + //if we have multiple-jedi combat, probably need to keep checking (at certain debounce intervals) for a better (closer, more active) enemy and switch if needbe... + if ( ((!ucmd.buttons&&!NPC->client->ps.fd.forcePowersActive)||(NPC->enemy&&NPC->enemy->health<=0)) && NPCInfo->enemyCheckDebounceTime < level.time ) + {//not doing anything (or walking toward a vanquished enemy - fixme: always taunt the player?), not using force powers and it's time to look again + //FIXME: build a list of all local enemies (since we have to find best anyway) for other AI factors- like when to use group attacks, determine when to change tactics, when surrounded, when blocked by another in the enemy group, etc. Should we build this group list or let the enemies maintain their own list and we just access it? + gentity_t *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + gentity_t *newEnemy; + + NPC->enemy = NULL; + newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + } + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 3000 ); + } + } +} diff --git a/code/game/NPC_AI_Mark1.c b/code/game/NPC_AI_Mark1.c new file mode 100644 index 0000000..95f6099 --- /dev/null +++ b/code/game/NPC_AI_Mark1.c @@ -0,0 +1,764 @@ +#include "b_local.h" +#include "g_nav.h" + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define TURN_OFF 0x00000100 + +#define LEFT_ARM_HEALTH 40 +#define RIGHT_ARM_HEALTH 40 +#define AMMO_POD_HEALTH 40 + +#define BOWCASTER_VELOCITY 1300 +#define BOWCASTER_NPC_DAMAGE_EASY 12 +#define BOWCASTER_NPC_DAMAGE_NORMAL 24 +#define BOWCASTER_NPC_DAMAGE_HARD 36 +#define BOWCASTER_SIZE 2 +#define BOWCASTER_SPLASH_DAMAGE 0 +#define BOWCASTER_SPLASH_RADIUS 0 + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_ASLEEP, + LSTATE_WAKEUP, + LSTATE_FIRED0, + LSTATE_FIRED1, + LSTATE_FIRED2, + LSTATE_FIRED3, + LSTATE_FIRED4, +}; + +qboolean NPC_CheckPlayerTeamStealth( void ); +void Mark1_BlasterAttack(qboolean advance); +void DeathFX( gentity_t *ent ); + +#include "../namespace_begin.h" +extern gitem_t *BG_FindItemForAmmo( ammo_t ammo ); +#include "../namespace_end.h" + +/* +------------------------- +NPC_Mark1_Precache +------------------------- +*/ +void NPC_Mark1_Precache(void) +{ + G_SoundIndex( "sound/chars/mark1/misc/mark1_wakeup"); + G_SoundIndex( "sound/chars/mark1/misc/shutdown"); + G_SoundIndex( "sound/chars/mark1/misc/walk"); + G_SoundIndex( "sound/chars/mark1/misc/run"); + G_SoundIndex( "sound/chars/mark1/misc/death1"); + G_SoundIndex( "sound/chars/mark1/misc/death2"); + G_SoundIndex( "sound/chars/mark1/misc/anger"); + G_SoundIndex( "sound/chars/mark1/misc/mark1_fire"); + G_SoundIndex( "sound/chars/mark1/misc/mark1_pain"); + G_SoundIndex( "sound/chars/mark1/misc/mark1_explo"); + +// G_EffectIndex( "small_chunks"); + G_EffectIndex( "env/med_explode2"); + G_EffectIndex( "explosions/probeexplosion1"); + G_EffectIndex( "blaster/smoke_bolton"); + G_EffectIndex( "bryar/muzzle_flash"); + G_EffectIndex( "explosions/droidexplosion1" ); + + RegisterItem( BG_FindItemForAmmo( AMMO_METAL_BOLTS)); + RegisterItem( BG_FindItemForAmmo( AMMO_BLASTER )); + RegisterItem( BG_FindItemForWeapon( WP_BOWCASTER )); + RegisterItem( BG_FindItemForWeapon( WP_BRYAR_PISTOL )); +} + +/* +------------------------- +NPC_Mark1_Part_Explode +------------------------- +*/ +void NPC_Mark1_Part_Explode( gentity_t *self, int bolt ) +{ + if ( bolt >=0 ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir; + + trap_G2API_GetBoltMatrix( self->ghoul2, 0, + bolt, + &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time, + NULL, self->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + + G_PlayEffectID( G_EffectIndex("env/med_explode2"), org, dir ); + + G_PlayEffectID( G_EffectIndex("blaster/smoke_bolton"), org, dir ); + } + + //G_PlayEffectID( G_EffectIndex("blaster/smoke_bolton"), self->playerModel, bolt, self->s.number ); +} + +/* +------------------------- +Mark1_Idle +------------------------- +*/ +void Mark1_Idle( void ) +{ + + NPC_BSIdle(); + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SLEEP1, SETANIM_FLAG_NORMAL ); +} + +/* +------------------------- +Mark1Dead_FireRocket +- Shoot the left weapon, the multi-blaster +------------------------- +*/ +void Mark1Dead_FireRocket (void) +{ + mdxaBone_t boltMatrix; + vec3_t muzzle1,muzzle_dir; + gentity_t *missile; + + int damage = 50; + int bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash5"); + + trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, + bolt, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time, + NULL, NPC->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle1 ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, muzzle_dir ); + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle1, muzzle_dir ); + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_fire")); + + missile = CreateMissile( muzzle1, muzzle_dir, BOWCASTER_VELOCITY, 10000, NPC, qfalse ); + + missile->classname = "bowcaster_proj"; + missile->s.weapon = WP_BOWCASTER; + + VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE ); + VectorScale( missile->r.maxs, -1, missile->r.mins ); + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + //missile->methodOfDeath = MOD_ENERGY; + missile->methodOfDeath = MOD_ROCKET; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = BOWCASTER_SPLASH_DAMAGE; + missile->splashRadius = BOWCASTER_SPLASH_RADIUS; + + // we don't want it to bounce + missile->bounceCount = 0; + +} + +/* +------------------------- +Mark1Dead_FireBlaster +- Shoot the left weapon, the multi-blaster +------------------------- +*/ +void Mark1Dead_FireBlaster (void) +{ + vec3_t muzzle1,muzzle_dir; + gentity_t *missile; + mdxaBone_t boltMatrix; + int bolt; + + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash1"); + + trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, + bolt, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time, + NULL, NPC->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle1 ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, muzzle_dir ); + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle1, muzzle_dir ); + + missile = CreateMissile( muzzle1, muzzle_dir, 1600, 10000, NPC, qfalse ); + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_fire")); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = 1; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BRYAR_PISTOL; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +Mark1_die +------------------------- +*/ +void Mark1_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod,int dFlags,int hitLoc ) +{ + /* + int anim; + + // Is he dead already? + anim = self->client->ps.legsAnim; + if (((anim==BOTH_DEATH1) || (anim==BOTH_DEATH2)) && (self->client->ps.torsoTimer<=0)) + { // This is because self->health keeps getting zeroed out. HL_NONE acts as health in this case. + self->locationDamage[HL_NONE] += damage; + if (self->locationDamage[HL_NONE] > 50) + { + DeathFX(self); + self->client->ps.eFlags |= EF_NODRAW; + self->contents = CONTENTS_CORPSE; + // G_FreeEntity( self ); // Is this safe? I can't see why we'd mark it nodraw and then just leave it around?? + self->e_ThinkFunc = thinkF_G_FreeEntity; + self->nextthink = level.time + FRAMETIME; + } + return; + } + */ + + G_Sound( self, CHAN_AUTO, G_SoundIndex(va("sound/chars/mark1/misc/death%d.wav",Q_irand( 1, 2)))); + + // Choose a death anim + if (Q_irand( 1, 10) > 5) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_DEATH2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_DEATH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } +} + +/* +------------------------- +Mark1_dying +------------------------- +*/ +void Mark1_dying( gentity_t *self ) +{ + int num,newBolt; + + if (self->client->ps.torsoTimer>0) + { + if (TIMER_Done(self,"dyingExplosion")) + { + num = Q_irand( 1, 3); + + // Find place to generate explosion + if (num == 1) + { + num = Q_irand( 8, 10); + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("*flash%d",num) ); + NPC_Mark1_Part_Explode(self,newBolt); + } + else + { + num = Q_irand( 1, 6); + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("*torso_tube%d",num) ); + NPC_Mark1_Part_Explode(self,newBolt); + NPC_SetSurfaceOnOff( self, va("torso_tube%d",num), TURN_OFF ); + } + + TIMER_Set( self, "dyingExplosion", Q_irand( 300, 1000 ) ); + } + + +// int dir; +// vec3_t right; + + // Shove to the side +// AngleVectors( self->client->renderInfo.eyeAngles, NULL, right, NULL ); +// VectorMA( self->client->ps.velocity, -80, right, self->client->ps.velocity ); + + // See which weapons are there + // Randomly fire blaster + if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm" )) // Is the blaster still on the model? + { + if (Q_irand( 1, 5) == 1) + { + SaveNPCGlobals(); + SetNPCGlobals( self ); + Mark1Dead_FireBlaster(); + RestoreNPCGlobals(); + } + } + + // Randomly fire rocket + if (!trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_arm" )) // Is the rocket still on the model? + { + if (Q_irand( 1, 10) == 1) + { + SaveNPCGlobals(); + SetNPCGlobals( self ); + Mark1Dead_FireRocket(); + RestoreNPCGlobals(); + } + } + } + +} + +/* +------------------------- +NPC_Mark1_Pain +- look at what was hit and see if it should be removed from the model. +------------------------- +*/ +void NPC_Mark1_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + int newBolt,i,chance; + int hitLoc = gPainHitLoc; + + NPC_Pain( self, attacker, damage ); + + G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_pain")); + + // Hit in the CHEST??? + if (hitLoc==HL_CHEST) + { + chance = Q_irand( 1, 4); + + if ((chance == 1) && (damage > 5)) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + // Hit in the left arm? + else if ((hitLoc==HL_ARM_LT) && (self->locationDamage[HL_ARM_LT] > LEFT_ARM_HEALTH)) + { + if (self->locationDamage[hitLoc] >= LEFT_ARM_HEALTH) // Blow it up? + { + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*flash3" ); + if ( newBolt != -1 ) + { + NPC_Mark1_Part_Explode(self,newBolt); + } + + NPC_SetSurfaceOnOff( self, "l_arm", TURN_OFF ); + } + } + // Hit in the right arm? + else if ((hitLoc==HL_ARM_RT) && (self->locationDamage[HL_ARM_RT] > RIGHT_ARM_HEALTH)) // Blow it up? + { + if (self->locationDamage[hitLoc] >= RIGHT_ARM_HEALTH) + { + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*flash4" ); + if ( newBolt != -1 ) + { +// G_PlayEffect( "small_chunks", self->playerModel, self->genericBolt2, self->s.number); + NPC_Mark1_Part_Explode( self, newBolt ); + } + + NPC_SetSurfaceOnOff( self, "r_arm", TURN_OFF ); + } + } + // Check ammo pods + else + { + for (i=0;i<6;i++) + { + if ((hitLoc==HL_GENERIC1+i) && (self->locationDamage[HL_GENERIC1+i] > AMMO_POD_HEALTH)) // Blow it up? + { + if (self->locationDamage[hitLoc] >= AMMO_POD_HEALTH) + { + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("*torso_tube%d",(i+1)) ); + if ( newBolt != -1 ) + { + NPC_Mark1_Part_Explode(self,newBolt); + } + NPC_SetSurfaceOnOff( self, va("torso_tube%d",(i+1)), TURN_OFF ); + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + break; + } + } + } + } + + // Are both guns shot off? + if ((trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "l_arm" )>0) && + (trap_G2API_GetSurfaceRenderStatus( self->ghoul2, 0, "r_arm" )>0)) + { + G_Damage(self,NULL,NULL,NULL,NULL,self->health,0,MOD_UNKNOWN); + } +} + +/* +------------------------- +Mark1_Hunt +- look for enemy. +-------------------------` +*/ +void Mark1_Hunt(void) +{ + + if ( NPCInfo->goalEntity == NULL ) + { + NPCInfo->goalEntity = NPC->enemy; + } + + NPC_FaceEnemy( qtrue ); + + NPCInfo->combatMove = qtrue; + NPC_MoveToGoal( qtrue ); +} + +/* +------------------------- +Mark1_FireBlaster +- Shoot the left weapon, the multi-blaster +------------------------- +*/ +void Mark1_FireBlaster(void) +{ + vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + gentity_t *missile; + mdxaBone_t boltMatrix; + int bolt; + + // Which muzzle to fire from? + if ((NPCInfo->localState <= LSTATE_FIRED0) || (NPCInfo->localState == LSTATE_FIRED4)) + { + NPCInfo->localState = LSTATE_FIRED1; + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash1"); + } + else if (NPCInfo->localState == LSTATE_FIRED1) + { + NPCInfo->localState = LSTATE_FIRED2; + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash2"); + } + else if (NPCInfo->localState == LSTATE_FIRED2) + { + NPCInfo->localState = LSTATE_FIRED3; + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash3"); + } + else + { + NPCInfo->localState = LSTATE_FIRED4; + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash4"); + } + + trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, + bolt, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time, + NULL, NPC->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle1 ); + + if (NPC->health) + { + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); + VectorSubtract (enemy_org1, muzzle1, delta1); + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + } + else + { + AngleVectors (NPC->r.currentAngles, forward, vright, up); + } + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle1, forward ); + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_fire")); + + missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC, qfalse ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = 1; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BRYAR_PISTOL; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +Mark1_BlasterAttack +------------------------- +*/ +void Mark1_BlasterAttack(qboolean advance ) +{ + int chance; + + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + chance = Q_irand( 1, 5); + + NPCInfo->burstCount++; + + if (NPCInfo->burstCount<3) // Too few shots this burst? + { + chance = 2; // Force it to keep firing. + } + else if (NPCInfo->burstCount>12) // Too many shots fired this burst? + { + NPCInfo->burstCount = 0; + chance = 1; // Force it to stop firing. + } + + // Stop firing. + if (chance == 1) + { + NPCInfo->burstCount = 0; + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000) ); + NPC->client->ps.torsoTimer=0; // Just in case the firing anim is running. + } + else + { + if (TIMER_Done( NPC, "attackDelay2" )) // Can't be shooting every frame. + { + TIMER_Set( NPC, "attackDelay2", Q_irand( 50, 50) ); + Mark1_FireBlaster(); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + return; + } + } + else if (advance) + { + if ( NPC->client->ps.torsoAnim == BOTH_ATTACK1 ) + { + NPC->client->ps.torsoTimer=0; // Just in case the firing anim is running. + } + Mark1_Hunt(); + } + else // Make sure he's not firing. + { + if ( NPC->client->ps.torsoAnim == BOTH_ATTACK1 ) + { + NPC->client->ps.torsoTimer=0; // Just in case the firing anim is running. + } + } +} + +/* +------------------------- +Mark1_FireRocket +------------------------- +*/ +void Mark1_FireRocket(void) +{ + mdxaBone_t boltMatrix; + vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; + static vec3_t forward, vright, up; + int bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash5"); + gentity_t *missile; + + int damage = 50; + + trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, + bolt, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time, + NULL, NPC->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle1 ); + +// G_PlayEffect( "blaster/muzzle_flash", muzzle1 ); + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); + VectorSubtract (enemy_org1, muzzle1, delta1); + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_fire" )); + + missile = CreateMissile( muzzle1, forward, BOWCASTER_VELOCITY, 10000, NPC, qfalse ); + + missile->classname = "bowcaster_proj"; + missile->s.weapon = WP_BOWCASTER; + + VectorSet( missile->r.maxs, BOWCASTER_SIZE, BOWCASTER_SIZE, BOWCASTER_SIZE ); + VectorScale( missile->r.maxs, -1, missile->r.mins ); + + missile->damage = damage; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_ROCKET; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + missile->splashDamage = BOWCASTER_SPLASH_DAMAGE; + missile->splashRadius = BOWCASTER_SPLASH_RADIUS; + + // we don't want it to bounce + missile->bounceCount = 0; + +} + +/* +------------------------- +Mark1_RocketAttack +------------------------- +*/ +void Mark1_RocketAttack( qboolean advance ) +{ + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000) ); + NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + Mark1_FireRocket(); + } + else if (advance) + { + Mark1_Hunt(); + } +} + +/* +------------------------- +Mark1_AttackDecision +------------------------- +*/ +void Mark1_AttackDecision( void ) +{ + int blasterTest,rocketTest; + float distance; + distance_e distRate; + qboolean visible; + qboolean advance; + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { +// G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4)))); + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // Enemy is dead or he has no enemy. + if ((NPC->enemy->health<1) || ( NPC_CheckEnemyExt(qfalse) == qfalse )) + { + NPC->enemy = NULL; + return; + } + + // Rate our distance to the target and visibility + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + distRate = ( distance > MIN_MELEE_RANGE_SQR ) ? DIST_LONG : DIST_MELEE; + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ((!visible) || (!NPC_FaceEnemy(qtrue))) + { + Mark1_Hunt(); + return; + } + + // See if the side weapons are there + blasterTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "l_arm" ); + rocketTest = trap_G2API_GetSurfaceRenderStatus( NPC->ghoul2, 0, "r_arm" ); + + // It has both side weapons + if (!blasterTest && !rocketTest) + { + ; // So do nothing. + } + else if (blasterTest!=-1 + &&blasterTest) + { + distRate = DIST_LONG; + } + else if (rocketTest!=-1 + &&rocketTest) + { + distRate = DIST_MELEE; + } + else // It should never get here, but just in case + { + NPC->health = 0; + NPC->client->ps.stats[STAT_HEALTH] = 0; + //GEntity_DieFunc(NPC, NPC, NPC, 100, MOD_UNKNOWN); + if (NPC->die) + { + NPC->die(NPC, NPC, NPC, 100, MOD_UNKNOWN); + } + } + + // We can see enemy so shoot him if timers let you. + NPC_FaceEnemy( qtrue ); + + if (distRate == DIST_MELEE) + { + Mark1_BlasterAttack(advance); + } + else if (distRate == DIST_LONG) + { + Mark1_RocketAttack(advance); + } +} + +/* +------------------------- +Mark1_Patrol +------------------------- +*/ +void Mark1_Patrol( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/mark1/misc/mark1_wakeup")); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + } + + //randomly talk +// if (TIMER_Done(NPC,"patrolNoise")) +// { +// G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4)))); +// +// TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); +// } + } + +} + + +/* +------------------------- +NPC_BSMark1_Default +------------------------- +*/ +void NPC_BSMark1_Default( void ) +{ + //NPC->e_DieFunc = dieF_Mark1_die; + + if ( NPC->enemy ) + { + NPCInfo->goalEntity = NPC->enemy; + Mark1_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Mark1_Patrol(); + } + else + { + Mark1_Idle(); + } +} diff --git a/code/game/NPC_AI_Mark2.c b/code/game/NPC_AI_Mark2.c new file mode 100644 index 0000000..43e0c6f --- /dev/null +++ b/code/game/NPC_AI_Mark2.c @@ -0,0 +1,362 @@ +#include "b_local.h" +#include "g_nav.h" + +//#define AMMO_POD_HEALTH 40 +#define AMMO_POD_HEALTH 1 +#define TURN_OFF 0x00000100 + +#define VELOCITY_DECAY 0.25 +#define MAX_DISTANCE 256 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) +#define MIN_DISTANCE 24 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#include "../namespace_begin.h" +extern gitem_t *BG_FindItemForAmmo( ammo_t ammo ); +#include "../namespace_end.h" + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_DROPPINGDOWN, + LSTATE_DOWN, + LSTATE_RISINGUP, +}; + +void NPC_Mark2_Precache( void ) +{ + G_SoundIndex( "sound/chars/mark2/misc/mark2_explo" );// blows up on death + G_SoundIndex( "sound/chars/mark2/misc/mark2_pain" ); + G_SoundIndex( "sound/chars/mark2/misc/mark2_fire" ); + G_SoundIndex( "sound/chars/mark2/misc/mark2_move_lp" ); + + G_EffectIndex( "explosions/droidexplosion1" ); + G_EffectIndex( "env/med_explode2" ); + G_EffectIndex( "blaster/smoke_bolton" ); + G_EffectIndex( "bryar/muzzle_flash" ); + + RegisterItem( BG_FindItemForWeapon( WP_BRYAR_PISTOL )); + RegisterItem( BG_FindItemForAmmo( AMMO_METAL_BOLTS)); + RegisterItem( BG_FindItemForAmmo( AMMO_POWERCELL )); + RegisterItem( BG_FindItemForAmmo( AMMO_BLASTER )); +} + +/* +------------------------- +NPC_Mark2_Part_Explode +------------------------- +*/ +void NPC_Mark2_Part_Explode( gentity_t *self, int bolt ) +{ + if ( bolt >=0 ) + { + mdxaBone_t boltMatrix; + vec3_t org, dir; + + trap_G2API_GetBoltMatrix( self->ghoul2, 0, + bolt, + &boltMatrix, self->r.currentAngles, self->r.currentOrigin, level.time, + NULL, self->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, org ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, dir ); + + G_PlayEffectID( G_EffectIndex("env/med_explode2"), org, dir ); + G_PlayEffectID( G_EffectIndex("blaster/smoke_bolton"), org, dir ); + } + + //G_PlayEffectID( G_EffectIndex("blaster/smoke_bolton"), self->playerModel, bolt, self->s.number); + + self->count++; // Count of pods blown off +} + +/* +------------------------- +NPC_Mark2_Pain +- look at what was hit and see if it should be removed from the model. +------------------------- +*/ +void NPC_Mark2_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + int newBolt,i; + int hitLoc = gPainHitLoc; + + NPC_Pain( self, attacker, damage ); + + for (i=0;i<3;i++) + { + if ((hitLoc==HL_GENERIC1+i) && (self->locationDamage[HL_GENERIC1+i] > AMMO_POD_HEALTH)) // Blow it up? + { + if (self->locationDamage[hitLoc] >= AMMO_POD_HEALTH) + { + newBolt = trap_G2API_AddBolt( self->ghoul2, 0, va("torso_canister%d",(i+1)) ); + if ( newBolt != -1 ) + { + NPC_Mark2_Part_Explode(self,newBolt); + } + NPC_SetSurfaceOnOff( self, va("torso_canister%d",(i+1)), TURN_OFF ); + break; + } + } + } + + G_Sound( self, CHAN_AUTO, G_SoundIndex( "sound/chars/mark2/misc/mark2_pain" )); + + // If any pods were blown off, kill him + if (self->count > 0) + { + G_Damage( self, NULL, NULL, NULL, NULL, self->health, DAMAGE_NO_PROTECTION, MOD_UNKNOWN ); + } +} + +/* +------------------------- +Mark2_Hunt +------------------------- +*/ +void Mark2_Hunt(void) +{ + if ( NPCInfo->goalEntity == NULL ) + { + NPCInfo->goalEntity = NPC->enemy; + } + + // Turn toward him before moving towards him. + NPC_FaceEnemy( qtrue ); + + NPCInfo->combatMove = qtrue; + NPC_MoveToGoal( qtrue ); +} + +/* +------------------------- +Mark2_FireBlaster +------------------------- +*/ +void Mark2_FireBlaster(qboolean advance) +{ + vec3_t muzzle1,enemy_org1,delta1,angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + gentity_t *missile; + mdxaBone_t boltMatrix; + int bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash"); + + trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, + bolt, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time, + NULL, NPC->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle1 ); + + if (NPC->health) + { + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); + VectorSubtract (enemy_org1, muzzle1, delta1); + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + } + else + { + AngleVectors (NPC->r.currentAngles, forward, vright, up); + } + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle1, forward ); + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/mark2/misc/mark2_fire")); + + missile = CreateMissile( muzzle1, forward, 1600, 10000, NPC, qfalse ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = 1; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BRYAR_PISTOL; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +Mark2_BlasterAttack +------------------------- +*/ +void Mark2_BlasterAttack(qboolean advance) +{ + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + if (NPCInfo->localState == LSTATE_NONE) // He's up so shoot less often. + { + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2000) ); + } + else + { + TIMER_Set( NPC, "attackDelay", Q_irand( 100, 500) ); + } + Mark2_FireBlaster(advance); + return; + } + else if (advance) + { + Mark2_Hunt(); + } +} + +/* +------------------------- +Mark2_AttackDecision +------------------------- +*/ +void Mark2_AttackDecision( void ) +{ + float distance; + qboolean visible; + qboolean advance; + + NPC_FaceEnemy( qtrue ); + + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // He's been ordered to get up + if (NPCInfo->localState == LSTATE_RISINGUP) + { + NPC->flags &= ~FL_SHIELDED; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + if ((NPC->client->ps.legsTimer<=0) && + NPC->client->ps.torsoAnim == BOTH_RUN1START ) + { + NPCInfo->localState = LSTATE_NONE; // He's up again. + } + return; + } + + // If we cannot see our target, move to see it + if ((!visible) || (!NPC_FaceEnemy(qtrue))) + { + // If he's going down or is down, make him get up + if ((NPCInfo->localState == LSTATE_DOWN) || (NPCInfo->localState == LSTATE_DROPPINGDOWN)) + { + if ( TIMER_Done( NPC, "downTime" ) ) // Down being down?? (The delay is so he doesn't pop up and down when the player goes in and out of range) + { + NPCInfo->localState = LSTATE_RISINGUP; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + TIMER_Set( NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. + } + } + else + { + Mark2_Hunt(); + } + return; + } + + // He's down but he could advance if he wants to. + if ((advance) && (TIMER_Done( NPC, "downTime" )) && (NPCInfo->localState == LSTATE_DOWN)) + { + NPCInfo->localState = LSTATE_RISINGUP; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + TIMER_Set( NPC, "runTime", Q_irand( 3000, 8000) ); // So he runs for a while before testing to see if he should drop down. + } + + NPC_FaceEnemy( qtrue ); + + // Dropping down to shoot + if (NPCInfo->localState == LSTATE_DROPPINGDOWN) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_RUN1STOP, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); + TIMER_Set( NPC, "downTime", Q_irand( 3000, 9000) ); + + if ((NPC->client->ps.legsTimer<=0) && NPC->client->ps.torsoAnim == BOTH_RUN1STOP ) + { + NPC->flags |= FL_SHIELDED; + NPCInfo->localState = LSTATE_DOWN; + } + } + // He's down and shooting + else if (NPCInfo->localState == LSTATE_DOWN) + { + NPC->flags |= FL_SHIELDED;//only damagable by lightsabers and missiles + + Mark2_BlasterAttack(qfalse); + } + else if (TIMER_Done( NPC, "runTime" )) // Lowering down to attack. But only if he's done running at you. + { + NPCInfo->localState = LSTATE_DROPPINGDOWN; + } + else if (advance) + { + // We can see enemy so shoot him if timer lets you. + Mark2_BlasterAttack(advance); + } +} + + +/* +------------------------- +Mark2_Patrol +------------------------- +*/ +void Mark2_Patrol( void ) +{ + if ( NPC_CheckPlayerTeamStealth() ) + { +// G_Sound( NPC, G_SoundIndex("sound/chars/mark1/misc/anger.wav")); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + NPC_UpdateAngles( qtrue, qtrue ); + } + + //randomly talk + if (TIMER_Done(NPC,"patrolNoise")) + { +// G_Sound( NPC, G_SoundIndex(va("sound/chars/mark1/misc/talk%d.wav", Q_irand(1, 4)))); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } +} + +/* +------------------------- +Mark2_Idle +------------------------- +*/ +void Mark2_Idle( void ) +{ + NPC_BSIdle(); +} + +/* +------------------------- +NPC_BSMark2_Default +------------------------- +*/ +void NPC_BSMark2_Default( void ) +{ + if ( NPC->enemy ) + { + NPCInfo->goalEntity = NPC->enemy; + Mark2_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Mark2_Patrol(); + } + else + { + Mark2_Idle(); + } +} diff --git a/code/game/NPC_AI_MineMonster.c b/code/game/NPC_AI_MineMonster.c new file mode 100644 index 0000000..edd1f22 --- /dev/null +++ b/code/game/NPC_AI_MineMonster.c @@ -0,0 +1,278 @@ +#include "b_local.h" + +// These define the working combat range for these suckers +#define MIN_DISTANCE 54 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 128 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +/* +------------------------- +NPC_MineMonster_Precache +------------------------- +*/ +void NPC_MineMonster_Precache( void ) +{ + int i; + + for ( i = 0; i < 4; i++ ) + { + G_SoundIndex( va("sound/chars/mine/misc/bite%i.wav", i+1 )); + G_SoundIndex( va("sound/chars/mine/misc/miss%i.wav", i+1 )); + } +} + + +/* +------------------------- +MineMonster_Idle +------------------------- +*/ +void MineMonster_Idle( void ) +{ + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } +} + + +/* +------------------------- +MineMonster_Patrol +------------------------- +*/ +void MineMonster_Patrol( void ) +{ + vec3_t dif; + + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + else + { + if ( TIMER_Done( NPC, "patrolTime" )) + { + TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 ); + } + } + + //rwwFIXMEFIXME: Care about all clients, not just client 0 + VectorSubtract( g_entities[0].r.currentOrigin, NPC->r.currentOrigin, dif ); + + if ( VectorLengthSquared( dif ) < 256 * 256 ) + { + G_SetEnemy( NPC, &g_entities[0] ); + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + MineMonster_Idle(); + return; + } +} + +/* +------------------------- +MineMonster_Move +------------------------- +*/ +void MineMonster_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + NPC_MoveToGoal( qtrue ); + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + } +} + +//--------------------------------------------------------- +void MineMonster_TryDamage( gentity_t *enemy, int damage ) +{ + vec3_t end, dir; + trace_t tr; + + if ( !enemy ) + { + return; + } + + AngleVectors( NPC->client->ps.viewangles, dir, NULL, NULL ); + VectorMA( NPC->r.currentOrigin, MIN_DISTANCE, dir, end ); + + // Should probably trace from the mouth, but, ah well. + trap_Trace( &tr, NPC->r.currentOrigin, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + + if ( tr.entityNum >= 0 && tr.entityNum < ENTITYNUM_NONE ) + { + G_Damage( &g_entities[tr.entityNum], NPC, NPC, dir, tr.endpos, damage, DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + G_Sound( NPC, CHAN_AUTO, G_EffectIndex(va("sound/chars/mine/misc/bite%i.wav", Q_irand(1,4)))); + } + else + { + G_Sound( NPC, CHAN_AUTO, G_EffectIndex(va("sound/chars/mine/misc/miss%i.wav", Q_irand(1,4)))); + } +} + +//------------------------------ +void MineMonster_Attack( void ) +{ + if ( !TIMER_Exists( NPC, "attacking" )) + { + // usually try and play a jump attack if the player somehow got above them....or just really rarely + if ( NPC->enemy && ((NPC->enemy->r.currentOrigin[2] - NPC->r.currentOrigin[2] > 10 && random() > 0.1f ) + || random() > 0.8f )) + { + // Going to do ATTACK4 + TIMER_Set( NPC, "attacking", 1750 + random() * 200 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK4, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack2_dmg", 950 ); // level two damage + } + else if ( random() > 0.5f ) + { + if ( random() > 0.8f ) + { + // Going to do ATTACK3, (rare) + TIMER_Set( NPC, "attacking", 850 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack2_dmg", 400 ); // level two damage + } + else + { + // Going to do ATTACK1 + TIMER_Set( NPC, "attacking", 850 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack1_dmg", 450 ); // level one damage + } + } + else + { + // Going to do ATTACK2 + TIMER_Set( NPC, "attacking", 1250 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + TIMER_Set( NPC, "attack1_dmg", 700 ); // level one damage + } + } + else + { + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + if ( TIMER_Done2( NPC, "attack1_dmg", qtrue )) + { + MineMonster_TryDamage( NPC->enemy, 5 ); + } + else if ( TIMER_Done2( NPC, "attack2_dmg", qtrue )) + { + MineMonster_TryDamage( NPC->enemy, 10 ); + } + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); +} + +//---------------------------------- +void MineMonster_Combat( void ) +{ + float distance; + qboolean advance; + + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS4( NPC->enemy ) || UpdateGoal( )) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + NPC_MoveToGoal( qtrue ); + return; + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + distance = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + + advance = (qboolean)( distance > MIN_DISTANCE_SQR ? qtrue : qfalse ); + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + MineMonster_Move( 1 ); + } + } + else + { + MineMonster_Attack(); + } +} + +/* +------------------------- +NPC_MineMonster_Pain +------------------------- +*/ +void NPC_MineMonster_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + G_AddEvent( self, EV_PAIN, floor((float)self->health/self->client->pers.maxHealth*100.0f) ); + + if ( damage >= 10 ) + { + TIMER_Remove( self, "attacking" ); + TIMER_Remove( self, "attacking1_dmg" ); + TIMER_Remove( self, "attacking2_dmg" ); + TIMER_Set( self, "takingPain", 1350 ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } +} + + +/* +------------------------- +NPC_BSMineMonster_Default +------------------------- +*/ +void NPC_BSMineMonster_Default( void ) +{ + if ( NPC->enemy ) + { + MineMonster_Combat(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + MineMonster_Patrol(); + } + else + { + MineMonster_Idle(); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/code/game/NPC_AI_Rancor.c b/code/game/NPC_AI_Rancor.c new file mode 100644 index 0000000..450406f --- /dev/null +++ b/code/game/NPC_AI_Rancor.c @@ -0,0 +1,955 @@ +// leave this line at the top of all AI_xxxx.cpp files for PCH reasons... +#include "g_headers.h" + + +#include "b_local.h" + +extern void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex ); + +// These define the working combat range for these suckers +#define MIN_DISTANCE 128 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 1024 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +void Rancor_SetBolts( gentity_t *self ) +{ + if ( self && self->client ) + { + renderInfo_t *ri = &self->client->renderInfo; + ri->handRBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*r_hand" ); + ri->handLBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*l_hand" ); + ri->headBolt = trap_G2API_AddBolt( self->ghoul2, 0, "*head_eyes" ); + ri->torsoBolt = trap_G2API_AddBolt( self->ghoul2, 0, "jaw_bone" ); + } +} + +/* +------------------------- +NPC_Rancor_Precache +------------------------- +*/ +void NPC_Rancor_Precache( void ) +{ + int i; + for ( i = 1; i < 3; i ++ ) + { + G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", i) ); + } + G_SoundIndex( "sound/chars/rancor/swipehit.wav" ); + G_SoundIndex( "sound/chars/rancor/chomp.wav" ); +} + + +/* +------------------------- +Rancor_Idle +------------------------- +*/ +void Rancor_Idle( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } +} + + +qboolean Rancor_CheckRoar( gentity_t *self ) +{ + if ( !self->wait ) + {//haven't ever gotten mad yet + self->wait = 1;//do this only once + self->client->ps.eFlags2 |= EF2_ALERTED; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( self, "rageTime", self->client->ps.legsTimer ); + return qtrue; + } + return qfalse; +} +/* +------------------------- +Rancor_Patrol +------------------------- +*/ +void Rancor_Patrol( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + else + { + if ( TIMER_Done( NPC, "patrolTime" )) + { + TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 ); + } + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Rancor_Idle(); + return; + } + Rancor_CheckRoar( NPC ); + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); +} + +/* +------------------------- +Rancor_Move +------------------------- +*/ +void Rancor_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + if ( !NPC_MoveToGoal( qtrue ) ) + { + NPCInfo->consecutiveBlockedMoves++; + } + else + { + NPCInfo->consecutiveBlockedMoves = 0; + } + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + } +} + +//--------------------------------------------------------- +//extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_Knockdown( gentity_t *victim ); +extern void G_Dismember( gentity_t *ent, gentity_t *enemy, vec3_t point, int limbType, float limbRollBase, float limbPitchBase, int deathAnim, qboolean postDeath ); +//extern qboolean G_DoDismemberment( gentity_t *self, vec3_t point, int mod, int damage, int hitLoc, qboolean force ); +extern float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex ); +extern int NPC_GetEntsNearBolt( int *radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); + +void Rancor_DropVictim( gentity_t *self ) +{ + //FIXME: if Rancor dies, it should drop its victim. + //FIXME: if Rancor is removed, it must remove its victim. + if ( self->activator ) + { + if ( self->activator->client ) + { + self->activator->client->ps.eFlags2 &= ~EF2_HELD_BY_MONSTER; + self->activator->client->ps.hasLookTarget = qfalse; + self->activator->client->ps.lookTarget = ENTITYNUM_NONE; + self->activator->client->ps.viewangles[ROLL] = 0; + SetClientViewAngle( self->activator, self->activator->client->ps.viewangles ); + self->activator->r.currentAngles[PITCH] = self->activator->r.currentAngles[ROLL] = 0; + G_SetAngles( self->activator, self->activator->r.currentAngles ); + } + if ( self->activator->health <= 0 ) + { + //if ( self->activator->s.number ) + {//never free player + if ( self->count == 1 ) + {//in my hand, just drop them + if ( self->activator->client ) + { + self->activator->client->ps.legsTimer = self->activator->client->ps.torsoTimer = 0; + //FIXME: ragdoll? + } + } + else + { + if ( self->activator->client ) + { + self->activator->client->ps.eFlags |= EF_NODRAW;//so his corpse doesn't drop out of me... + } + //G_FreeEntity( self->activator ); + } + } + } + else + { + if ( self->activator->NPC ) + {//start thinking again + self->activator->NPC->nextBStateThink = level.time; + } + //clear their anim and let them fall + self->activator->client->ps.legsTimer = self->activator->client->ps.torsoTimer = 0; + } + if ( self->enemy == self->activator ) + { + self->enemy = NULL; + } + self->activator = NULL; + } + self->count = 0;//drop him +} + +void Rancor_Swing( qboolean tryGrab ) +{ + int radiusEntNums[128]; + int numEnts; + const float radius = 88; + const float radiusSquared = (radius*radius); + int i; + vec3_t boltOrg; + + numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.handRBolt, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; + if ( !radiusEnt->inuse ) + { + continue; + } + + if ( radiusEnt == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnt->client == NULL ) + {//must be a client + continue; + } + + if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//can't be one already being held + continue; + } + + if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) + { + if ( tryGrab + && NPC->count != 1 //don't have one in hand or in mouth already - FIXME: allow one in hand and any number in mouth! + && radiusEnt->client->NPC_class != CLASS_RANCOR + && radiusEnt->client->NPC_class != CLASS_GALAKMECH + && radiusEnt->client->NPC_class != CLASS_ATST + && radiusEnt->client->NPC_class != CLASS_GONK + && radiusEnt->client->NPC_class != CLASS_R2D2 + && radiusEnt->client->NPC_class != CLASS_R5D2 + && radiusEnt->client->NPC_class != CLASS_MARK1 + && radiusEnt->client->NPC_class != CLASS_MARK2 + && radiusEnt->client->NPC_class != CLASS_MOUSE + && radiusEnt->client->NPC_class != CLASS_PROBE + && radiusEnt->client->NPC_class != CLASS_SEEKER + && radiusEnt->client->NPC_class != CLASS_REMOTE + && radiusEnt->client->NPC_class != CLASS_SENTRY + && radiusEnt->client->NPC_class != CLASS_INTERROGATOR + && radiusEnt->client->NPC_class != CLASS_VEHICLE ) + {//grab + if ( NPC->count == 2 ) + {//have one in my mouth, remove him + TIMER_Remove( NPC, "clearGrabbed" ); + Rancor_DropVictim( NPC ); + } + NPC->enemy = radiusEnt;//make him my new best friend + radiusEnt->client->ps.eFlags2 |= EF2_HELD_BY_MONSTER; + //FIXME: this makes it so that the victim can't hit us with shots! Just use activator or something + radiusEnt->client->ps.hasLookTarget = qtrue; + radiusEnt->client->ps.lookTarget = NPC->s.number; + NPC->activator = radiusEnt;//remember him + NPC->count = 1;//in my hand + //wait to attack + TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + Q_irand(500, 2500) ); + if ( radiusEnt->health > 0 && radiusEnt->pain ) + {//do pain on enemy + radiusEnt->pain( radiusEnt, NPC, 100 ); + //GEntity_PainFunc( radiusEnt, NPC, NPC, radiusEnt->r.currentOrigin, 0, MOD_CRUSH ); + } + else if ( radiusEnt->client ) + { + radiusEnt->client->ps.forceHandExtend = HANDEXTEND_NONE; + radiusEnt->client->ps.forceHandExtendTime = 0; + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + } + else + {//smack + vec3_t pushDir; + vec3_t angs; + + G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + //actually push the enemy + /* + //VectorSubtract( radiusEnt->r.currentOrigin, boltOrg, pushDir ); + VectorSubtract( radiusEnt->r.currentOrigin, NPC->r.currentOrigin, pushDir ); + pushDir[2] = Q_flrand( 100, 200 ); + VectorNormalize( pushDir ); + */ + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += flrand( 25, 50 ); + angs[PITCH] = flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + if ( radiusEnt->client->NPC_class != CLASS_RANCOR + && radiusEnt->client->NPC_class != CLASS_ATST ) + { + G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + G_Throw( radiusEnt, pushDir, 250 ); + if ( radiusEnt->health > 0 ) + {//do pain on enemy + G_Knockdown( radiusEnt );//, NPC, pushDir, 100, qtrue ); + } + } + } + } + } +} + +void Rancor_Smash( void ) +{ + int radiusEntNums[128]; + int numEnts; + const float radius = 128; + const float halfRadSquared = ((radius/2)*(radius/2)); + const float radiusSquared = (radius*radius); + float distSq; + int i; + vec3_t boltOrg; + + AddSoundEvent( NPC, NPC->r.currentOrigin, 512, AEL_DANGER, qfalse );//, qtrue ); + + numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.handLBolt, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; + if ( !radiusEnt->inuse ) + { + continue; + } + + if ( radiusEnt == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnt->client == NULL ) + {//must be a client + continue; + } + + if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//can't be one being held + continue; + } + + distSq = DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ); + if ( distSq <= radiusSquared ) + { + G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + if ( distSq < halfRadSquared ) + {//close enough to do damage, too + G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 10, 25 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + } + if ( radiusEnt->health > 0 + && radiusEnt->client + && radiusEnt->client->NPC_class != CLASS_RANCOR + && radiusEnt->client->NPC_class != CLASS_ATST ) + { + if ( distSq < halfRadSquared + || radiusEnt->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//within range of my fist or withing ground-shaking range and not in the air + G_Knockdown( radiusEnt );//, NPC, vec3_origin, 100, qtrue ); + } + } + } + } +} + +void Rancor_Bite( void ) +{ + int radiusEntNums[128]; + int numEnts; + const float radius = 100; + const float radiusSquared = (radius*radius); + int i; + vec3_t boltOrg; + + numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, NPC->client->renderInfo.crotchBolt, boltOrg );//was gutBolt? + + for ( i = 0; i < numEnts; i++ ) + { + gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; + if ( !radiusEnt->inuse ) + { + continue; + } + + if ( radiusEnt == NPC ) + {//Skip the rancor ent + continue; + } + + if ( radiusEnt->client == NULL ) + {//must be a client + continue; + } + + if ( (radiusEnt->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//can't be one already being held + continue; + } + + if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) + { + G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, Q_irand( 15, 30 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + if ( radiusEnt->health <= 0 && radiusEnt->client ) + {//killed them, chance of dismembering + if ( !Q_irand( 0, 1 ) ) + {//bite something off + int hitLoc = Q_irand( G2_MODELPART_HEAD, G2_MODELPART_RLEG ); + if ( hitLoc == G2_MODELPART_HEAD ) + { + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else if ( hitLoc == G2_MODELPART_WAIST ) + { + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + //radiusEnt->client->dismembered = qfalse; + //FIXME: the limb should just disappear, cuz I ate it + G_Dismember( radiusEnt, NPC, radiusEnt->r.currentOrigin, hitLoc, 90, 0, radiusEnt->client->ps.torsoAnim, qtrue); + //G_DoDismemberment( radiusEnt, radiusEnt->r.currentOrigin, MOD_SABER, 1000, hitLoc, qtrue ); + } + } + G_Sound( radiusEnt, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); + } + } +} +//------------------------------ +extern void TossClientItems( gentity_t *self ); +void Rancor_Attack( float distance, qboolean doCharge ) +{ + if ( !TIMER_Exists( NPC, "attacking" ) ) + { + if ( NPC->count == 2 && NPC->activator ) + { + } + else if ( NPC->count == 1 && NPC->activator ) + {//holding enemy + if ( NPC->activator->health > 0 && Q_irand( 0, 1 ) ) + {//quick bite + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 450 ); + } + else + {//full eat + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 900 ); + //Make victim scream in fright + if ( NPC->activator->health > 0 && NPC->activator->client ) + { + G_AddEvent( NPC->activator, Q_irand(EV_DEATH1, EV_DEATH3), 0 ); + NPC_SetAnim( NPC->activator, SETANIM_TORSO, BOTH_FALLDEATH1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + if ( NPC->activator->NPC ) + {//no more thinking for you + TossClientItems( NPC ); + NPC->activator->NPC->nextBStateThink = Q3_INFINITE; + } + } + } + } + else if ( NPC->enemy->health > 0 && doCharge ) + {//charge + vec3_t fwd, yawAng; + VectorSet( yawAng, 0, NPC->client->ps.viewangles[YAW], 0 ); + AngleVectors( yawAng, fwd, NULL, NULL ); + VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 150; + NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; + + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 1250 ); + } + else if ( !Q_irand(0, 1) ) + {//smash + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_MELEE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 1000 ); + } + else + {//try to grab + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 1000 ); + } + + TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + random() * 200 ); + } + + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + + if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) + { + vec3_t shakePos; + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_MELEE1: + Rancor_Smash(); + G_GetBoltPosition( NPC, NPC->client->renderInfo.handLBolt, shakePos, 0 ); + G_ScreenShake( shakePos, NULL, 4.0f, 1000, qfalse ); + //CGCam_Shake( 1.0f*playerDist/128.0f, 1000 ); + break; + case BOTH_MELEE2: + Rancor_Bite(); + TIMER_Set( NPC, "attack_dmg2", 450 ); + break; + case BOTH_ATTACK1: + if ( NPC->count == 1 && NPC->activator ) + { + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, Q_irand( 25, 40 ), DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK, MOD_MELEE ); + if ( NPC->activator->health <= 0 ) + {//killed him + //make it look like we bit his head off + //NPC->activator->client->dismembered = qfalse; + G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_HEAD, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue); + //G_DoDismemberment( NPC->activator, NPC->activator->r.currentOrigin, MOD_SABER, 1000, HL_HEAD, qtrue ); + NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; + NPC->activator->client->ps.forceHandExtendTime = 0; + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); + } + break; + case BOTH_ATTACK2: + //try to grab + Rancor_Swing( qtrue ); + break; + case BOTH_ATTACK3: + if ( NPC->count == 1 && NPC->activator ) + { + //cut in half + if ( NPC->activator->client ) + { + //NPC->activator->client->dismembered = qfalse; + G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue); + //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); + } + //KILL + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, NPC->enemy->health+10, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE );// + if ( NPC->activator->client ) + { + NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; + NPC->activator->client->ps.forceHandExtendTime = 0; + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + TIMER_Set( NPC, "attack_dmg2", 1350 ); + G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); + } + break; + } + } + else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_MELEE1: + break; + case BOTH_MELEE2: + Rancor_Bite(); + break; + case BOTH_ATTACK1: + break; + case BOTH_ATTACK2: + break; + case BOTH_ATTACK3: + if ( NPC->count == 1 && NPC->activator ) + {//swallow victim + G_Sound( NPC->activator, CHAN_AUTO, G_SoundIndex( "sound/chars/rancor/chomp.wav" ) ); + //FIXME: sometimes end up with a live one in our mouths? + //just make sure they're dead + if ( NPC->activator->health > 0 ) + { + //cut in half + //NPC->activator->client->dismembered = qfalse; + G_Dismember( NPC->activator, NPC, NPC->activator->r.currentOrigin, G2_MODELPART_WAIST, 90, 0, NPC->activator->client->ps.torsoAnim, qtrue); + //G_DoDismemberment( NPC->activator, NPC->enemy->r.currentOrigin, MOD_SABER, 1000, HL_WAIST, qtrue ); + //KILL + G_Damage( NPC->activator, NPC, NPC, vec3_origin, NPC->activator->r.currentOrigin, NPC->enemy->health+10, DAMAGE_NO_PROTECTION|DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK|DAMAGE_NO_HIT_LOC, MOD_MELEE );//, HL_NONE ); + NPC->activator->client->ps.forceHandExtend = HANDEXTEND_NONE; + NPC->activator->client->ps.forceHandExtendTime = 0; + NPC_SetAnim( NPC->activator, SETANIM_BOTH, BOTH_SWIM_IDLE1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + G_AddEvent( NPC->activator, EV_JUMP, NPC->activator->health ); + } + if ( NPC->activator->client ) + {//*sigh*, can't get tags right, just remove them? + NPC->activator->client->ps.eFlags |= EF_NODRAW; + } + NPC->count = 2; + TIMER_Set( NPC, "clearGrabbed", 2600 ); + } + break; + } + } + else if ( NPC->client->ps.legsAnim == BOTH_ATTACK2 ) + { + if ( NPC->client->ps.legsTimer >= 1200 && NPC->client->ps.legsTimer <= 1350 ) + { + if ( Q_irand( 0, 2 ) ) + { + Rancor_Swing( qfalse ); + } + else + { + Rancor_Swing( qtrue ); + } + } + else if ( NPC->client->ps.legsTimer >= 1100 && NPC->client->ps.legsTimer <= 1550 ) + { + Rancor_Swing( qtrue ); + } + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); +} + +//---------------------------------- +void Rancor_Combat( void ) +{ + if ( NPC->count ) + {//holding my enemy + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Rancor_Attack( 0, qfalse ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS4( NPC->enemy ) )//|| UpdateGoal( )) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MIN_DISTANCE;//MAX_DISTANCE; // just get us within combat range + + if ( !NPC_MoveToGoal( qtrue ) ) + {//couldn't go after him? Look for a new one + TIMER_Set( NPC, "lookForNewEnemy", 0 ); + NPCInfo->consecutiveBlockedMoves++; + } + else + { + NPCInfo->consecutiveBlockedMoves = 0; + } + return; + } + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + NPC_FaceEnemy( qtrue ); + + { + float distance; + qboolean advance; + qboolean doCharge; + + distance = Distance( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + advance = (qboolean)( distance > (NPC->r.maxs[0]+MIN_DISTANCE) ? qtrue : qfalse ); + doCharge = qfalse; + + if ( advance ) + {//have to get closer + vec3_t yawOnlyAngles; + VectorSet( yawOnlyAngles, 0, NPC->r.currentAngles[YAW], 0 ); + if ( NPC->enemy->health > 0 + && fabs(distance-250) <= 80 + && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, yawOnlyAngles, 30, 30 ) ) + { + if ( !Q_irand( 0, 9 ) ) + {//go for the charge + doCharge = qtrue; + advance = qfalse; + } + } + } + + if (( advance /*|| NPCInfo->localState == LSTATE_WAITING*/ ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Rancor_Move( 1 ); + } + } + else + { + Rancor_Attack( distance, doCharge ); + } + } +} + +/* +------------------------- +NPC_Rancor_Pain +------------------------- +*/ +//void NPC_Rancor_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +void NPC_Rancor_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + qboolean hitByRancor = qfalse; + if ( attacker&&attacker->client&&attacker->client->NPC_class==CLASS_RANCOR ) + { + hitByRancor = qtrue; + } + if ( attacker + && attacker->inuse + && attacker != self->enemy + && !(attacker->flags&FL_NOTARGET) ) + { + if ( !self->count ) + { + if ( (!attacker->s.number&&!Q_irand(0,3)) + || !self->enemy + || self->enemy->health == 0 + || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_RANCOR) + || (self->NPC && self->NPC->consecutiveBlockedMoves>=10 && DistanceSquared( attacker->r.currentOrigin, self->r.currentOrigin ) < DistanceSquared( self->enemy->r.currentOrigin, self->r.currentOrigin )) ) + {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker + //FIXME: if can't nav to my enemy, take this guy if I can nav to him + G_SetEnemy( self, attacker ); + TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + if ( hitByRancor ) + {//stay mad at this Rancor for 2-5 secs before looking for attacker enemies + TIMER_Set( self, "rancorInfight", Q_irand( 2000, 5000 ) ); + } + + } + } + } + if ( (hitByRancor|| (self->count==1&&self->activator&&!Q_irand(0,4)) || Q_irand( 0, 200 ) < damage )//hit by rancor, hit while holding live victim, or took a lot of damage + && self->client->ps.legsAnim != BOTH_STAND1TO2 + && TIMER_Done( self, "takingPain" ) ) + { + if ( !Rancor_CheckRoar( self ) ) + { + if ( self->client->ps.legsAnim != BOTH_MELEE1 + && self->client->ps.legsAnim != BOTH_MELEE2 + && self->client->ps.legsAnim != BOTH_ATTACK2 ) + {//cant interrupt one of the big attack anims + /* + if ( self->count != 1 + || attacker == self->activator + || (self->client->ps.legsAnim != BOTH_ATTACK1&&self->client->ps.legsAnim != BOTH_ATTACK3) ) + */ + {//if going to bite our victim, only victim can interrupt that anim + if ( self->health > 100 || hitByRancor ) + { + TIMER_Remove( self, "attacking" ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( self->count == 1 ) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + TIMER_Set( self, "takingPain", self->client->ps.legsTimer+Q_irand(0, 500) ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } + } + } + } + //let go + /* + if ( !Q_irand( 0, 3 ) && self->count == 1 ) + { + Rancor_DropVictim( self ); + } + */ + } +} + +void Rancor_CheckDropVictim( void ) +{ + vec3_t mins; + vec3_t maxs; + vec3_t start; + vec3_t end; + trace_t trace; + + VectorSet( mins, NPC->activator->r.mins[0]-1, NPC->activator->r.mins[1]-1, 0 ); + VectorSet( maxs, NPC->activator->r.maxs[0]+1, NPC->activator->r.maxs[1]+1, 1 ); + VectorSet( start, NPC->activator->r.currentOrigin[0], NPC->activator->r.currentOrigin[1], NPC->activator->r.absmin[2] ); + VectorSet( end, NPC->activator->r.currentOrigin[0], NPC->activator->r.currentOrigin[1], NPC->activator->r.absmax[2]-1 ); + + trap_Trace( &trace, start, mins, maxs, end, NPC->activator->s.number, NPC->activator->clipmask ); + if ( !trace.allsolid && !trace.startsolid && trace.fraction >= 1.0f ) + { + Rancor_DropVictim( NPC ); + } +} + +//if he's stepping on things then crush them -rww +void Rancor_Crush(void) +{ + gentity_t *crush; + + if (!NPC || + !NPC->client || + NPC->client->ps.groundEntityNum >= ENTITYNUM_WORLD) + { //nothing to crush + return; + } + + crush = &g_entities[NPC->client->ps.groundEntityNum]; + if (crush->inuse && crush->client && !crush->localAnimIndex) + { //a humanoid, smash them good. + G_Damage(crush, NPC, NPC, NULL, NPC->r.currentOrigin, 200, 0, MOD_CRUSH); + } +} + +/* +------------------------- +NPC_BSRancor_Default +------------------------- +*/ +void NPC_BSRancor_Default( void ) +{ + AddSightEvent( NPC, NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, 50 ); + + Rancor_Crush(); + + NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG); + if ( NPC->count ) + {//holding someone + NPC->client->ps.eFlags2 |= EF2_USE_ALT_ANIM; + if ( NPC->count == 2 ) + {//in my mouth + NPC->client->ps.eFlags2 |= EF2_GENERIC_NPC_FLAG; + } + } + else + { + NPC->client->ps.eFlags2 &= ~(EF2_USE_ALT_ANIM|EF2_GENERIC_NPC_FLAG); + } + + if ( TIMER_Done2( NPC, "clearGrabbed", qtrue ) ) + { + Rancor_DropVictim( NPC ); + } + else if ( NPC->client->ps.legsAnim == BOTH_PAIN2 + && NPC->count == 1 + && NPC->activator ) + { + if ( !Q_irand( 0, 3 ) ) + { + Rancor_CheckDropVictim(); + } + } + if ( !TIMER_Done( NPC, "rageTime" ) ) + {//do nothing but roar first time we see an enemy + AddSoundEvent( NPC, NPC->r.currentOrigin, 1024, AEL_DANGER_GREAT, qfalse );//, qfalse ); + NPC_FaceEnemy( qtrue ); + return; + } + if ( NPC->enemy ) + { + /* + if ( NPC->enemy->client //enemy is a client + && (NPC->enemy->client->NPC_class == CLASS_UGNAUGHT || NPC->enemy->client->NPC_class == CLASS_JAWA )//enemy is a lowly jawa or ugnaught + && NPC->enemy->enemy != NPC//enemy's enemy is not me + && (!NPC->enemy->enemy || !NPC->enemy->enemy->client || NPC->enemy->enemy->client->NPC_class!=CLASS_RANCOR) )//enemy's enemy is not a client or is not a rancor (which is as scary as me anyway) + {//they should be scared of ME and no-one else + G_SetEnemy( NPC->enemy, NPC ); + } + */ + if ( TIMER_Done(NPC,"angrynoise") ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/misc/anger%d.wav", Q_irand(1, 3))) ); + + TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); + } + else + { + AddSoundEvent( NPC, NPC->r.currentOrigin, 512, AEL_DANGER_GREAT, qfalse );//, qfalse ); + } + if ( NPC->count == 2 && NPC->client->ps.legsAnim == BOTH_ATTACK3 ) + {//we're still chewing our enemy up + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while + if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_RANCOR ) + {//got mad at another Rancor, look for a valid enemy + if ( TIMER_Done( NPC, "rancorInfight" ) ) + { + NPC_CheckEnemyExt( qtrue ); + } + } + else if ( !NPC->count ) + { + if ( ValidEnemy( NPC->enemy ) == qfalse ) + { + TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now + if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) + {//it's been a while since the enemy died, or enemy is completely gone, get bored with him + NPC->enemy = NULL; + Rancor_Patrol(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) + { + gentity_t *newEnemy, *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + NPC->enemy = NULL; + newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + //hold this one for at least 5-15 seconds + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + } + else + {//look again in 2-5 secs + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); + } + } + } + Rancor_Combat(); + } + else + { + if ( TIMER_Done(NPC,"idlenoise") ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( va("sound/chars/rancor/snort_%d.wav", Q_irand(1, 2))) ); + + TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); + AddSoundEvent( NPC, NPC->r.currentOrigin, 384, AEL_DANGER, qfalse );//, qfalse ); + } + if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Rancor_Patrol(); + } + else + { + Rancor_Idle(); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/code/game/NPC_AI_Remote.c b/code/game/NPC_AI_Remote.c new file mode 100644 index 0000000..aa1383d --- /dev/null +++ b/code/game/NPC_AI_Remote.c @@ -0,0 +1,389 @@ +#include "b_local.h" +#include "g_nav.h" + +void Remote_Strafe( void ); + +#define VELOCITY_DECAY 0.85f + + +//Local state enums +enum +{ + LSTATE_NONE = 0, +}; + +void Remote_Idle( void ); + +void NPC_Remote_Precache(void) +{ + G_SoundIndex("sound/chars/remote/misc/fire.wav"); + G_SoundIndex( "sound/chars/remote/misc/hiss.wav"); + G_EffectIndex( "env/small_explode"); +} + +/* +------------------------- +NPC_Remote_Pain +------------------------- +*/ +void NPC_Remote_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + SaveNPCGlobals(); + SetNPCGlobals( self ); + Remote_Strafe(); + RestoreNPCGlobals(); + + NPC_Pain( self, attacker, damage ); +} + +/* +------------------------- +Remote_MaintainHeight +------------------------- +*/ +void Remote_MaintainHeight( void ) +{ + float dif; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + // If we have an enemy, we should try to hover at or a little below enemy eye level + if ( NPC->enemy ) + { + if (TIMER_Done( NPC, "heightChange")) + { + TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); + + // Find the height difference + dif = (NPC->enemy->r.currentOrigin[2] + Q_irand( 0, NPC->enemy->r.maxs[2]+8 )) - NPC->r.currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 2 ) + { + if ( fabs( dif ) > 24 ) + { + dif = ( dif < 0 ? -24 : 24 ); + } + dif *= 10; + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + // NPC->fx_time = level.time; + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); + } + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + dif = ( dif < 0 ? -24 : 24 ); + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +#define REMOTE_STRAFE_VEL 256 +#define REMOTE_STRAFE_DIS 200 +#define REMOTE_UPWARD_PUSH 32 + +/* +------------------------- +Remote_Strafe +------------------------- +*/ +void Remote_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->r.currentOrigin, REMOTE_STRAFE_DIS * dir, right, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, REMOTE_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/remote/misc/hiss.wav")); + + // Add a slight upward push + NPC->client->ps.velocity[2] += REMOTE_UPWARD_PUSH; + + // Set the strafe start time so we can do a controlled roll + // NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +#define REMOTE_FORWARD_BASE_SPEED 10 +#define REMOTE_FORWARD_MULTIPLIER 5 + +/* +------------------------- +Remote_Hunt +------------------------- +*/ +void Remote_Hunt( qboolean visible, qboolean advance, qboolean retreat ) +{ + float distance, speed; + vec3_t forward; + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Remote_Strafe(); + return; + } + } + + //If we don't want to advance, stop here + if ( advance == qfalse && visible == qtrue ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + //Get our direction from the navigator if we can't see our target + if ( NPC_GetMoveDirection( forward, &distance ) == qfalse ) + return; + } + else + { + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = REMOTE_FORWARD_BASE_SPEED + REMOTE_FORWARD_MULTIPLIER * g_spskill.integer; + if ( retreat == qtrue ) + { + speed *= -1; + } + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + + +/* +------------------------- +Remote_Fire +------------------------- +*/ +void Remote_Fire (void) +{ + vec3_t delta1, enemy_org1, muzzle1; + vec3_t angleToEnemy1; + static vec3_t forward, vright, up; + static vec3_t muzzle; + gentity_t *missile; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org1 ); + VectorCopy( NPC->r.currentOrigin, muzzle1 ); + + VectorSubtract (enemy_org1, muzzle1, delta1); + + vectoangles ( delta1, angleToEnemy1 ); + AngleVectors (angleToEnemy1, forward, vright, up); + + missile = CreateMissile( NPC->r.currentOrigin, forward, 1000, 10000, NPC, qfalse ); + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), NPC->r.currentOrigin, forward ); + + missile->classname = "briar"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->damage = 10; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BRYAR_PISTOL; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + +} + +/* +------------------------- +Remote_Ranged +------------------------- +*/ +void Remote_Ranged( qboolean visible, qboolean advance, qboolean retreat ) +{ + + if ( TIMER_Done( NPC, "attackDelay" ) ) // Attack? + { + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 3000 ) ); + Remote_Fire(); + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Remote_Hunt( visible, advance, retreat ); + } +} + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 80 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +/* +------------------------- +Remote_Attack +------------------------- +*/ +void Remote_Attack( void ) +{ + float distance; + qboolean visible; + float idealDist; + qboolean advance; + qboolean retreat; + + if ( TIMER_Done(NPC,"spin") ) + { + TIMER_Set( NPC, "spin", Q_irand( 250, 1500 ) ); + NPCInfo->desiredYaw += Q_irand( -200, 200 ); + } + // Always keep a good height off the ground + Remote_MaintainHeight(); + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse ) + { + Remote_Idle(); + return; + } + + // Rate our distance to the target, and our visibilty + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + idealDist = MIN_DISTANCE_SQR+(MIN_DISTANCE_SQR*flrand( 0, 1 )); + advance = (qboolean)(distance > idealDist*1.25); + retreat = (qboolean)(distance < idealDist*0.75); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Remote_Hunt( visible, advance, retreat ); + return; + } + } + + Remote_Ranged( visible, advance, retreat ); + +} + +/* +------------------------- +Remote_Idle +------------------------- +*/ +void Remote_Idle( void ) +{ + Remote_MaintainHeight(); + + NPC_BSIdle(); +} + +/* +------------------------- +Remote_Patrol +------------------------- +*/ +void Remote_Patrol( void ) +{ + Remote_MaintainHeight(); + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( UpdateGoal() ) + { + //start loop sound once we move + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + + +/* +------------------------- +NPC_BSRemote_Default +------------------------- +*/ +void NPC_BSRemote_Default( void ) +{ + if ( NPC->enemy ) + { + Remote_Attack(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Remote_Patrol(); + } + else + { + Remote_Idle(); + } +} diff --git a/code/game/NPC_AI_Seeker.c b/code/game/NPC_AI_Seeker.c new file mode 100644 index 0000000..bf624d5 --- /dev/null +++ b/code/game/NPC_AI_Seeker.c @@ -0,0 +1,574 @@ +#include "b_local.h" +#include "g_nav.h" + +extern void Boba_FireDecide( void ); + +void Seeker_Strafe( void ); + +#define VELOCITY_DECAY 0.7f + +#define MIN_MELEE_RANGE 320 +#define MIN_MELEE_RANGE_SQR ( MIN_MELEE_RANGE * MIN_MELEE_RANGE ) + +#define MIN_DISTANCE 80 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define SEEKER_STRAFE_VEL 100 +#define SEEKER_STRAFE_DIS 200 +#define SEEKER_UPWARD_PUSH 32 + +#define SEEKER_FORWARD_BASE_SPEED 10 +#define SEEKER_FORWARD_MULTIPLIER 2 + +#define SEEKER_SEEK_RADIUS 1024 + +//------------------------------------ +void NPC_Seeker_Precache(void) +{ + G_SoundIndex("sound/chars/seeker/misc/fire.wav"); + G_SoundIndex( "sound/chars/seeker/misc/hiss.wav"); + G_EffectIndex( "env/small_explode"); +} + +//------------------------------------ +void NPC_Seeker_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + if ( !(self->NPC->aiFlags&NPCAI_CUSTOM_GRAVITY )) + {//void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, vec3_t dir, vec3_t point, int damage, int dflags, int mod, int hitLoc=HL_NONE ); + G_Damage( self, NULL, NULL, (float*)vec3_origin, (float*)vec3_origin, 999, 0, MOD_FALLING ); + } + + SaveNPCGlobals(); + SetNPCGlobals( self ); + Seeker_Strafe(); + RestoreNPCGlobals(); + NPC_Pain( self, attacker, damage ); +} + +//------------------------------------ +void Seeker_MaintainHeight( void ) +{ + float dif; + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at or a little below enemy eye level + if ( NPC->enemy ) + { + if (TIMER_Done( NPC, "heightChange" )) + { + float difFactor; + + TIMER_Set( NPC,"heightChange",Q_irand( 1000, 3000 )); + + // Find the height difference + dif = (NPC->enemy->r.currentOrigin[2] + flrand( NPC->enemy->r.maxs[2]/2, NPC->enemy->r.maxs[2]+8 )) - NPC->r.currentOrigin[2]; + + difFactor = 1.0f; + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( TIMER_Done( NPC, "flameTime" ) ) + { + difFactor = 10.0f; + } + } + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 2*difFactor ) + { + if ( fabs( dif ) > 24*difFactor ) + { + dif = ( dif < 0 ? -24*difFactor : 24*difFactor ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + NPC->client->ps.velocity[2] *= flrand( 0.85f, 3.0f ); + } + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + if ( goal ) + { + dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + if ( fabs( dif ) > 24 ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } +} + +//------------------------------------ +void Seeker_Strafe( void ) +{ + int side; + vec3_t end, right, dir; + trace_t tr; + + if ( random() > 0.7f || !NPC->enemy || !NPC->enemy->client ) + { + // Do a regular style strafe + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonably valid + side = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->r.currentOrigin, SEEKER_STRAFE_DIS * side, right, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + float vel = SEEKER_STRAFE_VEL; + float upPush = SEEKER_UPWARD_PUSH; + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + } + else + { + vel *= 3.0f; + upPush *= 4.0f; + } + VectorMA( NPC->client->ps.velocity, vel*side, right, NPC->client->ps.velocity ); + // Add a slight upward push + NPC->client->ps.velocity[2] += upPush; + + NPCInfo->standTime = level.time + 1000 + random() * 500; + } + } + else + { + float stDis; + + // Do a strafe to try and keep on the side of their enemy + AngleVectors( NPC->enemy->client->renderInfo.eyeAngles, dir, right, NULL ); + + // Pick a random side + side = ( rand() & 1 ) ? -1 : 1; + stDis = SEEKER_STRAFE_DIS; + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + stDis *= 2.0f; + } + VectorMA( NPC->enemy->r.currentOrigin, stDis * side, right, end ); + + // then add a very small bit of random in front of/behind the player action + VectorMA( end, crandom() * 25, dir, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + float dis, upPush; + + VectorSubtract( tr.endpos, NPC->r.currentOrigin, dir ); + dir[2] *= 0.25; // do less upward change + dis = VectorNormalize( dir ); + + // Try to move the desired enemy side + VectorMA( NPC->client->ps.velocity, dis, dir, NPC->client->ps.velocity ); + + upPush = SEEKER_UPWARD_PUSH; + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + } + else + { + upPush *= 4.0f; + } + + // Add a slight upward push + NPC->client->ps.velocity[2] += upPush; + + NPCInfo->standTime = level.time + 2500 + random() * 500; + } + } +} + +//------------------------------------ +void Seeker_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + NPC_FaceEnemy( qtrue ); + + // If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Seeker_Strafe(); + return; + } + } + + // If we don't want to advance, stop here + if ( advance == qfalse ) + { + return; + } + + // Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 24; + + // Get our direction from the navigator if we can't see our target + if ( NPC_GetMoveDirection( forward, &distance ) == qfalse ) + { + return; + } + } + else + { + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = SEEKER_FORWARD_BASE_SPEED + SEEKER_FORWARD_MULTIPLIER * g_spskill.integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +//------------------------------------ +void Seeker_Fire( void ) +{ + vec3_t dir, enemy_org, muzzle; + gentity_t *missile; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); + VectorSubtract( enemy_org, NPC->r.currentOrigin, dir ); + VectorNormalize( dir ); + + // move a bit forward in the direction we shall shoot in so that the bolt doesn't poke out the other side of the seeker + VectorMA( NPC->r.currentOrigin, 15, dir, muzzle ); + + missile = CreateMissile( muzzle, dir, 1000, 10000, NPC, qfalse ); + + G_PlayEffectID( G_EffectIndex("blaster/muzzle_flash"), NPC->r.currentOrigin, dir ); + + missile->classname = "blaster"; + missile->s.weapon = WP_BLASTER; + + missile->damage = 5; + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BLASTER; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + if ( NPC->r.ownerNum < ENTITYNUM_NONE ) + { + missile->r.ownerNum = NPC->r.ownerNum; + } +} + +//------------------------------------ +void Seeker_Ranged( qboolean visible, qboolean advance ) +{ + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( NPC->count > 0 ) + { + if ( TIMER_Done( NPC, "attackDelay" )) // Attack? + { + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 2500 )); + Seeker_Fire(); + NPC->count--; + } + } + else + { + // out of ammo, so let it die...give it a push up so it can fall more and blow up on impact + // NPC->client->ps.gravity = 900; + // NPC->svFlags &= ~SVF_CUSTOM_GRAVITY; + // NPC->client->ps.velocity[2] += 16; + G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN ); + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Seeker_Hunt( visible, advance ); + } +} + +//------------------------------------ +void Seeker_Attack( void ) +{ + float distance; + qboolean visible; + qboolean advance; + + // Always keep a good height off the ground + Seeker_MaintainHeight(); + + // Rate our distance to the target, and our visibilty + distance = DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + advance = (qboolean)(distance>(200.0f*200.0f)); + } + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Seeker_Hunt( visible, advance ); + return; + } + } + + Seeker_Ranged( visible, advance ); +} + +//------------------------------------ +void Seeker_FindEnemy( void ) +{ + int numFound; + float dis, bestDis = SEEKER_SEEK_RADIUS * SEEKER_SEEK_RADIUS + 1; + vec3_t mins, maxs; + int entityList[MAX_GENTITIES]; + gentity_t *ent, *best = NULL; + int i; + + VectorSet( maxs, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS, SEEKER_SEEK_RADIUS ); + VectorScale( maxs, -1, mins ); + + numFound = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + + for ( i = 0 ; i < numFound ; i++ ) + { + ent = &g_entities[entityList[i]]; + + if ( ent->s.number == NPC->s.number + || !ent->client //&& || !ent->NPC + || ent->health <= 0 + || !ent->inuse ) + { + continue; + } + + if ( ent->client->playerTeam == NPC->client->playerTeam || ent->client->playerTeam == NPCTEAM_NEUTRAL ) // don't attack same team or bots + { + continue; + } + + // try to find the closest visible one + if ( !NPC_ClearLOS4( ent )) + { + continue; + } + + dis = DistanceHorizontalSquared( NPC->r.currentOrigin, ent->r.currentOrigin ); + + if ( dis <= bestDis ) + { + bestDis = dis; + best = ent; + } + } + + if ( best ) + { + // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method. + NPC->random = random() * 6.3f; // roughly 2pi + + NPC->enemy = best; + } +} + +//------------------------------------ +void Seeker_FollowOwner( void ) +{ + float dis, minDistSqr; + vec3_t pt, dir; + gentity_t *owner = &g_entities[NPC->s.owner]; + + Seeker_MaintainHeight(); + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + owner = NPC->enemy; + } + if ( !owner || owner == NPC || !owner->client ) + { + return; + } + //rwwFIXMEFIXME: Care about all clients not just 0 + dis = DistanceHorizontalSquared( NPC->r.currentOrigin, owner->r.currentOrigin ); + + minDistSqr = MIN_DISTANCE_SQR; + + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + if ( TIMER_Done( NPC, "flameTime" ) ) + { + minDistSqr = 200*200; + } + } + + if ( dis < minDistSqr ) + { + // generally circle the player closely till we take an enemy..this is our target point + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 250; + pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 250; + if ( NPC->client->jetPackTime < level.time ) + { + pt[2] = NPC->r.currentOrigin[2] - 64; + } + else + { + pt[2] = owner->r.currentOrigin[2] + 200; + } + } + else + { + pt[0] = owner->r.currentOrigin[0] + cos( level.time * 0.001f + NPC->random ) * 56; + pt[1] = owner->r.currentOrigin[1] + sin( level.time * 0.001f + NPC->random ) * 56; + pt[2] = owner->r.currentOrigin[2] + 40; + } + + VectorSubtract( pt, NPC->r.currentOrigin, dir ); + VectorMA( NPC->client->ps.velocity, 0.8f, dir, NPC->client->ps.velocity ); + } + else + { + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + if ( TIMER_Done( NPC, "seekerhiss" )) + { + TIMER_Set( NPC, "seekerhiss", 1000 + random() * 1000 ); + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/seeker/misc/hiss" )); + } + } + + // Hey come back! + NPCInfo->goalEntity = owner; + NPCInfo->goalRadius = 32; + NPC_MoveToGoal( qtrue ); + NPC->parent = owner; + } + + if ( NPCInfo->enemyCheckDebounceTime < level.time ) + { + // check twice a second to find a new enemy + Seeker_FindEnemy(); + NPCInfo->enemyCheckDebounceTime = level.time + 500; + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +//------------------------------------ +void NPC_BSSeeker_Default( void ) +{ + /* + if ( in_camera ) + { + if ( NPC->client->NPC_class != CLASS_BOBAFETT ) + { + // cameras make me commit suicide.... + G_Damage( NPC, NPC, NPC, NULL, NULL, 999, 0, MOD_UNKNOWN ); + } + } + */ + //N/A for MP. + if ( NPC->r.ownerNum < ENTITYNUM_NONE ) + { + gentity_t *owner = &g_entities[0]; + if ( owner->health <= 0 + || (owner->client && owner->client->pers.connected == CON_DISCONNECTED) ) + {//owner is dead or gone + //remove me + G_Damage( NPC, NULL, NULL, NULL, NULL, 10000, DAMAGE_NO_PROTECTION, MOD_TELEFRAG ); + return; + } + } + + if ( NPC->random == 0.0f ) + { + // used to offset seekers around a circle so they don't occupy the same spot. This is not a fool-proof method. + NPC->random = random() * 6.3f; // roughly 2pi + } + + if ( NPC->enemy && NPC->enemy->health && NPC->enemy->inuse ) + { + if ( NPC->client->NPC_class != CLASS_BOBAFETT && ( NPC->enemy->s.number == 0 || ( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_SEEKER )) ) + { + //hacked to never take the player as an enemy, even if the player shoots at it + NPC->enemy = NULL; + } + else + { + Seeker_Attack(); + if ( NPC->client->NPC_class == CLASS_BOBAFETT ) + { + Boba_FireDecide(); + } + return; + } + } + + // In all other cases, follow the player and look for enemies to take on + Seeker_FollowOwner(); +} diff --git a/code/game/NPC_AI_Sentry.c b/code/game/NPC_AI_Sentry.c new file mode 100644 index 0000000..c5f73dd --- /dev/null +++ b/code/game/NPC_AI_Sentry.c @@ -0,0 +1,577 @@ +#include "b_local.h" +#include "g_nav.h" + +#include "../namespace_begin.h" +extern gitem_t *BG_FindItemForAmmo( ammo_t ammo ); +#include "../namespace_end.h" +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); + +#define MIN_DISTANCE 256 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define SENTRY_FORWARD_BASE_SPEED 10 +#define SENTRY_FORWARD_MULTIPLIER 5 + +#define SENTRY_VELOCITY_DECAY 0.85f +#define SENTRY_STRAFE_VEL 256 +#define SENTRY_STRAFE_DIS 200 +#define SENTRY_UPWARD_PUSH 32 +#define SENTRY_HOVER_HEIGHT 24 + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_ASLEEP, + LSTATE_WAKEUP, + LSTATE_ACTIVE, + LSTATE_POWERING_UP, + LSTATE_ATTACKING, +}; + +/* +------------------------- +NPC_Sentry_Precache +------------------------- +*/ +void NPC_Sentry_Precache(void) +{ + int i; + + G_SoundIndex( "sound/chars/sentry/misc/sentry_explo" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_pain" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_open" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_shield_close" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" ); + G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" ); + + for ( i = 1; i < 4; i++) + { + G_SoundIndex( va( "sound/chars/sentry/misc/talk%d", i ) ); + } + + G_EffectIndex( "bryar/muzzle_flash"); + G_EffectIndex( "env/med_explode"); + + RegisterItem( BG_FindItemForAmmo( AMMO_BLASTER )); +} + +/* +================ +sentry_use +================ +*/ +void sentry_use( gentity_t *self, gentity_t *other, gentity_t *activator) +{ + G_ActivateBehavior(self,BSET_USE); + + self->flags &= ~FL_SHIELDED; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +// self->NPC->localState = LSTATE_WAKEUP; + self->NPC->localState = LSTATE_ACTIVE; +} + +/* +------------------------- +NPC_Sentry_Pain +------------------------- +*/ +void NPC_Sentry_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + int mod = gPainMOD; + + NPC_Pain( self, attacker, damage ); + + if ( mod == MOD_DEMP2 || mod == MOD_DEMP2_ALT ) + { + self->NPC->burstCount = 0; + TIMER_Set( self, "attackDelay", Q_irand( 9000, 12000) ); + self->flags |= FL_SHIELDED; + NPC_SetAnim( self, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + G_Sound( self, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_pain") ); + + self->NPC->localState = LSTATE_ACTIVE; + } + + // You got hit, go after the enemy +// if (self->NPC->localState == LSTATE_ASLEEP) +// { +// G_Sound( self, G_SoundIndex("sound/chars/sentry/misc/shieldsopen.wav")); +// +// self->flags &= ~FL_SHIELDED; +// NPC_SetAnim( self, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); +// self->NPC->localState = LSTATE_WAKEUP; +// } +} + +/* +------------------------- +Sentry_Fire +------------------------- +*/ +void Sentry_Fire (void) +{ + vec3_t muzzle; + static vec3_t forward, vright, up; + gentity_t *missile; + mdxaBone_t boltMatrix; + int bolt; + int which; + + NPC->flags &= ~FL_SHIELDED; + + if ( NPCInfo->localState == LSTATE_POWERING_UP ) + { + if ( TIMER_Done( NPC, "powerup" )) + { + NPCInfo->localState = LSTATE_ATTACKING; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + else + { + // can't do anything right now + return; + } + } + else if ( NPCInfo->localState == LSTATE_ACTIVE ) + { + NPCInfo->localState = LSTATE_POWERING_UP; + + G_Sound( NPC, CHAN_AUTO, G_SoundIndex("sound/chars/sentry/misc/sentry_shield_open") ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_POWERUP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "powerup", 250 ); + return; + } + else if ( NPCInfo->localState != LSTATE_ATTACKING ) + { + // bad because we are uninitialized + NPCInfo->localState = LSTATE_ACTIVE; + return; + } + + // Which muzzle to fire from? + which = NPCInfo->burstCount % 3; + switch( which ) + { + case 0: + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash1"); + break; + case 1: + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash2"); + break; + case 2: + default: + bolt = trap_G2API_AddBolt(NPC->ghoul2, 0, "*flash03"); + } + + trap_G2API_GetBoltMatrix( NPC->ghoul2, 0, + bolt, + &boltMatrix, NPC->r.currentAngles, NPC->r.currentOrigin, level.time, + NULL, NPC->modelScale ); + + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, muzzle ); + + AngleVectors( NPC->r.currentAngles, forward, vright, up ); +// G_Sound( NPC, G_SoundIndex("sound/chars/sentry/misc/shoot.wav")); + + G_PlayEffectID( G_EffectIndex("bryar/muzzle_flash"), muzzle, forward ); + + missile = CreateMissile( muzzle, forward, 1600, 10000, NPC, qfalse ); + + missile->classname = "bryar_proj"; + missile->s.weapon = WP_BRYAR_PISTOL; + + missile->dflags = DAMAGE_DEATH_KNOCKBACK; + missile->methodOfDeath = MOD_BRYAR_PISTOL; + missile->clipmask = MASK_SHOT | CONTENTS_LIGHTSABER; + + NPCInfo->burstCount++; + NPC->attackDebounceTime = level.time + 50; + missile->damage = 5; + + // now scale for difficulty + if ( g_spskill.integer == 0 ) + { + NPC->attackDebounceTime += 200; + missile->damage = 1; + } + else if ( g_spskill.integer == 1 ) + { + NPC->attackDebounceTime += 100; + missile->damage = 3; + } +} + +/* +------------------------- +Sentry_MaintainHeight +------------------------- +*/ +void Sentry_MaintainHeight( void ) +{ + float dif; + + NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_1_lp" ); + + // Update our angles regardless + NPC_UpdateAngles( qtrue, qtrue ); + + // If we have an enemy, we should try to hover at about enemy eye level + if ( NPC->enemy ) + { + // Find the height difference + dif = (NPC->enemy->r.currentOrigin[2]+NPC->enemy->r.maxs[2]) - NPC->r.currentOrigin[2]; + + // cap to prevent dramatic height shifts + if ( fabs( dif ) > 8 ) + { + if ( fabs( dif ) > SENTRY_HOVER_HEIGHT ) + { + dif = ( dif < 0 ? -24 : 24 ); + } + + NPC->client->ps.velocity[2] = (NPC->client->ps.velocity[2]+dif)/2; + } + } + else + { + gentity_t *goal = NULL; + + if ( NPCInfo->goalEntity ) // Is there a goal? + { + goal = NPCInfo->goalEntity; + } + else + { + goal = NPCInfo->lastGoalEntity; + } + + if (goal) + { + dif = goal->r.currentOrigin[2] - NPC->r.currentOrigin[2]; + + if ( fabs( dif ) > SENTRY_HOVER_HEIGHT ) + { + ucmd.upmove = ( ucmd.upmove < 0 ? -4 : 4 ); + } + else + { + if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 2 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + } + // Apply friction to Z + else if ( NPC->client->ps.velocity[2] ) + { + NPC->client->ps.velocity[2] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[2] ) < 1 ) + { + NPC->client->ps.velocity[2] = 0; + } + } + } + + // Apply friction + if ( NPC->client->ps.velocity[0] ) + { + NPC->client->ps.velocity[0] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[0] ) < 1 ) + { + NPC->client->ps.velocity[0] = 0; + } + } + + if ( NPC->client->ps.velocity[1] ) + { + NPC->client->ps.velocity[1] *= SENTRY_VELOCITY_DECAY; + + if ( fabs( NPC->client->ps.velocity[1] ) < 1 ) + { + NPC->client->ps.velocity[1] = 0; + } + } + + NPC_FaceEnemy( qtrue ); +} + +/* +------------------------- +Sentry_Idle +------------------------- +*/ +void Sentry_Idle( void ) +{ + Sentry_MaintainHeight(); + + // Is he waking up? + if (NPCInfo->localState == LSTATE_WAKEUP) + { + if (NPC->client->ps.torsoTimer<=0) + { + NPCInfo->scriptFlags |= SCF_LOOK_FOR_ENEMIES; + NPCInfo->burstCount = 0; + } + } + else + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_SLEEP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->flags |= FL_SHIELDED; + + NPC_BSIdle(); + } +} + +/* +------------------------- +Sentry_Strafe +------------------------- +*/ +void Sentry_Strafe( void ) +{ + int dir; + vec3_t end, right; + trace_t tr; + + AngleVectors( NPC->client->renderInfo.eyeAngles, NULL, right, NULL ); + + // Pick a random strafe direction, then check to see if doing a strafe would be + // reasonable valid + dir = ( rand() & 1 ) ? -1 : 1; + VectorMA( NPC->r.currentOrigin, SENTRY_STRAFE_DIS * dir, right, end ); + + trap_Trace( &tr, NPC->r.currentOrigin, NULL, NULL, end, NPC->s.number, MASK_SOLID ); + + // Close enough + if ( tr.fraction > 0.9f ) + { + VectorMA( NPC->client->ps.velocity, SENTRY_STRAFE_VEL * dir, right, NPC->client->ps.velocity ); + + // Add a slight upward push + NPC->client->ps.velocity[2] += SENTRY_UPWARD_PUSH; + + // Set the strafe start time so we can do a controlled roll + // NPC->fx_time = level.time; + NPCInfo->standTime = level.time + 3000 + random() * 500; + } +} + +/* +------------------------- +Sentry_Hunt +------------------------- +*/ +void Sentry_Hunt( qboolean visible, qboolean advance ) +{ + float distance, speed; + vec3_t forward; + + //If we're not supposed to stand still, pursue the player + if ( NPCInfo->standTime < level.time ) + { + // Only strafe when we can see the player + if ( visible ) + { + Sentry_Strafe(); + return; + } + } + + //If we don't want to advance, stop here + if ( !advance && visible ) + return; + + //Only try and navigate if the player is visible + if ( visible == qfalse ) + { + // Move towards our goal + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + + //Get our direction from the navigator if we can't see our target + if ( NPC_GetMoveDirection( forward, &distance ) == qfalse ) + return; + } + else + { + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, forward ); + distance = VectorNormalize( forward ); + } + + speed = SENTRY_FORWARD_BASE_SPEED + SENTRY_FORWARD_MULTIPLIER * g_spskill.integer; + VectorMA( NPC->client->ps.velocity, speed, forward, NPC->client->ps.velocity ); +} + +/* +------------------------- +Sentry_RangedAttack +------------------------- +*/ +void Sentry_RangedAttack( qboolean visible, qboolean advance ) +{ + if ( TIMER_Done( NPC, "attackDelay" ) && NPC->attackDebounceTime < level.time && visible ) // Attack? + { + if ( NPCInfo->burstCount > 6 ) + { + if ( !NPC->fly_sound_debounce_time ) + {//delay closing down to give the player an opening + NPC->fly_sound_debounce_time = level.time + Q_irand( 500, 2000 ); + } + else if ( NPC->fly_sound_debounce_time < level.time ) + { + NPCInfo->localState = LSTATE_ACTIVE; + NPC->fly_sound_debounce_time = NPCInfo->burstCount = 0; + TIMER_Set( NPC, "attackDelay", Q_irand( 2000, 3500) ); + NPC->flags |= FL_SHIELDED; + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_FLY_SHIELDED, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + G_SoundOnEnt( NPC, CHAN_AUTO, "sound/chars/sentry/misc/sentry_shield_close" ); + } + } + else + { + Sentry_Fire(); + } + } + + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Sentry_Hunt( visible, advance ); + } +} + +/* +------------------------- +Sentry_AttackDecision +------------------------- +*/ +void Sentry_AttackDecision( void ) +{ + float distance; + qboolean visible; + qboolean advance; + + // Always keep a good height off the ground + Sentry_MaintainHeight(); + + NPC->s.loopSound = G_SoundIndex( "sound/chars/sentry/misc/sentry_hover_2_lp" ); + + //randomly talk + if ( TIMER_Done(NPC,"patrolNoise") ) + { + if (TIMER_Done(NPC,"angerNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 4000, 10000 ) ); + } + } + + // He's dead. + if (NPC->enemy->health<1) + { + NPC->enemy = NULL; + Sentry_Idle(); + return; + } + + // If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse ) + { + Sentry_Idle(); + return; + } + + // Rate our distance to the target and visibilty + distance = (int) DistanceHorizontalSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + visible = NPC_ClearLOS4( NPC->enemy ); + advance = (qboolean)(distance > MIN_DISTANCE_SQR); + + // If we cannot see our target, move to see it + if ( visible == qfalse ) + { + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + Sentry_Hunt( visible, advance ); + return; + } + } + + NPC_FaceEnemy( qtrue ); + + Sentry_RangedAttack( visible, advance ); +} + +qboolean NPC_CheckPlayerTeamStealth( void ); + +/* +------------------------- +NPC_Sentry_Patrol +------------------------- +*/ +void NPC_Sentry_Patrol( void ) +{ + Sentry_MaintainHeight(); + + //If we have somewhere to go, then do that + if (!NPC->enemy) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( UpdateGoal() ) + { + //start loop sound once we move + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + //randomly talk + if (TIMER_Done(NPC,"patrolNoise")) + { + G_SoundOnEnt( NPC, CHAN_AUTO, va("sound/chars/sentry/misc/talk%d", Q_irand(1, 3)) ); + + TIMER_Set( NPC, "patrolNoise", Q_irand( 2000, 4000 ) ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSSentry_Default +------------------------- +*/ +void NPC_BSSentry_Default( void ) +{ + if ( NPC->targetname ) + { + NPC->use = sentry_use; + } + + if (( NPC->enemy ) && (NPCInfo->localState != LSTATE_WAKEUP)) + { + // Don't attack if waking up or if no enemy + Sentry_AttackDecision(); + } + else if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + NPC_Sentry_Patrol(); + } + else + { + Sentry_Idle(); + } +} diff --git a/code/game/NPC_AI_Sniper.c b/code/game/NPC_AI_Sniper.c new file mode 100644 index 0000000..bb3b965 --- /dev/null +++ b/code/game/NPC_AI_Sniper.c @@ -0,0 +1,864 @@ +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" + +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern qboolean FlyingCreature( gentity_t *ent ); + +#define SPF_NO_HIDE 2 + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 + +#define DISTANCE_SCALE 0.25f +#define DISTANCE_THRESHOLD 0.075f +#define SPEED_SCALE 0.25f +#define FOV_SCALE 0.5f +#define LIGHT_SCALE 0.25f + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS2; +static qboolean enemyCS2; +static qboolean faceEnemy2; +static qboolean move2; +static qboolean shoot2; +static float enemyDist2; + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void Sniper_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); +} + +void NPC_Sniper_PlayConfusionSound( gentity_t *self ) +{//FIXME: make this a custom sound in sound set + if ( self->health > 0 ) + { + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + TIMER_Set( self, "flee", 0 ); + self->NPC->squadState = SQUAD_IDLE; + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} + + +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_Sniper_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, attacker, damage ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void Sniper_HoldPosition( void ) +{ + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + NPCInfo->goalEntity = NULL; + + /*if ( TIMER_Done( NPC, "stand" ) ) + {//FIXME: what if can't shoot from this pos? + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +/* +------------------------- +ST_Move +------------------------- +*/ + +static qboolean Sniper_Move( void ) +{ + qboolean moved; + navInfo_t info; + + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + moved = NPC_MoveToGoal( qtrue ); + + //Get the move info + NAV_GetLastMove( &info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! + if ( info.flags & NIF_COLLISION ) + { + if ( info.blocker == NPC->enemy ) + { + Sniper_HoldPosition(); + } + } + + //If our move failed, then reset + if ( moved == qfalse ) + {//couldn't get to enemy + if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && NPCInfo->goalEntity && NPCInfo->goalEntity == NPC->enemy ) + {//we were running after enemy + //Try to find a combat point that can hit the enemy + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); + int cp; + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 ); + if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + {//okay, try one by the enemy + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 ); + } + //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... + if ( cp != -1 ) + {//found a combat point that has a clear shot to enemy + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + return moved; + } + } + //just hang here + Sniper_HoldPosition(); + } + + return moved; +} + +/* +------------------------- +NPC_BSSniper_Patrol +------------------------- +*/ + +void NPC_BSSniper_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + NPC->count = 0; + + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//Should be auto now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + //Is there danger nearby + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_SUSPICIOUS ); + if ( NPC_CheckForDanger( alertEvent ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + else + {//check for other alert events + //There is an event to look at + if ( alertEvent >= 0 && level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + NPCInfo->lastAlertID = level.alertEvents[alertEvent].ID; + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED ) + { + if ( level.alertEvents[alertEvent].owner && + level.alertEvents[alertEvent].owner->client && + level.alertEvents[alertEvent].owner->health >= 0 && + level.alertEvents[alertEvent].owner->client->playerTeam == NPC->client->enemyTeam ) + {//an enemy + G_SetEnemy( NPC, level.alertEvents[alertEvent].owner ); + //NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*100, (6-NPCInfo->stats.aim)*500 ) ); + } + } + else + {//FIXME: get more suspicious over time? + //Save the position for movement (if necessary) + //FIXME: sound? + VectorCopy( level.alertEvents[alertEvent].position, NPCInfo->investigateGoal ); + NPCInfo->investigateDebounceTime = level.time + Q_irand( 500, 1000 ); + if ( level.alertEvents[alertEvent].level == AEL_SUSPICIOUS ) + {//suspicious looks longer + NPCInfo->investigateDebounceTime += Q_irand( 500, 2500 ); + } + } + } + } + + if ( NPCInfo->investigateDebounceTime > level.time ) + {//FIXME: walk over to it, maybe? Not if not chase enemies flag + //NOTE: stops walking or doing anything else below + vec3_t dir, angles; + float o_yaw, o_pitch; + + VectorSubtract( NPCInfo->investigateGoal, NPC->client->renderInfo.eyePoint, dir ); + vectoangles( dir, angles ); + + o_yaw = NPCInfo->desiredYaw; + o_pitch = NPCInfo->desiredPitch; + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + NPC_UpdateAngles( qtrue, qtrue ); + + NPCInfo->desiredYaw = o_yaw; + NPCInfo->desiredPitch = o_pitch; + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSSniper_Idle +------------------------- +*/ +/* +void NPC_BSSniper_Idle( void ) +{ + //reset our shotcount + NPC->count = 0; + + //FIXME: check for other alert events? + + //Is there danger nearby? + if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue ) ) ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) ); + + NPC_UpdateAngles( qtrue, qtrue ); +} +*/ +/* +------------------------- +ST_CheckMoveState +------------------------- +*/ + +static void Sniper_CheckMoveState( void ) +{ + //See if we're a scout + if ( !(NPCInfo->scriptFlags & SCF_CHASE_ENEMIES) )//NPCInfo->behaviorState == BS_STAND_AND_SHOOT ) + { + if ( NPCInfo->goalEntity == NPC->enemy ) + { + move2 = qfalse; + return; + } + } + //See if we're running away + else if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + if ( TIMER_Done( NPC, "flee" ) ) + { + NPCInfo->squadState = SQUAD_IDLE; + } + else + { + faceEnemy2 = qfalse; + } + } + else if ( NPCInfo->squadState == SQUAD_IDLE ) + { + if ( !NPCInfo->goalEntity ) + { + move2 = qfalse; + return; + } + } + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) || + ( NPCInfo->squadState == SQUAD_SCOUT && enemyLOS2 && enemyDist2 <= 10000 ) ) + { + int newSquadState = SQUAD_STAND_AND_SHOOT; + //we got where we wanted to go, set timers based on why we were running + switch ( NPCInfo->squadState ) + { + case SQUAD_RETREAT://was running away + TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 ); + TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); + newSquadState = SQUAD_COVER; + break; + case SQUAD_TRANSITION://was heading for a combat point + TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); + break; + case SQUAD_SCOUT://was running after player + break; + default: + break; + } + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( (6-NPCInfo->stats.aim)*50, (6-NPCInfo->stats.aim)*100 ) ); //FIXME: Slant for difficulty levels, too? + //don't do something else just yet + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); + //stop fleeing + if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + TIMER_Set( NPC, "flee", -level.time ); + NPCInfo->squadState = SQUAD_IDLE; + } + return; + } + + //keep going, hold of roamTimer until we get there + TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); + } +} + +static void Sniper_ResolveBlockedShot( void ) +{ + if ( TIMER_Done( NPC, "duck" ) ) + {//we're not ducking + if ( TIMER_Done( NPC, "roamTime" ) ) + {//not roaming + //FIXME: try to find another spot from which to hit the enemy + if ( (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) && (!NPCInfo->goalEntity || NPCInfo->goalEntity == NPC->enemy) ) + {//we were running after enemy + //Try to find a combat point that can hit the enemy + int cpFlags = (CP_CLEAR|CP_HAS_ROUTE); + int cp; + + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->r.currentOrigin, cpFlags, 32, -1 ); + if ( cp == -1 && !(NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + {//okay, try one by the enemy + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, NPC->enemy->r.currentOrigin, CP_CLEAR|CP_HAS_ROUTE|CP_HORZ_DIST_COLL, 32, -1 ); + } + //NOTE: there may be a perfectly valid one, just not one within CP_COLLECT_RADIUS of either me or him... + if ( cp != -1 ) + {//found a combat point that has a clear shot to enemy + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) ); + return; + } + } + } + } + /* + else + {//maybe we should stand + if ( TIMER_Done( NPC, "stand" ) ) + {//stand for as long as we'll be here + TIMER_Set( NPC, "stand", Q_irand( 500, 2000 ) ); + return; + } + } + //Hmm, can't resolve this by telling them to duck or telling me to stand + //We need to move! + TIMER_Set( NPC, "roamTime", -1 ); + TIMER_Set( NPC, "stick", -1 ); + TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "attackDelay", Q_irand( 1000, 3000 ) ); + */ +} + +/* +------------------------- +ST_CheckFireState +------------------------- +*/ + +static void Sniper_CheckFireState( void ) +{ + if ( enemyCS2 ) + {//if have a clear shot, always try + return; + } + + if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT ) + {//runners never try to fire at the last pos + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //continue to fire on their last position + if ( !Q_irand( 0, 1 ) && NPCInfo->enemyLastSeenTime && level.time - NPCInfo->enemyLastSeenTime < ((5-NPCInfo->stats.aim)*1000) )//FIXME: incorporate skill too? + { + if ( !VectorCompare( vec3_origin, NPCInfo->enemyLastSeenLocation ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + + VectorNormalize( dir ); + + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot2 = qtrue; + //faceEnemy2 = qfalse; + } + return; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 10000 ) + {//next time we see him, we'll miss few times first + NPC->count = 0; + } +} + +qboolean Sniper_EvaluateShot( int hit ) +{ + gentity_t *hitEnt; + + if ( !NPC->enemy ) + { + return qfalse; + } + + hitEnt = &g_entities[hit]; + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) + || ( hitEnt && (hitEnt->r.svFlags&SVF_GLASS_BRUSH)) ) + {//can hit enemy or will hit glass, so shoot anyway + return qtrue; + } + return qfalse; +} + +void Sniper_FaceEnemy( void ) +{ + //FIXME: the ones behind kill holes are facing some arbitrary direction and not firing + //FIXME: If actually trying to hit enemy, don't fire unless enemy is at least in front of me? + //FIXME: need to give designers option to make them not miss first few shots + if ( NPC->enemy ) + { + vec3_t muzzle, target, angles, forward, right, up; + //Get the positions + AngleVectors( NPC->client->ps.viewangles, forward, right, up ); + CalcMuzzlePoint( NPC, forward, right, up, muzzle ); + //CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + CalcEntitySpot( NPC->enemy, SPOT_ORIGIN, target ); + + if ( enemyDist2 > 65536 && NPCInfo->stats.aim < 5 )//is 256 squared, was 16384 (128*128) + { + if ( NPC->count < (5-NPCInfo->stats.aim) ) + {//miss a few times first + if ( shoot2 && TIMER_Done( NPC, "attackDelay" ) && level.time >= NPCInfo->shotTime ) + {//ready to fire again + qboolean aimError = qfalse; + qboolean hit = qtrue; + int tryMissCount = 0; + trace_t trace; + + GetAnglesForDirection( muzzle, target, angles ); + AngleVectors( angles, forward, right, up ); + + while ( hit && tryMissCount < 10 ) + { + tryMissCount++; + if ( !Q_irand( 0, 1 ) ) + { + aimError = qtrue; + if ( !Q_irand( 0, 1 ) ) + { + VectorMA( target, NPC->enemy->r.maxs[2]*flrand(1.5, 4), right, target ); + } + else + { + VectorMA( target, NPC->enemy->r.mins[2]*flrand(1.5, 4), right, target ); + } + } + if ( !aimError || !Q_irand( 0, 1 ) ) + { + if ( !Q_irand( 0, 1 ) ) + { + VectorMA( target, NPC->enemy->r.maxs[2]*flrand(1.5, 4), up, target ); + } + else + { + VectorMA( target, NPC->enemy->r.mins[2]*flrand(1.5, 4), up, target ); + } + } + trap_Trace( &trace, muzzle, vec3_origin, vec3_origin, target, NPC->s.number, MASK_SHOT ); + hit = Sniper_EvaluateShot( trace.entityNum ); + } + NPC->count++; + } + else + { + if ( !enemyLOS2 ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + else + {//based on distance, aim value, difficulty and enemy movement, miss + //FIXME: incorporate distance as a factor? + int missFactor = 8-(NPCInfo->stats.aim+g_spskill.integer) * 3; + if ( missFactor > ENEMY_POS_LAG_STEPS ) + { + missFactor = ENEMY_POS_LAG_STEPS; + } + else if ( missFactor < 0 ) + {//??? + missFactor = 0 ; + } + VectorCopy( NPCInfo->enemyLaggedPos[missFactor], target ); + } + GetAnglesForDirection( muzzle, target, angles ); + } + else + { + target[2] += flrand( 0, NPC->enemy->r.maxs[2] ); + //CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, target ); + GetAnglesForDirection( muzzle, target, angles ); + } + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + } + NPC_UpdateAngles( qtrue, qtrue ); +} + +void Sniper_UpdateEnemyPos( void ) +{ + int index; + int i; + + for ( i = MAX_ENEMY_POS_LAG-ENEMY_POS_LAG_INTERVAL; i >= 0; i -= ENEMY_POS_LAG_INTERVAL ) + { + index = i/ENEMY_POS_LAG_INTERVAL; + if ( !index ) + { + CalcEntitySpot( NPC->enemy, SPOT_HEAD_LEAN, NPCInfo->enemyLaggedPos[index] ); + NPCInfo->enemyLaggedPos[index][2] -= flrand( 2, 16 ); + } + else + { + VectorCopy( NPCInfo->enemyLaggedPos[index-1], NPCInfo->enemyLaggedPos[index] ); + } + } +} + +/* +------------------------- +NPC_BSSniper_Attack +------------------------- +*/ + +void Sniper_StartHide( void ) +{ + int duckTime = Q_irand( 2000, 5000 ); + + TIMER_Set( NPC, "duck", duckTime ); + TIMER_Set( NPC, "watch", 500 ); + TIMER_Set( NPC, "attackDelay", duckTime + Q_irand( 500, 2000 ) ); +} + +void NPC_BSSniper_Attack( void ) +{ + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + NPC_BSSniper_Patrol();//FIXME: or patrol? + return; + } + + if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//going to run + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSSniper_Patrol();//FIXME: or patrol? + return; + } + + enemyLOS2 = enemyCS2 = qfalse; + move2 = qtrue; + faceEnemy2 = qfalse; + shoot2 = qfalse; + enemyDist2 = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + if ( enemyDist2 < 16384 )//128 squared + {//too close, so switch to primary fire + if ( NPC->client->ps.weapon == WP_DISRUPTOR ) + {//sniping... should be assumed + if ( NPCInfo->scriptFlags & SCF_ALT_FIRE ) + {//use primary fire + trace_t trace; + trap_Trace ( &trace, NPC->enemy->r.currentOrigin, NPC->enemy->r.mins, NPC->enemy->r.maxs, NPC->r.currentOrigin, NPC->enemy->s.number, NPC->enemy->clipmask ); + if ( !trace.allsolid && !trace.startsolid && (trace.fraction == 1.0 || trace.entityNum == NPC->s.number ) ) + {//he can get right to me + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( WP_DISRUPTOR ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + //FIXME: switch back if he gets far away again? + } + } + else if ( enemyDist2 > 65536 )//256 squared + { + if ( NPC->client->ps.weapon == WP_DISRUPTOR ) + {//sniping... should be assumed + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + {//use primary fire + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( WP_DISRUPTOR ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + Sniper_UpdateEnemyPos(); + //can we see our target? + if ( NPC_ClearLOS4( NPC->enemy ) )//|| (NPCInfo->stats.aim >= 5 && gi.inPVS( NPC->client->renderInfo.eyePoint, NPC->enemy->currentOrigin )) ) + { + float maxShootDist; + + NPCInfo->enemyLastSeenTime = level.time; + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + enemyLOS2 = qtrue; + maxShootDist = NPC_MaxDistSquaredForWeapon(); + if ( enemyDist2 < maxShootDist ) + { + vec3_t fwd, right, up, muzzle, end; + trace_t tr; + int hit; + + AngleVectors( NPC->client->ps.viewangles, fwd, right, up ); + CalcMuzzlePoint( NPC, fwd, right, up, muzzle ); + VectorMA( muzzle, 8192, fwd, end ); + trap_Trace ( &tr, muzzle, NULL, NULL, end, NPC->s.number, MASK_SHOT ); + + hit = tr.entityNum; + //can we shoot our target? + if ( Sniper_EvaluateShot( hit ) ) + { + enemyCS2 = qtrue; + } + } + } + /* + else if ( gi.inPVS( NPC->enemy->currentOrigin, NPC->currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy2 = qtrue; + } + */ + + if ( enemyLOS2 ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy2 = qtrue; + } + if ( enemyCS2 ) + { + shoot2 = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 3000 ) + {//Hmm, have to get around this bastard... FIXME: this NPCInfo->enemyLastSeenTime builds up when ducked seems to make them want to run when they uncrouch + Sniper_ResolveBlockedShot(); + } + + //Check for movement to take care of + Sniper_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + Sniper_CheckFireState(); + + if ( move2 ) + {//move toward goal + if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist2 > 10000 ) )//100 squared + { + move2 = Sniper_Move(); + } + else + { + move2 = qfalse; + } + } + + if ( !move2 ) + { + if ( !TIMER_Done( NPC, "duck" ) ) + { + if ( TIMER_Done( NPC, "watch" ) ) + {//not while watching + ucmd.upmove = -127; + } + } + //FIXME: what about leaning? + //FIXME: also, when stop ducking, start looking, if enemy can see me, chance of ducking back down again + } + else + {//stop ducking! + TIMER_Set( NPC, "duck", -1 ); + } + + if ( TIMER_Done( NPC, "duck" ) + && TIMER_Done( NPC, "watch" ) + && (TIMER_Get( NPC, "attackDelay" )-level.time) > 1000 + && NPC->attackDebounceTime < level.time ) + { + if ( enemyLOS2 && (NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + { + if ( NPC->fly_sound_debounce_time < level.time ) + { + NPC->fly_sound_debounce_time = level.time + 2000; + } + } + } + + if ( !faceEnemy2 ) + {//we want to face in the dir we're running + if ( move2 ) + {//don't run away and shoot + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + shoot2 = qfalse; + } + NPC_UpdateAngles( qtrue, qtrue ); + } + else// if ( faceEnemy2 ) + {//face the enemy + Sniper_FaceEnemy(); + } + + if ( NPCInfo->scriptFlags&SCF_DONT_FIRE ) + { + shoot2 = qfalse; + } + + //FIXME: don't shoot right away! + if ( shoot2 ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + WeaponThink( qtrue ); + if ( ucmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK) ) + { + G_SoundOnEnt( NPC, CHAN_WEAPON, "sound/null.wav" ); + } + + //took a shot, now hide + if ( !(NPC->spawnflags&SPF_NO_HIDE) && !Q_irand( 0, 1 ) ) + { + //FIXME: do this if in combat point and combat point has duck-type cover... also handle lean-type cover + Sniper_StartHide(); + } + else + { + TIMER_Set( NPC, "attackDelay", NPCInfo->shotTime-level.time ); + } + } + } +} + +void NPC_BSSniper_Default( void ) +{ + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSSniper_Patrol(); + } + else//if ( NPC->enemy ) + {//have an enemy + NPC_BSSniper_Attack(); + } +} diff --git a/code/game/NPC_AI_Stormtrooper.c b/code/game/NPC_AI_Stormtrooper.c new file mode 100644 index 0000000..214c24a --- /dev/null +++ b/code/game/NPC_AI_Stormtrooper.c @@ -0,0 +1,2742 @@ +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" + +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState ); +extern qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum ); +extern void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot ); +extern void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group ); +extern void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ); +extern qboolean G_ExpandPointToBBox( vec3_t point, const vec3_t mins, const vec3_t maxs, int ignore, int clipmask ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void NPC_CheckGetNewWeapon( void ); +extern int GetTime ( int lastTime ); +extern void NPC_AimAdjust( int change ); +extern qboolean FlyingCreature( gentity_t *ent ); + +extern vmCvar_t d_asynchronousGroupAI; + +#define MAX_VIEW_DIST 1024 +#define MAX_VIEW_SPEED 250 +#define MAX_LIGHT_INTENSITY 255 +#define MIN_LIGHT_THRESHOLD 0.1 +#define ST_MIN_LIGHT_THRESHOLD 30 +#define ST_MAX_LIGHT_THRESHOLD 180 +#define DISTANCE_THRESHOLD 0.075f + +#define DISTANCE_SCALE 0.35f //These first three get your base detection rating, ideally add up to 1 +#define FOV_SCALE 0.40f // +#define LIGHT_SCALE 0.25f // + +#define SPEED_SCALE 0.25f //These next two are bonuses +#define TURNING_SCALE 0.25f // + +#define REALIZE_THRESHOLD 0.6f +#define CAUTIOUS_THRESHOLD ( REALIZE_THRESHOLD * 0.75 ) + +qboolean NPC_CheckPlayerTeamStealth( void ); + +static qboolean enemyLOS; +static qboolean enemyCS; +static qboolean enemyInFOV; +static qboolean hitAlly; +static qboolean faceEnemy; +static qboolean move; +static qboolean shoot; +static float enemyDist; +static vec3_t impactPos; + +int groupSpeechDebounceTime[TEAM_NUM_TEAMS];//used to stop several group AI from speaking all at once + +//Local state enums +enum +{ + LSTATE_NONE = 0, + LSTATE_UNDERFIRE, + LSTATE_INVESTIGATE, +}; + +void ST_AggressionAdjust( gentity_t *self, int change ) +{ + int upper_threshold, lower_threshold; + + self->NPC->stats.aggression += change; + + //FIXME: base this on initial NPC stats + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + {//good guys are less aggressive + upper_threshold = 7; + lower_threshold = 1; + } + else + {//bad guys are more aggressive + upper_threshold = 10; + lower_threshold = 3; + } + + if ( self->NPC->stats.aggression > upper_threshold ) + { + self->NPC->stats.aggression = upper_threshold; + } + else if ( self->NPC->stats.aggression < lower_threshold ) + { + self->NPC->stats.aggression = lower_threshold; + } +} + +void ST_ClearTimers( gentity_t *ent ) +{ + TIMER_Set( ent, "chatter", 0 ); + TIMER_Set( ent, "duck", 0 ); + TIMER_Set( ent, "stand", 0 ); + TIMER_Set( ent, "shuffleTime", 0 ); + TIMER_Set( ent, "sleepTime", 0 ); + TIMER_Set( ent, "enemyLastVisible", 0 ); + TIMER_Set( ent, "roamTime", 0 ); + TIMER_Set( ent, "hideTime", 0 ); + TIMER_Set( ent, "attackDelay", 0 ); //FIXME: Slant for difficulty levels + TIMER_Set( ent, "stick", 0 ); + TIMER_Set( ent, "scoutTime", 0 ); + TIMER_Set( ent, "flee", 0 ); + TIMER_Set( ent, "interrogating", 0 ); + TIMER_Set( ent, "verifyCP", 0 ); +} + +enum +{ + SPEECH_CHASE, + SPEECH_CONFUSED, + SPEECH_COVER, + SPEECH_DETECTED, + SPEECH_GIVEUP, + SPEECH_LOOK, + SPEECH_LOST, + SPEECH_OUTFLANK, + SPEECH_ESCAPING, + SPEECH_SIGHT, + SPEECH_SOUND, + SPEECH_SUSPICIOUS, + SPEECH_YELL, + SPEECH_PUSHED +}; + +static void ST_Speech( gentity_t *self, int speechType, float failChance ) +{ + if ( random() < failChance ) + { + return; + } + + if ( failChance >= 0 ) + {//a negative failChance makes it always talk + if ( self->NPC->group ) + {//group AI speech debounce timer + if ( self->NPC->group->speechDebounceTime > level.time ) + { + return; + } + /* + else if ( !self->NPC->group->enemy ) + { + if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time ) + { + return; + } + } + */ + } + else if ( !TIMER_Done( self, "chatter" ) ) + {//personal timer + return; + } + else if ( groupSpeechDebounceTime[self->client->playerTeam] > level.time ) + {//for those not in group AI + //FIXME: let certain speech types interrupt others? Let closer NPCs interrupt farther away ones? + return; + } + } + + if ( self->NPC->group ) + {//So they don't all speak at once... + //FIXME: if they're not yet mad, they have no group, so distracting a group of them makes them all speak! + self->NPC->group->speechDebounceTime = level.time + Q_irand( 2000, 4000 ); + } + else + { + TIMER_Set( self, "chatter", Q_irand( 2000, 4000 ) ); + } + groupSpeechDebounceTime[self->client->playerTeam] = level.time + Q_irand( 2000, 4000 ); + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + { + return; + } + + switch( speechType ) + { + case SPEECH_CHASE: + G_AddVoiceEvent( self, Q_irand(EV_CHASE1, EV_CHASE3), 2000 ); + break; + case SPEECH_CONFUSED: + G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + break; + case SPEECH_COVER: + G_AddVoiceEvent( self, Q_irand(EV_COVER1, EV_COVER5), 2000 ); + break; + case SPEECH_DETECTED: + G_AddVoiceEvent( self, Q_irand(EV_DETECTED1, EV_DETECTED5), 2000 ); + break; + case SPEECH_GIVEUP: + G_AddVoiceEvent( self, Q_irand(EV_GIVEUP1, EV_GIVEUP4), 2000 ); + break; + case SPEECH_LOOK: + G_AddVoiceEvent( self, Q_irand(EV_LOOK1, EV_LOOK2), 2000 ); + break; + case SPEECH_LOST: + G_AddVoiceEvent( self, EV_LOST1, 2000 ); + break; + case SPEECH_OUTFLANK: + G_AddVoiceEvent( self, Q_irand(EV_OUTFLANK1, EV_OUTFLANK2), 2000 ); + break; + case SPEECH_ESCAPING: + G_AddVoiceEvent( self, Q_irand(EV_ESCAPING1, EV_ESCAPING3), 2000 ); + break; + case SPEECH_SIGHT: + G_AddVoiceEvent( self, Q_irand(EV_SIGHT1, EV_SIGHT3), 2000 ); + break; + case SPEECH_SOUND: + G_AddVoiceEvent( self, Q_irand(EV_SOUND1, EV_SOUND3), 2000 ); + break; + case SPEECH_SUSPICIOUS: + G_AddVoiceEvent( self, Q_irand(EV_SUSPICIOUS1, EV_SUSPICIOUS5), 2000 ); + break; + case SPEECH_YELL: + G_AddVoiceEvent( self, Q_irand( EV_ANGER1, EV_ANGER3 ), 2000 ); + break; + case SPEECH_PUSHED: + G_AddVoiceEvent( self, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 2000 ); + break; + default: + break; + } + + self->NPC->blockedSpeechDebounceTime = level.time + 2000; +} + +void ST_MarkToCover( gentity_t *self ) +{ + if ( !self || !self->NPC ) + { + return; + } + self->NPC->localState = LSTATE_UNDERFIRE; + TIMER_Set( self, "attackDelay", Q_irand( 500, 2500 ) ); + ST_AggressionAdjust( self, -3 ); + if ( self->NPC->group && self->NPC->group->numGroup > 1 ) + { + ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound? + } +} + +void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime ) +{ + if ( !self || !self->NPC ) + { + return; + } + G_StartFlee( self, enemy, dangerPoint, dangerLevel, minTime, maxTime ); + if ( self->NPC->group && self->NPC->group->numGroup > 1 ) + { + ST_Speech( self, SPEECH_COVER, 0 );//FIXME: flee sound? + } +} +/* +------------------------- +NPC_ST_Pain +------------------------- +*/ + +void NPC_ST_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + self->NPC->localState = LSTATE_UNDERFIRE; + + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "hideTime", -1 ); + TIMER_Set( self, "stand", 2000 ); + + NPC_Pain( self, attacker, damage ); + + if ( !damage && self->health > 0 ) + {//FIXME: better way to know I was pushed + G_AddVoiceEvent( self, Q_irand(EV_PUSHED1, EV_PUSHED3), 2000 ); + } +} + +/* +------------------------- +ST_HoldPosition +------------------------- +*/ + +static void ST_HoldPosition( void ) +{ + if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + TIMER_Set( NPC, "flee", -level.time ); + } + TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );//don't look for another one for a few seconds + NPC_FreeCombatPoint( NPCInfo->combatPoint, qtrue ); + //NPCInfo->combatPoint = -1;//??? + if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//don't have a script waiting for me to get to my point, okay to stop trying and stand + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + NPCInfo->goalEntity = NULL; + } + + /*if ( TIMER_Done( NPC, "stand" ) ) + {//FIXME: what if can't shoot from this pos? + TIMER_Set( NPC, "duck", Q_irand( 2000, 4000 ) ); + } + */ +} + +void NPC_ST_SayMovementSpeech( void ) +{ + if ( !NPCInfo->movementSpeech ) + { + return; + } + if ( NPCInfo->group && + NPCInfo->group->commander && + NPCInfo->group->commander->client && + NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL && + !Q_irand( 0, 3 ) ) + {//imperial (commander) gives the order + ST_Speech( NPCInfo->group->commander, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance ); + } + else + {//really don't want to say this unless we can actually get there... + ST_Speech( NPC, NPCInfo->movementSpeech, NPCInfo->movementSpeechChance ); + } + + NPCInfo->movementSpeech = 0; + NPCInfo->movementSpeechChance = 0.0f; +} + +void NPC_ST_StoreMovementSpeech( int speech, float chance ) +{ + NPCInfo->movementSpeech = speech; + NPCInfo->movementSpeechChance = chance; +} +/* +------------------------- +ST_Move +------------------------- +*/ +void ST_TransferMoveGoal( gentity_t *self, gentity_t *other ); +static qboolean ST_Move( void ) +{ + qboolean moved; + navInfo_t info; + + NPCInfo->combatMove = qtrue;//always move straight toward our goal + + moved = NPC_MoveToGoal( qtrue ); + + //Get the move info + NAV_GetLastMove( &info ); + + //FIXME: if we bump into another one of our guys and can't get around him, just stop! + //If we hit our target, then stop and fire! + if ( info.flags & NIF_COLLISION ) + { + if ( info.blocker == NPC->enemy ) + { + ST_HoldPosition(); + } + } + + //If our move failed, then reset + if ( moved == qfalse ) + {//FIXME: if we're going to a combat point, need to pick a different one + if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//can't transfer movegoal or stop when a script we're running is waiting to complete + if ( info.blocker && info.blocker->NPC && NPCInfo->group != NULL && info.blocker->NPC->group == NPCInfo->group )//(NPCInfo->aiFlags&NPCAI_BLOCKED) && NPCInfo->group != NULL ) + {//dammit, something is in our way + //see if it's one of ours + int j; + + for ( j = 0; j < NPCInfo->group->numGroup; j++ ) + { + if ( NPCInfo->group->member[j].number == NPCInfo->blockingEntNum ) + {//we're being blocked by one of our own, pass our goal onto them and I'll stand still + ST_TransferMoveGoal( NPC, &g_entities[NPCInfo->group->member[j].number] ); + break; + } + } + } + + ST_HoldPosition(); + } + } + else + { + //First time you successfully move, say what it is you're doing + NPC_ST_SayMovementSpeech(); + } + + return moved; +} + + +/* +------------------------- +NPC_ST_SleepShuffle +------------------------- +*/ + +static void NPC_ST_SleepShuffle( void ) +{ + //Play an awake script if we have one + if ( G_ActivateBehavior( NPC, BSET_AWAKE) ) + { + return; + } + + //Automate some movement and noise + if ( TIMER_Done( NPC, "shuffleTime" ) ) + { + + //TODO: Play sleeping shuffle animation + + //int soundIndex = Q_irand( 0, 1 ); + + /* + switch ( soundIndex ) + { + case 0: + G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper1/scav4/hunh.mp3") ); + break; + + case 1: + G_Sound( NPC, G_SoundIndex("sound/chars/imperialsleeper3/scav4/tryingtosleep.wav") ); + break; + } + */ + + TIMER_Set( NPC, "shuffleTime", 4000 ); + TIMER_Set( NPC, "sleepTime", 2000 ); + return; + } + + //They made another noise while we were stirring, see if we can see them + if ( TIMER_Done( NPC, "sleepTime" ) ) + { + NPC_CheckPlayerTeamStealth(); + TIMER_Set( NPC, "sleepTime", 2000 ); + } +} + +/* +------------------------- +NPC_ST_Sleep +------------------------- +*/ + +void NPC_BSST_Sleep( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qfalse, qtrue, -1, qfalse, AEL_MINOR );//only check sounds since we're alseep! + + //There is an event we heard + if ( alertEvent >= 0 ) + { + //See if it was enough to wake us up + if ( level.alertEvents[alertEvent].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { //rwwFIXMEFIXME: Care about all clients not just 0 + if ( &g_entities[0] && g_entities[0].health > 0 ) + { + G_SetEnemy( NPC, &g_entities[0] ); + return; + } + } + + //Otherwise just stir a bit + NPC_ST_SleepShuffle(); + return; + } +} + +/* +------------------------- +NPC_CheckEnemyStealth +------------------------- +*/ + +qboolean NPC_CheckEnemyStealth( gentity_t *target ) +{ + float target_dist, minDist = 40;//any closer than 40 and we definitely notice + float maxViewDist; + qboolean clearLOS; + + //In case we aquired one some other way + if ( NPC->enemy != NULL ) + return qtrue; + + //Ignore notarget + if ( target->flags & FL_NOTARGET ) + return qfalse; + + if ( target->health <= 0 ) + { + return qfalse; + } + + if ( target->client->ps.weapon == WP_SABER && !target->client->ps.saberHolstered && !target->client->ps.saberInFlight ) + {//if target has saber in hand and activated, we wake up even sooner even if not facing him + minDist = 100; + } + + target_dist = DistanceSquared( target->r.currentOrigin, NPC->r.currentOrigin ); + + //If the target is this close, then wake up regardless + if ( !(target->client->ps.pm_flags&PMF_DUCKED) + && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) + && (target_dist) < (minDist*minDist) ) + { + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + maxViewDist = MAX_VIEW_DIST; + + if ( NPCInfo->stats.visrange > maxViewDist ) + {//FIXME: should we always just set maxViewDist to this? + maxViewDist = NPCInfo->stats.visrange; + } + + if ( target_dist > (maxViewDist*maxViewDist) ) + {//out of possible visRange + return qfalse; + } + + //Check FOV first + if ( InFOV( target, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) + return qfalse; + + //clearLOS = ( target->client->ps.leanofs ) ? NPC_ClearLOS5( target->client->renderInfo.eyePoint ) : NPC_ClearLOS4( target ); + clearLOS = NPC_ClearLOS4( target ); + + //Now check for clear line of vision + if ( clearLOS ) + { + vec3_t targ_org; + float hAngle_perc; + float vAngle_perc; + float target_speed; + int target_crouching; + float dist_rating; + float speed_rating; + float turning_rating; + float light_level; + float FOV_perc; + float vis_rating; + float dist_influence; + float fov_influence; + float light_influence; + float target_rating; + int contents; + float realize, cautious; + + if ( target->client->NPC_class == CLASS_ATST ) + {//can't miss 'em! + G_SetEnemy( NPC, target ); + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + VectorSet(targ_org, target->r.currentOrigin[0],target->r.currentOrigin[1],target->r.currentOrigin[2]+target->r.maxs[2]-4); + hAngle_perc = NPC_GetHFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.hfov ); + vAngle_perc = NPC_GetVFOVPercentage( targ_org, NPC->client->renderInfo.eyePoint, NPC->client->renderInfo.eyeAngles, NPCInfo->stats.vfov ); + + //Scale them vertically some, and horizontally pretty harshly + vAngle_perc *= vAngle_perc;//( vAngle_perc * vAngle_perc ); + hAngle_perc *= ( hAngle_perc * hAngle_perc ); + + //Cap our vertical vision severely + //if ( vAngle_perc <= 0.3f ) // was 0.5f + // return qfalse; + + //Assess the player's current status + target_dist = Distance( target->r.currentOrigin, NPC->r.currentOrigin ); + + target_speed = VectorLength( target->client->ps.velocity ); + target_crouching = ( target->client->pers.cmd.upmove < 0 ); + dist_rating = ( target_dist / maxViewDist ); + speed_rating = ( target_speed / MAX_VIEW_SPEED ); + turning_rating = 5.0f;//AngleDelta( target->client->ps.viewangles[PITCH], target->lastAngles[PITCH] )/180.0f + AngleDelta( target->client->ps.viewangles[YAW], target->lastAngles[YAW] )/180.0f; + light_level = (255/MAX_LIGHT_INTENSITY); //( target->lightLevel / MAX_LIGHT_INTENSITY ); + FOV_perc = 1.0f - ( hAngle_perc + vAngle_perc ) * 0.5f; //FIXME: Dunno about the average... + vis_rating = 0.0f; + + //Too dark + if ( light_level < MIN_LIGHT_THRESHOLD ) + return qfalse; + + //Too close? + if ( dist_rating < DISTANCE_THRESHOLD ) + { + G_SetEnemy( NPC, target ); + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + //Out of range + if ( dist_rating > 1.0f ) + return qfalse; + + //Cap our speed checks + if ( speed_rating > 1.0f ) + speed_rating = 1.0f; + + + //Calculate the distance, fov and light influences + //...Visibilty linearly wanes over distance + dist_influence = DISTANCE_SCALE * ( ( 1.0f - dist_rating ) ); + //...As the percentage out of the FOV increases, straight perception suffers on an exponential scale + fov_influence = FOV_SCALE * ( 1.0f - FOV_perc ); + //...Lack of light hides, abundance of light exposes + light_influence = ( light_level - 0.5f ) * LIGHT_SCALE; + + //Calculate our base rating + target_rating = dist_influence + fov_influence + light_influence; + + //Now award any final bonuses to this number + contents = trap_PointContents( targ_org, target->s.number ); + if ( contents&CONTENTS_WATER ) + { + int myContents = trap_PointContents( NPC->client->renderInfo.eyePoint, NPC->s.number ); + if ( !(myContents&CONTENTS_WATER) ) + {//I'm not in water + if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER ) + {//these guys can see in in/through water pretty well + vis_rating = 0.10f;//10% bonus + } + else + { + vis_rating = 0.35f;//35% bonus + } + } + else + {//else, if we're both in water + if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER ) + {//I can see him just fine + } + else + { + vis_rating = 0.15f;//15% bonus + } + } + } + else + {//not in water + if ( contents&CONTENTS_FOG ) + { + vis_rating = 0.15f;//15% bonus + } + } + + target_rating *= (1.0f - vis_rating); + + //...Motion draws the eye quickly + target_rating += speed_rating * SPEED_SCALE; + target_rating += turning_rating * TURNING_SCALE; + //FIXME: check to see if they're animating, too? But can we do something as simple as frame != oldframe? + + //...Smaller targets are harder to indentify + if ( target_crouching ) + { + target_rating *= 0.9f; //10% bonus + } + + //If he's violated the threshold, then realize him + //float difficulty_scale = 1.0f + (2.0f-g_spskill.value);//if playing on easy, 20% harder to be seen...? + if ( NPC->client->NPC_class == CLASS_SWAMPTROOPER ) + {//swamptroopers can see much better + realize = (float)CAUTIOUS_THRESHOLD/**difficulty_scale*/; + cautious = (float)CAUTIOUS_THRESHOLD * 0.75f/**difficulty_scale*/; + } + else + { + realize = (float)REALIZE_THRESHOLD/**difficulty_scale*/; + cautious = (float)CAUTIOUS_THRESHOLD * 0.75f/**difficulty_scale*/; + } + + if ( target_rating > realize && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + return qtrue; + } + + //If he's above the caution threshold, then realize him in a few seconds unless he moves to cover + if ( target_rating > cautious && !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + {//FIXME: ambushing guys should never talk + if ( TIMER_Done( NPC, "enemyLastVisible" ) ) + {//If we haven't already, start the counter + int lookTime = Q_irand( 4500, 8500 ); + //NPCInfo->timeEnemyLastVisible = level.time + 2000; + TIMER_Set( NPC, "enemyLastVisible", lookTime ); + //TODO: Play a sound along the lines of, "Huh? What was that?" + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + NPC_TempLookTarget( NPC, target->s.number, lookTime, lookTime ); + //FIXME: set desired yaw and pitch towards this guy? + } + else if ( TIMER_Get( NPC, "enemyLastVisible" ) <= level.time + 500 && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) //FIXME: Is this reliable? + { + if ( NPCInfo->rank < RANK_LT && !Q_irand( 0, 2 ) ) + { + int interrogateTime = Q_irand( 2000, 4000 ); + ST_Speech( NPC, SPEECH_SUSPICIOUS, 0 ); + TIMER_Set( NPC, "interrogating", interrogateTime ); + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", interrogateTime ); + TIMER_Set( NPC, "stand", interrogateTime ); + } + else + { + G_SetEnemy( NPC, target ); + NPCInfo->enemyLastSeenTime = level.time; + //FIXME: ambush guys (like those popping out of water) shouldn't delay... + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + TIMER_Set( NPC, "stand", Q_irand( 500, 2500 ) ); + } + return qtrue; + } + + return qfalse; + } + } + + return qfalse; +} + +qboolean NPC_CheckPlayerTeamStealth( void ) +{ + /* + //NOTENOTE: For now, all stealh checks go against the player, since + // he is the main focus. Squad members and rivals do not + // fall into this category and will be ignored. + + NPC_CheckEnemyStealth( &g_entities[0] ); //Change this pointer to assess other entities + */ + gentity_t *enemy; + int i; + + for ( i = 0; i < ENTITYNUM_WORLD; i++ ) + { + enemy = &g_entities[i]; + + if (!enemy->inuse) + { + continue; + } + + if ( enemy && enemy->client && NPC_ValidEnemy( enemy ) && enemy->client->playerTeam == NPC->client->enemyTeam ) + { + if ( NPC_CheckEnemyStealth( enemy ) ) //Change this pointer to assess other entities + { + return qtrue; + } + } + } + return qfalse; +} +/* +------------------------- +NPC_ST_InvestigateEvent +------------------------- +*/ + +#define MAX_CHECK_THRESHOLD 1 + +static qboolean NPC_ST_InvestigateEvent( int eventID, qboolean extraSuspicious ) +{ + //If they've given themselves away, just take them as an enemy + if ( NPCInfo->confusionTime < level.time ) + { + if ( level.alertEvents[eventID].level == AEL_DISCOVERED && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + if ( !level.alertEvents[eventID].owner || + !level.alertEvents[eventID].owner->client || + level.alertEvents[eventID].owner->health <= 0 || + level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam ) + {//not an enemy + return qfalse; + } + //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him) + //ST_Speech( NPC, SPEECH_CHARGE, 0 ); + G_SetEnemy( NPC, level.alertEvents[eventID].owner ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + if ( level.alertEvents[eventID].type == AET_SOUND ) + {//heard him, didn't see him, stick for a bit + TIMER_Set( NPC, "roamTime", Q_irand( 500, 2500 ) ); + } + return qtrue; + } + } + + //don't look at the same alert twice + if ( level.alertEvents[eventID].ID == NPCInfo->lastAlertID ) + { + return qfalse; + } + NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + + //Must be ready to take another sound event + /* + if ( NPCInfo->investigateSoundDebounceTime > level.time ) + { + return qfalse; + } + */ + + if ( level.alertEvents[eventID].type == AET_SIGHT ) + {//sight alert, check the light level + if ( level.alertEvents[eventID].light < Q_irand( ST_MIN_LIGHT_THRESHOLD, ST_MAX_LIGHT_THRESHOLD ) ) + {//below my threshhold of potentially seeing + return qfalse; + } + } + + //Save the position for movement (if necessary) + VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal ); + + //First awareness of it + NPCInfo->investigateCount += ( extraSuspicious ) ? 2 : 1; + + //Clamp the value + if ( NPCInfo->investigateCount > 4 ) + NPCInfo->investigateCount = 4; + + //See if we should walk over and investigate + if ( level.alertEvents[eventID].level > AEL_MINOR && NPCInfo->investigateCount > 1 && (NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + { + //make it so they can walk right to this point and look at it rather than having to use combatPoints + if ( G_ExpandPointToBBox( NPCInfo->investigateGoal, NPC->r.mins, NPC->r.maxs, NPC->s.number, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) ) ) + {//we were able to move the investigateGoal to a point in which our bbox would fit + //drop the goal to the ground so we can get at it + vec3_t end; + trace_t trace; + VectorCopy( NPCInfo->investigateGoal, end ); + end[2] -= 512;//FIXME: not always right? What if it's even higher, somehow? + trap_Trace( &trace, NPCInfo->investigateGoal, NPC->r.mins, NPC->r.maxs, end, ENTITYNUM_NONE, ((NPC->clipmask&~CONTENTS_BODY)|CONTENTS_BOTCLIP) ); + if ( trace.fraction >= 1.0f ) + {//too high to even bother + //FIXME: look at them??? + } + else + { + VectorCopy( trace.endpos, NPCInfo->investigateGoal ); + NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue, -1, NULL ); + NPCInfo->localState = LSTATE_INVESTIGATE; + } + } + else + { + int id = NPC_FindCombatPoint( NPCInfo->investigateGoal, NPCInfo->investigateGoal, NPCInfo->investigateGoal, CP_INVESTIGATE|CP_HAS_ROUTE, 0, -1 ); + + if ( id != -1 ) + { + NPC_SetMoveGoal( NPC, level.combatPoints[id].origin, 16, qtrue, id, NULL ); + NPCInfo->localState = LSTATE_INVESTIGATE; + } + } + //Say something + //FIXME: only if have others in group... these should be responses? + if ( NPCInfo->investigateDebounceTime+NPCInfo->pauseTime > level.time ) + {//was already investigating + if ( NPCInfo->group && + NPCInfo->group->commander && + NPCInfo->group->commander->client && + NPCInfo->group->commander->client->NPC_class == CLASS_IMPERIAL && + !Q_irand( 0, 3 ) ) + { + ST_Speech( NPCInfo->group->commander, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds + } + else + { + ST_Speech( NPC, SPEECH_LOOK, 0 );//FIXME: "I'll go check it out" type sounds + } + } + else + { + if ( level.alertEvents[eventID].type == AET_SIGHT ) + { + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + } + else if ( level.alertEvents[eventID].type == AET_SOUND ) + { + ST_Speech( NPC, SPEECH_SOUND, 0 ); + } + } + //Setup the debounce info + NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000; + NPCInfo->investigateSoundDebounceTime = level.time + 2000; + NPCInfo->pauseTime = level.time; + } + else + {//just look? + //Say something + if ( level.alertEvents[eventID].type == AET_SIGHT ) + { + ST_Speech( NPC, SPEECH_SIGHT, 0 ); + } + else if ( level.alertEvents[eventID].type == AET_SOUND ) + { + ST_Speech( NPC, SPEECH_SOUND, 0 ); + } + //Setup the debounce info + NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 1000; + NPCInfo->investigateSoundDebounceTime = level.time + 1000; + NPCInfo->pauseTime = level.time; + VectorCopy( level.alertEvents[eventID].position, NPCInfo->investigateGoal ); + } + + if ( level.alertEvents[eventID].level >= AEL_DANGER ) + { + NPCInfo->investigateDebounceTime = Q_irand( 500, 2500 ); + } + + //Start investigating + NPCInfo->tempBehavior = BS_INVESTIGATE; + return qtrue; +} + +/* +------------------------- +ST_OffsetLook +------------------------- +*/ + +static void ST_OffsetLook( float offset, vec3_t out ) +{ + vec3_t angles, forward, temp; + + GetAnglesForDirection( NPC->r.currentOrigin, NPCInfo->investigateGoal, angles ); + angles[YAW] += offset; + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( NPC->r.currentOrigin, 64, forward, out ); + + CalcEntitySpot( NPC, SPOT_HEAD, temp ); + out[2] = temp[2]; +} + +/* +------------------------- +ST_LookAround +------------------------- +*/ + +static void ST_LookAround( void ) +{ + vec3_t lookPos; + float perc = (float) ( level.time - NPCInfo->pauseTime ) / (float) NPCInfo->investigateDebounceTime; + + //Keep looking at the spot + if ( perc < 0.25 ) + { + VectorCopy( NPCInfo->investigateGoal, lookPos ); + } + else if ( perc < 0.5f ) //Look up but straight ahead + { + ST_OffsetLook( 0.0f, lookPos ); + } + else if ( perc < 0.75f ) //Look right + { + ST_OffsetLook( 45.0f, lookPos ); + } + else //Look left + { + ST_OffsetLook( -45.0f, lookPos ); + } + + NPC_FacePosition( lookPos, qtrue ); +} + +/* +------------------------- +NPC_BSST_Investigate +------------------------- +*/ + +void NPC_BSST_Investigate( void ) +{ + //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too + AI_GetGroup( NPC ); + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( NPCInfo->confusionTime < level.time ) + { + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + //Look for an enemy + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + ST_Speech( NPC, SPEECH_DETECTED, 0 ); + NPCInfo->tempBehavior = BS_DEFAULT; + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, NPCInfo->lastAlertID, qfalse, AEL_MINOR ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + if ( NPCInfo->confusionTime < level.time ) + { + if ( NPC_CheckForDanger( alertEvent ) ) + {//running like hell + ST_Speech( NPC, SPEECH_COVER, 0 );//FIXME: flee sound? + return; + } + } + + if ( level.alertEvents[alertEvent].ID != NPCInfo->lastAlertID ) + { + NPC_ST_InvestigateEvent( alertEvent, qtrue ); + } + } + } + + //If we're done looking, then just return to what we were doing + if ( ( NPCInfo->investigateDebounceTime + NPCInfo->pauseTime ) < level.time ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + NPCInfo->goalEntity = UpdateGoal(); + + NPC_UpdateAngles( qtrue, qtrue ); + //Say something + ST_Speech( NPC, SPEECH_GIVEUP, 0 ); + return; + } + + //FIXME: else, look for new alerts + + //See if we're searching for the noise's origin + if ( NPCInfo->localState == LSTATE_INVESTIGATE && (NPCInfo->goalEntity!=NULL) ) + { + //See if we're there + if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 32, FlyingCreature( NPC ) ) == qfalse ) + { + ucmd.buttons |= BUTTON_WALKING; + + //Try and move there + if ( NPC_MoveToGoal( qtrue ) ) + { + //Bump our times + NPCInfo->investigateDebounceTime = NPCInfo->investigateCount * 5000; + NPCInfo->pauseTime = level.time; + + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + + //Otherwise we're done or have given up + //Say something + //ST_Speech( NPC, SPEECH_LOOK, 0.33f ); + NPCInfo->localState = LSTATE_NONE; + } + + //Look around + ST_LookAround(); +} + +/* +------------------------- +NPC_BSST_Patrol +------------------------- +*/ + +void NPC_BSST_Patrol( void ) +{//FIXME: pick up on bodies of dead buddies? + + //get group- mainly for group speech debouncing, but may use for group scouting/investigating AI, too + AI_GetGroup( NPC ); + + if ( NPCInfo->confusionTime < level.time ) + { + //Look for any enemies + if ( NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckPlayerTeamStealth() ) + { + //NPCInfo->behaviorState = BS_HUNT_AND_KILL;//should be auto now + //NPC_AngerSound(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + if ( NPC_ST_InvestigateEvent( alertEvent, qfalse ) ) + {//actually going to investigate it + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + //ST_Move( NPCInfo->goalEntity ); + NPC_MoveToGoal( qtrue ); + } + else// if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + if ( NPC->client->NPC_class != CLASS_IMPERIAL && NPC->client->NPC_class != CLASS_IMPWORKER ) + {//imperials do not look around + if ( TIMER_Done( NPC, "enemyLastVisible" ) ) + {//nothing suspicious, look around + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 ); + } + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredPitch = Q_irand( -20, 20 ); + } + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); + //TEMP hack for Imperial stand anim + if ( NPC->client->NPC_class == CLASS_IMPERIAL || NPC->client->NPC_class == CLASS_IMPWORKER ) + {//hack + if ( ucmd.forwardmove || ucmd.rightmove || ucmd.upmove ) + {//moving + + if( (NPC->client->ps.torsoTimer <= 0) || (NPC->client->ps.torsoAnim == BOTH_STAND4) ) + { + if ( (ucmd.buttons&BUTTON_WALKING) && !(NPCInfo->scriptFlags&SCF_RUNNING) ) + {//not running, only set upper anim + // No longer overrides scripted anims + NPC_SetAnim( NPC, SETANIM_TORSO, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoTimer = 200; + } + } + } + else + {//standing still, set both torso and legs anim + // No longer overrides scripted anims + if( ( NPC->client->ps.torsoTimer <= 0 || (NPC->client->ps.torsoAnim == BOTH_STAND4) ) && + ( NPC->client->ps.legsTimer <= 0 || (NPC->client->ps.legsAnim == BOTH_STAND4) ) ) + { + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_STAND4, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + NPC->client->ps.torsoTimer = NPC->client->ps.legsTimer = 200; + } + } + //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way + if ( NPC->client->ps.weapon != WP_NONE ) + { + ChangeWeapon( NPC, WP_NONE ); + NPC->client->ps.weapon = WP_NONE; + NPC->client->ps.weaponstate = WEAPON_READY; + /* + if ( NPC->weaponModel[0] > 0 ) + { + gi.G2API_RemoveGhoul2Model( NPC->ghoul2, NPC->weaponModel[0] ); + NPC->weaponModel[0] = -1; + } + */ + //rwwFIXMEFIXME: Do this? + } + } +} + +/* +------------------------- +NPC_BSST_Idle +------------------------- +*/ +/* +void NPC_BSST_Idle( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + NPC_ST_InvestigateEvent( alertEvent, qfalse ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + TIMER_Set( NPC, "roamTime", 2000 + Q_irand( 1000, 2000 ) ); + + NPC_UpdateAngles( qtrue, qtrue ); +} +*/ +/* +------------------------- +ST_CheckMoveState +------------------------- +*/ + +static void ST_CheckMoveState( void ) +{ + if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//moving toward a goal that a script is waiting on, so don't stop for anything! + move = qtrue; + } + //See if we're a scout + else if ( NPCInfo->squadState == SQUAD_SCOUT ) + { + //If we're supposed to stay put, then stand there and fire + if ( TIMER_Done( NPC, "stick" ) == qfalse ) + { + move = qfalse; + return; + } + + //Otherwise, if we can see our target, just shoot + if ( enemyLOS ) + { + if ( enemyCS ) + { + //if we're going after our enemy, we can stop now + if ( NPCInfo->goalEntity == NPC->enemy ) + { + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + move = qfalse; + return; + } + } + } + else + { + //Move to find our target + faceEnemy = qfalse; + } + + /* + if ( TIMER_Done( NPC, "scoutTime" ) ) + {//we can't scout to him, someone else give it a try + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 2000 ) ); + move = qfalse; + return; + } + */ + + //ucmd.buttons |= BUTTON_CAREFUL; + } + //See if we're running away + else if ( NPCInfo->squadState == SQUAD_RETREAT ) + { + if ( NPCInfo->goalEntity ) + { + faceEnemy = qfalse; + } + else + {//um, lost our goal? Just stand and shoot, then + NPCInfo->squadState = SQUAD_STAND_AND_SHOOT; + } + } + //see if we're heading to some other combatPoint + else if ( NPCInfo->squadState == SQUAD_TRANSITION ) + { + //ucmd.buttons |= BUTTON_CAREFUL; + if ( !NPCInfo->goalEntity ) + {//um, lost our goal? Just stand and shoot, then + NPCInfo->squadState = SQUAD_STAND_AND_SHOOT; + } + } + //see if we're at point, duck and fire + else if ( NPCInfo->squadState == SQUAD_POINT ) + { + if ( TIMER_Done( NPC, "stick" ) ) + { + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + return; + } + + move = qfalse; + return; + } + //see if we're just standing around + else if ( NPCInfo->squadState == SQUAD_STAND_AND_SHOOT ) + {//from this squadState we can transition to others? + move = qfalse; + return; + } + //see if we're hiding + else if ( NPCInfo->squadState == SQUAD_COVER ) + { + //Should we duck? + move = qfalse; + return; + } + //see if we're just standing around + else if ( NPCInfo->squadState == SQUAD_IDLE ) + { + if ( !NPCInfo->goalEntity ) + { + move = qfalse; + return; + } + } + //?? + else + {//invalid squadState! + } + + //See if we're moving towards a goal, not the enemy + if ( ( NPCInfo->goalEntity != NPC->enemy ) && ( NPCInfo->goalEntity != NULL ) ) + { + //Did we make it? + if ( NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, NPCInfo->goalEntity->r.currentOrigin, 16, FlyingCreature( NPC ) ) || + ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) && NPCInfo->squadState == SQUAD_SCOUT && enemyLOS && enemyDist <= 10000 ) ) + {//either hit our navgoal or our navgoal was not a crucial (scripted) one (maybe a combat point) and we're scouting and found our enemy + int newSquadState = SQUAD_STAND_AND_SHOOT; + //we got where we wanted to go, set timers based on why we were running + switch ( NPCInfo->squadState ) + { + case SQUAD_RETREAT://was running away + //done fleeing, obviously + TIMER_Set( NPC, "duck", (NPC->client->pers.maxHealth - NPC->health) * 100 ); + TIMER_Set( NPC, "hideTime", Q_irand( 3000, 7000 ) ); + TIMER_Set( NPC, "flee", -level.time ); + newSquadState = SQUAD_COVER; + break; + case SQUAD_TRANSITION://was heading for a combat point + TIMER_Set( NPC, "hideTime", Q_irand( 2000, 4000 ) ); + break; + case SQUAD_SCOUT://was running after player + break; + default: + break; + } + AI_GroupUpdateSquadstates( NPCInfo->group, NPC, newSquadState ); + NPC_ReachedGoal(); + //don't attack right away + TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) ); //FIXME: Slant for difficulty levels + //don't do something else just yet + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 4000 ) ); + return; + } + + //keep going, hold of roamTimer until we get there + TIMER_Set( NPC, "roamTime", Q_irand( 4000, 8000 ) ); + } +} + +void ST_ResolveBlockedShot( int hit ) +{ + int stuckTime; + //figure out how long we intend to stand here, max + if ( TIMER_Get( NPC, "roamTime" ) > TIMER_Get( NPC, "stick" ) ) + { + stuckTime = TIMER_Get( NPC, "roamTime" )-level.time; + } + else + { + stuckTime = TIMER_Get( NPC, "stick" )-level.time; + } + + if ( TIMER_Done( NPC, "duck" ) ) + {//we're not ducking + if ( AI_GroupContainsEntNum( NPCInfo->group, hit ) ) + { + gentity_t *member = &g_entities[hit]; + if ( TIMER_Done( member, "duck" ) ) + {//they aren't ducking + if ( TIMER_Done( member, "stand" ) ) + {//they're not being forced to stand + //tell them to duck at least as long as I'm not moving + TIMER_Set( member, "duck", stuckTime ); + return; + } + } + } + } + else + {//maybe we should stand + if ( TIMER_Done( NPC, "stand" ) ) + {//stand for as long as we'll be here + TIMER_Set( NPC, "stand", stuckTime ); + return; + } + } + //Hmm, can't resolve this by telling them to duck or telling me to stand + //We need to move! + TIMER_Set( NPC, "roamTime", -1 ); + TIMER_Set( NPC, "stick", -1 ); + TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "attakDelay", Q_irand( 1000, 3000 ) ); +} + +/* +------------------------- +ST_CheckFireState +------------------------- +*/ + +static void ST_CheckFireState( void ) +{ + if ( enemyCS ) + {//if have a clear shot, always try + return; + } + + if ( NPCInfo->squadState == SQUAD_RETREAT || NPCInfo->squadState == SQUAD_TRANSITION || NPCInfo->squadState == SQUAD_SCOUT ) + {//runners never try to fire at the last pos + return; + } + + if ( !VectorCompare( NPC->client->ps.velocity, vec3_origin ) ) + {//if moving at all, don't do this + return; + } + + //See if we should continue to fire on their last position + //!TIMER_Done( NPC, "stick" ) || + if ( !hitAlly //we're not going to hit an ally + && enemyInFOV //enemy is in our FOV //FIXME: or we don't have a clear LOS? + && NPCInfo->enemyLastSeenTime > 0 //we've seen the enemy + && NPCInfo->group //have a group + && (NPCInfo->group->numState[SQUAD_RETREAT]>0||NPCInfo->group->numState[SQUAD_TRANSITION]>0||NPCInfo->group->numState[SQUAD_SCOUT]>0) )//laying down covering fire + { + if ( level.time - NPCInfo->enemyLastSeenTime < 10000 &&//we have seem the enemy in the last 10 seconds + (!NPCInfo->group || level.time - NPCInfo->group->lastSeenEnemyTime < 10000 ))//we are not in a group or the group has seen the enemy in the last 10 seconds + { + if ( !Q_irand( 0, 10 ) ) + { + //Fire on the last known position + vec3_t muzzle, dir, angles; + qboolean tooClose = qfalse; + qboolean tooFar = qfalse; + float distThreshold; + float dist; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + if ( VectorCompare( impactPos, vec3_origin ) ) + {//never checked ShotEntity this frame, so must do a trace... + trace_t tr; + //vec3_t mins = {-2,-2,-2}, maxs = {2,2,2}; + vec3_t forward, end; + AngleVectors( NPC->client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, 8192, forward, end ); + trap_Trace( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, impactPos ); + } + + //see if impact would be too close to me + distThreshold = 16384/*128*128*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 65536/*256*256*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 65536/*256*256*/; + } + break; + default: + break; + } + + dist = DistanceSquared( impactPos, muzzle ); + + if ( dist < distThreshold ) + {//impact would be too close to me + tooClose = qtrue; + } + else if ( level.time - NPCInfo->enemyLastSeenTime > 5000 || + (NPCInfo->group && level.time - NPCInfo->group->lastSeenEnemyTime > 5000 )) + {//we've haven't seen them in the last 5 seconds + //see if it's too far from where he is + distThreshold = 65536/*256*256*/;//default + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + case WP_FLECHETTE: + case WP_THERMAL: + case WP_TRIP_MINE: + case WP_DET_PACK: + distThreshold = 262144/*512*512*/; + break; + case WP_REPEATER: + if ( NPCInfo->scriptFlags&SCF_ALT_FIRE ) + { + distThreshold = 262144/*512*512*/; + } + break; + default: + break; + } + dist = DistanceSquared( impactPos, NPCInfo->enemyLastSeenLocation ); + if ( dist > distThreshold ) + {//impact would be too far from enemy + tooFar = qtrue; + } + } + + if ( !tooClose && !tooFar ) + {//okay too shoot at last pos + VectorSubtract( NPCInfo->enemyLastSeenLocation, muzzle, dir ); + VectorNormalize( dir ); + vectoangles( dir, angles ); + + NPCInfo->desiredYaw = angles[YAW]; + NPCInfo->desiredPitch = angles[PITCH]; + + shoot = qtrue; + faceEnemy = qfalse; + //AI_GroupUpdateSquadstates( NPCInfo->group, NPC, SQUAD_STAND_AND_SHOOT ); + return; + } + } + } + } +} + +void ST_TrackEnemy( gentity_t *self, vec3_t enemyPos ) +{ + //clear timers + TIMER_Set( self, "attackDelay", Q_irand( 1000, 2000 ) ); + //TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stick", Q_irand( 500, 1500 ) ); + TIMER_Set( self, "stand", -1 ); + TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) ); + //leave my combat point + NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse ); + //go after his last seen pos + NPC_SetMoveGoal( self, enemyPos, 16, qfalse, -1, NULL ); +} + +int ST_ApproachEnemy( gentity_t *self ) +{ + TIMER_Set( self, "attackDelay", Q_irand( 250, 500 ) ); + //TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stick", Q_irand( 1000, 2000 ) ); + TIMER_Set( self, "stand", -1 ); + TIMER_Set( self, "scoutTime", TIMER_Get( self, "stick" )-level.time+Q_irand(5000, 10000) ); + //leave my combat point + NPC_FreeCombatPoint( self->NPC->combatPoint, qfalse ); + //return the relevant combat point flags + return (CP_CLEAR|CP_CLOSEST); +} + +void ST_HuntEnemy( gentity_t *self ) +{ + //TIMER_Set( NPC, "attackDelay", Q_irand( 250, 500 ) );//Disabled this for now, guys who couldn't hunt would never attack + //TIMER_Set( NPC, "duck", -1 ); + TIMER_Set( NPC, "stick", Q_irand( 250, 1000 ) ); + TIMER_Set( NPC, "stand", -1 ); + TIMER_Set( NPC, "scoutTime", TIMER_Get( NPC, "stick" )-level.time+Q_irand(5000, 10000) ); + //leave my combat point + NPC_FreeCombatPoint( NPCInfo->combatPoint, qfalse ); + //go directly after the enemy + if ( NPCInfo->scriptFlags & SCF_CHASE_ENEMIES ) + { + self->NPC->goalEntity = NPC->enemy; + } +} + +void ST_TransferTimers( gentity_t *self, gentity_t *other ) +{ + TIMER_Set( other, "attackDelay", TIMER_Get( self, "attackDelay" )-level.time ); + TIMER_Set( other, "duck", TIMER_Get( self, "duck" )-level.time ); + TIMER_Set( other, "stick", TIMER_Get( self, "stick" )-level.time ); + TIMER_Set( other, "scoutTime", TIMER_Get( self, "scout" )-level.time ); + TIMER_Set( other, "roamTime", TIMER_Get( self, "roamTime" )-level.time ); + TIMER_Set( other, "stand", TIMER_Get( self, "stand" )-level.time ); + TIMER_Set( self, "attackDelay", -1 ); + TIMER_Set( self, "duck", -1 ); + TIMER_Set( self, "stick", -1 ); + TIMER_Set( self, "scoutTime", -1 ); + TIMER_Set( self, "roamTime", -1 ); + TIMER_Set( self, "stand", -1 ); +} + +void ST_TransferMoveGoal( gentity_t *self, gentity_t *other ) +{ + if ( trap_ICARUS_TaskIDPending( self, TID_MOVE_NAV ) ) + {//can't transfer movegoal when a script we're running is waiting to complete + return; + } + if ( self->NPC->combatPoint != -1 ) + {//I've got a combatPoint I'm going to, give it to him + self->NPC->lastFailedCombatPoint = other->NPC->combatPoint = self->NPC->combatPoint; + self->NPC->combatPoint = -1; + } + else + {//I must be going for a goal, give that to him instead + if ( self->NPC->goalEntity == self->NPC->tempGoal ) + { + NPC_SetMoveGoal( other, self->NPC->tempGoal->r.currentOrigin, self->NPC->goalRadius, ((self->NPC->tempGoal->flags&FL_NAVGOAL)?qtrue:qfalse), -1, NULL ); + } + else + { + other->NPC->goalEntity = self->NPC->goalEntity; + } + } + //give him my squadstate + AI_GroupUpdateSquadstates( self->NPC->group, other, NPCInfo->squadState ); + + //give him my timers and clear mine + ST_TransferTimers( self, other ); + + //now make me stand around for a second or two at least + AI_GroupUpdateSquadstates( self->NPC->group, self, SQUAD_STAND_AND_SHOOT ); + TIMER_Set( self, "stand", Q_irand( 1000, 3000 ) ); +} + +int ST_GetCPFlags( void ) +{ + int cpFlags = 0; + if ( NPC && NPCInfo->group ) + { + if ( NPC == NPCInfo->group->commander && NPC->client->NPC_class == CLASS_IMPERIAL ) + {//imperials hang back and give orders + if ( NPCInfo->group->numGroup > 1 && Q_irand( -3, NPCInfo->group->numGroup ) > 1 ) + {//FIXME: make sure he;s giving orders with these lines + if ( Q_irand( 0, 1 ) ) + { + ST_Speech( NPC, SPEECH_CHASE, 0.5 ); + } + else + { + ST_Speech( NPC, SPEECH_YELL, 0.5 ); + } + } + cpFlags = (CP_CLEAR|CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT); + } + else if ( NPCInfo->group->morale < 0 ) + {//hide + cpFlags = (CP_COVER|CP_AVOID|CP_SAFE|CP_RETREAT); + } + else if ( NPCInfo->group->morale < NPCInfo->group->numGroup ) + {//morale is low for our size + int moraleDrop = NPCInfo->group->numGroup - NPCInfo->group->morale; + if ( moraleDrop < -6 ) + {//flee (no clear shot needed) + cpFlags = (CP_FLEE|CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE); + } + else if ( moraleDrop < -3 ) + {//retreat (no clear shot needed) + cpFlags = (CP_RETREAT|CP_COVER|CP_AVOID|CP_SAFE); + } + else if ( moraleDrop < 0 ) + {//cover (no clear shot needed) + cpFlags = (CP_COVER|CP_AVOID|CP_SAFE); + } + } + else + { + int moraleBoost = NPCInfo->group->morale - NPCInfo->group->numGroup; + if ( moraleBoost > 20 ) + {//charge to any one and outflank (no cover needed) + cpFlags = (CP_CLEAR|CP_FLANK|CP_APPROACH_ENEMY); + } + else if ( moraleBoost > 15 ) + {//charge to closest one (no cover needed) + cpFlags = (CP_CLEAR|CP_CLOSEST|CP_APPROACH_ENEMY); + } + else if ( moraleBoost > 10 ) + {//charge closer (no cover needed) + cpFlags = (CP_CLEAR|CP_APPROACH_ENEMY); + } + } + } + if ( !cpFlags ) + { + //at some medium level of morale + switch( Q_irand( 0, 3 ) ) + { + case 0://just take the nearest one + cpFlags = (CP_CLEAR|CP_COVER|CP_NEAREST); + break; + case 1://take one closer to the enemy + cpFlags = (CP_CLEAR|CP_COVER|CP_APPROACH_ENEMY); + break; + case 2://take the one closest to the enemy + cpFlags = (CP_CLEAR|CP_COVER|CP_CLOSEST|CP_APPROACH_ENEMY); + break; + case 3://take the one on the other side of the enemy + cpFlags = (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY); + break; + } + } + if ( NPC && (NPCInfo->scriptFlags&SCF_USE_CP_NEAREST) ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + return cpFlags; +} +/* +------------------------- +ST_Commander + + Make decisions about who should go where, etc. + +FIXME: leader (group-decision-making) AI? +FIXME: need alternate routes! +FIXME: more group voice interaction +FIXME: work in pairs? + +------------------------- +*/ +void ST_Commander( void ) +{ + int i, j; + int cp, cpFlags_org, cpFlags; + AIGroupInfo_t *group = NPCInfo->group; + gentity_t *member;//, *buddy; + qboolean runner = qfalse; + qboolean enemyLost = qfalse; + qboolean enemyProtected = qfalse; + qboolean scouting = qfalse; + int squadState; + int curMemberNum, lastMemberNum; + float avoidDist; + + group->processed = qtrue; + + if ( group->enemy == NULL || group->enemy->client == NULL ) + {//hmm, no enemy...?! + return; + } + + //FIXME: have this group commander check the enemy group (if any) and see if they have + // superior numbers. If they do, fall back rather than advance. If you have + // superior numbers, advance on them. + //FIXME: find the group commander and have him occasionally give orders when there is speech + //FIXME: start fleeing when only a couple of you vs. a lightsaber, possibly give up if the only one left + + SaveNPCGlobals(); + + if ( group->lastSeenEnemyTime < level.time - 180000 ) + {//dissolve the group + ST_Speech( NPC, SPEECH_LOST, 0.0f ); + group->enemy->waypoint = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE ); + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + SetNPCGlobals( member ); + if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//running somewhere that a script requires us to go, don't break from that + continue; + } + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not allowed to move on my own + continue; + } + //Lost enemy for three minutes? go into search mode? + G_ClearEnemy( NPC ); + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, group->enemy->waypoint ); + if ( NPC->waypoint == WAYPOINT_NONE ) + { + NPCInfo->behaviorState = BS_DEFAULT;//BS_PATROL; + } + else if ( group->enemy->waypoint == WAYPOINT_NONE || (trap_Nav_GetPathCost( NPC->waypoint, group->enemy->waypoint ) >= Q3_INFINITE) ) + { + NPC_BSSearchStart( NPC->waypoint, BS_SEARCH ); + } + else + { + NPC_BSSearchStart( group->enemy->waypoint, BS_SEARCH ); + } + } + group->enemy = NULL; + RestoreNPCGlobals(); + return; + } + + + //See if anyone in our group is not alerted and alert them + /* + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( !member->enemy ) + {//he's not mad, so get him mad + //Have his buddy tell him to get mad + if ( group->member[i].closestBuddy != ENTITYNUM_NONE ) + { + buddy = &g_entities[group->member[i].closestBuddy]; + if ( buddy->enemy == group->enemy ) + { + SetNPCGlobals( buddy ); + ST_Speech( NPC, SPEECH_CHARGE, 0.7f ); + } + } + SetNPCGlobals( member ); + G_SetEnemy( member, group->enemy ); + } + } + */ + //Okay, everyone is mad + + //see if anyone is running + if ( group->numState[SQUAD_SCOUT] > 0 || + group->numState[SQUAD_TRANSITION] > 0 || + group->numState[SQUAD_RETREAT] > 0 ) + {//someone is running + runner = qtrue; + } + + if ( /*!runner &&*/ group->lastSeenEnemyTime > level.time - 32000 && group->lastSeenEnemyTime < level.time - 30000 ) + {//no-one has seen the enemy for 30 seconds// and no-one is running after him + if ( group->commander && !Q_irand( 0, 1 ) ) + { + ST_Speech( group->commander, SPEECH_ESCAPING, 0.0f ); + } + else + { + ST_Speech( NPC, SPEECH_ESCAPING, 0.0f ); + } + //don't say this again + NPCInfo->blockedSpeechDebounceTime = level.time + 3000; + } + + if ( group->lastSeenEnemyTime < level.time - 10000 ) + {//no-one has seen the enemy for at least 10 seconds! Should send a scout + enemyLost = qtrue; + } + + if ( group->lastClearShotTime < level.time - 5000 ) + {//no-one has had a clear shot for 5 seconds! + enemyProtected = qtrue; + } + + //Go through the list: + + //Everyone should try to get to a combat point if possible + if ( d_asynchronousGroupAI.integer ) + {//do one member a turn + group->activeMemberNum++; + if ( group->activeMemberNum >= group->numGroup ) + { + group->activeMemberNum = 0; + } + curMemberNum = group->activeMemberNum; + lastMemberNum = curMemberNum + 1; + } + else + { + curMemberNum = 0; + lastMemberNum = group->numGroup; + } + for ( i = curMemberNum; i < lastMemberNum; i++ ) + { + //reset combat point flags + cp = -1; + cpFlags = 0; + squadState = SQUAD_IDLE; + avoidDist = 0; + scouting = qfalse; + + //get the next guy + member = &g_entities[group->member[i].number]; + if ( !member->enemy ) + {//don't include guys that aren't angry + continue; + } + SetNPCGlobals( member ); + + if ( !TIMER_Done( NPC, "flee" ) ) + {//running away + continue; + } + + if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//running somewhere that a script requires us to go + continue; + } + + if ( NPC->s.weapon == WP_NONE + && NPCInfo->goalEntity + && NPCInfo->goalEntity == NPCInfo->tempGoal + && NPCInfo->goalEntity->enemy + && NPCInfo->goalEntity->enemy->s.eType == ET_ITEM ) + {//running to pick up a gun, don't do other logic + continue; + } + + //see if this member should start running (only if have no officer... FIXME: should always run from AEL_DANGER_GREAT?) + if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN ) + { + if ( NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//going to run + ST_Speech( NPC, SPEECH_COVER, 0 ); + continue; + } + } + + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not allowed to do combat-movement + continue; + } + + //check the local state + if ( NPCInfo->squadState != SQUAD_RETREAT ) + {//not already retreating + if ( NPC->client->ps.weapon == WP_NONE ) + {//weaponless, should be hiding + if ( NPCInfo->goalEntity == NULL || NPCInfo->goalEntity->enemy == NULL || NPCInfo->goalEntity->enemy->s.eType != ET_ITEM ) + {//not running after a pickup + if ( TIMER_Done( NPC, "hideTime" ) || (DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 && NPC_ClearLOS4( NPC->enemy )) ) + {//done hiding or enemy near and can see us + //er, start another flee I guess? + NPC_StartFlee( NPC->enemy, NPC->enemy->r.currentOrigin, AEL_DANGER_GREAT, 5000, 10000 ); + }//else, just hang here + } + continue; + } + if ( TIMER_Done( NPC, "roamTime" ) && TIMER_Done( NPC, "hideTime" ) && NPC->health > 10 && !trap_InPVS( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + {//cant even see enemy + //better go after him + cpFlags |= (CP_CLEAR|CP_COVER); + } + else if ( NPCInfo->localState == LSTATE_UNDERFIRE ) + {//we've been shot + switch( group->enemy->client->ps.weapon ) + { + case WP_SABER: + if ( DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 )//256 squared + { + cpFlags |= (CP_AVOID_ENEMY|CP_COVER|CP_AVOID|CP_RETREAT); + if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN ) + { + squadState = SQUAD_RETREAT; + } + avoidDist = 256; + } + break; + default: + case WP_BLASTER: + cpFlags |= (CP_COVER); + break; + } + if ( NPC->health <= 10 ) + { + if ( !group->commander || group->commander->NPC->rank < RANK_ENSIGN ) + { + cpFlags |= (CP_FLEE|CP_AVOID|CP_RETREAT); + squadState = SQUAD_RETREAT; + } + } + } + else + {//not hit, see if there are other reasons we should run + if ( trap_InPVS( NPC->r.currentOrigin, group->enemy->r.currentOrigin ) ) + {//in the same room as enemy + if ( NPC->client->ps.weapon == WP_ROCKET_LAUNCHER && + DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < MIN_ROCKET_DIST_SQUARED && + NPCInfo->squadState != SQUAD_TRANSITION ) + {//too close for me to fire my weapon and I'm not already on the move + cpFlags |= (CP_AVOID_ENEMY|CP_CLEAR|CP_AVOID); + avoidDist = 256; + } + else + { + switch( group->enemy->client->ps.weapon ) + { + case WP_SABER: + //if ( group->enemy->client->ps.SaberLength() > 0 ) + if (!group->enemy->client->ps.saberHolstered) + { + if ( DistanceSquared( group->enemy->r.currentOrigin, NPC->r.currentOrigin ) < 65536 ) + { + if ( TIMER_Done( NPC, "hideTime" ) ) + { + if ( NPCInfo->squadState != SQUAD_TRANSITION ) + {//not already moving: FIXME: we need to see if where we're going is good now? + cpFlags |= (CP_AVOID_ENEMY|CP_CLEAR|CP_AVOID); + avoidDist = 256; + } + } + } + } + default: + break; + } + } + } + } + } + + if ( !cpFlags ) + {//okay, we have no new enemy-driven reason to run... let's use tactics now + if ( runner && NPCInfo->combatPoint != -1 ) + {//someone is running and we have a combat point already + if ( NPCInfo->squadState != SQUAD_SCOUT && + NPCInfo->squadState != SQUAD_TRANSITION && + NPCInfo->squadState != SQUAD_RETREAT ) + {//it's not us + if ( TIMER_Done( NPC, "verifyCP" ) && DistanceSquared( NPC->r.currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin ) > 64*64 ) + {//1 - 3 seconds have passed since you chose a CP, see if you're there since, for some reason, you've stopped running... + //uh, WTF, we're not on our combat point? + //er, try again, I guess? + cp = NPCInfo->combatPoint; + cpFlags |= ST_GetCPFlags(); + } + else + {//cover them + //stop ducking + TIMER_Set( NPC, "duck", -1 ); + //start shooting + TIMER_Set( NPC, "attackDelay", -1 ); + //AI should take care of the rest - fire at enemy + } + } + else + {//we're running + //see if we're blocked + if ( NPCInfo->aiFlags & NPCAI_BLOCKED ) + {//dammit, something is in our way + //see if it's one of ours + for ( j = 0; j < group->numGroup; j++ ) + { + if ( group->member[j].number == NPCInfo->blockingEntNum ) + {//we're being blocked by one of our own, pass our goal onto them and I'll stand still + ST_TransferMoveGoal( NPC, &g_entities[group->member[j].number] ); + break; + } + } + } + //we don't need to do anything else + continue; + } + } + else + {//okay no-one is running, use some tactics + if ( NPCInfo->combatPoint != -1 ) + {//we have a combat point we're supposed to be running to + if ( NPCInfo->squadState != SQUAD_SCOUT && + NPCInfo->squadState != SQUAD_TRANSITION && + NPCInfo->squadState != SQUAD_RETREAT ) + {//but we're not running + if ( TIMER_Done( NPC, "verifyCP" ) ) + {//1 - 3 seconds have passed since you chose a CP, see if you're there since, for some reason, you've stopped running... + if ( DistanceSquared( NPC->r.currentOrigin, level.combatPoints[NPCInfo->combatPoint].origin ) > 64*64 ) + {//uh, WTF, we're not on our combat point? + //er, try again, I guess? + cp = NPCInfo->combatPoint; + cpFlags |= ST_GetCPFlags(); + } + } + } + } + if ( enemyLost ) + {//if no-one has seen the enemy for a while, send a scout + //ask where he went + if ( group->numState[SQUAD_SCOUT] <= 0 ) + { + scouting = qtrue; + NPC_ST_StoreMovementSpeech( SPEECH_CHASE, 0.0f ); + } + //Since no-one else has done this, I should be the closest one, so go after him... + ST_TrackEnemy( NPC, group->enemyLastSeenPos ); + //set me into scout mode + AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT ); + //we're not using a cp, so we need to set runner to true right here + runner = qtrue; + } + else if ( enemyProtected ) + {//if no-one has a clear shot at the enemy, someone should go after him + //FIXME: if I'm in an area where no safe combat points have a clear shot at me, they don't come after me... they should anyway, though after some extra hesitation. + //ALSO: seem to give up when behind an area portal? + //since no-one else here has done this, I should be the closest one + if ( TIMER_Done( NPC, "roamTime" ) && !Q_irand( 0, group->numGroup) ) + {//only do this if we're ready to move again and we feel like it + cpFlags |= ST_ApproachEnemy( NPC ); + //set me into scout mode + AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT ); + } + } + else + {//group can see and has been shooting at the enemy + //see if we should do something fancy? + + {//we're ready to move + if ( NPCInfo->combatPoint == -1 ) + {//we're not on a combat point + if ( 1 )//!Q_irand( 0, 2 ) ) + {//we should go for a combat point + cpFlags |= ST_GetCPFlags(); + } + else + { + TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) ); + TIMER_Set( NPC, "roamTime", Q_irand( 1000, 3000 ) ); + } + } + else if ( TIMER_Done( NPC, "roamTime" ) ) + {//we are already on a combat point + if ( i == 0 ) + {//we're the closest + if ( (group->morale-group->numGroup>0) && !Q_irand( 0, 4 ) ) + {//try to outflank him + cpFlags |= (CP_CLEAR|CP_COVER|CP_FLANK|CP_APPROACH_ENEMY); + } + else if ( (group->morale-group->numGroup<0) ) + {//better move! + cpFlags |= ST_GetCPFlags(); + } + else + {//If we're point, then get down + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + TIMER_Set( NPC, "stick", Q_irand( 2000, 5000 ) ); + //FIXME: what if we can't shoot from a ducked pos? + TIMER_Set( NPC, "duck", Q_irand( 3000, 4000 ) ); + AI_GroupUpdateSquadstates( group, NPC, SQUAD_POINT ); + } + } + else if ( i == group->numGroup - 1 ) + {//farthest from the enemy + if ( (group->morale-group->numGroup<0) ) + {//low morale, just hang here + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 5000 ) ); + TIMER_Set( NPC, "stick", Q_irand( 2000, 5000 ) ); + } + else if ( (group->morale-group->numGroup>0) ) + {//try to move in on the enemy + cpFlags |= ST_ApproachEnemy( NPC ); + //set me into scout mode + AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT ); + } + else + {//use normal decision making process + cpFlags |= ST_GetCPFlags(); + } + } + else + {//someone in-between + if ( (group->morale-group->numGroup<0) || !Q_irand( 0, 4 ) ) + {//do something + cpFlags |= ST_GetCPFlags(); + } + else + { + TIMER_Set( NPC, "stick", Q_irand( 2000, 4000 ) ); + TIMER_Set( NPC, "roamTime", Q_irand( 2000, 4000 ) ); + } + } + } + } + if ( !cpFlags ) + {//still not moving + //see if we should say something? + /* + if ( NPC->attackDebounceTime < level.time - 2000 ) + {//we, personally, haven't shot for 2 seconds + //maybe yell at the enemy? + ST_Speech( NPC, SPEECH_CHARGE, 0.9f ); + } + */ + + //see if we should do other fun stuff + //toy with ducking + if ( TIMER_Done( NPC, "duck" ) ) + {//not ducking + if ( TIMER_Done( NPC, "stand" ) ) + {//don't have to keep standing + if ( NPCInfo->combatPoint == -1 || (level.combatPoints[NPCInfo->combatPoint].flags&CPF_DUCK) ) + {//okay to duck here + if ( !Q_irand( 0, 3 ) ) + { + TIMER_Set( NPC, "duck", Q_irand( 1000, 3000 ) ); + } + } + } + } + //FIXME: what about CPF_LEAN? + } + } + } + } + + //clear the local state + NPCInfo->localState = LSTATE_NONE; + + if ( NPCInfo->scriptFlags&SCF_USE_CP_NEAREST ) + { + cpFlags &= ~(CP_FLANK|CP_APPROACH_ENEMY|CP_CLOSEST); + cpFlags |= CP_NEAREST; + } + //Assign combat points + if ( cpFlags ) + {//we want to run to a combat point + /* + if ( NPCInfo->combatPoint != -1 ) + {//if we're on a combat point, we obviously don't want the one we're closest to + cpFlags |= CP_AVOID; + } + */ + + if ( group->enemy->client->ps.weapon == WP_SABER && /*group->enemy->client->ps.SaberLength() > 0*/!group->enemy->client->ps.saberHolstered ) + {//we obviously want to avoid the enemy if he has a saber + cpFlags |= CP_AVOID_ENEMY; + avoidDist = 256; + } + + //remember what we *wanted* to do... + cpFlags_org = cpFlags; + + //now get a combat point + if ( cp == -1 ) + {//may have had sone set above + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, group->enemy->r.currentOrigin, cpFlags|CP_HAS_ROUTE, avoidDist, NPCInfo->lastFailedCombatPoint ); + } + while ( cp == -1 && cpFlags != CP_ANY ) + {//start "OR"ing out certain flags to see if we can find *any* point + if ( cpFlags & CP_INVESTIGATE ) + {//don't need to investigate + cpFlags &= ~CP_INVESTIGATE; + } + else if ( cpFlags & CP_SQUAD ) + {//don't need to stick to squads + cpFlags &= ~CP_SQUAD; + } + else if ( cpFlags & CP_DUCK ) + {//don't need to duck + cpFlags &= ~CP_DUCK; + } + else if ( cpFlags & CP_NEAREST ) + {//don't need closest one to me + cpFlags &= ~CP_NEAREST; + } + else if ( cpFlags & CP_FLANK ) + {//don't need to flank enemy + cpFlags &= ~CP_FLANK; + } + else if ( cpFlags & CP_SAFE ) + {//don't need one that hasn't been shot at recently + cpFlags &= ~CP_SAFE; + } + else if ( cpFlags & CP_CLOSEST ) + {//don't need to get closest to enemy + cpFlags &= ~CP_CLOSEST; + //but let's try to approach at least + cpFlags |= CP_APPROACH_ENEMY; + } + else if ( cpFlags & CP_APPROACH_ENEMY ) + {//don't need to approach enemy + cpFlags &= ~CP_APPROACH_ENEMY; + } + else if ( cpFlags & CP_COVER ) + {//don't need cover + cpFlags &= ~CP_COVER; + //but let's pick one that makes us duck + cpFlags |= CP_DUCK; + } + else if ( cpFlags & CP_CLEAR ) + {//don't need a clear shot to enemy + cpFlags &= ~CP_CLEAR; + } + else if ( cpFlags & CP_AVOID_ENEMY ) + {//don't need to avoid enemy + cpFlags &= ~CP_AVOID_ENEMY; + } + else if ( cpFlags & CP_RETREAT ) + {//don't need to retreat + cpFlags &= ~CP_RETREAT; + } + else if ( cpFlags &CP_FLEE ) + {//don't need to flee + cpFlags &= ~CP_FLEE; + //but at least avoid enemy and pick one that gives cover + cpFlags |= (CP_COVER|CP_AVOID_ENEMY); + } + else if ( cpFlags & CP_AVOID ) + {//okay, even pick one right by me + cpFlags &= ~CP_AVOID; + } + else + { + cpFlags = CP_ANY; + } + //now try again + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, group->enemy->r.currentOrigin, cpFlags|CP_HAS_ROUTE, avoidDist, -1 ); + } + //see if we got a valid one + if ( cp != -1 ) + {//found a combat point + //let others know that someone is now running + runner = qtrue; + //don't change course again until we get to where we're going + TIMER_Set( NPC, "roamTime", Q3_INFINITE ); + TIMER_Set( NPC, "verifyCP", Q_irand( 1000, 3000 ) );//don't make sure you're in your CP for 1 - 3 seconds + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + //okay, try a move right now to see if we can even get there + + //if ( ST_Move() ) + {//we actually can get to it, so okay to say you're going there. + //FIXME: Hmm... any way we can store this move info so we don't have to do it again + // when our turn to think comes up? + + //set us up so others know we're on the move + if ( squadState != SQUAD_IDLE ) + { + AI_GroupUpdateSquadstates( group, NPC, squadState ); + } + else if ( cpFlags&CP_FLEE ) + {//outright running for your life + AI_GroupUpdateSquadstates( group, NPC, SQUAD_RETREAT ); + } + else + {//any other kind of transition between combat points + AI_GroupUpdateSquadstates( group, NPC, SQUAD_TRANSITION ); + } + + //unless we're trying to flee, walk slowly + if ( !(cpFlags_org&CP_FLEE) ) + { + //ucmd.buttons |= BUTTON_CAREFUL; + } + + /* + if ( scouting ) + {//successfully chasing enemy + ST_Speech( NPC, SPEECH_CHASE, 0.0f ); + //don't say this again + //group->speechDebounceTime = level.time + 5000; + } + //flanking: + else */if ( cpFlags & CP_FLANK ) + { + if ( group->numGroup > 1 ) + { + NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 ); + } + } + else + {//okay, let's cheat + if ( group->numGroup > 1 ) + { + float dot = 1.0f; + if ( !Q_irand( 0, 3 ) ) + {//25% of the time, see if we're flanking the enemy + vec3_t eDir2Me, eDir2CP; + + VectorSubtract( NPC->r.currentOrigin, group->enemy->r.currentOrigin, eDir2Me ); + VectorNormalize( eDir2Me ); + + VectorSubtract( level.combatPoints[NPCInfo->combatPoint].origin, group->enemy->r.currentOrigin, eDir2CP ); + VectorNormalize( eDir2CP ); + + dot = DotProduct( eDir2Me, eDir2CP ); + } + + if ( dot < 0.4 ) + {//flanking! + NPC_ST_StoreMovementSpeech( SPEECH_OUTFLANK, -1 ); + } + else if ( !Q_irand( 0, 10 ) ) + {//regular movement + NPC_ST_StoreMovementSpeech( SPEECH_YELL, 0.2f );//was SPEECH_COVER + } + } + } + /* + else if ( cpFlags & CP_CLOSEST || cpFlags & CP_APPROACH_ENEMY ) + { + if ( group->numGroup > 1 ) + { + NPC_ST_StoreMovementSpeech( SPEECH_CHASE, 0.4f ); + } + } + */ + }//else: nothing, a failed move should clear the combatPoint and you can try again next frame + } + else if ( NPCInfo->squadState == SQUAD_SCOUT ) + {//we couldn't find a combatPoint by the player, so just go after him directly + ST_HuntEnemy( NPC ); + //set me into scout mode + AI_GroupUpdateSquadstates( group, NPC, SQUAD_SCOUT ); + //AI should take care of rest + } + } + } + + RestoreNPCGlobals(); + return; +} + +/* +------------------------- +NPC_BSST_Attack +------------------------- +*/ + +void NPC_BSST_Attack( void ) +{ + vec3_t enemyDir, shootDir; + float dot; + + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + //NPC_CheckEnemy( qtrue, qfalse ); + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse )//!NPC->enemy )// + { + NPC->enemy = NULL; + if( NPC->client->playerTeam == NPCTEAM_PLAYER ) + { + NPC_BSPatrol(); + } + else + { + NPC_BSST_Patrol();//FIXME: or patrol? + } + return; + } + + //FIXME: put some sort of delay into the guys depending on how they saw you...? + + //Get our group info + if ( TIMER_Done( NPC, "interrogating" ) ) + { + AI_GetGroup( NPC );//, 45, 512, NPC->enemy ); + } + else + { + //FIXME: when done interrogating, I should send out a team alert! + } + + if ( NPCInfo->group ) + {//I belong to a squad of guys - we should *always* have a group + if ( !NPCInfo->group->processed ) + {//I'm the first ent in my group, I'll make the command decisions +#if AI_TIMERS + int startTime = GetTime(0); +#endif// AI_TIMERS + ST_Commander(); +#if AI_TIMERS + int commTime = GetTime ( startTime ); + if ( commTime > 20 ) + { + gi.Printf( S_COLOR_RED"ERROR: Commander time: %d\n", commTime ); + } + else if ( commTime > 10 ) + { + gi.Printf( S_COLOR_YELLOW"WARNING: Commander time: %d\n", commTime ); + } + else if ( commTime > 2 ) + { + gi.Printf( S_COLOR_GREEN"Commander time: %d\n", commTime ); + } +#endif// AI_TIMERS + } + } + else if ( TIMER_Done( NPC, "flee" ) && NPC_CheckForDanger( NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_DANGER ) ) ) + {//not already fleeing, and going to run + ST_Speech( NPC, SPEECH_COVER, 0 ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( !NPC->enemy ) + {//WTF? somehow we lost our enemy? + NPC_BSST_Patrol();//FIXME: or patrol? + return; + } + + enemyLOS = enemyCS = enemyInFOV = qfalse; + move = qtrue; + faceEnemy = qfalse; + shoot = qfalse; + hitAlly = qfalse; + VectorClear( impactPos ); + enemyDist = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + + VectorSubtract( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, enemyDir ); + VectorNormalize( enemyDir ); + AngleVectors( NPC->client->ps.viewangles, shootDir, NULL, NULL ); + dot = DotProduct( enemyDir, shootDir ); + if ( dot > 0.5f ||( enemyDist * (1.0f-dot)) < 10000 ) + {//enemy is in front of me or they're very close and not behind me + enemyInFOV = qtrue; + } + + if ( enemyDist < MIN_ROCKET_DIST_SQUARED )//128 + {//enemy within 128 + if ( (NPC->client->ps.weapon == WP_FLECHETTE || NPC->client->ps.weapon == WP_REPEATER) && + (NPCInfo->scriptFlags & SCF_ALT_FIRE) ) + {//shooting an explosive, but enemy too close, switch to primary fire + NPCInfo->scriptFlags &= ~SCF_ALT_FIRE; + //FIXME: we can never go back to alt-fire this way since, after this, we don't know if we were initially supposed to use alt-fire or not... + } + } + else if ( enemyDist > 65536 )//256 squared + { + if ( NPC->client->ps.weapon == WP_DISRUPTOR ) + {//sniping... should be assumed + if ( !(NPCInfo->scriptFlags&SCF_ALT_FIRE) ) + {//use primary fire + NPCInfo->scriptFlags |= SCF_ALT_FIRE; + //reset fire-timing variables + NPC_ChangeWeapon( WP_DISRUPTOR ); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + } + } + + //can we see our target? + if ( NPC_ClearLOS4( NPC->enemy ) ) + { + AI_GroupUpdateEnemyLastSeen( NPCInfo->group, NPC->enemy->r.currentOrigin ); + NPCInfo->enemyLastSeenTime = level.time; + enemyLOS = qtrue; + + if ( NPC->client->ps.weapon == WP_NONE ) + { + enemyCS = qfalse;//not true, but should stop us from firing + NPC_AimAdjust( -1 );//adjust aim worse longer we have no weapon + } + else + {//can we shoot our target? + if ( (NPC->client->ps.weapon == WP_ROCKET_LAUNCHER || (NPC->client->ps.weapon == WP_FLECHETTE && (NPCInfo->scriptFlags&SCF_ALT_FIRE))) && enemyDist < MIN_ROCKET_DIST_SQUARED )//128*128 + { + enemyCS = qfalse;//not true, but should stop us from firing + hitAlly = qtrue;//us! + //FIXME: if too close, run away! + } + else if ( enemyInFOV ) + {//if enemy is FOV, go ahead and check for shooting + int hit = NPC_ShotEntity( NPC->enemy, impactPos ); + gentity_t *hitEnt = &g_entities[hit]; + + if ( hit == NPC->enemy->s.number + || ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->enemyTeam ) + || ( hitEnt && hitEnt->takedamage && ((hitEnt->r.svFlags&SVF_GLASS_BRUSH)||hitEnt->health < 40||NPC->s.weapon == WP_EMPLACED_GUN) ) ) + {//can hit enemy or enemy ally or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway + AI_GroupUpdateClearShotTime( NPCInfo->group ); + enemyCS = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + else + {//Hmm, have to get around this bastard + NPC_AimAdjust( 1 );//adjust aim better longer we can see enemy + ST_ResolveBlockedShot( hit ); + if ( hitEnt && hitEnt->client && hitEnt->client->playerTeam == NPC->client->playerTeam ) + {//would hit an ally, don't fire!!! + hitAlly = qtrue; + } + else + {//Check and see where our shot *would* hit... if it's not close to the enemy (within 256?), then don't fire + } + } + } + else + { + enemyCS = qfalse;//not true, but should stop us from firing + } + } + } + else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } + + if ( NPC->client->ps.weapon == WP_NONE ) + { + faceEnemy = qfalse; + shoot = qfalse; + } + else + { + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + if ( enemyCS ) + { + shoot = qtrue; + } + } + + //Check for movement to take care of + ST_CheckMoveState(); + + //See if we should override shooting decision with any special considerations + ST_CheckFireState(); + + if ( faceEnemy ) + {//face the enemy + NPC_FaceEnemy( qtrue ); + } + + if ( !(NPCInfo->scriptFlags&SCF_CHASE_ENEMIES) ) + {//not supposed to chase my enemies + if ( NPCInfo->goalEntity == NPC->enemy ) + {//goal is my entity, so don't move + move = qfalse; + } + } + + if ( NPC->client->ps.weaponTime > 0 && NPC->s.weapon == WP_ROCKET_LAUNCHER ) + { + move = qfalse; + } + + if ( move ) + {//move toward goal + if ( NPCInfo->goalEntity )//&& ( NPCInfo->goalEntity != NPC->enemy || enemyDist > 10000 ) )//100 squared + { + move = ST_Move(); + } + else + { + move = qfalse; + } + } + + if ( !move ) + { + if ( !TIMER_Done( NPC, "duck" ) ) + { + ucmd.upmove = -127; + } + //FIXME: what about leaning? + } + else + {//stop ducking! + TIMER_Set( NPC, "duck", -1 ); + } + + if ( !TIMER_Done( NPC, "flee" ) ) + {//running away + faceEnemy = qfalse; + } + + //FIXME: check scf_face_move_dir here? + + if ( !faceEnemy ) + {//we want to face in the dir we're running + if ( !move ) + {//if we haven't moved, we should look in the direction we last looked? + VectorCopy( NPC->client->ps.viewangles, NPCInfo->lastPathAngles ); + } + NPCInfo->desiredYaw = NPCInfo->lastPathAngles[YAW]; + NPCInfo->desiredPitch = 0; + NPC_UpdateAngles( qtrue, qtrue ); + if ( move ) + {//don't run away and shoot + shoot = qfalse; + } + } + + if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + if ( NPC->enemy && NPC->enemy->enemy ) + { + if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) + {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) + shoot = qfalse; + } + } + //FIXME: don't shoot right away! + if ( NPC->client->ps.weaponTime > 0 ) + { + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER ) + { + if ( !enemyLOS || !enemyCS ) + {//cancel it + NPC->client->ps.weaponTime = 0; + } + else + {//delay our next attempt + TIMER_Set( NPC, "attackDelay", Q_irand( 3000, 5000 ) ); + } + } + } + else if ( shoot ) + {//try to shoot if it's time + if ( TIMER_Done( NPC, "attackDelay" ) ) + { + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + //NASTY + if ( NPC->s.weapon == WP_ROCKET_LAUNCHER + && (ucmd.buttons&BUTTON_ATTACK) + && !move + && g_spskill.integer > 1 + && !Q_irand( 0, 3 ) ) + {//every now and then, shoot a homing rocket + ucmd.buttons &= ~BUTTON_ATTACK; + ucmd.buttons |= BUTTON_ALT_ATTACK; + NPC->client->ps.weaponTime = Q_irand( 1000, 2500 ); + } + } + } +} + +void NPC_BSST_Default( void ) +{ + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if( !NPC->enemy ) + {//don't have an enemy, look for one + NPC_BSST_Patrol(); + } + else //if ( NPC->enemy ) + {//have an enemy + NPC_CheckGetNewWeapon(); + NPC_BSST_Attack(); + } +} diff --git a/code/game/NPC_AI_Utils.c b/code/game/NPC_AI_Utils.c new file mode 100644 index 0000000..617bd92 --- /dev/null +++ b/code/game/NPC_AI_Utils.c @@ -0,0 +1,1139 @@ +// These utilities are meant for strictly non-player, non-team NPCs. +// These functions are in their own file because they are only intended +// for use with NPCs who's logic has been overriden from the original +// AI code, and who's code resides in files with the AI_ prefix. + +#include "b_local.h" +#include "g_nav.h" + +#define MAX_RADIUS_ENTS 128 +#define DEFAULT_RADIUS 45 + +extern vmCvar_t d_noGroupAI; +qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member ); + +extern void G_TestLine(vec3_t start, vec3_t end, int color, int time); + +/* +------------------------- +AI_GetGroupSize +------------------------- +*/ + +int AI_GetGroupSize( vec3_t origin, int radius, team_t playerTeam, gentity_t *avoid ) +{ + int radiusEnts[ MAX_RADIUS_ENTS ]; + gentity_t *check; + vec3_t mins, maxs; + int numEnts, realCount = 0; + int i; + int j; + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = origin[i] - radius; + maxs[i] = origin[i] + radius; + } + + //Get the number of entities in a given space + numEnts = trap_EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS ); + + //Cull this list + for ( j = 0; j < numEnts; j++ ) + { + check = &g_entities[radiusEnts[j]]; + + //Validate clients + if ( check->client == NULL ) + continue; + + //Skip the requested avoid ent if present + if ( ( avoid != NULL ) && ( check == avoid ) ) + continue; + + //Must be on the same team + if ( check->client->playerTeam != playerTeam ) + continue; + + //Must be alive + if ( check->health <= 0 ) + continue; + + realCount++; + } + + return realCount; +} + +//Overload + +int AI_GetGroupSize2( gentity_t *ent, int radius ) +{ + if ( ( ent == NULL ) || ( ent->client == NULL ) ) + return -1; + + return AI_GetGroupSize( ent->r.currentOrigin, radius, ent->client->playerTeam, ent ); +} + +extern int NAV_FindClosestWaypointForPoint( gentity_t *ent, vec3_t point ); +int AI_ClosestGroupEntityNumToPoint( AIGroupInfo_t *group, vec3_t point ) +{ + int markerWP = WAYPOINT_NONE; + int cost, bestCost = Q3_INFINITE; + int closest = ENTITYNUM_NONE; + int i; + + if ( group == NULL || group->numGroup <= 0 ) + { + return ENTITYNUM_NONE; + } + + markerWP = NAV_FindClosestWaypointForPoint( &g_entities[group->member[0].number], point ); + + if ( markerWP == WAYPOINT_NONE ) + { + return ENTITYNUM_NONE; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + cost = trap_Nav_GetPathCost( group->member[i].waypoint, markerWP ); + if ( cost < bestCost ) + { + bestCost = cost; + closest = group->member[i].number; + } + } + + return closest; +} + +void AI_SetClosestBuddy( AIGroupInfo_t *group ) +{ + int i, j; + int dist, bestDist; + + for ( i = 0; i < group->numGroup; i++ ) + { + group->member[i].closestBuddy = ENTITYNUM_NONE; + + bestDist = Q3_INFINITE; + for ( j = 0; j < group->numGroup; j++ ) + { + dist = DistanceSquared( g_entities[group->member[i].number].r.currentOrigin, g_entities[group->member[j].number].r.currentOrigin ); + if ( dist < bestDist ) + { + bestDist = dist; + group->member[i].closestBuddy = group->member[j].number; + } + } + } +} + +void AI_SortGroupByPathCostToEnemy( AIGroupInfo_t *group ) +{ + AIGroupMember_t bestMembers[MAX_GROUP_MEMBERS]; + int i, j, k; + qboolean sort = qfalse; + + if ( group->enemy != NULL ) + {//FIXME: just use enemy->waypoint? + group->enemyWP = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE ); + } + else + { + group->enemyWP = WAYPOINT_NONE; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + if ( group->enemyWP == WAYPOINT_NONE ) + {//FIXME: just use member->waypoint? + group->member[i].waypoint = WAYPOINT_NONE; + group->member[i].pathCostToEnemy = Q3_INFINITE; + } + else + {//FIXME: just use member->waypoint? + group->member[i].waypoint = NAV_FindClosestWaypointForEnt( group->enemy, WAYPOINT_NONE ); + if ( group->member[i].waypoint != WAYPOINT_NONE ) + { + group->member[i].pathCostToEnemy = trap_Nav_GetPathCost( group->member[i].waypoint, group->enemyWP ); + //at least one of us has a path, so do sorting + sort = qtrue; + } + else + { + group->member[i].pathCostToEnemy = Q3_INFINITE; + } + } + } + //Now sort + if ( sort ) + { + //initialize bestMembers data + for ( j = 0; j < group->numGroup; j++ ) + { + bestMembers[j].number = ENTITYNUM_NONE; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + for ( j = 0; j < group->numGroup; j++ ) + { + if ( bestMembers[j].number != ENTITYNUM_NONE ) + {//slot occupied + if ( group->member[i].pathCostToEnemy < bestMembers[j].pathCostToEnemy ) + {//this guy has a shorter path than the one currenly in this spot, bump him and put myself in here + for ( k = group->numGroup; k > j; k++ ) + { + memcpy( &bestMembers[k], &bestMembers[k-1], sizeof( bestMembers[k] ) ); + } + memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) ); + break; + } + } + else + {//slot unoccupied, reached end of list, throw self in here + memcpy( &bestMembers[j], &group->member[i], sizeof( bestMembers[j] ) ); + break; + } + } + } + + //Okay, now bestMembers is a sorted list, just copy it into group->members + for ( i = 0; i < group->numGroup; i++ ) + { + memcpy( &group->member[i], &bestMembers[i], sizeof( group->member[i] ) ); + } + } +} + +qboolean AI_FindSelfInPreviousGroup( gentity_t *self ) +{//go through other groups made this frame and see if any of those contain me already + int i, j; + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( level.groups[i].numGroup )//&& level.groups[i].enemy != NULL ) + {//check this one + for ( j = 0; j < level.groups[i].numGroup; j++ ) + { + if ( level.groups[i].member[j].number == self->s.number ) + { + self->NPC->group = &level.groups[i]; + return qtrue; + } + } + } + } + return qfalse; +} + +void AI_InsertGroupMember( AIGroupInfo_t *group, gentity_t *member ) +{ + int i; + + //okay, you know what? Check this damn group and make sure we're not already in here! + for ( i = 0; i < group->numGroup; i++ ) + { + if ( group->member[i].number == member->s.number ) + {//already in here + break; + } + } + if ( i < group->numGroup ) + {//found him in group already + } + else + {//add him in + group->member[group->numGroup++].number = member->s.number; + group->numState[member->NPC->squadState]++; + } + if ( !group->commander || (member->NPC->rank > group->commander->NPC->rank) ) + {//keep track of highest rank + group->commander = member; + } + member->NPC->group = group; +} + +qboolean AI_TryJoinPreviousGroup( gentity_t *self ) +{//go through other groups made this frame and see if any of those have the same enemy as me... if so, add me in! + int i; + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( level.groups[i].numGroup + && level.groups[i].numGroup < (MAX_GROUP_MEMBERS - 1) + //&& level.groups[i].enemy != NULL + && level.groups[i].enemy == self->enemy ) + {//has members, not full and has my enemy + if ( AI_ValidateGroupMember( &level.groups[i], self ) ) + {//I am a valid member for this group + AI_InsertGroupMember( &level.groups[i], self ); + return qtrue; + } + } + } + return qfalse; +} + +qboolean AI_GetNextEmptyGroup( gentity_t *self ) +{ + int i; + + if ( AI_FindSelfInPreviousGroup( self ) ) + {//already in one, no need to make a new one + return qfalse; + } + + if ( AI_TryJoinPreviousGroup( self ) ) + {//try to just put us in one that already exists + return qfalse; + } + + //okay, make a whole new one, then + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( !level.groups[i].numGroup ) + {//make a new one + self->NPC->group = &level.groups[i]; + return qtrue; + } + } + + //if ( i >= MAX_FRAME_GROUPS ) + {//WTF? Out of groups! + self->NPC->group = NULL; + return qfalse; + } +} + +qboolean AI_ValidateNoEnemyGroupMember( AIGroupInfo_t *group, gentity_t *member ) +{ + vec3_t center; + + if ( !group ) + { + return qfalse; + } + if ( group->commander ) + { + VectorCopy( group->commander->r.currentOrigin, center ); + } + else + {//hmm, just pick the first member + if ( group->member[0].number < 0 || group->member[0].number >= ENTITYNUM_WORLD ) + { + return qfalse; + } + VectorCopy( g_entities[group->member[0].number].r.currentOrigin, center ); + } + //FIXME: maybe it should be based on the center of the mass of the group, not the commander? + if ( DistanceSquared( center, member->r.currentOrigin ) > 147456/*384*384*/ ) + { + return qfalse; + } + if ( !trap_InPVS( member->r.currentOrigin, center ) ) + {//not within PVS of the group enemy + return qfalse; + } + return qtrue; +} + +qboolean AI_ValidateGroupMember( AIGroupInfo_t *group, gentity_t *member ) +{ + //Validate ents + if ( member == NULL ) + return qfalse; + + //Validate clients + if ( member->client == NULL ) + return qfalse; + + //Validate NPCs + if ( member->NPC == NULL ) + return qfalse; + + //must be aware + if ( member->NPC->confusionTime > level.time ) + return qfalse; + + //must be allowed to join groups + if ( member->NPC->scriptFlags&SCF_NO_GROUPS ) + return qfalse; + + //Must not be in another group + if ( member->NPC->group != NULL && member->NPC->group != group ) + {//FIXME: if that group's enemy is mine, why not absorb that group into mine? + return qfalse; + } + + //Must be alive + if ( member->health <= 0 ) + return qfalse; + + //can't be in an emplaced gun +// if( member->s.eFlags & EF_LOCKED_TO_WEAPON ) +// return qfalse; + //rwwFIXMEFIXME: support this flag + + //Must be on the same team + if ( member->client->playerTeam != group->team ) + return qfalse; + + if ( member->client->ps.weapon == WP_SABER ||//!= self->s.weapon ) + member->client->ps.weapon == WP_THERMAL || + member->client->ps.weapon == WP_DISRUPTOR || + member->client->ps.weapon == WP_EMPLACED_GUN || +// member->client->ps.weapon == WP_BOT_LASER || // Probe droid - Laser blast + member->client->ps.weapon == WP_STUN_BATON || + member->client->ps.weapon == WP_TURRET /*|| // turret guns + member->client->ps.weapon == WP_ATST_MAIN || + member->client->ps.weapon == WP_ATST_SIDE || + member->client->ps.weapon == WP_TIE_FIGHTER*/ ) + {//not really a squad-type guy + return qfalse; + } + + if ( member->client->NPC_class == CLASS_ATST || + member->client->NPC_class == CLASS_PROBE || + member->client->NPC_class == CLASS_SEEKER || + member->client->NPC_class == CLASS_REMOTE || + member->client->NPC_class == CLASS_SENTRY || + member->client->NPC_class == CLASS_INTERROGATOR || + member->client->NPC_class == CLASS_MINEMONSTER || + member->client->NPC_class == CLASS_HOWLER || + member->client->NPC_class == CLASS_MARK1 || + member->client->NPC_class == CLASS_MARK2 ) + {//these kinds of enemies don't actually use this group AI + return qfalse; + } + + //should have same enemy + if ( member->enemy != group->enemy ) + { + if ( member->enemy != NULL ) + {//he's fighting someone else, leave him out + return qfalse; + } + if ( !trap_InPVS( member->r.currentOrigin, group->enemy->r.currentOrigin ) ) + {//not within PVS of the group enemy + return qfalse; + } + } + else if ( group->enemy == NULL ) + {//if the group is a patrol group, only take those within the room and radius + if ( !AI_ValidateNoEnemyGroupMember( group, member ) ) + { + return qfalse; + } + } + //must be actually in combat mode + if ( !TIMER_Done( member, "interrogating" ) ) + return qfalse; + //FIXME: need to have a route to enemy and/or clear shot? + return qtrue; +} + +/* +------------------------- +AI_GetGroup +------------------------- +*/ +//#define MAX_WAITERS 128 +void AI_GetGroup( gentity_t *self ) +{ + int i; + gentity_t *member;//, *waiter; + //int waiters[MAX_WAITERS]; + + if ( !self || !self->NPC ) + { + return; + } + + if ( d_noGroupAI.integer ) + { + self->NPC->group = NULL; + return; + } + + if ( !self->client ) + { + self->NPC->group = NULL; + return; + } + + if ( self->NPC->scriptFlags&SCF_NO_GROUPS ) + { + self->NPC->group = NULL; + return; + } + + if ( self->enemy && (!self->enemy->client || (level.time - self->NPC->enemyLastSeenTime > 7000 ))) + { + self->NPC->group = NULL; + return; + } + + if ( !AI_GetNextEmptyGroup( self ) ) + {//either no more groups left or we're already in a group built earlier + return; + } + + //create a new one + memset( self->NPC->group, 0, sizeof( AIGroupInfo_t ) ); + + self->NPC->group->enemy = self->enemy; + self->NPC->group->team = self->client->playerTeam; + self->NPC->group->processed = qfalse; + self->NPC->group->commander = self; + self->NPC->group->memberValidateTime = level.time + 2000; + self->NPC->group->activeMemberNum = 0; + + if ( self->NPC->group->enemy ) + { + self->NPC->group->lastSeenEnemyTime = level.time; + self->NPC->group->lastClearShotTime = level.time; + VectorCopy( self->NPC->group->enemy->r.currentOrigin, self->NPC->group->enemyLastSeenPos ); + } + +// for ( i = 0, member = &g_entities[0]; i < globals.num_entities ; i++, member++) + for ( i = 0; i < level.num_entities ; i++) + { + member = &g_entities[i]; + + if (!member->inuse) + { + continue; + } + + if ( !AI_ValidateGroupMember( self->NPC->group, member ) ) + {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group + continue; + } + + //store it + AI_InsertGroupMember( self->NPC->group, member ); + + if ( self->NPC->group->numGroup >= (MAX_GROUP_MEMBERS - 1) ) + {//full + break; + } + } + + /* + //now go through waiters and see if any should join the group + //NOTE: Some should hang back and probably not attack, so we can ambush + //NOTE: only do this if calling for reinforcements? + for ( i = 0; i < numWaiters; i++ ) + { + waiter = &g_entities[waiters[i]]; + + for ( j = 0; j < self->NPC->group->numGroup; j++ ) + { + member = &g_entities[self->NPC->group->member[j]; + + if ( trap_InPVS( waiter->r.currentOrigin, member->r.currentOrigin ) ) + {//this waiter is within PVS of a current member + } + } + } + */ + + if ( self->NPC->group->numGroup <= 0 ) + {//none in group + self->NPC->group = NULL; + return; + } + + AI_SortGroupByPathCostToEnemy( self->NPC->group ); + AI_SetClosestBuddy( self->NPC->group ); +} + +void AI_SetNewGroupCommander( AIGroupInfo_t *group ) +{ + gentity_t *member = NULL; + int i; + + group->commander = NULL; + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + + if ( !group->commander || (member && member->NPC && group->commander->NPC && member->NPC->rank > group->commander->NPC->rank) ) + {//keep track of highest rank + group->commander = member; + } + } +} + +void AI_DeleteGroupMember( AIGroupInfo_t *group, int memberNum ) +{ + int i; + + if ( group->commander && group->commander->s.number == group->member[memberNum].number ) + { + group->commander = NULL; + } + if ( g_entities[group->member[memberNum].number].NPC ) + { + g_entities[group->member[memberNum].number].NPC->group = NULL; + } + for ( i = memberNum; i < (group->numGroup-1); i++ ) + { + memcpy( &group->member[i], &group->member[i+1], sizeof( group->member[i] ) ); + } + if ( memberNum < group->activeMemberNum ) + { + group->activeMemberNum--; + if ( group->activeMemberNum < 0 ) + { + group->activeMemberNum = 0; + } + } + group->numGroup--; + if ( group->numGroup < 0 ) + { + group->numGroup = 0; + } + AI_SetNewGroupCommander( group ); +} + +void AI_DeleteSelfFromGroup( gentity_t *self ) +{ + int i; + + //FIXME: if killed, keep track of how many in group killed? To affect morale? + for ( i = 0; i < self->NPC->group->numGroup; i++ ) + { + if ( self->NPC->group->member[i].number == self->s.number ) + { + AI_DeleteGroupMember( self->NPC->group, i ); + return; + } + } +} + +extern void ST_AggressionAdjust( gentity_t *self, int change ); +extern void ST_MarkToCover( gentity_t *self ); +extern void ST_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int minTime, int maxTime ); +void AI_GroupMemberKilled( gentity_t *self ) +{ + AIGroupInfo_t *group = self->NPC->group; + gentity_t *member; + qboolean noflee = qfalse; + int i; + + if ( !group ) + {//what group? + return; + } + if ( !self || !self->NPC || self->NPC->rank < RANK_ENSIGN ) + {//I'm not an officer, let's not really care for now + return; + } + //temporarily drop group morale for a few seconds + group->moraleAdjust -= self->NPC->rank; + //go through and drop aggression on my teammates (more cover, worse aim) + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( member == self ) + { + continue; + } + if ( member->NPC->rank > RANK_ENSIGN ) + {//officers do not panic + noflee = qtrue; + } + else + { + ST_AggressionAdjust( member, -1 ); + member->NPC->currentAim -= Q_irand( 0, 10 );//Q_irand( 0, 2);//drop their aim accuracy + } + } + //okay, if I'm the group commander, make everyone else flee + if ( group->commander != self ) + {//I'm not the commander... hmm, should maybe a couple flee... maybe those near me? + return; + } + //now see if there is another of sufficient rank to keep them from fleeing + if ( !noflee ) + { + self->NPC->group->speechDebounceTime = 0; + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( member == self ) + { + continue; + } + if ( member->NPC->rank < RANK_ENSIGN ) + {//grunt + if ( group->enemy && DistanceSquared( member->r.currentOrigin, group->enemy->r.currentOrigin ) < 65536/*256*256*/ ) + {//those close to enemy run away! + ST_StartFlee( member, group->enemy, member->r.currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + } + else if ( DistanceSquared( member->r.currentOrigin, self->r.currentOrigin ) < 65536/*256*256*/ ) + {//those close to me run away! + ST_StartFlee( member, group->enemy, member->r.currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + } + else + {//else, maybe just a random chance + if ( Q_irand( 0, self->NPC->rank ) > member->NPC->rank ) + {//lower rank they are, higher rank I am, more likely they are to flee + ST_StartFlee( member, group->enemy, member->r.currentOrigin, AEL_DANGER_GREAT, 3000, 5000 ); + } + else + { + ST_MarkToCover( member ); + } + } + member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more + } + member->NPC->currentAim -= Q_irand( 1, 15 ); //Q_irand( 1, 3 );//drop their aim accuracy even more + } + } +} + +void AI_GroupUpdateEnemyLastSeen( AIGroupInfo_t *group, vec3_t spot ) +{ + if ( !group ) + { + return; + } + group->lastSeenEnemyTime = level.time; + VectorCopy( spot, group->enemyLastSeenPos ); +} + +void AI_GroupUpdateClearShotTime( AIGroupInfo_t *group ) +{ + if ( !group ) + { + return; + } + group->lastClearShotTime = level.time; +} + +void AI_GroupUpdateSquadstates( AIGroupInfo_t *group, gentity_t *member, int newSquadState ) +{ + int i; + + if ( !group ) + { + member->NPC->squadState = newSquadState; + return; + } + + for ( i = 0; i < group->numGroup; i++ ) + { + if ( group->member[i].number == member->s.number ) + { + group->numState[member->NPC->squadState]--; + member->NPC->squadState = newSquadState; + group->numState[member->NPC->squadState]++; + return; + } + } +} + +qboolean AI_RefreshGroup( AIGroupInfo_t *group ) +{ + gentity_t *member; + int i;//, j; + + //see if we should merge with another group + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( &level.groups[i] == group ) + { + break; + } + else + { + if ( level.groups[i].enemy == group->enemy ) + {//2 groups with same enemy + if ( level.groups[i].numGroup+group->numGroup < (MAX_GROUP_MEMBERS - 1) ) + {//combining the members would fit in one group + qboolean deleteWhenDone = qtrue; + int j; + + //combine the members of mine into theirs + for ( j = 0; j < group->numGroup; j++ ) + { + member = &g_entities[group->member[j].number]; + if ( level.groups[i].enemy == NULL ) + {//special case for groups without enemies, must be in range + if ( !AI_ValidateNoEnemyGroupMember( &level.groups[i], member ) ) + { + deleteWhenDone = qfalse; + continue; + } + } + //remove this member from this group + AI_DeleteGroupMember( group, j ); + //keep marker at same place since we deleted this guy and shifted everyone up one + j--; + //add them to the earlier group + AI_InsertGroupMember( &level.groups[i], member ); + } + //return and delete this group + if ( deleteWhenDone ) + { + return qfalse; + } + } + } + } + } + //clear numStates + for ( i = 0; i < NUM_SQUAD_STATES; i++ ) + { + group->numState[i] = 0; + } + + //go through group and validate each membership + group->commander = NULL; + for ( i = 0; i < group->numGroup; i++ ) + { + /* + //this checks for duplicate copies of one member in a group + for ( j = 0; j < group->numGroup; j++ ) + { + if ( i != j ) + { + if ( group->member[i].number == group->member[j].number ) + { + break; + } + } + } + if ( j < group->numGroup ) + {//found a dupe! + gi.Printf( S_COLOR_RED"ERROR: member %s(%d) a duplicate group member!!!\n", g_entities[group->member[i].number].targetname, group->member[i].number ); + AI_DeleteGroupMember( group, i ); + i--; + continue; + } + */ + member = &g_entities[group->member[i].number]; + + //Must be alive + if ( member->health <= 0 ) + { + AI_DeleteGroupMember( group, i ); + //keep marker at same place since we deleted this guy and shifted everyone up one + i--; + } + else if ( group->memberValidateTime < level.time && !AI_ValidateGroupMember( group, member ) ) + { + //remove this one from the group + AI_DeleteGroupMember( group, i ); + //keep marker at same place since we deleted this guy and shifted everyone up one + i--; + } + else + {//membership is valid + //keep track of squadStates + group->numState[member->NPC->squadState]++; + if ( !group->commander || member->NPC->rank > group->commander->NPC->rank ) + {//keep track of highest rank + group->commander = member; + } + } + } + if ( group->memberValidateTime < level.time ) + { + group->memberValidateTime = level.time + Q_irand( 500, 2500 ); + } + //Now add any new guys as long as we're not full + /* + for ( i = 0, member = &g_entities[0]; i < globals.num_entities && group->numGroup < (MAX_GROUP_MEMBERS - 1); i++, member++) + { + if ( !AI_ValidateGroupMember( group, member ) ) + {//FIXME: keep track of those who aren't angry yet and see if we should wake them after we assemble the core group + continue; + } + if ( member->NPC->group == group ) + {//DOH, already in our group + continue; + } + + //store it + AI_InsertGroupMember( group, member ); + } + */ + + //calc the morale of this group + group->morale = group->moraleAdjust; + for ( i = 0; i < group->numGroup; i++ ) + { + member = &g_entities[group->member[i].number]; + if ( member->NPC->rank < RANK_ENSIGN ) + {//grunts + group->morale++; + } + else + { + group->morale += member->NPC->rank; + } + if ( group->commander && debugNPCAI.integer ) + { + //G_DebugLine( group->commander->r.currentOrigin, member->r.currentOrigin, FRAMETIME, 0x00ff00ff, qtrue ); + G_TestLine(group->commander->r.currentOrigin, member->r.currentOrigin, 0x00000ff, FRAMETIME); + } + } + if ( group->enemy ) + {//modify morale based on enemy health and weapon + if ( group->enemy->health < 10 ) + { + group->morale += 10; + } + else if ( group->enemy->health < 25 ) + { + group->morale += 5; + } + else if ( group->enemy->health < 50 ) + { + group->morale += 2; + } + switch( group->enemy->s.weapon ) + { + case WP_SABER: + group->morale -= 5; + break; + case WP_BRYAR_PISTOL: + group->morale += 3; + break; + case WP_DISRUPTOR: + group->morale += 2; + break; + case WP_REPEATER: + group->morale -= 1; + break; + case WP_FLECHETTE: + group->morale -= 2; + break; + case WP_ROCKET_LAUNCHER: + group->morale -= 10; + break; + case WP_THERMAL: + group->morale -= 5; + break; + case WP_TRIP_MINE: + group->morale -= 3; + break; + case WP_DET_PACK: + group->morale -= 10; + break; +// case WP_MELEE: // Any ol' melee attack +// group->morale += 20; +// break; + case WP_STUN_BATON: + group->morale += 10; + break; + case WP_EMPLACED_GUN: + group->morale -= 8; + break; +// case WP_ATST_MAIN: +// group->morale -= 8; +// break; +// case WP_ATST_SIDE: +// group->morale -= 20; +// break; + } + } + if ( group->moraleDebounce < level.time ) + {//slowly degrade whatever moraleAdjusters we may have + if ( group->moraleAdjust > 0 ) + { + group->moraleAdjust--; + } + else if ( group->moraleAdjust < 0 ) + { + group->moraleAdjust++; + } + group->moraleDebounce = level.time + 1000;//FIXME: define? + } + //mark this group as not having been run this frame + group->processed = qfalse; + + return (group->numGroup>0); +} + +void AI_UpdateGroups( void ) +{ + int i; + + if ( d_noGroupAI.integer ) + { + return; + } + //Clear all Groups + for ( i = 0; i < MAX_FRAME_GROUPS; i++ ) + { + if ( !level.groups[i].numGroup || AI_RefreshGroup( &level.groups[i] ) == qfalse )//level.groups[i].enemy == NULL || + { + memset( &level.groups[i], 0, sizeof( level.groups[i] ) ); + } + } +} + +qboolean AI_GroupContainsEntNum( AIGroupInfo_t *group, int entNum ) +{ + int i; + + if ( !group ) + { + return qfalse; + } + for ( i = 0; i < group->numGroup; i++ ) + { + if ( group->member[i].number == entNum ) + { + return qtrue; + } + } + return qfalse; +} +//Overload + +/* +void AI_GetGroup( AIGroupInfo_t &group, gentity_t *ent, int radius ) +{ + if ( ent->client == NULL ) + return; + + vec3_t temp, angles; + + //FIXME: This is specialized code.. move? + if ( ent->enemy ) + { + VectorSubtract( ent->enemy->r.currentOrigin, ent->r.currentOrigin, temp ); + VectorNormalize( temp ); //FIXME: Needed? + vectoangles( temp, angles ); + } + else + { + VectorCopy( ent->currentAngles, angles ); + } + + AI_GetGroup( group, ent->r.currentOrigin, ent->currentAngles, DEFAULT_RADIUS, radius, ent->client->playerTeam, ent, ent->enemy ); +} +*/ +/* +------------------------- +AI_CheckEnemyCollision +------------------------- +*/ + +qboolean AI_CheckEnemyCollision( gentity_t *ent, qboolean takeEnemy ) +{ + navInfo_t info; + + if ( ent == NULL ) + return qfalse; + +// if ( ent->svFlags & SVF_LOCKEDENEMY ) +// return qfalse; + + NAV_GetLastMove( &info ); + + //See if we've hit something + if ( ( info.blocker ) && ( info.blocker != ent->enemy ) ) + { + if ( ( info.blocker->client ) && ( info.blocker->client->playerTeam == ent->client->enemyTeam ) ) + { + if ( takeEnemy ) + G_SetEnemy( ent, info.blocker ); + + return qtrue; + } + } + + return qfalse; +} + +/* +------------------------- +AI_DistributeAttack +------------------------- +*/ + +#define MAX_RADIUS_ENTS 128 + +gentity_t *AI_DistributeAttack( gentity_t *attacker, gentity_t *enemy, team_t team, int threshold ) +{ + int radiusEnts[ MAX_RADIUS_ENTS ]; + gentity_t *check; + int numEnts; + int numSurrounding; + int i; + int j; + vec3_t mins, maxs; + + //Don't take new targets +// if ( NPC->svFlags & SVF_LOCKEDENEMY ) +// return enemy; + + numSurrounding = AI_GetGroupSize( enemy->r.currentOrigin, 48, team, attacker ); + + //First, see if we should look for the player + if ( enemy != &g_entities[0] ) + { + //rwwFIXMEFIXME: care about all clients not just 0 + int aroundPlayer = AI_GetGroupSize( g_entities[0].r.currentOrigin, 48, team, attacker ); + + //See if we're above our threshold + if ( aroundPlayer < threshold ) + { + return &g_entities[0]; + } + } + + //See if our current enemy is still ok + if ( numSurrounding < threshold ) + return enemy; + + //Otherwise we need to take a new enemy if possible + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = enemy->r.currentOrigin[i] - 512; + maxs[i] = enemy->r.currentOrigin[i] + 512; + } + + //Get the number of entities in a given space + numEnts = trap_EntitiesInBox( mins, maxs, radiusEnts, MAX_RADIUS_ENTS ); + + //Cull this list + for ( j = 0; j < numEnts; j++ ) + { + check = &g_entities[radiusEnts[j]]; + + //Validate clients + if ( check->client == NULL ) + continue; + + //Skip the requested avoid ent if present + if ( ( check == enemy ) ) + continue; + + //Must be on the same team + if ( check->client->playerTeam != enemy->client->playerTeam ) + continue; + + //Must be alive + if ( check->health <= 0 ) + continue; + + //Must not be overwhelmed + if ( AI_GetGroupSize( check->r.currentOrigin, 48, team, attacker ) > threshold ) + continue; + + return check; + } + + return NULL; +} diff --git a/code/game/NPC_AI_Wampa.c b/code/game/NPC_AI_Wampa.c new file mode 100644 index 0000000..a1c279f --- /dev/null +++ b/code/game/NPC_AI_Wampa.c @@ -0,0 +1,654 @@ +#include "b_local.h" +#include "g_nav.h" + +// These define the working combat range for these suckers +#define MIN_DISTANCE 48 +#define MIN_DISTANCE_SQR ( MIN_DISTANCE * MIN_DISTANCE ) + +#define MAX_DISTANCE 1024 +#define MAX_DISTANCE_SQR ( MAX_DISTANCE * MAX_DISTANCE ) + +#define LSTATE_CLEAR 0 +#define LSTATE_WAITING 1 + +float enemyDist = 0; + +void Wampa_SetBolts( gentity_t *self ) +{ + if ( self && self->client ) + { + renderInfo_t *ri = &self->client->renderInfo; + ri->headBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*head_eyes"); + //ri->cervicalBolt = trap_G2API_AddBolt(self->ghoul2, 0, "neck_bone" ); + //ri->chestBolt = trap_G2API_AddBolt(self->ghoul2, 0, "upper_spine"); + //ri->gutBolt = trap_G2API_AddBolt(self->ghoul2, 0, "mid_spine"); + ri->torsoBolt = trap_G2API_AddBolt(self->ghoul2, 0, "lower_spine"); + ri->crotchBolt = trap_G2API_AddBolt(self->ghoul2, 0, "rear_bone"); + //ri->elbowLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_arm_elbow"); + //ri->elbowRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_arm_elbow"); + ri->handLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_hand"); + ri->handRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_hand"); + //ri->kneeLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*hips_l_knee"); + //ri->kneeRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*hips_r_knee"); + ri->footLBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*l_leg_foot"); + ri->footRBolt = trap_G2API_AddBolt(self->ghoul2, 0, "*r_leg_foot"); + } +} + +/* +------------------------- +NPC_Wampa_Precache +------------------------- +*/ +void NPC_Wampa_Precache( void ) +{ + /* + int i; + for ( i = 1; i < 4; i ++ ) + { + G_SoundIndex( va("sound/chars/wampa/growl%d.wav", i) ); + } + for ( i = 1; i < 3; i ++ ) + { + G_SoundIndex( va("sound/chars/wampa/snort%d.wav", i) ); + } + */ + G_SoundIndex( "sound/chars/rancor/swipehit.wav" ); + //G_SoundIndex( "sound/chars/wampa/chomp.wav" ); +} + + +/* +------------------------- +Wampa_Idle +------------------------- +*/ +void Wampa_Idle( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons &= ~BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } +} + +qboolean Wampa_CheckRoar( gentity_t *self ) +{ + if ( self->wait < level.time ) + { + self->wait = level.time + Q_irand( 5000, 20000 ); + NPC_SetAnim( self, SETANIM_BOTH, Q_irand(BOTH_GESTURE1,BOTH_GESTURE2), (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD) ); + TIMER_Set( self, "rageTime", self->client->ps.legsTimer ); + return qtrue; + } + return qfalse; +} +/* +------------------------- +Wampa_Patrol +------------------------- +*/ +void Wampa_Patrol( void ) +{ + NPCInfo->localState = LSTATE_CLEAR; + + //If we have somewhere to go, then do that + if ( UpdateGoal() ) + { + ucmd.buttons |= BUTTON_WALKING; + NPC_MoveToGoal( qtrue ); + } + else + { + if ( TIMER_Done( NPC, "patrolTime" )) + { + TIMER_Set( NPC, "patrolTime", crandom() * 5000 + 5000 ); + } + } + + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Wampa_Idle(); + return; + } + Wampa_CheckRoar( NPC ); + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); +} + +/* +------------------------- +Wampa_Move +------------------------- +*/ +void Wampa_Move( qboolean visible ) +{ + if ( NPCInfo->localState != LSTATE_WAITING ) + { + NPCInfo->goalEntity = NPC->enemy; + + if ( NPC->enemy ) + {//pick correct movement speed and anim + //run by default + ucmd.buttons &= ~BUTTON_WALKING; + if ( !TIMER_Done( NPC, "runfar" ) + || !TIMER_Done( NPC, "runclose" ) ) + {//keep running with this anim & speed for a bit + } + else if ( !TIMER_Done( NPC, "walk" ) ) + {//keep walking for a bit + ucmd.buttons |= BUTTON_WALKING; + } + else if ( visible && enemyDist > 384 && NPCInfo->stats.runSpeed == 180 ) + {//fast run, all fours + NPCInfo->stats.runSpeed = 300; + TIMER_Set( NPC, "runfar", Q_irand( 2000, 4000 ) ); + } + else if ( enemyDist > 256 && NPCInfo->stats.runSpeed == 300 ) + {//slow run, upright + NPCInfo->stats.runSpeed = 180; + TIMER_Set( NPC, "runclose", Q_irand( 3000, 5000 ) ); + } + else if ( enemyDist < 128 ) + {//walk + NPCInfo->stats.runSpeed = 180; + ucmd.buttons |= BUTTON_WALKING; + TIMER_Set( NPC, "walk", Q_irand( 4000, 6000 ) ); + } + } + + if ( NPCInfo->stats.runSpeed == 300 ) + {//need to use the alternate run - hunched over on all fours + NPC->client->ps.eFlags2 |= EF2_USE_ALT_ANIM; + } + NPC_MoveToGoal( qtrue ); + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + } +} + +//--------------------------------------------------------- +//extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_Knockdown( gentity_t *victim ); +extern void G_Dismember( gentity_t *ent, gentity_t *enemy, vec3_t point, int limbType, float limbRollBase, float limbPitchBase, int deathAnim, qboolean postDeath ); +extern int NPC_GetEntsNearBolt( int *radiusEnts, float radius, int boltIndex, vec3_t boltOrg ); + +void Wampa_Slash( int boltIndex, qboolean backhand ) +{ + int radiusEntNums[128]; + int numEnts; + const float radius = 88; + const float radiusSquared = (radius*radius); + int i; + vec3_t boltOrg; + int damage = (backhand)?Q_irand(10,15):Q_irand(20,30); + + numEnts = NPC_GetEntsNearBolt( radiusEntNums, radius, boltIndex, boltOrg ); + + for ( i = 0; i < numEnts; i++ ) + { + gentity_t *radiusEnt = &g_entities[radiusEntNums[i]]; + if ( !radiusEnt->inuse ) + { + continue; + } + + if ( radiusEnt == NPC ) + {//Skip the wampa ent + continue; + } + + if ( radiusEnt->client == NULL ) + {//must be a client + continue; + } + + if ( DistanceSquared( radiusEnt->r.currentOrigin, boltOrg ) <= radiusSquared ) + { + //smack + G_Damage( radiusEnt, NPC, NPC, vec3_origin, radiusEnt->r.currentOrigin, damage, ((backhand)?DAMAGE_NO_ARMOR:(DAMAGE_NO_ARMOR|DAMAGE_NO_KNOCKBACK)), MOD_MELEE ); + if ( backhand ) + { + //actually push the enemy + vec3_t pushDir; + vec3_t angs; + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += flrand( 25, 50 ); + angs[PITCH] = flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + if ( radiusEnt->client->NPC_class != CLASS_WAMPA + && radiusEnt->client->NPC_class != CLASS_RANCOR + && radiusEnt->client->NPC_class != CLASS_ATST ) + { + G_Throw( radiusEnt, pushDir, 65 ); + if ( BG_KnockDownable(&radiusEnt->client->ps) && + radiusEnt->health > 0 && Q_irand( 0, 1 ) ) + {//do pain on enemy + radiusEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + radiusEnt->client->ps.forceDodgeAnim = 0; + radiusEnt->client->ps.forceHandExtendTime = level.time + 1100; + radiusEnt->client->ps.quickerGetup = qfalse; + } + } + } + else if ( radiusEnt->health <= 0 && radiusEnt->client ) + {//killed them, chance of dismembering + if ( !Q_irand( 0, 1 ) ) + {//bite something off + int hitLoc = Q_irand( G2_MODELPART_HEAD, G2_MODELPART_RLEG ); + if ( hitLoc == G2_MODELPART_HEAD ) + { + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATH17, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else if ( hitLoc == G2_MODELPART_WAIST ) + { + NPC_SetAnim( radiusEnt, SETANIM_BOTH, BOTH_DEATHBACKWARD2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + G_Dismember( radiusEnt, NPC, radiusEnt->r.currentOrigin, hitLoc, 90, 0, radiusEnt->client->ps.torsoAnim, qtrue); + } + } + else if ( !Q_irand( 0, 3 ) && radiusEnt->health > 0 ) + {//one out of every 4 normal hits does a knockdown, too + vec3_t pushDir; + vec3_t angs; + VectorCopy( NPC->client->ps.viewangles, angs ); + angs[YAW] += flrand( 25, 50 ); + angs[PITCH] = flrand( -25, -15 ); + AngleVectors( angs, pushDir, NULL, NULL ); + G_Knockdown( radiusEnt ); + } + G_Sound( radiusEnt, CHAN_WEAPON, G_SoundIndex( "sound/chars/rancor/swipehit.wav" ) ); + } + } +} + +//------------------------------ +void Wampa_Attack( float distance, qboolean doCharge ) +{ + if ( !TIMER_Exists( NPC, "attacking" ) ) + { + if ( Q_irand(0, 2) && !doCharge ) + {//double slash + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 750 ); + } + else if ( doCharge || (distance > 270 && distance < 430 && !Q_irand(0, 1)) ) + {//leap + vec3_t fwd, yawAng; + VectorSet( yawAng, 0, NPC->client->ps.viewangles[YAW], 0 ); + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 500 ); + AngleVectors( yawAng, fwd, NULL, NULL ); + VectorScale( fwd, distance*1.5f, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 150; + NPC->client->ps.groundEntityNum = ENTITYNUM_NONE; + } + else + {//backhand + NPC_SetAnim( NPC, SETANIM_BOTH, BOTH_ATTACK3, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + TIMER_Set( NPC, "attack_dmg", 250 ); + } + + TIMER_Set( NPC, "attacking", NPC->client->ps.legsTimer + random() * 200 ); + //allow us to re-evaluate our running speed/anim + TIMER_Set( NPC, "runfar", -1 ); + TIMER_Set( NPC, "runclose", -1 ); + TIMER_Set( NPC, "walk", -1 ); + } + + // Need to do delayed damage since the attack animations encapsulate multiple mini-attacks + + if ( TIMER_Done2( NPC, "attack_dmg", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_ATTACK1: + Wampa_Slash( NPC->client->renderInfo.handRBolt, qfalse ); + //do second hit + TIMER_Set( NPC, "attack_dmg2", 100 ); + break; + case BOTH_ATTACK2: + Wampa_Slash( NPC->client->renderInfo.handRBolt, qfalse ); + TIMER_Set( NPC, "attack_dmg2", 100 ); + break; + case BOTH_ATTACK3: + Wampa_Slash( NPC->client->renderInfo.handLBolt, qtrue ); + break; + } + } + else if ( TIMER_Done2( NPC, "attack_dmg2", qtrue ) ) + { + switch ( NPC->client->ps.legsAnim ) + { + case BOTH_ATTACK1: + Wampa_Slash( NPC->client->renderInfo.handLBolt, qfalse ); + break; + case BOTH_ATTACK2: + Wampa_Slash( NPC->client->renderInfo.handLBolt, qfalse ); + break; + } + } + + // Just using this to remove the attacking flag at the right time + TIMER_Done2( NPC, "attacking", qtrue ); + + if ( NPC->client->ps.legsAnim == BOTH_ATTACK1 && distance > (NPC->r.maxs[0]+MIN_DISTANCE) ) + {//okay to keep moving + ucmd.buttons |= BUTTON_WALKING; + Wampa_Move( 1 ); + } +} + +//---------------------------------- +void Wampa_Combat( void ) +{ + // If we cannot see our target or we have somewhere to go, then do that + if ( !NPC_ClearLOS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + { + if ( !Q_irand( 0, 10 ) ) + { + if ( Wampa_CheckRoar( NPC ) ) + { + return; + } + } + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + Wampa_Move( 0 ); + return; + } + else if ( UpdateGoal() ) + { + NPCInfo->combatMove = qtrue; + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = MAX_DISTANCE; // just get us within combat range + + Wampa_Move( 1 ); + return; + } + else + { + float distance = enemyDist = Distance( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + qboolean advance = (qboolean)( distance > (NPC->r.maxs[0]+MIN_DISTANCE) ? qtrue : qfalse ); + qboolean doCharge = qfalse; + + // Sometimes I have problems with facing the enemy I'm attacking, so force the issue so I don't look dumb + //FIXME: always seems to face off to the left or right?!!!! + NPC_FaceEnemy( qtrue ); + + + if ( advance ) + {//have to get closer + vec3_t yawOnlyAngles; + VectorSet( yawOnlyAngles, 0, NPC->r.currentAngles[YAW], 0 ); + if ( NPC->enemy->health > 0//enemy still alive + && fabs(distance-350) <= 80 //enemy anywhere from 270 to 430 away + && InFOV3( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, yawOnlyAngles, 20, 20 ) )//enemy generally in front + {//10% chance of doing charge anim + if ( !Q_irand( 0, 9 ) ) + {//go for the charge + doCharge = qtrue; + advance = qfalse; + } + } + } + + if (( advance || NPCInfo->localState == LSTATE_WAITING ) && TIMER_Done( NPC, "attacking" )) // waiting monsters can't attack + { + if ( TIMER_Done2( NPC, "takingPain", qtrue )) + { + NPCInfo->localState = LSTATE_CLEAR; + } + else + { + Wampa_Move( 1 ); + } + } + else + { + if ( !Q_irand( 0, 20 ) ) + {//FIXME: only do this if we just damaged them or vice-versa? + if ( Wampa_CheckRoar( NPC ) ) + { + return; + } + } + if ( !Q_irand( 0, 1 ) ) + {//FIXME: base on skill + Wampa_Attack( distance, doCharge ); + } + } + } +} + +/* +------------------------- +NPC_Wampa_Pain +------------------------- +*/ +//void NPC_Wampa_Pain( gentity_t *self, gentity_t *inflictor, gentity_t *other, const vec3_t point, int damage, int mod,int hitLoc ) +void NPC_Wampa_Pain( gentity_t *self, gentity_t *attacker, int damage ) +{ + qboolean hitByWampa = qfalse; + if ( attacker&&attacker->client&&attacker->client->NPC_class==CLASS_WAMPA ) + { + hitByWampa = qtrue; + } + if ( attacker + && attacker->inuse + && attacker != self->enemy + && !(attacker->flags&FL_NOTARGET) ) + { + if ( (!attacker->s.number&&!Q_irand(0,3)) + || !self->enemy + || self->enemy->health == 0 + || (self->enemy->client&&self->enemy->client->NPC_class == CLASS_WAMPA) + || (!Q_irand(0, 4 ) && DistanceSquared( attacker->r.currentOrigin, self->r.currentOrigin ) < DistanceSquared( self->enemy->r.currentOrigin, self->r.currentOrigin )) ) + {//if my enemy is dead (or attacked by player) and I'm not still holding/eating someone, turn on the attacker + //FIXME: if can't nav to my enemy, take this guy if I can nav to him + G_SetEnemy( self, attacker ); + TIMER_Set( self, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + if ( hitByWampa ) + {//stay mad at this Wampa for 2-5 secs before looking for attacker enemies + TIMER_Set( self, "wampaInfight", Q_irand( 2000, 5000 ) ); + } + } + } + if ( (hitByWampa|| Q_irand( 0, 100 ) < damage )//hit by wampa, hit while holding live victim, or took a lot of damage + && self->client->ps.legsAnim != BOTH_GESTURE1 + && self->client->ps.legsAnim != BOTH_GESTURE2 + && TIMER_Done( self, "takingPain" ) ) + { + if ( !Wampa_CheckRoar( self ) ) + { + if ( self->client->ps.legsAnim != BOTH_ATTACK1 + && self->client->ps.legsAnim != BOTH_ATTACK2 + && self->client->ps.legsAnim != BOTH_ATTACK3 ) + {//cant interrupt one of the big attack anims + if ( self->health > 100 || hitByWampa ) + { + TIMER_Remove( self, "attacking" ); + + VectorCopy( self->NPC->lastPathAngles, self->s.angles ); + + if ( !Q_irand( 0, 1 ) ) + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN2, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( self, SETANIM_BOTH, BOTH_PAIN1, SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD ); + } + TIMER_Set( self, "takingPain", self->client->ps.legsTimer+Q_irand(0, 500) ); + //allow us to re-evaluate our running speed/anim + TIMER_Set( self, "runfar", -1 ); + TIMER_Set( self, "runclose", -1 ); + TIMER_Set( self, "walk", -1 ); + + if ( self->NPC ) + { + self->NPC->localState = LSTATE_WAITING; + } + } + } + } + } +} + +/* +------------------------- +NPC_BSWampa_Default +------------------------- +*/ +void NPC_BSWampa_Default( void ) +{ + NPC->client->ps.eFlags2 &= ~EF2_USE_ALT_ANIM; + //NORMAL ANIMS + // stand1 = normal stand + // walk1 = normal, non-angry walk + // walk2 = injured + // run1 = far away run + // run2 = close run + //VICTIM ANIMS + // grabswipe = melee1 - sweep out and grab + // stand2 attack = attack4 - while holding victim, swipe at him + // walk3_drag = walk5 - walk with drag + // stand2 = hold victim + // stand2to1 = drop victim + if ( !TIMER_Done( NPC, "rageTime" ) ) + {//do nothing but roar first time we see an enemy + NPC_FaceEnemy( qtrue ); + return; + } + if ( NPC->enemy ) + { + if ( !TIMER_Done(NPC,"attacking") ) + {//in middle of attack + //face enemy + NPC_FaceEnemy( qtrue ); + //continue attack logic + enemyDist = Distance( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + Wampa_Attack( enemyDist, qfalse ); + return; + } + else + { + if ( TIMER_Done(NPC,"angrynoise") ) + { + G_Sound( NPC, CHAN_VOICE, G_SoundIndex( va("sound/chars/wampa/misc/anger%d.wav", Q_irand(1, 2)) ) ); + + TIMER_Set( NPC, "angrynoise", Q_irand( 5000, 10000 ) ); + } + //else, if he's in our hand, we eat, else if he's on the ground, we keep attacking his dead body for a while + if( NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_WAMPA ) + {//got mad at another Wampa, look for a valid enemy + if ( TIMER_Done( NPC, "wampaInfight" ) ) + { + NPC_CheckEnemyExt( qtrue ); + } + } + else + { + if ( ValidEnemy( NPC->enemy ) == qfalse ) + { + TIMER_Remove( NPC, "lookForNewEnemy" );//make them look again right now + if ( !NPC->enemy->inuse || level.time - NPC->enemy->s.time > Q_irand( 10000, 15000 ) ) + {//it's been a while since the enemy died, or enemy is completely gone, get bored with him + NPC->enemy = NULL; + Wampa_Patrol(); + NPC_UpdateAngles( qtrue, qtrue ); + //just lost my enemy + if ( (NPC->spawnflags&2) ) + {//search around me if I don't have an enemy + NPC_BSSearchStart( NPC->waypoint, BS_SEARCH ); + NPCInfo->tempBehavior = BS_DEFAULT; + } + else if ( (NPC->spawnflags&1) ) + {//wander if I don't have an enemy + NPC_BSSearchStart( NPC->waypoint, BS_WANDER ); + NPCInfo->tempBehavior = BS_DEFAULT; + } + return; + } + } + if ( TIMER_Done( NPC, "lookForNewEnemy" ) ) + { + gentity_t *newEnemy, *sav_enemy = NPC->enemy;//FIXME: what about NPC->lastEnemy? + NPC->enemy = NULL; + newEnemy = NPC_CheckEnemy( NPCInfo->confusionTime < level.time, qfalse, qfalse ); + NPC->enemy = sav_enemy; + if ( newEnemy && newEnemy != sav_enemy ) + {//picked up a new enemy! + NPC->lastEnemy = NPC->enemy; + G_SetEnemy( NPC, newEnemy ); + //hold this one for at least 5-15 seconds + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + } + else + {//look again in 2-5 secs + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 2000, 5000 ) ); + } + } + } + Wampa_Combat(); + return; + } + } + else + { + if ( TIMER_Done(NPC,"idlenoise") ) + { + G_Sound( NPC, CHAN_AUTO, G_SoundIndex( "sound/chars/wampa/misc/anger3.wav" ) ); + + TIMER_Set( NPC, "idlenoise", Q_irand( 2000, 4000 ) ); + } + if ( (NPC->spawnflags&2) ) + {//search around me if I don't have an enemy + if ( NPCInfo->homeWp == WAYPOINT_NONE ) + {//no homewap, initialize the search behavior + NPC_BSSearchStart( WAYPOINT_NONE, BS_SEARCH ); + NPCInfo->tempBehavior = BS_DEFAULT; + } + ucmd.buttons |= BUTTON_WALKING; + NPC_BSSearch();//this automatically looks for enemies + } + else if ( (NPC->spawnflags&1) ) + {//wander if I don't have an enemy + if ( NPCInfo->homeWp == WAYPOINT_NONE ) + {//no homewap, initialize the wander behavior + NPC_BSSearchStart( WAYPOINT_NONE, BS_WANDER ); + NPCInfo->tempBehavior = BS_DEFAULT; + } + ucmd.buttons |= BUTTON_WALKING; + NPC_BSWander(); + if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + if ( NPC_CheckEnemyExt( qtrue ) == qfalse ) + { + Wampa_Idle(); + } + else + { + Wampa_CheckRoar( NPC ); + TIMER_Set( NPC, "lookForNewEnemy", Q_irand( 5000, 15000 ) ); + } + } + } + else + { + if ( NPCInfo->scriptFlags & SCF_LOOK_FOR_ENEMIES ) + { + Wampa_Patrol(); + } + else + { + Wampa_Idle(); + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} diff --git a/code/game/NPC_behavior.c b/code/game/NPC_behavior.c new file mode 100644 index 0000000..a7e3369 --- /dev/null +++ b/code/game/NPC_behavior.c @@ -0,0 +1,1748 @@ +//NPC_behavior.cpp +/* +FIXME - MCG: +These all need to make use of the snapshots. Write something that can look for only specific +things in a snapshot or just go through the snapshot every frame and save the info in case +we need it... +*/ + +#include "b_local.h" +#include "g_nav.h" +#include "../icarus/Q3_Interface.h" + +extern qboolean showBBoxes; +extern vec3_t NPCDEBUG_BLUE; +extern void G_Cube( vec3_t mins, vec3_t maxs, vec3_t color, float alpha ); +extern void NPC_CheckGetNewWeapon( void ); + +#include "../namespace_begin.h" +extern qboolean PM_InKnockDown( playerState_t *ps ); +#include "../namespace_end.h" + +extern void NPC_AimAdjust( int change ); +extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent); +/* + void NPC_BSAdvanceFight (void) + +Advance towards your captureGoal and shoot anyone you can along the way. +*/ +void NPC_BSAdvanceFight (void) +{//FIXME: IMPLEMENT +//Head to Goal if I can + + //Make sure we're still headed where we want to capture + if ( NPCInfo->captureGoal ) + {//FIXME: if no captureGoal, what do we do? + //VectorCopy( NPCInfo->captureGoal->r.currentOrigin, NPCInfo->tempGoal->r.currentOrigin ); + //NPCInfo->goalEntity = NPCInfo->tempGoal; + + NPC_SetMoveGoal( NPC, NPCInfo->captureGoal->r.currentOrigin, 16, qtrue, -1, NULL ); + +// NAV_ClearLastRoute(NPC); + NPCInfo->goalTime = level.time + 100000; + } + +// NPC_BSRun(); + + NPC_CheckEnemy(qtrue, qfalse, qtrue); + + //FIXME: Need melee code + if( NPC->enemy ) + {//See if we can shoot him + vec3_t delta, forward; + vec3_t angleToEnemy; + vec3_t hitspot, muzzle, diff, enemy_org, enemy_head; + float distanceToEnemy; + qboolean attack_ok = qfalse; + qboolean dead_on = qfalse; + float attack_scale = 1.0; + float aim_off; + float max_aim_off = 64; + + //Yaw to enemy + VectorMA(NPC->enemy->r.absmin, 0.5, NPC->enemy->r.maxs, enemy_org); + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorSubtract (enemy_org, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + distanceToEnemy = VectorNormalize(delta); + + if(!NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue)) + { + attack_ok = qtrue; + } + + if(attack_ok) + { + NPC_UpdateShootAngles(angleToEnemy, qfalse, qtrue); + + NPCInfo->enemyLastVisibility = enemyVisibility; + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV);//CHECK_360|//CHECK_PVS| + + if(enemyVisibility == VIS_FOV) + {//He's in our FOV + + attack_ok = qtrue; + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head); + + if(attack_ok) + { + trace_t tr; + gentity_t *traceEnt; + //are we gonna hit him if we shoot at his center? + trap_Trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT ); + traceEnt = &g_entities[tr.entityNum]; + if( traceEnt != NPC->enemy && + (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) ) + {//no, so shoot for the head + attack_scale *= 0.75; + trap_Trace ( &tr, muzzle, NULL, NULL, enemy_head, NPC->s.number, MASK_SHOT ); + traceEnt = &g_entities[tr.entityNum]; + } + + VectorCopy( tr.endpos, hitspot ); + + if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) ) + { + dead_on = qtrue; + } + else + { + attack_scale *= 0.5; + if(NPC->client->playerTeam) + { + if(traceEnt && traceEnt->client && traceEnt->client->playerTeam) + { + if(NPC->client->playerTeam == traceEnt->client->playerTeam) + {//Don't shoot our own team + attack_ok = qfalse; + } + } + } + } + } + + if( attack_ok ) + { + //ok, now adjust pitch aim + VectorSubtract (hitspot, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateShootAngles(angleToEnemy, qtrue, qfalse); + + if( !dead_on ) + {//We're not going to hit him directly, try a suppressing fire + //see if where we're going to shoot is too far from his origin + AngleVectors (NPCInfo->shootAngles, forward, NULL, NULL); + VectorMA ( muzzle, distanceToEnemy, forward, hitspot); + VectorSubtract(hitspot, enemy_org, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off)//FIXME: use aim value to allow poor aim? + { + attack_scale *= 0.75; + //see if where we're going to shoot is too far from his head + VectorSubtract(hitspot, enemy_head, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off) + { + attack_ok = qfalse; + } + } + attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off; + } + } + } + } + + if( attack_ok ) + { + if( NPC_CheckAttack( attack_scale )) + {//check aggression to decide if we should shoot + enemyVisibility = VIS_SHOOT; + WeaponThink(qtrue); + } + else + attack_ok = qfalse; + } +//Don't do this- only for when stationary and trying to shoot an enemy +// else +// NPC->cantHitEnemyCounter++; + } + else + {//FIXME: + NPC_UpdateShootAngles(NPC->client->ps.viewangles, qtrue, qtrue); + } + + if(!ucmd.forwardmove && !ucmd.rightmove) + {//We reached our captureGoal + if(trap_ICARUS_IsInitialized(NPC->s.number)) + { + trap_ICARUS_TaskIDComplete( NPC, TID_BSTATE ); + } + } +} + +void Disappear(gentity_t *self) +{ +// ClientDisconnect(self); + self->s.eFlags |= EF_NODRAW; + self->think = 0; + self->nextthink = -1; +} + +void MakeOwnerInvis (gentity_t *self); +void BeamOut (gentity_t *self) +{ +// gentity_t *tent = G_Spawn(); + +/* + tent->owner = self; + tent->think = MakeOwnerInvis; + tent->nextthink = level.time + 1800; + //G_AddEvent( ent, EV_PLAYER_TELEPORT, 0 ); + tent = G_TempEntity( self->client->pcurrentOrigin, EV_PLAYER_TELEPORT ); +*/ + //fixme: doesn't actually go away! + self->nextthink = level.time + 1500; + self->think = Disappear; + self->client->squadname = NULL; + self->client->playerTeam = self->s.teamowner = TEAM_FREE; + //self->r.svFlags |= SVF_BEAMING; //this appears unused in SP as well +} + +void NPC_BSCinematic( void ) +{ + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + if ( UpdateGoal() ) + {//have a goalEntity + //move toward goal, should also face that goal + NPC_MoveToGoal( qtrue ); + } + + if ( NPCInfo->watchTarget ) + {//have an entity which we want to keep facing + //NOTE: this will override any angles set by NPC_MoveToGoal + vec3_t eyes, viewSpot, viewvec, viewangles; + + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + CalcEntitySpot( NPCInfo->watchTarget, SPOT_HEAD_LEAN, viewSpot ); + + VectorSubtract( viewSpot, eyes, viewvec ); + + vectoangles( viewvec, viewangles ); + + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw = viewangles[YAW]; + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch = viewangles[PITCH]; + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +void NPC_BSWait( void ) +{ + NPC_UpdateAngles( qtrue, qtrue ); +} + + +void NPC_BSInvestigate (void) +{ +/* + //FIXME: maybe allow this to be set as a tempBState in a script? Just specify the + //investigateGoal, investigateDebounceTime and investigateCount? (Needs a macro) + vec3_t invDir, invAngles, spot; + gentity_t *saveGoal; + //BS_INVESTIGATE would turn toward goal, maybe take a couple steps towards it, + //look for enemies, then turn away after your investigate counter was down- + //investigate counter goes up every time you set it... + + if(level.time > NPCInfo->enemyCheckDebounceTime) + { + NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 1000); + NPC_CheckEnemy(qtrue, qfalse); + if(NPC->enemy) + {//FIXME: do anger script + NPCInfo->goalEntity = NPC->enemy; +// NAV_ClearLastRoute(NPC); + NPCInfo->behaviorState = BS_RUN_AND_SHOOT; + NPCInfo->tempBehavior = BS_DEFAULT; + NPC_AngerSound(); + return; + } + } + + NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_WEAPONREADY3, SETANIM_FLAG_NORMAL ); + + if(NPCInfo->stats.vigilance <= 1.0 && NPCInfo->eventOwner) + { + VectorCopy(NPCInfo->eventOwner->r.currentOrigin, NPCInfo->investigateGoal); + } + + saveGoal = NPCInfo->goalEntity; + if( level.time > NPCInfo->walkDebounceTime ) + { + vec3_t vec; + + VectorSubtract(NPCInfo->investigateGoal, NPC->r.currentOrigin, vec); + vec[2] = 0; + if(VectorLength(vec) > 64) + { + if(Q_irand(0, 100) < NPCInfo->investigateCount) + {//take a full step + //NPCInfo->walkDebounceTime = level.time + 1400; + //actually finds length of my BOTH_WALK anim + NPCInfo->walkDebounceTime = PM_AnimLength( NPC->client->clientInfo.animFileIndex, BOTH_WALK1 ); + } + } + } + + if( level.time < NPCInfo->walkDebounceTime ) + {//walk toward investigateGoal + + /* + NPCInfo->goalEntity = NPCInfo->tempGoal; +// NAV_ClearLastRoute(NPC); + VectorCopy(NPCInfo->investigateGoal, NPCInfo->tempGoal->r.currentOrigin); + */ + +/* NPC_SetMoveGoal( NPC, NPCInfo->investigateGoal, 16, qtrue ); + + NPC_MoveToGoal( qtrue ); + + //FIXME: walk2? + NPC_SetAnim(NPC,SETANIM_LEGS,BOTH_WALK1,SETANIM_FLAG_NORMAL); + + ucmd.buttons |= BUTTON_WALKING; + } + else + { + + NPC_SetAnim(NPC,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + + if(NPCInfo->hlookCount > 30) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->hlookCount = 0; + } + } + else if(NPCInfo->hlookCount < -30) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->hlookCount = 0; + } + } + else if(NPCInfo->hlookCount == 0) + { + NPCInfo->hlookCount = Q_irand(-1, 1); + } + else if(Q_irand(0, 10) > 7) + { + if(NPCInfo->hlookCount > 0) + { + NPCInfo->hlookCount++; + } + else//lookCount < 0 + { + NPCInfo->hlookCount--; + } + } + + if(NPCInfo->vlookCount >= 15) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->vlookCount = 0; + } + } + else if(NPCInfo->vlookCount <= -15) + { + if(Q_irand(0, 10) > 7) + { + NPCInfo->vlookCount = 0; + } + } + else if(NPCInfo->vlookCount == 0) + { + NPCInfo->vlookCount = Q_irand(-1, 1); + } + else if(Q_irand(0, 10) > 8) + { + if(NPCInfo->vlookCount > 0) + { + NPCInfo->vlookCount++; + } + else//lookCount < 0 + { + NPCInfo->vlookCount--; + } + } + + //turn toward investigateGoal + CalcEntitySpot( NPC, SPOT_HEAD, spot ); + VectorSubtract(NPCInfo->investigateGoal, spot, invDir); + VectorNormalize(invDir); + vectoangles(invDir, invAngles); + NPCInfo->desiredYaw = AngleNormalize360(invAngles[YAW] + NPCInfo->hlookCount); + NPCInfo->desiredPitch = AngleNormalize360(invAngles[PITCH] + NPCInfo->hlookCount); + } + + NPC_UpdateAngles(qtrue, qtrue); + + NPCInfo->goalEntity = saveGoal; +// NAV_ClearLastRoute(NPC); + + if(level.time > NPCInfo->investigateDebounceTime) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + + NPC_CheckSoundEvents(); + */ +} + +qboolean NPC_CheckInvestigate( int alertEventNum ) +{ + gentity_t *owner = level.alertEvents[alertEventNum].owner; + int invAdd = level.alertEvents[alertEventNum].level; + vec3_t soundPos; + float soundRad = level.alertEvents[alertEventNum].radius; + float earshot = NPCInfo->stats.earshot; + + VectorCopy( level.alertEvents[alertEventNum].position, soundPos ); + + //NOTE: Trying to preserve previous investigation behavior + if ( !owner ) + { + return qfalse; + } + + if ( owner->s.eType != ET_PLAYER && owner->s.eType != ET_NPC && owner == NPCInfo->goalEntity ) + { + return qfalse; + } + + if ( owner->s.eFlags & EF_NODRAW ) + { + return qfalse; + } + + if ( owner->flags & FL_NOTARGET ) + { + return qfalse; + } + + if ( soundRad < earshot ) + { + return qfalse; + } + + //if(!trap_InPVSIgnorePortals(ent->r.currentOrigin, NPC->r.currentOrigin))//should we be able to hear through areaportals? + if ( !trap_InPVS( soundPos, NPC->r.currentOrigin ) ) + {//can hear through doors? + return qfalse; + } + + if ( owner->client && owner->client->playerTeam && NPC->client->playerTeam && owner->client->playerTeam != NPC->client->playerTeam ) + { + if( (float)NPCInfo->investigateCount >= (NPCInfo->stats.vigilance*200) && owner ) + {//If investigateCount == 10, just take it as enemy and go + if ( ValidEnemy( owner ) ) + {//FIXME: run angerscript + G_SetEnemy( NPC, owner ); + NPCInfo->goalEntity = NPC->enemy; + NPCInfo->goalRadius = 12; + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + return qtrue; + } + } + else + { + NPCInfo->investigateCount += invAdd; + } + //run awakescript + G_ActivateBehavior(NPC, BSET_AWAKE); + + /* + if ( Q_irand(0, 10) > 7 ) + { + NPC_AngerSound(); + } + */ + + //NPCInfo->hlookCount = NPCInfo->vlookCount = 0; + NPCInfo->eventOwner = owner; + VectorCopy( soundPos, NPCInfo->investigateGoal ); + if ( NPCInfo->investigateCount > 20 ) + { + NPCInfo->investigateDebounceTime = level.time + 10000; + } + else + { + NPCInfo->investigateDebounceTime = level.time + (NPCInfo->investigateCount*500); + } + NPCInfo->tempBehavior = BS_INVESTIGATE; + return qtrue; + } + + return qfalse; +} + + +/* +void NPC_BSSleep( void ) +*/ +void NPC_BSSleep( void ) +{ + int alertEvent = NPC_CheckAlertEvents( qtrue, qfalse, -1, qfalse, AEL_MINOR ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + G_ActivateBehavior(NPC, BSET_AWAKE); + return; + } + + /* + if ( level.time > NPCInfo->enemyCheckDebounceTime ) + { + if ( NPC_CheckSoundEvents() != -1 ) + {//only 1 alert per second per 0.1 of vigilance + NPCInfo->enemyCheckDebounceTime = level.time + (NPCInfo->stats.vigilance * 10000); + G_ActivateBehavior(NPC, BSET_AWAKE); + } + } + */ +} + +extern qboolean NPC_MoveDirClear( int forwardmove, int rightmove, qboolean reset ); +void NPC_BSFollowLeader (void) +{ + vec3_t vec; + float leaderDist; + visibility_t leaderVis; + int curAnim; + + if ( !NPC->client->leader ) + {//ok, stand guard until we find an enemy + if( NPCInfo->tempBehavior == BS_HUNT_AND_KILL ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + { + NPCInfo->tempBehavior = BS_STAND_GUARD; + NPC_BSStandGuard(); + } + return; + } + + if ( !NPC->enemy ) + {//no enemy, find one + NPC_CheckEnemy( NPCInfo->confusionTimeenemy ) + {//just found one + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + } + else + { + if ( !(NPCInfo->scriptFlags&SCF_IGNORE_ALERTS) ) + { + int eventID = NPC_CheckAlertEvents( qtrue, qtrue, -1, qfalse, AEL_MINOR ); + if ( level.alertEvents[eventID].level >= AEL_SUSPICIOUS && (NPCInfo->scriptFlags&SCF_LOOK_FOR_ENEMIES) ) + { + NPCInfo->lastAlertID = level.alertEvents[eventID].ID; + if ( !level.alertEvents[eventID].owner || + !level.alertEvents[eventID].owner->client || + level.alertEvents[eventID].owner->health <= 0 || + level.alertEvents[eventID].owner->client->playerTeam != NPC->client->enemyTeam ) + {//not an enemy + } + else + { + //FIXME: what if can't actually see enemy, don't know where he is... should we make them just become very alert and start looking for him? Or just let combat AI handle this... (act as if you lost him) + G_SetEnemy( NPC, level.alertEvents[eventID].owner ); + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + NPCInfo->enemyLastSeenTime = level.time; + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 1000 ) ); + } + } + + } + } + if ( !NPC->enemy ) + { + if ( NPC->client->leader + && NPC->client->leader->enemy + && NPC->client->leader->enemy != NPC + && ( (NPC->client->leader->enemy->client&&NPC->client->leader->enemy->client->playerTeam==NPC->client->enemyTeam) + ||(/*NPC->client->leader->enemy->r.svFlags&SVF_NONNPC_ENEMY*/0&&NPC->client->leader->enemy->alliedTeam==NPC->client->enemyTeam) ) + && NPC->client->leader->enemy->health > 0 ) + { //rwwFIXMEFIXME: use SVF_NONNPC_ENEMY? + G_SetEnemy( NPC, NPC->client->leader->enemy ); + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 3000, 10000 ); + NPCInfo->enemyLastSeenTime = level.time; + } + } + } + else + { + if ( NPC->enemy->health <= 0 || (NPC->enemy->flags&FL_NOTARGET) ) + { + G_ClearEnemy( NPC ); + if ( NPCInfo->enemyCheckDebounceTime > level.time + 1000 ) + { + NPCInfo->enemyCheckDebounceTime = level.time + Q_irand( 1000, 2000 ); + } + } + else if ( NPC->client->ps.weapon && NPCInfo->enemyCheckDebounceTime < level.time ) + { + NPC_CheckEnemy( (NPCInfo->confusionTimetempBehavior!=BS_FOLLOW_LEADER), qfalse, qtrue );//don't find new enemy if this is tempbehav + } + } + + if ( NPC->enemy && NPC->client->ps.weapon ) + {//If have an enemy, face him and fire + if ( NPC->client->ps.weapon == WP_SABER )//|| NPCInfo->confusionTime>level.time ) + {//lightsaber user or charmed enemy + if ( NPCInfo->tempBehavior != BS_FOLLOW_LEADER ) + {//not already in a temp bState + //go after the guy + NPCInfo->tempBehavior = BS_HUNT_AND_KILL; + NPC_UpdateAngles(qtrue, qtrue); + return; + } + } + + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_FOV|CHECK_SHOOT );//CHECK_360|CHECK_PVS| + if ( enemyVisibility > VIS_PVS ) + {//face + vec3_t enemy_org, muzzle, delta, angleToEnemy; + float distanceToEnemy; + + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); + NPC_AimWiggle( enemy_org ); + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorSubtract( enemy_org, muzzle, delta); + vectoangles( delta, angleToEnemy ); + distanceToEnemy = VectorNormalize( delta ); + + NPCInfo->desiredYaw = angleToEnemy[YAW]; + NPCInfo->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles( qtrue, qtrue ); + + if ( enemyVisibility >= VIS_SHOOT ) + {//shoot + NPC_AimAdjust( 2 ); + if ( NPC_GetHFOVPercentage( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.hfov ) > 0.6f + && NPC_GetHFOVPercentage( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin, NPC->client->ps.viewangles, NPCInfo->stats.vfov ) > 0.5f ) + {//actually withing our front cone + WeaponThink( qtrue ); + } + } + else + { + NPC_AimAdjust( 1 ); + } + + //NPC_CheckCanAttack(1.0, qfalse); + } + else + { + NPC_AimAdjust( -1 ); + } + } + else + {//FIXME: combine with vector calc below + vec3_t head, leaderHead, delta, angleToLeader; + + CalcEntitySpot( NPC->client->leader, SPOT_HEAD, leaderHead ); + CalcEntitySpot( NPC, SPOT_HEAD, head ); + VectorSubtract (leaderHead, head, delta); + vectoangles ( delta, angleToLeader ); + VectorNormalize(delta); + NPC->NPC->desiredYaw = angleToLeader[YAW]; + NPC->NPC->desiredPitch = angleToLeader[PITCH]; + + NPC_UpdateAngles(qtrue, qtrue); + } + + //leader visible? + leaderVis = NPC_CheckVisibility( NPC->client->leader, CHECK_PVS|CHECK_360|CHECK_SHOOT );// ent->e_UseFunc = useF_NULL; + + + //Follow leader, stay within visibility and a certain distance, maintain a distance from. + curAnim = NPC->client->ps.legsAnim; + if ( curAnim != BOTH_ATTACK1 && curAnim != BOTH_ATTACK2 && curAnim != BOTH_ATTACK3 && curAnim != BOTH_MELEE1 && curAnim != BOTH_MELEE2 ) + {//Don't move toward leader if we're in a full-body attack anim + //FIXME, use IdealDistance to determine if we need to close distance + float followDist = 96.0f;//FIXME: If there are enmies, make this larger? + float backupdist, walkdist, minrundist; + float leaderHDist; + + if ( NPCInfo->followDist ) + { + followDist = NPCInfo->followDist; + } + backupdist = followDist/2.0f; + walkdist = followDist*0.83; + minrundist = followDist*1.33; + + VectorSubtract(NPC->client->leader->r.currentOrigin, NPC->r.currentOrigin, vec); + leaderDist = VectorLength( vec );//FIXME: make this just nav distance? + //never get within their radius horizontally + vec[2] = 0; + leaderHDist = VectorLength( vec ); + if( leaderHDist > backupdist && (leaderVis != VIS_SHOOT || leaderDist > walkdist) ) + {//We should close in? + NPCInfo->goalEntity = NPC->client->leader; + + NPC_SlideMoveToGoal(); + if ( leaderVis == VIS_SHOOT && leaderDist < minrundist ) + { + ucmd.buttons |= BUTTON_WALKING; + } + } + else if ( leaderDist < backupdist ) + {//We should back off? + NPCInfo->goalEntity = NPC->client->leader; + NPC_SlideMoveToGoal(); + + //reversing direction + ucmd.forwardmove = -ucmd.forwardmove; + ucmd.rightmove = -ucmd.rightmove; + VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + }//otherwise, stay where we are + //check for do not enter and stop if there's one there... + if ( ucmd.forwardmove || ucmd.rightmove || VectorCompare( vec3_origin, NPC->client->ps.moveDir ) ) + { + NPC_MoveDirClear( ucmd.forwardmove, ucmd.rightmove, qtrue ); + } + } +} +#define APEX_HEIGHT 200.0f +#define PARA_WIDTH (sqrt(APEX_HEIGHT)+sqrt(APEX_HEIGHT)) +#define JUMP_SPEED 200.0f +void NPC_BSJump (void) +{ + vec3_t dir, angles, p1, p2, apex; + float time, height, forward, z, xy, dist, yawError, apexHeight; + + if( !NPCInfo->goalEntity ) + {//Should have task completed the navgoal + return; + } + + if ( NPCInfo->jumpState != JS_JUMPING && NPCInfo->jumpState != JS_LANDING ) + { + //Face navgoal + VectorSubtract(NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir); + vectoangles(dir, angles); + NPCInfo->desiredPitch = NPCInfo->lockedDesiredPitch = AngleNormalize360(angles[PITCH]); + NPCInfo->desiredYaw = NPCInfo->lockedDesiredYaw = AngleNormalize360(angles[YAW]); + } + + NPC_UpdateAngles ( qtrue, qtrue ); + yawError = AngleDelta ( NPC->client->ps.viewangles[YAW], NPCInfo->desiredYaw ); + //We don't really care about pitch here + + switch ( NPCInfo->jumpState ) + { + case JS_FACING: + if ( yawError < MIN_ANGLE_ERROR ) + {//Facing it, Start crouching + NPC_SetAnim(NPC, SETANIM_LEGS, BOTH_CROUCH1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + NPCInfo->jumpState = JS_CROUCHING; + } + break; + case JS_CROUCHING: + if ( NPC->client->ps.legsTimer > 0 ) + {//Still playing crouching anim + return; + } + + //Create a parabola + + if ( NPC->r.currentOrigin[2] > NPCInfo->goalEntity->r.currentOrigin[2] ) + { + VectorCopy( NPC->r.currentOrigin, p1 ); + VectorCopy( NPCInfo->goalEntity->r.currentOrigin, p2 ); + } + else if ( NPC->r.currentOrigin[2] < NPCInfo->goalEntity->r.currentOrigin[2] ) + { + VectorCopy( NPCInfo->goalEntity->r.currentOrigin, p1 ); + VectorCopy( NPC->r.currentOrigin, p2 ); + } + else + { + VectorCopy( NPC->r.currentOrigin, p1 ); + VectorCopy( NPCInfo->goalEntity->r.currentOrigin, p2 ); + } + + //z = xy*xy + VectorSubtract( p2, p1, dir ); + dir[2] = 0; + + //Get xy and z diffs + xy = VectorNormalize( dir ); + z = p1[2] - p2[2]; + + apexHeight = APEX_HEIGHT/2; + /* + //Determine most desirable apex height + apexHeight = (APEX_HEIGHT * PARA_WIDTH/xy) + (APEX_HEIGHT * z/128); + if ( apexHeight < APEX_HEIGHT * 0.5 ) + { + apexHeight = APEX_HEIGHT*0.5; + } + else if ( apexHeight > APEX_HEIGHT * 2 ) + { + apexHeight = APEX_HEIGHT*2; + } + */ + + //FIXME: length of xy will change curve of parabola, need to account for this + //somewhere... PARA_WIDTH + + z = (sqrt(apexHeight + z) - sqrt(apexHeight)); + + assert(z >= 0); + +// Com_Printf("apex is %4.2f percent from p1: ", (xy-z)*0.5/xy*100.0f); + + xy -= z; + xy *= 0.5; + + assert(xy > 0); + + VectorMA( p1, xy, dir, apex ); + apex[2] += apexHeight; + + VectorCopy(apex, NPC->pos1); + + //Now we have the apex, aim for it + height = apex[2] - NPC->r.currentOrigin[2]; + time = sqrt( height / ( .5 * NPC->client->ps.gravity ) ); + if ( !time ) + { +// Com_Printf("ERROR no time in jump\n"); + return; + } + + // set s.origin2 to the push velocity + VectorSubtract ( apex, NPC->r.currentOrigin, NPC->client->ps.velocity ); + NPC->client->ps.velocity[2] = 0; + dist = VectorNormalize( NPC->client->ps.velocity ); + + forward = dist / time; + VectorScale( NPC->client->ps.velocity, forward, NPC->client->ps.velocity ); + + NPC->client->ps.velocity[2] = time * NPC->client->ps.gravity; + +// Com_Printf( "%s jumping %s, gravity at %4.0f percent\n", NPC->targetname, vtos(NPC->client->ps.velocity), NPC->client->ps.gravity/8.0f ); + + NPC->flags |= FL_NO_KNOCKBACK; + NPCInfo->jumpState = JS_JUMPING; + //FIXME: jumpsound? + break; + case JS_JUMPING: + + if ( showBBoxes ) + { + VectorAdd(NPC->r.mins, NPC->pos1, p1); + VectorAdd(NPC->r.maxs, NPC->pos1, p2); + G_Cube( p1, p2, NPCDEBUG_BLUE, 0.5 ); + } + + if ( NPC->s.groundEntityNum != ENTITYNUM_NONE) + {//Landed, start landing anim + //FIXME: if the + VectorClear(NPC->client->ps.velocity); + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_LAND1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD); + NPCInfo->jumpState = JS_LANDING; + //FIXME: landsound? + } + else if ( NPC->client->ps.legsTimer > 0 ) + {//Still playing jumping anim + //FIXME: apply jump velocity here, a couple frames after start, not right away + return; + } + else + {//still in air, but done with jump anim, play inair anim + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_INAIR1, SETANIM_FLAG_OVERRIDE); + } + break; + case JS_LANDING: + if ( NPC->client->ps.legsTimer > 0 ) + {//Still playing landing anim + return; + } + else + { + NPCInfo->jumpState = JS_WAITING; + + + //task complete no matter what... + NPC_ClearGoal(); + NPCInfo->goalTime = level.time; + NPCInfo->aiFlags &= ~NPCAI_MOVING; + ucmd.forwardmove = 0; + NPC->flags &= ~FL_NO_KNOCKBACK; + //Return that the goal was reached + trap_ICARUS_TaskIDComplete( NPC, TID_MOVE_NAV ); + + //Or should we keep jumping until reached goal? + + /* + NPCInfo->goalEntity = UpdateGoal(); + if ( !NPCInfo->goalEntity ) + { + NPC->flags &= ~FL_NO_KNOCKBACK; + Q3_TaskIDComplete( NPC, TID_MOVE_NAV ); + } + */ + + } + break; + case JS_WAITING: + default: + NPCInfo->jumpState = JS_FACING; + break; + } +} + +void NPC_BSRemove (void) +{ + NPC_UpdateAngles ( qtrue, qtrue ); + if( !trap_InPVS( NPC->r.currentOrigin, g_entities[0].r.currentOrigin ) )//FIXME: use cg.vieworg? + { //rwwFIXMEFIXME: Care about all clients instead of just 0? + G_UseTargets2( NPC, NPC, NPC->target3 ); + NPC->s.eFlags |= EF_NODRAW; + NPC->s.eType = ET_INVISIBLE; + NPC->r.contents = 0; + NPC->health = 0; + NPC->targetname = NULL; + + //Disappear in half a second + NPC->think = G_FreeEntity; + NPC->nextthink = level.time + FRAMETIME; + }//FIXME: else allow for out of FOV??? +} + +void NPC_BSSearch (void) +{ + NPC_CheckEnemy(qtrue, qfalse, qtrue); + //Look for enemies, if find one: + if ( NPC->enemy ) + { + if( NPCInfo->tempBehavior == BS_SEARCH ) + {//if tempbehavior, set tempbehavior to default + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + {//if bState, change to run and shoot + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + NPC_BSRunAndShoot(); + } + return; + } + + //FIXME: what if our goalEntity is not NULL and NOT our tempGoal - they must + //want us to do something else? If tempBehavior, just default, else set + //to run and shoot...? + + //FIXME: Reimplement + + if ( !NPCInfo->investigateDebounceTime ) + {//On our way to a tempGoal + float minGoalReachedDistSquared = 32*32; + vec3_t vec; + + //Keep moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + + VectorSubtract ( NPCInfo->tempGoal->r.currentOrigin, NPC->r.currentOrigin, vec); + if ( vec[2] < 24 ) + { + vec[2] = 0; + } + + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + /* + //FIXME: can't get the radius... + float wpRadSq = waypoints[NPCInfo->tempGoal->waypoint].radius * waypoints[NPCInfo->tempGoal->waypoint].radius; + if ( minGoalReachedDistSquared > wpRadSq ) + { + minGoalReachedDistSquared = wpRadSq; + } + */ + + minGoalReachedDistSquared = 32*32;//12*12; + } + + if ( VectorLengthSquared( vec ) < minGoalReachedDistSquared ) + { + //Close enough, just got there + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + + if ( ( NPCInfo->homeWp == WAYPOINT_NONE ) || ( NPC->waypoint == WAYPOINT_NONE ) ) + { + //Heading for or at an invalid waypoint, get out of this bState + if( NPCInfo->tempBehavior == BS_SEARCH ) + {//if tempbehavior, set tempbehavior to default + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + {//if bState, change to stand guard + NPCInfo->behaviorState = BS_STAND_GUARD; + NPC_BSRunAndShoot(); + } + return; + } + + if ( NPC->waypoint == NPCInfo->homeWp ) + { + //Just Reached our homeWp, if this is the first time, run your lostenemyscript + if ( NPCInfo->aiFlags & NPCAI_ENROUTE_TO_HOMEWP ) + { + NPCInfo->aiFlags &= ~NPCAI_ENROUTE_TO_HOMEWP; + G_ActivateBehavior( NPC, BSET_LOSTENEMY ); + } + + } + + //Com_Printf("Got there.\n"); + //Com_Printf("Looking..."); + if( !Q_irand(0, 1) ) + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL); + } + else + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL); + } + NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); + } + else + { + NPC_MoveToGoal( qtrue ); + } + } + else + { + //We're there + if ( NPCInfo->investigateDebounceTime > level.time ) + { + //Still waiting around for a bit + //Turn angles every now and then to look around + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + if ( !Q_irand( 0, 30 ) ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPCInfo->tempGoal->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + int branchNum = Q_irand( 0, numEdges - 1 ); + + vec3_t branchPos, lookDir; + + int nextWp = trap_Nav_GetNodeEdge( NPCInfo->tempGoal->waypoint, branchNum ); + trap_Nav_GetNodePosition( nextWp, branchPos ); + + VectorSubtract( branchPos, NPCInfo->tempGoal->r.currentOrigin, lookDir ); + NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + flrand( -45, 45 ) ); + } + + //pick an angle +-45 degrees off of the dir of a random branch + //from NPCInfo->tempGoal->waypoint + //int branch = Q_irand( 0, (waypoints[NPCInfo->tempGoal->waypoint].numNeighbors - 1) ); + //int nextWp = waypoints[NPCInfo->tempGoal->waypoint].nextWaypoint[branch][NPC->client->moveType]; + //vec3_t lookDir; + + //VectorSubtract( waypoints[nextWp].origin, NPCInfo->tempGoal->r.currentOrigin, lookDir ); + //Look in that direction +- 45 degrees + //NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + Q_flrand( -45, 45 ) ); + } + } + //Com_Printf("."); + } + else + {//Just finished waiting + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + + if ( NPC->waypoint == NPCInfo->homeWp ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPCInfo->tempGoal->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + int branchNum = Q_irand( 0, numEdges - 1 ); + + int nextWp = trap_Nav_GetNodeEdge( NPCInfo->homeWp, branchNum ); + trap_Nav_GetNodePosition( nextWp, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = nextWp; + } + + /* + //Pick a random branch + int branch = Q_irand( 0, (waypoints[NPCInfo->homeWp].numNeighbors - 1) ); + int nextWp = waypoints[NPCInfo->homeWp].nextWaypoint[branch][NPC->client->moveType]; + + VectorCopy( waypoints[nextWp].origin, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = nextWp; + //Com_Printf("\nHeading for wp %d...\n", waypoints[NPCInfo->homeWp].nextWaypoint[branch][NPC->client->moveType]); + */ + } + else + {//At a branch, so return home + trap_Nav_GetNodePosition( NPCInfo->homeWp, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = NPCInfo->homeWp; + /* + VectorCopy( waypoints[NPCInfo->homeWp].origin, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = NPCInfo->homeWp; + //Com_Printf("\nHeading for wp %d...\n", NPCInfo->homeWp); + */ + } + + NPCInfo->investigateDebounceTime = 0; + //Start moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + NPC_MoveToGoal( qtrue ); + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +------------------------- +NPC_BSSearchStart +------------------------- +*/ + +void NPC_BSSearchStart( int homeWp, bState_t bState ) +{ + //FIXME: Reimplement + if ( homeWp == WAYPOINT_NONE ) + { + homeWp = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + if( NPC->waypoint == WAYPOINT_NONE ) + { + NPC->waypoint = homeWp; + } + } + NPCInfo->homeWp = homeWp; + NPCInfo->tempBehavior = bState; + NPCInfo->aiFlags |= NPCAI_ENROUTE_TO_HOMEWP; + NPCInfo->investigateDebounceTime = 0; + trap_Nav_GetNodePosition( homeWp, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = homeWp; + //Com_Printf("\nHeading for wp %d...\n", NPCInfo->homeWp); +} + +/* +------------------------- +NPC_BSNoClip + + Use in extreme circumstances only +------------------------- +*/ + +void NPC_BSNoClip ( void ) +{ + if ( UpdateGoal() ) + { + vec3_t dir, forward, right, angles, up = {0, 0, 1}; + float fDot, rDot, uDot; + + VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir ); + + vectoangles( dir, angles ); + NPCInfo->desiredYaw = angles[YAW]; + + AngleVectors( NPC->r.currentAngles, forward, right, NULL ); + + VectorNormalize( dir ); + + fDot = DotProduct(forward, dir) * 127; + rDot = DotProduct(right, dir) * 127; + uDot = DotProduct(up, dir) * 127; + + ucmd.forwardmove = floor(fDot); + ucmd.rightmove = floor(rDot); + ucmd.upmove = floor(uDot); + } + else + { + //Cut velocity? + VectorClear( NPC->client->ps.velocity ); + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +void NPC_BSWander (void) +{//FIXME: don't actually go all the way to the next waypoint, just move in fits and jerks...? + if ( !NPCInfo->investigateDebounceTime ) + {//Starting out + float minGoalReachedDistSquared = 64;//32*32; + vec3_t vec; + + //Keep moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + + VectorSubtract ( NPCInfo->tempGoal->r.currentOrigin, NPC->r.currentOrigin, vec); + + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + minGoalReachedDistSquared = 64; + } + + if ( VectorLengthSquared( vec ) < minGoalReachedDistSquared ) + { + //Close enough, just got there + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + + if( !Q_irand(0, 1) ) + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_LOOKAROUND1, SETANIM_FLAG_NORMAL); + } + else + { + NPC_SetAnim(NPC, SETANIM_BOTH, BOTH_GUARD_IDLE1, SETANIM_FLAG_NORMAL); + } + //Just got here, so Look around for a while + NPCInfo->investigateDebounceTime = level.time + Q_irand(3000, 10000); + } + else + { + //Keep moving toward goal + NPC_MoveToGoal( qtrue ); + } + } + else + { + //We're there + if ( NPCInfo->investigateDebounceTime > level.time ) + { + //Still waiting around for a bit + //Turn angles every now and then to look around + if ( NPCInfo->tempGoal->waypoint != WAYPOINT_NONE ) + { + if ( !Q_irand( 0, 30 ) ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPCInfo->tempGoal->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + int branchNum = Q_irand( 0, numEdges - 1 ); + + vec3_t branchPos, lookDir; + + int nextWp = trap_Nav_GetNodeEdge( NPCInfo->tempGoal->waypoint, branchNum ); + trap_Nav_GetNodePosition( nextWp, branchPos ); + + VectorSubtract( branchPos, NPCInfo->tempGoal->r.currentOrigin, lookDir ); + NPCInfo->desiredYaw = AngleNormalize360( vectoyaw( lookDir ) + flrand( -45, 45 ) ); + } + } + } + } + else + {//Just finished waiting + NPC->waypoint = NAV_FindClosestWaypointForEnt( NPC, WAYPOINT_NONE ); + + if ( NPC->waypoint != WAYPOINT_NONE ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPC->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + int branchNum = Q_irand( 0, numEdges - 1 ); + + int nextWp = trap_Nav_GetNodeEdge( NPC->waypoint, branchNum ); + trap_Nav_GetNodePosition( nextWp, NPCInfo->tempGoal->r.currentOrigin ); + NPCInfo->tempGoal->waypoint = nextWp; + } + + NPCInfo->investigateDebounceTime = 0; + //Start moving toward our tempGoal + NPCInfo->goalEntity = NPCInfo->tempGoal; + NPC_MoveToGoal( qtrue ); + } + } + } + + NPC_UpdateAngles( qtrue, qtrue ); +} + +/* +void NPC_BSFaceLeader (void) +{ + vec3_t head, leaderHead, delta, angleToLeader; + + if ( !NPC->client->leader ) + {//uh.... okay. + return; + } + + CalcEntitySpot( NPC->client->leader, SPOT_HEAD, leaderHead ); + CalcEntitySpot( NPC, SPOT_HEAD, head ); + VectorSubtract( leaderHead, head, delta ); + vectoangles( delta, angleToLeader ); + VectorNormalize( delta ); + NPC->NPC->desiredYaw = angleToLeader[YAW]; + NPC->NPC->desiredPitch = angleToLeader[PITCH]; + + NPC_UpdateAngles(qtrue, qtrue); +} +*/ +/* +------------------------- +NPC_BSFlee +------------------------- +*/ +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void WP_DropWeapon( gentity_t *dropper, vec3_t velocity ); +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +void NPC_Surrender( void ) +{//FIXME: say "don't shoot!" if we weren't already surrendering + if ( NPC->client->ps.weaponTime || PM_InKnockDown( &NPC->client->ps ) ) + { + return; + } + if ( NPC->s.weapon != WP_NONE && + NPC->s.weapon != WP_STUN_BATON && + NPC->s.weapon != WP_SABER ) + { + //WP_DropWeapon( NPC, NULL ); //rwwFIXMEFIXME: Do this (gonna need a system for notifying client of removal) + } + if ( NPCInfo->surrenderTime < level.time - 5000 ) + {//haven't surrendered for at least 6 seconds, tell them what you're doing + //FIXME: need real dialogue EV_SURRENDER + NPCInfo->blockedSpeechDebounceTime = 0;//make sure we say this + G_AddVoiceEvent( NPC, Q_irand( EV_PUSHED1, EV_PUSHED3 ), 3000 ); + } +// NPC_SetAnim( NPC, SETANIM_TORSO, TORSO_SURRENDER_START, SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE ); +// NPC->client->ps.torsoTimer = 1000; + NPCInfo->surrenderTime = level.time + 1000;//stay surrendered for at least 1 second + //FIXME: while surrendering, make a big sight/sound alert? Or G_AlertTeam? +} + +qboolean NPC_CheckSurrender( void ) +{ + if ( !trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) + && NPC->client->ps.groundEntityNum != ENTITYNUM_NONE + && !NPC->client->ps.weaponTime && !PM_InKnockDown( &NPC->client->ps ) + && NPC->enemy && NPC->enemy->client && NPC->enemy->enemy == NPC && NPC->enemy->s.weapon != WP_NONE && NPC->enemy->s.weapon != WP_STUN_BATON + && NPC->enemy->health > 20 && NPC->enemy->painDebounceTime < level.time - 3000 && NPC->enemy->client->ps.fd.forcePowerDebounce[FP_SABER_DEFENSE] < level.time - 1000 ) + {//don't surrender if scripted to run somewhere or if we're in the air or if we're busy or if we don't have an enemy or if the enemy is not mad at me or is hurt or not a threat or busy being attacked + //FIXME: even if not in a group, don't surrender if there are other enemies in the PVS and within a certain range? + if ( NPC->s.weapon != WP_ROCKET_LAUNCHER + && NPC->s.weapon != WP_REPEATER + && NPC->s.weapon != WP_FLECHETTE + && NPC->s.weapon != WP_SABER ) + {//jedi and heavy weapons guys never surrender + //FIXME: rework all this logic into some orderly fashion!!! + if ( NPC->s.weapon != WP_NONE ) + {//they have a weapon so they'd have to drop it to surrender + //don't give up unless low on health + if ( NPC->health > 25 /*|| NPC->health >= NPC->max_health*/ ) + { //rwwFIXMEFIXME: Keep max health not a ps state? + return qfalse; + } + //if ( g_crosshairEntNum == NPC->s.number && NPC->painDebounceTime > level.time ) + if (NPC_SomeoneLookingAtMe(NPC) && NPC->painDebounceTime > level.time) + {//if he just shot me, always give up + //fall through + } + else + {//don't give up unless facing enemy and he's very close + if ( !InFOV( NPC->enemy, NPC, 60, 30 ) ) + {//I'm not looking at them + return qfalse; + } + else if ( DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) < 65536/*256*256*/ ) + {//they're not close + return qfalse; + } + else if ( !trap_InPVS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + {//they're not in the same room + return qfalse; + } + } + } + //fixme: this logic keeps making npc's randomly surrender + /* + if ( NPCInfo->group && NPCInfo->group->numGroup <= 1 ) + {//I'm alone but I was in a group//FIXME: surrender anyway if just melee or no weap? + if ( NPC->s.weapon == WP_NONE + //NPC has a weapon + || (NPC->enemy && NPC->enemy->s.number < MAX_CLIENTS) + || (NPC->enemy->s.weapon == WP_SABER&&NPC->enemy->client&&!NPC->enemy->client->ps.saberHolstered) + || (NPC->enemy->NPC && NPC->enemy->NPC->group && NPC->enemy->NPC->group->numGroup > 2) ) + {//surrender only if have no weapon or fighting a player or jedi or if we are outnumbered at least 3 to 1 + if ( (NPC->enemy && NPC->enemy->s.number < MAX_CLIENTS) ) + {//player is the guy I'm running from + //if ( g_crosshairEntNum == NPC->s.number ) + if (NPC_SomeoneLookingAtMe(NPC)) + {//give up if player is aiming at me + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + else if ( NPC->enemy->s.weapon == WP_SABER ) + {//player is using saber + if ( InFOV( NPC, NPC->enemy, 60, 30 ) ) + {//they're looking at me + if ( DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) < 16384 ) + {//they're close + if ( trap_InPVS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + {//they're in the same room + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + } + } + } + else if ( NPC->enemy ) + {//??? + //should NPC's surrender to others? + if ( InFOV( NPC, NPC->enemy, 30, 30 ) ) + {//they're looking at me + if ( DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) < 4096 ) + {//they're close + if ( trap_InPVS( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + {//they're in the same room + //FIXME: should player-team NPCs not fire on surrendered NPCs? + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return qtrue; + } + } + } + } + } + } + */ + } + } + return qfalse; +} + +void NPC_BSFlee( void ) +{//FIXME: keep checking for danger + gentity_t *goal; + + if ( TIMER_Done( NPC, "flee" ) && NPCInfo->tempBehavior == BS_FLEE ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + NPCInfo->squadState = SQUAD_IDLE; + //FIXME: should we set some timer to make him stay in this spot for a bit, + //so he doesn't just suddenly turn around and come back at the enemy? + //OR, just stop running toward goal for last second or so of flee? + } + if ( NPC_CheckSurrender() ) + { + return; + } + goal = NPCInfo->goalEntity; + if ( !goal ) + { + goal = NPCInfo->lastGoalEntity; + if ( !goal ) + {//???!!! + goal = NPCInfo->tempGoal; + } + } + + if ( goal ) + { + qboolean moved; + qboolean reverseCourse = qtrue; + + //FIXME: if no weapon, find one and run to pick it up? + + //Let's try to find a waypoint that gets me away from this thing + if ( NPC->waypoint == WAYPOINT_NONE ) + { + NPC->waypoint = NAV_GetNearestNode( NPC, NPC->lastWaypoint ); + } + if ( NPC->waypoint != WAYPOINT_NONE ) + { + int numEdges = trap_Nav_GetNodeNumEdges( NPC->waypoint ); + + if ( numEdges != WAYPOINT_NONE ) + { + vec3_t dangerDir; + int nextWp; + int branchNum; + + VectorSubtract( NPCInfo->investigateGoal, NPC->r.currentOrigin, dangerDir ); + VectorNormalize( dangerDir ); + + for ( branchNum = 0; branchNum < numEdges; branchNum++ ) + { + vec3_t branchPos, runDir; + + nextWp = trap_Nav_GetNodeEdge( NPC->waypoint, branchNum ); + trap_Nav_GetNodePosition( nextWp, branchPos ); + + VectorSubtract( branchPos, NPC->r.currentOrigin, runDir ); + VectorNormalize( runDir ); + if ( DotProduct( runDir, dangerDir ) > flrand( 0, 0.5 ) ) + {//don't run toward danger + continue; + } + //FIXME: don't want to ping-pong back and forth + NPC_SetMoveGoal( NPC, branchPos, 0, qtrue, -1, NULL ); + reverseCourse = qfalse; + break; + } + } + } + + moved = NPC_MoveToGoal( qfalse );//qtrue? (do try to move straight to (away from) goal) + + if ( NPC->s.weapon == WP_NONE && (moved == qfalse || reverseCourse) ) + {//No weapon and no escape route... Just cower? Need anim. + NPC_Surrender(); + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + //If our move failed, then just run straight away from our goal + //FIXME: We really shouldn't do this. + if ( moved == qfalse ) + { + vec3_t dir; + float dist; + if ( reverseCourse ) + { + VectorSubtract( NPC->r.currentOrigin, goal->r.currentOrigin, dir ); + } + else + { + VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, dir ); + } + NPCInfo->distToGoal = dist = VectorNormalize( dir ); + NPCInfo->desiredYaw = vectoyaw( dir ); + NPCInfo->desiredPitch = 0; + ucmd.forwardmove = 127; + } + else if ( reverseCourse ) + { + //ucmd.forwardmove *= -1; + //ucmd.rightmove *= -1; + //VectorScale( NPC->client->ps.moveDir, -1, NPC->client->ps.moveDir ); + NPCInfo->desiredYaw *= -1; + } + //FIXME: can stop after a safe distance? + //ucmd.upmove = 0; + ucmd.buttons &= ~BUTTON_WALKING; + //FIXME: what do we do once we've gotten to our goal? + } + NPC_UpdateAngles( qtrue, qtrue ); + + NPC_CheckGetNewWeapon(); +} + +void NPC_StartFlee( gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ) +{ + int cp = -1; + + if ( trap_ICARUS_TaskIDPending( NPC, TID_MOVE_NAV ) ) + {//running somewhere that a script requires us to go, don't interrupt that! + return; + } + + //if have a fleescript, run that instead + if ( G_ActivateBehavior( NPC, BSET_FLEE ) ) + { + return; + } + //FIXME: play a flee sound? Appropriate to situation? + if ( enemy ) + { + G_SetEnemy( NPC, enemy ); + } + + //FIXME: if don't have a weapon, find nearest one we have a route to and run for it? + if ( dangerLevel > AEL_DANGER || NPC->s.weapon == WP_NONE || ((!NPCInfo->group || NPCInfo->group->numGroup <= 1) && NPC->health <= 10 ) ) + {//IF either great danger OR I have no weapon OR I'm alone and low on health, THEN try to find a combat point out of PVS + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_COVER|CP_AVOID|CP_HAS_ROUTE|CP_NO_PVS, 128, -1 ); + } + //FIXME: still happens too often... + if ( cp == -1 ) + {//okay give up on the no PVS thing + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_COVER|CP_AVOID|CP_HAS_ROUTE, 128, -1 ); + if ( cp == -1 ) + {//okay give up on the avoid + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_COVER|CP_HAS_ROUTE, 128, -1 ); + if ( cp == -1 ) + {//okay give up on the cover + cp = NPC_FindCombatPoint( NPC->r.currentOrigin, NPC->r.currentOrigin, dangerPoint, CP_HAS_ROUTE, 128, -1 ); + } + } + } + + //see if we got a valid one + if ( cp != -1 ) + {//found a combat point + NPC_SetCombatPoint( cp ); + NPC_SetMoveGoal( NPC, level.combatPoints[cp].origin, 8, qtrue, cp, NULL ); + NPCInfo->behaviorState = BS_HUNT_AND_KILL; + NPCInfo->tempBehavior = BS_DEFAULT; + } + else + {//need to just run like hell! + if ( NPC->s.weapon != WP_NONE ) + { + return;//let's just not flee? + } + else + { + //FIXME: other evasion AI? Duck? Strafe? Dodge? + NPCInfo->tempBehavior = BS_FLEE; + //Run straight away from here... FIXME: really want to find farthest waypoint/navgoal from this pos... maybe based on alert event radius? + NPC_SetMoveGoal( NPC, dangerPoint, 0, qtrue, -1, NULL ); + //store the danger point + VectorCopy( dangerPoint, NPCInfo->investigateGoal );//FIXME: make a new field for this? + } + } + //FIXME: localize this Timer? + TIMER_Set( NPC, "attackDelay", Q_irand( 500, 2500 ) ); + //FIXME: is this always applicable? + NPCInfo->squadState = SQUAD_RETREAT; + TIMER_Set( NPC, "flee", Q_irand( fleeTimeMin, fleeTimeMax ) ); + TIMER_Set( NPC, "panic", Q_irand( 1000, 4000 ) );//how long to wait before trying to nav to a dropped weapon + + if (NPC->client->NPC_class != CLASS_PROTOCOL) + { + TIMER_Set( NPC, "duck", 0 ); + } +} + +void G_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ) +{ + if ( !self->NPC ) + {//player + return; + } + SaveNPCGlobals(); + SetNPCGlobals( self ); + + NPC_StartFlee( enemy, dangerPoint, dangerLevel, fleeTimeMin, fleeTimeMax ); + + RestoreNPCGlobals(); +} + +void NPC_BSEmplaced( void ) +{ + qboolean enemyLOS = qfalse; + qboolean enemyCS = qfalse; + qboolean faceEnemy = qfalse; + qboolean shoot = qfalse; + vec3_t impactPos; + + //Don't do anything if we're hurt + if ( NPC->painDebounceTime > level.time ) + { + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if( NPCInfo->scriptFlags & SCF_FIRE_WEAPON ) + { + WeaponThink( qtrue ); + } + + //If we don't have an enemy, just idle + if ( NPC_CheckEnemyExt(qfalse) == qfalse ) + { + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredYaw = NPC->s.angles[1] + Q_irand( -90, 90 ); + } + if ( !Q_irand( 0, 30 ) ) + { + NPCInfo->desiredPitch = Q_irand( -20, 20 ); + } + NPC_UpdateAngles( qtrue, qtrue ); + return; + } + + if ( NPC_ClearLOS4( NPC->enemy ) ) + { + int hit; + gentity_t *hitEnt; + + enemyLOS = qtrue; + + hit = NPC_ShotEntity( NPC->enemy, impactPos ); + hitEnt = &g_entities[hit]; + + if ( hit == NPC->enemy->s.number || ( hitEnt && hitEnt->takedamage ) ) + {//can hit enemy or will hit glass or other minor breakable (or in emplaced gun), so shoot anyway + enemyCS = qtrue; + NPC_AimAdjust( 2 );//adjust aim better longer we have clear shot at enemy + VectorCopy( NPC->enemy->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + } + } +/* + else if ( trap_InPVS( NPC->enemy->r.currentOrigin, NPC->r.currentOrigin ) ) + { + NPCInfo->enemyLastSeenTime = level.time; + faceEnemy = qtrue; + NPC_AimAdjust( -1 );//adjust aim worse longer we cannot see enemy + } +*/ + + if ( enemyLOS ) + {//FIXME: no need to face enemy if we're moving to some other goal and he's too far away to shoot? + faceEnemy = qtrue; + } + if ( enemyCS ) + { + shoot = qtrue; + } + + if ( faceEnemy ) + {//face the enemy + NPC_FaceEnemy( qtrue ); + } + else + {//we want to face in the dir we're running + NPC_UpdateAngles( qtrue, qtrue ); + } + + if ( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + { + shoot = qfalse; + } + + if ( NPC->enemy && NPC->enemy->enemy ) + { + if ( NPC->enemy->s.weapon == WP_SABER && NPC->enemy->enemy->s.weapon == WP_SABER ) + {//don't shoot at an enemy jedi who is fighting another jedi, for fear of injuring one or causing rogue blaster deflections (a la Obi Wan/Vader duel at end of ANH) + shoot = qfalse; + } + } + if ( shoot ) + {//try to shoot if it's time + if( !(NPCInfo->scriptFlags & SCF_FIRE_WEAPON) ) // we've already fired, no need to do it again here + { + WeaponThink( qtrue ); + } + } +} diff --git a/code/game/NPC_combat.c b/code/game/NPC_combat.c new file mode 100644 index 0000000..763f7d5 --- /dev/null +++ b/code/game/NPC_combat.c @@ -0,0 +1,3145 @@ +//NPC_combat.cpp +#include "b_local.h" +#include "g_nav.h" + +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void G_SetEnemy( gentity_t *self, gentity_t *enemy ); +extern qboolean NPC_CheckLookTarget( gentity_t *self ); +extern void NPC_ClearLookTarget( gentity_t *self ); +extern void NPC_Jedi_RateNewEnemy( gentity_t *self, gentity_t *enemy ); +extern int NAV_FindClosestWaypointForPoint2( vec3_t point ); +extern int NAV_GetNearestNode( gentity_t *self, int lastNode ); +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum ); +extern qboolean PM_DroidMelee( int npc_class ); + +void ChangeWeapon( gentity_t *ent, int newWeapon ); + +void G_ClearEnemy (gentity_t *self) +{ + NPC_CheckLookTarget( self ); + + if ( self->enemy ) + { + if( self->client && self->client->renderInfo.lookTarget == self->enemy->s.number ) + { + NPC_ClearLookTarget( self ); + } + + if ( self->NPC && self->enemy == self->NPC->goalEntity ) + { + self->NPC->goalEntity = NULL; + } + //FIXME: set last enemy? + } + + self->enemy = NULL; +} + +/* +------------------------- +NPC_AngerAlert +------------------------- +*/ + +#define ANGER_ALERT_RADIUS 512 +#define ANGER_ALERT_SOUND_RADIUS 256 + +void G_AngerAlert( gentity_t *self ) +{ + if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) ) + {//I'm not a team playa... + return; + } + if ( !TIMER_Done( self, "interrogating" ) ) + {//I'm interrogating, don't wake everyone else up yet... FIXME: this may never wake everyone else up, though! + return; + } + //FIXME: hmm.... with all the other new alerts now, is this still neccesary or even a good idea...? + G_AlertTeam( self, self->enemy, ANGER_ALERT_RADIUS, ANGER_ALERT_SOUND_RADIUS ); +} + +/* +------------------------- +G_TeamEnemy +------------------------- +*/ + +qboolean G_TeamEnemy( gentity_t *self ) +{//FIXME: Probably a better way to do this, is a linked list of your teammates already available? + int i; + gentity_t *ent; + + if ( !self->client || self->client->playerTeam == TEAM_FREE ) + { + return qfalse; + } + if ( self && self->NPC && (self->NPC->scriptFlags&SCF_NO_GROUPS) ) + {//I'm not a team playa... + return qfalse; + } + + for( i = 1; i < level.num_entities; i++ ) + { + ent = &g_entities[i]; + + if ( ent == self ) + { + continue; + } + + if ( ent->health <= 0 ) + { + continue; + } + + if ( !ent->client ) + { + continue; + } + + if ( ent->client->playerTeam != self->client->playerTeam ) + {//ent is not on my team + continue; + } + + if ( ent->enemy ) + {//they have an enemy + if ( !ent->enemy->client || ent->enemy->client->playerTeam != self->client->playerTeam ) + {//the ent's enemy is either a normal ent or is a player/NPC that is not on my team + return qtrue; + } + } + } + + return qfalse; +} + +void G_AttackDelay( gentity_t *self, gentity_t *enemy ) +{ + if ( enemy && self->client && self->NPC ) + {//delay their attack based on how far away they're facing from enemy + vec3_t fwd, dir; + int attDelay; + + VectorSubtract( self->client->renderInfo.eyePoint, enemy->r.currentOrigin, dir );//purposely backwards + VectorNormalize( dir ); + AngleVectors( self->client->renderInfo.eyeAngles, fwd, NULL, NULL ); + //dir[2] = fwd[2] = 0;//ignore z diff? + + attDelay = (4-g_spskill.integer)*500;//initial: from 1000ms delay on hard to 2000ms delay on easy + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + {//invert + attDelay = 2000-attDelay; + } + attDelay += floor( (DotProduct( fwd, dir )+1.0f) * 2000.0f );//add up to 4000ms delay if they're facing away + + //FIXME: should distance matter, too? + + //Now modify the delay based on NPC_class, weapon, and team + //NOTE: attDelay should be somewhere between 1000 to 6000 milliseconds + switch ( self->client->NPC_class ) + { + case CLASS_IMPERIAL://they give orders and hang back + attDelay += Q_irand( 500, 1500 ); + break; + case CLASS_STORMTROOPER://stormtroopers shoot sooner + if ( self->NPC->rank >= RANK_LT ) + {//officers shoot even sooner + attDelay -= Q_irand( 500, 1500 ); + } + else + {//normal stormtroopers don't have as fast reflexes as officers + attDelay -= Q_irand( 0, 1000 ); + } + break; + case CLASS_SWAMPTROOPER://shoot very quickly? What about guys in water? + attDelay -= Q_irand( 1000, 2000 ); + break; + case CLASS_IMPWORKER://they panic, don't fire right away + attDelay += Q_irand( 1000, 2500 ); + break; + case CLASS_TRANDOSHAN: + attDelay -= Q_irand( 500, 1500 ); + break; + case CLASS_JAN: + case CLASS_LANDO: + case CLASS_PRISONER: + case CLASS_REBEL: + attDelay -= Q_irand( 500, 1500 ); + break; + case CLASS_GALAKMECH: + case CLASS_ATST: + attDelay -= Q_irand( 1000, 2000 ); + break; + case CLASS_REELO: + case CLASS_UGNAUGHT: + case CLASS_JAWA: + return; + break; + case CLASS_MINEMONSTER: + case CLASS_MURJJ: + return; + break; + case CLASS_INTERROGATOR: + case CLASS_PROBE: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_SENTRY: + return; + break; + case CLASS_REMOTE: + case CLASS_SEEKER: + return; + break; + /* + case CLASS_GRAN: + case CLASS_RODIAN: + case CLASS_WEEQUAY: + break; + case CLASS_JEDI: + case CLASS_SHADOWTROOPER: + case CLASS_TAVION: + case CLASS_REBORN: + case CLASS_LUKE: + case CLASS_DESANN: + break; + */ + } + + switch ( self->s.weapon ) + { + case WP_NONE: + case WP_SABER: + return; + break; + case WP_BRYAR_PISTOL: + break; + case WP_BLASTER: + if ( self->NPC->scriptFlags & SCF_ALT_FIRE ) + {//rapid-fire blasters + attDelay += Q_irand( 0, 500 ); + } + else + {//regular blaster + attDelay -= Q_irand( 0, 500 ); + } + break; + case WP_BOWCASTER: + attDelay += Q_irand( 0, 500 ); + break; + case WP_REPEATER: + if ( !(self->NPC->scriptFlags&SCF_ALT_FIRE) ) + {//rapid-fire blasters + attDelay += Q_irand( 0, 500 ); + } + break; + case WP_FLECHETTE: + attDelay += Q_irand( 500, 1500 ); + break; + case WP_ROCKET_LAUNCHER: + attDelay += Q_irand( 500, 1500 ); + break; +// case WP_BLASTER_PISTOL: // apparently some enemy only version of the blaster +// attDelay -= Q_irand( 500, 1500 ); +// break; + //rwwFIXMEFIXME: Have this weapon for NPCs? + case WP_DISRUPTOR://sniper's don't delay? + return; + break; + case WP_THERMAL://grenade-throwing has a built-in delay + return; + break; + case WP_STUN_BATON: // Any ol' melee attack + return; + break; + case WP_EMPLACED_GUN: + return; + break; + case WP_TURRET: // turret guns + return; + break; +// case WP_BOT_LASER: // Probe droid - Laser blast +// return; //rwwFIXMEFIXME: Have this weapon for NPCs? + break; + /* + case WP_DEMP2: + break; + case WP_TRIP_MINE: + break; + case WP_DET_PACK: + break; + case WP_STUN_BATON: + break; + case WP_ATST_MAIN: + break; + case WP_ATST_SIDE: + break; + case WP_TIE_FIGHTER: + break; + case WP_RAPID_FIRE_CONC: + break; + */ + } + + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + {//clamp it + if ( attDelay > 2000 ) + { + attDelay = 2000; + } + } + + //don't shoot right away + if ( attDelay > 4000+((2-g_spskill.integer)*3000) ) + { + attDelay = 4000+((2-g_spskill.integer)*3000); + } + TIMER_Set( self, "attackDelay", attDelay );//Q_irand( 1500, 4500 ) ); + //don't move right away either + if ( attDelay > 4000 ) + { + attDelay = 4000 - Q_irand(500, 1500); + } + else + { + attDelay -= Q_irand(500, 1500); + } + + TIMER_Set( self, "roamTime", attDelay );//was Q_irand( 1000, 3500 ); + } +} + +void G_ForceSaberOn(gentity_t *ent) +{ + if (ent->client->ps.saberInFlight) + { //alright, can't turn it on now in any case, so forget it. + return; + } + + if (!ent->client->ps.saberHolstered) + { //it's already on! + return; + } + + if (ent->client->ps.weapon != WP_SABER) + { //This probably should never happen. But if it does we'll just return without complaining. + return; + } + + //Well then, turn it on. + ent->client->ps.saberHolstered = 0; + + if (ent->client->saber[0].soundOn) + { + G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn); + } + if (ent->client->saber[1].soundOn) + { + G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn); + } +} + + +/* +------------------------- +G_SetEnemy +------------------------- +*/ +void G_AimSet( gentity_t *self, int aim ); +void G_SetEnemy( gentity_t *self, gentity_t *enemy ) +{ + int event = 0; + + //Must be valid + if ( enemy == NULL ) + return; + + //Must be valid + if ( enemy->inuse == 0 ) + { + return; + } + + //Don't take the enemy if in notarget + if ( enemy->flags & FL_NOTARGET ) + return; + + if ( !self->NPC ) + { + self->enemy = enemy; + return; + } + + if ( self->NPC->confusionTime > level.time ) + {//can't pick up enemies if confused + return; + } + +#ifdef _DEBUG + if ( self->s.number ) + { + assert( enemy != self ); + } +#endif// _DEBUG + +// if ( enemy->client && enemy->client->playerTeam == TEAM_DISGUISE ) +// {//unmask the player +// enemy->client->playerTeam = TEAM_PLAYER; +// } + + if ( self->client && self->NPC && enemy->client && enemy->client->playerTeam == self->client->playerTeam ) + {//Probably a damn script! + if ( self->NPC->charmedTime > level.time ) + {//Probably a damn script! + return; + } + } + + if ( self->NPC && self->client && self->client->ps.weapon == WP_SABER ) + { + //when get new enemy, set a base aggression based on what that enemy is using, how far they are, etc. + NPC_Jedi_RateNewEnemy( self, enemy ); + } + + //NOTE: this is not necessarily true! + //self->NPC->enemyLastSeenTime = level.time; + + if ( self->enemy == NULL ) + { + //TEMP HACK: turn on our saber + if ( self->health > 0 ) + { + G_ForceSaberOn(self); + } + + //FIXME: Have to do this to prevent alert cascading + G_ClearEnemy( self ); + self->enemy = enemy; + + //Special case- if player is being hunted by his own people, set their enemy team correctly + if ( self->client->playerTeam == NPCTEAM_PLAYER && enemy->s.number == 0 ) + { + self->client->enemyTeam = NPCTEAM_PLAYER; + } + + //If have an anger script, run that instead of yelling + if( G_ActivateBehavior( self, BSET_ANGER ) ) + { + } + else if ( self->client && enemy->client && self->client->playerTeam != enemy->client->playerTeam ) + { + //FIXME: Use anger when entire team has no enemy. + // Basically, you're first one to notice enemies + //if ( self->forcePushTime < level.time ) // not currently being pushed + if (1) //rwwFIXMEFIXME: Set forcePushTime + { + if ( !G_TeamEnemy( self ) ) + {//team did not have an enemy previously + event = Q_irand(EV_ANGER1, EV_ANGER3); + } + } + + if ( event ) + {//yell + G_AddVoiceEvent( self, event, 2000 ); + } + } + + if ( self->s.weapon == WP_BLASTER || self->s.weapon == WP_REPEATER || + self->s.weapon == WP_THERMAL /*|| self->s.weapon == WP_BLASTER_PISTOL */ //rwwFIXMEFIXME: Blaster pistol useable by npcs? + || self->s.weapon == WP_BOWCASTER ) + {//Hmm, how about sniper and bowcaster? + //When first get mad, aim is bad + //Hmm, base on game difficulty, too? Rank? + if ( self->client->playerTeam == NPCTEAM_PLAYER ) + { + G_AimSet( self, Q_irand( self->NPC->stats.aim - (5*(g_spskill.integer)), self->NPC->stats.aim - g_spskill.integer ) ); + } + else + { + int minErr = 3; + int maxErr = 12; + if ( self->client->NPC_class == CLASS_IMPWORKER ) + { + minErr = 15; + maxErr = 30; + } + else if ( self->client->NPC_class == CLASS_STORMTROOPER && self->NPC && self->NPC->rank <= RANK_CREWMAN ) + { + minErr = 5; + maxErr = 15; + } + + G_AimSet( self, Q_irand( self->NPC->stats.aim - (maxErr*(3-g_spskill.integer)), self->NPC->stats.aim - (minErr*(3-g_spskill.integer)) ) ); + } + } + + //Alert anyone else in the area + if ( Q_stricmp( "desperado", self->NPC_type ) != 0 && Q_stricmp( "paladin", self->NPC_type ) != 0 ) + {//special holodeck enemies exception + if ( self->client->ps.fd.forceGripBeingGripped < level.time ) + {//gripped people can't call for help + G_AngerAlert( self ); + } + } + + //Stormtroopers don't fire right away! + G_AttackDelay( self, enemy ); + + //rwwFIXMEFIXME: Deal with this some other way. + /* + //FIXME: this is a disgusting hack that is supposed to make the Imperials start with their weapon holstered- need a better way + if ( self->client->ps.weapon == WP_NONE && !Q_strncmp( self->NPC_type, "imp", 3 ) && !(self->NPC->scriptFlags & SCF_FORCED_MARCH) ) + { + if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER ) ) + { + ChangeWeapon( self, WP_BLASTER ); + self->client->ps.weapon = WP_BLASTER; + self->client->ps.weaponstate = WEAPON_READY; + G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER].weaponMdl, self->handRBolt, 0 ); + } + else if ( self->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER_PISTOL ) ) + { + ChangeWeapon( self, WP_BLASTER_PISTOL ); + self->client->ps.weapon = WP_BLASTER_PISTOL; + self->client->ps.weaponstate = WEAPON_READY; + G_CreateG2AttachedWeaponModel( self, weaponData[WP_BLASTER_PISTOL].weaponMdl, self->handRBolt, 0 ); + } + } + */ + return; + } + + //Otherwise, just picking up another enemy + + if ( event ) + { + G_AddVoiceEvent( self, event, 2000 ); + } + + //Take the enemy + G_ClearEnemy(self); + self->enemy = enemy; +} + +/* +int ChooseBestWeapon( void ) +{ + int n; + int weapon; + + // check weapons in the NPC's weapon preference order + for ( n = 0; n < MAX_WEAPONS; n++ ) + { + weapon = NPCInfo->weaponOrder[n]; + + if ( weapon == WP_NONE ) + { + break; + } + + if ( !HaveWeapon( weapon ) ) + { + continue; + } + + if ( client->ps.ammo[weaponData[weapon].ammoIndex] ) + { + return weapon; + } + } + + // check weapons serially (mainly in case a weapon is not on the NPC's list) + for ( weapon = 1; weapon < WP_NUM_WEAPONS; weapon++ ) + { + if ( !HaveWeapon( weapon ) ) + { + continue; + } + + if ( client->ps.ammo[weaponData[weapon].ammoIndex] ) + { + return weapon; + } + } + + return client->ps.weapon; +} +*/ + +void ChangeWeapon( gentity_t *ent, int newWeapon ) +{ + if ( !ent || !ent->client || !ent->NPC ) + { + return; + } + + ent->client->ps.weapon = newWeapon; + ent->client->pers.cmd.weapon = newWeapon; + ent->NPC->shotTime = 0; + ent->NPC->burstCount = 0; + ent->NPC->attackHold = 0; + ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[newWeapon].ammoIndex]; + + switch ( newWeapon ) + { + case WP_BRYAR_PISTOL://prifle + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attackdebounce + break; + + /* + case WP_BLASTER_PISTOL: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 1000;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + break; + */ + //rwwFIXMEFIXME: support WP_BLASTER_PISTOL and WP_BOT_LASER + + /* + case WP_BOT_LASER://probe attack + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 600;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 600;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 400;//attack debounce + else + ent->NPC->burstSpacing = 200;//attack debounce + break; + */ + + case WP_SABER: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 0;//attackdebounce + break; + + case WP_DISRUPTOR: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + switch( g_spskill.integer ) + { + case 0: + ent->NPC->burstSpacing = 2500;//attackdebounce + break; + case 1: + ent->NPC->burstSpacing = 2000;//attackdebounce + break; + case 2: + ent->NPC->burstSpacing = 1500;//attackdebounce + break; + } + } + else + { + ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_BOWCASTER: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 1000;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + break; + + case WP_REPEATER: + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 2000;//attackdebounce + } + else + { + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 3; + ent->NPC->burstMean = 6; + ent->NPC->burstMax = 10; + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1500;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + } + break; + + case WP_DEMP2: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attackdebounce + break; + + case WP_FLECHETTE: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + ent->NPC->burstSpacing = 2000;//attackdebounce + } + else + { + ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_ROCKET_LAUNCHER: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 2500;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 2500;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 2000;//attack debounce + else + ent->NPC->burstSpacing = 1500;//attack debounce + break; + + case WP_THERMAL: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 3000;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 3000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 2500;//attack debounce + else + ent->NPC->burstSpacing = 2000;//attack debounce + break; + + /* + case WP_SABER: + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 5;//0.5 sec + ent->NPC->burstMean = 10;//1 second + ent->NPC->burstMax = 20;//3 seconds + ent->NPC->burstSpacing = 2000;//2 seconds + ent->NPC->attackHold = 1000;//Hold attack button for a 1-second burst + break; + + case WP_TRICORDER: + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 5; + ent->NPC->burstMean = 10; + ent->NPC->burstMax = 30; + ent->NPC->burstSpacing = 1000; + break; + */ + + case WP_BLASTER: + if ( ent->NPC->scriptFlags & SCF_ALT_FIRE ) + { + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 3; + ent->NPC->burstMean = 3; + ent->NPC->burstMax = 3; + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1500;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + } + else + { + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + // ent->NPC->burstSpacing = 1000;//attackdebounce + } + break; + + case WP_STUN_BATON: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attackdebounce + break; + + /* + case WP_ATST_MAIN: + case WP_ATST_SIDE: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + // ent->NPC->burstSpacing = 1000;//attackdebounce + if ( g_spskill.integer == 0 ) + ent->NPC->burstSpacing = 1000;//attack debounce + else if ( g_spskill.integer == 1 ) + ent->NPC->burstSpacing = 750;//attack debounce + else + ent->NPC->burstSpacing = 500;//attack debounce + break; + */ + //rwwFIXMEFIXME: support for atst weaps + + case WP_EMPLACED_GUN: + //FIXME: give some designer-control over this? + if ( ent->client && ent->client->NPC_class == CLASS_REELO ) + { + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + ent->NPC->burstSpacing = 1000;//attack debounce + // if ( g_spskill.integer == 0 ) + // ent->NPC->burstSpacing = 300;//attack debounce + // else if ( g_spskill.integer == 1 ) + // ent->NPC->burstSpacing = 200;//attack debounce + // else + // ent->NPC->burstSpacing = 100;//attack debounce + } + else + { + ent->NPC->aiFlags |= NPCAI_BURST_WEAPON; + ent->NPC->burstMin = 2; // 3 shots, really + ent->NPC->burstMean = 2; + ent->NPC->burstMax = 2; + + if ( ent->parent ) // if we have an owner, it should be the chair at this point...so query the chair for its shot debounce times, etc. + { + if ( g_spskill.integer == 0 ) + { + ent->NPC->burstSpacing = ent->parent->wait + 400;//attack debounce + ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots + } + else if ( g_spskill.integer == 1 ) + { + ent->NPC->burstSpacing = ent->parent->wait + 200;//attack debounce + } + else + { + ent->NPC->burstSpacing = ent->parent->wait;//attack debounce + } + } + else + { + if ( g_spskill.integer == 0 ) + { + ent->NPC->burstSpacing = 1200;//attack debounce + ent->NPC->burstMin = ent->NPC->burstMax = 1; // two shots + } + else if ( g_spskill.integer == 1 ) + { + ent->NPC->burstSpacing = 1000;//attack debounce + } + else + { + ent->NPC->burstSpacing = 800;//attack debounce + } + } + } + break; + + default: + ent->NPC->aiFlags &= ~NPCAI_BURST_WEAPON; + break; + } +} + +void NPC_ChangeWeapon( int newWeapon ) +{ + /* + qboolean changing = qfalse; + if ( newWeapon != NPC->client->ps.weapon ) + { + changing = qtrue; + } + if ( changing && NPC->weaponModel[0] > 9 ) + { + trap_G2API_RemoveGhoul2Model( NPC->ghoul2, NPC->weaponModel[0] ); + } + ChangeWeapon( NPC, newWeapon ); + if ( changing && NPC->client->ps.weapon != WP_NONE ) + { + if ( NPC->client->ps.weapon == WP_SABER ) + { + G_CreateG2AttachedWeaponModel( NPC, NPC->client->ps.saber[0].model, NPC->handRBolt, 0 ); + if ( NPC->client->ps.dualSabers ) + { + G_CreateG2AttachedWeaponModel( NPC, NPC->client->ps.saber[1].model, NPC->handLBolt, 0 ); + } + } + else + { + G_CreateG2AttachedWeaponModel( NPC, weaponData[NPC->client->ps.weapon].weaponMdl, NPC->handRBolt, 0 ); + } + }*/ + //rwwFIXMEFIXME: Change the same way as players, all this stuff is just crazy. +} +/* +void NPC_ApplyWeaponFireDelay(void) +How long, if at all, in msec the actual fire should delay from the time the attack was started +*/ +void NPC_ApplyWeaponFireDelay(void) +{ + if ( NPC->attackDebounceTime > level.time ) + {//Just fired, if attacking again, must be a burst fire, so don't add delay + //NOTE: Borg AI uses attackDebounceTime "incorrectly", so this will always return for them! + return; + } + + switch(client->ps.weapon) + { + /* + case WP_BOT_LASER: + NPCInfo->burstCount = 0; + client->ps.weaponTime = 500; + break; + */ //rwwFIXMEFIXME: support for this + + case WP_THERMAL: + if ( client->ps.clientNum ) + {//NPCs delay... + //FIXME: player should, too, but would feel weird in 1st person, even though it + // would look right in 3rd person. Really should have a wind-up anim + // for player as he holds down the fire button to throw, then play + // the actual throw when he lets go... + client->ps.weaponTime = 700; + } + break; + + case WP_STUN_BATON: + //if ( !PM_DroidMelee( client->NPC_class ) ) + if (1) //rwwFIXMEFIXME: ... + {//FIXME: should be unique per melee anim + client->ps.weaponTime = 300; + } + break; + + default: + client->ps.weaponTime = 0; + break; + } +} + +/* +------------------------- +ShootThink +------------------------- +*/ +void ShootThink( void ) +{ + int delay; + + ucmd.buttons &= ~BUTTON_ATTACK; +/* + if ( enemyVisibility != VIS_SHOOT) + return; +*/ + + if ( client->ps.weapon == WP_NONE ) + return; + + if ( client->ps.weaponstate != WEAPON_READY && client->ps.weaponstate != WEAPON_FIRING && client->ps.weaponstate != WEAPON_IDLE) + return; + + if ( level.time < NPCInfo->shotTime ) + { + return; + } + + ucmd.buttons |= BUTTON_ATTACK; + + NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex]; // checkme + + NPC_ApplyWeaponFireDelay(); + + if ( NPCInfo->aiFlags & NPCAI_BURST_WEAPON ) + { + if ( !NPCInfo->burstCount ) + { + NPCInfo->burstCount = Q_irand( NPCInfo->burstMin, NPCInfo->burstMax ); + /* + NPCInfo->burstCount = erandom( NPCInfo->burstMean ); + if ( NPCInfo->burstCount < NPCInfo->burstMin ) + { + NPCInfo->burstCount = NPCInfo->burstMin; + } + else if ( NPCInfo->burstCount > NPCInfo->burstMax ) + { + NPCInfo->burstCount = NPCInfo->burstMax; + } + */ + delay = 0; + } + else + { + NPCInfo->burstCount--; + if ( NPCInfo->burstCount == 0 ) + { + delay = NPCInfo->burstSpacing; + } + else + { + delay = 0; + } + } + + if ( !delay ) + { + // HACK: dirty little emplaced bits, but is done because it would otherwise require some sort of new variable... + if ( client->ps.weapon == WP_EMPLACED_GUN ) + { + if ( NPC->parent ) // try and get the debounce values from the chair if we can + { + if ( g_spskill.integer == 0 ) + { + delay = NPC->parent->random + 150; + } + else if ( g_spskill.integer == 1 ) + { + delay = NPC->parent->random + 100; + } + else + { + delay = NPC->parent->random; + } + } + else + { + if ( g_spskill.integer == 0 ) + { + delay = 350; + } + else if ( g_spskill.integer == 1 ) + { + delay = 300; + } + else + { + delay = 200; + } + } + } + } + } + else + { + delay = NPCInfo->burstSpacing; + } + + NPCInfo->shotTime = level.time + delay; + NPC->attackDebounceTime = level.time + NPC_AttackDebounceForWeapon(); +} + +/* +static void WeaponThink( qboolean inCombat ) +FIXME makes this so there's a delay from event that caused us to check to actually doing it + +Added: hacks for Borg +*/ +void WeaponThink( qboolean inCombat ) +{ + if ( client->ps.weaponstate == WEAPON_RAISING || client->ps.weaponstate == WEAPON_DROPPING ) + { + ucmd.weapon = client->ps.weapon; + ucmd.buttons &= ~BUTTON_ATTACK; + return; + } + +//MCG - Begin + //For now, no-one runs out of ammo + if(NPC->client->ps.ammo[ weaponData[client->ps.weapon].ammoIndex ] < 10) // checkme +// if(NPC->client->ps.ammo[ client->ps.weapon ] < 10) + { + Add_Ammo (NPC, client->ps.weapon, 100); + } + + /*if ( NPC->playerTeam == TEAM_BORG ) + {//HACK!!! + if(!(NPC->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BORG_WEAPON ))) + NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BORG_WEAPON ); + + if ( client->ps.weapon != WP_BORG_WEAPON ) + { + NPC_ChangeWeapon( WP_BORG_WEAPON ); + Add_Ammo (NPC, client->ps.weapon, 10); + NPCInfo->currentAmmo = client->ps.ammo[client->ps.weapon]; + } + } + else */ + + /*if ( NPC->client->playerTeam == TEAM_SCAVENGERS ) + {//HACK!!! + if(!(NPC->client->ps.stats[STAT_WEAPONS] & ( 1 << WP_BLASTER ))) + NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BLASTER ); + + if ( client->ps.weapon != WP_BLASTER ) + + { + NPC_ChangeWeapon( WP_BLASTER ); + Add_Ammo (NPC, client->ps.weapon, 10); +// NPCInfo->currentAmmo = client->ps.ammo[client->ps.weapon]; + NPCInfo->currentAmmo = client->ps.ammo[weaponData[client->ps.weapon].ammoIndex]; // checkme + } + } + else*/ +//MCG - End + { + // if the gun in our hands is out of ammo, we need to change + /*if ( client->ps.ammo[client->ps.weapon] == 0 ) + { + NPCInfo->aiFlags |= NPCAI_CHECK_WEAPON; + } + + if ( NPCInfo->aiFlags & NPCAI_CHECK_WEAPON ) + { + NPCInfo->aiFlags &= ~NPCAI_CHECK_WEAPON; + bestWeapon = ChooseBestWeapon(); + if ( bestWeapon != client->ps.weapon ) + { + NPC_ChangeWeapon( bestWeapon ); + } + }*/ + } + + ucmd.weapon = client->ps.weapon; + ShootThink(); +} + +/* +HaveWeapon +*/ + +qboolean HaveWeapon( int weapon ) +{ + return ( client->ps.stats[STAT_WEAPONS] & ( 1 << weapon ) ); +} + +qboolean EntIsGlass (gentity_t *check) +{ + if(check->classname && + !Q_stricmp("func_breakable", check->classname) && + check->count == 1 && check->health <= 100) + { + return qtrue; + } + + return qfalse; +} + +qboolean ShotThroughGlass (trace_t *tr, gentity_t *target, vec3_t spot, int mask) +{ + gentity_t *hit = &g_entities[ tr->entityNum ]; + if(hit != target && EntIsGlass(hit)) + {//ok to shoot through breakable glass + int skip = hit->s.number; + vec3_t muzzle; + + VectorCopy(tr->endpos, muzzle); + trap_Trace (tr, muzzle, NULL, NULL, spot, skip, mask ); + return qtrue; + } + + return qfalse; +} + +/* +CanShoot +determine if NPC can directly target enemy + +this function does not check teams, invulnerability, notarget, etc.... + +Added: If can't shoot center, try head, if not, see if it's close enough to try anyway. +*/ +qboolean CanShoot ( gentity_t *ent, gentity_t *shooter ) +{ + trace_t tr; + vec3_t muzzle; + vec3_t spot, diff; + gentity_t *traceEnt; + + CalcEntitySpot( shooter, SPOT_WEAPON, muzzle ); + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); //FIXME preferred target locations for some weapons (feet for R/L) + + trap_Trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT ); + traceEnt = &g_entities[ tr.entityNum ]; + + // point blank, baby! + if (tr.startsolid && (shooter->NPC) && (shooter->NPC->touchedByPlayer) ) + { + traceEnt = shooter->NPC->touchedByPlayer; + } + + if ( ShotThroughGlass( &tr, ent, spot, MASK_SHOT ) ) + { + traceEnt = &g_entities[ tr.entityNum ]; + } + + // shot is dead on + if ( traceEnt == ent ) + { + return qtrue; + } +//MCG - Begin + else + {//ok, can't hit them in center, try their head + CalcEntitySpot( ent, SPOT_HEAD, spot ); + trap_Trace ( &tr, muzzle, NULL, NULL, spot, shooter->s.number, MASK_SHOT ); + traceEnt = &g_entities[ tr.entityNum ]; + if ( traceEnt == ent) + { + return qtrue; + } + } + + //Actually, we should just check to fire in dir we're facing and if it's close enough, + //and we didn't hit someone on our own team, shoot + VectorSubtract(spot, tr.endpos, diff); + if(VectorLength(diff) < random() * 32) + { + return qtrue; + } +//MCG - End + // shot would hit a non-client + if ( !traceEnt->client ) + { + return qfalse; + } + + // shot is blocked by another player + + // he's already dead, so go ahead + if ( traceEnt->health <= 0 ) + { + return qtrue; + } + + // don't deliberately shoot a teammate + if ( traceEnt->client && ( traceEnt->client->playerTeam == shooter->client->playerTeam ) ) + { + return qfalse; + } + + // he's just in the wrong place, go ahead + return qtrue; +} + + +/* +void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ) + +Added: hacks for scripted NPCs +*/ +void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ) +{ + // is he is already our enemy? + if ( other == NPC->enemy ) + return; + + if ( other->flags & FL_NOTARGET ) + return; + + // we already have an enemy and this guy is in our FOV, see if this guy would be better + if ( NPC->enemy && vis == VIS_FOV ) + { + if ( NPCInfo->enemyLastSeenTime - level.time < 2000 ) + { + return; + } + if ( enemyVisibility == VIS_UNKNOWN ) + { + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV ); + } + if ( enemyVisibility == VIS_FOV ) + { + return; + } + } + + if ( !NPC->enemy ) + {//only take an enemy if you don't have one yet + G_SetEnemy( NPC, other ); + } + + if ( vis == VIS_FOV ) + { + NPCInfo->enemyLastSeenTime = level.time; + VectorCopy( other->r.currentOrigin, NPCInfo->enemyLastSeenLocation ); + NPCInfo->enemyLastHeardTime = 0; + VectorClear( NPCInfo->enemyLastHeardLocation ); + } + else + { + NPCInfo->enemyLastSeenTime = 0; + VectorClear( NPCInfo->enemyLastSeenLocation ); + NPCInfo->enemyLastHeardTime = level.time; + VectorCopy( other->r.currentOrigin, NPCInfo->enemyLastHeardLocation ); + } +} + + +//========================================== +//MCG Added functions: +//========================================== + +/* +int NPC_AttackDebounceForWeapon (void) + +DOES NOT control how fast you can fire +Only makes you keep your weapon up after you fire + +*/ +int NPC_AttackDebounceForWeapon (void) +{ + switch ( NPC->client->ps.weapon ) + { +/* + case WP_BLASTER://scav rifle + return 1000; + break; + + case WP_BRYAR_PISTOL://prifle + return 3000; + break; + + case WP_SABER: + return 100; + break; + + + case WP_TRICORDER: + return 0;//tricorder + break; +*/ + case WP_SABER: + return 0; + break; + + /* + case WP_BOT_LASER: + + if ( g_spskill.integer == 0 ) + return 2000; + + if ( g_spskill.integer == 1 ) + return 1500; + + return 1000; + break; + */ + //rwwFIXMEFIXME: support + default: + return NPCInfo->burstSpacing;//was 100 by default + break; + } +} + +//FIXME: need a mindist for explosive weapons +float NPC_MaxDistSquaredForWeapon (void) +{ + if(NPCInfo->stats.shootDistance > 0) + {//overrides default weapon dist + return NPCInfo->stats.shootDistance * NPCInfo->stats.shootDistance; + } + + switch ( NPC->s.weapon ) + { + case WP_BLASTER://scav rifle + return 1024 * 1024;//should be shorter? + break; + + case WP_BRYAR_PISTOL://prifle + return 1024 * 1024; + break; + + /* + case WP_BLASTER_PISTOL://prifle + return 1024 * 1024; + break; + */ + + case WP_DISRUPTOR://disruptor + if ( NPCInfo->scriptFlags & SCF_ALT_FIRE ) + { + return ( 4096 * 4096 ); + } + else + { + return 1024 * 1024; + } + break; +/* + case WP_SABER: + return 1024 * 1024; + break; + + + case WP_TRICORDER: + return 0;//tricorder + break; +*/ + case WP_SABER: + if ( NPC->client && NPC->client->saber[0].blade[0].lengthMax ) + {//FIXME: account for whether enemy and I are heading towards each other! + return (NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5)*(NPC->client->saber[0].blade[0].lengthMax + NPC->r.maxs[0]*1.5); + } + else + { + return 48*48; + } + break; + + default: + return 1024 * 1024;//was 0 + break; + } +} + +/* +------------------------- +ValidEnemy +------------------------- +*/ + +qboolean ValidEnemy(gentity_t *ent) +{ + if ( ent == NULL ) + return qfalse; + + if ( ent == NPC ) + return qfalse; + + //if team_free, maybe everyone is an enemy? + //if ( !NPC->client->enemyTeam ) + // return qfalse; + + if ( !(ent->flags & FL_NOTARGET) ) + { + if( ent->health > 0 ) + { + if( !ent->client ) + { + return qtrue; + } + else if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + {//don't go after spectators + return qfalse; + } + else + { + int entTeam = TEAM_FREE; + if ( ent->NPC && ent->client ) + { + entTeam = ent->client->playerTeam; + } + else if ( ent->client ) + { + if ( ent->client->sess.sessionTeam == TEAM_BLUE ) + { + entTeam = NPCTEAM_PLAYER; + } + else if ( ent->client->sess.sessionTeam == TEAM_RED ) + { + entTeam = NPCTEAM_ENEMY; + } + else + { + entTeam = NPCTEAM_NEUTRAL; + } + } + if( entTeam == NPCTEAM_FREE + || NPC->client->enemyTeam == NPCTEAM_FREE + || entTeam == NPC->client->enemyTeam ) + { + if ( entTeam != NPC->client->playerTeam ) + { + return qtrue; + } + } + } + } + } + + return qfalse; +} + +qboolean NPC_EnemyTooFar(gentity_t *enemy, float dist, qboolean toShoot) +{ + vec3_t vec; + + + if ( !toShoot ) + {//Not trying to actually press fire button with this check + if ( NPC->client->ps.weapon == WP_SABER ) + {//Just have to get to him + return qfalse; + } + } + + + if(!dist) + { + VectorSubtract(NPC->r.currentOrigin, enemy->r.currentOrigin, vec); + dist = VectorLengthSquared(vec); + } + + if(dist > NPC_MaxDistSquaredForWeapon()) + return qtrue; + + return qfalse; +} + +/* +NPC_PickEnemy + +Randomly picks a living enemy from the specified team and returns it + +FIXME: For now, you MUST specify an enemy team + +If you specify choose closest, it will find only the closest enemy + +If you specify checkVis, it will return and enemy that is visible + +If you specify findPlayersFirst, it will try to find players first + +You can mix and match any of those options (example: find closest visible players first) + +FIXME: this should go through the snapshot and find the closest enemy +*/ +gentity_t *NPC_PickEnemy( gentity_t *closestTo, int enemyTeam, qboolean checkVis, qboolean findPlayersFirst, qboolean findClosest ) +{ + int num_choices = 0; + int choice[128];//FIXME: need a different way to determine how many choices? + gentity_t *newenemy = NULL; + gentity_t *closestEnemy = NULL; + int entNum; + vec3_t diff; + float relDist; + float bestDist = Q3_INFINITE; + qboolean failed = qfalse; + int visChecks = (CHECK_360|CHECK_FOV|CHECK_VISRANGE); + int minVis = VIS_FOV; + + if ( enemyTeam == NPCTEAM_NEUTRAL ) + { + return NULL; + } + + if ( NPCInfo->behaviorState == BS_STAND_AND_SHOOT || + NPCInfo->behaviorState == BS_HUNT_AND_KILL ) + {//Formations guys don't require inFov to pick up a target + //These other behavior states are active battle states and should not + //use FOV. FOV checks are for enemies who are patrolling, guarding, etc. + visChecks &= ~CHECK_FOV; + minVis = VIS_360; + } + + if( findPlayersFirst ) + {//try to find a player first + newenemy = &g_entities[0]; + if( newenemy->client && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW)) + { + if( newenemy->health > 0 ) + { + if( NPC_ValidEnemy( newenemy) )//enemyTeam == TEAM_PLAYER || newenemy->client->playerTeam == enemyTeam || ( enemyTeam == TEAM_PLAYER ) ) + {//FIXME: check for range and FOV or vis? + if( newenemy != NPC->lastEnemy ) + {//Make sure we're not just going back and forth here + if ( trap_InPVS(newenemy->r.currentOrigin, NPC->r.currentOrigin) ) + { + if(NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL) + { + if(!NPC->enemy) + { + if(!InVisrange(newenemy)) + { + failed = qtrue; + } + else if(NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV) + { + failed = qtrue; + } + } + } + + if ( !failed ) + { + VectorSubtract( closestTo->r.currentOrigin, newenemy->r.currentOrigin, diff ); + relDist = VectorLengthSquared(diff); + if ( newenemy->client->hiddenDist > 0 ) + { + if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist ) + { + //out of hidden range + if ( VectorLengthSquared( newenemy->client->hiddenDir ) ) + {//They're only hidden from a certain direction, check + float dot; + VectorNormalize( diff ); + dot = DotProduct( newenemy->client->hiddenDir, diff ); + if ( dot > 0.5 ) + {//I'm not looking in the right dir toward them to see them + failed = qtrue; + } + else + { + Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot ); + } + } + else + { + failed = qtrue; + } + } + else + { + Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist ); + } + } + + if(!failed) + { + if(findClosest) + { + if(relDist < bestDist) + { + if(!NPC_EnemyTooFar(newenemy, relDist, qfalse)) + { + if(checkVis) + { + if( NPC_CheckVisibility ( newenemy, visChecks ) == minVis ) + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + else + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + } + } + else if(!NPC_EnemyTooFar(newenemy, 0, qfalse)) + { + if(checkVis) + { + if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV ) + { + choice[num_choices++] = newenemy->s.number; + } + } + else + { + choice[num_choices++] = newenemy->s.number; + } + } + } + } + } + } + } + } + } + } + + if (findClosest && closestEnemy) + { + return closestEnemy; + } + + if (num_choices) + { + return &g_entities[ choice[rand() % num_choices] ]; + } + + /* + //FIXME: used to have an option to look *only* for the player... now...? Still need it? + if ( enemyTeam == TEAM_PLAYER ) + {//couldn't find the player + return NULL; + } + */ + + num_choices = 0; + bestDist = Q3_INFINITE; + closestEnemy = NULL; + + for ( entNum = 0; entNum < level.num_entities; entNum++ ) + { + newenemy = &g_entities[entNum]; + + if ( newenemy != NPC && (newenemy->client /*|| newenemy->svFlags & SVF_NONNPC_ENEMY*/) && !(newenemy->flags & FL_NOTARGET) && !(newenemy->s.eFlags & EF_NODRAW)) + { + if ( newenemy->health > 0 ) + { + if ( (newenemy->client && NPC_ValidEnemy( newenemy)) + || (!newenemy->client && newenemy->alliedTeam == enemyTeam) ) + {//FIXME: check for range and FOV or vis? + if ( NPC->client->playerTeam == NPCTEAM_PLAYER && enemyTeam == NPCTEAM_PLAYER ) + {//player allies turning on ourselves? How? + if ( newenemy->s.number ) + {//only turn on the player, not other player allies + continue; + } + } + + if ( newenemy != NPC->lastEnemy ) + {//Make sure we're not just going back and forth here + if(!trap_InPVS(newenemy->r.currentOrigin, NPC->r.currentOrigin)) + { + continue; + } + + if ( NPCInfo->behaviorState == BS_INVESTIGATE || NPCInfo->behaviorState == BS_PATROL ) + { + if ( !NPC->enemy ) + { + if ( !InVisrange( newenemy ) ) + { + continue; + } + else if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) != VIS_FOV ) + { + continue; + } + } + } + + VectorSubtract( closestTo->r.currentOrigin, newenemy->r.currentOrigin, diff ); + relDist = VectorLengthSquared(diff); + if ( newenemy->client && newenemy->client->hiddenDist > 0 ) + { + if( relDist > newenemy->client->hiddenDist*newenemy->client->hiddenDist ) + { + //out of hidden range + if ( VectorLengthSquared( newenemy->client->hiddenDir ) ) + {//They're only hidden from a certain direction, check + float dot; + + VectorNormalize( diff ); + dot = DotProduct( newenemy->client->hiddenDir, diff ); + if ( dot > 0.5 ) + {//I'm not looking in the right dir toward them to see them + continue; + } + else + { + Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDir %s targetDir %s dot %f\n", NPC->targetname, newenemy->targetname, vtos(newenemy->client->hiddenDir), vtos(diff), dot ); + } + } + else + { + continue; + } + } + else + { + Debug_Printf(&debugNPCAI, DEBUG_LEVEL_INFO, "%s saw %s trying to hide - hiddenDist %f\n", NPC->targetname, newenemy->targetname, newenemy->client->hiddenDist ); + } + } + + if ( findClosest ) + { + if ( relDist < bestDist ) + { + if ( !NPC_EnemyTooFar( newenemy, relDist, qfalse ) ) + { + if ( checkVis ) + { + //FIXME: NPCs need to be able to pick up other NPCs behind them, + //but for now, commented out because it was picking up enemies it shouldn't + //if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 ) + if ( NPC_CheckVisibility ( newenemy, visChecks ) == minVis ) + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + else + { + bestDist = relDist; + closestEnemy = newenemy; + } + } + } + } + else if ( !NPC_EnemyTooFar( newenemy, 0, qfalse ) ) + { + if ( checkVis ) + { + //if( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_FOV|CHECK_VISRANGE ) == VIS_FOV ) + if ( NPC_CheckVisibility ( newenemy, CHECK_360|CHECK_VISRANGE ) >= VIS_360 ) + { + choice[num_choices++] = newenemy->s.number; + } + } + else + { + choice[num_choices++] = newenemy->s.number; + } + } + } + } + } + } + } + + + if (findClosest) + {//FIXME: you can pick up an enemy around a corner this way. + return closestEnemy; + } + + if (!num_choices) + { + return NULL; + } + + return &g_entities[ choice[rand() % num_choices] ]; +} + +/* +gentity_t *NPC_PickAlly ( void ) + + Simply returns closest visible ally +*/ + +gentity_t *NPC_PickAlly ( qboolean facingEachOther, float range, qboolean ignoreGroup, qboolean movingOnly ) +{ + gentity_t *ally = NULL; + gentity_t *closestAlly = NULL; + int entNum; + vec3_t diff; + float relDist; + float bestDist = range; + + for ( entNum = 0; entNum < level.num_entities; entNum++ ) + { + ally = &g_entities[entNum]; + + if ( ally->client ) + { + if ( ally->health > 0 ) + { + if ( ally->client && ( ally->client->playerTeam == NPC->client->playerTeam || + NPC->client->playerTeam == NPCTEAM_ENEMY ) )// && ally->client->playerTeam == TEAM_DISGUISE ) ) ) + {//if on same team or if player is disguised as your team + if ( ignoreGroup ) + { + if ( ally == NPC->client->leader ) + { + //reject + continue; + } + if ( ally->client && ally->client->leader && ally->client->leader == NPC ) + { + //reject + continue; + } + } + + if(!trap_InPVS(ally->r.currentOrigin, NPC->r.currentOrigin)) + { + continue; + } + + if ( movingOnly && ally->client && NPC->client ) + {//They have to be moving relative to each other + if ( !DistanceSquared( ally->client->ps.velocity, NPC->client->ps.velocity ) ) + { + continue; + } + } + + VectorSubtract( NPC->r.currentOrigin, ally->r.currentOrigin, diff ); + relDist = VectorNormalize( diff ); + if ( relDist < bestDist ) + { + if ( facingEachOther ) + { + vec3_t vf; + float dot; + + AngleVectors( ally->client->ps.viewangles, vf, NULL, NULL ); + VectorNormalize(vf); + dot = DotProduct(diff, vf); + + if ( dot < 0.5 ) + {//Not facing in dir to me + continue; + } + //He's facing me, am I facing him? + AngleVectors( NPC->client->ps.viewangles, vf, NULL, NULL ); + VectorNormalize(vf); + dot = DotProduct(diff, vf); + + if ( dot > -0.5 ) + {//I'm not facing opposite of dir to me + continue; + } + //I am facing him + } + + if ( NPC_CheckVisibility ( ally, CHECK_360|CHECK_VISRANGE ) >= VIS_360 ) + { + bestDist = relDist; + closestAlly = ally; + } + } + } + } + } + } + + + return closestAlly; +} + +gentity_t *NPC_CheckEnemy( qboolean findNew, qboolean tooFarOk, qboolean setEnemy ) +{ + qboolean forcefindNew = qfalse; + gentity_t *closestTo; + gentity_t *newEnemy = NULL; + //FIXME: have a "NPCInfo->persistance" we can set to determine how long to try to shoot + //someone we can't hit? Rather than hard-coded 10? + + //FIXME they shouldn't recognize enemy's death instantly + + //TEMP FIX: + //if(NPC->enemy->client) + //{ + // NPC->enemy->health = NPC->enemy->client->ps.stats[STAT_HEALTH]; + //} + + if ( NPC->enemy ) + { + if ( !NPC->enemy->inuse )//|| NPC->enemy == NPC )//wtf? NPCs should never get mad at themselves! + { + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + } + } + + //if ( NPC->svFlags & SVF_IGNORE_ENEMIES ) + if (0) //rwwFIXMEFIXME: support for this flag + {//We're ignoring all enemies for now + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + return NULL; + } + + //rwwFIXMEFIXME: support for this flag + /* + if ( NPC->svFlags & SVF_LOCKEDENEMY ) + {//keep this enemy until dead + if ( NPC->enemy ) + { + if ( (!NPC->NPC && !(NPC->svFlags & SVF_NONNPC_ENEMY) ) || NPC->enemy->health > 0 ) + {//Enemy never had health (a train or info_not_null, etc) or did and is now dead (NPCs, turrets, etc) + return NULL; + } + } + NPC->svFlags &= ~SVF_LOCKEDENEMY; + } + */ + + if ( NPC->enemy ) + { + if ( NPC_EnemyTooFar(NPC->enemy, 0, qfalse) ) + { + if(findNew) + {//See if there is a close one and take it if so, else keep this one + forcefindNew = qtrue; + } + else if(!tooFarOk)//FIXME: don't need this extra bool any more + { + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + } + } + else if ( !trap_InPVS(NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ) ) + {//FIXME: should this be a line-of site check? + //FIXME: a lot of things check PVS AGAIN when deciding whether + //or not to shoot, redundant! + //Should we lose the enemy? + //FIXME: if lose enemy, run lostenemyscript + if ( NPC->enemy->client && NPC->enemy->client->hiddenDist ) + {//He ducked into shadow while we weren't looking + //Drop enemy and see if we should search for him + NPC_LostEnemyDecideChase(); + } + else + {//If we're not chasing him, we need to lose him + //NOTE: since we no longer have bStates, really, this logic doesn't work, so never give him up + + /* + switch( NPCInfo->behaviorState ) + { + case BS_HUNT_AND_KILL: + //Okay to lose PVS, we're chasing them + break; + case BS_RUN_AND_SHOOT: + //FIXME: only do this if !(NPCInfo->scriptFlags&SCF_CHASE_ENEMY) + //If he's not our goalEntity, we're running somewhere else, so lose him + if ( NPC->enemy != NPCInfo->goalEntity ) + { + G_ClearEnemy( NPC ); + } + break; + default: + //We're not chasing him, so lose him as an enemy + G_ClearEnemy( NPC ); + break; + } + */ + } + } + } + + if ( NPC->enemy ) + { + if ( NPC->enemy->health <= 0 || NPC->enemy->flags & FL_NOTARGET ) + { + if ( setEnemy ) + { + G_ClearEnemy( NPC ); + } + } + } + + closestTo = NPC; + //FIXME: check your defendEnt, if you have one, see if their enemy is different + //than yours, or, if they don't have one, pick the closest enemy to THEM? + if ( NPCInfo->defendEnt ) + {//Trying to protect someone + if ( NPCInfo->defendEnt->health > 0 ) + {//Still alive, We presume we're close to them, navigation should handle this? + if ( NPCInfo->defendEnt->enemy ) + {//They were shot or acquired an enemy + if ( NPC->enemy != NPCInfo->defendEnt->enemy ) + {//They have a different enemy, take it! + newEnemy = NPCInfo->defendEnt->enemy; + if ( setEnemy ) + { + G_SetEnemy( NPC, NPCInfo->defendEnt->enemy ); + } + } + } + else if ( NPC->enemy == NULL ) + {//We don't have an enemy, so find closest to defendEnt + closestTo = NPCInfo->defendEnt; + } + } + } + + if (!NPC->enemy || ( NPC->enemy && NPC->enemy->health <= 0 ) || forcefindNew ) + {//FIXME: NPCs that are moving after an enemy should ignore the can't hit enemy counter- that should only be for NPCs that are standing still + //NOTE: cantHitEnemyCounter >= 100 means we couldn't hit enemy for a full + // 10 seconds, so give up. This means even if we're chasing him, we would + // try to find another enemy after 10 seconds (assuming the cantHitEnemyCounter + // is allowed to increment in a chasing bState) + qboolean foundenemy = qfalse; + + if(!findNew) + { + if ( setEnemy ) + { + NPC->lastEnemy = NPC->enemy; + G_ClearEnemy(NPC); + } + return NULL; + } + + //If enemy dead or unshootable, look for others on out enemy's team + if ( NPC->client->enemyTeam != NPCTEAM_NEUTRAL ) + { + //NOTE: this only checks vis if can't hit enemy for 10 tries, which I suppose + // means they need to find one that in more than just PVS + //newenemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, (NPC->cantHitEnemyCounter > 10), qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET) + //For now, made it so you ALWAYS have to check VIS + newEnemy = NPC_PickEnemy( closestTo, NPC->client->enemyTeam, qtrue, qfalse, qtrue );//3rd parm was (NPC->enemyTeam == TEAM_STARFLEET) + if ( newEnemy ) + { + foundenemy = qtrue; + if ( setEnemy ) + { + G_SetEnemy( NPC, newEnemy ); + } + } + } + + if ( !forcefindNew ) + { + if ( !foundenemy ) + { + if ( setEnemy ) + { + NPC->lastEnemy = NPC->enemy; + G_ClearEnemy(NPC); + } + } + + NPC->cantHitEnemyCounter = 0; + } + //FIXME: if we can't find any at all, go into INdependant NPC AI, pursue and kill + } + + if ( NPC->enemy && NPC->enemy->client ) + { + if(NPC->enemy->client->playerTeam) + { +// assert( NPC->client->playerTeam != NPC->enemy->client->playerTeam); + if( NPC->client->playerTeam != NPC->enemy->client->playerTeam ) + { + NPC->client->enemyTeam = NPC->enemy->client->playerTeam; + } + } + } + return newEnemy; +} + +/* +------------------------- +NPC_ClearShot +------------------------- +*/ + +qboolean NPC_ClearShot( gentity_t *ent ) +{ + vec3_t muzzle; + trace_t tr; + + if ( ( NPC == NULL ) || ( ent == NULL ) ) + return qfalse; + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + // add aim error + // use weapon instead of specific npc types, although you could add certain npc classes if you wanted +// if ( NPC->client->playerTeam == TEAM_SCAVENGERS ) + if( NPC->s.weapon == WP_BLASTER /*|| NPC->s.weapon == WP_BLASTER_PISTOL*/ ) // any other guns to check for? + { + vec3_t mins = { -2, -2, -2 }; + vec3_t maxs = { 2, 2, 2 }; + + trap_Trace ( &tr, muzzle, mins, maxs, ent->r.currentOrigin, NPC->s.number, MASK_SHOT ); + } + else + { + trap_Trace ( &tr, muzzle, NULL, NULL, ent->r.currentOrigin, NPC->s.number, MASK_SHOT ); + } + + if ( tr.startsolid || tr.allsolid ) + { + return qfalse; + } + + if ( tr.entityNum == ent->s.number ) + return qtrue; + + return qfalse; +} + +/* +------------------------- +NPC_ShotEntity +------------------------- +*/ + +int NPC_ShotEntity( gentity_t *ent, vec3_t impactPos ) +{ + vec3_t muzzle; + vec3_t targ; + trace_t tr; + + if ( ( NPC == NULL ) || ( ent == NULL ) ) + return qfalse; + + if ( NPC->s.weapon == WP_THERMAL ) + {//thermal aims from slightly above head + //FIXME: what about low-angle shots, rolling the thermal under something? + vec3_t angles, forward, end; + + CalcEntitySpot( NPC, SPOT_HEAD, muzzle ); + VectorSet( angles, 0, NPC->client->ps.viewangles[1], 0 ); + AngleVectors( angles, forward, NULL, NULL ); + VectorMA( muzzle, 8, forward, end ); + end[2] += 24; + trap_Trace ( &tr, muzzle, vec3_origin, vec3_origin, end, NPC->s.number, MASK_SHOT ); + VectorCopy( tr.endpos, muzzle ); + } + else + { + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + } + CalcEntitySpot( ent, SPOT_CHEST, targ ); + + // add aim error + // use weapon instead of specific npc types, although you could add certain npc classes if you wanted +// if ( NPC->client->playerTeam == TEAM_SCAVENGERS ) + if( NPC->s.weapon == WP_BLASTER /*|| NPC->s.weapon == WP_BLASTER_PISTOL*/ ) // any other guns to check for? + { + vec3_t mins = { -2, -2, -2 }; + vec3_t maxs = { 2, 2, 2 }; + + trap_Trace ( &tr, muzzle, mins, maxs, targ, NPC->s.number, MASK_SHOT ); + } + else + { + trap_Trace ( &tr, muzzle, NULL, NULL, targ, NPC->s.number, MASK_SHOT ); + } + //FIXME: if using a bouncing weapon like the bowcaster, should we check the reflection of the wall, too? + if ( impactPos ) + {//they want to know *where* the hit would be, too + VectorCopy( tr.endpos, impactPos ); + } +/* // NPCs should be able to shoot even if the muzzle would be inside their target + if ( tr.startsolid || tr.allsolid ) + { + return ENTITYNUM_NONE; + } +*/ + return tr.entityNum; +} + +qboolean NPC_EvaluateShot( int hit, qboolean glassOK ) +{ + if ( !NPC->enemy ) + { + return qfalse; + } + + if ( hit == NPC->enemy->s.number || (&g_entities[hit] != NULL && (g_entities[hit].r.svFlags&SVF_GLASS_BRUSH)) ) + {//can hit enemy or will hit glass, so shoot anyway + return qtrue; + } + return qfalse; +} + +/* +NPC_CheckAttack + +Simply checks aggression and returns true or false +*/ + +qboolean NPC_CheckAttack (float scale) +{ + if(!scale) + scale = 1.0; + + if(((float)NPCInfo->stats.aggression) * scale < flrand(0, 4)) + { + return qfalse; + } + + if(NPCInfo->shotTime > level.time) + return qfalse; + + return qtrue; +} + +/* +NPC_CheckDefend + +Simply checks evasion and returns true or false +*/ + +qboolean NPC_CheckDefend (float scale) +{ + if(!scale) + scale = 1.0; + + if((float)(NPCInfo->stats.evasion) > random() * 4 * scale) + return qtrue; + + return qfalse; +} + + +//NOTE: BE SURE TO CHECK PVS BEFORE THIS! +qboolean NPC_CheckCanAttack (float attack_scale, qboolean stationary) +{ + vec3_t delta, forward; + vec3_t angleToEnemy; + vec3_t hitspot, muzzle, diff, enemy_org;//, enemy_head; + float distanceToEnemy; + qboolean attack_ok = qfalse; +// qboolean duck_ok = qfalse; + qboolean dead_on = qfalse; + float aim_off; + float max_aim_off = 128 - (16 * (float)NPCInfo->stats.aim); + trace_t tr; + gentity_t *traceEnt = NULL; + + if(NPC->enemy->flags & FL_NOTARGET) + { + return qfalse; + } + + //FIXME: only check to see if should duck if that provides cover from the + //enemy!!! + if(!attack_scale) + { + attack_scale = 1.0; + } + //Yaw to enemy + CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_org ); + NPC_AimWiggle( enemy_org ); + + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + + VectorSubtract (enemy_org, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + distanceToEnemy = VectorNormalize(delta); + + NPC->NPC->desiredYaw = angleToEnemy[YAW]; + NPC_UpdateFiringAngles(qfalse, qtrue); + + if( NPC_EnemyTooFar(NPC->enemy, distanceToEnemy*distanceToEnemy, qtrue) ) + {//Too far away? Do not attack + return qfalse; + } + + if(client->ps.weaponTime > 0) + {//already waiting for a shot to fire + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles(qtrue, qfalse); + return qfalse; + } + + if(NPCInfo->scriptFlags & SCF_DONT_FIRE) + { + return qfalse; + } + + NPCInfo->enemyLastVisibility = enemyVisibility; + //See if they're in our FOV and we have a clear shot to them + enemyVisibility = NPC_CheckVisibility ( NPC->enemy, CHECK_360|CHECK_FOV);////CHECK_PVS| + + if(enemyVisibility >= VIS_FOV) + {//He's in our FOV + + attack_ok = qtrue; + //CalcEntitySpot( NPC->enemy, SPOT_HEAD, enemy_head); + + //Check to duck + if ( NPC->enemy->client ) + { + if ( NPC->enemy->enemy == NPC ) + { + if ( NPC->enemy->client->buttons & BUTTON_ATTACK ) + {//FIXME: determine if enemy fire angles would hit me or get close + if ( NPC_CheckDefend( 1.0 ) )//FIXME: Check self-preservation? Health? + {//duck and don't shoot + attack_ok = qfalse; + ucmd.upmove = -127; + } + } + } + } + + if(attack_ok) + { + //are we gonna hit him + //NEW: use actual forward facing + AngleVectors( client->ps.viewangles, forward, NULL, NULL ); + VectorMA( muzzle, distanceToEnemy, forward, hitspot ); + trap_Trace( &tr, muzzle, NULL, NULL, hitspot, NPC->s.number, MASK_SHOT ); + ShotThroughGlass( &tr, NPC->enemy, hitspot, MASK_SHOT ); + /* + //OLD: trace regardless of facing + trap_Trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT ); + ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT); + */ + + traceEnt = &g_entities[tr.entityNum]; + + /* + if( traceEnt != NPC->enemy &&//FIXME: if someone on our team is in the way, suggest that they duck if possible + (!traceEnt || !traceEnt->client || !NPC->client->enemyTeam || NPC->client->enemyTeam != traceEnt->client->playerTeam) ) + {//no, so shoot for somewhere between the head and torso + //NOTE: yes, I know this looks weird, but it works + enemy_org[0] += 0.3*Q_flrand(NPC->enemy->r.mins[0], NPC->enemy->r.maxs[0]); + enemy_org[1] += 0.3*Q_flrand(NPC->enemy->r.mins[1], NPC->enemy->r.maxs[1]); + enemy_org[2] -= NPC->enemy->r.maxs[2]*Q_flrand(0.0f, 1.0f); + + attack_scale *= 0.75; + trap_Trace ( &tr, muzzle, NULL, NULL, enemy_org, NPC->s.number, MASK_SHOT ); + ShotThroughGlass(&tr, NPC->enemy, enemy_org, MASK_SHOT); + traceEnt = &g_entities[tr.entityNum]; + } + */ + + VectorCopy( tr.endpos, hitspot ); + + if( traceEnt == NPC->enemy || (traceEnt->client && NPC->client->enemyTeam && NPC->client->enemyTeam == traceEnt->client->playerTeam) ) + { + dead_on = qtrue; + } + else + { + attack_scale *= 0.5; + if(NPC->client->playerTeam) + { + if(traceEnt && traceEnt->client && traceEnt->client->playerTeam) + { + if(NPC->client->playerTeam == traceEnt->client->playerTeam) + {//Don't shoot our own team + attack_ok = qfalse; + } + } + } + } + } + + if( attack_ok ) + { + //ok, now adjust pitch aim + VectorSubtract (hitspot, muzzle, delta); + vectoangles ( delta, angleToEnemy ); + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles(qtrue, qfalse); + + if( !dead_on ) + {//We're not going to hit him directly, try a suppressing fire + //see if where we're going to shoot is too far from his origin + if(traceEnt && (traceEnt->health <= 30 || EntIsGlass(traceEnt))) + {//easy to kill - go for it + //if(traceEnt->die == ExplodeDeath_Wait && traceEnt->splashDamage) + if (0) //rwwFIXMEFIXME: ExplodeDeath_Wait? + {//going to explode, don't shoot if close to self + VectorSubtract(NPC->r.currentOrigin, traceEnt->r.currentOrigin, diff); + if(VectorLengthSquared(diff) < traceEnt->splashRadius*traceEnt->splashRadius) + {//Too close to shoot! + attack_ok = qfalse; + } + else + {//Hey, it might kill him, do it! + attack_scale *= 2;// + } + } + } + else + { + AngleVectors (client->ps.viewangles, forward, NULL, NULL); + VectorMA ( muzzle, distanceToEnemy, forward, hitspot); + VectorSubtract(hitspot, enemy_org, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off)//FIXME: use aim value to allow poor aim? + { + attack_scale *= 0.75; + //see if where we're going to shoot is too far from his head + VectorSubtract(hitspot, enemy_org, diff); + aim_off = VectorLength(diff); + if(aim_off > random() * max_aim_off) + { + attack_ok = qfalse; + } + } + attack_scale *= (max_aim_off - aim_off + 1)/max_aim_off; + } + } + } + } + else + {//Update pitch anyway + NPC->NPC->desiredPitch = angleToEnemy[PITCH]; + NPC_UpdateFiringAngles(qtrue, qfalse); + } + + if( attack_ok ) + { + if( NPC_CheckAttack( attack_scale )) + {//check aggression to decide if we should shoot + enemyVisibility = VIS_SHOOT; + WeaponThink(qtrue); + } + else + attack_ok = qfalse; + } + + return attack_ok; +} +//======================================================================================== +//OLD id-style hunt and kill +//======================================================================================== +/* +IdealDistance + +determines what the NPC's ideal distance from it's enemy should +be in the current situation +*/ +float IdealDistance ( gentity_t *self ) +{ + float ideal; + + ideal = 225 - 20 * NPCInfo->stats.aggression; + switch ( NPC->s.weapon ) + { + case WP_ROCKET_LAUNCHER: + ideal += 200; + break; + + case WP_THERMAL: + ideal += 50; + break; + +/* case WP_TRICORDER: + ideal = 0; + break; +*/ + case WP_SABER: + case WP_BRYAR_PISTOL: +// case WP_BLASTER_PISTOL: + case WP_BLASTER: + default: + break; + } + + return ideal; +} + +/*QUAKED point_combat (0.7 0 0.7) (-16 -16 -24) (16 16 32) DUCK FLEE INVESTIGATE SQUAD LEAN SNIPE +NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + +DUCK - NPC will duck and fire from this point, NOT IMPLEMENTED? +FLEE - Will choose this point when running +INVESTIGATE - Will look here if a sound is heard near it +SQUAD - NOT IMPLEMENTED +LEAN - Lean-type cover, NOT IMPLEMENTED +SNIPE - Snipers look for these first, NOT IMPLEMENTED +*/ + +void SP_point_combat( gentity_t *self ) +{ + if(level.numCombatPoints >= MAX_COMBAT_POINTS) + { +#ifndef FINAL_BUILD + Com_Printf(S_COLOR_RED"ERROR: Too many combat points, limit is %d\n", MAX_COMBAT_POINTS); +#endif + G_FreeEntity(self); + return; + } + + self->s.origin[2] += 0.125; + G_SetOrigin(self, self->s.origin); + trap_LinkEntity(self); + + if ( G_CheckInSolid( self, qtrue ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: combat point at %s in solid!\n", vtos(self->r.currentOrigin) ); +#endif + } + + VectorCopy( self->r.currentOrigin, level.combatPoints[level.numCombatPoints].origin ); + + level.combatPoints[level.numCombatPoints].flags = self->spawnflags; + level.combatPoints[level.numCombatPoints].occupied = qfalse; + + level.numCombatPoints++; + + G_FreeEntity(self); +} + +void CP_FindCombatPointWaypoints( void ) +{ + int i; + + for ( i = 0; i < level.numCombatPoints; i++ ) + { + level.combatPoints[i].waypoint = NAV_FindClosestWaypointForPoint2( level.combatPoints[i].origin ); +#ifndef FINAL_BUILD + if ( level.combatPoints[i].waypoint == WAYPOINT_NONE ) + { + Com_Printf( S_COLOR_RED"ERROR: Combat Point at %s has no waypoint!\n", vtos(level.combatPoints[i].origin) ); + } +#endif + } +} + + +/* +------------------------- +NPC_CollectCombatPoints +------------------------- +*/ +typedef struct +{ + float dist; + int index; +} combatPt_t; +static int NPC_CollectCombatPoints( const vec3_t origin, const float radius, combatPt_t *points, const int flags ) +{ + float radiusSqr = (radius*radius); + float distance; + float bestDistance = Q3_INFINITE; + int bestPoint = 0; + int numPoints = 0; + int i; + + //Collect all nearest + for ( i = 0; i < level.numCombatPoints; i++ ) + { + if (numPoints >= MAX_COMBAT_POINTS) + { + break; + } + + //Must be vacant + if ( level.combatPoints[i].occupied == (int) qtrue ) + continue; + + //If we want a duck space, make sure this is one + if ( ( flags & CP_DUCK ) && ( level.combatPoints[i].flags & CPF_DUCK ) ) + continue; + + //If we want a duck space, make sure this is one + if ( ( flags & CP_FLEE ) && ( level.combatPoints[i].flags & CPF_FLEE ) ) + continue; + + ///Make sure this is an investigate combat point + if ( ( flags & CP_INVESTIGATE ) && ( level.combatPoints[i].flags & CPF_INVESTIGATE ) ) + continue; + + //Squad points are only valid if we're looking for them + if ( ( level.combatPoints[i].flags & CPF_SQUAD ) && ( ( flags & CP_SQUAD ) == qfalse ) ) + continue; + + if ( flags&CP_NO_PVS ) + {//must not be within PVS of mu current origin + if ( trap_InPVS( origin, level.combatPoints[i].origin ) ) + { + continue; + } + } + + if ( flags&CP_HORZ_DIST_COLL ) + { + distance = DistanceHorizontalSquared( origin, level.combatPoints[i].origin ); + } + else + { + distance = DistanceSquared( origin, level.combatPoints[i].origin ); + } + + if ( distance < radiusSqr ) + { + if (distance < bestDistance) + { + bestDistance = distance; + bestPoint = numPoints; + } + + points[numPoints].dist = distance; + points[numPoints].index = i; + numPoints++; + } + } + + return numPoints;//bestPoint; +} + +/* +------------------------- +NPC_FindCombatPoint +------------------------- +*/ + +#define MIN_AVOID_DOT 0.75f +#define MIN_AVOID_DISTANCE 128 +#define MIN_AVOID_DISTANCE_SQUARED ( MIN_AVOID_DISTANCE * MIN_AVOID_DISTANCE ) +#define CP_COLLECT_RADIUS 512.0f + +int NPC_FindCombatPoint( const vec3_t position, const vec3_t avoidPosition, vec3_t enemyPosition, const int flags, const float avoidDist, const int ignorePoint ) +{ + combatPt_t points[MAX_COMBAT_POINTS]; + int best = -1, cost, bestCost = Q3_INFINITE, waypoint = WAYPOINT_NONE; + float dist; + trace_t tr; + float collRad = CP_COLLECT_RADIUS; + int numPoints; + int j = 0; + float modifiedAvoidDist = avoidDist; + + if ( modifiedAvoidDist <= 0 ) + { + modifiedAvoidDist = MIN_AVOID_DISTANCE_SQUARED; + } + else + { + modifiedAvoidDist *= modifiedAvoidDist; + } + + if ( (flags & CP_HAS_ROUTE) || (flags & CP_NEAREST) ) + {//going to be doing macro nav tests + if ( NPC->waypoint == WAYPOINT_NONE ) + { + waypoint = NAV_GetNearestNode( NPC, NPC->lastWaypoint ); + } + else + { + waypoint = NPC->waypoint; + } + } + + //Collect our nearest points + if ( flags & CP_NO_PVS ) + {//much larger radius since most will be dropped? + collRad = CP_COLLECT_RADIUS*4; + } + numPoints = NPC_CollectCombatPoints( enemyPosition, collRad, points, flags );//position + + + for ( j = 0; j < numPoints; j++ ) + { + //const int i = (*cpi).second; + const int i = points[j].index; + const float pdist = points[j].dist; + + //Must not be one we want to ignore + if ( i == ignorePoint ) + continue; + + //FIXME: able to mark certain ones as too dangerous to go to for now? Like a tripmine/thermal/detpack is near or something? + //If we need a cover point, check this point + if ( ( flags & CP_COVER ) && ( NPC_ClearLOS( level.combatPoints[i].origin, enemyPosition ) == qtrue ) )//Used to use NPC->enemy + continue; + + //Need a clear LOS to our target... and be within shot range to enemy position (FIXME: make this a separate CS_ flag? and pass in a range?) + if ( flags & CP_CLEAR ) + { + if ( NPC_ClearLOS3( level.combatPoints[i].origin, NPC->enemy ) == qfalse ) + { + continue; + } + if ( NPC->s.weapon == WP_THERMAL ) + {//horizontal + dist = DistanceHorizontalSquared( level.combatPoints[i].origin, NPC->enemy->r.currentOrigin ); + } + else + {//actual + dist = DistanceSquared( level.combatPoints[i].origin, NPC->enemy->r.currentOrigin ); + } + if ( dist > (NPCInfo->stats.visrange*NPCInfo->stats.visrange) ) + { + continue; + } + } + + //Avoid this position? + if ( ( flags & CP_AVOID ) && ( DistanceSquared( level.combatPoints[i].origin, position ) < modifiedAvoidDist ) )//was using MIN_AVOID_DISTANCE_SQUARED, not passed in modifiedAvoidDist + continue; + + //Try to find a point closer to the enemy than where we are + if ( flags & CP_APPROACH_ENEMY ) + { + if ( flags&CP_HORZ_DIST_COLL ) + { + if ( pdist > DistanceHorizontalSquared( position, enemyPosition ) ) + { + continue; + } + } + else + { + if ( pdist > DistanceSquared( position, enemyPosition ) ) + { + continue; + } + } + } + //Try to find a point farther from the enemy than where we are + if ( flags & CP_RETREAT ) + { + if ( flags&CP_HORZ_DIST_COLL ) + { + if ( pdist < DistanceHorizontalSquared( position, enemyPosition ) ) + {//it's closer, don't use it + continue; + } + } + else + { + if ( pdist < DistanceSquared( position, enemyPosition ) ) + {//it's closer, don't use it + continue; + } + } + } + + //We want a point on other side of the enemy from current pos + if ( flags & CP_FLANK ) + { + vec3_t eDir2Me, eDir2CP; + float dot; + + VectorSubtract( position, enemyPosition, eDir2Me ); + VectorNormalize( eDir2Me ); + + VectorSubtract( level.combatPoints[i].origin, enemyPosition, eDir2CP ); + VectorNormalize( eDir2CP ); + + dot = DotProduct( eDir2Me, eDir2CP ); + + //Not far enough behind enemy from current pos + if ( dot >= 0.4 ) + continue; + } + + //See if we're trying to avoid our enemy + //FIXME: this needs to check for the waypoint you'll be taking to get to that combat point + if ( flags & CP_AVOID_ENEMY ) + { + vec3_t eDir, gDir; + vec3_t wpOrg; + float dot; + + VectorSubtract( position, enemyPosition, eDir ); + VectorNormalize( eDir ); + + /* + NAV_FindClosestWaypointForEnt( NPC, level.combatPoints[i].waypoint ); + if ( NPC->waypoint != WAYPOINT_NONE && NPC->waypoint != level.combatPoints[i].waypoint ) + { + trap_Nav_GetNodePosition( NPC->waypoint, wpOrg ); + } + else + */ + { + VectorCopy( level.combatPoints[i].origin, wpOrg ); + } + VectorSubtract( position, wpOrg, gDir ); + VectorNormalize( gDir ); + + dot = DotProduct( gDir, eDir ); + + //Don't want to run at enemy + if ( dot >= MIN_AVOID_DOT ) + continue; + + //Can't be too close to the enemy + if ( DistanceSquared( wpOrg, enemyPosition ) < modifiedAvoidDist ) + continue; + } + + //Okay, now make sure it's not blocked + trap_Trace( &tr, level.combatPoints[i].origin, NPC->r.mins, NPC->r.maxs, level.combatPoints[i].origin, NPC->s.number, NPC->clipmask ); + if ( tr.allsolid || tr.startsolid ) + { + continue; + } + + //we must have a route to the combat point + if ( flags & CP_HAS_ROUTE ) + { + /* + if ( level.combatPoints[i].waypoint == WAYPOINT_NONE ) + { + level.combatPoints[i].waypoint = NAV_FindClosestWaypointForPoint( level.combatPoints[i].origin ); + } + */ + + if ( waypoint == WAYPOINT_NONE || level.combatPoints[i].waypoint == WAYPOINT_NONE || trap_Nav_GetBestNodeAltRoute2( waypoint, level.combatPoints[i].waypoint, NODE_NONE ) == WAYPOINT_NONE ) + {//can't possibly have a route to any OR can't possibly have a route to this one OR don't have a route to this one + if ( !NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, level.combatPoints[i].origin, NPC->clipmask, ENTITYNUM_NONE ) ) + {//don't even have a clear straight path to this one + continue; + } + } + } + + //We want the one with the shortest path from current pos + if ( flags & CP_NEAREST && waypoint != WAYPOINT_NONE && level.combatPoints[i].waypoint != WAYPOINT_NONE ) + { + cost = trap_Nav_GetPathCost( waypoint, level.combatPoints[i].waypoint ); + if ( cost < bestCost ) + { + bestCost = cost; + best = i; + } + continue; + } + + //we want the combat point closest to the enemy + //if ( flags & CP_CLOSEST ) + //they are sorted by this distance, so the first one to get this far is the closest + return i; + } + + return best; +} + +/* +------------------------- +NPC_FindSquadPoint +------------------------- +*/ + +int NPC_FindSquadPoint( vec3_t position ) +{ + float dist, nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE; + int nearestPoint = -1; + int i; + + //float playerDist = DistanceSquared( g_entities[0].currentOrigin, NPC->r.currentOrigin ); + + for ( i = 0; i < level.numCombatPoints; i++ ) + { + //Squad points are only valid if we're looking for them + if ( ( level.combatPoints[i].flags & CPF_SQUAD ) == qfalse ) + continue; + + //Must be vacant + if ( level.combatPoints[i].occupied == qtrue ) + continue; + + dist = DistanceSquared( position, level.combatPoints[i].origin ); + + //The point cannot take us past the player + //if ( dist > ( playerDist * DotProduct( dirToPlayer, playerDir ) ) ) //FIXME: Retain this + // continue; + + //See if this is closer than the others + if ( dist < nearestDist ) + { + nearestPoint = i; + nearestDist = dist; + } + } + + return nearestPoint; +} + +/* +------------------------- +NPC_ReserveCombatPoint +------------------------- +*/ + +qboolean NPC_ReserveCombatPoint( int combatPointID ) +{ + //Make sure it's valid + if ( combatPointID > level.numCombatPoints ) + return qfalse; + + //Make sure it's not already occupied + if ( level.combatPoints[combatPointID].occupied ) + return qfalse; + + //Reserve it + level.combatPoints[combatPointID].occupied = qtrue; + + return qtrue; +} + +/* +------------------------- +NPC_FreeCombatPoint +------------------------- +*/ + +qboolean NPC_FreeCombatPoint( int combatPointID, qboolean failed ) +{ + if ( failed ) + {//remember that this one failed for us + NPCInfo->lastFailedCombatPoint = combatPointID; + } + //Make sure it's valid + if ( combatPointID > level.numCombatPoints ) + return qfalse; + + //Make sure it's currently occupied + if ( level.combatPoints[combatPointID].occupied == qfalse ) + return qfalse; + + //Free it + level.combatPoints[combatPointID].occupied = qfalse; + + return qtrue; +} + +/* +------------------------- +NPC_SetCombatPoint +------------------------- +*/ + +qboolean NPC_SetCombatPoint( int combatPointID ) +{ + //Free a combat point if we already have one + if ( NPCInfo->combatPoint != -1 ) + { + NPC_FreeCombatPoint( NPCInfo->combatPoint, qfalse ); + } + + if ( NPC_ReserveCombatPoint( combatPointID ) == qfalse ) + return qfalse; + + NPCInfo->combatPoint = combatPointID; + + return qtrue; +} + +extern qboolean CheckItemCanBePickedUpByNPC( gentity_t *item, gentity_t *pickerupper ); +gentity_t *NPC_SearchForWeapons( void ) +{ + gentity_t *found = g_entities, *bestFound = NULL; + float dist, bestDist = Q3_INFINITE; + int i; +// for ( found = g_entities; found < &g_entities[globals.num_entities] ; found++) + for ( i = 0; iinuse ) +// { +// continue; +// } + if(!g_entities[i].inuse) + continue; + + found=&g_entities[i]; + + //FIXME: Also look for ammo_racks that have weapons on them? + if ( found->s.eType != ET_ITEM ) + { + continue; + } + if ( found->item->giType != IT_WEAPON ) + { + continue; + } + if ( found->s.eFlags & EF_NODRAW ) + { + continue; + } + if ( CheckItemCanBePickedUpByNPC( found, NPC ) ) + { + if ( trap_InPVS( found->r.currentOrigin, NPC->r.currentOrigin ) ) + { + dist = DistanceSquared( found->r.currentOrigin, NPC->r.currentOrigin ); + if ( dist < bestDist ) + { + if ( !trap_Nav_GetBestPathBetweenEnts( NPC, found, NF_CLEAR_PATH ) + || trap_Nav_GetBestNodeAltRoute2( NPC->waypoint, found->waypoint, NODE_NONE ) == WAYPOINT_NONE ) + {//can't possibly have a route to any OR can't possibly have a route to this one OR don't have a route to this one + if ( NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, found->r.currentOrigin, NPC->clipmask, ENTITYNUM_NONE ) ) + {//have a clear straight path to this one + bestDist = dist; + bestFound = found; + } + } + else + {//can nav to it + bestDist = dist; + bestFound = found; + } + } + } + } + } + + return bestFound; +} + +void NPC_SetPickUpGoal( gentity_t *foundWeap ) +{ + vec3_t org; + + //NPCInfo->goalEntity = foundWeap; + VectorCopy( foundWeap->r.currentOrigin, org ); + org[2] += 24 - (foundWeap->r.mins[2]*-1);//adjust the origin so that I am on the ground + NPC_SetMoveGoal( NPC, org, foundWeap->r.maxs[0]*0.75, qfalse, -1, foundWeap ); + NPCInfo->tempGoal->waypoint = foundWeap->waypoint; + NPCInfo->tempBehavior = BS_DEFAULT; + NPCInfo->squadState = SQUAD_TRANSITION; +} + +void NPC_CheckGetNewWeapon( void ) +{ + if ( NPC->s.weapon == WP_NONE && NPC->enemy ) + {//if running away because dropped weapon... + if ( NPCInfo->goalEntity + && NPCInfo->goalEntity == NPCInfo->tempGoal + && NPCInfo->goalEntity->enemy + && !NPCInfo->goalEntity->enemy->inuse ) + {//maybe was running at a weapon that was picked up + NPCInfo->goalEntity = NULL; + } + if ( TIMER_Done( NPC, "panic" ) && NPCInfo->goalEntity == NULL ) + {//need a weapon, any lying around? + gentity_t *foundWeap = NPC_SearchForWeapons(); + if ( foundWeap ) + {//try to nav to it + /* + if ( !trap_Nav_GetBestPathBetweenEnts( NPC, foundWeap, NF_CLEAR_PATH ) + || trap_Nav_GetBestNodeAltRoute( NPC->waypoint, foundWeap->waypoint ) == WAYPOINT_NONE ) + {//can't possibly have a route to any OR can't possibly have a route to this one OR don't have a route to this one + if ( !NAV_ClearPathToPoint( NPC, NPC->r.mins, NPC->r.maxs, foundWeap->r.currentOrigin, NPC->clipmask, ENTITYNUM_NONE ) ) + {//don't even have a clear straight path to this one + } + else + { + NPC_SetPickUpGoal( foundWeap ); + } + } + else + */ + { + NPC_SetPickUpGoal( foundWeap ); + } + } + } + } +} + +void NPC_AimAdjust( int change ) +{ + if ( !TIMER_Exists( NPC, "aimDebounce" ) ) + { + int debounce = 500+(3-g_spskill.integer)*100; + TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) ); + //int debounce = 1000+(3-g_spskill.integer)*500; + //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) ); + return; + } + if ( TIMER_Done( NPC, "aimDebounce" ) ) + { + int debounce; + + NPCInfo->currentAim += change; + if ( NPCInfo->currentAim > NPCInfo->stats.aim ) + {//can never be better than max aim + NPCInfo->currentAim = NPCInfo->stats.aim; + } + else if ( NPCInfo->currentAim < -30 ) + {//can never be worse than this + NPCInfo->currentAim = -30; + } + + //Com_Printf( "%s new aim = %d\n", NPC->NPC_type, NPCInfo->currentAim ); + + debounce = 500+(3-g_spskill.integer)*100; + TIMER_Set( NPC, "aimDebounce", Q_irand( debounce,debounce+1000 ) ); + //int debounce = 1000+(3-g_spskill.integer)*500; + //TIMER_Set( NPC, "aimDebounce", Q_irand( debounce, debounce+2000 ) ); + } +} + +void G_AimSet( gentity_t *self, int aim ) +{ + if ( self->NPC ) + { + int debounce; + + self->NPC->currentAim = aim; + //Com_Printf( "%s new aim = %d\n", self->NPC_type, self->NPC->currentAim ); + + debounce = 500+(3-g_spskill.integer)*100; + TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+1000 ) ); + // int debounce = 1000+(3-g_spskill.integer)*500; + // TIMER_Set( self, "aimDebounce", Q_irand( debounce,debounce+2000 ) ); + } +} diff --git a/code/game/NPC_goal.c b/code/game/NPC_goal.c new file mode 100644 index 0000000..6b2066a --- /dev/null +++ b/code/game/NPC_goal.c @@ -0,0 +1,267 @@ +//b_goal.cpp +#include "b_local.h" +#include "../icarus/Q3_Interface.h" + +extern qboolean FlyingCreature( gentity_t *ent ); +/* +SetGoal +*/ + +void SetGoal( gentity_t *goal, float rating ) +{ + NPCInfo->goalEntity = goal; +// NPCInfo->goalEntityNeed = rating; + NPCInfo->goalTime = level.time; +// NAV_ClearLastRoute(NPC); + if ( goal ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_SetGoal: %s @ %s (%f)\n", goal->classname, vtos( goal->currentOrigin), rating ); + } + else + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_SetGoal: NONE\n" ); + } +} + + +/* +NPC_SetGoal +*/ + +void NPC_SetGoal( gentity_t *goal, float rating ) +{ + if ( goal == NPCInfo->goalEntity ) + { + return; + } + + if ( !goal ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_ERROR, "NPC_SetGoal: NULL goal\n" ); + return; + } + + if ( goal->client ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_ERROR, "NPC_SetGoal: goal is a client\n" ); + return; + } + + if ( NPCInfo->goalEntity ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_SetGoal: push %s\n", NPCInfo->goalEntity->classname ); + NPCInfo->lastGoalEntity = NPCInfo->goalEntity; +// NPCInfo->lastGoalEntityNeed = NPCInfo->goalEntityNeed; + } + + SetGoal( goal, rating ); +} + + +/* +NPC_ClearGoal +*/ + +void NPC_ClearGoal( void ) +{ + gentity_t *goal; + + if ( !NPCInfo->lastGoalEntity ) + { + SetGoal( NULL, 0.0 ); + return; + } + + goal = NPCInfo->lastGoalEntity; + NPCInfo->lastGoalEntity = NULL; +// NAV_ClearLastRoute(NPC); + if ( goal->inuse && !(goal->s.eFlags & EF_NODRAW) ) + { +// Debug_NPCPrintf( NPC, debugNPCAI, DEBUG_LEVEL_INFO, "NPC_ClearGoal: pop %s\n", goal->classname ); + SetGoal( goal, 0 );//, NPCInfo->lastGoalEntityNeed + return; + } + + SetGoal( NULL, 0.0 ); +} + +/* +------------------------- +G_BoundsOverlap +------------------------- +*/ + +qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2) +{//NOTE: flush up against counts as overlapping + if(mins1[0]>maxs2[0]) + return qfalse; + + if(mins1[1]>maxs2[1]) + return qfalse; + + if(mins1[2]>maxs2[2]) + return qfalse; + + if(maxs1[0]goalTime = level.time; + +//MCG - Begin + NPCInfo->aiFlags &= ~NPCAI_MOVING; + ucmd.forwardmove = 0; + //Return that the goal was reached + trap_ICARUS_TaskIDComplete( NPC, TID_MOVE_NAV ); +//MCG - End +} +/* +ReachedGoal + +id removed checks against waypoints and is now checking surfaces +*/ +//qboolean NAV_HitNavGoal( vec3_t point, vec3_t mins, vec3_t maxs, gentity_t *goal, qboolean flying ); +qboolean ReachedGoal( gentity_t *goal ) +{ + //FIXME: For script waypoints, need a special check +/* + int goalWpNum; + vec3_t vec; + //vec3_t angles; + float delta; + + if ( goal->flags & FL_NAVGOAL ) + {//waypoint_navgoal + return NAV_HitNavGoal( NPC->currentOrigin, NPC->mins, NPC->maxs, goal, FlyingCreature( NPC ) ); + } + + if ( goal == NPCInfo->tempGoal && !(goal->flags & FL_NAVGOAL)) + {//MUST touch waypoints, even if moving to it + //This is odd, it just checks to see if they are on the same + //surface and the tempGoal in in the FOV - does NOT check distance! + // are we on same surface? + + //FIXME: NPC->waypoint reset every frame, need to find it first + //Should we do that here? (Still will do it only once per frame) + if ( NPC->waypoint >= 0 && NPC->waypoint < num_waypoints ) + { + goalWpNum = NAV_FindWaypointAt ( goal->currentOrigin ); + if ( NPC->waypoint != goalWpNum ) + { + return qfalse; + } + } + + VectorSubtract ( NPCInfo->tempGoal->currentOrigin, NPC->currentOrigin, vec); + //Who cares if it's in our FOV?! + /* + // is it in our FOV + vectoangles ( vec, angles ); + delta = AngleDelta ( NPC->client->ps.viewangles[YAW], angles[YAW] ); + if ( fabs ( delta ) > NPCInfo->stats.hfov ) + { + return qfalse; + } + */ + + /* + //If in the same waypoint as tempGoal, we're there, right? + if ( goal->waypoint >= 0 && goal->waypoint < num_waypoints ) + { + if ( NPC->waypoint == goal->waypoint ) + { + return qtrue; + } + } + */ + +/* + if ( VectorLengthSquared( vec ) < (64*64) ) + {//Close enough + return qtrue; + } + + return qfalse; + } +*/ + if ( NPCInfo->aiFlags & NPCAI_TOUCHED_GOAL ) + { + NPCInfo->aiFlags &= ~NPCAI_TOUCHED_GOAL; + return qtrue; + } +/* + if ( goal->s.eFlags & EF_NODRAW ) + { + goalWpNum = NAV_FindWaypointAt( goal->currentOrigin ); + if ( NPC->waypoint == goalWpNum ) + { + return qtrue; + } + return qfalse; + } + + if(goal->client && goal->health <= 0) + {//trying to get to dead guy + goalWpNum = NAV_FindWaypointAt( goal->currentOrigin ); + if ( NPC->waypoint == goalWpNum ) + { + VectorSubtract(NPC->currentOrigin, goal->currentOrigin, vec); + vec[2] = 0; + delta = VectorLengthSquared(vec); + if(delta <= 800) + {//with 20-30 of other guy's origin + return qtrue; + } + } + } +*/ + return NAV_HitNavGoal( NPC->r.currentOrigin, NPC->r.mins, NPC->r.maxs, goal->r.currentOrigin, NPCInfo->goalRadius, FlyingCreature( NPC ) ); +} + +/* +static gentity_t *UpdateGoal( void ) + +Id removed a lot of shit here... doesn't seem to handle waypoints independantly of goalentity + +In fact, doesn't seem to be any waypoint info on entities at all any more? + +MCG - Since goal is ALWAYS goalEntity, took out a lot of sending goal entity pointers around for no reason +*/ + +gentity_t *UpdateGoal( void ) +{ + gentity_t *goal; + + if ( !NPCInfo->goalEntity ) + { + return NULL; + } + + if ( !NPCInfo->goalEntity->inuse ) + {//Somehow freed it, but didn't clear it + NPC_ClearGoal(); + return NULL; + } + + goal = NPCInfo->goalEntity; + + if ( ReachedGoal( goal ) ) + { + NPC_ReachedGoal(); + goal = NULL;//so they don't keep trying to move to it + }//else if fail, need to tell script so? + + return goal; +} diff --git a/code/game/NPC_misc.c b/code/game/NPC_misc.c new file mode 100644 index 0000000..69c3f32 --- /dev/null +++ b/code/game/NPC_misc.c @@ -0,0 +1,73 @@ +// +// NPC_misc.cpp +// +#include "b_local.h" +#include "q_shared.h" + +/* +Debug_Printf +*/ +void Debug_Printf (vmCvar_t *cv, int debugLevel, char *fmt, ...) +{ + char *color; + va_list argptr; + char msg[1024]; + + if (cv->value < debugLevel) + return; + + if (debugLevel == DEBUG_LEVEL_DETAIL) + color = S_COLOR_WHITE; + else if (debugLevel == DEBUG_LEVEL_INFO) + color = S_COLOR_GREEN; + else if (debugLevel == DEBUG_LEVEL_WARNING) + color = S_COLOR_YELLOW; + else if (debugLevel == DEBUG_LEVEL_ERROR) + color = S_COLOR_RED; + else + color = S_COLOR_RED; + + va_start (argptr,fmt); + vsprintf (msg, fmt, argptr); + va_end (argptr); + + Com_Printf("%s%5i:%s", color, level.time, msg); +} + + +/* +Debug_NPCPrintf +*/ +void Debug_NPCPrintf (gentity_t *printNPC, vmCvar_t *cv, int debugLevel, char *fmt, ...) +{ + int color; + va_list argptr; + char msg[1024]; + + if (cv->value < debugLevel) + { + return; + } + +// if ( debugNPCName.string[0] && Q_stricmp( debugNPCName.string, printNPC->targetname) != 0 ) +// { +// return; +// } + + if (debugLevel == DEBUG_LEVEL_DETAIL) + color = COLOR_WHITE; + else if (debugLevel == DEBUG_LEVEL_INFO) + color = COLOR_GREEN; + else if (debugLevel == DEBUG_LEVEL_WARNING) + color = COLOR_YELLOW; + else if (debugLevel == DEBUG_LEVEL_ERROR) + color = COLOR_RED; + else + color = COLOR_RED; + + va_start (argptr,fmt); + vsprintf (msg, fmt, argptr); + va_end (argptr); + + Com_Printf ("%c%c%5i (%s) %s", Q_COLOR_ESCAPE, color, level.time, printNPC->targetname, msg); +} diff --git a/code/game/NPC_move.c b/code/game/NPC_move.c new file mode 100644 index 0000000..3714afa --- /dev/null +++ b/code/game/NPC_move.c @@ -0,0 +1,505 @@ +// +// NPC_move.cpp +// +#include "b_local.h" +#include "g_nav.h" +#include "anims.h" + +void G_Cylinder( vec3_t start, vec3_t end, float radius, vec3_t color ); + +qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2); +int NAV_Steer( gentity_t *self, vec3_t dir, float distance ); +extern int GetTime ( int lastTime ); + +navInfo_t frameNavInfo; +extern qboolean FlyingCreature( gentity_t *ent ); + +#include "../namespace_begin.h" +extern qboolean PM_InKnockDown( playerState_t *ps ); +#include "../namespace_end.h" + +/* +------------------------- +NPC_ClearPathToGoal +------------------------- +*/ + +qboolean NPC_ClearPathToGoal( vec3_t dir, gentity_t *goal ) +{ + trace_t trace; + float radius, dist, tFrac; + + //FIXME: What does do about area portals? THIS IS BROKEN + //if ( gi.inPVS( NPC->r.currentOrigin, goal->r.currentOrigin ) == qfalse ) + // return qfalse; + + //Look ahead and see if we're clear to move to our goal position + if ( NAV_CheckAhead( NPC, goal->r.currentOrigin, &trace, ( NPC->clipmask & ~CONTENTS_BODY )|CONTENTS_BOTCLIP ) ) + { + //VectorSubtract( goal->r.currentOrigin, NPC->r.currentOrigin, dir ); + return qtrue; + } + + if (!FlyingCreature(NPC)) + { + //See if we're too far above + if ( fabs( NPC->r.currentOrigin[2] - goal->r.currentOrigin[2] ) > 48 ) + return qfalse; + } + + //This is a work around + radius = ( NPC->r.maxs[0] > NPC->r.maxs[1] ) ? NPC->r.maxs[0] : NPC->r.maxs[1]; + dist = Distance( NPC->r.currentOrigin, goal->r.currentOrigin ); + tFrac = 1.0f - ( radius / dist ); + + if ( trace.fraction >= tFrac ) + return qtrue; + + //See if we're looking for a navgoal + if ( goal->flags & FL_NAVGOAL ) + { + //Okay, didn't get all the way there, let's see if we got close enough: + if ( NAV_HitNavGoal( trace.endpos, NPC->r.mins, NPC->r.maxs, goal->r.currentOrigin, NPCInfo->goalRadius, FlyingCreature( NPC ) ) ) + { + //VectorSubtract(goal->r.currentOrigin, NPC->r.currentOrigin, dir); + return qtrue; + } + } + + return qfalse; +} + +/* +------------------------- +NPC_CheckCombatMove +------------------------- +*/ + +ID_INLINE qboolean NPC_CheckCombatMove( void ) +{ + //return NPCInfo->combatMove; + if ( ( NPCInfo->goalEntity && NPC->enemy && NPCInfo->goalEntity == NPC->enemy ) || ( NPCInfo->combatMove ) ) + { + return qtrue; + } + + if ( NPCInfo->goalEntity && NPCInfo->watchTarget ) + { + if ( NPCInfo->goalEntity != NPCInfo->watchTarget ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +------------------------- +NPC_LadderMove +------------------------- +*/ + +static void NPC_LadderMove( vec3_t dir ) +{ + //FIXME: this doesn't guarantee we're facing ladder + //ALSO: Need to be able to get off at top + //ALSO: Need to play an anim + //ALSO: Need transitionary anims? + + if ( ( dir[2] > 0 ) || ( dir[2] < 0 && NPC->client->ps.groundEntityNum == ENTITYNUM_NONE ) ) + { + //Set our movement direction + ucmd.upmove = (dir[2] > 0) ? 127 : -127; + + //Don't move around on XY + ucmd.forwardmove = ucmd.rightmove = 0; + } +} + +/* +------------------------- +NPC_GetMoveInformation +------------------------- +*/ + +ID_INLINE qboolean NPC_GetMoveInformation( vec3_t dir, float *distance ) +{ + //NOTENOTE: Use path stacks! + + //Make sure we have somewhere to go + if ( NPCInfo->goalEntity == NULL ) + return qfalse; + + //Get our move info + VectorSubtract( NPCInfo->goalEntity->r.currentOrigin, NPC->r.currentOrigin, dir ); + *distance = VectorNormalize( dir ); + + VectorCopy( NPCInfo->goalEntity->r.currentOrigin, NPCInfo->blockedDest ); + + return qtrue; +} + +/* +------------------------- +NAV_GetLastMove +------------------------- +*/ + +void NAV_GetLastMove( navInfo_t *info ) +{ + *info = frameNavInfo; +} + +/* +------------------------- +NPC_GetMoveDirection +------------------------- +*/ + +qboolean NPC_GetMoveDirection( vec3_t out, float *distance ) +{ + vec3_t angles; + + //Clear the struct + memset( &frameNavInfo, 0, sizeof( frameNavInfo ) ); + + //Get our movement, if any + if ( NPC_GetMoveInformation( frameNavInfo.direction, &frameNavInfo.distance ) == qfalse ) + return qfalse; + + //Setup the return value + *distance = frameNavInfo.distance; + + //For starters + VectorCopy( frameNavInfo.direction, frameNavInfo.pathDirection ); + + //If on a ladder, move appropriately + if ( NPC->watertype & CONTENTS_LADDER ) + { + NPC_LadderMove( frameNavInfo.direction ); + return qtrue; + } + + //Attempt a straight move to goal + if ( NPC_ClearPathToGoal( frameNavInfo.direction, NPCInfo->goalEntity ) == qfalse ) + { + //See if we're just stuck + if ( NAV_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) + { + //Can't reach goal, just face + vectoangles( frameNavInfo.direction, angles ); + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + return qfalse; + } + + frameNavInfo.flags |= NIF_MACRO_NAV; + } + + //Avoid any collisions on the way + if ( NAV_AvoidCollision( NPC, NPCInfo->goalEntity, &frameNavInfo ) == qfalse ) + { + //FIXME: Emit a warning, this is a worst case scenario + //FIXME: if we have a clear path to our goal (exluding bodies), but then this + // check (against bodies only) fails, shouldn't we fall back + // to macro navigation? Like so: + if ( !(frameNavInfo.flags&NIF_MACRO_NAV) ) + {//we had a clear path to goal and didn't try macro nav, but can't avoid collision so try macro nav here + //See if we're just stuck + if ( NAV_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) + { + //Can't reach goal, just face + vectoangles( frameNavInfo.direction, angles ); + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + return qfalse; + } + + frameNavInfo.flags |= NIF_MACRO_NAV; + } + } + + //Setup the return values + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + + return qtrue; +} + +/* +------------------------- +NPC_GetMoveDirectionAltRoute +------------------------- +*/ +extern int NAVNEW_MoveToGoal( gentity_t *self, navInfo_t *info ); +extern qboolean NAVNEW_AvoidCollision( gentity_t *self, gentity_t *goal, navInfo_t *info, qboolean setBlockedInfo, int blockedMovesLimit ); +qboolean NPC_GetMoveDirectionAltRoute( vec3_t out, float *distance, qboolean tryStraight ) +{ + vec3_t angles; + + NPCInfo->aiFlags &= ~NPCAI_BLOCKED; + + //Clear the struct + memset( &frameNavInfo, 0, sizeof( frameNavInfo ) ); + + //Get our movement, if any + if ( NPC_GetMoveInformation( frameNavInfo.direction, &frameNavInfo.distance ) == qfalse ) + return qfalse; + + //Setup the return value + *distance = frameNavInfo.distance; + + //For starters + VectorCopy( frameNavInfo.direction, frameNavInfo.pathDirection ); + + //If on a ladder, move appropriately + if ( NPC->watertype & CONTENTS_LADDER ) + { + NPC_LadderMove( frameNavInfo.direction ); + return qtrue; + } + + //Attempt a straight move to goal + if ( !tryStraight || NPC_ClearPathToGoal( frameNavInfo.direction, NPCInfo->goalEntity ) == qfalse ) + {//blocked + //Can't get straight to goal, use macro nav + if ( NAVNEW_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) + { + //Can't reach goal, just face + vectoangles( frameNavInfo.direction, angles ); + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + return qfalse; + } + //else we are on our way + frameNavInfo.flags |= NIF_MACRO_NAV; + } + else + {//we have no architectural problems, see if there are ents inthe way and try to go around them + //not blocked + if ( d_altRoutes.integer ) + {//try macro nav + navInfo_t tempInfo; + memcpy( &tempInfo, &frameNavInfo, sizeof( tempInfo ) ); + if ( NAVNEW_AvoidCollision( NPC, NPCInfo->goalEntity, &tempInfo, qtrue, 5 ) == qfalse ) + {//revert to macro nav + //Can't get straight to goal, dump tempInfo and use macro nav + if ( NAVNEW_MoveToGoal( NPC, &frameNavInfo ) == WAYPOINT_NONE ) + { + //Can't reach goal, just face + vectoangles( frameNavInfo.direction, angles ); + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + return qfalse; + } + //else we are on our way + frameNavInfo.flags |= NIF_MACRO_NAV; + } + else + {//otherwise, either clear or can avoid + memcpy( &frameNavInfo, &tempInfo, sizeof( frameNavInfo ) ); + } + } + else + {//OR: just give up + if ( NAVNEW_AvoidCollision( NPC, NPCInfo->goalEntity, &frameNavInfo, qtrue, 30 ) == qfalse ) + {//give up + return qfalse; + } + } + } + + //Setup the return values + VectorCopy( frameNavInfo.direction, out ); + *distance = frameNavInfo.distance; + + return qtrue; +} + +void G_UcmdMoveForDir( gentity_t *self, usercmd_t *cmd, vec3_t dir ) +{ + vec3_t forward, right; + float fDot, rDot; + + AngleVectors( self->r.currentAngles, forward, right, NULL ); + + dir[2] = 0; + VectorNormalize( dir ); + //NPCs cheat and store this directly because converting movement into a ucmd loses precision + VectorCopy( dir, self->client->ps.moveDir ); + + fDot = DotProduct( forward, dir ) * 127.0f; + rDot = DotProduct( right, dir ) * 127.0f; + //Must clamp this because DotProduct is not guaranteed to return a number within -1 to 1, and that would be bad when we're shoving this into a signed byte + if ( fDot > 127.0f ) + { + fDot = 127.0f; + } + if ( fDot < -127.0f ) + { + fDot = -127.0f; + } + if ( rDot > 127.0f ) + { + rDot = 127.0f; + } + if ( rDot < -127.0f ) + { + rDot = -127.0f; + } + cmd->forwardmove = floor(fDot); + cmd->rightmove = floor(rDot); + + /* + vec3_t wishvel; + for ( int i = 0 ; i < 3 ; i++ ) + { + wishvel[i] = forward[i]*cmd->forwardmove + right[i]*cmd->rightmove; + } + VectorNormalize( wishvel ); + if ( !VectorCompare( wishvel, dir ) ) + { + Com_Printf( "PRECISION LOSS: %s != %s\n", vtos(wishvel), vtos(dir) ); + } + */ +} + +/* +------------------------- +NPC_MoveToGoal + + Now assumes goal is goalEntity, was no reason for it to be otherwise +------------------------- +*/ +#if AI_TIMERS +extern int navTime; +#endif// AI_TIMERS +qboolean NPC_MoveToGoal( qboolean tryStraight ) +{ + float distance; + vec3_t dir; + +#if AI_TIMERS + int startTime = GetTime(0); +#endif// AI_TIMERS + //If taking full body pain, don't move + if ( PM_InKnockDown( &NPC->client->ps ) || ( ( NPC->s.legsAnim >= BOTH_PAIN1 ) && ( NPC->s.legsAnim <= BOTH_PAIN18 ) ) ) + { + return qtrue; + } + + /* + if( NPC->s.eFlags & EF_LOCKED_TO_WEAPON ) + {//If in an emplaced gun, never try to navigate! + return qtrue; + } + */ + //rwwFIXMEFIXME: emplaced support + + //FIXME: if can't get to goal & goal is a target (enemy), try to find a waypoint that has line of sight to target, at least? + //Get our movement direction +#if 1 + if ( NPC_GetMoveDirectionAltRoute( dir, &distance, tryStraight ) == qfalse ) +#else + if ( NPC_GetMoveDirection( dir, &distance ) == qfalse ) +#endif + return qfalse; + + NPCInfo->distToGoal = distance; + + //Convert the move to angles + vectoangles( dir, NPCInfo->lastPathAngles ); + if ( (ucmd.buttons&BUTTON_WALKING) ) + { + NPC->client->ps.speed = NPCInfo->stats.walkSpeed; + } + else + { + NPC->client->ps.speed = NPCInfo->stats.runSpeed; + } + + //FIXME: still getting ping-ponging in certain cases... !!! Nav/avoidance error? WTF???!!! + //If in combat move, then move directly towards our goal + if ( NPC_CheckCombatMove() ) + {//keep current facing + G_UcmdMoveForDir( NPC, &ucmd, dir ); + } + else + {//face our goal + //FIXME: strafe instead of turn if change in dir is small and temporary + NPCInfo->desiredPitch = 0.0f; + NPCInfo->desiredYaw = AngleNormalize360( NPCInfo->lastPathAngles[YAW] ); + + //Pitch towards the goal and also update if flying or swimming + if ( (NPC->client->ps.eFlags2&EF2_FLYING) )//moveType == MT_FLYSWIM ) + { + NPCInfo->desiredPitch = AngleNormalize360( NPCInfo->lastPathAngles[PITCH] ); + + if ( dir[2] ) + { + float scale = (dir[2] * distance); + if ( scale > 64 ) + { + scale = 64; + } + else if ( scale < -64 ) + { + scale = -64; + } + NPC->client->ps.velocity[2] = scale; + //NPC->client->ps.velocity[2] = (dir[2] > 0) ? 64 : -64; + } + } + + //Set any final info + ucmd.forwardmove = 127; + } + +#if AI_TIMERS + navTime += GetTime( startTime ); +#endif// AI_TIMERS + return qtrue; +} + +/* +------------------------- +void NPC_SlideMoveToGoal( void ) + + Now assumes goal is goalEntity, if want to use tempGoal, you set that before calling the func +------------------------- +*/ +qboolean NPC_SlideMoveToGoal( void ) +{ + float saveYaw = NPC->client->ps.viewangles[YAW]; + qboolean ret; + + NPCInfo->combatMove = qtrue; + + ret = NPC_MoveToGoal( qtrue ); + + NPCInfo->desiredYaw = saveYaw; + + return ret; +} + + +/* +------------------------- +NPC_ApplyRoff +------------------------- +*/ + +void NPC_ApplyRoff(void) +{ + BG_PlayerStateToEntityState( &NPC->client->ps, &NPC->s, qfalse ); + //VectorCopy ( NPC->r.currentOrigin, NPC->lastOrigin ); + //rwwFIXMEFIXME: Any significance to this? + + // use the precise origin for linking + trap_LinkEntity(NPC); +} diff --git a/code/game/NPC_reactions.c b/code/game/NPC_reactions.c new file mode 100644 index 0000000..c0f5979 --- /dev/null +++ b/code/game/NPC_reactions.c @@ -0,0 +1,1125 @@ +//NPC_reactions.cpp +#include "b_local.h" +#include "anims.h" +#include "w_saber.h" + +extern qboolean G_CheckForStrongAttackMomentum( gentity_t *self ); +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +extern void G_SoundOnEnt( gentity_t *ent, soundChannel_t channel, const char *soundPath ); +extern void cgi_S_StartSound( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ); +extern qboolean Q3_TaskIDPending( gentity_t *ent, taskID_t taskType ); +extern qboolean NPC_CheckLookTarget( gentity_t *self ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); +extern qboolean Jedi_WaitingAmbush( gentity_t *self ); +extern void Jedi_Ambush( gentity_t *self ); +extern qboolean NPC_SomeoneLookingAtMe(gentity_t *ent); + +#include "../namespace_begin.h" +extern qboolean BG_SaberInSpecialAttack( int anim ); +extern qboolean PM_SpinningSaberAnim( int anim ); +extern qboolean PM_SpinningAnim( int anim ); +extern qboolean PM_InKnockDown( playerState_t *ps ); +extern qboolean BG_FlippingAnim( int anim ); +extern qboolean PM_RollingAnim( int anim ); +extern qboolean PM_InCartwheel( int anim ); +extern qboolean BG_CrouchAnim( int anim ); +#include "../namespace_end.h" + +extern int teamLastEnemyTime[]; +extern int killPlayerTimer; + +//float g_crosshairEntDist = Q3_INFINITE; +//int g_crosshairSameEntTime = 0; +//int g_crosshairEntNum = ENTITYNUM_NONE; +//int g_crosshairEntTime = 0; + +/* +------------------------- +NPC_CheckAttacker +------------------------- +*/ + +static void NPC_CheckAttacker( gentity_t *other, int mod ) +{ + //FIXME: I don't see anything in here that would stop teammates from taking a teammate + // as an enemy. Ideally, there would be code before this to prevent that from + // happening, but that is presumptuous. + + //valid ent - FIXME: a VALIDENT macro would be nice here + if ( !other ) + return; + + if ( other == NPC ) + return; + + if ( !other->inuse ) + return; + + //Don't take a target that doesn't want to be + if ( other->flags & FL_NOTARGET ) + return; + +// if ( NPC->svFlags & SVF_LOCKEDENEMY ) +// {//IF LOCKED, CANNOT CHANGE ENEMY!!!!! +// return; +// } + //rwwFIXMEFIXME: support this + + //If we haven't taken a target, just get mad + if ( NPC->enemy == NULL )//was using "other", fixed to NPC + { + G_SetEnemy( NPC, other ); + return; + } + + //we have an enemy, see if he's dead + if ( NPC->enemy->health <= 0 ) + { + G_ClearEnemy( NPC ); + G_SetEnemy( NPC, other ); + return; + } + + //Don't take the same enemy again + if ( other == NPC->enemy ) + return; + + if ( NPC->client->ps.weapon == WP_SABER ) + {//I'm a jedi + if ( mod == MOD_SABER ) + {//I was hit by a saber FIXME: what if this was a thrown saber? + //always switch to this enemy if I'm a jedi and hit by another saber + G_ClearEnemy( NPC ); + G_SetEnemy( NPC, other ); + return; + } + } + //Special case player interactions + if ( other == &g_entities[0] ) + { + //Account for the skill level to skew the results + float luckThreshold; + + switch ( g_spskill.integer ) + { + //Easiest difficulty, mild chance of picking up the player + case 0: + luckThreshold = 0.9f; + break; + + //Medium difficulty, half-half chance of picking up the player + case 1: + luckThreshold = 0.5f; + break; + + //Hardest difficulty, always turn on attacking player + case 2: + default: + luckThreshold = 0.0f; + break; + } + + //Randomly pick up the target + if ( random() > luckThreshold ) + { + G_ClearEnemy( other ); + other->enemy = NPC; + } + + return; + } +} + +void NPC_SetPainEvent( gentity_t *self ) +{ + if ( !self->NPC || !(self->NPC->aiFlags&NPCAI_DIE_ON_IMPACT) ) + { + // no more borg + // if( self->client->playerTeam != TEAM_BORG ) + // { + //if ( !Q3_TaskIDPending( self, TID_CHAN_VOICE ) ) + if (!trap_ICARUS_TaskIDPending(self, TID_CHAN_VOICE) && self->client) + { + //G_AddEvent( self, EV_PAIN, floor((float)self->health/self->max_health*100.0f) ); + G_AddEvent( self, EV_PAIN, floor((float)self->health/self->client->ps.stats[STAT_MAX_HEALTH]*100.0f) ); + //rwwFIXMEFIXME: Do this properly? + } + // } + } +} + +/* +------------------------- +NPC_GetPainChance +------------------------- +*/ + +float NPC_GetPainChance( gentity_t *self, int damage ) +{ + float pain_chance; + if ( !self->enemy ) + {//surprised, always take pain + return 1.0f; + } + + if (!self->client) + { + return 1.0f; + } + + //if ( damage > self->max_health/2.0f ) + if (damage > self->client->ps.stats[STAT_MAX_HEALTH]/2.0f) + { + return 1.0f; + } + + pain_chance = (float)(self->client->ps.stats[STAT_MAX_HEALTH]-self->health)/(self->client->ps.stats[STAT_MAX_HEALTH]*2.0f) + (float)damage/(self->client->ps.stats[STAT_MAX_HEALTH]/2.0f); + switch ( g_spskill.integer ) + { + case 0: //easy + //return 0.75f; + break; + + case 1://med + pain_chance *= 0.5f; + //return 0.35f; + break; + + case 2://hard + default: + pain_chance *= 0.1f; + //return 0.05f; + break; + } + //Com_Printf( "%s: %4.2f\n", self->NPC_type, pain_chance ); + return pain_chance; +} + +/* +------------------------- +NPC_ChoosePainAnimation +------------------------- +*/ + +#define MIN_PAIN_TIME 200 + +extern int G_PickPainAnim( gentity_t *self, vec3_t point, int damage, int hitLoc ); +void NPC_ChoosePainAnimation( gentity_t *self, gentity_t *other, vec3_t point, int damage, int mod, int hitLoc, int voiceEvent ) +{ + int pain_anim = -1; + float pain_chance; + + //If we've already taken pain, then don't take it again + if ( level.time < self->painDebounceTime && /*mod != MOD_ELECTROCUTE &&*/ mod != MOD_MELEE ) //rwwFIXMEFIXME: MOD_ELECTROCUTE + {//FIXME: if hit while recoving from losing a saber lock, we should still play a pain anim? + return; + } + + if ( self->s.weapon == WP_THERMAL && self->client->ps.weaponTime > 0 ) + {//don't interrupt thermal throwing anim + return; + } + else if ( self->client->NPC_class == CLASS_GALAKMECH ) + { + if ( hitLoc == HL_GENERIC1 ) + {//hit the antenna! + pain_chance = 1.0f; + // self->s.powerups |= ( 1 << PW_SHOCKED ); + // self->client->ps.powerups[PW_SHOCKED] = level.time + Q_irand( 500, 2500 ); + //rwwFIXMEFIXME: support for this + } + // else if ( self->client->ps.powerups[PW_GALAK_SHIELD] ) + // {//shield up + // return; + // } + //rwwFIXMEFIXME: and this + else if ( self->health > 200 && damage < 100 ) + {//have a *lot* of health + pain_chance = 0.05f; + } + else + {//the lower my health and greater the damage, the more likely I am to play a pain anim + pain_chance = (200.0f-self->health)/100.0f + damage/50.0f; + } + } + else if ( self->client && self->client->playerTeam == NPCTEAM_PLAYER && other && !other->s.number ) + {//ally shot by player always complains + pain_chance = 1.1f; + } + else + { + if ( other && other->s.weapon == WP_SABER || /*mod == MOD_ELECTROCUTE ||*/ mod == MOD_CRUSH/*FIXME:MOD_FORCE_GRIP*/ ) + { + pain_chance = 1.0f;//always take pain from saber + } + else if ( mod == MOD_MELEE ) + {//higher in rank (skill) we are, less likely we are to be fazed by a punch + pain_chance = 1.0f - ((RANK_CAPTAIN-self->NPC->rank)/(float)RANK_CAPTAIN); + } + else if ( self->client->NPC_class == CLASS_PROTOCOL ) + { + pain_chance = 1.0f; + } + else + { + pain_chance = NPC_GetPainChance( self, damage ); + } + if ( self->client->NPC_class == CLASS_DESANN ) + { + pain_chance *= 0.5f; + } + } + + //See if we're going to flinch + if ( random() < pain_chance ) + { + int animLength; + + //Pick and play our animation + if ( self->client->ps.fd.forceGripBeingGripped < level.time ) + {//not being force-gripped or force-drained + if ( /*G_CheckForStrongAttackMomentum( self ) //rwwFIXMEFIXME: Is this needed? + ||*/ PM_SpinningAnim( self->client->ps.legsAnim ) + || BG_SaberInSpecialAttack( self->client->ps.torsoAnim ) + || PM_InKnockDown( &self->client->ps ) + || PM_RollingAnim( self->client->ps.legsAnim ) + || (BG_FlippingAnim( self->client->ps.legsAnim )&&!PM_InCartwheel( self->client->ps.legsAnim )) ) + {//strong attacks, rolls, knockdowns, flips and spins cannot be interrupted by pain + } + else + {//play an anim + int parts; + + if ( self->client->NPC_class == CLASS_GALAKMECH ) + {//only has 1 for now + //FIXME: never plays this, it seems... + pain_anim = BOTH_PAIN1; + } + else if ( mod == MOD_MELEE ) + { + pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 ); + } + else if ( self->s.weapon == WP_SABER ) + {//temp HACK: these are the only 2 pain anims that look good when holding a saber + pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN2, BOTH_PAIN3 ); + } + /* + else if ( mod != MOD_ELECTROCUTE ) + { + pain_anim = G_PickPainAnim( self, point, damage, hitLoc ); + } + */ + + if ( pain_anim == -1 ) + { + pain_anim = BG_PickAnim( self->localAnimIndex, BOTH_PAIN1, BOTH_PAIN18 ); + } + self->client->ps.fd.saberAnimLevel = FORCE_LEVEL_1;//next attack must be a quick attack + self->client->ps.saberMove = LS_READY;//don't finish whatever saber move you may have been in + parts = SETANIM_BOTH; + if ( BG_CrouchAnim( self->client->ps.legsAnim ) || PM_InCartwheel( self->client->ps.legsAnim ) ) + { + parts = SETANIM_LEGS; + } + + if (pain_anim != -1) + { + NPC_SetAnim( self, parts, pain_anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } + if ( voiceEvent != -1 ) + { + G_AddVoiceEvent( self, voiceEvent, Q_irand( 2000, 4000 ) ); + } + else + { + NPC_SetPainEvent( self ); + } + } + else + { + G_AddVoiceEvent( self, Q_irand(EV_CHOKE1, EV_CHOKE3), 0 ); + } + + //Setup the timing for it + /* + if ( mod == MOD_ELECTROCUTE ) + { + self->painDebounceTime = level.time + 4000; + } + */ + animLength = bgAllAnims[self->localAnimIndex].anims[pain_anim].numFrames * fabs((float)(bgHumanoidAnimations[pain_anim].frameLerp)); + + self->painDebounceTime = level.time + animLength; + self->client->ps.weaponTime = 0; + } +} + +/* +=============== +NPC_Pain +=============== +*/ +void NPC_Pain(gentity_t *self, gentity_t *attacker, int damage) +{ + team_t otherTeam = TEAM_FREE; + int voiceEvent = -1; + gentity_t *other = attacker; + int mod = gPainMOD; + int hitLoc = gPainHitLoc; + vec3_t point; + + VectorCopy(gPainPoint, point); + + if ( self->NPC == NULL ) + return; + + if ( other == NULL ) + return; + + //or just remove ->pain in player_die? + if ( self->client->ps.pm_type == PM_DEAD ) + return; + + if ( other == self ) + return; + + //MCG: Ignore damage from your own team for now + if ( other->client ) + { + otherTeam = other->client->playerTeam; + // if ( otherTeam == TEAM_DISGUISE ) + // { + // otherTeam = TEAM_PLAYER; + // } + } + + if ( self->client->playerTeam + && other->client + && otherTeam == self->client->playerTeam + /* && (!player->client->ps.viewEntity || other->s.number != player->client->ps.viewEntity)*/) + //rwwFIXMEFIXME: Will need modification when player controllable npcs are done + {//hit by a teammate + if ( other != self->enemy && self != other->enemy ) + {//we weren't already enemies + if ( self->enemy || other->enemy + + //|| (other->s.number&&other->s.number!=player->client->ps.viewEntity) + //rwwFIXMEFIXME: same + + /*|| (!other->s.number&&Q_irand( 0, 3 ))*/ ) + {//if one of us actually has an enemy already, it's okay, just an accident OR wasn't hit by player or someone controlled by player OR player hit ally and didn't get 25% chance of getting mad (FIXME:accumulate anger+base on diff?) + //FIXME: player should have to do a certain amount of damage to ally or hit them several times to make them mad + //Still run pain and flee scripts + if ( self->client && self->NPC ) + {//Run any pain instructions + if ( self->health <= (self->client->ps.stats[STAT_MAX_HEALTH]/3) && G_ActivateBehavior(self, BSET_FLEE) ) + { + + } + else// if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) ) + { + G_ActivateBehavior(self, BSET_PAIN); + } + } + if ( damage != -1 ) + {//-1 == don't play pain anim + //Set our proper pain animation + if ( Q_irand( 0, 1 ) ) + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN ); + } + else + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, -1 ); + } + } + return; + } + else if ( self->NPC && !other->s.number )//should be assumed, but... + {//dammit, stop that! + if ( self->NPC->charmedTime ) + {//mindtricked + return; + } + else if ( self->NPC->ffireCount < 3+((2-g_spskill.integer)*2) ) + {//not mad enough yet + //Com_Printf( "chck: %d < %d\n", self->NPC->ffireCount, 3+((2-g_spskill.integer)*2) ); + if ( damage != -1 ) + {//-1 == don't play pain anim + //Set our proper pain animation + if ( Q_irand( 0, 1 ) ) + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, EV_FFWARN ); + } + else + { + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, -1 ); + } + } + return; + } + else if ( G_ActivateBehavior( self, BSET_FFIRE ) ) + {//we have a specific script to run, so do that instead + return; + } + else + {//okay, we're going to turn on our ally, we need to set and lock our enemy and put ourselves in a bstate that lets us attack him (and clear any flags that would stop us) + self->NPC->blockedSpeechDebounceTime = 0; + voiceEvent = EV_FFTURN; + self->NPC->behaviorState = self->NPC->tempBehavior = self->NPC->defaultBehavior = BS_DEFAULT; + other->flags &= ~FL_NOTARGET; + //self->svFlags &= ~(SVF_IGNORE_ENEMIES|SVF_ICARUS_FREEZE|SVF_NO_COMBAT_SOUNDS); + self->r.svFlags &= ~SVF_ICARUS_FREEZE; + G_SetEnemy( self, other ); + //self->svFlags |= SVF_LOCKEDENEMY; //rwwFIXMEFIXME: proper support for these flags. + self->NPC->scriptFlags &= ~(SCF_DONT_FIRE|SCF_CROUCHED|SCF_WALKING|SCF_NO_COMBAT_TALK|SCF_FORCED_MARCH); + self->NPC->scriptFlags |= (SCF_CHASE_ENEMIES|SCF_NO_MIND_TRICK); + //NOTE: we also stop ICARUS altogether + //stop_icarus = qtrue; + //rwwFIXMEFIXME: stop icarus? + if ( !killPlayerTimer ) + { + killPlayerTimer = level.time + 10000; + } + } + } + } + } + + SaveNPCGlobals(); + SetNPCGlobals( self ); + + //Do extra bits + if ( NPCInfo->ignorePain == qfalse ) + { + NPCInfo->confusionTime = 0;//clear any charm or confusion, regardless + if ( damage != -1 ) + {//-1 == don't play pain anim + //Set our proper pain animation + NPC_ChoosePainAnimation( self, other, point, damage, mod, hitLoc, voiceEvent ); + } + //Check to take a new enemy + if ( NPC->enemy != other && NPC != other ) + {//not already mad at them + NPC_CheckAttacker( other, mod ); + } + } + + //Attempt to run any pain instructions + if ( self->client && self->NPC ) + { + //FIXME: This needs better heuristics perhaps + if(self->health <= (self->client->ps.stats[STAT_MAX_HEALTH]/3) && G_ActivateBehavior(self, BSET_FLEE) ) + { + } + else //if( VALIDSTRING( self->behaviorSet[BSET_PAIN] ) ) + { + G_ActivateBehavior(self, BSET_PAIN); + } + } + + //Attempt to fire any paintargets we might have + if( self->paintarget && self->paintarget[0] ) + { + G_UseTargets2(self, other, self->paintarget); + } + + RestoreNPCGlobals(); +} + +/* +------------------------- +NPC_Touch +------------------------- +*/ +extern qboolean INV_SecurityKeyGive( gentity_t *target, const char *keyname ); +void NPC_Touch(gentity_t *self, gentity_t *other, trace_t *trace) +{ + if(!self->NPC) + return; + + SaveNPCGlobals(); + SetNPCGlobals( self ); + + if ( self->message && self->health <= 0 ) + {//I am dead and carrying a key + //if ( other && player && player->health > 0 && other == player ) + if (other && other->client && other->s.number < MAX_CLIENTS) + {//player touched me + /* + char *text; + qboolean keyTaken; + //give him my key + if ( Q_stricmp( "goodie", self->message ) == 0 ) + {//a goodie key + if ( (keyTaken = INV_GoodieKeyGive( other )) == qtrue ) + { + text = "cp @SP_INGAME_TOOK_IMPERIAL_GOODIE_KEY"; + G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_GOODIE_KEY )-bg_itemlist) ); + } + else + { + text = "cp @SP_INGAME_CANT_CARRY_GOODIE_KEY"; + } + } + else + {//a named security key + if ( (keyTaken = INV_SecurityKeyGive( player, self->message )) == qtrue ) + { + text = "cp @SP_INGAME_TOOK_IMPERIAL_SECURITY_KEY"; + G_AddEvent( other, EV_ITEM_PICKUP, (FindItemForInventory( INV_SECURITY_KEY )-bg_itemlist) ); + } + else + { + text = "cp @SP_INGAME_CANT_CARRY_SECURITY_KEY"; + } + } + */ + //rwwFIXMEFIXME: support for goodie/security keys? + /* + if ( keyTaken ) + {//remove my key + NPC_SetSurfaceOnOff( self, "l_arm_key", 0x00000002 ); + self->message = NULL; + //FIXME: temp pickup sound + G_Sound( player, G_SoundIndex( "sound/weapons/key_pkup.wav" ) ); + //FIXME: need some event to pass to cgame for sound/graphic/message? + } + //FIXME: temp message + gi.SendServerCommand( NULL, text ); + */ + } + } + + if ( other->client ) + {//FIXME: if pushing against another bot, both ucmd.rightmove = 127??? + //Except if not facing one another... + if ( other->health > 0 ) + { + NPCInfo->touchedByPlayer = other; + } + + if ( other == NPCInfo->goalEntity ) + { + NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL; + } + + if( /*!(self->svFlags&SVF_LOCKEDENEMY) && !(self->svFlags&SVF_IGNORE_ENEMIES) &&*/ !(other->flags & FL_NOTARGET) ) + { + if ( self->client->enemyTeam ) + {//See if we bumped into an enemy + if ( other->client->playerTeam == self->client->enemyTeam ) + {//bumped into an enemy + if( NPCInfo->behaviorState != BS_HUNT_AND_KILL && !NPCInfo->tempBehavior ) + {//MCG - Begin: checking specific BS mode here, this is bad, a HACK + //FIXME: not medics? + if ( NPC->enemy != other ) + {//not already mad at them + G_SetEnemy( NPC, other ); + } + // NPCInfo->tempBehavior = BS_HUNT_AND_KILL; + } + } + } + } + + //FIXME: do this if player is moving toward me and with a certain dist? + /* + if ( other->s.number == 0 && self->client->playerTeam == other->client->playerTeam ) + { + VectorAdd( self->client->pushVec, other->client->ps.velocity, self->client->pushVec ); + } + */ + } + else + {//FIXME: check for SVF_NONNPC_ENEMY flag here? + if ( other->health > 0 ) + { + //if ( NPC->enemy == other && (other->svFlags&SVF_NONNPC_ENEMY) ) + if (0) //rwwFIXMEFIXME: Can probably just check if num < MAX_CLIENTS for non-npc enemy stuff + { + NPCInfo->touchedByPlayer = other; + } + } + + if ( other == NPCInfo->goalEntity ) + { + NPCInfo->aiFlags |= NPCAI_TOUCHED_GOAL; + } + } + + RestoreNPCGlobals(); +} + +/* +------------------------- +NPC_TempLookTarget +------------------------- +*/ + +void NPC_TempLookTarget( gentity_t *self, int lookEntNum, int minLookTime, int maxLookTime ) +{ + if ( !self->client ) + { + return; + } + + if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//lookTarget is set by and to the monster that's holding you, no other operations can change that + return; + } + + if ( !minLookTime ) + { + minLookTime = 1000; + } + + if ( !maxLookTime ) + { + maxLookTime = 1000; + } + + if ( !NPC_CheckLookTarget( self ) ) + {//Not already looking at something else + //Look at him for 1 to 3 seconds + NPC_SetLookTarget( self, lookEntNum, level.time + Q_irand( minLookTime, maxLookTime ) ); + } +} + +void NPC_Respond( gentity_t *self, int userNum ) +{ + int event = -1; + /* + + if ( Q_irand( 0, 1 ) ) + { + event = Q_irand(EV_RESPOND1, EV_RESPOND3); + } + else + { + event = Q_irand(EV_BUSY1, EV_BUSY3); + } + */ + + if ( !Q_irand( 0, 1 ) ) + {//set looktarget to them for a second or two + NPC_TempLookTarget( self, userNum, 1000, 3000 ); + } + + //some last-minute hacked in responses + switch ( self->client->NPC_class ) + { + case CLASS_JAN: + if ( self->enemy ) + { + if ( !Q_irand( 0, 2 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 2 ) ) + { + event = EV_SUSPICIOUS4; + } + else if ( !Q_irand( 0, 1 ) ) + { + event = EV_SOUND1; + } + else + { + event = EV_CONFUSE1; + } + break; + case CLASS_LANDO: + if ( self->enemy ) + { + if ( !Q_irand( 0, 2 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 6 ) ) + { + event = EV_SIGHT2; + } + else if ( !Q_irand( 0, 5 ) ) + { + event = EV_GIVEUP4; + } + else if ( Q_irand( 0, 4 ) > 1 ) + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + else + { + event = Q_irand( EV_JDETECTED1, EV_JDETECTED2 ); + } + break; + case CLASS_LUKE: + if ( self->enemy ) + { + event = EV_COVER1; + } + else + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + break; + case CLASS_JEDI: + if ( !self->enemy ) + { + /* + if ( !(self->svFlags&SVF_IGNORE_ENEMIES) + && (self->NPC->scriptFlags&SCF_LOOK_FOR_ENEMIES) + && self->client->enemyTeam == TEAM_ENEMY ) + */ + if (0) //rwwFIXMEFIXME: support flags! + { + event = Q_irand( EV_ANGER1, EV_ANGER3 ); + } + else + { + event = Q_irand( EV_TAUNT1, EV_TAUNT2 ); + } + } + break; + case CLASS_PRISONER: + if ( self->enemy ) + { + if ( Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + } + else + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + break; + case CLASS_REBEL: + if ( self->enemy ) + { + if ( !Q_irand( 0, 2 ) ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else + { + event = Q_irand( EV_DETECTED1, EV_DETECTED5 ); + } + } + else + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + break; + case CLASS_BESPIN_COP: + if ( !Q_stricmp( "bespincop", self->NPC_type ) ) + {//variant 1 + if ( self->enemy ) + { + if ( Q_irand( 0, 9 ) > 6 ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 6 ) > 4 ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 3 ) ) + { + event = Q_irand( EV_SIGHT2, EV_SIGHT3 ); + } + else if ( !Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + else if ( !Q_irand( 0, 2 ) ) + { + event = EV_LOST1; + } + else if ( !Q_irand( 0, 1 ) ) + { + event = EV_ESCAPING2; + } + else + { + event = EV_GIVEUP4; + } + } + else + {//variant2 + if ( self->enemy ) + { + if ( Q_irand( 0, 9 ) > 6 ) + { + event = Q_irand( EV_CHASE1, EV_CHASE3 ); + } + else if ( Q_irand( 0, 6 ) > 4 ) + { + event = Q_irand( EV_OUTFLANK1, EV_OUTFLANK2 ); + } + else + { + event = Q_irand( EV_COVER1, EV_COVER5 ); + } + } + else if ( !Q_irand( 0, 3 ) ) + { + event = Q_irand( EV_SIGHT1, EV_SIGHT2 ); + } + else if ( !Q_irand( 0, 1 ) ) + { + event = Q_irand( EV_SOUND1, EV_SOUND3 ); + } + else if ( !Q_irand( 0, 2 ) ) + { + event = EV_LOST1; + } + else if ( !Q_irand( 0, 1 ) ) + { + event = EV_GIVEUP3; + } + else + { + event = EV_CONFUSE1; + } + } + break; + case CLASS_R2D2: // droid + G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/r2d2/misc/r2d2talk0%d.wav",Q_irand(1, 3)))); + break; + case CLASS_R5D2: // droid + G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/r5d2/misc/r5talk%d.wav",Q_irand(1, 4)))); + break; + case CLASS_MOUSE: // droid + G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/mouse/misc/mousego%d.wav",Q_irand(1, 3)))); + break; + case CLASS_GONK: // droid + G_Sound(self, CHAN_AUTO, G_SoundIndex(va("sound/chars/gonk/misc/gonktalk%d.wav",Q_irand(1, 2)))); + break; + } + + if ( event != -1 ) + { + //hack here because we reuse some "combat" and "extra" sounds + qboolean addFlag = (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK); + self->NPC->scriptFlags &= ~SCF_NO_COMBAT_TALK; + + G_AddVoiceEvent( self, event, 3000 ); + + if ( addFlag ) + { + self->NPC->scriptFlags |= SCF_NO_COMBAT_TALK; + } + } +} + +/* +------------------------- +NPC_UseResponse +------------------------- +*/ + +void NPC_UseResponse( gentity_t *self, gentity_t *user, qboolean useWhenDone ) +{ + if ( !self->NPC || !self->client ) + { + return; + } + + if ( user->s.number != 0 ) + {//not used by the player + if ( useWhenDone ) + { + G_ActivateBehavior( self, BSET_USE ); + } + return; + } + + if ( user->client && self->client->playerTeam != user->client->playerTeam && self->client->playerTeam != NPCTEAM_NEUTRAL ) + {//only those on the same team react + if ( useWhenDone ) + { + G_ActivateBehavior( self, BSET_USE ); + } + return; + } + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + {//I'm not responding right now + return; + } + + /* + if ( gi.VoiceVolume[self->s.number] ) + {//I'm talking already + if ( !useWhenDone ) + {//you're not trying to use me + return; + } + } + */ + //rwwFIXMEFIXME: Support for this? + + if ( useWhenDone ) + { + G_ActivateBehavior( self, BSET_USE ); + } + else + { + NPC_Respond( self, user->s.number ); + } +} + +/* +------------------------- +NPC_Use +------------------------- +*/ +extern void Add_Batteries( gentity_t *ent, int *count ); + +void NPC_Use( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if (self->client->ps.pm_type == PM_DEAD) + {//or just remove ->pain in player_die? + return; + } + + SaveNPCGlobals(); + SetNPCGlobals( self ); + + if(self->client && self->NPC) + { + // If this is a vehicle, let the other guy board it. Added 12/14/02 by AReis. + if ( self->client->NPC_class == CLASS_VEHICLE ) + { + Vehicle_t *pVeh = self->m_pVehicle; + + if ( pVeh && pVeh->m_pVehicleInfo ) + { + //if I used myself, eject everyone on me + if ( other == self ) + { + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + // If other is already riding this vehicle (self), eject him. + else if ( other->s.owner == self->s.number ) + { + pVeh->m_pVehicleInfo->Eject( pVeh, (bgEntity_t *)other, qfalse ); + } + // Otherwise board this vehicle. + else + { + pVeh->m_pVehicleInfo->Board( pVeh, (bgEntity_t *)other ); + } + } + } + else if ( Jedi_WaitingAmbush( NPC ) ) + { + Jedi_Ambush( NPC ); + } + //Run any use instructions + if ( activator && activator->s.number == 0 && self->client->NPC_class == CLASS_GONK ) + { + // must be using the gonk, so attempt to give battery power. + // NOTE: this will steal up to MAX_BATTERIES for the activator, leaving the residual on the gonk for potential later use. +// Add_Batteries( activator, &self->client->ps.batteryCharge ); + //rwwFIXMEFIXME: support for this? + } + // Not using MEDICs anymore +/* + if ( self->NPC->behaviorState == BS_MEDIC_HIDE && activator->client ) + {//Heal me NOW, dammit! + if ( activator->health < activator->client->ps.stats[STAT_MAX_HEALTH] ) + {//person needs help + if ( self->NPC->eventualGoal != activator ) + {//not my current patient already + NPC_TakePatient( activator ); + G_ActivateBehavior( self, BSET_USE ); + } + } + else if ( !self->enemy && activator->s.number == 0 && !gi.VoiceVolume[self->s.number] && !(self->NPC->scriptFlags&SCF_NO_RESPONSE) ) + {//I don't have an enemy and I'm not talking and I was used by the player + NPC_UseResponse( self, other, qfalse ); + } + } +*/ +// else if ( self->behaviorSet[BSET_USE] ) + if ( self->behaviorSet[BSET_USE] ) + { + NPC_UseResponse( self, other, qtrue ); + } +// else if ( isMedic( self ) ) +// {//Heal me NOW, dammit! +// NPC_TakePatient( activator ); +// } + else if ( !self->enemy + && activator->s.number == 0 + && /*!gi.VoiceVolume[self->s.number] &&*/ !(self->NPC->scriptFlags&SCF_NO_RESPONSE) ) + //rwwFIXMEFIXME: voice volume support? + {//I don't have an enemy and I'm not talking and I was used by the player + NPC_UseResponse( self, other, qfalse ); + } + } + + RestoreNPCGlobals(); +} + +void NPC_CheckPlayerAim( void ) +{ + //FIXME: need appropriate dialogue + /* + gentity_t *player = &g_entities[0]; + + if ( player && player->client && player->client->ps.weapon > (int)(WP_NONE) && player->client->ps.weapon < (int)(WP_TRICORDER) ) + {//player has a weapon ready + if ( g_crosshairEntNum == NPC->s.number && level.time - g_crosshairEntTime < 200 + && g_crosshairSameEntTime >= 3000 && g_crosshairEntDist < 256 ) + {//if the player holds the crosshair on you for a few seconds + //ask them what the fuck they're doing + G_AddVoiceEvent( NPC, Q_irand( EV_FF_1A, EV_FF_1C ), 0 ); + } + } + */ +} + +void NPC_CheckAllClear( void ) +{ + //FIXME: need to make this happen only once after losing enemies, not over and over again + /* + if ( NPC->client && !NPC->enemy && level.time - teamLastEnemyTime[NPC->client->playerTeam] > 10000 ) + {//Team hasn't seen an enemy in 10 seconds + if ( !Q_irand( 0, 2 ) ) + { + G_AddVoiceEvent( NPC, Q_irand(EV_SETTLE1, EV_SETTLE3), 3000 ); + } + } + */ +} diff --git a/code/game/NPC_senses.c b/code/game/NPC_senses.c new file mode 100644 index 0000000..0bb6af1 --- /dev/null +++ b/code/game/NPC_senses.c @@ -0,0 +1,934 @@ +//NPC_senses.cpp + +#include "b_local.h" + +extern int eventClearTime; +/* +qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask) + +returns true if can see from point 1 to 2, even through glass (1 pane)- doesn't work with portals +*/ +qboolean G_ClearLineOfSight(const vec3_t point1, const vec3_t point2, int ignore, int clipmask) +{ + trace_t tr; + gentity_t *hit; + + trap_Trace ( &tr, point1, NULL, NULL, point2, ignore, clipmask ); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + hit = &g_entities[ tr.entityNum ]; + if(EntIsGlass(hit)) + { + vec3_t newpoint1; + VectorCopy(tr.endpos, newpoint1); + trap_Trace (&tr, newpoint1, NULL, NULL, point2, hit->s.number, clipmask ); + + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +CanSee +determine if NPC can see an entity + +This is a straight line trace check. This function does not look at PVS or FOV, +or take any AI related factors (for example, the NPC's reaction time) into account + +FIXME do we need fat and thin version of this? +*/ +qboolean CanSee ( gentity_t *ent ) +{ + trace_t tr; + vec3_t eyes; + vec3_t spot; + + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + trap_Trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE ); + ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_HEAD, spot ); + trap_Trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE ); + ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_LEGS, spot ); + trap_Trace ( &tr, eyes, NULL, NULL, spot, NPC->s.number, MASK_OPAQUE ); + ShotThroughGlass (&tr, ent, spot, MASK_OPAQUE); + if ( tr.fraction == 1.0 ) + { + return qtrue; + } + + return qfalse; +} + +qboolean InFront( vec3_t spot, vec3_t from, vec3_t fromAngles, float threshHold ) +{ + vec3_t dir, forward, angles; + float dot; + + VectorSubtract( spot, from, dir ); + dir[2] = 0; + VectorNormalize( dir ); + + VectorCopy( fromAngles, angles ); + angles[0] = 0; + AngleVectors( angles, forward, NULL, NULL ); + + dot = DotProduct( dir, forward ); + + return (dot > threshHold); +} + +/* +InFOV + +IDEA: further off to side of FOV range, higher chance of failing even if technically in FOV, + keep core of 50% to sides as always succeeding +*/ + +//Position compares + +qboolean InFOV3( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV ) +{ + vec3_t deltaVector, angles, deltaAngles; + + VectorSubtract ( spot, from, deltaVector ); + vectoangles ( deltaVector, angles ); + + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + return qfalse; +} + +//NPC to position + +qboolean InFOV2( vec3_t origin, gentity_t *from, int hFOV, int vFOV ) +{ + vec3_t fromAngles, eyes; + + if( from->client ) + { + VectorCopy(from->client->ps.viewangles, fromAngles); + } + else + { + VectorCopy(from->s.angles, fromAngles); + } + + CalcEntitySpot( from, SPOT_HEAD, eyes ); + + return InFOV3( origin, eyes, fromAngles, hFOV, vFOV ); +} + +//Entity to entity + +qboolean InFOV ( gentity_t *ent, gentity_t *from, int hFOV, int vFOV ) +{ + vec3_t eyes; + vec3_t spot; + vec3_t deltaVector; + vec3_t angles, fromAngles; + vec3_t deltaAngles; + + if( from->client ) + { + if( !VectorCompare( from->client->renderInfo.eyeAngles, vec3_origin ) ) + {//Actual facing of tag_head! + //NOTE: Stasis aliens may have a problem with this? + VectorCopy( from->client->renderInfo.eyeAngles, fromAngles ); + } + else + { + VectorCopy( from->client->ps.viewangles, fromAngles ); + } + } + else + { + VectorCopy(from->s.angles, fromAngles); + } + + CalcEntitySpot( from, SPOT_HEAD_LEAN, eyes ); + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + VectorSubtract ( spot, eyes, deltaVector); + + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_HEAD, spot ); + VectorSubtract ( spot, eyes, deltaVector); + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + CalcEntitySpot( ent, SPOT_LEGS, spot ); + VectorSubtract ( spot, eyes, deltaVector); + vectoangles ( deltaVector, angles ); + deltaAngles[PITCH] = AngleDelta ( fromAngles[PITCH], angles[PITCH] ); + deltaAngles[YAW] = AngleDelta ( fromAngles[YAW], angles[YAW] ); + if ( fabs ( deltaAngles[PITCH] ) <= vFOV && fabs ( deltaAngles[YAW] ) <= hFOV ) + { + return qtrue; + } + + return qfalse; +} + +qboolean InVisrange ( gentity_t *ent ) +{//FIXME: make a calculate visibility for ents that takes into account + //lighting, movement, turning, crouch/stand up, other anims, hide brushes, etc. + vec3_t eyes; + vec3_t spot; + vec3_t deltaVector; + float visrange = (NPCInfo->stats.visrange*NPCInfo->stats.visrange); + + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, eyes ); + + CalcEntitySpot( ent, SPOT_ORIGIN, spot ); + VectorSubtract ( spot, eyes, deltaVector); + + /*if(ent->client) + { + float vel, avel; + if(ent->client->ps.velocity[0] || ent->client->ps.velocity[1] || ent->client->ps.velocity[2]) + { + vel = VectorLength(ent->client->ps.velocity); + if(vel > 128) + { + visrange += visrange * (vel/256); + } + } + + if(ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2]) + {//FIXME: shouldn't they need to have line of sight to you to detect this? + avel = VectorLength(ent->avelocity); + if(avel > 15) + { + visrange += visrange * (avel/60); + } + } + }*/ + + if(VectorLengthSquared(deltaVector) > visrange) + { + return qfalse; + } + + return qtrue; +} + +/* +NPC_CheckVisibility +*/ + +visibility_t NPC_CheckVisibility ( gentity_t *ent, int flags ) +{ + // flags should never be 0 + if ( !flags ) + { + return VIS_NOT; + } + + // check PVS + if ( flags & CHECK_PVS ) + { + if ( !trap_InPVS ( ent->r.currentOrigin, NPC->r.currentOrigin ) ) + { + return VIS_NOT; + } + } + if ( !(flags & (CHECK_360|CHECK_FOV|CHECK_SHOOT)) ) + { + return VIS_PVS; + } + + // check within visrange + if (flags & CHECK_VISRANGE) + { + if( !InVisrange ( ent ) ) + { + return VIS_PVS; + } + } + + // check 360 degree visibility + //Meaning has to be a direct line of site + if ( flags & CHECK_360 ) + { + if ( !CanSee ( ent ) ) + { + return VIS_PVS; + } + } + if ( !(flags & (CHECK_FOV|CHECK_SHOOT)) ) + { + return VIS_360; + } + + // check FOV + if ( flags & CHECK_FOV ) + { + if ( !InFOV ( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov) ) + { + return VIS_360; + } + } + + if ( !(flags & CHECK_SHOOT) ) + { + return VIS_FOV; + } + + // check shootability + if ( flags & CHECK_SHOOT ) + { + if ( !CanShoot ( ent, NPC ) ) + { + return VIS_FOV; + } + } + + return VIS_SHOOT; +} + +/* +------------------------- +NPC_CheckSoundEvents +------------------------- +*/ +static int G_CheckSoundEvents( gentity_t *self, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) +{ + int bestEvent = -1; + int bestAlert = -1; + int bestTime = -1; + int i; + float dist, radius; + + maxHearDist *= maxHearDist; + + for ( i = 0; i < level.numAlertEvents; i++ ) + { + //are we purposely ignoring this alert? + if ( i == ignoreAlert ) + continue; + //We're only concerned about sounds + if ( level.alertEvents[i].type != AET_SOUND ) + continue; + //must be at least this noticable + if ( level.alertEvents[i].level < minAlertLevel ) + continue; + //must have an owner? + if ( mustHaveOwner && !level.alertEvents[i].owner ) + continue; + //Must be within range + dist = DistanceSquared( level.alertEvents[i].position, self->r.currentOrigin ); + + //can't hear it + if ( dist > maxHearDist ) + continue; + + radius = level.alertEvents[i].radius * level.alertEvents[i].radius; + if ( dist > radius ) + continue; + + if ( level.alertEvents[i].addLight ) + {//a quiet sound, must have LOS to hear it + if ( G_ClearLOS5( self, level.alertEvents[i].position ) == qfalse ) + {//no LOS, didn't hear it + continue; + } + } + + //See if this one takes precedence over the previous one + if ( level.alertEvents[i].level >= bestAlert //higher alert level + || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer + {//NOTE: equal is better because it's later in the array + bestEvent = i; + bestAlert = level.alertEvents[i].level; + bestTime = level.alertEvents[i].timestamp; + } + } + + return bestEvent; +} + +float G_GetLightLevel( vec3_t pos, vec3_t fromDir ) +{ + /* + vec3_t ambient={0}, directed, lightDir; + + cgi_R_GetLighting( pos, ambient, directed, lightDir ); + lightLevel = VectorLength( ambient ) + (VectorLength( directed )*DotProduct( lightDir, fromDir )); + */ + float lightLevel; + //rwwFIXMEFIXME: ...this is evil. We can possibly read from the server BSP data, or load the lightmap along + //with collision data and whatnot, but is it worth it? + lightLevel = 255; + + return lightLevel; +} +/* +------------------------- +NPC_CheckSightEvents +------------------------- +*/ +static int G_CheckSightEvents( gentity_t *self, int hFOV, int vFOV, float maxSeeDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) +{ + int bestEvent = -1; + int bestAlert = -1; + int bestTime = -1; + int i; + float dist, radius; + + maxSeeDist *= maxSeeDist; + for ( i = 0; i < level.numAlertEvents; i++ ) + { + //are we purposely ignoring this alert? + if ( i == ignoreAlert ) + continue; + //We're only concerned about sounds + if ( level.alertEvents[i].type != AET_SIGHT ) + continue; + //must be at least this noticable + if ( level.alertEvents[i].level < minAlertLevel ) + continue; + //must have an owner? + if ( mustHaveOwner && !level.alertEvents[i].owner ) + continue; + + //Must be within range + dist = DistanceSquared( level.alertEvents[i].position, self->r.currentOrigin ); + + //can't see it + if ( dist > maxSeeDist ) + continue; + + radius = level.alertEvents[i].radius * level.alertEvents[i].radius; + if ( dist > radius ) + continue; + + //Must be visible + if ( InFOV2( level.alertEvents[i].position, self, hFOV, vFOV ) == qfalse ) + continue; + + if ( G_ClearLOS5( self, level.alertEvents[i].position ) == qfalse ) + continue; + + //FIXME: possibly have the light level at this point affect the + // visibility/alert level of this event? Would also + // need to take into account how bright the event + // itself is. A lightsaber would stand out more + // in the dark... maybe pass in a light level that + // is added to the actual light level at this position? + + //See if this one takes precedence over the previous one + if ( level.alertEvents[i].level >= bestAlert //higher alert level + || (level.alertEvents[i].level==bestAlert&&level.alertEvents[i].timestamp >= bestTime) )//same alert level, but this one is newer + {//NOTE: equal is better because it's later in the array + bestEvent = i; + bestAlert = level.alertEvents[i].level; + bestTime = level.alertEvents[i].timestamp; + } + } + + return bestEvent; +} + +/* +------------------------- +NPC_CheckAlertEvents + + NOTE: Should all NPCs create alertEvents too so they can detect each other? +------------------------- +*/ + +int G_CheckAlertEvents( gentity_t *self, qboolean checkSight, qboolean checkSound, float maxSeeDist, float maxHearDist, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) +{ + int bestSoundEvent = -1; + int bestSightEvent = -1; + int bestSoundAlert = -1; + int bestSightAlert = -1; + + if ( &g_entities[0] == NULL || g_entities[0].health <= 0 ) + { + //player is dead + return -1; + } + + //get sound event + bestSoundEvent = G_CheckSoundEvents( self, maxHearDist, ignoreAlert, mustHaveOwner, minAlertLevel ); + //get sound event alert level + if ( bestSoundEvent >= 0 ) + { + bestSoundAlert = level.alertEvents[bestSoundEvent].level; + } + + //get sight event + if ( self->NPC ) + { + bestSightEvent = G_CheckSightEvents( self, self->NPC->stats.hfov, self->NPC->stats.vfov, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel ); + } + else + { + bestSightEvent = G_CheckSightEvents( self, 80, 80, maxSeeDist, ignoreAlert, mustHaveOwner, minAlertLevel );//FIXME: look at cg_view to get more accurate numbers? + } + //get sight event alert level + if ( bestSightEvent >= 0 ) + { + bestSightAlert = level.alertEvents[bestSightEvent].level; + } + + //return the one that has a higher alert (or sound if equal) + //FIXME: This doesn't take the distance of the event into account + + if ( bestSightEvent >= 0 && bestSightAlert > bestSoundAlert ) + {//valid best sight event, more important than the sound event + //get the light level of the alert event for this checker + vec3_t eyePoint, sightDir; + //get eye point + CalcEntitySpot( self, SPOT_HEAD_LEAN, eyePoint ); + VectorSubtract( level.alertEvents[bestSightEvent].position, eyePoint, sightDir ); + level.alertEvents[bestSightEvent].light = level.alertEvents[bestSightEvent].addLight + G_GetLightLevel( level.alertEvents[bestSightEvent].position, sightDir ); + //return the sight event + return bestSightEvent; + } + //return the sound event + return bestSoundEvent; +} + +int NPC_CheckAlertEvents( qboolean checkSight, qboolean checkSound, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ) +{ + return G_CheckAlertEvents( NPC, checkSight, checkSound, NPCInfo->stats.visrange, NPCInfo->stats.earshot, ignoreAlert, mustHaveOwner, minAlertLevel ); +} + +qboolean G_CheckForDanger( gentity_t *self, int alertEvent ) +{//FIXME: more bStates need to call this? + if ( alertEvent == -1 ) + { + return qfalse; + } + + if ( level.alertEvents[alertEvent].level >= AEL_DANGER ) + {//run away! + if ( !level.alertEvents[alertEvent].owner || !level.alertEvents[alertEvent].owner->client || (level.alertEvents[alertEvent].owner!=self&&level.alertEvents[alertEvent].owner->client->playerTeam!=self->client->playerTeam) ) + { + if ( self->NPC ) + { + if ( self->NPC->scriptFlags & SCF_DONT_FLEE ) + {//can't flee + return qfalse; + } + else + { + NPC_StartFlee( level.alertEvents[alertEvent].owner, level.alertEvents[alertEvent].position, level.alertEvents[alertEvent].level, 3000, 6000 ); + return qtrue; + } + } + else + { + return qtrue; + } + } + } + return qfalse; +} +qboolean NPC_CheckForDanger( int alertEvent ) +{//FIXME: more bStates need to call this? + return G_CheckForDanger( NPC, alertEvent ); +} + +/* +------------------------- +AddSoundEvent +------------------------- +*/ +qboolean RemoveOldestAlert( void ); +void AddSoundEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, qboolean needLOS ) +{ + //FIXME: Handle this in another manner? + if ( level.numAlertEvents >= MAX_ALERT_EVENTS ) + { + if ( !RemoveOldestAlert() ) + {//how could that fail? + return; + } + } + + if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts + return; + + //FIXME: if owner is not a player or player ally, and there are no player allies present, + // perhaps we don't need to store the alert... unless we want the player to + // react to enemy alert events in some way? + + VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position ); + + level.alertEvents[ level.numAlertEvents ].radius = radius; + level.alertEvents[ level.numAlertEvents ].level = alertLevel; + level.alertEvents[ level.numAlertEvents ].type = AET_SOUND; + level.alertEvents[ level.numAlertEvents ].owner = owner; + if ( needLOS ) + {//a very low-level sound, when check this sound event, check for LOS + level.alertEvents[ level.numAlertEvents ].addLight = 1; //will force an LOS trace on this sound + } + else + { + level.alertEvents[ level.numAlertEvents ].addLight = 0; //will force an LOS trace on this sound + } + level.alertEvents[ level.numAlertEvents ].ID = level.curAlertID++; + level.alertEvents[ level.numAlertEvents ].timestamp = level.time; + + level.numAlertEvents++; +} + +/* +------------------------- +AddSightEvent +------------------------- +*/ + +void AddSightEvent( gentity_t *owner, vec3_t position, float radius, alertEventLevel_e alertLevel, float addLight ) +{ + //FIXME: Handle this in another manner? + if ( level.numAlertEvents >= MAX_ALERT_EVENTS ) + { + if ( !RemoveOldestAlert() ) + {//how could that fail? + return; + } + } + + if ( owner == NULL && alertLevel < AEL_DANGER ) //allows un-owned danger alerts + return; + + //FIXME: if owner is not a player or player ally, and there are no player allies present, + // perhaps we don't need to store the alert... unless we want the player to + // react to enemy alert events in some way? + + VectorCopy( position, level.alertEvents[ level.numAlertEvents ].position ); + + level.alertEvents[ level.numAlertEvents ].radius = radius; + level.alertEvents[ level.numAlertEvents ].level = alertLevel; + level.alertEvents[ level.numAlertEvents ].type = AET_SIGHT; + level.alertEvents[ level.numAlertEvents ].owner = owner; + level.alertEvents[ level.numAlertEvents ].addLight = addLight; //will get added to actual light at that point when it's checked + level.alertEvents[ level.numAlertEvents ].ID = level.curAlertID++; + level.alertEvents[ level.numAlertEvents ].timestamp = level.time; + + level.numAlertEvents++; +} + +/* +------------------------- +ClearPlayerAlertEvents +------------------------- +*/ + +void ClearPlayerAlertEvents( void ) +{ + int curNumAlerts = level.numAlertEvents; + int i; + //loop through them all (max 32) + for ( i = 0; i < curNumAlerts; i++ ) + { + //see if the event is old enough to delete + if ( level.alertEvents[i].timestamp && level.alertEvents[i].timestamp + ALERT_CLEAR_TIME < level.time ) + {//this event has timed out + //drop the count + level.numAlertEvents--; + //shift the rest down + if ( level.numAlertEvents > 0 ) + {//still have more in the array + if ( (i+1) < MAX_ALERT_EVENTS ) + { + memmove( &level.alertEvents[i], &level.alertEvents[i+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(i+1) ) ); + } + } + else + {//just clear this one... or should we clear the whole array? + memset( &level.alertEvents[i], 0, sizeof( alertEvent_t ) ); + } + } + } + //make sure this never drops below zero... if it does, something very very bad happened + assert( level.numAlertEvents >= 0 ); + + if ( eventClearTime < level.time ) + {//this is just a 200ms debouncer so things that generate constant alerts (like corpses and missiles) add an alert every 200 ms + eventClearTime = level.time + ALERT_CLEAR_TIME; + } +} + +qboolean RemoveOldestAlert( void ) +{ + int oldestEvent = -1, oldestTime = Q3_INFINITE; + int i; + //loop through them all (max 32) + for ( i = 0; i < level.numAlertEvents; i++ ) + { + //see if the event is old enough to delete + if ( level.alertEvents[i].timestamp < oldestTime ) + { + oldestEvent = i; + oldestTime = level.alertEvents[i].timestamp; + } + } + if ( oldestEvent != -1 ) + { + //drop the count + level.numAlertEvents--; + //shift the rest down + if ( level.numAlertEvents > 0 ) + {//still have more in the array + if ( (oldestEvent+1) < MAX_ALERT_EVENTS ) + { + memmove( &level.alertEvents[oldestEvent], &level.alertEvents[oldestEvent+1], sizeof(alertEvent_t)*(MAX_ALERT_EVENTS-(oldestEvent+1) ) ); + } + } + else + {//just clear this one... or should we clear the whole array? + memset( &level.alertEvents[oldestEvent], 0, sizeof( alertEvent_t ) ); + } + } + //make sure this never drops below zero... if it does, something very very bad happened + assert( level.numAlertEvents >= 0 ); + //return true is have room for one now + return (level.numAlertEvents hFOV ) + return 0.0f; + + return ( ( hFOV - delta ) / hFOV ); +} + +/* +------------------------- +NPC_GetVFOVPercentage +------------------------- +*/ + +float NPC_GetVFOVPercentage( vec3_t spot, vec3_t from, vec3_t facing, float vFOV ) +{ + vec3_t deltaVector, angles; + float delta; + + VectorSubtract ( spot, from, deltaVector ); + + vectoangles ( deltaVector, angles ); + + delta = fabs( AngleDelta ( facing[PITCH], angles[PITCH] ) ); + + if ( delta > vFOV ) + return 0.0f; + + return ( ( vFOV - delta ) / vFOV ); +} + +#define MAX_INTEREST_DIST ( 256 * 256 ) +/* +------------------------- +NPC_FindLocalInterestPoint +------------------------- +*/ + +int G_FindLocalInterestPoint( gentity_t *self ) +{ + int i, bestPoint = ENTITYNUM_NONE; + float dist, bestDist = Q3_INFINITE; + vec3_t diffVec, eyes; + + CalcEntitySpot( self, SPOT_HEAD_LEAN, eyes ); + for ( i = 0; i < level.numInterestPoints; i++ ) + { + //Don't ignore portals? If through a portal, need to look at portal! + if ( trap_InPVS( level.interestPoints[i].origin, eyes ) ) + { + VectorSubtract( level.interestPoints[i].origin, eyes, diffVec ); + if ( (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 < 48 && + fabs(diffVec[2]) > (fabs(diffVec[0]) + fabs(diffVec[1])) / 2 ) + {//Too close to look so far up or down + continue; + } + dist = VectorLengthSquared( diffVec ); + //Some priority to more interesting points + //dist -= ((int)level.interestPoints[i].lookMode * 5) * ((int)level.interestPoints[i].lookMode * 5); + if ( dist < MAX_INTEREST_DIST && dist < bestDist ) + { + if ( G_ClearLineOfSight( eyes, level.interestPoints[i].origin, self->s.number, MASK_OPAQUE ) ) + { + bestDist = dist; + bestPoint = i; + } + } + } + } + if ( bestPoint != ENTITYNUM_NONE && level.interestPoints[bestPoint].target ) + { + G_UseTargets2( self, self, level.interestPoints[bestPoint].target ); + } + return bestPoint; +} + +/*QUAKED target_interest (1 0.8 0.5) (-4 -4 -4) (4 4 4) +A point that a squadmate will look at if standing still + +target - thing to fire when someone looks at this thing +*/ + +void SP_target_interest( gentity_t *self ) +{//FIXME: rename point_interest + if(level.numInterestPoints >= MAX_INTEREST_POINTS) + { + Com_Printf("ERROR: Too many interest points, limit is %d\n", MAX_INTEREST_POINTS); + G_FreeEntity(self); + return; + } + + VectorCopy(self->r.currentOrigin, level.interestPoints[level.numInterestPoints].origin); + + if(self->target && self->target[0]) + { + level.interestPoints[level.numInterestPoints].target = G_NewString( self->target ); + } + + level.numInterestPoints++; + + G_FreeEntity(self); +} diff --git a/code/game/NPC_sounds.c b/code/game/NPC_sounds.c new file mode 100644 index 0000000..2d7e041 --- /dev/null +++ b/code/game/NPC_sounds.c @@ -0,0 +1,93 @@ +//NPC_sounds.cpp +#include "b_local.h" +#include "../icarus/Q3_Interface.h" + +/* +void NPC_AngerSound (void) +{ + if(NPCInfo->investigateSoundDebounceTime) + return; + + NPCInfo->investigateSoundDebounceTime = 1; + +// switch((int)NPC->client->race) +// { +// case RACE_KLINGON: + //G_Sound(NPC, G_SoundIndex(va("sound/mgtest/klingon/talk%d.wav", Q_irand(1, 4)))); +// break; +// } +} +*/ + +extern void G_SpeechEvent( gentity_t *self, int event ); +void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ) +{ + if ( !self->NPC ) + { + return; + } + + if ( !self->client || self->client->ps.pm_type >= PM_DEAD ) + { + return; + } + + if ( self->NPC->blockedSpeechDebounceTime > level.time ) + { + return; + } + + if ( trap_ICARUS_TaskIDPending( self, TID_CHAN_VOICE ) ) + { + return; + } + + + if ( (self->NPC->scriptFlags&SCF_NO_COMBAT_TALK) && ( (event >= EV_ANGER1 && event <= EV_VICTORY3) || (event >= EV_CHASE1 && event <= EV_SUSPICIOUS5) ) )//(event < EV_FF_1A || event > EV_FF_3C) && (event < EV_RESPOND1 || event > EV_MISSION3) ) + { + return; + } + + if ( (self->NPC->scriptFlags&SCF_NO_ALERT_TALK) && (event >= EV_GIVEUP1 && event <= EV_SUSPICIOUS5) ) + { + return; + } + //FIXME: Also needs to check for teammates. Don't want + // everyone babbling at once + + //NOTE: was losing too many speech events, so we do it directly now, screw networking! + //G_AddEvent( self, event, 0 ); + G_SpeechEvent( self, event ); + + //won't speak again for 5 seconds (unless otherwise specified) + self->NPC->blockedSpeechDebounceTime = level.time + ((speakDebounceTime==0) ? 5000 : speakDebounceTime); +} + +void NPC_PlayConfusionSound( gentity_t *self ) +{ + if ( self->health > 0 ) + { + if ( self->enemy ||//was mad + !TIMER_Done( self, "enemyLastVisible" ) ||//saw something suspicious + self->client->renderInfo.lookTarget == 0//was looking at player + ) + { + self->NPC->blockedSpeechDebounceTime = 0;//make sure we say this + G_AddVoiceEvent( self, Q_irand( EV_CONFUSE2, EV_CONFUSE3 ), 2000 ); + } + else if ( self->NPC && self->NPC->investigateDebounceTime+self->NPC->pauseTime > level.time )//was checking something out + { + self->NPC->blockedSpeechDebounceTime = 0;//make sure we say this + G_AddVoiceEvent( self, EV_CONFUSE1, 2000 ); + } + //G_AddVoiceEvent( self, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } + //reset him to be totally unaware again + TIMER_Set( self, "enemyLastVisible", 0 ); + self->NPC->tempBehavior = BS_DEFAULT; + + //self->NPC->behaviorState = BS_PATROL; + G_ClearEnemy( self );//FIXME: or just self->enemy = NULL;? + + self->NPC->investigateCount = 0; +} diff --git a/code/game/NPC_spawn.c b/code/game/NPC_spawn.c new file mode 100644 index 0000000..80457cc --- /dev/null +++ b/code/game/NPC_spawn.c @@ -0,0 +1,4237 @@ +//b_spawn.cpp +//added by MCG +#include "b_local.h" +#include "anims.h" +#include "w_saber.h" +#include "bg_saga.h" +#include "bg_vehicles.h" +#include "g_nav.h" + +extern void G_DebugPrint( int level, const char *format, ... ); + +extern qboolean G_CheckInSolid (gentity_t *self, qboolean fix); +extern void ClientUserinfoChanged( int clientNum ); +extern qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ); +extern void Jedi_Cloak( gentity_t *self ); + +//extern void FX_BorgTeleport( vec3_t org ); + +extern void Q3_SetParm (int entID, int parmNum, const char *parmValue); +extern team_t TranslateTeamName( const char *name ); +extern char *TeamNames[TEAM_NUM_TEAMS]; + +//extern void CG_ShimmeryThing_Spawner( vec3_t start, vec3_t end, float radius, qboolean taper, int duration ); + +//extern void NPC_StasisSpawnEffect( gentity_t *ent ); + +extern void PM_SetTorsoAnimTimer( gentity_t *ent, int *torsoAnimTimer, int time ); +extern void PM_SetLegsAnimTimer( gentity_t *ent, int *legsAnimTimer, int time ); + +extern void ST_ClearTimers( gentity_t *ent ); +extern void Jedi_ClearTimers( gentity_t *ent ); +extern void NPC_ShadowTrooper_Precache( void ); +extern void NPC_Gonk_Precache( void ); +extern void NPC_Mouse_Precache( void ); +extern void NPC_Seeker_Precache( void ); +extern void NPC_Remote_Precache( void ); +extern void NPC_R2D2_Precache(void); +extern void NPC_R5D2_Precache(void); +extern void NPC_Probe_Precache(void); +extern void NPC_Interrogator_Precache(gentity_t *self); +extern void NPC_MineMonster_Precache( void ); +extern void NPC_Howler_Precache( void ); +extern void NPC_ATST_Precache(void); +extern void NPC_Sentry_Precache(void); +extern void NPC_Mark1_Precache(void); +extern void NPC_Mark2_Precache(void); +extern void NPC_GalakMech_Precache( void ); +extern void NPC_GalakMech_Init( gentity_t *ent ); +extern void NPC_Protocol_Precache( void ); +extern void Boba_Precache( void ); +extern void NPC_Wampa_Precache( void ); +gentity_t *NPC_SpawnType( gentity_t *ent, char *npc_type, char *targetname, qboolean isVehicle ); + +extern void Rancor_SetBolts( gentity_t *self ); +extern void Wampa_SetBolts( gentity_t *self ); + +#define NSF_DROP_TO_FLOOR 16 + +// PAIN functions... +// +extern void funcBBrushPain (gentity_t *self, gentity_t *attacker, int damage); +extern void misc_model_breakable_pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void station_pain (gentity_t *self, gentity_t *attacker, int damage); +extern void func_usable_pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_ATST_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_ST_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Jedi_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Droid_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Probe_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_MineMonster_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Howler_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Seeker_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Remote_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void emplaced_gun_pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Mark1_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_GM_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Sentry_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Mark2_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void PlayerPain (gentity_t *self, gentity_t *attacker, int damage); +extern void GasBurst (gentity_t *self, gentity_t *attacker, int damage); +extern void CrystalCratePain (gentity_t *self, gentity_t *attacker, int damage); +extern void TurretPain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Wampa_Pain (gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Rancor_Pain (gentity_t *self, gentity_t *attacker, int damage); + + +//void HirogenAlpha_Precache( void ); + +int WP_SetSaberModel( gclient_t *client, class_t npcClass ) +{ + //rwwFIXMEFIXME: Do something here, need to let the client know. + return 1; +} + +/* +------------------------- +NPC_PainFunc +------------------------- +*/ +typedef void (PAIN_FUNC) (gentity_t *self, gentity_t *attacker, int damage); + +PAIN_FUNC *NPC_PainFunc( gentity_t *ent ) +{ + void (*func)(gentity_t *self, gentity_t *attacker, int damage); + + if ( ent->client->ps.weapon == WP_SABER ) + { + func = NPC_Jedi_Pain; + } + else + { + // team no longer indicates race/species, use NPC_class to determine different npc types + /* + switch ( ent->client->playerTeam ) + { + default: + func = painF_NPC_Pain; + break; + } + */ + switch( ent->client->NPC_class ) + { + // troopers get special pain + case CLASS_STORMTROOPER: + case CLASS_SWAMPTROOPER: + func = NPC_ST_Pain; + break; + + case CLASS_SEEKER: + func = NPC_Seeker_Pain; + break; + + case CLASS_REMOTE: + func = NPC_Remote_Pain; + break; + + case CLASS_MINEMONSTER: + func = NPC_MineMonster_Pain; + break; + + case CLASS_HOWLER: + func = NPC_Howler_Pain; + break; + + // all other droids, did I miss any? + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_MOUSE: + case CLASS_PROTOCOL: + case CLASS_INTERROGATOR: + func = NPC_Droid_Pain; + break; + case CLASS_PROBE: + func = NPC_Probe_Pain; + break; + + case CLASS_SENTRY: + func = NPC_Sentry_Pain; + break; + case CLASS_MARK1: + func = NPC_Mark1_Pain; + break; + case CLASS_MARK2: + func = NPC_Mark2_Pain; + break; + case CLASS_ATST: + func = NPC_ATST_Pain; + break; + case CLASS_GALAKMECH: + func = NPC_GM_Pain; + break; + case CLASS_RANCOR: + func = NPC_Rancor_Pain; + break; + case CLASS_WAMPA: + func = NPC_Wampa_Pain; + break; + // everyone else gets the normal pain func + default: + func = NPC_Pain; + break; + } + + } + + return func; +} + + +/* +------------------------- +NPC_TouchFunc +------------------------- +*/ +typedef void (TOUCH_FUNC) (gentity_t *self, gentity_t *other, trace_t *trace); + +TOUCH_FUNC *NPC_TouchFunc( gentity_t *ent ) +{ + void (*func)(gentity_t *self, gentity_t *other, trace_t *trace); + + func = NPC_Touch; + + return func; +} + +/* +------------------------- +NPC_SetMiscDefaultData +------------------------- +*/ + +extern void G_CreateG2AttachedWeaponModel( gentity_t *ent, const char *weaponModel, int boltNum, int weaponNum ); +void NPC_SetMiscDefaultData( gentity_t *ent ) +{ + if ( ent->spawnflags & SFB_CINEMATIC ) + {//if a cinematic guy, default us to wait bState + ent->NPC->behaviorState = BS_CINEMATIC; + } + if ( ent->client->NPC_class == CLASS_BOBAFETT ) + {//set some stuff, precache + Boba_Precache(); + ent->client->ps.fd.forcePowersKnown |= ( 1 << FP_LEVITATION ); + ent->client->ps.fd.forcePowerLevel[FP_LEVITATION] = FORCE_LEVEL_3; + ent->client->ps.fd.forcePower = 100; + ent->NPC->scriptFlags |= (SCF_ALT_FIRE|SCF_NO_GROUPS); + } + //if ( !Q_stricmp( "atst_vehicle", ent->NPC_type ) )//FIXME: has to be a better, easier way to tell this, no? + if (ent->s.NPC_class == CLASS_VEHICLE && ent->m_pVehicle) + { + ent->s.g2radius = 255;//MAX for this value, was (ent->r.maxs[2]-ent->r.mins[2]), which is 272 or something + + if (ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER) + { + ent->mass = 2000;//??? + ent->flags |= (FL_SHIELDED|FL_NO_KNOCKBACK); + ent->pain = NPC_ATST_Pain; + } + //turn the damn hatch cover on and LEAVE it on + trap_G2API_SetSurfaceOnOff( ent->ghoul2, "head_hatchcover", 0/*TURN_ON*/ ); + } + if ( !Q_stricmp( "wampa", ent->NPC_type ) ) + {//FIXME: extern this into NPC.cfg? + Wampa_SetBolts( ent ); + ent->s.g2radius = 80;//??? + ent->mass = 300;//??? + ent->flags |= FL_NO_KNOCKBACK; + ent->pain = NPC_Wampa_Pain; + } + if ( ent->client->NPC_class == CLASS_RANCOR ) + { + Rancor_SetBolts( ent ); + ent->s.g2radius = 255;//MAX for this value, was (ent->r.maxs[2]-ent->r.mins[2]), which is 272 or something + ent->mass = 1000;//??? + ent->flags |= FL_NO_KNOCKBACK; + ent->pain = NPC_Rancor_Pain; + ent->health *= 4; + } + if ( !Q_stricmp( "Yoda", ent->NPC_type ) ) + {//FIXME: extern this into NPC.cfg? + ent->NPC->scriptFlags |= SCF_NO_FORCE;//force powers don't work on him + } + if ( !Q_stricmp( "emperor", ent->NPC_type ) ) + {//FIXME: extern this into NPC.cfg? + ent->NPC->scriptFlags |= SCF_DONT_FIRE;//so he uses only force powers + } + //================== +// if ( ent->client->ps.saber[0].type != SABER_NONE ) + if (ent->client->ps.weapon == WP_SABER) //rwwFIXMEFIXME: is this going to work? + {//if I'm equipped with a saber, initialize it (them) + // ent->client->ps.SaberDeactivate(); + // ent->client->ps.SetSaberLength( 0 ); + WP_SaberInitBladeData( ent ); + ent->client->ps.saberHolstered = 2; + // G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[0].model, ent->handRBolt, 0 ); + // if ( ent->client->ps.dualSabers ) + // { + // G_CreateG2AttachedWeaponModel( ent, ent->client->ps.saber[1].model, ent->handLBolt, 1 ); + // } + Jedi_ClearTimers( ent ); + } + if ( ent->client->ps.fd.forcePowersKnown != 0 ) + { + WP_InitForcePowers( ent ); + WP_SpawnInitForcePowers(ent); //rww + } + if ( ent->client->NPC_class == CLASS_SEEKER ) + { + ent->NPC->defaultBehavior = BS_DEFAULT; + ent->client->ps.gravity = 0; + ent->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; + ent->client->ps.eFlags2 |= EF2_FLYING; + ent->count = 30; // SEEKER shot ammo count + } + //***I'm not sure whether I should leave this as a TEAM_ switch, I think NPC_class may be more appropriate - dmv + switch(ent->client->playerTeam) + { + case NPCTEAM_PLAYER: + //ent->flags |= FL_NO_KNOCKBACK; + if ( ent->client->NPC_class == CLASS_JEDI || ent->client->NPC_class == CLASS_LUKE ) + {//good jedi + ent->client->enemyTeam = NPCTEAM_ENEMY; + if ( ent->spawnflags & JSF_AMBUSH ) + {//ambusher + ent->NPC->scriptFlags |= SCF_IGNORE_ALERTS; + ent->client->noclip = qtrue;//hang + } + } + else + { + /* + if (ent->client->ps.weapon != WP_NONE) + { + G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + } + */ + switch ( ent->client->ps.weapon ) + { + case WP_BRYAR_PISTOL://FIXME: new weapon: imp blaster pistol + // case WP_BLASTER_PISTOL: + case WP_DISRUPTOR: + case WP_BOWCASTER: + case WP_REPEATER: + case WP_DEMP2: + case WP_FLECHETTE: + case WP_ROCKET_LAUNCHER: + default: + break; + case WP_THERMAL: + case WP_BLASTER: + //FIXME: health in NPCs.cfg, and not all blaster users are stormtroopers + //ent->health = 25; + //FIXME: not necc. a ST + ST_ClearTimers( ent ); + if ( ent->NPC->rank >= RANK_LT || ent->client->ps.weapon == WP_THERMAL ) + {//officers, grenade-throwers use alt-fire + //ent->health = 50; + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + break; + } + } + if ( ent->client->NPC_class == CLASS_KYLE || ent->client->NPC_class == CLASS_VEHICLE || (ent->spawnflags & SFB_CINEMATIC) ) + { + ent->NPC->defaultBehavior = BS_CINEMATIC; + } + else + { + /* + ent->NPC->defaultBehavior = BS_FOLLOW_LEADER; + ent->client->leader = &g_entities[0]; + */ + } + break; + + case NPCTEAM_NEUTRAL: + + if ( Q_stricmp( ent->NPC_type, "gonk" ) == 0 ) + { + // I guess we generically make them player usable + ent->r.svFlags |= SVF_PLAYER_USABLE; + + // Not even sure if we want to give different levels of batteries? ...Or even that these are the values we'd want to use. + /* + switch ( g_spskill.integer ) + { + case 0: // EASY + ent->client->ps.batteryCharge = MAX_BATTERIES * 0.8f; + break; + case 1: // MEDIUM + ent->client->ps.batteryCharge = MAX_BATTERIES * 0.75f; + break; + default : + case 2: // HARD + ent->client->ps.batteryCharge = MAX_BATTERIES * 0.5f; + break; + }*/ + //rwwFIXMEFIXME: Make use of this. + } + break; + + case NPCTEAM_ENEMY: + { + ent->NPC->defaultBehavior = BS_DEFAULT; + if ( ent->client->NPC_class == CLASS_SHADOWTROOPER ) + {//FIXME: a spawnflag? + Jedi_Cloak( ent ); + } + if( ent->client->NPC_class == CLASS_TAVION || + ent->client->NPC_class == CLASS_REBORN || + ent->client->NPC_class == CLASS_DESANN || + ent->client->NPC_class == CLASS_SHADOWTROOPER ) + { + ent->client->enemyTeam = NPCTEAM_PLAYER; + if ( ent->spawnflags & JSF_AMBUSH ) + {//ambusher + ent->NPC->scriptFlags |= SCF_IGNORE_ALERTS; + ent->client->noclip = qtrue;//hang + } + } + else if( ent->client->NPC_class == CLASS_PROBE || ent->client->NPC_class == CLASS_REMOTE || + ent->client->NPC_class == CLASS_INTERROGATOR || ent->client->NPC_class == CLASS_SENTRY) + { + ent->NPC->defaultBehavior = BS_DEFAULT; + ent->client->ps.gravity = 0; + ent->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; + ent->client->ps.eFlags2 |= EF2_FLYING; + } + else + { + // G_CreateG2AttachedWeaponModel( ent, weaponData[ent->client->ps.weapon].weaponMdl, ent->handRBolt, 0 ); + switch ( ent->client->ps.weapon ) + { + case WP_BRYAR_PISTOL: + break; + // case WP_BLASTER_PISTOL: + // break; + case WP_DISRUPTOR: + //Sniper + //ent->NPC->scriptFlags |= SCF_ALT_FIRE;//FIXME: use primary fire sometimes? Up close? Different class of NPC? + break; + case WP_BOWCASTER: + break; + case WP_REPEATER: + //machine-gunner + break; + case WP_DEMP2: + break; + case WP_FLECHETTE: + //shotgunner + if ( !Q_stricmp( "stofficeralt", ent->NPC_type ) ) + { + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + break; + case WP_ROCKET_LAUNCHER: + break; + case WP_THERMAL: + //Gran, use main, bouncy fire +// ent->NPC->scriptFlags |= SCF_ALT_FIRE; + break; + case WP_STUN_BATON: + break; + default: + case WP_BLASTER: + //FIXME: health in NPCs.cfg, and not all blaster users are stormtroopers + //FIXME: not necc. a ST + ST_ClearTimers( ent ); + if ( ent->NPC->rank >= RANK_COMMANDER ) + {//commanders use alt-fire + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + if ( !Q_stricmp( "rodian2", ent->NPC_type ) ) + { + //ent->NPC->scriptFlags |= SCF_ALT_FIRE; + } + break; + } + if ( !Q_stricmp( "galak_mech", ent->NPC_type ) ) + {//starts with armor + NPC_GalakMech_Init( ent ); + } + } + } + break; + + default: + break; + } + + + if ( ent->client->NPC_class == CLASS_SEEKER + && ent->activator ) + {//assume my teams are already set correctly + } + else + { + //for siege, want "bad" npc's allied with the "bad" team + if (g_gametype.integer == GT_SIEGE && ent->s.NPC_class != CLASS_VEHICLE) + { + if (ent->client->enemyTeam == NPCTEAM_PLAYER) + { + ent->client->sess.sessionTeam = SIEGETEAM_TEAM1; + } + else if (ent->client->enemyTeam == NPCTEAM_ENEMY) + { + ent->client->sess.sessionTeam = SIEGETEAM_TEAM2; + } + else + { + ent->client->sess.sessionTeam = TEAM_FREE; + } + } + } + + if ( ent->client->NPC_class == CLASS_ATST || ent->client->NPC_class == CLASS_MARK1 ) // chris/steve/kevin requested that the mark1 be shielded also + { + ent->flags |= (FL_SHIELDED|FL_NO_KNOCKBACK); + } +} + +/* +------------------------- +NPC_WeaponsForTeam +------------------------- +*/ + +int NPC_WeaponsForTeam( team_t team, int spawnflags, const char *NPC_type ) +{ + //*** not sure how to handle this, should I pass in class instead of team and go from there? - dmv + switch(team) + { + // no longer exists +// case TEAM_BORG: +// break; + +// case TEAM_HIROGEN: +// if( Q_stricmp( "hirogenalpha", NPC_type ) == 0 ) +// return ( 1 << WP_BLASTER); + //Falls through + +// case TEAM_KLINGON: + + //NOTENOTE: Falls through + +// case TEAM_IMPERIAL: + case NPCTEAM_ENEMY: + if ( Q_stricmp( "tavion", NPC_type ) == 0 || + Q_strncmp( "reborn", NPC_type, 6 ) == 0 || + Q_stricmp( "desann", NPC_type ) == 0 || + Q_strncmp( "shadowtrooper", NPC_type, 13 ) == 0 ) + return ( 1 << WP_SABER); +// return ( 1 << WP_IMPERIAL_BLADE); + //NOTENOTE: Falls through if not a knife user + +// case TEAM_SCAVENGERS: +// case TEAM_MALON: + //FIXME: default weapon in npc config? + if ( Q_strncmp( "stofficer", NPC_type, 9 ) == 0 ) + { + return ( 1 << WP_FLECHETTE); + } + if ( Q_stricmp( "stcommander", NPC_type ) == 0 ) + { + return ( 1 << WP_REPEATER); + } + if ( Q_stricmp( "swamptrooper", NPC_type ) == 0 ) + { + return ( 1 << WP_FLECHETTE); + } + if ( Q_stricmp( "swamptrooper2", NPC_type ) == 0 ) + { + return ( 1 << WP_REPEATER); + } + if ( Q_stricmp( "rockettrooper", NPC_type ) == 0 ) + { + return ( 1 << WP_ROCKET_LAUNCHER); + } + if ( Q_strncmp( "shadowtrooper", NPC_type, 13 ) == 0 ) + { + return ( 1 << WP_SABER);//|( 1 << WP_RAPID_CONCUSSION)? + } + if ( Q_stricmp( "imperial", NPC_type ) == 0 ) + { + //return ( 1 << WP_BLASTER_PISTOL); + return ( 1 << WP_BLASTER); + } + if ( Q_strncmp( "impworker", NPC_type, 9 ) == 0 ) + { + //return ( 1 << WP_BLASTER_PISTOL); + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "stormpilot", NPC_type ) == 0 ) + { + //return ( 1 << WP_BLASTER_PISTOL); + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "galak", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "galak_mech", NPC_type ) == 0 ) + { + return ( 1 << WP_REPEATER); + } + if ( Q_strncmp( "ugnaught", NPC_type, 8 ) == 0 ) + { + return WP_NONE; + } + if ( Q_stricmp( "granshooter", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "granboxer", NPC_type ) == 0 ) + { + return ( 1 << WP_STUN_BATON); + } + if ( Q_strncmp( "gran", NPC_type, 4 ) == 0 ) + { + return (( 1 << WP_THERMAL)|( 1 << WP_STUN_BATON)); + } + if ( Q_stricmp( "rodian", NPC_type ) == 0 ) + { + return ( 1 << WP_DISRUPTOR); + } + if ( Q_stricmp( "rodian2", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + + if (( Q_stricmp( "interrogator",NPC_type) == 0) || ( Q_stricmp( "sentry",NPC_type) == 0) || (Q_strncmp( "protocol",NPC_type,8) == 0) ) + { + return WP_NONE; + } + + if ( Q_strncmp( "weequay", NPC_type, 7 ) == 0 ) + { + return ( 1 << WP_BOWCASTER);//|( 1 << WP_STAFF )(FIXME: new weap?) + } + if ( Q_stricmp( "impofficer", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if ( Q_stricmp( "impcommander", NPC_type ) == 0 ) + { + return ( 1 << WP_BLASTER); + } + if (( Q_stricmp( "probe", NPC_type ) == 0 ) || ( Q_stricmp( "seeker", NPC_type ) == 0 )) + { + //return ( 1 << WP_BOT_LASER); + return 0; + } + if ( Q_stricmp( "remote", NPC_type ) == 0 ) + { + //return ( 1 << WP_BOT_LASER ); + return 0; + } + if ( Q_stricmp( "trandoshan", NPC_type ) == 0 ) + { + return (1<client->playerTeam, ent->spawnflags, ent->NPC_type ); + + ent->client->ps.stats[STAT_WEAPONS] = 0; + for ( curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ ) + { + if ( (weapons & ( 1 << curWeap )) ) + { + ent->client->ps.stats[STAT_WEAPONS] |= ( 1 << curWeap ); +// RegisterItem( FindItemForWeapon( (weapon_t)(curWeap) ) ); //precache the weapon + //rwwFIXMEFIXME: Precache + ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[curWeap].ammoIndex] = 100;//FIXME: max ammo + + if ( bestWeap == WP_SABER ) + { + // still want to register other weapons -- force saber to be best weap + continue; + } + + if ( curWeap == WP_STUN_BATON ) + { + if ( bestWeap == WP_NONE ) + {// We'll only consider giving Melee since we haven't found anything better yet. + bestWeap = curWeap; + } + } + else if ( curWeap > bestWeap || bestWeap == WP_STUN_BATON ) + { + // This will never override saber as best weap. Also will override WP_STUN_BATON if something better comes later in the list + bestWeap = curWeap; + } + } + } + + ent->client->ps.weapon = bestWeap; +} + +/* +------------------------- +NPC_SpawnEffect + + NOTE: Make sure any effects called here have their models, tga's and sounds precached in + CG_RegisterNPCEffects in cg_player.cpp +------------------------- +*/ + +void NPC_SpawnEffect (gentity_t *ent) +{ +} + +//-------------------------------------------------------------- +// NPC_SetFX_SpawnStates +// +// Set up any special parms for spawn effects +//-------------------------------------------------------------- +void NPC_SetFX_SpawnStates( gentity_t *ent ) +{ + if ( !(ent->NPC->aiFlags&NPCAI_CUSTOM_GRAVITY) ) + { + ent->client->ps.gravity = g_gravity.value; + } +} + +/* +================ +NPC_SpotWouldTelefrag + +================ +*/ +qboolean NPC_SpotWouldTelefrag( gentity_t *npc ) +{ + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( npc->r.currentOrigin, npc->r.mins, mins ); + VectorAdd( npc->r.currentOrigin, npc->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + if (hit->inuse + && hit->client + && hit->s.number != npc->s.number + && (hit->r.contents&MASK_NPCSOLID) + && hit->s.number != npc->r.ownerNum + && hit->r.ownerNum != npc->s.number) + { + return qtrue; + } + + } + + return qfalse; +} + +//-------------------------------------------------------------- +void NPC_Begin (gentity_t *ent) +{ + vec3_t spawn_origin, spawn_angles; + gclient_t *client; + usercmd_t ucmd; + gentity_t *spawnPoint = NULL; + + memset( &ucmd, 0, sizeof( ucmd ) ); + + if ( !(ent->spawnflags & SFB_NOTSOLID) ) + {//No NPCs should telefrag + if (NPC_SpotWouldTelefrag(ent)) + { + if ( ent->wait < 0 ) + {//remove yourself + G_DebugPrint( WL_DEBUG, "NPC %s could not spawn, firing target3 (%s) and removing self\n", ent->targetname, ent->target3 ); + //Fire off our target3 + G_UseTargets2( ent, ent, ent->target3 ); + + //Kill us + ent->think = G_FreeEntity; + ent->nextthink = level.time + 100; + } + else + { + G_DebugPrint( WL_DEBUG, "NPC %s could not spawn, waiting %4.2 secs to try again\n", ent->targetname, ent->wait/1000.0f ); + ent->think = NPC_Begin; + ent->nextthink = level.time + ent->wait;//try again in half a second + } + return; + } + } + //Spawn effect + NPC_SpawnEffect( ent ); + + VectorCopy( ent->client->ps.origin, spawn_origin); + VectorCopy( ent->s.angles, spawn_angles); + spawn_angles[YAW] = ent->NPC->desiredYaw; + + client = ent->client; + + // increment the spawncount so the client will detect the respawn + client->ps.persistant[PERS_SPAWN_COUNT]++; + + client->airOutTime = level.time + 12000; + + client->ps.clientNum = ent->s.number; + // clear entity values + + if ( ent->health ) // Was health supplied in map + { + client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = ent->health; + } + else if ( ent->NPC->stats.health ) // Was health supplied in NPC.cfg? + { + + if ( ent->client->NPC_class != CLASS_REBORN + && ent->client->NPC_class != CLASS_SHADOWTROOPER + //&& ent->client->NPC_class != CLASS_TAVION + //&& ent->client->NPC_class != CLASS_DESANN + && ent->client->NPC_class != CLASS_JEDI ) + {// up everyone except jedi + ent->NPC->stats.health += ent->NPC->stats.health/4 * g_spskill.integer; // 100% on easy, 125% on medium, 150% on hard + } + + client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = ent->NPC->stats.health; + } + else + { + client->pers.maxHealth = client->ps.stats[STAT_MAX_HEALTH] = 100; + } + + if ( !Q_stricmp( "rodian", ent->NPC_type ) ) + {//sniper + //NOTE: this will get overridden by any aim settings in their spawnscripts + switch ( g_spskill.integer ) + { + case 0: + ent->NPC->stats.aim = 1; + break; + case 1: + ent->NPC->stats.aim = Q_irand( 2, 3 ); + break; + case 2: + ent->NPC->stats.aim = Q_irand( 3, 4 ); + break; + } + } + else if ( ent->client->NPC_class == CLASS_STORMTROOPER + || ent->client->NPC_class == CLASS_SWAMPTROOPER + || ent->client->NPC_class == CLASS_IMPWORKER + || !Q_stricmp( "rodian2", ent->NPC_type ) ) + {//tweak yawspeed for these NPCs based on difficulty + switch ( g_spskill.integer ) + { + case 0: + ent->NPC->stats.yawSpeed *= 0.75f; + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + { + ent->NPC->stats.aim -= Q_irand( 3, 6 ); + } + break; + case 1: + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + { + ent->NPC->stats.aim -= Q_irand( 2, 4 ); + } + break; + case 2: + ent->NPC->stats.yawSpeed *= 1.5f; + if ( ent->client->NPC_class == CLASS_IMPWORKER ) + { + ent->NPC->stats.aim -= Q_irand( 0, 2 ); + } + break; + } + } + else if ( ent->client->NPC_class == CLASS_REBORN + || ent->client->NPC_class == CLASS_SHADOWTROOPER ) + { + switch ( g_spskill.integer ) + { + case 1: + ent->NPC->stats.yawSpeed *= 1.25f; + break; + case 2: + ent->NPC->stats.yawSpeed *= 1.5f; + break; + } + } + + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->mass = 10; + ent->takedamage = qtrue; + ent->inuse = qtrue; + ent->classname = "NPC"; +// if ( ent->client->race == RACE_HOLOGRAM ) +// {//can shoot through holograms, but not walk through them +// ent->contents = CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|CONTENTS_ITEM;//contents_corspe to make them show up in ID and use traces +// ent->clipmask = MASK_NPCSOLID; +// } else + if(!(ent->spawnflags & SFB_NOTSOLID)) + { + ent->r.contents = CONTENTS_BODY; + ent->clipmask = MASK_NPCSOLID; + } + else + { + ent->r.contents = 0; + ent->clipmask = MASK_NPCSOLID&~CONTENTS_BODY; + } + /* + if(!ent->client->moveType)//Static? + { + ent->client->moveType = MT_RUNJUMP; + } + */ + //rwwFIXMEFIXME: movetype support + + ent->die = player_die; + ent->waterlevel = 0; + ent->watertype = 0; + ent->client->ps.rocketLockIndex = ENTITYNUM_NONE; + ent->client->ps.rocketLockTime = 0; + + //visible to player and NPCs + if ( ent->client->NPC_class != CLASS_R2D2 && + ent->client->NPC_class != CLASS_R5D2 && + ent->client->NPC_class != CLASS_MOUSE && + ent->client->NPC_class != CLASS_GONK && + ent->client->NPC_class != CLASS_PROTOCOL ) + { + ent->flags &= ~FL_NOTARGET; + } + ent->s.eFlags &= ~EF_NODRAW; + + NPC_SetFX_SpawnStates( ent ); + + //client->ps.friction = 6; + //rwwFIXMEFIXME: per ent friction? + + if ( ent->client->ps.weapon == WP_NONE ) + {//not set by the NPCs.cfg + NPC_SetWeapons(ent); + } + //select the weapon + ent->NPC->currentAmmo = ent->client->ps.ammo[weaponData[ent->client->ps.weapon].ammoIndex]; + ent->client->ps.weaponstate = WEAPON_IDLE; + ChangeWeapon( ent, ent->client->ps.weapon ); + + 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; + + // clear entity state values + //ent->s.eType = ET_PLAYER; + ent->s.eType = ET_NPC; +// ent->s.skinNum = ent - g_entities - 1; // used as index to get custom models + + VectorCopy (spawn_origin, ent->s.origin); +// ent->s.origin[2] += 1; // make sure off ground + + SetClientViewAngle( ent, spawn_angles ); + client->renderInfo.lookTarget = ENTITYNUM_NONE; + + if(!(ent->spawnflags & 64)) + { + G_KillBox( ent ); + trap_LinkEntity (ent); + } + + // 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.value * 1000; + client->latched_buttons = 0; + if ( ent->s.m_iVehicleNum ) + {//I'm an NPC in a vehicle (or a vehicle), I already have owner set + } + else if ( client->NPC_class == CLASS_SEEKER && ent->activator != NULL ) + {//somebody else "owns" me + ent->s.owner = ent->r.ownerNum = ent->activator->s.number; + } + else + { + ent->s.owner = ENTITYNUM_NONE; + } + + // set default animations + if ( ent->client->NPC_class != CLASS_VEHICLE ) + { + NPC_SetAnim( ent, SETANIM_BOTH, BOTH_STAND1, SETANIM_FLAG_NORMAL ); + } + + if( spawnPoint ) + { + // fire the targets of the spawn point + G_UseTargets( spawnPoint, ent ); + } + + //ICARUS include + trap_ICARUS_InitEnt( ent ); + +//==NPC initialization + SetNPCGlobals( ent ); + + ent->enemy = NULL; + NPCInfo->timeOfDeath = 0; + NPCInfo->shotTime = 0; + NPC_ClearGoal(); + NPC_ChangeWeapon( ent->client->ps.weapon ); + +//==Final NPC initialization + ent->pain = NPC_PainFunc( ent ); //painF_NPC_Pain; + ent->touch = NPC_TouchFunc( ent ); //touchF_NPC_Touch; +// ent->NPC->side = 1; + + ent->client->ps.ping = ent->NPC->stats.reactions * 50; + + //MCG - Begin: NPC hacks + //FIXME: Set the team correctly + if (ent->s.NPC_class != CLASS_VEHICLE || g_gametype.integer != GT_SIEGE) + { + ent->client->ps.persistant[PERS_TEAM] = ent->client->playerTeam; + } + + ent->use = NPC_Use; + ent->think = NPC_Think; + ent->nextthink = level.time + FRAMETIME + Q_irand(0, 100); + + NPC_SetMiscDefaultData( ent ); + if ( ent->health <= 0 ) + { + //ORIGINAL ID: health will count down towards max_health + ent->health = client->ps.stats[STAT_HEALTH] = ent->client->pers.maxHealth; + } + else + { + client->ps.stats[STAT_HEALTH] = ent->health; + } + + if (ent->s.shouldtarget) + { + ent->maxHealth = ent->health; + G_ScaleNetHealth(ent); + } + + ChangeWeapon( ent, ent->client->ps.weapon );//yes, again... sigh + + if ( !(ent->spawnflags & SFB_STARTINSOLID) ) + {//Not okay to start in solid + G_CheckInSolid( ent, qtrue ); + } + VectorClear( ent->NPC->lastClearOrigin ); + + //Run a script if you have one assigned to you + if ( G_ActivateBehavior( ent, BSET_SPAWN ) ) + { + trap_ICARUS_MaintainTaskManager(ent->s.number); + } + + VectorCopy( ent->r.currentOrigin, ent->client->renderInfo.eyePoint ); + + // run a client frame to drop exactly to the floor, + // initialize animations and other things + memset( &ucmd, 0, sizeof( ucmd ) ); + //_VectorCopy( client->pers.cmd_angles, ucmd.angles ); + VectorCopy(client->pers.cmd.angles, ucmd.angles); + + ent->client->ps.groundEntityNum = ENTITYNUM_NONE; + + if ( ent->NPC->aiFlags & NPCAI_MATCHPLAYERWEAPON ) + { + //G_MatchPlayerWeapon( ent ); + //rwwFIXMEFIXME: Use this? Probably doesn't really matter for MP. + } + + ClientThink( ent->s.number, &ucmd ); + + trap_LinkEntity( ent ); + + if ( ent->client->playerTeam == NPCTEAM_ENEMY ) + {//valid enemy spawned + if ( !(ent->spawnflags&SFB_CINEMATIC) && ent->NPC->behaviorState != BS_CINEMATIC ) + {//not a cinematic enemy + if ( g_entities[0].client ) + { + //g_entities[0].client->sess.missionStats.enemiesSpawned++; + //rwwFIXMEFIXME: Do something here? And this is a rather strange place to be storing + //this sort of data. + } + } + } + ent->waypoint = ent->NPC->homeWp = WAYPOINT_NONE; + + if ( ent->m_pVehicle ) + {//a vehicle + //check for droidunit + if ( ent->m_pVehicle->m_iDroidUnitTag != -1 ) + { + char *droidNPCType = NULL; + gentity_t *droidEnt = NULL; + if ( ent->model2 + && ent->model2[0] ) + {//specified on the NPC_Vehicle spawner ent + droidNPCType = ent->model2; + } + else if ( ent->m_pVehicle->m_pVehicleInfo->droidNPC + && ent->m_pVehicle->m_pVehicleInfo->droidNPC[0] ) + {//specified in the vehicle's .veh file + droidNPCType = ent->m_pVehicle->m_pVehicleInfo->droidNPC; + } + + if ( droidNPCType != NULL ) + { + if ( Q_stricmp( "random", droidNPCType ) == 0 + || Q_stricmp( "default", droidNPCType ) == 0 ) + {//use default - R2D2 or R5D2 + if ( Q_irand( 0, 1 ) ) + { + droidNPCType = "r2d2"; + } + else + { + droidNPCType = "r5d2"; + } + } + droidEnt = NPC_SpawnType( ent, droidNPCType, NULL, qfalse ); + if ( droidEnt != NULL ) + { + if ( droidEnt->client ) + { + droidEnt->client->ps.m_iVehicleNum = + droidEnt->s.m_iVehicleNum = + //droidEnt->s.otherEntityNum2 = + droidEnt->s.owner = + droidEnt->r.ownerNum = ent->s.number; + ent->m_pVehicle->m_pDroidUnit = (bgEntity_t *)droidEnt; + //SP way: + //droidEnt->s.m_iVehicleNum = ent->s.number; + //droidEnt->owner = ent; + VectorCopy( ent->r.currentOrigin, droidEnt->s.origin ); + VectorCopy( ent->r.currentOrigin, droidEnt->client->ps.origin ); + G_SetOrigin( droidEnt, droidEnt->s.origin ); + trap_LinkEntity( droidEnt ); + VectorCopy( ent->r.currentAngles, droidEnt->s.angles ); + G_SetAngles( droidEnt, droidEnt->s.angles ); + if ( droidEnt->NPC ) + { + droidEnt->NPC->desiredYaw = droidEnt->s.angles[YAW]; + droidEnt->NPC->desiredPitch = droidEnt->s.angles[PITCH]; + } + droidEnt->flags |= FL_UNDYING; + } + else + {//wtf? + G_FreeEntity( droidEnt ); + } + } + } + } + } +} + +gNPC_t *gNPCPtrs[MAX_GENTITIES]; + +gNPC_t *New_NPC_t(int entNum) +{ + gNPC_t *ptr; + + if (!gNPCPtrs[entNum]) + { + gNPCPtrs[entNum] = (gNPC_t *)BG_Alloc (sizeof(gNPC_t)); + } + + ptr = gNPCPtrs[entNum]; + + if (ptr) + { + // clear it... + // + memset(ptr, 0, sizeof( *ptr ) ); + } + + return ptr; +} + +#ifdef _XBOX +void NPC_NPCPtrsClear(void) +{ + for(int i=0; iownername ) + { + ent->parent = G_Find( NULL, FOFS( targetname ), ent->ownername ); + + if ( ( ent->parent ) && ( ent->parent->health <= 0 ) ) + {//our spawner thing is broken + if ( ent->target2 && ent->target2[0] ) + { + //Fire off our target2 + G_UseTargets2( ent, ent, ent->target2 ); + + //Kill us + ent->e_ThinkFunc = thinkF_G_FreeEntity; + ent->nextthink = level.time + 100; + } + else + { + //Try to spawn again in one second + ent->e_ThinkFunc = thinkF_NPC_Spawn_Go; + ent->nextthink = level.time + 1000; + } + return qfalse; + } + } + + //Test for an entity blocking the spawn + trace_t tr; + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, ent->r.currentOrigin, ent->s.number, MASK_NPCSOLID ); + + //Can't have anything in the way + if ( tr.allsolid || tr.startsolid ) + { + ent->nextthink = level.time + 1000; + return qfalse; + } + + return qtrue; +} +*/ +void NPC_DefaultScriptFlags( gentity_t *ent ) +{ + if ( !ent || !ent->NPC ) + { + return; + } + //Set up default script flags + ent->NPC->scriptFlags = (SCF_CHASE_ENEMIES|SCF_LOOK_FOR_ENEMIES); +} +/* +------------------------- +NPC_Spawn_Go +------------------------- +*/ +#include "../namespace_begin.h" +extern void G_CreateAnimalNPC( Vehicle_t **pVeh, const char *strAnimalType ); +extern void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType ); +extern void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strAnimalType ); +extern void G_CreateFighterNPC( Vehicle_t **pVeh, const char *strType ); +#include "../namespace_end.h" + +gentity_t *NPC_Spawn_Do( gentity_t *ent ) +{ + gentity_t *newent = NULL; + int index; + vec3_t saveOrg; + +/* //Do extra code for stasis spawners + if ( Q_stricmp( ent->classname, "NPC_Stasis" ) == 0 ) + { + if ( NPC_StasisSpawn_Go( ent ) == qfalse ) + return; + } +*/ + + //Test for drop to floor + if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + trace_t tr; + vec3_t bottom; + + VectorCopy( ent->r.currentOrigin, saveOrg ); + VectorCopy( ent->r.currentOrigin, bottom ); + bottom[2] = MIN_WORLD_COORD; + trap_Trace( &tr, ent->r.currentOrigin, ent->r.mins, ent->r.maxs, bottom, ent->s.number, MASK_NPCSOLID ); + if ( !tr.allsolid && !tr.startsolid && tr.fraction < 1.0 ) + { + G_SetOrigin( ent, tr.endpos ); + } + } + + //Check the spawner's count + if( ent->count != -1 ) + { + ent->count--; + + if( ent->count <= 0 ) + { + ent->use = 0;//never again + //FIXME: why not remove me...? Because of all the string pointers? Just do G_NewStrings? + } + } + + newent = G_Spawn(); + + if ( newent == NULL ) + { + Com_Printf ( S_COLOR_RED"ERROR: NPC G_Spawn failed\n" ); + return NULL; + } + + newent->fullName = ent->fullName; + + newent->NPC = New_NPC_t(newent->s.number); + if ( newent->NPC == NULL ) + { + Com_Printf ( S_COLOR_RED"ERROR: NPC G_Alloc NPC failed\n" ); + goto finish; + return NULL; + } + + //newent->client = (gclient_s *)G_Alloc (sizeof(gclient_s)); + G_CreateFakeClient(newent->s.number, &newent->client); + + newent->NPC->tempGoal = G_Spawn(); + + if ( newent->NPC->tempGoal == NULL ) + { + newent->NPC = NULL; + goto finish; + return NULL; + } + + newent->NPC->tempGoal->classname = "NPC_goal"; + newent->NPC->tempGoal->parent = newent; + newent->NPC->tempGoal->r.svFlags |= SVF_NOCLIENT; + + if ( newent->client == NULL ) + { + Com_Printf ( S_COLOR_RED"ERROR: NPC BG_Alloc client failed\n" ); + goto finish; + return NULL; + } + + memset ( newent->client, 0, sizeof(*newent->client) ); + + //Assign the pointer for bg entity access + newent->playerState = &newent->client->ps; + +//==NPC_Connect( newent, net_name );=================================== + + if ( ent->NPC_type == NULL ) + { + ent->NPC_type = "random"; + } + else + { + ent->NPC_type = Q_strlwr( G_NewString( ent->NPC_type ) ); + } + + if ( ent->r.svFlags & SVF_NO_BASIC_SOUNDS ) + { + newent->r.svFlags |= SVF_NO_BASIC_SOUNDS; + } + if ( ent->r.svFlags & SVF_NO_COMBAT_SOUNDS ) + { + newent->r.svFlags |= SVF_NO_COMBAT_SOUNDS; + } + if ( ent->r.svFlags & SVF_NO_EXTRA_SOUNDS ) + { + newent->r.svFlags |= SVF_NO_EXTRA_SOUNDS; + } + + if ( ent->message ) + {//has a key + newent->message = ent->message;//transfer the key name + newent->flags |= FL_NO_KNOCKBACK;//don't fall off ledges + } + + // If this is a vehicle we need to see what kind it is so we properlly allocate it. + if ( Q_stricmp( ent->classname, "NPC_Vehicle" ) == 0 ) + { + // Get the vehicle entry index. + int iVehIndex = BG_VehicleGetIndex( ent->NPC_type ); + + if ( iVehIndex == VEHICLE_NONE ) + { + G_FreeEntity( newent ); + //get rid of the spawner, too, I guess + G_FreeEntity( ent ); + return NULL; + } + // NOTE: If you change/add any of these, update NPC_Spawn_f for the new vehicle you + // want to be able to spawn in manually. + + // See what kind of vehicle this is and allocate it properly. + switch( g_vehicleInfo[iVehIndex].type ) + { + case VH_ANIMAL: + // Create the animal (making sure all it's data is initialized). + G_CreateAnimalNPC( &newent->m_pVehicle, ent->NPC_type ); + break; + case VH_SPEEDER: + // Create the speeder (making sure all it's data is initialized). + G_CreateSpeederNPC( &newent->m_pVehicle, ent->NPC_type ); + break; + case VH_FIGHTER: + // Create the fighter (making sure all it's data is initialized). + G_CreateFighterNPC( &newent->m_pVehicle, ent->NPC_type ); + break; + case VH_WALKER: + // Create the walker (making sure all it's data is initialized). + G_CreateWalkerNPC( &newent->m_pVehicle, ent->NPC_type ); + break; + + default: + Com_Printf ( S_COLOR_RED "ERROR: Couldn't spawn NPC %s\n", ent->NPC_type ); + G_FreeEntity( newent ); + //get rid of the spawner, too, I guess + G_FreeEntity( ent ); + return NULL; + } + + assert(newent->m_pVehicle && + newent->m_pVehicle->m_pVehicleInfo && + newent->m_pVehicle->m_pVehicleInfo->Initialize); + + //set up my happy prediction hack + newent->m_pVehicle->m_vOrientation = &newent->client->ps.vehOrientation[0]; + + // Setup the vehicle. + newent->m_pVehicle->m_pParentEntity = (bgEntity_t *)newent; + newent->m_pVehicle->m_pVehicleInfo->Initialize( newent->m_pVehicle ); + + //cache all the assets + newent->m_pVehicle->m_pVehicleInfo->RegisterAssets( newent->m_pVehicle ); + //set the class + newent->client->NPC_class = CLASS_VEHICLE; + if ( g_vehicleInfo[iVehIndex].type == VH_FIGHTER ) + {//FIXME: EXTERN!!! + newent->flags |= (FL_NO_KNOCKBACK|FL_SHIELDED|FL_DMG_BY_HEAVY_WEAP_ONLY);//don't get pushed around, blasters bounce off, only damage from heavy weaps + } + //WTF?!!! Ships spawning in pointing straight down! + //set them up to start landed + newent->m_pVehicle->m_vOrientation[YAW] = ent->s.angles[YAW]; + newent->m_pVehicle->m_vOrientation[PITCH] = newent->m_pVehicle->m_vOrientation[ROLL] = 0.0f; + G_SetAngles( newent, newent->m_pVehicle->m_vOrientation ); + SetClientViewAngle( newent, newent->m_pVehicle->m_vOrientation ); + + //newent->m_pVehicle->m_ulFlags |= VEH_GEARSOPEN; + //why? this would just make it so the initial anim never got played... -rww + //There was no initial anim, it would just open the gear even though it's already on the ground (fixed now, made an initial anim) + + //For SUSPEND spawnflag, the amount of time to drop like a rock after SUSPEND turns off + newent->fly_sound_debounce_time = ent->fly_sound_debounce_time; + + //for no-pilot-death delay + newent->damage = ent->damage; + + //no-pilot-death distance + newent->speed = ent->speed; + + //for veh transfer all healy stuff + newent->healingclass = ent->healingclass; + newent->healingsound = ent->healingsound; + newent->healingrate = ent->healingrate; + newent->model2 = ent->model2;//for droidNPC + } + else + { + newent->client->ps.weapon = WP_NONE;//init for later check in NPC_Begin + } + + VectorCopy(ent->s.origin, newent->s.origin); + VectorCopy(ent->s.origin, newent->client->ps.origin); + VectorCopy(ent->s.origin, newent->r.currentOrigin); + G_SetOrigin(newent, ent->s.origin);//just to be sure! + //NOTE: on vehicles, anything in the .npc file will STOMP data on the NPC that's set by the vehicle + if ( !NPC_ParseParms( ent->NPC_type, newent ) ) + { + Com_Printf ( S_COLOR_RED "ERROR: Couldn't spawn NPC %s\n", ent->NPC_type ); + G_FreeEntity( newent ); + //get rid of the spawner, too, I guess + G_FreeEntity( ent ); + return NULL; + } + + if ( ent->NPC_type ) + { + if ( !Q_stricmp( ent->NPC_type, "kyle" ) ) + {//FIXME: "player", not Kyle? Or check NPC_type against player's NPC_type? + newent->NPC->aiFlags |= NPCAI_MATCHPLAYERWEAPON; + } + else if ( !Q_stricmp( ent->NPC_type, "test" ) ) + { + int n; + for ( n = 0; n < 1 ; n++) + { + if ( g_entities[n].s.eType != ET_NPC && g_entities[n].client) + { + VectorCopy(g_entities[n].s.origin, newent->s.origin); + newent->client->playerTeam = newent->s.teamowner = g_entities[n].client->playerTeam; + break; + } + } + newent->NPC->defaultBehavior = newent->NPC->behaviorState = BS_WAIT; + newent->classname = "NPC"; + // newent->r.svFlags |= SVF_NOPUSH; + } + } +//===================================================================== + //set the info we want + if ( !newent->health ) + { + newent->health = ent->health; + } + newent->script_targetname = ent->NPC_targetname; + newent->targetname = ent->NPC_targetname; + newent->target = ent->NPC_target;//death + newent->target2 = ent->target2;//knocked out death + newent->target3 = ent->target3;//??? + newent->target4 = ent->target4;//ffire death + newent->wait = ent->wait; + + for( index = BSET_FIRST; index < NUM_BSETS; index++) + { + if ( ent->behaviorSet[index] ) + { + newent->behaviorSet[index] = ent->behaviorSet[index]; + } + } + + newent->classname = "NPC"; + newent->NPC_type = ent->NPC_type; + trap_UnlinkEntity(newent); + + VectorCopy(ent->s.angles, newent->s.angles); + VectorCopy(ent->s.angles, newent->r.currentAngles); + VectorCopy(ent->s.angles, newent->client->ps.viewangles); + newent->NPC->desiredYaw =ent->s.angles[YAW]; + + trap_LinkEntity(newent); + newent->spawnflags = ent->spawnflags; + + if(ent->paintarget) + { //safe to point at owner's string since memory is never freed during game + newent->paintarget = ent->paintarget; + } + if(ent->opentarget) + { + newent->opentarget = ent->opentarget; + } + +//==New stuff===================================================================== + newent->s.eType = ET_NPC;//ET_PLAYER; + + //FIXME: Call CopyParms + if ( ent->parms ) + { + int parmNum; + + for ( parmNum = 0; parmNum < MAX_PARMS; parmNum++ ) + { + if ( ent->parms->parm[parmNum] && ent->parms->parm[parmNum][0] ) + { + Q3_SetParm( newent->s.number, parmNum, ent->parms->parm[parmNum] ); + } + } + } + //FIXME: copy cameraGroup, store mine in message or other string field + + //set origin + newent->s.pos.trType = TR_INTERPOLATE; + newent->s.pos.trTime = level.time; + VectorCopy( newent->r.currentOrigin, newent->s.pos.trBase ); + VectorClear( newent->s.pos.trDelta ); + newent->s.pos.trDuration = 0; + //set angles + newent->s.apos.trType = TR_INTERPOLATE; + newent->s.apos.trTime = level.time; + //VectorCopy( newent->r.currentOrigin, newent->s.apos.trBase ); + //Why was the origin being used as angles? Typo I'm assuming -rww + VectorCopy( newent->s.angles, newent->s.apos.trBase ); + + VectorClear( newent->s.apos.trDelta ); + newent->s.apos.trDuration = 0; + + newent->NPC->combatPoint = -1; + + newent->flags |= FL_NOTARGET;//So he's ignored until he's fully spawned + newent->s.eFlags |= EF_NODRAW;//So he's ignored until he's fully spawned + + newent->think = NPC_Begin; + newent->nextthink = level.time + FRAMETIME; + NPC_DefaultScriptFlags( newent ); + + //copy over team variables, too + newent->s.shouldtarget = ent->s.shouldtarget; + newent->s.teamowner = ent->s.teamowner; + newent->alliedTeam = ent->alliedTeam; + newent->teamnodmg = ent->teamnodmg; + if ( ent->team && ent->team[0] ) + {//specified team directly? + newent->client->sess.sessionTeam = atoi(ent->team); + } + else if ( newent->s.teamowner != TEAM_FREE ) + { + newent->client->sess.sessionTeam = newent->s.teamowner; + } + else if ( newent->alliedTeam != TEAM_FREE ) + { + newent->client->sess.sessionTeam = newent->alliedTeam; + } + else if ( newent->teamnodmg != TEAM_FREE ) + { + newent->client->sess.sessionTeam = newent->teamnodmg; + } + else + { + newent->client->sess.sessionTeam = TEAM_FREE; + } + newent->client->ps.persistant[PERS_TEAM] = newent->client->sess.sessionTeam; + + trap_LinkEntity (newent); + + if(!ent->use) + { + if( ent->target ) + {//use any target we're pointed at + G_UseTargets ( ent, ent ); + } + if(ent->closetarget) + {//last guy should fire this target when he dies + newent->target = ent->closetarget; + } + ent->targetname = NULL; + //why not remove me...? Because of all the string pointers? Just do G_NewStrings? + G_FreeEntity( ent );//bye! + } + +finish: + if ( ent->spawnflags & NSF_DROP_TO_FLOOR ) + { + G_SetOrigin( ent, saveOrg ); + } + + return newent; +} + +void NPC_Spawn_Go(gentity_t *ent) +{ + NPC_Spawn_Do(ent); +} + +/* +------------------------- +NPC_StasisSpawnEffect +------------------------- +*/ +/* +void NPC_StasisSpawnEffect( gentity_t *ent ) +{ + vec3_t start, end, forward; + qboolean taper; + + //Floor or wall? + if ( ent->spawnflags & 1 ) + { + AngleVectors( ent->s.angles, forward, NULL, NULL ); + VectorMA( ent->r.currentOrigin, 24, forward, end ); + VectorMA( ent->r.currentOrigin, -20, forward, start ); + + start[2] += 64; + + taper = qtrue; + } + else + { + VectorCopy( ent->r.currentOrigin, start ); + VectorCopy( start, end ); + end[2] += 48; + taper = qfalse; + } + + //Add the effect +// CG_ShimmeryThing_Spawner( start, end, 32, qtrue, 1000 ); +} +*/ +/* +------------------------- +NPC_ShySpawn +------------------------- +*/ + +#define SHY_THINK_TIME 1000 +#define SHY_SPAWN_DISTANCE 128 +#define SHY_SPAWN_DISTANCE_SQR ( SHY_SPAWN_DISTANCE * SHY_SPAWN_DISTANCE ) + +void NPC_ShySpawn( gentity_t *ent ) +{ + ent->nextthink = level.time + SHY_THINK_TIME; + ent->think = NPC_ShySpawn; + + //rwwFIXMEFIXME: Care about other clients not just 0? + if ( DistanceSquared( g_entities[0].r.currentOrigin, ent->r.currentOrigin ) <= SHY_SPAWN_DISTANCE_SQR ) + return; + + if ( (InFOV( ent, &g_entities[0], 80, 64 )) ) // FIXME: hardcoded fov + if ( (NPC_ClearLOS2( &g_entities[0], ent->r.currentOrigin )) ) + return; + + ent->think = 0; + ent->nextthink = 0; + + NPC_Spawn_Go( ent ); +} + +/* +------------------------- +NPC_Spawn +------------------------- +*/ + +void NPC_Spawn ( gentity_t *ent, gentity_t *other, gentity_t *activator ) +{ + //delay before spawning NPC + if( ent->delay ) + { +/* //Stasis does an extra step + if ( Q_stricmp( ent->classname, "NPC_Stasis" ) == 0 ) + { + if ( NPC_StasisSpawn_Go( ent ) == qfalse ) + return; + } +*/ + if ( ent->spawnflags & 2048 ) // SHY + { + ent->think = NPC_ShySpawn; + } + else + { + ent->think = NPC_Spawn_Go; + } + + ent->nextthink = level.time + ent->delay; + } + else + { + if ( ent->spawnflags & 2048 ) // SHY + { + NPC_ShySpawn( ent ); + } + else + { + NPC_Spawn_Do( ent ); + } + } +} + +/*QUAKED NPC_spawner (1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +NPC_type - name of NPC (in npcs.cfg) to spawn in + +targetname - name this NPC goes by for targetting +target - NPC will fire this when it spawns it's last NPC (should this be when the last NPC it spawned dies?) +target2 - Fired by stasis spawners when they try to spawn while their spawner model is broken +target3 - Fired by spawner if they try to spawn and are blocked and have a wait < 0 (removes them) + +If targeted, will only spawn a NPC when triggered +count - how many NPCs to spawn (only if targetted) default = 1 +delay - how long to wait to spawn after used +wait - if trying to spawn and blocked, how many seconds to wait before trying again (default = 0.5, < 0 = never try again and fire target2) + +NPC_targetname - NPC's targetname AND script_targetname +NPC_target - NPC's target to fire when killed +NPC_target2 - NPC's target to fire when knocked out +NPC_target4 - NPC's target to fire when killed by friendly fire +NPC_type - type of NPC ("Borg" (default), "Xian", etc) +health - starting health (default = 100) + +spawnscript - default script to run once spawned (none by default) +usescript - default script to run when used (none by default) +awakescript - default script to run once awoken (none by default) +angerscript - default script to run once angered (none by default) +painscript - default script to run when hit (none by default) +fleescript - default script to run when hit and below 50% health (none by default) +deathscript - default script to run when killed (none by default) +These strings can be used to activate behaviors instead of scripts - these are checked +first and so no scripts should be names with these names: + default - 0: whatever + idle - 1: Stand around, do abolutely nothing + roam - 2: Roam around, collect stuff + walk - 3: Crouch-Walk toward their goals + run - 4: Run toward their goals + standshoot - 5: Stay in one spot and shoot- duck when neccesary + standguard - 6: Wait around for an enemy + patrol - 7: Follow a path, looking for enemies + huntkill - 8: Track down enemies and kill them + evade - 9: Run from enemies + evadeshoot - 10: Run from enemies, shoot them if they hit you + runshoot - 11: Run to your goal and shoot enemy when possible + defend - 12: Defend an entity or spot? + snipe - 13: Stay hidden, shoot enemy only when have perfect shot and back turned + combat - 14: Attack, evade, use cover, move about, etc. Full combat AI - id NPC code + medic - 15: Go for lowest health buddy, hide and heal him. + takecover - 16: Find nearest cover from enemies + getammo - 17: Go get some ammo + advancefight - 18: Go somewhere and fight along the way + face - 19: turn until facing desired angles + wait - 20: do nothing + formation - 21: Maintain a formation + crouch - 22: Crouch-walk toward their goals + +delay - after spawned or triggered, how many seconds to wait to spawn the NPC + +showhealth - set to 1 to show health bar on this entity when crosshair is over it + +teamowner - crosshair shows green for this team, red for opposite team + 0 - none + 1 - red + 2 - blue + +teamuser - only this team can use this NPC + 0 - none + 1 - red + 2 - blue + +teamnodmg - team that NPC does not take damage from (turrets and other auto-defenses that have "alliedTeam" set to this team won't target this NPC) + 0 - none + 1 - red + 2 - blue + +"noBasicSounds" - set to 1 to prevent loading and usage of basic sounds (pain, death, etc) +"noCombatSounds" - set to 1 to prevent loading and usage of combat sounds (anger, victory, etc.) +"noExtraSounds" - set to 1 to prevent loading and usage of "extra" sounds (chasing the enemy - detecting them, flanking them... also jedi combat sounds) +*/ +//void NPC_PrecacheModels ( char *NPCName ); +extern void NPC_PrecacheAnimationCFG( const char *NPC_type ); +void NPC_Precache ( gentity_t *spawner ); +void NPC_PrecacheType( char *NPC_type ) +{ + gentity_t *fakespawner = G_Spawn(); + if ( fakespawner ) + { + fakespawner->NPC_type = NPC_type; + NPC_Precache( fakespawner ); + //NOTE: does the spawner have to stay around to send any precached info to the clients...? + G_FreeEntity( fakespawner ); + } +} + +void SP_NPC_spawner( gentity_t *self) +{ + int t; + + if (!g_allowNPC.integer) + { + self->think = G_FreeEntity; + self->nextthink = level.time; + return; + } + if ( !self->fullName || !self->fullName[0] ) + { + //FIXME: make an index into an external string table for localization + self->fullName = "Humanoid Lifeform"; + } + + //register/precache the models needed for this NPC, not anymore + //self->classname = "NPC_spawner"; + + if(!self->count) + { + self->count = 1; + } + + {//Stop loading of certain extra sounds + static int garbage; + + if ( G_SpawnInt( "noBasicSounds", "0", &garbage ) ) + { + self->r.svFlags |= SVF_NO_BASIC_SOUNDS; + } + if ( G_SpawnInt( "noCombatSounds", "0", &garbage ) ) + { + self->r.svFlags |= SVF_NO_COMBAT_SOUNDS; + } + if ( G_SpawnInt( "noExtraSounds", "0", &garbage ) ) + { + self->r.svFlags |= SVF_NO_EXTRA_SOUNDS; + } + } + + if ( !self->wait ) + { + self->wait = 500; + } + else + { + self->wait *= 1000;//1 = 1 msec, 1000 = 1 sec + } + + self->delay *= 1000;//1 = 1 msec, 1000 = 1 sec + + G_SpawnInt( "showhealth", "0", &t ); + if (t) + { + self->s.shouldtarget = qtrue; + } + /* + if ( self->delay > 0 ) + { + self->r.svFlags |= SVF_NPC_PRECACHE; + } + */ + //rwwFIXMEFIXME: support for this flag? + + //We have to load the animation.cfg now because spawnscripts are going to want to set anims and we need to know their length and if they're valid + NPC_PrecacheAnimationCFG( self->NPC_type ); + + //rww - can't cheat and do this on the client like in SP, so I'm doing this. + NPC_Precache(self); + + if ( self->targetname ) + {//Wait for triggering + self->use = NPC_Spawn; + // self->r.svFlags |= SVF_NPC_PRECACHE;//FIXME: precache my weapons somehow? + + //NPC_PrecacheModels( self->NPC_type ); + } + else + { + //NOTE: auto-spawners never check for shy spawning + //if ( spawning ) + if (1) //just gonna always do this I suppose. + {//in entity spawn stage - map starting up + self->think = NPC_Spawn_Go; + self->nextthink = level.time + START_TIME_REMOVE_ENTS + 50; + } + else + {//else spawn right now + NPC_Spawn( self, self, self ); + } + } + + //FIXME: store cameraGroup somewhere else and apply to spawned NPCs' cameraGroup + //Or just don't include NPC_spawners in cameraGroupings +} + +extern void G_VehicleSpawn( gentity_t *self ); +/*QUAKED NPC_Vehicle (1 0 0) (-16 -16 -24) (16 16 32) NO_PILOT_DIE SUSPENDED x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +NO_PILOT_DIE - die after certain amount of time of not having a pilot +SUSPENDED - Fighters: Don't drop until someone gets in it (this only works as long as no-nw has *ever* ridden the vehicle, to simulate ships that are suspended-docked) - note: ships inside trigger_spaces do not drop when unoccupied +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +set NPC_type to vehicle name in vehicles.dat + +"dropTime" use with SUSPENDED - if set, the vehicle will drop straight down for this number of seconds before flying forward +"dmg" use with NO_PILOT_DIE - delay in milliseconds for ship to explode if no pilot (default 10000) +"speed" use with NO_PILOT_DIE - distance for pilot to get away from ship after dismounting before it starts counting down the death timer +"model2" - if the vehicle can have a droid (has "*droidunit" tag), this NPC will be spawned and placed there - note: game will automatically use the one specified in the .veh file (if any) or, absent that, it will use an R2D2 or R5D2 NPC) + +showhealth - set to 1 to show health bar on this entity when crosshair is over it + +teamowner - crosshair shows green for this team, red for opposite team + 0 - none + 1 - red + 2 - blue + +teamuser - only this team can use this NPC + 0 - none + 1 - red + 2 - blue + +teamnodmg - team that NPC does not take damage from (turrets and other auto-defenses that have "alliedTeam" set to this team won't target this NPC) + 0 - none + 1 - red + 2 - blue +*/ +qboolean NPC_VehiclePrecache( gentity_t *spawner ) +{ + char *droidNPCType = NULL; + //This will precache the vehicle + vehicleInfo_t *pVehInfo; + int iVehIndex = BG_VehicleGetIndex( spawner->NPC_type ); + if ( iVehIndex == VEHICLE_NONE ) + {//fixme: error msg? + return qfalse; + } + + G_ModelIndex(va("$%s", spawner->NPC_type)); //make sure the thing is frickin precached + //now cache his model/skin/anim config + pVehInfo = &g_vehicleInfo[iVehIndex]; + if (pVehInfo->model && pVehInfo->model[0]) + { + void *tempG2 = NULL; + int skin = 0; + if (pVehInfo->skin && pVehInfo->skin[0]) + { + skin = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", pVehInfo->model, pVehInfo->skin)); + } + trap_G2API_InitGhoul2Model(&tempG2, va("models/players/%s/model.glm", pVehInfo->model), 0, skin, 0, 0, 0); + if (tempG2) + { //now, cache the anim config. + char GLAName[1024]; + + GLAName[0] = 0; + trap_G2API_GetGLAName(tempG2, 0, GLAName); + + if (GLAName[0]) + { + char *slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + } + trap_G2API_CleanGhoul2Models(&tempG2); + } + } + + //also precache the droid NPC if there is one + if ( spawner->model2 + && spawner->model2[0] ) + { + droidNPCType = spawner->model2; + } + else if ( g_vehicleInfo[iVehIndex].droidNPC + && g_vehicleInfo[iVehIndex].droidNPC[0] ) + { + droidNPCType = g_vehicleInfo[iVehIndex].droidNPC; + } + + if ( droidNPCType ) + { + if ( Q_stricmp( "random", droidNPCType ) == 0 + || Q_stricmp( "default", droidNPCType ) == 0 ) + {//precache both r2 and r5, as defaults + NPC_PrecacheType( "r2d2" ); + NPC_PrecacheType( "r5d2" ); + } + else + { + NPC_PrecacheType( droidNPCType ); + } + } + return qtrue; +} + +void NPC_VehicleSpawnUse( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + if ( self->delay ) + { + self->think = G_VehicleSpawn; + self->nextthink = level.time + self->delay; + } + else + { + G_VehicleSpawn( self ); + } +} + +void SP_NPC_Vehicle( gentity_t *self) +{ + float dropTime; + int t; + if ( !self->NPC_type ) + { + self->NPC_type = "swoop"; + } + + if ( !self->classname ) + { + self->classname = "NPC_Vehicle"; + } + + if ( !self->wait ) + { + self->wait = 500; + } + else + { + self->wait *= 1000;//1 = 1 msec, 1000 = 1 sec + } + self->delay *= 1000;//1 = 1 msec, 1000 = 1 sec + + G_SetOrigin( self, self->s.origin ); + G_SetAngles( self, self->s.angles ); + G_SpawnFloat( "dropTime", "0", &dropTime ); + if ( dropTime ) + { + self->fly_sound_debounce_time = ceil(dropTime*1000.0); + } + + G_SpawnInt( "showhealth", "0", &t ); + if (t) + { + self->s.shouldtarget = qtrue; + } + //FIXME: PRECACHE!!! + + if ( self->targetname ) + { + if ( !NPC_VehiclePrecache( self ) ) + {//FIXME: err msg? + G_FreeEntity( self ); + return; + } + self->use = NPC_VehicleSpawnUse; + } + else + { + if ( self->delay ) + { + if ( !NPC_VehiclePrecache( self ) ) + {//FIXME: err msg? + G_FreeEntity( self ); + return; + } + self->think = G_VehicleSpawn; + self->nextthink = level.time + self->delay; + } + else + { + G_VehicleSpawn( self ); + } + } +} + +//Characters + +//STAR WARS NPCs============================================================================ +/*QUAKED NPC_spawner (1 0 0) (-16 -16 -24) (16 16 32) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY + +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +targetname - name this NPC goes by for targetting +target - NPC will fire this when it spawns it's last NPC (should this be when the last NPC it spawned dies?) + +If targeted, will only spawn a NPC when triggered +count - how many NPCs to spawn (only if targetted) default = 1 + +NPC_type - the name of the NPC (from NPCs.cfg or a from .npc file) + +NPC_targetname - NPC's targetname AND script_targetname +NPC_target - NPC's target to fire when killed +health - starting health (default = 100) +playerTeam - Who not to shoot! (default is TEAM_STARFLEET) + TEAM_FREE (none) = 0 + TEAM_RED = 1 + TEAM_BLUE = 2 + TEAM_GOLD = 3 + TEAM_GREEN = 4 + TEAM_STARFLEET = 5 + TEAM_BORG = 6 + TEAM_SCAVENGERS = 7 + TEAM_STASIS = 8 + TEAM_NPCS = 9 + TEAM_HARVESTER, = 10 + TEAM_FORGE = 11 +enemyTeam - Who to shoot (all but own if not set) + +spawnscript - default script to run once spawned (none by default) +usescript - default script to run when used (none by default) +awakescript - default script to run once awoken (none by default) +angerscript - default script to run once angered (none by default) +painscript - default script to run when hit (none by default) +fleescript - default script to run when hit and below 50% health (none by default) +deathscript - default script to run when killed (none by default) + +These strings can be used to activate behaviors instead of scripts - these are checked +first and so no scripts should be names with these names: + default - 0: whatever + idle - 1: Stand around, do abolutely nothing + roam - 2: Roam around, collect stuff + walk - 3: Crouch-Walk toward their goals + run - 4: Run toward their goals + standshoot - 5: Stay in one spot and shoot- duck when neccesary + standguard - 6: Wait around for an enemy + patrol - 7: Follow a path, looking for enemies + huntkill - 8: Track down enemies and kill them + evade - 9: Run from enemies + evadeshoot - 10: Run from enemies, shoot them if they hit you + runshoot - 11: Run to your goal and shoot enemy when possible + defend - 12: Defend an entity or spot? + snipe - 13: Stay hidden, shoot enemy only when have perfect shot and back turned + combat - 14: Attack, evade, use cover, move about, etc. Full combat AI - id NPC code + medic - 15: Go for lowest health buddy, hide and heal him. + takecover - 16: Find nearest cover from enemies + getammo - 17: Go get some ammo + advancefight - 18: Go somewhere and fight along the way + face - 19: turn until facing desired angles + wait - 20: do nothing + formation - 21: Maintain a formation + crouch - 22: Crouch-walk toward their goals + +delay - after spawned or triggered, how many seconds to wait to spawn the NPC +*/ + +//============================================================================================= +//CHARACTERS +//============================================================================================= + +/*QUAKED NPC_Kyle (1 0 0) (-16 -16 -24) (16 16 32) x RIFLEMAN PHASER TRICORDER DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Kyle( gentity_t *self) +{ + self->NPC_type = "Kyle"; + + WP_SetSaberModel( NULL, CLASS_KYLE ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Lando(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Lando( gentity_t *self) +{ + self->NPC_type = "Lando"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Jan(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Jan( gentity_t *self) +{ + self->NPC_type = "Jan"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Luke(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Luke( gentity_t *self) +{ + self->NPC_type = "Luke"; + + WP_SetSaberModel( NULL, CLASS_LUKE ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_MonMothma(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_MonMothma( gentity_t *self) +{ + self->NPC_type = "MonMothma"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Tavion (1 0 0) (-16 -16 -24) (16 16 32) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tavion( gentity_t *self) +{ + self->NPC_type = "Tavion"; + + WP_SetSaberModel( NULL, CLASS_TAVION ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Tavion_New (1 0 0) (-16 -16 -24) (16 16 32) SCEPTER SITH_SWORD x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Has a red lightsaber and force powers, uses her saber style from JK2 + +SCEPTER - Has a red lightsaber and force powers, Ragnos' Scepter in left hand, uses dual saber style and occasionally attacks with Scepter +SITH_SWORD - Has Ragnos' Sith Sword in right hand and force powers, uses strong style +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tavion_New( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "tavion_scepter"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "tavion_sith_sword"; + } + else + { + self->NPC_type = "tavion_new"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Alora (1 0 0) (-16 -16 -24) (16 16 32) DUAL x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Lightsaber and level 2 force powers, 300 health + +DUAL - Dual sabers and level 3 force powers, 500 health +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Alora( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "alora_dual"; + } + else + { + self->NPC_type = "alora"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Reborn_New(1 0 0) (-16 -16 -24) (16 16 40) DUAL STAFF WEAK x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Reborn is an excellent lightsaber fighter, acrobatic and uses force powers. Full-length red saber, 200 health. + +DUAL - Use 2 shorter sabers +STAFF - Uses a saber staff +WEAK - Is a bit less tough +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Reborn_New( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&4) ) + {//weaker guys + if ( (self->spawnflags&1) ) + { + self->NPC_type = "reborn_dual2"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "reborn_staff2"; + } + else + { + self->NPC_type = "reborn_new2"; + } + } + else + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "reborn_dual"; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "reborn_staff"; + } + else + { + self->NPC_type = "reborn_new"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist_Saber(1 0 0) (-16 -16 -24) (16 16 40) MED STRONG ALL THROW CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Uses a saber and no force powers. 100 health. + +default fencer uses fast style - weak, but can attack rapidly. Good defense. + +MED - Uses medium style - average speed and attack strength, average defense. +STRONG - Uses strong style, slower than others, but can do a lot of damage with one blow. Weak defense. +ALL - Knows all 3 styles, switches between them, good defense. +THROW - can throw their saber (level 2) - reduces their defense some (can use this spawnflag alone or in combination with any *one* of the previous 3 spawnflags) + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist_Saber( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_med_throw"; + } + else + { + self->NPC_type = "cultist_saber_med"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_strong_throw"; + } + else + { + self->NPC_type = "cultist_saber_strong"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_all_throw"; + } + else + { + self->NPC_type = "cultist_saber_all"; + } + } + else + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_throw"; + } + else + { + self->NPC_type = "cultist_saber"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist_Saber_Powers(1 0 0) (-16 -16 -24) (16 16 40) MED STRONG ALL THROW CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +Uses a saber and has a couple low-level powers. 150 health. + +default fencer uses fast style - weak, but can attack rapidly. Good defense. + +MED - Uses medium style - average speed and attack strength, average defense. +STRONG - Uses strong style, slower than others, but can do a lot of damage with one blow. Weak defense. +ALL - Knows all 3 styles, switches between them, good defense. +THROW - can throw their saber (level 2) - reduces their defense some (can use this spawnflag alone or in combination with any *one* of the previous 3 spawnflags) + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist_Saber_Powers( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_med_throw2"; + } + else + { + self->NPC_type = "cultist_saber_med2"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_strong_throw2"; + } + else + { + self->NPC_type = "cultist_saber_strong2"; + } + } + else if ( (self->spawnflags&2) ) + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_all_throw2"; + } + else + { + self->NPC_type = "cultist_saber_all2"; + } + } + else + { + if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_saber_throw"; + } + else + { + self->NPC_type = "cultist_saber2"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist(1 0 0) (-16 -16 -24) (16 16 40) SABER GRIP LIGHTNING DRAIN CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Cultist uses a blaster and force powers. 40 health. + +SABER - Uses a saber and no force powers +GRIP - Uses no weapon and grip, push and pull +LIGHTNING - Uses no weapon and lightning and push +DRAIN - Uses no weapons and drain and push + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = NULL; + self->spawnflags = 0;//fast, no throw + switch ( Q_irand( 0, 2 ) ) + { + case 0://medium + self->spawnflags |= 1; + break; + case 1://strong + self->spawnflags |= 2; + break; + case 2://all + self->spawnflags |= 4; + break; + } + if ( Q_irand( 0, 1 ) ) + {//throw + self->spawnflags |= 8; + } + SP_NPC_Cultist_Saber( self ); + return; + } + else if ( (self->spawnflags&2) ) + { + self->NPC_type = "cultist_grip"; + } + else if ( (self->spawnflags&4) ) + { + self->NPC_type = "cultist_lightning"; + } + else if ( (self->spawnflags&8) ) + { + self->NPC_type = "cultist_drain"; + } + else + { + self->NPC_type = "cultist"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Cultist_Commando(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Cultist uses dual blaster pistols and force powers. 40 health. + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist_Commando( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "cultistcommando"; + } + SP_NPC_spawner( self ); +} +/*QUAKED NPC_Cultist_Destroyer(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Cultist has no weapons, runs up to you chanting & building up a Force Destruction blast - when gets to you, screams & explodes + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Cultist_Destroyer( gentity_t *self) +{ + self->NPC_type = "cultist";//"cultist_explode"; + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Reelo(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Reelo( gentity_t *self) +{ + self->NPC_type = "Reelo"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Galak(1 0 0) (-16 -16 -24) (16 16 40) MECH x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +MECH - will be the armored Galak + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Galak( gentity_t *self) +{ + if ( self->spawnflags & 1 ) + { + self->NPC_type = "Galak_Mech"; + NPC_GalakMech_Precache(); + } + else + { + self->NPC_type = "Galak"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Desann(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Desann( gentity_t *self) +{ + self->NPC_type = "Desann"; + + WP_SetSaberModel( NULL, CLASS_DESANN ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Bartender(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Bartender( gentity_t *self) +{ + self->NPC_type = "Bartender"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_MorganKatarn(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_MorganKatarn( gentity_t *self) +{ + self->NPC_type = "MorganKatarn"; + + SP_NPC_spawner( self ); +} + +//============================================================================================= +//ALLIES +//============================================================================================= + +/*QUAKED NPC_Jedi(1 0 0) (-16 -16 -24) (16 16 40) TRAINER x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +TRAINER - Special Jedi- instructor +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Ally Jedi NPC Buddy - tags along with player +*/ +void SP_NPC_Jedi( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "jeditrainer"; + } + else + { + /* + if ( !Q_irand( 0, 2 ) ) + { + self->NPC_type = "JediF"; + } + else + */if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Jedi"; + } + else + { + self->NPC_type = "Jedi2"; + } + } + } + + WP_SetSaberModel( NULL, CLASS_JEDI ); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Prisoner(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Prisoner( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Prisoner"; + } + else + { + self->NPC_type = "Prisoner2"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Rebel(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Rebel( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Rebel"; + } + else + { + self->NPC_type = "Rebel2"; + } + } + + SP_NPC_spawner( self ); +} + +//============================================================================================= +//ENEMIES +//============================================================================================= + +/*QUAKED NPC_Stormtrooper(1 0 0) (-16 -16 -24) (16 16 40) OFFICER COMMANDER ALTOFFICER ROCKET DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +30 health, blaster + +OFFICER - 60 health, flechette +COMMANDER - 60 health, heavy repeater +ALTOFFICER - 60 health, alt-fire flechette (grenades) +ROCKET - 60 health, rocket launcher + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Stormtrooper( gentity_t *self) +{ + if ( self->spawnflags & 8 ) + {//rocketer + self->NPC_type = "rockettrooper"; + } + else if ( self->spawnflags & 4 ) + {//alt-officer + self->NPC_type = "stofficeralt"; + } + else if ( self->spawnflags & 2 ) + {//commander + self->NPC_type = "stcommander"; + } + else if ( self->spawnflags & 1 ) + {//officer + self->NPC_type = "stofficer"; + } + else + {//regular trooper + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "StormTrooper"; + } + else + { + self->NPC_type = "StormTrooper2"; + } + } + + SP_NPC_spawner( self ); +} +void SP_NPC_StormtrooperOfficer( gentity_t *self) +{ + self->spawnflags |= 1; + SP_NPC_Stormtrooper( self ); +} +/*QUAKED NPC_Snowtrooper(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +30 health, blaster + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Snowtrooper( gentity_t *self) +{ + self->NPC_type = "snowtrooper"; + + SP_NPC_spawner( self ); +} +/*QUAKED NPC_Tie_Pilot(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +30 health, blaster + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tie_Pilot( gentity_t *self) +{ + self->NPC_type = "stormpilot"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Ugnaught(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Ugnaught( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "Ugnaught"; + } + else + { + self->NPC_type = "Ugnaught2"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Jawa(1 0 0) (-16 -16 -24) (16 16 40) ARMED x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +ARMED - starts with the Jawa gun in-hand + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Jawa( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "jawa_armed"; + } + else + { + self->NPC_type = "jawa"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Gran(1 0 0) (-16 -16 -24) (16 16 40) SHOOTER BOXER x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +Uses grenade + +SHOOTER - uses blaster instead of +BOXER - uses fists only +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Gran( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "granshooter"; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "granboxer"; + } + else + { + if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "gran"; + } + else + { + self->NPC_type = "gran2"; + } + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Rodian(1 0 0) (-16 -16 -24) (16 16 40) BLASTER NO_HIDE x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +BLASTER uses a blaster instead of sniper rifle, different skin +NO_HIDE (only applicable with snipers) does not duck and hide between shots +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Rodian( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags&1 ) + { + self->NPC_type = "rodian2"; + } + else + { + self->NPC_type = "rodian"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Weequay(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Weequay( gentity_t *self) +{ + if ( !self->NPC_type ) + { + switch ( Q_irand( 0, 3 ) ) + { + case 0: + self->NPC_type = "Weequay"; + break; + case 1: + self->NPC_type = "Weequay2"; + break; + case 2: + self->NPC_type = "Weequay3"; + break; + case 3: + self->NPC_type = "Weequay4"; + break; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Trandoshan(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Trandoshan( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "Trandoshan"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Tusken(1 0 0) (-16 -16 -24) (16 16 40) SNIPER x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Tusken( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( (self->spawnflags&1) ) + { + self->NPC_type = "tuskensniper"; + } + else + { + self->NPC_type = "tusken"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Noghri(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Noghri( gentity_t *self) +{ + if ( !self->NPC_type ) + { + self->NPC_type = "noghri"; + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_SwampTrooper(1 0 0) (-16 -16 -24) (16 16 40) REPEATER x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +REPEATER - Swaptrooper who uses the repeater +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_SwampTrooper( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "SwampTrooper2"; + } + else + { + self->NPC_type = "SwampTrooper"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Imperial(1 0 0) (-16 -16 -24) (16 16 40) OFFICER COMMANDER x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY + +Greyshirt grunt, uses blaster pistol, 20 health. + +OFFICER - Brownshirt Officer, uses blaster rifle, 40 health +COMMANDER - Blackshirt Commander, uses rapid-fire blaster rifle, 80 healt + +"message" - if a COMMANDER, turns on his key surface. This is the name of the key you get when you walk over his body. This must match the "message" field of the func_security_panel you want this key to open. Set to "goodie" to have him carrying a goodie key that player can use to operate doors with "GOODIE" spawnflag. + +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Imperial( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "ImpOfficer"; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "ImpCommander"; + } + else + { + self->NPC_type = "Imperial"; + } + } + + /* + if ( self->message ) + {//may drop a key, precache the key model and pickup sound + G_SoundIndex( "sound/weapons/key_pkup.wav" ); + if ( !Q_stricmp( "goodie", self->message ) ) + { + RegisterItem( FindItemForInventory( INV_GOODIE_KEY ) ); + } + else + { + RegisterItem( FindItemForInventory( INV_SECURITY_KEY ) ); + } + } + */ + //rwwFIXMEFIXME: Allow goodie keys + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_ImpWorker(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_ImpWorker( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( !Q_irand( 0, 2 ) ) + { + self->NPC_type = "ImpWorker"; + } + else if ( Q_irand( 0, 1 ) ) + { + self->NPC_type = "ImpWorker2"; + } + else + { + self->NPC_type = "ImpWorker3"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_BespinCop(1 0 0) (-16 -16 -24) (16 16 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_BespinCop( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( !Q_irand( 0, 1 ) ) + { + self->NPC_type = "BespinCop"; + } + else + { + self->NPC_type = "BespinCop2"; + } + } + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Reborn(1 0 0) (-16 -16 -24) (16 16 40) FORCE FENCER ACROBAT BOSS CEILING CINEMATIC NOTSOLID STARTINSOLID SHY + +Default Reborn is A poor lightsaber fighter, acrobatic and uses no force powers. Yellow saber, 40 health. + +FORCE - Uses force powers but is not the best lightsaber fighter and not acrobatic. Purple saber, 75 health. +FENCER - A good lightsaber fighter, but not acrobatic and uses no force powers. Yellow saber, 100 health. +ACROBAT - quite acrobatic, but not the best lightsaber fighter and uses no force powers. Red saber, 100 health. +BOSS - quite acrobatic, good lightsaber fighter and uses force powers. Orange saber, 150 health. + +NOTE: Saber colors are temporary until they have different by skins to tell them apart + +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Reborn( gentity_t *self) +{ + if ( !self->NPC_type ) + { + if ( self->spawnflags & 1 ) + { + self->NPC_type = "rebornforceuser"; + } + else if ( self->spawnflags & 2 ) + { + self->NPC_type = "rebornfencer"; + } + else if ( self->spawnflags & 4 ) + { + self->NPC_type = "rebornacrobat"; + } + else if ( self->spawnflags & 8 ) + { + self->NPC_type = "rebornboss"; + } + else + { + self->NPC_type = "reborn"; + } + } + + WP_SetSaberModel( NULL, CLASS_REBORN ); + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_ShadowTrooper(1 0 0) (-16 -16 -24) (16 16 40) x x x x CEILING CINEMATIC NOTSOLID STARTINSOLID SHY +CEILING - Sticks to the ceiling until he sees an enemy or takes pain +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_ShadowTrooper( gentity_t *self) +{ + if(!self->NPC_type) + { + if ( !Q_irand( 0, 1 ) ) + { + self->NPC_type = "ShadowTrooper"; + } + else + { + self->NPC_type = "ShadowTrooper2"; + } + } + + NPC_ShadowTrooper_Precache(); + WP_SetSaberModel( NULL, CLASS_SHADOWTROOPER ); + + SP_NPC_spawner( self ); +} +//============================================================================================= +//MONSTERS +//============================================================================================= + +/*QUAKED NPC_Monster_Murjj (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Murjj( gentity_t *self) +{ + self->NPC_type = "Murjj"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Swamp (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Swamp( gentity_t *self) +{ + self->NPC_type = "Swamp"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Howler (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Howler( gentity_t *self) +{ + self->NPC_type = "howler"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_MineMonster (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_MineMonster( gentity_t *self) +{ + self->NPC_type = "minemonster"; + + SP_NPC_spawner( self ); + NPC_MineMonster_Precache(); +} + +/*QUAKED NPC_Monster_Claw (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Claw( gentity_t *self) +{ + self->NPC_type = "Claw"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Glider (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Glider( gentity_t *self) +{ + self->NPC_type = "Glider"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Flier2 (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Flier2( gentity_t *self) +{ + self->NPC_type = "Flier2"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Lizard (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Lizard( gentity_t *self) +{ + self->NPC_type = "Lizard"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Fish (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Fish( gentity_t *self) +{ + self->NPC_type = "Fish"; + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Wampa (1 0 0) (-12 -12 -24) (12 12 40) WANDER SEARCH x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +WANDER - When I don't have an enemy, I'll just wander the waypoint network aimlessly +SEARCH - When I don't have an enemy, I'll go back and forth between the nearest waypoints, looking for enemies +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Wampa( gentity_t *self) +{ + self->NPC_type = "wampa"; + + NPC_Wampa_Precache(); + + SP_NPC_spawner( self ); +} + +/*QUAKED NPC_Monster_Rancor (1 0 0) (-30 -30 -24) (30 30 104) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Monster_Rancor( gentity_t *self) +{ + self->NPC_type = "rancor"; + + SP_NPC_spawner( self ); +} + +//============================================================================================= +//DROIDS +//============================================================================================= + +/*QUAKED NPC_Droid_Interrogator (1 0 0) (-12 -12 -24) (12 12 0) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Droid_Interrogator( gentity_t *self) +{ + self->NPC_type = "interrogator"; + + SP_NPC_spawner( self ); + + NPC_Interrogator_Precache(self); +} + +/*QUAKED NPC_Droid_Probe (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Imperial Probe Droid - the multilegged floating droid that Han and Chewie shot on the ice planet Hoth +*/ +void SP_NPC_Droid_Probe( gentity_t *self) +{ + self->NPC_type = "probe"; + + SP_NPC_spawner( self ); + + NPC_Probe_Precache(); +} + +/*QUAKED NPC_Droid_Mark1 (1 0 0) (-36 -36 -24) (36 36 80) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Big walking droid + +*/ +void SP_NPC_Droid_Mark1( gentity_t *self) +{ + self->NPC_type = "mark1"; + + SP_NPC_spawner( self ); + + NPC_Mark1_Precache(); +} + +/*QUAKED NPC_Droid_Mark2 (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Small rolling droid with one gun. + +*/ +void SP_NPC_Droid_Mark2( gentity_t *self) +{ + self->NPC_type = "mark2"; + + SP_NPC_spawner( self ); + + NPC_Mark2_Precache(); +} + +/*QUAKED NPC_Droid_ATST (1 0 0) (-40 -40 -24) (40 40 248) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy +*/ +void SP_NPC_Droid_ATST( gentity_t *self) +{ + if ( (self->spawnflags&1) ) + { + self->NPC_type = "atst_vehicle"; + } + else + { + self->NPC_type = "atst"; + } + + SP_NPC_spawner( self ); + + NPC_ATST_Precache(); +} + +/*QUAKED NPC_Droid_Remote (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Remote Droid - the floating round droid used by Obi Wan to train Luke about the force while on the Millenium Falcon. +*/ +void SP_NPC_Droid_Remote( gentity_t *self) +{ + self->NPC_type = "remote"; + + SP_NPC_spawner( self ); + + NPC_Remote_Precache(); +} + +/*QUAKED NPC_Droid_Seeker (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Seeker Droid - floating round droids that shadow troopers spawn +*/ +void SP_NPC_Droid_Seeker( gentity_t *self) +{ + self->NPC_type = "seeker"; + + SP_NPC_spawner( self ); + + NPC_Seeker_Precache(); +} + +/*QUAKED NPC_Droid_Sentry (1 0 0) (-24 -24 -24) (24 24 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Sentry Droid - Large, armored floating Imperial droids with 3 forward-facing gun turrets +*/ +void SP_NPC_Droid_Sentry( gentity_t *self) +{ + self->NPC_type = "sentry"; + + SP_NPC_spawner( self ); + + NPC_Sentry_Precache(); +} + +/*QUAKED NPC_Droid_Gonk (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Gonk Droid - the droid that looks like a walking ice machine. Was in the Jawa land crawler, walking around talking to itself. + +NOTARGET by default +*/ +void SP_NPC_Droid_Gonk( gentity_t *self) +{ + self->NPC_type = "gonk"; + + SP_NPC_spawner( self ); + + //precache the Gonk sounds + NPC_Gonk_Precache(); +} + +/*QUAKED NPC_Droid_Mouse (1 0 0) (-12 -12 -24) (12 12 40) x x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +Mouse Droid - small, box shaped droid, first seen on the Death Star. Chewie yelled at it and it backed up and ran away. + +NOTARGET by default +*/ +void SP_NPC_Droid_Mouse( gentity_t *self) +{ + self->NPC_type = "mouse"; + + SP_NPC_spawner( self ); + + //precache the Mouse sounds + NPC_Mouse_Precache(); + +} + +/*QUAKED NPC_Droid_R2D2 (1 0 0) (-12 -12 -24) (12 12 40) IMPERIAL x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +R2D2 Droid - you probably know this one already. + +NOTARGET by default +*/ +void SP_NPC_Droid_R2D2( gentity_t *self) +{ + if ( self->spawnflags&1 ) + {//imperial skin + self->NPC_type = "r2d2_imp"; + } + else + { + self->NPC_type = "r2d2"; + } + + SP_NPC_spawner( self ); + + NPC_R2D2_Precache(); +} + +/*QUAKED NPC_Droid_R5D2 (1 0 0) (-12 -12 -24) (12 12 40) IMPERIAL ALWAYSDIE x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +ALWAYSDIE - won't go into spinning zombie AI when at low health. +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +R5D2 Droid - the droid originally chosen by Uncle Owen until it blew a bad motivator, and they took R2D2 instead. + +NOTARGET by default +*/ +void SP_NPC_Droid_R5D2( gentity_t *self) +{ + if ( self->spawnflags&1 ) + {//imperial skin + self->NPC_type = "r5d2_imp"; + } + else + { + self->NPC_type = "r5d2"; + } + + SP_NPC_spawner( self ); + + NPC_R5D2_Precache(); +} + +/*QUAKED NPC_Droid_Protocol (1 0 0) (-12 -12 -24) (12 12 40) IMPERIAL x x x DROPTOFLOOR CINEMATIC NOTSOLID STARTINSOLID SHY +DROPTOFLOOR - NPC can be in air, but will spawn on the closest floor surface below it +CINEMATIC - Will spawn with no default AI (BS_CINEMATIC) +NOTSOLID - Starts not solid +STARTINSOLID - Don't try to fix if spawn in solid +SHY - Spawner is shy + +NOTARGET by default +*/ +void SP_NPC_Droid_Protocol( gentity_t *self) +{ + if ( self->spawnflags&1 ) + {//imperial skin + self->NPC_type = "protocol_imp"; + } + else + { + self->NPC_type = "protocol"; + } + + SP_NPC_spawner( self ); + NPC_Protocol_Precache(); +} + + +//NPC console commands +/* +NPC_Spawn_f +*/ + +gentity_t *NPC_SpawnType( gentity_t *ent, char *npc_type, char *targetname, qboolean isVehicle ) +{ + gentity_t *NPCspawner = G_Spawn(); + vec3_t forward, end; + trace_t trace; + + if(!NPCspawner) + { + Com_Printf( S_COLOR_RED"NPC_Spawn Error: Out of entities!\n" ); + return NULL; + } + + NPCspawner->think = G_FreeEntity; + NPCspawner->nextthink = level.time + FRAMETIME; + + if ( !npc_type ) + { + return NULL; + } + + if (!npc_type[0]) + { + Com_Printf( S_COLOR_RED"Error, expected one of:\n"S_COLOR_WHITE" NPC spawn [NPC type (from ext_data/NPCs)]\n NPC spawn vehicle [VEH type (from ext_data/vehicles)]\n" ); + return NULL; + } + + if ( !ent || !ent->client ) + {//screw you, go away + return NULL; + } + + //rwwFIXMEFIXME: Care about who is issuing this command/other clients besides 0? + //Spawn it at spot of first player + //FIXME: will gib them! + AngleVectors(ent->client->ps.viewangles, forward, NULL, NULL); + VectorNormalize(forward); + VectorMA(ent->r.currentOrigin, 64, forward, end); + trap_Trace(&trace, ent->r.currentOrigin, NULL, NULL, end, 0, MASK_SOLID); + VectorCopy(trace.endpos, end); + end[2] -= 24; + trap_Trace(&trace, trace.endpos, NULL, NULL, end, 0, MASK_SOLID); + VectorCopy(trace.endpos, end); + end[2] += 24; + G_SetOrigin(NPCspawner, end); + VectorCopy(NPCspawner->r.currentOrigin, NPCspawner->s.origin); + //set the yaw so that they face away from player + NPCspawner->s.angles[1] = ent->client->ps.viewangles[1]; + + trap_LinkEntity(NPCspawner); + + NPCspawner->NPC_type = G_NewString( npc_type ); + + if ( targetname ) + { + NPCspawner->NPC_targetname = G_NewString(targetname); + } + + NPCspawner->count = 1; + + NPCspawner->delay = 0; + + //NPCspawner->spawnflags |= SFB_NOTSOLID; + + //NPCspawner->playerTeam = TEAM_FREE; + //NPCspawner->behaviorSet[BSET_SPAWN] = "common/guard"; + + if ( isVehicle ) + { + NPCspawner->classname = "NPC_Vehicle"; + } + + //call precache funcs for James' builds + if ( !Q_stricmp( "gonk", NPCspawner->NPC_type)) + { + NPC_Gonk_Precache(); + } + else if ( !Q_stricmp( "mouse", NPCspawner->NPC_type)) + { + NPC_Mouse_Precache(); + } + else if ( !Q_strncmp( "r2d2", NPCspawner->NPC_type, 4)) + { + NPC_R2D2_Precache(); + } + else if ( !Q_stricmp( "atst", NPCspawner->NPC_type)) + { + NPC_ATST_Precache(); + } + else if ( !Q_strncmp( "r5d2", NPCspawner->NPC_type, 4)) + { + NPC_R5D2_Precache(); + } + else if ( !Q_stricmp( "mark1", NPCspawner->NPC_type)) + { + NPC_Mark1_Precache(); + } + else if ( !Q_stricmp( "mark2", NPCspawner->NPC_type)) + { + NPC_Mark2_Precache(); + } + else if ( !Q_stricmp( "interrogator", NPCspawner->NPC_type)) + { + NPC_Interrogator_Precache(NULL); + } + else if ( !Q_stricmp( "probe", NPCspawner->NPC_type)) + { + NPC_Probe_Precache(); + } + else if ( !Q_stricmp( "seeker", NPCspawner->NPC_type)) + { + NPC_Seeker_Precache(); + } + else if ( !Q_stricmp( "remote", NPCspawner->NPC_type)) + { + NPC_Remote_Precache(); + } + else if ( !Q_strncmp( "shadowtrooper", NPCspawner->NPC_type, 13 ) ) + { + NPC_ShadowTrooper_Precache(); + } + else if ( !Q_stricmp( "minemonster", NPCspawner->NPC_type )) + { + NPC_MineMonster_Precache(); + } + else if ( !Q_stricmp( "howler", NPCspawner->NPC_type )) + { + NPC_Howler_Precache(); + } + else if ( !Q_stricmp( "sentry", NPCspawner->NPC_type )) + { + NPC_Sentry_Precache(); + } + else if ( !Q_stricmp( "protocol", NPCspawner->NPC_type )) + { + NPC_Protocol_Precache(); + } + else if ( !Q_stricmp( "galak_mech", NPCspawner->NPC_type )) + { + NPC_GalakMech_Precache(); + } + else if ( !Q_stricmp( "wampa", NPCspawner->NPC_type )) + { + NPC_Wampa_Precache(); + } + + return (NPC_Spawn_Do( NPCspawner )); +} + +void NPC_Spawn_f( gentity_t *ent ) +{ + char npc_type[1024]; + char targetname[1024]; + qboolean isVehicle = qfalse; + + trap_Argv(2, npc_type, 1024); + if ( Q_stricmp( "vehicle", npc_type ) == 0 ) + { + isVehicle = qtrue; + trap_Argv(3, npc_type, 1024); + trap_Argv(4, targetname, 1024); + } + else + { + trap_Argv(3, targetname, 1024); + } + + NPC_SpawnType( ent, npc_type, targetname, isVehicle ); +} + +/* +NPC_Kill_f +*/ +extern stringID_table_t TeamTable[]; +void NPC_Kill_f( void ) +{ + int n; + gentity_t *player; + char name[1024]; + team_t killTeam = TEAM_FREE; + qboolean killNonSF = qfalse; + + trap_Argv(2, name, 1024); + + if ( !name[0] ) + { + Com_Printf( S_COLOR_RED"Error, Expected:\n"); + Com_Printf( S_COLOR_RED"NPC kill '[NPC targetname]' - kills NPCs with certain targetname\n" ); + Com_Printf( S_COLOR_RED"or\n" ); + Com_Printf( S_COLOR_RED"NPC kill 'all' - kills all NPCs\n" ); + Com_Printf( S_COLOR_RED"or\n" ); + Com_Printf( S_COLOR_RED"NPC team '[teamname]' - kills all NPCs of a certain team ('nonally' is all but your allies)\n" ); + return; + } + + if ( Q_stricmp( "team", name ) == 0 ) + { + trap_Argv(3, name, 1024); + + if ( !name[0] ) + { + Com_Printf( S_COLOR_RED"NPC_Kill Error: 'npc kill team' requires a team name!\n" ); + Com_Printf( S_COLOR_RED"Valid team names are:\n"); + for ( n = (TEAM_FREE + 1); n < TEAM_NUM_TEAMS; n++ ) + { + Com_Printf( S_COLOR_RED"%s\n", TeamNames[n] ); + } + Com_Printf( S_COLOR_RED"nonally - kills all but your teammates\n" ); + return; + } + + if ( Q_stricmp( "nonally", name ) == 0 ) + { + killNonSF = qtrue; + } + else + { + killTeam = (team_t)GetIDForString( TeamTable, name ); + + if ( killTeam == TEAM_FREE ) + { + Com_Printf( S_COLOR_RED"NPC_Kill Error: team '%s' not recognized\n", name ); + Com_Printf( S_COLOR_RED"Valid team names are:\n"); + for ( n = (TEAM_FREE + 1); n < TEAM_NUM_TEAMS; n++ ) + { + Com_Printf( S_COLOR_RED"%s\n", TeamNames[n] ); + } + Com_Printf( S_COLOR_RED"nonally - kills all but your teammates\n" ); + return; + } + } + } + + for ( n = 1; n < ENTITYNUM_MAX_NORMAL; n++) + { + player = &g_entities[n]; + if (!player->inuse) { + continue; + } + if ( killNonSF ) + { + if ( player ) + { + if ( player->client ) + { + if ( player->client->playerTeam != NPCTEAM_PLAYER ) + { + Com_Printf( S_COLOR_GREEN"Killing NPC %s named %s\n", player->NPC_type, player->targetname ); + player->health = 0; + + if (player->die && player->client) + { + player->die(player, player, player, player->client->pers.maxHealth, MOD_UNKNOWN); + } + } + } + else if ( player->NPC_type && player->classname && player->classname[0] && Q_stricmp( "NPC_starfleet", player->classname ) != 0 ) + {//A spawner, remove it + Com_Printf( S_COLOR_GREEN"Removing NPC spawner %s with NPC named %s\n", player->NPC_type, player->NPC_targetname ); + G_FreeEntity( player ); + //FIXME: G_UseTargets2(player, player, player->NPC_target & player->target);? + } + } + } + else if ( player && player->NPC && player->client ) + { + if ( killTeam != TEAM_FREE ) + { + if ( player->client->playerTeam == killTeam ) + { + Com_Printf( S_COLOR_GREEN"Killing NPC %s named %s\n", player->NPC_type, player->targetname ); + player->health = 0; + if (player->die) + { + player->die(player, player, player, player->client->pers.maxHealth, MOD_UNKNOWN); + } + } + } + else if( (player->targetname && Q_stricmp( name, player->targetname ) == 0) + || Q_stricmp( name, "all" ) == 0 ) + { + Com_Printf( S_COLOR_GREEN"Killing NPC %s named %s\n", player->NPC_type, player->targetname ); + player->health = 0; + player->client->ps.stats[STAT_HEALTH] = 0; + if (player->die) + { + player->die(player, player, player, 100, MOD_UNKNOWN); + } + } + } + /* + else if ( player && (player->r.svFlags&SVF_NPC_PRECACHE) ) + {//a spawner + Com_Printf( S_COLOR_GREEN"Removing NPC spawner %s named %s\n", player->NPC_type, player->targetname ); + G_FreeEntity( player ); + } + */ + //rwwFIXMEFIXME: should really do something here. + } +} + +void NPC_PrintScore( gentity_t *ent ) +{ + Com_Printf( "%s: %d\n", ent->targetname, ent->client->ps.persistant[PERS_SCORE] ); +} + +/* +Svcmd_NPC_f + +parse and dispatch bot commands +*/ +qboolean showBBoxes = qfalse; +void Cmd_NPC_f( gentity_t *ent ) +{ + char cmd[1024]; + + trap_Argv( 1, cmd, 1024 ); + + if ( !cmd[0] ) + { + Com_Printf( "Valid NPC commands are:\n" ); + Com_Printf( " spawn [NPC type (from NCPCs.cfg)]\n" ); + Com_Printf( " kill [NPC targetname] or [all(kills all NPCs)] or 'team [teamname]'\n" ); + Com_Printf( " showbounds (draws exact bounding boxes of NPCs)\n" ); + Com_Printf( " score [NPC targetname] (prints number of kills per NPC)\n" ); + } + else if ( Q_stricmp( cmd, "spawn" ) == 0 ) + { + NPC_Spawn_f( ent ); + } + else if ( Q_stricmp( cmd, "kill" ) == 0 ) + { + NPC_Kill_f(); + } + else if ( Q_stricmp( cmd, "showbounds" ) == 0 ) + {//Toggle on and off + showBBoxes = showBBoxes ? qfalse : qtrue; + } + else if ( Q_stricmp ( cmd, "score" ) == 0 ) + { + char cmd2[1024]; + gentity_t *ent = NULL; + + trap_Argv(2, cmd2, 1024); + + if ( !cmd2[0] ) + {//Show the score for all NPCs + int i; + + Com_Printf( "SCORE LIST:\n" ); + for ( i = 0; i < ENTITYNUM_WORLD; i++ ) + { + ent = &g_entities[i]; + if ( !ent || !ent->client ) + { + continue; + } + NPC_PrintScore( ent ); + } + } + else + { + if ( (ent = G_Find( NULL, FOFS(targetname), cmd2 )) != NULL && ent->client ) + { + NPC_PrintScore( ent ); + } + else + { + Com_Printf( "ERROR: NPC score - no such NPC %s\n", cmd2 ); + } + } + } +} diff --git a/code/game/NPC_stats.c b/code/game/NPC_stats.c new file mode 100644 index 0000000..b334eec --- /dev/null +++ b/code/game/NPC_stats.c @@ -0,0 +1,3302 @@ +//NPC_stats.cpp +#include "b_local.h" +#include "b_public.h" +#include "anims.h" +#include "../ghoul2/G2.h" + +extern qboolean NPCsPrecached; + +#include "../namespace_begin.h" +extern qboolean WP_SaberParseParms( const char *SaberName, saberInfo_t *saber ); +extern void WP_RemoveSaber( saberInfo_t *sabers, int saberNum ); +#include "../namespace_end.h" + +stringID_table_t TeamTable[] = +{ + ENUM2STRING(NPCTEAM_FREE), // caution, some code checks a team_t via "if (!team_t_varname)" so I guess this should stay as entry 0, great or what? -slc + ENUM2STRING(NPCTEAM_PLAYER), + ENUM2STRING(NPCTEAM_ENEMY), + ENUM2STRING(NPCTEAM_NEUTRAL), // most droids are team_neutral, there are some exceptions like Probe,Seeker,Interrogator + "", -1 +}; + +// this list was made using the model directories, this MUST be in the same order as the CLASS_ enum in teams.h +stringID_table_t ClassTable[] = +{ + ENUM2STRING(CLASS_NONE), // hopefully this will never be used by an npc), just covering all bases + ENUM2STRING(CLASS_ATST), // technically droid... + ENUM2STRING(CLASS_BARTENDER), + ENUM2STRING(CLASS_BESPIN_COP), + ENUM2STRING(CLASS_CLAW), + ENUM2STRING(CLASS_COMMANDO), + ENUM2STRING(CLASS_DESANN), + ENUM2STRING(CLASS_FISH), + ENUM2STRING(CLASS_FLIER2), + ENUM2STRING(CLASS_GALAK), + ENUM2STRING(CLASS_GLIDER), + ENUM2STRING(CLASS_GONK), // droid + ENUM2STRING(CLASS_GRAN), + ENUM2STRING(CLASS_HOWLER), +// ENUM2STRING(CLASS_RANCOR), + ENUM2STRING(CLASS_IMPERIAL), + ENUM2STRING(CLASS_IMPWORKER), + ENUM2STRING(CLASS_INTERROGATOR), // droid + ENUM2STRING(CLASS_JAN), + ENUM2STRING(CLASS_JEDI), + ENUM2STRING(CLASS_KYLE), + ENUM2STRING(CLASS_LANDO), + ENUM2STRING(CLASS_LIZARD), + ENUM2STRING(CLASS_LUKE), + ENUM2STRING(CLASS_MARK1), // droid + ENUM2STRING(CLASS_MARK2), // droid + ENUM2STRING(CLASS_GALAKMECH), // droid + ENUM2STRING(CLASS_MINEMONSTER), + ENUM2STRING(CLASS_MONMOTHA), + ENUM2STRING(CLASS_MORGANKATARN), + ENUM2STRING(CLASS_MOUSE), // droid + ENUM2STRING(CLASS_MURJJ), + ENUM2STRING(CLASS_PRISONER), + ENUM2STRING(CLASS_PROBE), // droid + ENUM2STRING(CLASS_PROTOCOL), // droid + ENUM2STRING(CLASS_R2D2), // droid + ENUM2STRING(CLASS_R5D2), // droid + ENUM2STRING(CLASS_REBEL), + ENUM2STRING(CLASS_REBORN), + ENUM2STRING(CLASS_REELO), + ENUM2STRING(CLASS_REMOTE), + ENUM2STRING(CLASS_RODIAN), + ENUM2STRING(CLASS_SEEKER), // droid + ENUM2STRING(CLASS_SENTRY), + ENUM2STRING(CLASS_SHADOWTROOPER), + ENUM2STRING(CLASS_STORMTROOPER), + ENUM2STRING(CLASS_SWAMP), + ENUM2STRING(CLASS_SWAMPTROOPER), + ENUM2STRING(CLASS_TAVION), + ENUM2STRING(CLASS_TRANDOSHAN), + ENUM2STRING(CLASS_UGNAUGHT), + ENUM2STRING(CLASS_JAWA), + ENUM2STRING(CLASS_WEEQUAY), + ENUM2STRING(CLASS_BOBAFETT), + //ENUM2STRING(CLASS_ROCKETTROOPER), + //ENUM2STRING(CLASS_PLAYER), + ENUM2STRING(CLASS_VEHICLE), + ENUM2STRING(CLASS_RANCOR), + ENUM2STRING(CLASS_WAMPA), + "", -1 +}; + +stringID_table_t BSTable[] = +{ + ENUM2STRING(BS_DEFAULT),//# default behavior for that NPC + ENUM2STRING(BS_ADVANCE_FIGHT),//# Advance to captureGoal and shoot enemies if you can + ENUM2STRING(BS_SLEEP),//# Play awake script when startled by sound + ENUM2STRING(BS_FOLLOW_LEADER),//# Follow your leader and shoot any enemies you come across + ENUM2STRING(BS_JUMP),//# Face navgoal and jump to it. + ENUM2STRING(BS_SEARCH),//# Using current waypoint as a base), search the immediate branches of waypoints for enemies + ENUM2STRING(BS_WANDER),//# Wander down random waypoint paths + ENUM2STRING(BS_NOCLIP),//# Moves through walls), etc. + ENUM2STRING(BS_REMOVE),//# Waits for player to leave PVS then removes itself + ENUM2STRING(BS_CINEMATIC),//# Does nothing but face it's angles and move to a goal if it has one + //the rest are internal only + "", -1, +}; + +#define stringIDExpand(str, strEnum) str, strEnum, ENUM2STRING(strEnum) + +stringID_table_t BSETTable[] = +{ + ENUM2STRING(BSET_SPAWN),//# script to use when first spawned + ENUM2STRING(BSET_USE),//# script to use when used + ENUM2STRING(BSET_AWAKE),//# script to use when awoken/startled + ENUM2STRING(BSET_ANGER),//# script to use when aquire an enemy + ENUM2STRING(BSET_ATTACK),//# script to run when you attack + ENUM2STRING(BSET_VICTORY),//# script to run when you kill someone + ENUM2STRING(BSET_LOSTENEMY),//# script to run when you can't find your enemy + ENUM2STRING(BSET_PAIN),//# script to use when take pain + ENUM2STRING(BSET_FLEE),//# script to use when take pain below 50% of health + ENUM2STRING(BSET_DEATH),//# script to use when killed + ENUM2STRING(BSET_DELAYED),//# script to run when self->delayScriptTime is reached + ENUM2STRING(BSET_BLOCKED),//# script to run when blocked by a friendly NPC or player + ENUM2STRING(BSET_BUMPED),//# script to run when bumped into a friendly NPC or player (can set bumpRadius) + ENUM2STRING(BSET_STUCK),//# script to run when blocked by a wall + ENUM2STRING(BSET_FFIRE),//# script to run when player shoots their own teammates + ENUM2STRING(BSET_FFDEATH),//# script to run when player kills a teammate + stringIDExpand("", BSET_INVALID), + "", -1, +}; + +#include "../namespace_begin.h" +extern stringID_table_t WPTable[]; +extern stringID_table_t FPTable[]; +#include "../namespace_end.h" + +char *TeamNames[TEAM_NUM_TEAMS] = +{ + "", +// "starfleet", +// "borg", +// "parasite", +// "scavengers", +// "klingon", +// "malon", +// "hirogen", +// "imperial", +// "stasis", +// "species8472", +// "dreadnought", +// "forge", +// "disguise", +// "player (not valid)" + "player", + "enemy", + "neutral" +}; + +// this list was made using the model directories, this MUST be in the same order as the CLASS_ enum in teams.h +char *ClassNames[CLASS_NUM_CLASSES] = +{ + "", // class none + "atst", + "bartender", + "bespin_cop", + "claw", + "commando", + "desann", + "fish", + "flier2", + "galak", + "glider", + "gonk", + "gran", + "howler", + "imperial", + "impworker", + "interrogator", + "jan", + "jedi", + "kyle", + "lando", + "lizard", + "luke", + "mark1", + "mark2", + "galak_mech", + "minemonster", + "monmotha", + "morgankatarn", + "mouse", + "murjj", + "prisoner", + "probe", + "protocol", + "r2d2", + "r5d2", + "rebel", + "reborn", + "reelo", + "remote", + "rodian", + "seeker", + "sentry", + "shadowtrooper", + "stormtrooper", + "swamp", + "swamptrooper", + "tavion", + "trandoshan", + "ugnaught", + "weequay", + "bobafett", + "vehicle", + "rancor", + "wampa", +}; + + +/* +NPC_ReactionTime +*/ +//FIXME use grandom in here +int NPC_ReactionTime ( void ) +{ + return 200 * ( 6 - NPCInfo->stats.reactions ); +} + +// +// parse support routines +// + +#include "../namespace_begin.h" +extern qboolean BG_ParseLiteral( const char **data, const char *string ); +#include "../namespace_end.h" + +// +// NPC parameters file : scripts/NPCs.cfg +// +#define MAX_NPC_DATA_SIZE 0x20000 +char NPCParms[MAX_NPC_DATA_SIZE]; +char NPCFile[MAX_QPATH]; + +/* +team_t TranslateTeamName( const char *name ) +{ + int n; + + for ( n = (NPCTEAM_FREE + 1); n < NPCTEAM_NUM_TEAMS; n++ ) + { + if ( Q_stricmp( TeamNames[n], name ) == 0 ) + { + return ((team_t) n); + } + } + + return NPCTEAM_FREE; +} + +class_t TranslateClassName( const char *name ) +{ + int n; + + for ( n = (CLASS_NONE + 1); n < CLASS_NUM_CLASSES; n++ ) + { + if ( Q_stricmp( ClassNames[n], name ) == 0 ) + { + return ((class_t) n); + } + } + + return CLASS_NONE; // I hope this never happens, maybe print a warning +} +*/ + +/* +static race_t TranslateRaceName( const char *name ) +{ + if ( !Q_stricmp( name, "human" ) ) + { + return RACE_HUMAN; + } + return RACE_NONE; +} +*/ +/* +static rank_t TranslateRankName( const char *name ) + + Should be used to determine pip bolt-ons +*/ +static rank_t TranslateRankName( const char *name ) +{ + if ( !Q_stricmp( name, "civilian" ) ) + { + return RANK_CIVILIAN; + } + + if ( !Q_stricmp( name, "crewman" ) ) + { + return RANK_CREWMAN; + } + + if ( !Q_stricmp( name, "ensign" ) ) + { + return RANK_ENSIGN; + } + + if ( !Q_stricmp( name, "ltjg" ) ) + { + return RANK_LT_JG; + } + + if ( !Q_stricmp( name, "lt" ) ) + { + return RANK_LT; + } + + if ( !Q_stricmp( name, "ltcomm" ) ) + { + return RANK_LT_COMM; + } + + if ( !Q_stricmp( name, "commander" ) ) + { + return RANK_COMMANDER; + } + + if ( !Q_stricmp( name, "captain" ) ) + { + return RANK_CAPTAIN; + } + + return RANK_CIVILIAN; +} + +#include "../namespace_begin.h" +extern saber_colors_t TranslateSaberColor( const char *name ); +#include "../namespace_end.h" + +/* static int MethodNameToNumber( const char *name ) { + if ( !Q_stricmp( name, "EXPONENTIAL" ) ) { + return METHOD_EXPONENTIAL; + } + if ( !Q_stricmp( name, "LINEAR" ) ) { + return METHOD_LINEAR; + } + if ( !Q_stricmp( name, "LOGRITHMIC" ) ) { + return METHOD_LOGRITHMIC; + } + if ( !Q_stricmp( name, "ALWAYS" ) ) { + return METHOD_ALWAYS; + } + if ( !Q_stricmp( name, "NEVER" ) ) { + return METHOD_NEVER; + } + return -1; +} + +static int ItemNameToNumber( const char *name, int itemType ) { +// int n; + + for ( n = 0; n < bg_numItems; n++ ) { + if ( bg_itemlist[n].type != itemType ) { + continue; + } + if ( Q_stricmp( bg_itemlist[n].classname, name ) == 0 ) { + return bg_itemlist[n].tag; + } + } + return -1; +} +*/ + +//rwwFIXMEFIXME: movetypes +/* +static int MoveTypeNameToEnum( const char *name ) +{ + if(!Q_stricmp("runjump", name)) + { + return MT_RUNJUMP; + } + else if(!Q_stricmp("walk", name)) + { + return MT_WALK; + } + else if(!Q_stricmp("flyswim", name)) + { + return MT_FLYSWIM; + } + else if(!Q_stricmp("static", name)) + { + return MT_STATIC; + } + + return MT_STATIC; +} +*/ + +//#define CONVENIENT_ANIMATION_FILE_DEBUG_THING + +#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING +void SpewDebugStuffToFile(animation_t *anims) +{ + char BGPAFtext[40000]; + fileHandle_t f; + int i = 0; + + trap_FS_FOpenFile("file_of_debug_stuff_SP.txt", &f, FS_WRITE); + + if (!f) + { + return; + } + + BGPAFtext[0] = 0; + + while (i < MAX_ANIMATIONS) + { + strcat(BGPAFtext, va("%i %i\n", i, anims[i].frameLerp)); + i++; + } + + trap_FS_Write(BGPAFtext, strlen(BGPAFtext), f); + trap_FS_FCloseFile(f); +} +#endif + +qboolean G_ParseAnimFileSet( const char *filename, const char *animCFG, int *animFileIndex ) +{ + *animFileIndex = BG_ParseAnimationFile(filename, NULL, qfalse); + //if it's humanoid we should have it cached and return it, if it is not it will be loaded (unless it's also cached already) + + if (*animFileIndex == -1) + { + return qfalse; + } + + //I guess this isn't really even needed game-side. + //BG_ParseAnimationSndFile(filename, *animFileIndex); + return qtrue; +} + +void NPC_PrecacheAnimationCFG( const char *NPC_type ) +{ +#if 0 //rwwFIXMEFIXME: Actually precache stuff here. + char filename[MAX_QPATH]; + const char *token; + const char *value; + const char *p; + int junk; + + if ( !Q_stricmp( "random", NPC_type ) ) + {//sorry, can't precache a random just yet + return; + } + + p = NPCParms; + COM_BeginParseSession(NPCFile); + + // look for the right NPC + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + return; + + if ( !Q_stricmp( token, NPC_type ) ) + { + break; + } + + SkipBracedSection( &p ); + } + + if ( !p ) + { + return; + } + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return; + } + + // parse the NPC info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPC_type ); + return; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + // legsmodel + if ( !Q_stricmp( token, "legsmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //must copy data out of this pointer into a different part of memory because the funcs we're about to call will call COM_ParseExt + Q_strncpyz( filename, value, sizeof( filename ) ); + G_ParseAnimFileSet( filename, filename, &junk ); + return; + } + + // playerModel + if ( !Q_stricmp( token, "playerModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + /* + char animName[MAX_QPATH]; + char *GLAName; + char *slash = NULL; + char *strippedName; + + int handle = gi.G2API_PrecacheGhoul2Model( va( "models/players/%s/model.glm", value ) ); + if ( handle > 0 )//FIXME: isn't 0 a valid handle? + { + GLAName = gi.G2API_GetAnimFileNameIndex( handle ); + if ( GLAName ) + { + Q_strncpyz( animName, GLAName, sizeof( animName ), qtrue ); + slash = strrchr( animName, '/' ); + if ( slash ) + { + *slash = 0; + } + strippedName = COM_SkipPath( animName ); + + //must copy data out of this pointer into a different part of memory because the funcs we're about to call will call COM_ParseExt + Q_strncpyz( filename, value, sizeof( filename ), qtrue ); + G_ParseAnimFileSet( value, strippedName, &junk );//qfalse ); + //FIXME: still not precaching the animsounds.cfg? + return; + } + } + */ + //rwwFIXMEFIXME: Do this properly. + } + } +#endif +} + +extern int NPC_WeaponsForTeam( team_t team, int spawnflags, const char *NPC_type ); +void NPC_PrecacheWeapons( team_t playerTeam, int spawnflags, char *NPCtype ) +{ + int weapons = NPC_WeaponsForTeam( playerTeam, spawnflags, NPCtype ); + int curWeap; + + for ( curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ ) + { + if (weapons & (1 << curWeap)) + { + RegisterItem(BG_FindItemForWeapon((weapon_t)curWeap)); + } + } + +#if 0 //rwwFIXMEFIXME: actually precache weapons here + int weapons = NPC_WeaponsForTeam( playerTeam, spawnflags, NPCtype ); + gitem_t *item; + for ( int curWeap = WP_SABER; curWeap < WP_NUM_WEAPONS; curWeap++ ) + { + if ( (weapons & ( 1 << curWeap )) ) + { + item = FindItemForWeapon( ((weapon_t)(curWeap)) ); //precache the weapon + CG_RegisterItemSounds( (item-bg_itemlist) ); + CG_RegisterItemVisuals( (item-bg_itemlist) ); + //precache the in-hand/in-world ghoul2 weapon model + + char weaponModel[64]; + + strcpy (weaponModel, weaponData[curWeap].weaponMdl); + if (char *spot = strstr(weaponModel, ".md3") ) { + *spot = 0; + spot = strstr(weaponModel, "_w");//i'm using the in view weapon array instead of scanning the item list, so put the _w back on + if (!spot) { + strcat (weaponModel, "_w"); + } + strcat (weaponModel, ".glm"); //and change to ghoul2 + } + gi.G2API_PrecacheGhoul2Model( weaponModel ); // correct way is item->world_model + } + } +#endif +} + +/* +void NPC_Precache ( char *NPCName ) + +Precaches NPC skins, tgas and md3s. + +*/ +void NPC_Precache ( gentity_t *spawner ) +{ + npcteam_t playerTeam = NPCTEAM_FREE; + const char *token; + const char *value; + const char *p; + char *patch; + char sound[MAX_QPATH]; + qboolean md3Model = qfalse; + char playerModel[MAX_QPATH]; + char customSkin[MAX_QPATH]; + + if ( !Q_stricmp( "random", spawner->NPC_type ) ) + {//sorry, can't precache a random just yet + return; + } + strcpy(customSkin,"default"); + + p = NPCParms; + COM_BeginParseSession(NPCFile); + + // look for the right NPC + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + return; + + if ( !Q_stricmp( token, spawner->NPC_type ) ) + { + break; + } + + SkipBracedSection( &p ); + } + + if ( !p ) + { + return; + } + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return; + } + + // parse the NPC info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", spawner->NPC_type ); + return; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + // headmodel + if ( !Q_stricmp( token, "headmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + } + else + { + //Q_strncpyz( ri.headModelName, value, sizeof(ri.headModelName), qtrue); + } + md3Model = qtrue; + continue; + } + + // torsomodel + if ( !Q_stricmp( token, "torsomodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + } + else + { + //Q_strncpyz( ri.torsoModelName, value, sizeof(ri.torsoModelName), qtrue); + } + md3Model = qtrue; + continue; + } + + // legsmodel + if ( !Q_stricmp( token, "legsmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //Q_strncpyz( ri.legsModelName, value, sizeof(ri.legsModelName), qtrue); + md3Model = qtrue; + continue; + } + + // playerModel + if ( !Q_stricmp( token, "playerModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( playerModel, value, sizeof(playerModel)); + md3Model = qfalse; + continue; + } + + // customSkin + if ( !Q_stricmp( token, "customSkin" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( customSkin, value, sizeof(customSkin)); + continue; + } + + // playerTeam + if ( !Q_stricmp( token, "playerTeam" ) ) + { + char tk[4096]; //rww - hackilicious! + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //playerTeam = TranslateTeamName(value); + Com_sprintf(tk, sizeof(tk), "NPC%s", token); + playerTeam = (team_t)GetIDForString( TeamTable, tk ); + continue; + } + + + // snd + if ( !Q_stricmp( token, "snd" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + if ( !(spawner->r.svFlags&SVF_NO_BASIC_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + spawner->s.csSounds_Std = G_SoundIndex( va("*$%s", sound) ); + } + continue; + } + + // sndcombat + if ( !Q_stricmp( token, "sndcombat" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + if ( !(spawner->r.svFlags&SVF_NO_COMBAT_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + spawner->s.csSounds_Combat = G_SoundIndex( va("*$%s", sound) ); + } + continue; + } + + // sndextra + if ( !Q_stricmp( token, "sndextra" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + if ( !(spawner->r.svFlags&SVF_NO_EXTRA_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + spawner->s.csSounds_Extra = G_SoundIndex( va("*$%s", sound) ); + } + continue; + } + + // sndjedi + if ( !Q_stricmp( token, "sndjedi" ) ) { + if ( COM_ParseString( &p, &value ) ) { + continue; + } + if ( !(spawner->r.svFlags&SVF_NO_EXTRA_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + spawner->s.csSounds_Jedi = G_SoundIndex( va("*$%s", sound) ); + } + continue; + } + + if (!Q_stricmp(token, "weapon")) + { + int curWeap; + + if (COM_ParseString(&p, &value)) + { + continue; + } + + curWeap = GetIDForString( WPTable, value ); + + if (curWeap > WP_NONE && curWeap < WP_NUM_WEAPONS) + { + RegisterItem(BG_FindItemForWeapon((weapon_t)curWeap)); + } + continue; + } + } + + // If we're not a vehicle, then an error here would be valid... + if ( !spawner->client || spawner->client->NPC_class != CLASS_VEHICLE ) + { + if ( md3Model ) + { + Com_Printf("MD3 model using NPCs are not supported in MP\n"); + } + else + { //if we have a model/skin then index them so they'll be registered immediately + //when the client gets a configstring update. + char modelName[MAX_QPATH]; + + Com_sprintf(modelName, sizeof(modelName), "models/players/%s/model.glm", playerModel); + if (customSkin[0]) + { //append it after a * + strcat( modelName, va("*%s", customSkin) ); + } + + G_ModelIndex(modelName); + } + } + + //precache this NPC's possible weapons + NPC_PrecacheWeapons( playerTeam, spawner->spawnflags, spawner->NPC_type ); + +// CG_RegisterNPCCustomSounds( &ci ); +// CG_RegisterNPCEffects( playerTeam ); + //rwwFIXMEFIXME: same + //FIXME: Look for a "sounds" directory and precache death, pain, alert sounds +} + +#if 0 +void NPC_BuildRandom( gentity_t *NPC ) +{ + int sex, color, head; + + sex = Q_irand(0, 2); + color = Q_irand(0, 2); + switch( sex ) + { + case 0://female + head = Q_irand(0, 2); + switch( head ) + { + default: + case 0: + Q_strncpyz( NPC->client->renderInfo.headModelName, "garren", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 1: + Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/salma", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 2: + Q_strncpyz( NPC->client->renderInfo.headModelName, "garren/mackey", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + color = Q_irand(3, 5);//torso needs to be afam + break; + } + switch( color ) + { + default: + case 0: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/gold", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 1: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 2: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 3: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframG", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 4: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframR", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 5: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewfemale/aframB", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + } + Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewfemale", sizeof(NPC->client->renderInfo.legsModelName), qtrue ); + break; + default: + case 1://male + case 2://male + head = Q_irand(0, 4); + switch( head ) + { + default: + case 0: + Q_strncpyz( NPC->client->renderInfo.headModelName, "chakotay/nelson", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 1: + Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/chase", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 2: + Q_strncpyz( NPC->client->renderInfo.headModelName, "doctor/pasty", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 3: + Q_strncpyz( NPC->client->renderInfo.headModelName, "kim/durk", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + case 4: + Q_strncpyz( NPC->client->renderInfo.headModelName, "paris/kray", sizeof(NPC->client->renderInfo.headModelName), qtrue ); + break; + } + switch( color ) + { + default: + case 0: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/red", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 1: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + case 2: + Q_strncpyz( NPC->client->renderInfo.torsoModelName, "crewthin/blue", sizeof(NPC->client->renderInfo.torsoModelName), qtrue ); + break; + //NOTE: 3 - 5 should be red, gold & blue, afram hands + } + Q_strncpyz( NPC->client->renderInfo.legsModelName, "crewthin", sizeof(NPC->client->renderInfo.legsModelName), qtrue ); + break; + } + + NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = Q_irand(87, 102)/100.0f; +// NPC->client->race = RACE_HUMAN; + NPC->NPC->rank = RANK_CREWMAN; + NPC->client->playerTeam = NPC->s.teamowner = TEAM_PLAYER; + NPC->client->clientInfo.customBasicSoundDir = "kyle";//FIXME: generic default? +} +#endif + +extern void SetupGameGhoul2Model(gentity_t *ent, char *modelname, char *skinName); +qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC ) +{ + const char *token; + const char *value; + const char *p; + int n; + float f; + char *patch; + char sound[MAX_QPATH]; + char playerModel[MAX_QPATH]; + char customSkin[MAX_QPATH]; + renderInfo_t *ri = &NPC->client->renderInfo; + gNPCstats_t *stats = NULL; + qboolean md3Model = qtrue; + char surfOff[1024]; + char surfOn[1024]; + qboolean parsingPlayer = qfalse; + vec3_t playerMins; + vec3_t playerMaxs; + int npcSaber1 = 0; + int npcSaber2 = 0; + + VectorSet(playerMins, -15, -15, DEFAULT_MINS_2); + VectorSet(playerMaxs, 15, 15, DEFAULT_MAXS_2); + + strcpy(customSkin,"default"); + if ( !NPCName || !NPCName[0]) + { + NPCName = "Player"; + } + + if ( !NPC->s.number && NPC->client != NULL ) + {//player, only want certain data + parsingPlayer = qtrue; + } + + if ( NPC->NPC ) + { + stats = &NPC->NPC->stats; +/* + NPC->NPC->allWeaponOrder[0] = WP_BRYAR_PISTOL; + NPC->NPC->allWeaponOrder[1] = WP_SABER; + NPC->NPC->allWeaponOrder[2] = WP_IMOD; + NPC->NPC->allWeaponOrder[3] = WP_SCAVENGER_RIFLE; + NPC->NPC->allWeaponOrder[4] = WP_TRICORDER; + NPC->NPC->allWeaponOrder[6] = WP_NONE; + NPC->NPC->allWeaponOrder[6] = WP_NONE; + NPC->NPC->allWeaponOrder[7] = WP_NONE; +*/ + // fill in defaults + stats->aggression = 3; + stats->aim = 3; + stats->earshot = 1024; + stats->evasion = 3; + stats->hfov = 90; + stats->intelligence = 3; + stats->move = 3; + stats->reactions = 3; + stats->vfov = 60; + stats->vigilance = 0.1f; + stats->visrange = 1024; + + stats->health = 0; + + stats->yawSpeed = 90; + stats->walkSpeed = 90; + stats->runSpeed = 300; + stats->acceleration = 15;//Increase/descrease speed this much per frame (20fps) + } + else + { + stats = NULL; + } + + //Set defaults + //FIXME: should probably put default torso and head models, but what about enemies + //that don't have any- like Stasis? + //Q_strncpyz( ri->headModelName, DEFAULT_HEADMODEL, sizeof(ri->headModelName), qtrue); + //Q_strncpyz( ri->torsoModelName, DEFAULT_TORSOMODEL, sizeof(ri->torsoModelName), qtrue); + //Q_strncpyz( ri->legsModelName, DEFAULT_LEGSMODEL, sizeof(ri->legsModelName), qtrue); + //FIXME: should we have one for weapon too? + memset( (char *)surfOff, 0, sizeof(surfOff) ); + memset( (char *)surfOn, 0, sizeof(surfOn) ); + + /* + ri->headYawRangeLeft = 50; + ri->headYawRangeRight = 50; + ri->headPitchRangeUp = 40; + ri->headPitchRangeDown = 50; + ri->torsoYawRangeLeft = 60; + ri->torsoYawRangeRight = 60; + ri->torsoPitchRangeUp = 30; + ri->torsoPitchRangeDown = 70; + */ + + ri->headYawRangeLeft = 80; + ri->headYawRangeRight = 80; + ri->headPitchRangeUp = 45; + ri->headPitchRangeDown = 45; + ri->torsoYawRangeLeft = 60; + ri->torsoYawRangeRight = 60; + ri->torsoPitchRangeUp = 30; + ri->torsoPitchRangeDown = 50; + + VectorCopy(playerMins, NPC->r.mins); + VectorCopy(playerMaxs, NPC->r.maxs); + NPC->client->ps.crouchheight = CROUCH_MAXS_2; + NPC->client->ps.standheight = DEFAULT_MAXS_2; + + //rwwFIXMEFIXME: ... + /* + NPC->client->moveType = MT_RUNJUMP; + + NPC->client->dismemberProbHead = 100; + NPC->client->dismemberProbArms = 100; + NPC->client->dismemberProbHands = 100; + NPC->client->dismemberProbWaist = 100; + NPC->client->dismemberProbLegs = 100; + + NPC->s.modelScale[0] = NPC->s.modelScale[1] = NPC->s.modelScale[2] = 1.0f; + */ + + NPC->client->ps.customRGBA[0]=255; + NPC->client->ps.customRGBA[1]=255; + NPC->client->ps.customRGBA[2]=255; + NPC->client->ps.customRGBA[3]=255; + + if ( !Q_stricmp( "random", NPCName ) ) + {//Randomly assemble a starfleet guy + //NPC_BuildRandom( NPC ); + Com_Printf("RANDOM NPC NOT SUPPORTED IN MP\n"); + return qfalse; + } + else + { + int fp; + + p = NPCParms; + COM_BeginParseSession(NPCFile); + + // look for the right NPC + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, NPCName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return qfalse; + } + + // parse the NPC info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", NPCName ); + return qfalse; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + //===MODEL PROPERTIES=========================================================== + // custom color + if ( !Q_stricmp( token, "customRGBA" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !Q_stricmp( value, "random") ) + { + NPC->client->ps.customRGBA[0]=Q_irand(0,255); + NPC->client->ps.customRGBA[1]=Q_irand(0,255); + NPC->client->ps.customRGBA[2]=Q_irand(0,255); + NPC->client->ps.customRGBA[3]=255; + } + else + { + NPC->client->ps.customRGBA[0]=atoi(value); + + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + NPC->client->ps.customRGBA[1]=n; + + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + NPC->client->ps.customRGBA[2]=n; + + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + NPC->client->ps.customRGBA[3]=n; + } + continue; + } + + // headmodel + if ( !Q_stricmp( token, "headmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + //Zero the head clamp range so the torso & legs don't lag behind + ri->headYawRangeLeft = + ri->headYawRangeRight = + ri->headPitchRangeUp = + ri->headPitchRangeDown = 0; + } + continue; + } + + // torsomodel + if ( !Q_stricmp( token, "torsomodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if(!Q_stricmp("none", value)) + { + //Zero the torso clamp range so the legs don't lag behind + ri->torsoYawRangeLeft = + ri->torsoYawRangeRight = + ri->torsoPitchRangeUp = + ri->torsoPitchRangeDown = 0; + } + continue; + } + + // legsmodel + if ( !Q_stricmp( token, "legsmodel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + /* + Q_strncpyz( ri->legsModelName, value, sizeof(ri->legsModelName), qtrue); + //Need to do this here to get the right index + G_ParseAnimFileSet( ri->legsModelName, ri->legsModelName, &ci->animFileIndex ); + */ + continue; + } + + // playerModel + if ( !Q_stricmp( token, "playerModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( playerModel, value, sizeof(playerModel)); + md3Model = qfalse; + continue; + } + + // customSkin + if ( !Q_stricmp( token, "customSkin" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Q_strncpyz( customSkin, value, sizeof(customSkin)); + continue; + } + + // surfOff + if ( !Q_stricmp( token, "surfOff" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( surfOff[0] ) + { + Q_strcat( (char *)surfOff, sizeof(surfOff), "," ); + Q_strcat( (char *)surfOff, sizeof(surfOff), value ); + } + else + { + Q_strncpyz( surfOff, value, sizeof(surfOff)); + } + continue; + } + + // surfOn + if ( !Q_stricmp( token, "surfOn" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( surfOn[0] ) + { + Q_strcat( (char *)surfOn, sizeof(surfOn), "," ); + Q_strcat( (char *)surfOn, sizeof(surfOn), value ); + } + else + { + Q_strncpyz( surfOn, value, sizeof(surfOn)); + } + continue; + } + + //headYawRangeLeft + if ( !Q_stricmp( token, "headYawRangeLeft" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->headYawRangeLeft = n; + continue; + } + + //headYawRangeRight + if ( !Q_stricmp( token, "headYawRangeRight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->headYawRangeRight = n; + continue; + } + + //headPitchRangeUp + if ( !Q_stricmp( token, "headPitchRangeUp" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->headPitchRangeUp = n; + continue; + } + + //headPitchRangeDown + if ( !Q_stricmp( token, "headPitchRangeDown" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->headPitchRangeDown = n; + continue; + } + + //torsoYawRangeLeft + if ( !Q_stricmp( token, "torsoYawRangeLeft" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->torsoYawRangeLeft = n; + continue; + } + + //torsoYawRangeRight + if ( !Q_stricmp( token, "torsoYawRangeRight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->torsoYawRangeRight = n; + continue; + } + + //torsoPitchRangeUp + if ( !Q_stricmp( token, "torsoPitchRangeUp" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->torsoPitchRangeUp = n; + continue; + } + + //torsoPitchRangeDown + if ( !Q_stricmp( token, "torsoPitchRangeDown" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + ri->torsoPitchRangeDown = n; + continue; + } + + // Uniform XYZ scale + if ( !Q_stricmp( token, "scale" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + NPC->client->ps.iModelScale = n; //so the client knows + if (n >= 1024) + { + Com_Printf("WARNING: MP does not support scaling up to or over 1024%\n"); + n = 1023; + } + + NPC->modelScale[0] = NPC->modelScale[1] = NPC->modelScale[2] = n/100.0f; + } + continue; + } + + //X scale + if ( !Q_stricmp( token, "scaleX" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + Com_Printf("MP doesn't support xyz scaling, use 'scale'.\n"); + //NPC->s.modelScale[0] = n/100.0f; + } + continue; + } + + //Y scale + if ( !Q_stricmp( token, "scaleY" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + Com_Printf("MP doesn't support xyz scaling, use 'scale'.\n"); + //NPC->s.modelScale[1] = n/100.0f; + } + continue; + } + + //Z scale + if ( !Q_stricmp( token, "scaleZ" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if (n != 100) + { + Com_Printf("MP doesn't support xyz scaling, use 'scale'.\n"); + // NPC->s.modelScale[2] = n/100.0f; + } + continue; + } + + //===AI STATS===================================================================== + if ( !parsingPlayer ) + { + // aggression + if ( !Q_stricmp( token, "aggression" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->aggression = n; + } + continue; + } + + // aim + if ( !Q_stricmp( token, "aim" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->aim = n; + } + continue; + } + + // earshot + if ( !Q_stricmp( token, "earshot" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->earshot = f; + } + continue; + } + + // evasion + if ( !Q_stricmp( token, "evasion" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->evasion = n; + } + continue; + } + + // hfov + if ( !Q_stricmp( token, "hfov" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 30 || n > 180 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->hfov = n;// / 2; //FIXME: Why was this being done?! + } + continue; + } + + // intelligence + if ( !Q_stricmp( token, "intelligence" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->intelligence = n; + } + continue; + } + + // move + if ( !Q_stricmp( token, "move" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->move = n; + } + continue; + } + + // reactions + if ( !Q_stricmp( token, "reactions" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > 5 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->reactions = n; + } + continue; + } + + // shootDistance + if ( !Q_stricmp( token, "shootDistance" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->shootDistance = f; + } + continue; + } + + // vfov + if ( !Q_stricmp( token, "vfov" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 30 || n > 180 ) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->vfov = n / 2; + } + continue; + } + + // vigilance + if ( !Q_stricmp( token, "vigilance" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->vigilance = f; + } + continue; + } + + // visrange + if ( !Q_stricmp( token, "visrange" ) ) { + if ( COM_ParseFloat( &p, &f ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( f < 0.0f ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->visrange = f; + } + continue; + } + + // race + // if ( !Q_stricmp( token, "race" ) ) + // { + // if ( COM_ParseString( &p, &value ) ) + // { + // continue; + // } + // NPC->client->race = TranslateRaceName(value); + // continue; + // } + + // rank + if ( !Q_stricmp( token, "rank" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->NPC ) + { + NPC->NPC->rank = TranslateRankName(value); + } + continue; + } + } + + // health + if ( !Q_stricmp( token, "health" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->health = n; + } + else if ( parsingPlayer ) + { + NPC->client->ps.stats[STAT_MAX_HEALTH] = NPC->client->pers.maxHealth = n; + } + continue; + } + + // fullName + if ( !Q_stricmp( token, "fullName" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + NPC->fullName = G_NewString(value); + continue; + } + + // playerTeam + if ( !Q_stricmp( token, "playerTeam" ) ) + { + char tk[4096]; //rww - hackilicious! + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Com_sprintf(tk, sizeof(tk), "NPC%s", token); + NPC->client->playerTeam = NPC->s.teamowner = (team_t)GetIDForString( TeamTable, tk );//TranslateTeamName(value); + continue; + } + + // enemyTeam + if ( !Q_stricmp( token, "enemyTeam" ) ) + { + char tk[4096]; //rww - hackilicious! + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + Com_sprintf(tk, sizeof(tk), "NPC%s", token); + NPC->client->enemyTeam = (team_t)GetIDForString( TeamTable, tk );//TranslateTeamName(value); + continue; + } + + // class + if ( !Q_stricmp( token, "class" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + NPC->client->NPC_class = (class_t)GetIDForString( ClassTable, value ); + NPC->s.NPC_class = NPC->client->NPC_class; //we actually only need this value now, but at the moment I don't feel like changing the 200+ references to client->NPC_class. + + // No md3's for vehicles. + if ( NPC->client->NPC_class == CLASS_VEHICLE ) + { + if ( !NPC->m_pVehicle ) + {//you didn't spawn this guy right! + Com_Printf ( S_COLOR_RED "ERROR: Tried to spawn a vehicle NPC (%s) without using NPC_Vehicle or 'NPC spawn vehicle '!!! Bad, bad, bad! Shame on you!\n", NPCName ); + return qfalse; + } + md3Model = qfalse; + } + + continue; + } + + // dismemberment probability for head + if ( !Q_stricmp( token, "dismemberProbHead" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbHead = n; + //rwwFIXMEFIXME: support for this? + } + continue; + } + + // dismemberment probability for arms + if ( !Q_stricmp( token, "dismemberProbArms" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbArms = n; + } + continue; + } + + // dismemberment probability for hands + if ( !Q_stricmp( token, "dismemberProbHands" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbHands = n; + } + continue; + } + + // dismemberment probability for waist + if ( !Q_stricmp( token, "dismemberProbWaist" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbWaist = n; + } + continue; + } + + // dismemberment probability for legs + if ( !Q_stricmp( token, "dismemberProbLegs" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + // NPC->client->dismemberProbLegs = n; + } + continue; + } + + //===MOVEMENT STATS============================================================ + + if ( !Q_stricmp( token, "width" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + + NPC->r.mins[0] = NPC->r.mins[1] = -n; + NPC->r.maxs[0] = NPC->r.maxs[1] = n; + continue; + } + + if ( !Q_stricmp( token, "height" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + if ( NPC->client->NPC_class == CLASS_VEHICLE + && NPC->m_pVehicle + && NPC->m_pVehicle->m_pVehicleInfo + && NPC->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + {//a flying vehicle's origin must be centered in bbox and it should spawn on the ground + //trace_t tr; + //vec3_t bottom; + //float adjust = 32.0f; + NPC->r.maxs[2] = NPC->client->ps.standheight = (n/2.0f); + NPC->r.mins[2] = -NPC->r.maxs[2]; + NPC->s.origin[2] += (DEFAULT_MINS_2-NPC->r.mins[2])+0.125f; + VectorCopy(NPC->s.origin, NPC->client->ps.origin); + VectorCopy(NPC->s.origin, NPC->r.currentOrigin); + G_SetOrigin( NPC, NPC->s.origin ); + trap_LinkEntity(NPC); + //now trace down + /* + VectorCopy( NPC->s.origin, bottom ); + bottom[2] -= adjust; + trap_Trace( &tr, NPC->s.origin, NPC->r.mins, NPC->r.maxs, bottom, NPC->s.number, MASK_NPCSOLID ); + if ( !tr.allsolid && !tr.startsolid ) + { + G_SetOrigin( NPC, tr.endpos ); + trap_LinkEntity(NPC); + } + */ + } + else + { + NPC->r.mins[2] = DEFAULT_MINS_2;//Cannot change + NPC->r.maxs[2] = NPC->client->ps.standheight = n + DEFAULT_MINS_2; + } + NPC->radius = n; + continue; + } + + if ( !Q_stricmp( token, "crouchheight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + continue; + } + + NPC->client->ps.crouchheight = n + DEFAULT_MINS_2; + continue; + } + + if ( !parsingPlayer ) + { + if ( !Q_stricmp( token, "movetype" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( Q_stricmp( "flyswim", value ) == 0 ) + { + NPC->client->ps.eFlags2 |= EF2_FLYING; + } + //NPC->client->moveType = (movetype_t)MoveTypeNameToEnum(value); + //rwwFIXMEFIXME: support for movetypes + continue; + } + + // yawSpeed + if ( !Q_stricmp( token, "yawSpeed" ) ) { + if ( COM_ParseInt( &p, &n ) ) { + SkipRestOfLine( &p ); + continue; + } + if ( n <= 0) { + Com_Printf( "bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->yawSpeed = ((float)(n)); + } + continue; + } + + // walkSpeed + if ( !Q_stricmp( token, "walkSpeed" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->walkSpeed = n; + } + continue; + } + + //runSpeed + if ( !Q_stricmp( token, "runSpeed" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->runSpeed = n; + } + continue; + } + + //acceleration + if ( !Q_stricmp( token, "acceleration" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 0 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + stats->acceleration = n; + } + continue; + } + //sex - skip in MP + if ( !Q_stricmp( token, "sex" ) ) + { + SkipRestOfLine( &p ); + continue; + } +//===MISC=============================================================================== + // default behavior + if ( !Q_stricmp( token, "behavior" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < BS_DEFAULT || n >= NUM_BSTATES ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad %s in NPC '%s'\n", token, NPCName ); + continue; + } + if ( NPC->NPC ) + { + NPC->NPC->defaultBehavior = (bState_t)(n); + } + continue; + } + } + + // snd + if ( !Q_stricmp( token, "snd" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !(NPC->r.svFlags&SVF_NO_BASIC_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + // ci->customBasicSoundDir = G_NewString( sound ); + //rwwFIXMEFIXME: Hooray for violating client server rules + } + continue; + } + + // sndcombat + if ( !Q_stricmp( token, "sndcombat" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !(NPC->r.svFlags&SVF_NO_COMBAT_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + // ci->customCombatSoundDir = G_NewString( sound ); + } + continue; + } + + // sndextra + if ( !Q_stricmp( token, "sndextra" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !(NPC->r.svFlags&SVF_NO_EXTRA_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + // ci->customExtraSoundDir = G_NewString( sound ); + } + continue; + } + + // sndjedi + if ( !Q_stricmp( token, "sndjedi" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( !(NPC->r.svFlags&SVF_NO_EXTRA_SOUNDS) ) + { + //FIXME: store this in some sound field or parse in the soundTable like the animTable... + Q_strncpyz( sound, value, sizeof( sound ) ); + patch = strstr( sound, "/" ); + if ( patch ) + { + *patch = 0; + } + //ci->customJediSoundDir = G_NewString( sound ); + } + continue; + } + + //New NPC/jedi stats: + //starting weapon + if ( !Q_stricmp( token, "weapon" ) ) + { + int weap; + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //FIXME: need to precache the weapon, too? (in above func) + weap = GetIDForString( WPTable, value ); + if ( weap >= WP_NONE && weap <= WP_NUM_WEAPONS )///*WP_BLASTER_PISTOL*/WP_SABER ) //?! + { + NPC->client->ps.weapon = weap; + NPC->client->ps.stats[STAT_WEAPONS] |= ( 1 << NPC->client->ps.weapon ); + if ( weap > WP_NONE ) + { + // RegisterItem( FindItemForWeapon( (weapon_t)(NPC->client->ps.weapon) ) ); //precache the weapon + NPC->client->ps.ammo[weaponData[NPC->client->ps.weapon].ammoIndex] = 100;//FIXME: max ammo! + } + } + continue; + } + + if ( !parsingPlayer ) + { + //altFire + if ( !Q_stricmp( token, "altFire" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( NPC->NPC ) + { + if ( n != 0 ) + { + NPC->NPC->scriptFlags |= SCF_ALT_FIRE; + } + } + continue; + } + //Other unique behaviors/numbers that are currently hardcoded? + } + + //force powers + fp = GetIDForString( FPTable, token ); + if ( fp >= FP_FIRST && fp < NUM_FORCE_POWERS ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //FIXME: need to precache the fx, too? (in above func) + //cap + if ( n > 5 ) + { + n = 5; + } + else if ( n < 0 ) + { + n = 0; + } + if ( n ) + {//set + NPC->client->ps.fd.forcePowersKnown |= ( 1 << fp ); + } + else + {//clear + NPC->client->ps.fd.forcePowersKnown &= ~( 1 << fp ); + } + NPC->client->ps.fd.forcePowerLevel[fp] = n; + continue; + } + + //max force power + if ( !Q_stricmp( token, "forcePowerMax" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + NPC->client->ps.fd.forcePowerMax = n; + continue; + } + + //force regen rate - default is 100ms + if ( !Q_stricmp( token, "forceRegenRate" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //NPC->client->ps.forcePowerRegenRate = n; + //rwwFIXMEFIXME: support this? + continue; + } + + //force regen amount - default is 1 (points per second) + if ( !Q_stricmp( token, "forceRegenAmount" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //NPC->client->ps.forcePowerRegenAmount = n; + //rwwFIXMEFIXME: support this? + continue; + } + + //have a sabers.cfg and just name your saber in your NPCs.cfg/ICARUS script + //saber name + if ( !Q_stricmp( token, "saber" ) ) + { + char *saberName; + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + saberName = (char *)BG_TempAlloc(4096);//G_NewString( value ); + strcpy(saberName, value); + + WP_SaberParseParms( saberName, &NPC->client->saber[0] ); + npcSaber1 = G_ModelIndex(va("@%s", saberName)); + + BG_TempFree(4096); + continue; + } + + //second saber name + if ( !Q_stricmp( token, "saber2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + + if ( !(NPC->client->saber[0].saberFlags&SFL_TWO_HANDED) ) + {//can't use a second saber if first one is a two-handed saber...? + char *saberName = (char *)BG_TempAlloc(4096);//G_NewString( value ); + strcpy(saberName, value); + + WP_SaberParseParms( saberName, &NPC->client->saber[1] ); + if ( (NPC->client->saber[1].saberFlags&SFL_TWO_HANDED) ) + {//tsk tsk, can't use a twoHanded saber as second saber + WP_RemoveSaber( NPC->client->saber, 1 ); + } + else + { + //NPC->client->ps.dualSabers = qtrue; + npcSaber2 = G_ModelIndex(va("@%s", saberName)); + } + BG_TempFree(4096); + } + continue; + } + + // saberColor + if ( !Q_stricmp( token, "saberColor" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + saber_colors_t color = TranslateSaberColor( value ); + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[0].blade[n].color = color; + } + } + continue; + } + + if ( !Q_stricmp( token, "saberColor2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[1].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[2].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor4" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[3].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor5" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[4].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor6" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[5].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor7" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[6].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saberColor8" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[0].blade[7].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + saber_colors_t color = TranslateSaberColor( value ); + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[1].blade[n].color = color; + } + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[1].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[2].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color4" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[3].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color5" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[4].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color6" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[5].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color7" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[6].color = TranslateSaberColor( value ); + } + continue; + } + + if ( !Q_stricmp( token, "saber2Color8" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + if ( NPC->client ) + { + NPC->client->saber[1].blade[7].color = TranslateSaberColor( value ); + } + continue; + } + + //saber length + if ( !Q_stricmp( token, "saberLength" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[0].blade[n].lengthMax = f; + } + continue; + } + + if ( !Q_stricmp( token, "saberLength2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[1].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength3" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[2].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength4" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[3].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength5" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[4].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength6" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[5].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength7" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[6].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saberLength8" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[0].blade[7].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[1].blade[n].lengthMax = f; + } + continue; + } + + if ( !Q_stricmp( token, "saber2Length2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[1].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length3" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[2].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length4" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[3].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length5" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[4].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length6" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[5].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length7" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[6].lengthMax = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Length8" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + NPC->client->saber[1].blade[7].lengthMax = f; + continue; + } + + //saber radius + if ( !Q_stricmp( token, "saberRadius" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[0].blade[n].radius = f; + } + continue; + } + + if ( !Q_stricmp( token, "saberRadius2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[1].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius3" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[2].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius4" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[3].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius5" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[4].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius6" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[5].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius7" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[6].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saberRadius8" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[0].blade[7].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + for ( n = 0; n < MAX_BLADES; n++ ) + { + NPC->client->saber[1].blade[n].radius = f; + } + continue; + } + + if ( !Q_stricmp( token, "saber2Radius2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[1].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius3" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[2].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius4" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[3].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius5" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[4].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius6" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[5].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius7" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[6].radius = f; + continue; + } + + if ( !Q_stricmp( token, "saber2Radius8" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + NPC->client->saber[1].blade[7].radius = f; + continue; + } + + //ADD: + //saber sounds (on, off, loop) + //loop sound (like Vader's breathing or droid bleeps, etc.) + + //starting saber style + if ( !Q_stricmp( token, "saberStyle" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( n < 0 ) + { + n = 0; + } + else if ( n > 5 ) + { + n = 5; + } + NPC->client->ps.fd.saberAnimLevel = n; + /* + if ( parsingPlayer ) + { + cg.saberAnimLevelPending = n; + } + */ + continue; + } + + if ( !parsingPlayer ) + { + Com_Printf( "WARNING: unknown keyword '%s' while parsing '%s'\n", token, NPCName ); + } + SkipRestOfLine( &p ); + } + } + +/* +Ghoul2 Insert Start +*/ + if ( !md3Model ) + { + qboolean setTypeBack = qfalse; + + if (npcSaber1 == 0) + { //use "kyle" for a default then + npcSaber1 = G_ModelIndex("@Kyle"); + WP_SaberParseParms( "Kyle", &NPC->client->saber[0] ); + } + + NPC->s.npcSaber1 = npcSaber1; + NPC->s.npcSaber2 = npcSaber2; + + if (!customSkin[0]) + { + strcpy(customSkin, "default"); + } + + if ( NPC->client && NPC->client->NPC_class == CLASS_VEHICLE ) + { //vehicles want their names fed in as models + //we put the $ in front to indicate a name and not a model + strcpy(playerModel, va("$%s", NPCName)); + } + SetupGameGhoul2Model(NPC, playerModel, customSkin); + + if (!NPC->NPC_type) + { //just do this for now so NPC_Precache can see the name. + NPC->NPC_type = (char *)NPCName; + setTypeBack = qtrue; + } + + NPC_Precache(NPC); //this will just soundindex some values for sounds on the client, + + if (setTypeBack) + { //don't want this being set if we aren't ready yet. + NPC->NPC_type = NULL; + } + } + else + { + Com_Printf("MD3 MODEL NPC'S ARE NOT SUPPORTED IN MP!\n"); + return qfalse; + } +/* +Ghoul2 Insert End +*/ + /* + if( NPCsPrecached ) + {//Spawning in after initial precache, our models are precached, we just need to set our clientInfo + CG_RegisterClientModels( NPC->s.number ); + CG_RegisterNPCCustomSounds( ci ); + CG_RegisterNPCEffects( NPC->client->playerTeam ); + } + */ + //rwwFIXMEFIXME: Do something here I guess to properly precache stuff. + + return qtrue; +} + +#ifdef _XBOX +char *npcParseBuffer = NULL; +#else +char npcParseBuffer[MAX_NPC_DATA_SIZE]; +#endif + +void NPC_LoadParms( void ) +{ + int len, totallen, npcExtFNLen, mainBlockLen, fileCnt, i; +// const char *filename = "ext_data/NPC2.cfg"; + char /**buffer,*/ *holdChar, *marker; + char npcExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = NPCParms+totallen; + *marker = 0; + + //now load in the extra .npc extensions + fileCnt = trap_FS_GetFileList("ext_data/NPCs", ".npc", npcExtensionListBuf, sizeof(npcExtensionListBuf) ); + +#ifdef _XBOX + npcParseBuffer = (char *) Z_Malloc(MAX_NPC_DATA_SIZE, TAG_TEMP_WORKSPACE, qfalse, 4); +#endif + + holdChar = npcExtensionListBuf; + for ( i = 0; i < fileCnt; i++, holdChar += npcExtFNLen + 1 ) + { + npcExtFNLen = strlen( holdChar ); + +// Com_Printf( "Parsing %s\n", holdChar ); + + len = trap_FS_FOpenFile(va( "ext_data/NPCs/%s", holdChar), &f, FS_READ); + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { + if ( totallen + len >= MAX_NPC_DATA_SIZE ) { + G_Error( "NPC extensions (*.npc) are too large" ); + } + trap_FS_Read(npcParseBuffer, len, f); + npcParseBuffer[len] = 0; + + len = COM_Compress( npcParseBuffer ); + + strcat( marker, npcParseBuffer ); + strcat(marker, "\n"); + len++; + trap_FS_FCloseFile(f); + + totallen += len; + marker = NPCParms+totallen; + //*marker = 0; //rww - make sure this is null or strcat will not append to the correct place + //rww 12/19/02-actually the probelm was npcParseBuffer not being nul-term'd, which could cause issues in the strcat too + } + } + +#ifdef _XBOX + Z_Free(npcParseBuffer); + npcParseBuffer = NULL; +#endif + +} diff --git a/code/game/NPC_utils.c b/code/game/NPC_utils.c new file mode 100644 index 0000000..e7f6138 --- /dev/null +++ b/code/game/NPC_utils.c @@ -0,0 +1,1782 @@ +//NPC_utils.cpp + +#include "b_local.h" +#include "../icarus/Q3_Interface.h" +#include "../ghoul2/G2.h" + +int teamNumbers[TEAM_NUM_TEAMS]; +int teamStrength[TEAM_NUM_TEAMS]; +int teamCounter[TEAM_NUM_TEAMS]; + +#define VALID_ATTACK_CONE 2.0f //Degrees +extern void G_DebugPrint( int level, const char *format, ... ); + +/* +void CalcEntitySpot ( gentity_t *ent, spot_t spot, vec3_t point ) + +Added: Uses shootAngles if a NPC has them + +*/ +void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point ) +{ + vec3_t forward, up, right; + vec3_t start, end; + trace_t tr; + + if ( !ent ) + { + return; + } + switch ( spot ) + { + case SPOT_ORIGIN: + if(VectorCompare(ent->r.currentOrigin, vec3_origin)) + {//brush + VectorSubtract(ent->r.absmax, ent->r.absmin, point);//size + VectorMA(ent->r.absmin, 0.5, point, point); + } + else + { + VectorCopy ( ent->r.currentOrigin, point ); + } + break; + + case SPOT_CHEST: + case SPOT_HEAD: + if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD)*/ ) + {//Actual tag_head eyespot! + //FIXME: Stasis aliens may have a problem here... + VectorCopy( ent->client->renderInfo.eyePoint, point ); + if ( ent->client->NPC_class == CLASS_ATST ) + {//adjust up some + point[2] += 28;//magic number :) + } + if ( ent->NPC ) + {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards + point[0] = ent->r.currentOrigin[0]; + point[1] = ent->r.currentOrigin[1]; + } + /* + else if (ent->s.eType == ET_PLAYER ) + { + SubtractLeanOfs( ent, point ); + } + */ + } + else + { + VectorCopy ( ent->r.currentOrigin, point ); + if ( ent->client ) + { + point[2] += ent->client->ps.viewheight; + } + } + if ( spot == SPOT_CHEST && ent->client ) + { + if ( ent->client->NPC_class != CLASS_ATST ) + {//adjust up some + point[2] -= ent->r.maxs[2]*0.2f; + } + } + break; + + case SPOT_HEAD_LEAN: + if ( ent->client && VectorLengthSquared( ent->client->renderInfo.eyePoint ) /*&& (ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD*/ ) + {//Actual tag_head eyespot! + //FIXME: Stasis aliens may have a problem here... + VectorCopy( ent->client->renderInfo.eyePoint, point ); + if ( ent->client->NPC_class == CLASS_ATST ) + {//adjust up some + point[2] += 28;//magic number :) + } + if ( ent->NPC ) + {//always aim from the center of my bbox, so we don't wiggle when we lean forward or backwards + point[0] = ent->r.currentOrigin[0]; + point[1] = ent->r.currentOrigin[1]; + } + /* + else if ( ent->s.eType == ET_PLAYER ) + { + SubtractLeanOfs( ent, point ); + } + */ + //NOTE: automatically takes leaning into account! + } + else + { + VectorCopy ( ent->r.currentOrigin, point ); + if ( ent->client ) + { + point[2] += ent->client->ps.viewheight; + } + //AddLeanOfs ( ent, point ); + } + break; + + //FIXME: implement... + //case SPOT_CHEST: + //Returns point 3/4 from tag_torso to tag_head? + //break; + + case SPOT_LEGS: + VectorCopy ( ent->r.currentOrigin, point ); + point[2] += (ent->r.mins[2] * 0.5); + break; + + case SPOT_WEAPON: + if( ent->NPC && !VectorCompare( ent->NPC->shootAngles, vec3_origin ) && !VectorCompare( ent->NPC->shootAngles, ent->client->ps.viewangles )) + { + AngleVectors( ent->NPC->shootAngles, forward, right, up ); + } + else + { + AngleVectors( ent->client->ps.viewangles, forward, right, up ); + } + CalcMuzzlePoint( (gentity_t*)ent, forward, right, up, point ); + //NOTE: automatically takes leaning into account! + break; + + case SPOT_GROUND: + // if entity is on the ground, just use it's absmin + if ( ent->s.groundEntityNum != -1 ) + { + VectorCopy( ent->r.currentOrigin, point ); + point[2] = ent->r.absmin[2]; + break; + } + + // if it is reasonably close to the ground, give the point underneath of it + VectorCopy( ent->r.currentOrigin, start ); + start[2] = ent->r.absmin[2]; + VectorCopy( start, end ); + end[2] -= 64; + trap_Trace( &tr, start, ent->r.mins, ent->r.maxs, end, ent->s.number, MASK_PLAYERSOLID ); + if ( tr.fraction < 1.0 ) + { + VectorCopy( tr.endpos, point); + break; + } + + // otherwise just use the origin + VectorCopy( ent->r.currentOrigin, point ); + break; + + default: + VectorCopy ( ent->r.currentOrigin, point ); + break; + } +} + + +//=================================================================================== + +/* +qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) + +Added: option to do just pitch or just yaw + +Does not include "aim" in it's calculations + +FIXME: stop compressing angles into shorts!!!! +*/ +qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ) +{ +#if 1 + + float error; + float decay; + float targetPitch = 0; + float targetYaw = 0; + float yawSpeed; + qboolean exact = qtrue; + + // if angle changes are locked; just keep the current angles + // aimTime isn't even set anymore... so this code was never reached, but I need a way to lock NPC's yaw, so instead of making a new SCF_ flag, just use the existing render flag... - dmv + if ( !NPC->enemy && ( (level.time < NPCInfo->aimTime) /*|| NPC->client->renderInfo.renderFlags & RF_LOCKEDANGLE*/) ) + { + if(doPitch) + targetPitch = NPCInfo->lockedDesiredPitch; + + if(doYaw) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + // we're changing the lockedDesired Pitch/Yaw below so it's lost it's original meaning, get rid of the lock flag + // NPC->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE; + + if(doPitch) + { + targetPitch = NPCInfo->desiredPitch; + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + } + + if(doYaw) + { + targetYaw = NPCInfo->desiredYaw; + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + } + + if ( NPC->s.weapon == WP_EMPLACED_GUN ) + { + // FIXME: this seems to do nothing, actually... + yawSpeed = 20; + } + else + { + yawSpeed = NPCInfo->stats.yawSpeed; + } + + if ( NPC->s.weapon == WP_SABER && NPC->client->ps.fd.forcePowersActive&(1<client->ps.viewangles[YAW], targetYaw ); + if( fabs(error) > MIN_ANGLE_ERROR ) + { + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0f / 1000.0f;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; + } + + //FIXME: have a pitchSpeed? + if( doPitch ) + { + // decay pitch error + error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + if ( fabs(error) > MIN_ANGLE_ERROR ) + { + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0f / 1000.0f;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + if ( exact && trap_ICARUS_TaskIDPending( NPC, TID_ANGLE_FACE ) ) + { + trap_ICARUS_TaskIDComplete( NPC, TID_ANGLE_FACE ); + } + return exact; + +#else + + float error; + float decay; + float targetPitch = 0; + float targetYaw = 0; + float yawSpeed; + //float runningMod = NPCInfo->currentSpeed/100.0f; + qboolean exact = qtrue; + qboolean doSound = qfalse; + + // if angle changes are locked; just keep the current angles + if ( level.time < NPCInfo->aimTime ) + { + if(doPitch) + targetPitch = NPCInfo->lockedDesiredPitch; + if(doYaw) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + if(doPitch) + targetPitch = NPCInfo->desiredPitch; + if(doYaw) + targetYaw = NPCInfo->desiredYaw; + +// NPCInfo->aimTime = level.time + 250; + if(doPitch) + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + if(doYaw) + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + + yawSpeed = NPCInfo->stats.yawSpeed; + + if(doYaw) + { + // decay yaw error + error = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + if( fabs(error) > MIN_ANGLE_ERROR ) + { + /* + if(NPC->client->playerTeam == TEAM_BORG&& + NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) + {//HACK - borg turn more jittery + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + //Snap to + if(fabs(error) > 10) + { + if(random() > 0.6) + { + doSound = qtrue; + } + } + + if ( error < 0.0)//-10.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else if ( error > 0.0)//10.0 ) + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + else*/ + + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + error ) - client->ps.delta_angles[YAW]; + } + + //FIXME: have a pitchSpeed? + if(doPitch) + { + // decay pitch error + error = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + if ( fabs(error) > MIN_ANGLE_ERROR ) + { + /* + if(NPC->client->playerTeam == TEAM_BORG&& + NPCInfo->behaviorState != BS_FACE&&NPCInfo->tempBehavior!= BS_FACE) + {//HACK - borg turn more jittery + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + //Snap to + if(fabs(error) > 10) + { + if(random() > 0.6) + { + doSound = qtrue; + } + } + + if ( error < 0.0)//-10.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else if ( error > 0.0)//10.0 ) + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + else*/ + + if ( error ) + { + exact = qfalse; + + decay = 60.0 + yawSpeed * 3; + decay *= 50.0 / 1000.0;//msec + + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + } + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + /* + if(doSound) + { + G_Sound(NPC, G_SoundIndex(va("sound/enemies/borg/borgservo%d.wav", Q_irand(1, 8)))); + } + */ + + return exact; + +#endif + +} + +void NPC_AimWiggle( vec3_t enemy_org ) +{ + //shoot for somewhere between the head and torso + //NOTE: yes, I know this looks weird, but it works + if ( NPCInfo->aimErrorDebounceTime < level.time ) + { + NPCInfo->aimOfs[0] = 0.3*flrand(NPC->enemy->r.mins[0], NPC->enemy->r.maxs[0]); + NPCInfo->aimOfs[1] = 0.3*flrand(NPC->enemy->r.mins[1], NPC->enemy->r.maxs[1]); + if ( NPC->enemy->r.maxs[2] > 0 ) + { + NPCInfo->aimOfs[2] = NPC->enemy->r.maxs[2]*flrand(0.0f, -1.0f); + } + } + VectorAdd( enemy_org, NPCInfo->aimOfs, enemy_org ); +} + +/* +qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) + + Includes aim when determining angles - so they don't always hit... + */ +qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ) +{ + +#if 0 + + float diff; + float error; + float targetPitch = 0; + float targetYaw = 0; + qboolean exact = qtrue; + + if ( level.time < NPCInfo->aimTime ) + { + if( doPitch ) + targetPitch = NPCInfo->lockedDesiredPitch; + + if( doYaw ) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + if( doPitch ) + { + targetPitch = NPCInfo->desiredPitch; + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + } + + if( doYaw ) + { + targetYaw = NPCInfo->desiredYaw; + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + } + + if( doYaw ) + { + // add yaw error based on NPCInfo->aim value + error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); + + if(Q_irand(0, 1)) + error *= -1; + + diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + + if ( diff ) + exact = qfalse; + + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; + } + + if( doPitch ) + { + // add pitch error based on NPCInfo->aim value + error = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); + + diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + + if ( diff ) + exact = qfalse; + + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + return exact; + +#else + + float error, diff; + float decay; + float targetPitch = 0; + float targetYaw = 0; + qboolean exact = qtrue; + + // if angle changes are locked; just keep the current angles + if ( level.time < NPCInfo->aimTime ) + { + if(doPitch) + targetPitch = NPCInfo->lockedDesiredPitch; + if(doYaw) + targetYaw = NPCInfo->lockedDesiredYaw; + } + else + { + if(doPitch) + targetPitch = NPCInfo->desiredPitch; + if(doYaw) + targetYaw = NPCInfo->desiredYaw; + +// NPCInfo->aimTime = level.time + 250; + if(doPitch) + NPCInfo->lockedDesiredPitch = NPCInfo->desiredPitch; + if(doYaw) + NPCInfo->lockedDesiredYaw = NPCInfo->desiredYaw; + } + + if ( NPCInfo->aimErrorDebounceTime < level.time ) + { + if ( Q_irand(0, 1 ) ) + { + NPCInfo->lastAimErrorYaw = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); + } + if ( Q_irand(0, 1 ) ) + { + NPCInfo->lastAimErrorPitch = ((float)(6 - NPCInfo->stats.aim)) * flrand(-1, 1); + } + NPCInfo->aimErrorDebounceTime = level.time + Q_irand(250, 2000); + } + + if(doYaw) + { + // decay yaw diff + diff = AngleDelta ( NPC->client->ps.viewangles[YAW], targetYaw ); + + if ( diff) + { + exact = qfalse; + + decay = 60.0 + 80.0; + decay *= 50.0f / 1000.0f;//msec + if ( diff < 0.0 ) + { + diff += decay; + if ( diff > 0.0 ) + { + diff = 0.0; + } + } + else + { + diff -= decay; + if ( diff < 0.0 ) + { + diff = 0.0; + } + } + } + + // add yaw error based on NPCInfo->aim value + error = NPCInfo->lastAimErrorYaw; + + /* + if(Q_irand(0, 1)) + { + error *= -1; + } + */ + + ucmd.angles[YAW] = ANGLE2SHORT( targetYaw + diff + error ) - client->ps.delta_angles[YAW]; + } + + if(doPitch) + { + // decay pitch diff + diff = AngleDelta ( NPC->client->ps.viewangles[PITCH], targetPitch ); + if ( diff) + { + exact = qfalse; + + decay = 60.0 + 80.0; + decay *= 50.0f / 1000.0f;//msec + if ( diff < 0.0 ) + { + diff += decay; + if ( diff > 0.0 ) + { + diff = 0.0; + } + } + else + { + diff -= decay; + if ( diff < 0.0 ) + { + diff = 0.0; + } + } + } + + error = NPCInfo->lastAimErrorPitch; + + ucmd.angles[PITCH] = ANGLE2SHORT( targetPitch + diff + error ) - client->ps.delta_angles[PITCH]; + } + + ucmd.angles[ROLL] = ANGLE2SHORT ( NPC->client->ps.viewangles[ROLL] ) - client->ps.delta_angles[ROLL]; + + return exact; + +#endif + +} +//=================================================================================== + +/* +static void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) + +Does update angles on shootAngles +*/ + +void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ) +{//FIXME: shoot angles either not set right or not used! + float error; + float decay; + float targetPitch = 0; + float targetYaw = 0; + + if(doPitch) + targetPitch = angles[PITCH]; + if(doYaw) + targetYaw = angles[YAW]; + + + if(doYaw) + { + // decay yaw error + error = AngleDelta ( NPCInfo->shootAngles[YAW], targetYaw ); + if ( error ) + { + decay = 60.0 + 80.0 * NPCInfo->stats.aim; + decay *= 100.0f / 1000.0f;//msec + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + NPCInfo->shootAngles[YAW] = targetYaw + error; + } + + if(doPitch) + { + // decay pitch error + error = AngleDelta ( NPCInfo->shootAngles[PITCH], targetPitch ); + if ( error ) + { + decay = 60.0 + 80.0 * NPCInfo->stats.aim; + decay *= 100.0f / 1000.0f;//msec + if ( error < 0.0 ) + { + error += decay; + if ( error > 0.0 ) + { + error = 0.0; + } + } + else + { + error -= decay; + if ( error < 0.0 ) + { + error = 0.0; + } + } + } + NPCInfo->shootAngles[PITCH] = targetPitch + error; + } +} + +/* +void SetTeamNumbers (void) + +Sets the number of living clients on each team + +FIXME: Does not account for non-respawned players! +FIXME: Don't include medics? +*/ +void SetTeamNumbers (void) +{ + gentity_t *found; + int i; + + for( i = 0; i < TEAM_NUM_TEAMS; i++ ) + { + teamNumbers[i] = 0; + teamStrength[i] = 0; + } + + for( i = 0; i < 1 ; i++ ) + { + found = &g_entities[i]; + + if( found->client ) + { + if( found->health > 0 )//FIXME: or if a player! + { + teamNumbers[found->client->playerTeam]++; + teamStrength[found->client->playerTeam] += found->health; + } + } + } + + for( i = 0; i < TEAM_NUM_TEAMS; i++ ) + {//Get the average health + teamStrength[i] = floor( ((float)(teamStrength[i])) / ((float)(teamNumbers[i])) ); + } +} + +extern stringID_table_t BSTable[]; +extern stringID_table_t BSETTable[]; +qboolean G_ActivateBehavior (gentity_t *self, int bset ) +{ + bState_t bSID = (bState_t)-1; + char *bs_name = NULL; + + if ( !self ) + { + return qfalse; + } + + bs_name = self->behaviorSet[bset]; + + if( !(VALIDSTRING( bs_name )) ) + { + return qfalse; + } + + if ( self->NPC ) + { + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + } + + if(bSID > -1) + { + self->NPC->tempBehavior = BS_DEFAULT; + self->NPC->behaviorState = bSID; + } + else + { + /* + char newname[MAX_FILENAME_LENGTH]; + sprintf((char *) &newname, "%s/%s", Q3_SCRIPT_DIR, bs_name ); + */ + + //FIXME: between here and actually getting into the ICARUS_RunScript function, the stack gets blown! + //if ( ( ICARUS_entFilter == -1 ) || ( ICARUS_entFilter == self->s.number ) ) + if (0) + { + G_DebugPrint( WL_VERBOSE, "%s attempting to run bSet %s (%s)\n", self->targetname, GetStringForID( BSETTable, bset ), bs_name ); + } + trap_ICARUS_RunScript( self, va( "%s/%s", Q3_SCRIPT_DIR, bs_name ) ); + } + return qtrue; +} + + +/* +============================================================================= + + Extended Functions + +============================================================================= +*/ + +//rww - special system for sync'ing bone angles between client and server. +void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles) +{ +#ifdef _XBOX + byte *thebone = &ent->s.boneIndex1; + byte *firstFree = NULL; +#else + int *thebone = &ent->s.boneIndex1; + int *firstFree = NULL; +#endif + int i = 0; + int boneIndex = G_BoneIndex(bone); + int flags, up, right, forward; + vec3_t *boneVector = &ent->s.boneAngles1; + vec3_t *freeBoneVec = NULL; + + while (thebone) + { + if (!*thebone && !firstFree) + { //if the value is 0 then this index is clear, we can use it if we don't find the bone we want already existing. + firstFree = thebone; + freeBoneVec = boneVector; + } + else if (*thebone) + { + if (*thebone == boneIndex) + { //this is it + break; + } + } + + switch (i) + { + case 0: + thebone = &ent->s.boneIndex2; + boneVector = &ent->s.boneAngles2; + break; + case 1: + thebone = &ent->s.boneIndex3; + boneVector = &ent->s.boneAngles3; + break; + case 2: + thebone = &ent->s.boneIndex4; + boneVector = &ent->s.boneAngles4; + break; + default: + thebone = NULL; + boneVector = NULL; + break; + } + + i++; + } + + if (!thebone) + { //didn't find it, create it + if (!firstFree) + { //no free bones.. can't do a thing then. + Com_Printf("WARNING: NPC has no free bone indexes\n"); + return; + } + + thebone = firstFree; + + *thebone = boneIndex; + boneVector = freeBoneVec; + } + + //If we got here then we have a vector and an index. + + //Copy the angles over the vector in the entitystate, so we can use the corresponding index + //to set the bone angles on the client. + VectorCopy(angles, *boneVector); + + //Now set the angles on our server instance if we have one. + + if (!ent->ghoul2) + { + return; + } + + flags = BONE_ANGLES_POSTMULT; + up = POSITIVE_X; + right = NEGATIVE_Y; + forward = NEGATIVE_Z; + + //first 3 bits is forward, second 3 bits is right, third 3 bits is up + ent->s.boneOrient = ((forward)|(right<<3)|(up<<6)); + + trap_G2API_SetBoneAngles(ent->ghoul2, 0, bone, angles, flags, up, right, forward, NULL, 100, level.time); +} + +//rww - and another method of automatically managing surface status for the client and server at once +#define TURN_ON 0x00000000 +#define TURN_OFF 0x00000100 + +void NPC_SetSurfaceOnOff(gentity_t *ent, const char *surfaceName, int surfaceFlags) +{ + int i = 0; + qboolean foundIt = qfalse; + + while (i < BG_NUM_TOGGLEABLE_SURFACES && bgToggleableSurfaces[i]) + { + if (!Q_stricmp(surfaceName, bgToggleableSurfaces[i])) + { //got it + foundIt = qtrue; + break; + } + i++; + } + + if (!foundIt) + { + Com_Printf("WARNING: Tried to toggle NPC surface that isn't in toggleable surface list (%s)\n", surfaceName); + return; + } + + if (surfaceFlags == TURN_ON) + { //Make sure the entitystate values reflect this surface as on now. + ent->s.surfacesOn |= (1 << i); + ent->s.surfacesOff &= ~(1 << i); + } + else + { //Otherwise make sure they're off. + ent->s.surfacesOn &= ~(1 << i); + ent->s.surfacesOff |= (1 << i); + } + + if (!ent->ghoul2) + { + return; + } + + trap_G2API_SetSurfaceOnOff(ent->ghoul2, surfaceName, surfaceFlags); +} + +//rww - cheap check to see if an armed client is looking in our general direction +qboolean NPC_SomeoneLookingAtMe(gentity_t *ent) +{ + int i = 0; + gentity_t *pEnt; + + while (i < MAX_CLIENTS) + { + pEnt = &g_entities[i]; + + if (pEnt && pEnt->inuse && pEnt->client && pEnt->client->sess.sessionTeam != TEAM_SPECTATOR && + !(pEnt->client->ps.pm_flags & PMF_FOLLOW) && pEnt->s.weapon != WP_NONE) + { + if (trap_InPVS(ent->r.currentOrigin, pEnt->r.currentOrigin)) + { + if (InFOV( ent, pEnt, 30, 30 )) + { //I'm in a 30 fov or so cone from this player.. that's enough I guess. + return qtrue; + } + } + } + + i++; + } + + return qfalse; +} + +qboolean NPC_ClearLOS( const vec3_t start, const vec3_t end ) +{ + return G_ClearLOS( NPC, start, end ); +} +qboolean NPC_ClearLOS5( const vec3_t end ) +{ + return G_ClearLOS5( NPC, end ); +} +qboolean NPC_ClearLOS4( gentity_t *ent ) +{ + return G_ClearLOS4( NPC, ent ); +} +qboolean NPC_ClearLOS3( const vec3_t start, gentity_t *ent ) +{ + return G_ClearLOS3( NPC, start, ent ); +} +qboolean NPC_ClearLOS2( gentity_t *ent, const vec3_t end ) +{ + return G_ClearLOS2( NPC, ent, end ); +} + +/* +------------------------- +NPC_ValidEnemy +------------------------- +*/ + +qboolean NPC_ValidEnemy( gentity_t *ent ) +{ + int entTeam = TEAM_FREE; + //Must be a valid pointer + if ( ent == NULL ) + return qfalse; + + //Must not be me + if ( ent == NPC ) + return qfalse; + + //Must not be deleted + if ( ent->inuse == qfalse ) + return qfalse; + + //Must be alive + if ( ent->health <= 0 ) + return qfalse; + + //In case they're in notarget mode + if ( ent->flags & FL_NOTARGET ) + return qfalse; + + //Must be an NPC + if ( ent->client == NULL ) + { + // if ( ent->svFlags&SVF_NONNPC_ENEMY ) + if (ent->s.eType != ET_NPC) + {//still potentially valid + if ( ent->alliedTeam == NPC->client->playerTeam ) + { + return qfalse; + } + else + { + return qtrue; + } + } + else + { + return qfalse; + } + } + else if ( ent->client && ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + {//don't go after spectators + return qfalse; + } + if ( ent->NPC && ent->client ) + { + entTeam = ent->client->playerTeam; + } + else if ( ent->client ) + { + if (g_gametype.integer < GT_TEAM) + { + entTeam = NPCTEAM_PLAYER; + } + else + { + if ( ent->client->sess.sessionTeam == TEAM_BLUE ) + { + entTeam = NPCTEAM_PLAYER; + } + else if ( ent->client->sess.sessionTeam == TEAM_RED ) + { + entTeam = NPCTEAM_ENEMY; + } + else + { + entTeam = NPCTEAM_NEUTRAL; + } + } + } + //Can't be on the same team + if ( ent->client->playerTeam == NPC->client->playerTeam ) + return qfalse; + + //if haven't seen him in a while, give up + //if ( NPCInfo->enemyLastSeenTime != 0 && level.time - NPCInfo->enemyLastSeenTime > 7000 )//FIXME: make a stat? + // return qfalse; + if ( entTeam == NPC->client->enemyTeam //simplest case: they're on my enemy team + || (NPC->client->enemyTeam == NPCTEAM_FREE && ent->client->NPC_class != NPC->client->NPC_class )//I get mad at anyone and this guy isn't the same class as me + || (ent->client->NPC_class == CLASS_WAMPA && ent->enemy )//a rampaging wampa + || (ent->client->NPC_class == CLASS_RANCOR && ent->enemy )//a rampaging rancor + || (entTeam == NPCTEAM_FREE && ent->client->enemyTeam == NPCTEAM_FREE && ent->enemy && ent->enemy->client && (ent->enemy->client->playerTeam == NPC->client->playerTeam||(ent->enemy->client->playerTeam != NPCTEAM_ENEMY&&NPC->client->playerTeam==NPCTEAM_PLAYER))) //enemy is a rampaging non-aligned creature who is attacking someone on our team or a non-enemy (this last condition is used only if we're a good guy - in effect, we protect the innocent) + ) + { + return qtrue; + } + + return qfalse; +} + +/* +------------------------- +NPC_TargetVisible +------------------------- +*/ + +qboolean NPC_TargetVisible( gentity_t *ent ) +{ + //Make sure we're in a valid range + if ( DistanceSquared( ent->r.currentOrigin, NPC->r.currentOrigin ) > ( NPCInfo->stats.visrange * NPCInfo->stats.visrange ) ) + return qfalse; + + //Check our FOV + if ( InFOV( ent, NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) + return qfalse; + + //Check for sight + if ( NPC_ClearLOS4( ent ) == qfalse ) + return qfalse; + + return qtrue; +} + +/* +------------------------- +NPC_GetCheckDelta +------------------------- +*/ + +/* +#define CHECK_TIME_BASE 250 +#define CHECK_TIME_BASE_SQUARED ( CHECK_TIME_BASE * CHECK_TIME_BASE ) + +static int NPC_GetCheckDelta( void ) +{ + if ( NPC_ValidEnemy( NPC->enemy ) == qfalse ) + { + int distance = DistanceSquared( NPC->r.currentOrigin, g_entities[0].currentOrigin ); + + distance /= CHECK_TIME_BASE_SQUARED; + + return ( CHECK_TIME_BASE * distance ); + } + + return 0; +} +*/ + +/* +------------------------- +NPC_FindNearestEnemy +------------------------- +*/ + +#define MAX_RADIUS_ENTS 256 //NOTE: This can cause entities to be lost +#define NEAR_DEFAULT_RADIUS 256 + +int NPC_FindNearestEnemy( gentity_t *ent ) +{ + int iradiusEnts[ MAX_RADIUS_ENTS ]; + gentity_t *radEnt; + vec3_t mins, maxs; + int nearestEntID = -1; + float nearestDist = (float)WORLD_SIZE*(float)WORLD_SIZE; + float distance; + int numEnts, numChecks = 0; + int i; + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = ent->r.currentOrigin[i] - NPCInfo->stats.visrange; + maxs[i] = ent->r.currentOrigin[i] + NPCInfo->stats.visrange; + } + + //Get a number of entities in a given space + numEnts = trap_EntitiesInBox( mins, maxs, iradiusEnts, MAX_RADIUS_ENTS ); + + for ( i = 0; i < numEnts; i++ ) + { + radEnt = &g_entities[iradiusEnts[i]]; + //Don't consider self + if ( radEnt == ent ) + continue; + + //Must be valid + if ( NPC_ValidEnemy( radEnt ) == qfalse ) + continue; + + numChecks++; + //Must be visible + if ( NPC_TargetVisible( radEnt ) == qfalse ) + continue; + + distance = DistanceSquared( ent->r.currentOrigin, radEnt->r.currentOrigin ); + + //Found one closer to us + if ( distance < nearestDist ) + { + nearestEntID = radEnt->s.number; + nearestDist = distance; + } + } + + return nearestEntID; +} + +/* +------------------------- +NPC_PickEnemyExt +------------------------- +*/ + +gentity_t *NPC_PickEnemyExt( qboolean checkAlerts ) +{ + //Check for Hazard Team status and remove this check + /* + if ( NPC->client->playerTeam != TEAM_STARFLEET ) + { + //If we've found the player, return it + if ( NPC_FindPlayer() ) + return &g_entities[0]; + } + */ + + //If we've asked for the closest enemy + int entID = NPC_FindNearestEnemy( NPC ); + + //If we have a valid enemy, use it + if ( entID >= 0 ) + return &g_entities[entID]; + + if ( checkAlerts ) + { + int alertEvent = NPC_CheckAlertEvents( qtrue, qtrue, -1, qtrue, AEL_DISCOVERED ); + + //There is an event to look at + if ( alertEvent >= 0 ) + { + alertEvent_t *event = &level.alertEvents[alertEvent]; + + //Don't pay attention to our own alerts + if ( event->owner == NPC ) + return NULL; + + if ( event->level >= AEL_DISCOVERED ) + { + //If it's the player, attack him + if ( event->owner == &g_entities[0] ) + return event->owner; + + //If it's on our team, then take its enemy as well + if ( ( event->owner->client ) && ( event->owner->client->playerTeam == NPC->client->playerTeam ) ) + return event->owner->enemy; + } + } + } + + return NULL; +} + +/* +------------------------- +NPC_FindPlayer +------------------------- +*/ + +qboolean NPC_FindPlayer( void ) +{ + return NPC_TargetVisible( &g_entities[0] ); +} + +/* +------------------------- +NPC_CheckPlayerDistance +------------------------- +*/ + +static qboolean NPC_CheckPlayerDistance( void ) +{ + return qfalse;//MOOT in MP + /* + float distance; + + //Make sure we have an enemy + if ( NPC->enemy == NULL ) + return qfalse; + + //Only do this for non-players + if ( NPC->enemy->s.number == 0 ) + return qfalse; + + //must be set up to get mad at player + if ( !NPC->client || NPC->client->enemyTeam != NPCTEAM_PLAYER ) + return qfalse; + + //Must be within our FOV + if ( InFOV( &g_entities[0], NPC, NPCInfo->stats.hfov, NPCInfo->stats.vfov ) == qfalse ) + return qfalse; + + distance = DistanceSquared( NPC->r.currentOrigin, NPC->enemy->r.currentOrigin ); + + if ( distance > DistanceSquared( NPC->r.currentOrigin, g_entities[0].r.currentOrigin ) ) + { //rwwFIXMEFIXME: care about all clients not just client 0 + G_SetEnemy( NPC, &g_entities[0] ); + return qtrue; + } + + return qfalse; + */ +} + +/* +------------------------- +NPC_FindEnemy +------------------------- +*/ + +qboolean NPC_FindEnemy( qboolean checkAlerts ) +{ + gentity_t *newenemy; + + //We're ignoring all enemies for now + //if( NPC->svFlags & SVF_IGNORE_ENEMIES ) + if (0) //rwwFIXMEFIXME: support for flag + { + G_ClearEnemy( NPC ); + return qfalse; + } + + //we can't pick up any enemies for now + if( NPCInfo->confusionTime > level.time ) + { + return qfalse; + } + + //Don't want a new enemy + //rwwFIXMEFIXME: support for locked enemy + //if ( ( ValidEnemy( NPC->enemy ) ) && ( NPC->svFlags & SVF_LOCKEDENEMY ) ) + // return qtrue; + + //See if the player is closer than our current enemy + if ( NPC_CheckPlayerDistance() ) + { + return qtrue; + } + + //Otherwise, turn off the flag +// NPC->svFlags &= ~SVF_LOCKEDENEMY; + //See if the player is closer than our current enemy + if ( NPC->client->NPC_class != CLASS_RANCOR + && NPC->client->NPC_class != CLASS_WAMPA + //&& NPC->client->NPC_class != CLASS_SAND_CREATURE + && NPC_CheckPlayerDistance() ) + {//rancors, wampas & sand creatures don't care if player is closer, they always go with closest + return qtrue; + } + + //If we've gotten here alright, then our target it still valid + if ( NPC_ValidEnemy( NPC->enemy ) ) + return qtrue; + + newenemy = NPC_PickEnemyExt( checkAlerts ); + + //if we found one, take it as the enemy + if( NPC_ValidEnemy( newenemy ) ) + { + G_SetEnemy( NPC, newenemy ); + return qtrue; + } + + return qfalse; +} + +/* +------------------------- +NPC_CheckEnemyExt +------------------------- +*/ + +qboolean NPC_CheckEnemyExt( qboolean checkAlerts ) +{ + //Make sure we're ready to think again +/* + if ( NPCInfo->enemyCheckDebounceTime > level.time ) + return qfalse; + + //Get our next think time + NPCInfo->enemyCheckDebounceTime = level.time + NPC_GetCheckDelta(); + + //Attempt to find an enemy + return NPC_FindEnemy(); +*/ + return NPC_FindEnemy( checkAlerts ); +} + +/* +------------------------- +NPC_FacePosition +------------------------- +*/ + +qboolean NPC_FacePosition( vec3_t position, qboolean doPitch ) +{ + vec3_t muzzle; + vec3_t angles; + float yawDelta; + qboolean facing = qtrue; + + //Get the positions + if ( NPC->client && (NPC->client->NPC_class == CLASS_RANCOR || NPC->client->NPC_class == CLASS_WAMPA) )// || NPC->client->NPC_class == CLASS_SAND_CREATURE) ) + { + CalcEntitySpot( NPC, SPOT_ORIGIN, muzzle ); + muzzle[2] += NPC->r.maxs[2] * 0.75f; + } + else if ( NPC->client && NPC->client->NPC_class == CLASS_GALAKMECH ) + { + CalcEntitySpot( NPC, SPOT_WEAPON, muzzle ); + } + else + { + CalcEntitySpot( NPC, SPOT_HEAD_LEAN, muzzle );//SPOT_HEAD + } + + //Find the desired angles + GetAnglesForDirection( muzzle, position, angles ); + + NPCInfo->desiredYaw = AngleNormalize360( angles[YAW] ); + NPCInfo->desiredPitch = AngleNormalize360( angles[PITCH] ); + + if ( NPC->enemy && NPC->enemy->client && NPC->enemy->client->NPC_class == CLASS_ATST ) + { + // FIXME: this is kind of dumb, but it was the easiest way to get it to look sort of ok + NPCInfo->desiredYaw += flrand( -5, 5 ) + sin( level.time * 0.004f ) * 7; + NPCInfo->desiredPitch += flrand( -2, 2 ); + } + //Face that yaw + NPC_UpdateAngles( qtrue, qtrue ); + + //Find the delta between our goal and our current facing + yawDelta = AngleNormalize360( NPCInfo->desiredYaw - ( SHORT2ANGLE( ucmd.angles[YAW] + client->ps.delta_angles[YAW] ) ) ); + + //See if we are facing properly + if ( fabs( yawDelta ) > VALID_ATTACK_CONE ) + facing = qfalse; + + if ( doPitch ) + { + //Find the delta between our goal and our current facing + float currentAngles = ( SHORT2ANGLE( ucmd.angles[PITCH] + client->ps.delta_angles[PITCH] ) ); + float pitchDelta = NPCInfo->desiredPitch - currentAngles; + + //See if we are facing properly + if ( fabs( pitchDelta ) > VALID_ATTACK_CONE ) + facing = qfalse; + } + + return facing; +} + +/* +------------------------- +NPC_FaceEntity +------------------------- +*/ + +qboolean NPC_FaceEntity( gentity_t *ent, qboolean doPitch ) +{ + vec3_t entPos; + + //Get the positions + CalcEntitySpot( ent, SPOT_HEAD_LEAN, entPos ); + + return NPC_FacePosition( entPos, doPitch ); +} + +/* +------------------------- +NPC_FaceEnemy +------------------------- +*/ + +qboolean NPC_FaceEnemy( qboolean doPitch ) +{ + if ( NPC == NULL ) + return qfalse; + + if ( NPC->enemy == NULL ) + return qfalse; + + return NPC_FaceEntity( NPC->enemy, doPitch ); +} + +/* +------------------------- +NPC_CheckCanAttackExt +------------------------- +*/ + +qboolean NPC_CheckCanAttackExt( void ) +{ + //We don't want them to shoot + if( NPCInfo->scriptFlags & SCF_DONT_FIRE ) + return qfalse; + + //Turn to face + if ( NPC_FaceEnemy( qtrue ) == qfalse ) + return qfalse; + + //Must have a clear line of sight to the target + if ( NPC_ClearShot( NPC->enemy ) == qfalse ) + return qfalse; + + return qtrue; +} + +/* +------------------------- +NPC_ClearLookTarget +------------------------- +*/ + +void NPC_ClearLookTarget( gentity_t *self ) +{ + if ( !self->client ) + { + return; + } + + if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//lookTarget is set by and to the monster that's holding you, no other operations can change that + return; + } + + self->client->renderInfo.lookTarget = ENTITYNUM_NONE;//ENTITYNUM_WORLD; + self->client->renderInfo.lookTargetClearTime = 0; +} + +/* +------------------------- +NPC_SetLookTarget +------------------------- +*/ +void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ) +{ + if ( !self->client ) + { + return; + } + + if ( (self->client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + {//lookTarget is set by and to the monster that's holding you, no other operations can change that + return; + } + + self->client->renderInfo.lookTarget = entNum; + self->client->renderInfo.lookTargetClearTime = clearTime; +} + +/* +------------------------- +NPC_CheckLookTarget +------------------------- +*/ +qboolean NPC_CheckLookTarget( gentity_t *self ) +{ + if ( self->client ) + { + if ( self->client->renderInfo.lookTarget >= 0 && self->client->renderInfo.lookTarget < ENTITYNUM_WORLD ) + {//within valid range + if ( (&g_entities[self->client->renderInfo.lookTarget] == NULL) || !g_entities[self->client->renderInfo.lookTarget].inuse ) + {//lookTarget not inuse or not valid anymore + NPC_ClearLookTarget( self ); + } + else if ( self->client->renderInfo.lookTargetClearTime && self->client->renderInfo.lookTargetClearTime < level.time ) + {//Time to clear lookTarget + NPC_ClearLookTarget( self ); + } + else if ( g_entities[self->client->renderInfo.lookTarget].client && self->enemy && (&g_entities[self->client->renderInfo.lookTarget] != self->enemy) ) + {//should always look at current enemy if engaged in battle... FIXME: this could override certain scripted lookTargets...??? + NPC_ClearLookTarget( self ); + } + else + { + return qtrue; + } + } + } + + return qfalse; +} + +/* +------------------------- +NPC_CheckCharmed +------------------------- +*/ +extern void G_AddVoiceEvent( gentity_t *self, int event, int speakDebounceTime ); +void NPC_CheckCharmed( void ) +{ + if ( NPCInfo->charmedTime && NPCInfo->charmedTime < level.time && NPC->client ) + {//we were charmed, set us back! + NPC->client->playerTeam = NPC->genericValue1; + NPC->client->enemyTeam = NPC->genericValue2; + NPC->s.teamowner = NPC->genericValue3; + + NPC->client->leader = NULL; + if ( NPCInfo->tempBehavior == BS_FOLLOW_LEADER ) + { + NPCInfo->tempBehavior = BS_DEFAULT; + } + G_ClearEnemy( NPC ); + NPCInfo->charmedTime = 0; + //say something to let player know you've snapped out of it + G_AddVoiceEvent( NPC, Q_irand(EV_CONFUSE1, EV_CONFUSE3), 2000 ); + } +} + +void G_GetBoltPosition( gentity_t *self, int boltIndex, vec3_t pos, int modelIndex ) +{ + mdxaBone_t boltMatrix; + vec3_t result, angles; + + if (!self || !self->inuse) + { + return; + } + + if (self->client) + { //clients don't actually even keep r.currentAngles maintained + VectorSet(angles, 0, self->client->ps.viewangles[YAW], 0); + } + else + { + VectorSet(angles, 0, self->r.currentAngles[YAW], 0); + } + + if ( /*!self || ...haha (sorry, i'm tired)*/ !self->ghoul2 ) + { + return; + } + + trap_G2API_GetBoltMatrix( self->ghoul2, modelIndex, + boltIndex, + &boltMatrix, angles, self->r.currentOrigin, level.time, + NULL, self->modelScale ); + if ( pos ) + { + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, result ); + VectorCopy( result, pos ); + } +} + +float NPC_EntRangeFromBolt( gentity_t *targEnt, int boltIndex ) +{ + vec3_t org; + + if ( !targEnt ) + { + return Q3_INFINITE; + } + + G_GetBoltPosition( NPC, boltIndex, org, 0 ); + + return (Distance( targEnt->r.currentOrigin, org )); +} + +float NPC_EnemyRangeFromBolt( int boltIndex ) +{ + return (NPC_EntRangeFromBolt( NPC->enemy, boltIndex )); +} + +int NPC_GetEntsNearBolt( int *radiusEnts, float radius, int boltIndex, vec3_t boltOrg ) +{ + vec3_t mins, maxs; + int i; + + //get my handRBolt's position + vec3_t org; + + G_GetBoltPosition( NPC, boltIndex, org, 0 ); + + VectorCopy( org, boltOrg ); + + //Setup the bbox to search in + for ( i = 0; i < 3; i++ ) + { + mins[i] = boltOrg[i] - radius; + maxs[i] = boltOrg[i] + radius; + } + + //Get the number of entities in a given space + return (trap_EntitiesInBox( mins, maxs, radiusEnts, 128 )); +} diff --git a/code/game/SpeederNPC.c b/code/game/SpeederNPC.c new file mode 100644 index 0000000..ae15c59 --- /dev/null +++ b/code/game/SpeederNPC.c @@ -0,0 +1,1134 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#include "..\game\wp_saber.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +#endif + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +#ifdef QAGAME //SP or gameside MP +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +#endif + +#ifdef _JK2MP + +#include "../namespace_begin.h" + +extern void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int BG_GetTime(void); +extern qboolean BG_SabersOff( playerState_t *ps ); +#endif + +//Alright, actually, most of this file is shared between game and cgame for MP. +//I would like to keep it this way, so when modifying for SP please keep in +//mind the bgEntity restrictions imposed. -rww + +#define STRAFERAM_DURATION 8 +#define STRAFERAM_ANGLE 8 + + +#ifndef _JK2MP +bool VEH_StartStrafeRam(Vehicle_t *pVeh, bool Right) +{ + if (!(pVeh->m_ulFlags&VEH_STRAFERAM)) + { + float speed = VectorLength(pVeh->m_pParentEntity->client->ps.velocity); + if (speed>400.0f) + { + // Compute Pos3 + //-------------- + vec3_t right; + AngleVectors(pVeh->m_vOrientation, 0, right, 0); + VectorMA(pVeh->m_pParentEntity->client->ps.velocity, (Right)?( speed):(-speed), right, pVeh->m_pParentEntity->pos3); + + pVeh->m_ulFlags |= VEH_STRAFERAM; + parentPS->hackingTime = (Right)?(STRAFERAM_DURATION):(-STRAFERAM_DURATION); + + if (pVeh->m_iSoundDebounceTimerm_pVehicleInfo->soundShift1; break; + case 2: shiftSound=pVeh->m_pVehicleInfo->soundShift2; break; + case 3: shiftSound=pVeh->m_pVehicleInfo->soundShift3; break; + case 4: shiftSound=pVeh->m_pVehicleInfo->soundShift4; break; + } + if (shiftSound) + { + pVeh->m_iSoundDebounceTimer = level.time + Q_irand(1000, 4000); + G_SoundIndexOnEnt( pVeh->m_pParentEntity, CHAN_AUTO, shiftSound); + } + } + return true; + } + } + return false; +} +#else +bool VEH_StartStrafeRam(Vehicle_t *pVeh, bool Right, int Duration) +{ + return false; +} +#endif + + +#ifdef QAGAME //game-only.. for now +// Like a think or move command, this updates various vehicle properties. +bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + if ( !g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ) ) + { + return false; + } + + // See whether this vehicle should be exploding. + if ( pVeh->m_iDieTime != 0 ) + { + pVeh->m_pVehicleInfo->DeathUpdate( pVeh ); + } + + // Update move direction. +#ifndef _JK2MP //this makes prediction unhappy, and rightfully so. + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + + if ( pVeh->m_ulFlags & VEH_FLYING ) + { + vec3_t vVehAngles; + VectorSet(vVehAngles, 0, pVeh->m_vOrientation[YAW], 0 ); + AngleVectors( vVehAngles, parent->client->ps.moveDir, NULL, NULL ); + } + else + { + vec3_t vVehAngles; + VectorSet(vVehAngles, pVeh->m_vOrientation[PITCH], pVeh->m_vOrientation[YAW], 0 ); + AngleVectors( vVehAngles, parent->client->ps.moveDir, NULL, NULL ); + } + + // Check For A Strafe Ram + //------------------------ + if (!(pVeh->m_ulFlags&VEH_STRAFERAM) && !(pVeh->m_ulFlags&VEH_FLYING)) + { + // Started A Strafe + //------------------ + if (pVeh->m_ucmd.rightmove && !parentPS->hackingTime) + { + parentPS->hackingTime = (pVeh->m_ucmd.rightmove>0)?(level.time):(-1*level.time); + } + + // Ended A Strafe + //---------------- + else if (!pVeh->m_ucmd.rightmove && parentPS->hackingTime) + { + // If It Was A Short Burst, Start The Strafe Ram + //----------------------------------------------- + if ((level.time - abs(parentPS->hackingTime))<300) + { + if (!VEH_StartStrafeRam(pVeh, (parentPS->hackingTime>0))) + { + parentPS->hackingTime = 0; + } + } + + // Otherwise, Clear The Timer + //---------------------------- + else + { + parentPS->hackingTime = 0; + } + } + } + + // If Currently In A StrafeRam, Check To See If It Is Done (Timed Out) + //--------------------------------------------------------------------- + else if (!parentPS->hackingTime) + { + pVeh->m_ulFlags &=~VEH_STRAFERAM; + } + + + // Exhaust Effects Start And Stop When The Accelerator Is Pressed + //---------------------------------------------------------------- + if (pVeh->m_pVehicleInfo->iExhaustFX) + { + // Start It On Each Exhaust Bolt + //------------------------------- + if (pVeh->m_ucmd.forwardmove && !(pVeh->m_ulFlags&VEH_ACCELERATORON)) + { + pVeh->m_ulFlags |= VEH_ACCELERATORON; + for (int i=0; (im_iExhaustTag[i]!=-1); i++) + { + G_PlayEffect(pVeh->m_pVehicleInfo->iExhaustFX, parent->playerModel, pVeh->m_iExhaustTag[i], parent->s.number, parent->currentOrigin, 1, qtrue); + } + } + + // Stop It On Each Exhaust Bolt + //------------------------------ + else if (!pVeh->m_ucmd.forwardmove && (pVeh->m_ulFlags&VEH_ACCELERATORON)) + { + pVeh->m_ulFlags &=~VEH_ACCELERATORON; + for (int i=0; (im_iExhaustTag[i]!=-1); i++) + { + G_StopEffect(pVeh->m_pVehicleInfo->iExhaustFX, parent->playerModel, pVeh->m_iExhaustTag[i], parent->s.number); + } + } + } + + if (!(pVeh->m_ulFlags&VEH_ARMORLOW) && (pVeh->m_iArmor <= pVeh->m_pVehicleInfo->armor/3)) + { + pVeh->m_ulFlags |= VEH_ARMORLOW; + + } + + // Armor Gone Effects (Fire) + //--------------------------- + if (pVeh->m_pVehicleInfo->iArmorGoneFX) + { + if (!(pVeh->m_ulFlags&VEH_ARMORGONE) && (pVeh->m_iArmor <= 0)) + { + pVeh->m_ulFlags |= VEH_ARMORGONE; + G_PlayEffect(pVeh->m_pVehicleInfo->iArmorGoneFX, parent->playerModel, parent->crotchBolt, parent->s.number, parent->currentOrigin, 1, qtrue); + parent->s.loopSound = G_SoundIndex( "sound/vehicles/common/fire_lp.wav" ); + } + } +#endif + + return true; +} +#endif //QAGAME + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + playerState_t *parentPS; + playerState_t *pilotPS = NULL; + int curTime; + +#ifdef _JK2MP + parentPS = pVeh->m_pParentEntity->playerState; + if (pVeh->m_pPilot) + { + pilotPS = pVeh->m_pPilot->playerState; + } +#else + parentPS = &pVeh->m_pParentEntity->client->ps; + if (pVeh->m_pPilot) + { + pilotPS = &pVeh->m_pPilot->client->ps; + } +#endif + + + // If we're flying, make us accelerate at 40% (about half) acceleration rate, and restore the pitch + // to origin (straight) position (at 5% increments). + if ( pVeh->m_ulFlags & VEH_FLYING ) + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier * 0.4f; + } +#ifdef _JK2MP + else if ( !parentPS->m_iVehicleNum ) +#else + else if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//drifts to a stop + speedInc = 0; + //pVeh->m_ucmd.forwardmove = 127; + } + else + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + + + if ( (pVeh->m_pPilot /*&& (pilotPS->weapon == WP_NONE || pilotPS->weapon == WP_MELEE )*/ && + (pVeh->m_ucmd.buttons & BUTTON_ALT_ATTACK) && pVeh->m_pVehicleInfo->turboSpeed) + /*|| + (parentPS && parentPS->electrifyTime > curTime && pVeh->m_pVehicleInfo->turboSpeed)*/ //make them go! + ) + { + if ( (parentPS && parentPS->electrifyTime > curTime) || + (pVeh->m_pPilot->playerState && + (pVeh->m_pPilot->playerState->weapon == WP_MELEE || + (pVeh->m_pPilot->playerState->weapon == WP_SABER && BG_SabersOff( pVeh->m_pPilot->playerState ) ))) ) + { + if ((curTime - pVeh->m_iTurboTime)>pVeh->m_pVehicleInfo->turboRecharge) + { + pVeh->m_iTurboTime = (curTime + pVeh->m_pVehicleInfo->turboDuration); + if (pVeh->m_pVehicleInfo->iTurboStartFX) + { + int i; + for (i=0; (im_iExhaustTag[i]!=-1); i++) + { +#ifdef QAGAME + if (pVeh->m_pParentEntity && + pVeh->m_pParentEntity->ghoul2 && + pVeh->m_pParentEntity->playerState) + { //fine, I'll use a tempent for this, but only because it's played only once at the start of a turbo. + vec3_t boltOrg, boltDir; + mdxaBone_t boltMatrix; + + VectorSet(boltDir, 0.0f, pVeh->m_pParentEntity->playerState->viewangles[YAW], 0.0f); + + trap_G2API_GetBoltMatrix(pVeh->m_pParentEntity->ghoul2, 0, pVeh->m_iExhaustTag[i], &boltMatrix, boltDir, pVeh->m_pParentEntity->playerState->origin, level.time, NULL, pVeh->m_pParentEntity->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltOrg); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, boltDir); + G_PlayEffectID(pVeh->m_pVehicleInfo->iTurboStartFX, boltOrg, boltDir); + } +#endif + } + } + parentPS->speed = pVeh->m_pVehicleInfo->turboSpeed; // Instantly Jump To Turbo Speed + } + } + } + + // Slide Breaking + if (pVeh->m_ulFlags&VEH_SLIDEBREAKING) + { + if (pVeh->m_ucmd.forwardmove>=0 +#ifndef _JK2MP + || ((level.time - pVeh->m_pParentEntity->lastMoveTime)>500) +#endif + ) + { + pVeh->m_ulFlags &= ~VEH_SLIDEBREAKING; + } + parentPS->speed = 0; + } + else if ( + (curTime > pVeh->m_iTurboTime) && + !(pVeh->m_ulFlags&VEH_FLYING) && + pVeh->m_ucmd.forwardmove<0 && + fabs(pVeh->m_vOrientation[ROLL])>25.0f) + { + pVeh->m_ulFlags |= VEH_SLIDEBREAKING; + } + + + if ( curTime < pVeh->m_iTurboTime ) + { + speedMax = pVeh->m_pVehicleInfo->turboSpeed; + if (parentPS) + { + parentPS->eFlags |= EF_JETPACK_ACTIVE; + } + } + else + { + speedMax = pVeh->m_pVehicleInfo->speedMax; + if (parentPS) + { + parentPS->eFlags &= ~EF_JETPACK_ACTIVE; + } + } + + + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + + if ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + parentPS->speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( parentPS->speed > 0.0f ) + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < 0.0f ) + { + parentPS->speed = 0.0f; + } + } + else if ( parentPS->speed < 0.0f ) + { + parentPS->speed += speedIdleDec; + if ( parentPS->speed > 0.0f ) + { + parentPS->speed = 0.0f; + } + } + } + else + { + if ( !pVeh->m_pVehicleInfo->strafePerc +#ifdef _JK2MP + || (0 && pVeh->m_pParentEntity->s.number < MAX_CLIENTS) ) +#else + || (!g_speederControlScheme->value && !pVeh->m_pParentEntity->s.number) ) +#endif + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + //pVeh->m_ucmd.rightmove = 0; + } + } + + if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + + if (parentPS && parentPS->electrifyTime > curTime) + { + parentPS->speed *= (pVeh->m_fTimeModifier/60.0f); + } + + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +//Oh, and please, use "< MAX_CLIENTS" to check for "player" and not +//"!s.number", this is a universal check that will work for both SP +//and MP. -rww +// ProcessOrientCommands the Vehicle. +#ifdef _JK2MP //temp hack til mp speeder controls are sorted -rww +extern void AnimalProcessOri(Vehicle_t *pVeh); +#endif +void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + playerState_t *riderPS; + playerState_t *parentPS; + +#ifdef _JK2MP + float angDif; + + if (pVeh->m_pPilot) + { + riderPS = pVeh->m_pPilot->playerState; + } + else + { + riderPS = pVeh->m_pParentEntity->playerState; + } + parentPS = pVeh->m_pParentEntity->playerState; + + //pVeh->m_vOrientation[YAW] = 0.0f;//riderPS->viewangles[YAW]; + angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*4.0f; //magic number hackery + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + + if (parentPS->electrifyTime > pm->cmd.serverTime) + { //do some crazy stuff + pVeh->m_vOrientation[YAW] += (sin(pm->cmd.serverTime/1000.0f)*3.0f)*pVeh->m_fTimeModifier; + } + } + +#else + gentity_t *rider = pVeh->m_pParentEntity->owner; + if ( !rider || !rider->client ) + { + riderPS = &pVeh->m_pParentEntity->client->ps; + } + else + { + riderPS = &rider->client->ps; + } + parentPS = &pVeh->m_pParentEntity->client->ps; + + if (pVeh->m_ulFlags & VEH_FLYING) + { + pVeh->m_vOrientation[YAW] += pVeh->m_vAngularVelocity; + } + else if ( + (pVeh->m_ulFlags & VEH_SLIDEBREAKING) || // No Angles Control While Out Of Control + (pVeh->m_ulFlags & VEH_OUTOFCONTROL) // No Angles Control While Out Of Control + ) + { + // Any ability to change orientation? + } + else if ( + (pVeh->m_ulFlags & VEH_STRAFERAM) // No Angles Control While Strafe Ramming + ) + { + if (parentPS->hackingTime>0) + { + parentPS->hackingTime--; + pVeh->m_vOrientation[ROLL] += (parentPS->hackingTime<( STRAFERAM_DURATION/2))?(-STRAFERAM_ANGLE):( STRAFERAM_ANGLE); + } + else if (pVeh->hackingTime<0) + { + parentPS->hackingTime++; + pVeh->m_vOrientation[ROLL] += (parentPS->hackingTime>(-STRAFERAM_DURATION/2))?( STRAFERAM_ANGLE):(-STRAFERAM_ANGLE); + } + } + else + { + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + } +#endif + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef QAGAME + +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); + +// This function makes sure that the vehicle is properly animated. +void AnimateVehicle( Vehicle_t *pVeh ) +{ +} + +#endif //QAGAME + +//rest of file is shared + +#ifndef _JK2MP +extern void CG_ChangeWeapon( int num ); +#endif + + +#ifndef _JK2MP +extern void G_StartMatrixEffect( gentity_t *ent, int meFlags = 0, int length = 1000, float timeScale = 0.0f, int spinTime = 0 ); +#endif + + +//NOTE NOTE NOTE NOTE NOTE NOTE +//I want to keep this function BG too, because it's fairly generic already, and it +//would be nice to have proper prediction of animations. -rww +// This function makes sure that the rider's in this vehicle are properly animated. +void AnimateRiders( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_VS_IDLE; + float fSpeedPercToMax; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + playerState_t *pilotPS; + playerState_t *parentPS; + int curTime; + + + // Boarding animation. + if ( pVeh->m_iBoarding != 0 ) + { + // We've just started moarding, set the amount of time it will take to finish moarding. + if ( pVeh->m_iBoarding < 0 ) + { + int iAnimLen; + + // Boarding from left... + if ( pVeh->m_iBoarding == -1 ) + { + Anim = BOTH_VS_MOUNT_L; + } + else if ( pVeh->m_iBoarding == -2 ) + { + Anim = BOTH_VS_MOUNT_R; + } + else if ( pVeh->m_iBoarding == -3 ) + { + Anim = BOTH_VS_MOUNTJUMP_L; + } + else if ( pVeh->m_iBoarding == VEH_MOUNT_THROW_LEFT) + { + iBlend = 0; + Anim = BOTH_VS_MOUNTTHROW_R; + } + else if ( pVeh->m_iBoarding == VEH_MOUNT_THROW_RIGHT) + { + iBlend = 0; + Anim = BOTH_VS_MOUNTTHROW_L; + } + + // Set the delay time (which happens to be the time it takes for the animation to complete). + // NOTE: Here I made it so the delay is actually 40% (0.4f) of the animation time. +#ifdef _JK2MP + iAnimLen = BG_AnimLength( pVeh->m_pPilot->localAnimIndex, Anim ) * 0.4f; + pVeh->m_iBoarding = BG_GetTime() + iAnimLen; +#else + iAnimLen = PM_AnimLength( pVeh->m_pPilot->client->clientInfo.animFileIndex, Anim );// * 0.4f; + if (pVeh->m_iBoarding!=VEH_MOUNT_THROW_LEFT && pVeh->m_iBoarding!=VEH_MOUNT_THROW_RIGHT) + { + pVeh->m_iBoarding = level.time + (iAnimLen*0.4f); + } + else + { + pVeh->m_iBoarding = level.time + iAnimLen; + } +#endif + // Set the animation, which won't be interrupted until it's completed. + // TODO: But what if he's killed? Should the animation remain persistant??? + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + +#ifdef _JK2MP + BG_SetAnim(pVeh->m_pPilot->playerState, bgAllAnims[pVeh->m_pPilot->localAnimIndex].anims, + SETANIM_BOTH, Anim, iFlags, iBlend); +#else + NPC_SetAnim( pVeh->m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); + if (pVeh->m_pOldPilot) + { + iAnimLen = PM_AnimLength( pVeh->m_pPilot->client->clientInfo.animFileIndex, BOTH_VS_MOUNTTHROWEE); + NPC_SetAnim( pVeh->m_pOldPilot, SETANIM_BOTH, BOTH_VS_MOUNTTHROWEE, iFlags, iBlend ); + } +#endif + } + +#ifndef _JK2MP + if (pVeh->m_pOldPilot && pVeh->m_pOldPilot->client->ps.torsoAnimTimer<=0) + { + if (Q_irand(0, player->count)==0) + { + player->count++; + player->lastEnemy = pVeh->m_pOldPilot; + G_StartMatrixEffect(player, MEF_LOOK_AT_ENEMY|MEF_NO_RANGEVAR|MEF_NO_VERTBOB|MEF_NO_SPIN, 1000); + } + + gentity_t* oldPilot = pVeh->m_pOldPilot; + pVeh->m_pVehicleInfo->Eject(pVeh, pVeh->m_pOldPilot, qtrue); // will set pointer to zero + + // Kill Him + //---------- + oldPilot->client->noRagTime = -1; // no ragdoll for you + G_Damage(oldPilot, pVeh->m_pPilot, pVeh->m_pPilot, pVeh->m_pPilot->currentAngles, pVeh->m_pPilot->currentOrigin, 1000, 0, MOD_CRUSH); + + // Compute THe Throw Direction As Backwards From The Vehicle's Velocity + //---------------------------------------------------------------------- + vec3_t throwDir; + VectorScale(pVeh->m_pParentEntity->client->ps.velocity, -1.0f, throwDir); + VectorNormalize(throwDir); + throwDir[2] += 0.3f; // up a little + + // Now Throw Him Out + //------------------- + G_Throw(oldPilot, throwDir, VectorLength(pVeh->m_pParentEntity->client->ps.velocity)/10.0f); + NPC_SetAnim(oldPilot, SETANIM_BOTH, BOTH_DEATHBACKWARD1, SETANIM_FLAG_OVERRIDE, iBlend ); + } +#endif + + return; + } + +#ifdef _JK2MP //fixme + if (1) return; +#endif + +#ifdef _JK2MP + pilotPS = pVeh->m_pPilot->playerState; + parentPS = pVeh->m_pPilot->playerState; +#else + pilotPS = &pVeh->m_pPilot->client->ps; + parentPS = &pVeh->m_pParentEntity->client->ps; +#endif + +#ifndef _JK2MP//SP + curTime = level.time; +#elif QAGAME//MP GAME + curTime = level.time; +#elif CGAME//MP CGAME + //FIXME: pass in ucmd? Not sure if this is reliable... + curTime = pm->cmd.serverTime; +#endif + + // Percentage of maximum speed relative to current speed. + fSpeedPercToMax = parentPS->speed / pVeh->m_pVehicleInfo->speedMax; + + // Going in reverse... +#ifdef _JK2MP + if ( pVeh->m_ucmd.forwardmove < 0 && !(pVeh->m_ulFlags & VEH_SLIDEBREAKING)) +#else + if ( fSpeedPercToMax < -0.018f && !(pVeh->m_ulFlags & VEH_SLIDEBREAKING)) +#endif + { + Anim = BOTH_VS_REV; + iBlend = 500; + } + else + { + bool HasWeapon = ((pilotPS->weapon != WP_NONE) && (pilotPS->weapon != WP_MELEE)); + bool Attacking = (HasWeapon && !!(pVeh->m_ucmd.buttons&BUTTON_ATTACK)); +#ifdef _JK2MP //fixme: flying tends to spaz out a lot + bool Flying = false; + bool Crashing = false; +#else + bool Flying = !!(pVeh->m_ulFlags & VEH_FLYING); + bool Crashing = !!(pVeh->m_ulFlags & VEH_CRASHING); +#endif + bool Right = (pVeh->m_ucmd.rightmove>0); + bool Left = (pVeh->m_ucmd.rightmove<0); + bool Turbo = (curTimem_iTurboTime); + EWeaponPose WeaponPose = WPOSE_NONE; + + + // Remove Crashing Flag + //---------------------- + pVeh->m_ulFlags &= ~VEH_CRASHING; + + + // Put Away Saber When It Is Not Active + //-------------------------------------- +#ifndef _JK2MP + if (HasWeapon && (Turbo || (pilotPS->weapon==WP_SABER && !pilotPS->SaberActive()))) + { + if (pVeh->m_pPilot->s.numberm_pPilot->client->ps.weapon = WP_NONE; + G_RemoveWeaponModels(pVeh->m_pPilot); + } +#endif + + // Don't Interrupt Attack Anims + //------------------------------ +#ifdef _JK2MP + if (pilotPS->weaponTime>0) + { + return; + } +#else + if (pilotPS->torsoAnim>=BOTH_VS_ATL_S && pilotPS->torsoAnim<=BOTH_VS_ATF_G) + { + float bodyCurrent = 0.0f; + int bodyEnd = 0; + if (!!gi.G2API_GetBoneAnimIndex(&pVeh->m_pPilot->ghoul2[pVeh->m_pPilot->playerModel], pVeh->m_pPilot->rootBone, level.time, &bodyCurrent, NULL, &bodyEnd, NULL, NULL, NULL)) + { + if (bodyCurrent<=((float)(bodyEnd)-1.5f)) + { + return; + } + } + } +#endif + + // Compute The Weapon Pose + //-------------------------- + if (pilotPS->weapon==WP_BLASTER) + { + WeaponPose = WPOSE_BLASTER; + } + else if (pilotPS->weapon==WP_SABER) + { + if ( (pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VS_ATL_TO_R_S) + { + pVeh->m_ulFlags &= ~VEH_SABERINLEFTHAND; + } + if (!(pVeh->m_ulFlags&VEH_SABERINLEFTHAND) && pilotPS->torsoAnim==BOTH_VS_ATR_TO_L_S) + { + pVeh->m_ulFlags |= VEH_SABERINLEFTHAND; + } + WeaponPose = (pVeh->m_ulFlags&VEH_SABERINLEFTHAND)?(WPOSE_SABERLEFT):(WPOSE_SABERRIGHT); + } + + + if (Attacking && WeaponPose) + {// Attack! + iBlend = 100; + iFlags = SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART; + + // Auto Aiming + //=============================================== + if (!Left && !Right) // Allow player strafe keys to override + { +#ifndef _JK2MP + if (pVeh->m_pPilot->enemy) + { + vec3_t toEnemy; + float toEnemyDistance; + vec3_t actorRight; + float actorRightDot; + + VectorSubtract(pVeh->m_pPilot->currentOrigin, pVeh->m_pPilot->enemy->currentOrigin, toEnemy); + toEnemyDistance = VectorNormalize(toEnemy); + + AngleVectors(pVeh->m_pParentEntity->currentAngles, 0, actorRight, 0); + actorRightDot = DotProduct(toEnemy, actorRight); + + if (fabsf(actorRightDot)>0.5f || pilotPS->weapon==WP_SABER) + { + Left = (actorRightDot>0.0f); + Right = !Left; + } + else + { + Right = Left = false; + } + } + else +#endif + if (pilotPS->weapon==WP_SABER && !Left && !Right) + { + Left = (WeaponPose==WPOSE_SABERLEFT); + Right = !Left; + } + } + + + if (Left) + {// Attack Left + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VS_ATL_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_ATL_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_ATR_TO_L_S; break; + default: assert(0); + } + } + else if (Right) + {// Attack Right + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VS_ATR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_ATL_TO_R_S; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_ATR_S; break; + default: assert(0); + } + } + else + {// Attack Ahead + switch(WeaponPose) + { + case WPOSE_BLASTER: Anim = BOTH_VS_ATF_G; break; + default: assert(0); + } + } + + } + else if (Left && pVeh->m_ucmd.buttons&BUTTON_USE) + {// Look To The Left Behind + iBlend = 400; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + switch(WeaponPose) + { + case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; + default: Anim = BOTH_VS_LOOKLEFT; + } + } + else if (Right && pVeh->m_ucmd.buttons&BUTTON_USE) + {// Look To The Right Behind + iBlend = 400; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + switch(WeaponPose) + { + case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; + default: Anim = BOTH_VS_LOOKRIGHT; + } + } + else if (Turbo) + {// Kicked In Turbo + iBlend = 50; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + Anim = BOTH_VS_TURBO; + } + else if (Flying) + {// Off the ground in a jump + iBlend = 800; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_AIR; break; + case WPOSE_BLASTER: Anim = BOTH_VS_AIR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_AIR_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_AIR_SR; break; + default: assert(0); + } + } + else if (Crashing) + {// Hit the ground! + iBlend = 100; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_LAND; break; + case WPOSE_BLASTER: Anim = BOTH_VS_LAND_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_LAND_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_LAND_SR; break; + default: assert(0); + } + } + else + {// No Special Moves + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLDLESS; + + if (pVeh->m_vOrientation[ROLL] <= -20) + {// Lean Left + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_LEANL; break; + case WPOSE_BLASTER: Anim = BOTH_VS_LEANL_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_LEANL_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_LEANL_SR; break; + default: assert(0); + } + } + else if (pVeh->m_vOrientation[ROLL] >= 20) + {// Lean Right + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_LEANR; break; + case WPOSE_BLASTER: Anim = BOTH_VS_LEANR_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_LEANR_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_LEANR_SR; break; + default: assert(0); + } + } + else + {// No Lean + switch(WeaponPose) + { + case WPOSE_NONE: Anim = BOTH_VS_IDLE; break; + case WPOSE_BLASTER: Anim = BOTH_VS_IDLE_G; break; + case WPOSE_SABERLEFT: Anim = BOTH_VS_IDLE_SL; break; + case WPOSE_SABERRIGHT: Anim = BOTH_VS_IDLE_SR; break; + default: assert(0); + } + } + }// No Special Moves + }// Going backwards? + +#ifdef _JK2MP + iFlags &= ~SETANIM_FLAG_OVERRIDE; + if (pVeh->m_pPilot->playerState->torsoAnim == Anim) + { + pVeh->m_pPilot->playerState->torsoTimer = BG_AnimLength(pVeh->m_pPilot->localAnimIndex, Anim); + } + if (pVeh->m_pPilot->playerState->legsAnim == Anim) + { + pVeh->m_pPilot->playerState->legsTimer = BG_AnimLength(pVeh->m_pPilot->localAnimIndex, Anim); + } + BG_SetAnim(pVeh->m_pPilot->playerState, bgAllAnims[pVeh->m_pPilot->localAnimIndex].anims, + SETANIM_BOTH, Anim, iFlags|SETANIM_FLAG_HOLD, iBlend); +#else + NPC_SetAnim( pVeh->m_pPilot, SETANIM_BOTH, Anim, iFlags, iBlend ); +#endif +} + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +void G_SetSpeederVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + pVehInfo->AnimateVehicle = AnimateVehicle; + pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; +// pVehInfo->Board = Board; +// pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; +// pVehInfo->DeathUpdate = DeathUpdate; +// pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; + pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif + + //shared + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +void G_CreateSpeederNPC( Vehicle_t **pVeh, const char *strType ) +{ +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strType )]; +#else + // Allocate the Vehicle. + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strType )]; +#endif +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/code/game/WalkerNPC.c b/code/game/WalkerNPC.c new file mode 100644 index 0000000..394d140 --- /dev/null +++ b/code/game/WalkerNPC.c @@ -0,0 +1,636 @@ +// leave this line at the top for all g_xxxx.cpp files... +#include "g_headers.h" + +//seems to be a compiler bug, it doesn't clean out the #ifdefs between dif-compiles +//or something, so the headers spew errors on these defs from the previous compile. +//this fixes that. -rww +#ifdef _JK2MP +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifndef _JK2MP //if single player +#ifndef QAGAME //I don't think we have a QAGAME define +#define QAGAME //but define it cause in sp we're always in the game +#endif +#endif + +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ +#include "g_local.h" +#elif defined _JK2MP +#include "bg_public.h" +#endif + +#ifndef _JK2MP +#include "g_functions.h" +#include "g_vehicles.h" +#else +#include "bg_vehicles.h" +#endif + +#ifdef _JK2MP +//this is really horrible, but it works! just be sure not to use any locals or anything +//with these names (exluding bool, false, true). -rww +#define currentAngles r.currentAngles +#define currentOrigin r.currentOrigin +#define mins r.mins +#define maxs r.maxs +#define legsAnimTimer legsTimer +#define torsoAnimTimer torsoTimer +#define bool qboolean +#define false qfalse +#define true qtrue + +#define sqrtf sqrt +#define Q_flrand flrand + +#define MOD_EXPLOSIVE MOD_SUICIDE +#else +#define bgEntity_t gentity_t +#endif + +#ifdef QAGAME //we only want a few of these functions for BG + +extern float DotToSpot( vec3_t spot, vec3_t from, vec3_t fromAngles ); +extern vmCvar_t cg_thirdPersonAlpha; +extern vec3_t playerMins; +extern vec3_t playerMaxs; +extern cvar_t *g_speederControlScheme; +extern void PM_SetAnim(pmove_t *pm,int setAnimParts,int anim,int setAnimFlags, int blendTime); +extern int PM_AnimLength( int index, animNumber_t anim ); +extern void Vehicle_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags, int iBlend); +extern void G_Knockdown( gentity_t *self, gentity_t *attacker, const vec3_t pushDir, float strength, qboolean breakSaberLock ); +extern void G_VehicleTrace( trace_t *results, const vec3_t start, const vec3_t tMins, const vec3_t tMaxs, const vec3_t end, int passEntityNum, int contentmask ); + +static void RegisterAssets( Vehicle_t *pVeh ) +{ + //atst uses turret weapon +#ifdef _JK2MP + RegisterItem(BG_FindItemForWeapon(WP_TURRET)); +#else + // PUT SOMETHING HERE... +#endif + + //call the standard RegisterAssets now + g_vehicleInfo[VEHICLE_BASE].RegisterAssets( pVeh ); +} + +// Like a think or move command, this updates various vehicle properties. +/* +static bool Update( Vehicle_t *pVeh, const usercmd_t *pUcmd ) +{ + return g_vehicleInfo[VEHICLE_BASE].Update( pVeh, pUcmd ); +} +*/ + +// Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. +static bool Board( Vehicle_t *pVeh, bgEntity_t *pEnt ) +{ + if ( !g_vehicleInfo[VEHICLE_BASE].Board( pVeh, pEnt ) ) + return false; + + // Set the board wait time (they won't be able to do anything, including getting off, for this amount of time). + pVeh->m_iBoarding = level.time + 1500; + + return true; +} +#endif //QAGAME + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +//MP RULE - ALL PROCESSMOVECOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessMoveCommands the Vehicle. +static void ProcessMoveCommands( Vehicle_t *pVeh ) +{ + /************************************************************************************/ + /* BEGIN Here is where we move the vehicle (forward or back or whatever). BEGIN */ + /************************************************************************************/ + + //Client sets ucmds and such for speed alterations + float speedInc, speedIdleDec, speedIdle, speedIdleAccel, speedMin, speedMax; + float fWalkSpeedMax; + bgEntity_t *parent = pVeh->m_pParentEntity; +#ifdef _JK2MP + playerState_t *parentPS = parent->playerState; +#else + playerState_t *parentPS = &parent->client->ps; +#endif + + speedIdleDec = pVeh->m_pVehicleInfo->decelIdle * pVeh->m_fTimeModifier; + speedMax = pVeh->m_pVehicleInfo->speedMax; + + speedIdle = pVeh->m_pVehicleInfo->speedIdle; + speedIdleAccel = pVeh->m_pVehicleInfo->accelIdle * pVeh->m_fTimeModifier; + speedMin = pVeh->m_pVehicleInfo->speedMin; + +#ifdef _JK2MP + if ( !parentPS->m_iVehicleNum ) +#else + if ( !pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//drifts to a stop + speedInc = speedIdle * pVeh->m_fTimeModifier; + VectorClear( parentPS->moveDir ); + //m_ucmd.forwardmove = 127; + parentPS->speed = 0; + } + else + { + speedInc = pVeh->m_pVehicleInfo->acceleration * pVeh->m_fTimeModifier; + } + + if ( parentPS->speed || parentPS->groundEntityNum == ENTITYNUM_NONE || + pVeh->m_ucmd.forwardmove || pVeh->m_ucmd.upmove > 0 ) + { + if ( pVeh->m_ucmd.forwardmove > 0 && speedInc ) + { + parentPS->speed += speedInc; + } + else if ( pVeh->m_ucmd.forwardmove < 0 ) + { + if ( parentPS->speed > speedIdle ) + { + parentPS->speed -= speedInc; + } + else if ( parentPS->speed > speedMin ) + { + parentPS->speed -= speedIdleDec; + } + } + // No input, so coast to stop. + else if ( parentPS->speed > 0.0f ) + { + parentPS->speed -= speedIdleDec; + if ( parentPS->speed < 0.0f ) + { + parentPS->speed = 0.0f; + } + } + else if ( parentPS->speed < 0.0f ) + { + parentPS->speed += speedIdleDec; + if ( parentPS->speed > 0.0f ) + { + parentPS->speed = 0.0f; + } + } + } + else + { + if ( pVeh->m_ucmd.forwardmove < 0 ) + { + pVeh->m_ucmd.forwardmove = 0; + } + if ( pVeh->m_ucmd.upmove < 0 ) + { + pVeh->m_ucmd.upmove = 0; + } + + pVeh->m_ucmd.rightmove = 0; + + /*if ( !pVeh->m_pVehicleInfo->strafePerc + || (!g_speederControlScheme->value && !parent->s.number) ) + {//if in a strafe-capable vehicle, clear strafing unless using alternate control scheme + pVeh->m_ucmd.rightmove = 0; + }*/ + } + + if (parentPS && parentPS->electrifyTime > pm->cmd.serverTime) + { + speedMax *= 0.5f; + } + + fWalkSpeedMax = speedMax * 0.275f; + if ( pVeh->m_ucmd.buttons & BUTTON_WALKING && parentPS->speed > fWalkSpeedMax ) + { + parentPS->speed = fWalkSpeedMax; + } + else if ( parentPS->speed > speedMax ) + { + parentPS->speed = speedMax; + } + else if ( parentPS->speed < speedMin ) + { + parentPS->speed = speedMin; + } + + if (parentPS->stats[STAT_HEALTH] <= 0) + { //don't keep moving while you're dying! + parentPS->speed = 0; + } + + /********************************************************************************/ + /* END Here is where we move the vehicle (forward or back or whatever). END */ + /********************************************************************************/ +} + +#ifdef _JK2MP +void WalkerYawAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(pVeh->m_vOrientation[YAW], riderPS->viewangles[YAW]); + + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*1.5f; //magic number hackery + + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[YAW] = AngleNormalize180(pVeh->m_vOrientation[YAW] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} + +/* +void WalkerPitchAdjust(Vehicle_t *pVeh, playerState_t *riderPS, playerState_t *parentPS) +{ + float angDif = AngleSubtract(pVeh->m_vOrientation[PITCH], riderPS->viewangles[PITCH]); + + if (parentPS && parentPS->speed) + { + float s = parentPS->speed; + float maxDif = pVeh->m_pVehicleInfo->turningSpeed*0.8f; //magic number hackery + + if (s < 0.0f) + { + s = -s; + } + angDif *= s/pVeh->m_pVehicleInfo->speedMax; + if (angDif > maxDif) + { + angDif = maxDif; + } + else if (angDif < -maxDif) + { + angDif = -maxDif; + } + pVeh->m_vOrientation[PITCH] = AngleNormalize360(pVeh->m_vOrientation[PITCH] - angDif*(pVeh->m_fTimeModifier*0.2f)); + } +} +*/ +#endif + +//MP RULE - ALL PROCESSORIENTCOMMANDS FUNCTIONS MUST BE BG-COMPATIBLE!!! +//If you really need to violate this rule for SP, then use ifdefs. +//By BG-compatible, I mean no use of game-specific data - ONLY use +//stuff available in the MP bgEntity (in SP, the bgEntity is #defined +//as a gentity, but the MP-compatible access restrictions are based +//on the bgEntity structure in the MP codebase) -rww +// ProcessOrientCommands the Vehicle. +static void ProcessOrientCommands( Vehicle_t *pVeh ) +{ + /********************************************************************************/ + /* BEGIN Here is where make sure the vehicle is properly oriented. BEGIN */ + /********************************************************************************/ + float speed; + bgEntity_t *parent = pVeh->m_pParentEntity; + playerState_t *parentPS, *riderPS; + +#ifdef _JK2MP + bgEntity_t *rider = NULL; + if (parent->s.owner != ENTITYNUM_NONE) + { + rider = PM_BGEntForNum(parent->s.owner); //&g_entities[parent->r.ownerNum]; + } +#else + gentity_t *rider = parent->owner; +#endif + +#ifdef _JK2MP + if ( !rider ) +#else + if ( !rider || !rider->client ) +#endif + { + rider = parent; + } + +#ifdef _JK2MP + parentPS = parent->playerState; + riderPS = rider->playerState; +#else + parentPS = &parent->client->ps; + riderPS = &rider->client->ps; +#endif + + speed = VectorLength( parentPS->velocity ); + + // If the player is the rider... + if ( rider->s.number < MAX_CLIENTS ) + {//FIXME: use the vehicle's turning stat in this calc +#ifdef _JK2MP + WalkerYawAdjust(pVeh, riderPS, parentPS); + //FighterPitchAdjust(pVeh, riderPS, parentPS); + pVeh->m_vOrientation[PITCH] = riderPS->viewangles[PITCH]; +#else + pVeh->m_vOrientation[YAW] = riderPS->viewangles[YAW]; + pVeh->m_vOrientation[PITCH] = riderPS->viewangles[PITCH]; +#endif + } + else + { + float turnSpeed = pVeh->m_pVehicleInfo->turningSpeed; + if ( !pVeh->m_pVehicleInfo->turnWhenStopped + && !parentPS->speed )//FIXME: or !pVeh->m_ucmd.forwardmove? + {//can't turn when not moving + //FIXME: or ramp up to max turnSpeed? + turnSpeed = 0.0f; + } +#ifdef _JK2MP + if (rider->s.eType == ET_NPC) +#else + if ( !rider || rider->NPC ) +#endif + {//help NPCs out some + turnSpeed *= 2.0f; +#ifdef _JK2MP + if (parentPS->speed > 200.0f) +#else + if ( parent->client->ps.speed > 200.0f ) +#endif + { + turnSpeed += turnSpeed * parentPS->speed/200.0f*0.05f; + } + } + turnSpeed *= pVeh->m_fTimeModifier; + + //default control scheme: strafing turns, mouselook aims + if ( pVeh->m_ucmd.rightmove < 0 ) + { + pVeh->m_vOrientation[YAW] += turnSpeed; + } + else if ( pVeh->m_ucmd.rightmove > 0 ) + { + pVeh->m_vOrientation[YAW] -= turnSpeed; + } + + if ( pVeh->m_pVehicleInfo->malfunctionArmorLevel && pVeh->m_iArmor <= pVeh->m_pVehicleInfo->malfunctionArmorLevel ) + {//damaged badly + } + } + + /********************************************************************************/ + /* END Here is where make sure the vehicle is properly oriented. END */ + /********************************************************************************/ +} + +#ifdef QAGAME //back to our game-only functions +// This function makes sure that the vehicle is properly animated. +static void AnimateVehicle( Vehicle_t *pVeh ) +{ + animNumber_t Anim = BOTH_STAND1; + int iFlags = SETANIM_FLAG_NORMAL, iBlend = 300; + gentity_t *parent = (gentity_t *)pVeh->m_pParentEntity; + float fSpeedPercToMax; + + // We're dead (boarding is reused here so I don't have to make another variable :-). + if ( parent->health <= 0 ) + { + /* + if ( pVeh->m_iBoarding != -999 ) // Animate the death just once! + { + pVeh->m_iBoarding = -999; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + + // FIXME! Why do you keep repeating over and over!!?!?!? Bastard! + //Vehicle_SetAnim( parent, SETANIM_LEGS, BOTH_VT_DEATH1, iFlags, iBlend ); + } + */ + return; + } + +// Following is redundant to g_vehicles.c +// if ( pVeh->m_iBoarding ) +// { +// //we have no boarding anim +// if (pVeh->m_iBoarding < level.time) +// { //we are on now +// pVeh->m_iBoarding = 0; +// } +// else +// { +// return; +// } +// } + + // Percentage of maximum speed relative to current speed. + //float fSpeed = VectorLength( client->ps.velocity ); + fSpeedPercToMax = parent->client->ps.speed / pVeh->m_pVehicleInfo->speedMax; + + // If we're moving... + if ( fSpeedPercToMax > 0.0f ) //fSpeedPercToMax >= 0.85f ) + { + float fYawDelta; + + iBlend = 300; + iFlags = SETANIM_FLAG_OVERRIDE; + fYawDelta = pVeh->m_vPrevOrientation[YAW] - pVeh->m_vOrientation[YAW]; + + // NOTE: Mikes suggestion for fixing the stuttering walk (left/right) is to maintain the + // current frame between animations. I have no clue how to do this and have to work on other + // stuff so good luck to him :-p AReis + + // If we're walking (or our speed is less than .275%)... + if ( ( pVeh->m_ucmd.buttons & BUTTON_WALKING ) || fSpeedPercToMax < 0.275f ) + { + // Make them lean if we're turning. + /*if ( fYawDelta < -0.0001f ) + { + Anim = BOTH_VT_WALK_FWD_L; + } + else if ( fYawDelta > 0.0001 ) + { + Anim = BOTH_VT_WALK_FWD_R; + } + else*/ + { + Anim = BOTH_WALK1; + } + } + // otherwise we're running. + else + { + // Make them lean if we're turning. + /*if ( fYawDelta < -0.0001f ) + { + Anim = BOTH_VT_RUN_FWD_L; + } + else if ( fYawDelta > 0.0001 ) + { + Anim = BOTH_VT_RUN_FWD_R; + } + else*/ + { + Anim = BOTH_RUN1; + } + } + } + else + { + // Going in reverse... + if ( fSpeedPercToMax < -0.018f ) + { + iFlags = SETANIM_FLAG_NORMAL; + Anim = BOTH_WALKBACK1; + iBlend = 500; + } + else + { + //int iChance = Q_irand( 0, 20000 ); + + // Every once in a while buck or do a different idle... + iFlags = SETANIM_FLAG_NORMAL | SETANIM_FLAG_RESTART | SETANIM_FLAG_HOLD; + iBlend = 600; +#ifdef _JK2MP + if (parent->client->ps.m_iVehicleNum) +#else + if ( pVeh->m_pVehicleInfo->Inhabited( pVeh ) ) +#endif + {//occupado + Anim = BOTH_STAND1; + } + else + {//wide open for you, baby + Anim = BOTH_STAND2; + } + } + } + + Vehicle_SetAnim( parent, SETANIM_LEGS, Anim, iFlags, iBlend ); +} + +//rwwFIXMEFIXME: This is all going to have to be predicted I think, or it will feel awful +//and lagged +#endif //QAGAME + +#ifndef QAGAME +void AttachRidersGeneric( Vehicle_t *pVeh ); +#endif + +//on the client this function will only set up the process command funcs +void G_SetWalkerVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + pVehInfo->AnimateVehicle = AnimateVehicle; +// pVehInfo->AnimateRiders = AnimateRiders; +// pVehInfo->ValidateBoard = ValidateBoard; +// pVehInfo->SetParent = SetParent; +// pVehInfo->SetPilot = SetPilot; +// pVehInfo->AddPassenger = AddPassenger; +// pVehInfo->Animate = Animate; + pVehInfo->Board = Board; +// pVehInfo->Eject = Eject; +// pVehInfo->EjectAll = EjectAll; +// pVehInfo->StartDeathDelay = StartDeathDelay; +// pVehInfo->DeathUpdate = DeathUpdate; + pVehInfo->RegisterAssets = RegisterAssets; +// pVehInfo->Initialize = Initialize; +// pVehInfo->Update = Update; +// pVehInfo->UpdateRider = UpdateRider; +#endif //QAGAME + pVehInfo->ProcessMoveCommands = ProcessMoveCommands; + pVehInfo->ProcessOrientCommands = ProcessOrientCommands; + +#ifndef QAGAME //cgame prediction attachment func + pVehInfo->AttachRiders = AttachRidersGeneric; +#endif +// pVehInfo->AttachRiders = AttachRiders; +// pVehInfo->Ghost = Ghost; +// pVehInfo->UnGhost = UnGhost; +// pVehInfo->Inhabited = Inhabited; +} + +// Following is only in game, not in namespace +#ifdef _JK2MP +#include "../namespace_end.h" +#endif + +#ifdef QAGAME +extern void G_AllocateVehicleObject(Vehicle_t **pVeh); +#endif + +#ifdef _JK2MP +#include "../namespace_begin.h" +#endif + +// Create/Allocate a new Animal Vehicle (initializing it as well). +//this is a BG function too in MP so don't un-bg-compatibilify it -rww +void G_CreateWalkerNPC( Vehicle_t **pVeh, const char *strAnimalType ) +{ + // Allocate the Vehicle. +#ifdef _JK2MP +#ifdef QAGAME + //these will remain on entities on the client once allocated because the pointer is + //never stomped. on the server, however, when an ent is freed, the entity struct is + //memset to 0, so this memory would be lost.. + G_AllocateVehicleObject(pVeh); +#else + if (!*pVeh) + { //only allocate a new one if we really have to + (*pVeh) = (Vehicle_t *) BG_Alloc( sizeof(Vehicle_t) ); + } +#endif + memset(*pVeh, 0, sizeof(Vehicle_t)); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#else + (*pVeh) = (Vehicle_t *) gi.Malloc( sizeof(Vehicle_t), TAG_G_ALLOC, qtrue ); + (*pVeh)->m_pVehicleInfo = &g_vehicleInfo[BG_VehicleGetIndex( strAnimalType )]; +#endif +} + +#ifdef _JK2MP + +#include "../namespace_end.h" + +//get rid of all the crazy defs we added for this file +#undef currentAngles +#undef currentOrigin +#undef mins +#undef maxs +#undef legsAnimTimer +#undef torsoAnimTimer +#undef bool +#undef false +#undef true + +#undef sqrtf +#undef Q_flrand + +#undef MOD_EXPLOSIVE +#endif diff --git a/code/game/ai.h b/code/game/ai.h new file mode 100644 index 0000000..4461f6e --- /dev/null +++ b/code/game/ai.h @@ -0,0 +1,126 @@ +#ifndef __AI__ +#define __AI__ + +//Distance ratings +typedef enum +{ + DIST_MELEE, + DIST_LONG, +} distance_e; + +//Attack types +typedef enum +{ + ATTACK_MELEE, + ATTACK_RANGE, +} attack_e; + +enum +{ + SQUAD_IDLE, //No target found, waiting + SQUAD_STAND_AND_SHOOT, //Standing in position and shoot (no cover) + SQUAD_RETREAT, //Running away from combat + SQUAD_COVER, //Under protective cover + SQUAD_TRANSITION, //Moving between points, not firing + SQUAD_POINT, //On point, laying down suppressive fire + SQUAD_SCOUT, //Poking out to draw enemy + NUM_SQUAD_STATES, +}; + +//sigh... had to move in here for groupInfo +typedef enum //# rank_e +{ + RANK_CIVILIAN, + RANK_CREWMAN, + RANK_ENSIGN, + RANK_LT_JG, + RANK_LT, + RANK_LT_COMM, + RANK_COMMANDER, + RANK_CAPTAIN +} rank_t; + +qboolean NPC_CheckPlayerTeamStealth( void ); + +//AI_GRENADIER +void NPC_BSGrenadier_Default( void ); + +//AI_SNIPER +void NPC_BSSniper_Default( void ); + +//AI_STORMTROOPER +void NPC_BSST_Investigate( void ); +void NPC_BSST_Default( void ); +void NPC_BSST_Sleep( void ); + +//AI_JEDI +void NPC_BSJedi_Investigate( void ); +void NPC_BSJedi_Default( void ); +void NPC_BSJedi_FollowLeader( void ); + +// AI_DROID +void NPC_BSDroid_Default( void ); + +// AI_ImperialProbe +void NPC_BSImperialProbe_Default( void ); + +// AI_atst +void NPC_BSATST_Default( void ); + +void NPC_BSInterrogator_Default( void ); + +// AI Mark 1 +void NPC_BSMark1_Default( void ); + +// AI Mark 2 +void NPC_BSMark2_Default( void ); + + +void NPC_BSMineMonster_Default( void ); +void NPC_BSHowler_Default( void ); +void NPC_BSRancor_Default( void ); + +//Utilities +//Group AI +#define MAX_FRAME_GROUPS 32 +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct AIGroupMember_s +{ + int number; + int waypoint; + int pathCostToEnemy; + int closestBuddy; +} AIGroupMember_t; + +#define MAX_GROUP_MEMBERS 32 +// !!!!!!!!!! LOADSAVE-affecting structure !!!!!!!!!! +typedef struct AIGroupInfo_s +{ + int numGroup; + qboolean processed; + team_t team; + gentity_t *enemy; + int enemyWP; + int speechDebounceTime; + int lastClearShotTime; + int lastSeenEnemyTime; + int morale; + int moraleAdjust; + int moraleDebounce; + int memberValidateTime; + int activeMemberNum; + gentity_t *commander; + vec3_t enemyLastSeenPos; + int numState[ NUM_SQUAD_STATES ]; + AIGroupMember_t member[ MAX_GROUP_MEMBERS ]; +} AIGroupInfo_t; + +int AI_GetGroupSize( vec3_t origin, int radius, team_t playerTeam, gentity_t *avoid ); +int AI_GetGroupSize2( gentity_t *ent, int radius ); + +void AI_GetGroup( gentity_t *self ); + +qboolean AI_CheckEnemyCollision( gentity_t *ent, qboolean takeEnemy ); +gentity_t *AI_DistributeAttack( gentity_t *attacker, gentity_t *enemy, team_t team, int threshold ); + +#endif //__AI__ diff --git a/code/game/ai_main.c b/code/game/ai_main.c new file mode 100644 index 0000000..cbe484b --- /dev/null +++ b/code/game/ai_main.c @@ -0,0 +1,7642 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: ai_main.c + * + * desc: Quake3 bot AI + * + * $Archive: /MissionPack/code/game/ai_main.c $ + * $Author: osman $ + * $Revision: 1.5 $ + * $Modtime: 6/06/01 1:11p $ + * $Date: 2003/03/15 23:43:59 $ + * + *****************************************************************************/ + + +#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 "w_saber.h" +// +#include "chars.h" +#include "inv.h" +#include "syn.h" + +/* +#define BOT_CTF_DEBUG 1 +*/ + +#define MAX_PATH 144 + +#define BOT_THINK_TIME 0 + +//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; +// + +//for siege: +extern int rebel_attackers; +extern int imperial_attackers; + +boteventtracker_t gBotEventTracker[MAX_CLIENTS]; + +//rww - new bot cvars.. +vmCvar_t bot_forcepowers; +vmCvar_t bot_forgimmick; +vmCvar_t bot_honorableduelacceptance; +vmCvar_t bot_pvstype; +vmCvar_t bot_normgpath; +#ifndef FINAL_BUILD +vmCvar_t bot_getinthecarrr; +#endif + +#ifdef _DEBUG +vmCvar_t bot_nogoals; +vmCvar_t bot_debugmessages; +#endif + +vmCvar_t bot_attachments; +vmCvar_t bot_camp; + +vmCvar_t bot_wp_info; +vmCvar_t bot_wp_edit; +vmCvar_t bot_wp_clearweight; +vmCvar_t bot_wp_distconnect; +vmCvar_t bot_wp_visconnect; +//end rww + +wpobject_t *flagRed; +wpobject_t *oFlagRed; +wpobject_t *flagBlue; +wpobject_t *oFlagBlue; + +gentity_t *eFlagRed; +gentity_t *droppedRedFlag; +gentity_t *eFlagBlue; +gentity_t *droppedBlueFlag; + +char *ctfStateNames[] = { + "CTFSTATE_NONE", + "CTFSTATE_ATTACKER", + "CTFSTATE_DEFENDER", + "CTFSTATE_RETRIEVAL", + "CTFSTATE_GUARDCARRIER", + "CTFSTATE_GETFLAGHOME", + "CTFSTATE_MAXCTFSTATES" +}; + +char *ctfStateDescriptions[] = { + "I'm not occupied", + "I'm attacking the enemy's base", + "I'm defending our base", + "I'm getting our flag back", + "I'm escorting our flag carrier", + "I've got the enemy's flag" +}; + +char *siegeStateDescriptions[] = { + "I'm not occupied", + "I'm attemtping to complete the current objective", + "I'm preventing the enemy from completing their objective" +}; + +char *teamplayStateDescriptions[] = { + "I'm not occupied", + "I'm following my squad commander", + "I'm assisting my commanding", + "I'm attempting to regroup and form a new squad" +}; + +void BotStraightTPOrderCheck(gentity_t *ent, int ordernum, bot_state_t *bs) +{ + switch (ordernum) + { + case 0: + if (bs->squadLeader == ent) + { + bs->teamplayState = 0; + bs->squadLeader = NULL; + } + break; + case TEAMPLAYSTATE_FOLLOWING: + bs->teamplayState = ordernum; + bs->isSquadLeader = 0; + bs->squadLeader = ent; + bs->wpDestSwitchTime = 0; + break; + case TEAMPLAYSTATE_ASSISTING: + bs->teamplayState = ordernum; + bs->isSquadLeader = 0; + bs->squadLeader = ent; + bs->wpDestSwitchTime = 0; + break; + default: + bs->teamplayState = ordernum; + break; + } +} + +void BotSelectWeapon(int client, int weapon) +{ + if (weapon <= WP_NONE) + { +// assert(0); + return; + } + trap_EA_SelectWeapon(client, weapon); +} + +void BotReportStatus(bot_state_t *bs) +{ + if (g_gametype.integer == GT_TEAM) + { + trap_EA_SayTeam(bs->client, teamplayStateDescriptions[bs->teamplayState]); + } + else if (g_gametype.integer == GT_SIEGE) + { + trap_EA_SayTeam(bs->client, siegeStateDescriptions[bs->siegeState]); + } + else if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) + { + trap_EA_SayTeam(bs->client, ctfStateDescriptions[bs->ctfState]); + } +} + +//accept a team order from a player +void BotOrder(gentity_t *ent, int clientnum, int ordernum) +{ + int stateMin = 0; + int stateMax = 0; + int i = 0; + + if (!ent || !ent->client || !ent->client->sess.teamLeader) + { + return; + } + + if (clientnum != -1 && !botstates[clientnum]) + { + return; + } + + if (clientnum != -1 && !OnSameTeam(ent, &g_entities[clientnum])) + { + return; + } + + if (g_gametype.integer != GT_CTF && g_gametype.integer != GT_CTY && g_gametype.integer != GT_SIEGE && + g_gametype.integer != GT_TEAM) + { + return; + } + + if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) + { + stateMin = CTFSTATE_NONE; + stateMax = CTFSTATE_MAXCTFSTATES; + } + else if (g_gametype.integer == GT_SIEGE) + { + stateMin = SIEGESTATE_NONE; + stateMax = SIEGESTATE_MAXSIEGESTATES; + } + else if (g_gametype.integer == GT_TEAM) + { + stateMin = TEAMPLAYSTATE_NONE; + stateMax = TEAMPLAYSTATE_MAXTPSTATES; + } + + if ((ordernum < stateMin && ordernum != -1) || ordernum >= stateMax) + { + return; + } + + if (clientnum != -1) + { + if (ordernum == -1) + { + BotReportStatus(botstates[clientnum]); + } + else + { + BotStraightTPOrderCheck(ent, ordernum, botstates[clientnum]); + botstates[clientnum]->state_Forced = ordernum; + botstates[clientnum]->chatObject = ent; + botstates[clientnum]->chatAltObject = NULL; + if (BotDoChat(botstates[clientnum], "OrderAccepted", 1)) + { + botstates[clientnum]->chatTeam = 1; + } + } + } + else + { + while (i < MAX_CLIENTS) + { + if (botstates[i] && OnSameTeam(ent, &g_entities[i])) + { + if (ordernum == -1) + { + BotReportStatus(botstates[i]); + } + else + { + BotStraightTPOrderCheck(ent, ordernum, botstates[i]); + botstates[i]->state_Forced = ordernum; + botstates[i]->chatObject = ent; + botstates[i]->chatAltObject = NULL; + if (BotDoChat(botstates[i], "OrderAccepted", 0)) + { + botstates[i]->chatTeam = 1; + } + } + } + + i++; + } + } +} + +//See if bot is mindtricked by the client in question +int BotMindTricked(int botClient, int enemyClient) +{ + forcedata_t *fd; + + if (!g_entities[enemyClient].client) + { + return 0; + } + + fd = &g_entities[enemyClient].client->ps.fd; + + if (!fd) + { + return 0; + } + + if (botClient > 47) + { + if (fd->forceMindtrickTargetIndex4 & (1 << (botClient-48))) + { + return 1; + } + } + else if (botClient > 31) + { + if (fd->forceMindtrickTargetIndex3 & (1 << (botClient-32))) + { + return 1; + } + } + else if (botClient > 15) + { + if (fd->forceMindtrickTargetIndex2 & (1 << (botClient-16))) + { + return 1; + } + } + else + { + if (fd->forceMindtrickTargetIndex & (1 << botClient)) + { + return 1; + } + } + + return 0; +} + +int BotGetWeaponRange(bot_state_t *bs); +int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent); + +void ExitLevel( void ); + +void QDECL BotAI_Print(int type, char *fmt, ...) { return; } + +qboolean WP_ForcePowerUsable( gentity_t *self, forcePowers_t forcePower ); + +int IsTeamplay(void) +{ + if ( g_gametype.integer < GT_TEAM ) + { + return 0; + } + + return 1; +} + +/* +================== +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; +} + +/* +============== +BotEntityInfo +============== +*/ +void BotEntityInfo(int entnum, aas_entityinfo_t *info) { + trap_AAS_EntityInfo(entnum, info); +} + +/* +============== +NumBots +============== +*/ +int NumBots(void) { + return numbots; +} + +/* +============== +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->currentEnemy && bs->frame_Enemy_Vis) + { + if (bs->settings.skill <= 1) + { + factor = (bs->skills.turnspeed_combat*0.4f)*bs->settings.skill; + } + else if (bs->settings.skill <= 2) + { + factor = (bs->skills.turnspeed_combat*0.6f)*bs->settings.skill; + } + else if (bs->settings.skill <= 3) + { + factor = (bs->skills.turnspeed_combat*0.8f)*bs->settings.skill; + } + else + { + factor = bs->skills.turnspeed_combat*bs->settings.skill; + } + } + else + { + factor = bs->skills.turnspeed; + } + + if (factor > 1) + { + factor = 1; + } + if (factor < 0.001) + { + factor = 0.001f; + } + + maxchange = bs->skills.maxturn; + + //if (maxchange < 240) maxchange = 240; + maxchange *= thinktime; + for (i = 0; i < 2; i++) { + 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]); + bs->viewanglespeed[i] *= 0.45 * (1 - factor); + } + if (bs->viewangles[PITCH] > 180) bs->viewangles[PITCH] -= 360; + trap_EA_View(bs->client, bs->viewangles); +} + +/* +============== +BotInputToUserCommand +============== +*/ +void BotInputToUserCommand(bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time, int useTime) { + 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_ALT_ATTACK) ucmd->buttons |= BUTTON_ALT_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_FORCEPOWER) ucmd->buttons |= BUTTON_FORCEPOWER; + + if (useTime < level.time && Q_irand(1, 10) < 5) + { //for now just hit use randomly in case there's something useable around + ucmd->buttons |= BUTTON_USE; + } +#if 0 +// Here's an interesting bit. The bots in TA used buttons to do additional gestures. +// I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2. +// We can always add some back in if we want though. + 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; +#endif //0 + + if (bi->weapon == WP_NONE) + { +#ifdef _DEBUG +// Com_Printf("WARNING: Bot tried to use WP_NONE!\n"); +#endif + bi->weapon = WP_BRYAR_PISTOL; + } + + // + 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]; + 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((int)(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, bs->noUseTime); + //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; +#ifdef _DEBUG + int start = 0; + int end = 0; +#endif + + 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, "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 + +#ifdef _DEBUG + start = trap_Milliseconds(); +#endif + StandardBotAI(bs, thinktime); +#ifdef _DEBUG + end = trap_Milliseconds(); + + trap_Cvar_Update(&bot_debugmessages); + + if (bot_debugmessages.integer) + { + Com_Printf("Single AI frametime: %i\n", (end - start)); + } +#endif + + //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_THINK_TIME * botnum / numbots; + botnum++; + } +} + +int PlayersInGame(void) +{ + int i = 0; + gentity_t *ent; + int pl = 0; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->pers.connected == CON_CONNECTED) + { + pl++; + } + + i++; + } + + return pl; +} + +/* +============== +BotAISetupClient +============== +*/ +int BotAISetupClient(int client, struct bot_settings_s *settings, qboolean restart) { + bot_state_t *bs; + + if (!botstates[client]) botstates[client] = (bot_state_t *) B_Alloc(sizeof(bot_state_t)); //G_Alloc(sizeof(bot_state_t)); + //rww - G_Alloc bad! B_Alloc good. + + memset(botstates[client], 0, sizeof(bot_state_t)); + + bs = botstates[client]; + + if (bs && bs->inuse) { + BotAI_Print(PRT_FATAL, "BotAISetupClient: client %d already setup\n", client); + return qfalse; + } + + memcpy(&bs->settings, settings, sizeof(bot_settings_t)); + + bs->client = client; //need to know the client number before doing personality stuff + + //initialize weapon weight defaults.. + bs->botWeaponWeights[WP_NONE] = 0; + bs->botWeaponWeights[WP_STUN_BATON] = 1; + bs->botWeaponWeights[WP_SABER] = 10; + bs->botWeaponWeights[WP_BRYAR_PISTOL] = 11; + bs->botWeaponWeights[WP_BLASTER] = 12; + bs->botWeaponWeights[WP_DISRUPTOR] = 13; + bs->botWeaponWeights[WP_BOWCASTER] = 14; + bs->botWeaponWeights[WP_REPEATER] = 15; + bs->botWeaponWeights[WP_DEMP2] = 16; + bs->botWeaponWeights[WP_FLECHETTE] = 17; + bs->botWeaponWeights[WP_ROCKET_LAUNCHER] = 18; + bs->botWeaponWeights[WP_THERMAL] = 14; + bs->botWeaponWeights[WP_TRIP_MINE] = 0; + bs->botWeaponWeights[WP_DET_PACK] = 0; + bs->botWeaponWeights[WP_MELEE] = 1; + + BotUtilizePersonality(bs); + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { + bs->botWeaponWeights[WP_SABER] = 13; + } + + //allocate a goal state + bs->gs = trap_BotAllocGoalState(client); + + //allocate a weapon state + bs->ws = trap_BotAllocWeaponState(); + + bs->inuse = qtrue; + bs->entitynum = client; + bs->setupcount = 4; + bs->entergame_time = FloatTime(); + bs->ms = trap_BotAllocMoveState(); + numbots++; + + //NOTE: reschedule the bot thinking + BotScheduleBotThink(); + + if (PlayersInGame()) + { //don't talk to yourself + BotDoChat(bs, "GeneralGreetings", 0); + } + + return qtrue; +} + +/* +============== +BotAIShutdownClient +============== +*/ +int BotAIShutdownClient(int client, qboolean restart) { + bot_state_t *bs; + + bs = botstates[client]; + if (!bs || !bs->inuse) { + //BotAI_Print(PRT_ERROR, "BotAIShutdownClient: client %d already shutdown\n", client); + return qfalse; + } + + trap_BotFreeMoveState(bs->ms); + //free the goal state` + trap_BotFreeGoalState(bs->gs); + //free the weapon weights + trap_BotFreeWeaponState(bs->ws); + // + //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, weaponstate; + bot_settings_t settings; + 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; + movestate = bs->ms; + goalstate = bs->gs; + weaponstate = bs->ws; + entergame_time = bs->entergame_time; + //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->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->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; + + for (i = 0; i < MAX_CLIENTS; i++) { + if (botstates[i] && botstates[i]->inuse) { + BotResetState( botstates[i] ); + botstates[i]->setupcount = 4; + } + } + + return qtrue; +} + +//rww - bot ai + +//standard visibility check +int OrgVisible(vec3_t org1, vec3_t org2, int ignore) +{ + trace_t tr; + + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1) + { + return 1; + } + + return 0; +} + +//special waypoint visibility check +int WPOrgVisible(gentity_t *bot, vec3_t org1, vec3_t org2, int ignore) +{ + trace_t tr; + gentity_t *ownent; + + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1) + { + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_PLAYERSOLID); + + if (tr.fraction != 1 && tr.entityNum != ENTITYNUM_NONE && g_entities[tr.entityNum].s.eType == ET_SPECIAL) + { + if (g_entities[tr.entityNum].parent && g_entities[tr.entityNum].parent->client) + { + ownent = g_entities[tr.entityNum].parent; + + if (OnSameTeam(bot, ownent) || bot->s.number == ownent->s.number) + { + return 1; + } + } + return 2; + } + + return 1; + } + + return 0; +} + +//visibility check with hull trace +int OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore) +{ + trace_t tr; + + if (g_RMG.integer) + { + trap_Trace(&tr, org1, NULL, NULL, org2, ignore, MASK_SOLID); + } + else + { + trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID); + } + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + + return 0; +} + +//see if there's a func_* ent under the given pos. +//kind of badly done, but this shouldn't happen +//often. +int CheckForFunc(vec3_t org, int ignore) +{ + gentity_t *fent; + vec3_t under; + trace_t tr; + + VectorCopy(org, under); + + under[2] -= 64; + + trap_Trace(&tr, org, NULL, NULL, under, ignore, MASK_SOLID); + + if (tr.fraction == 1) + { + return 0; + } + + fent = &g_entities[tr.entityNum]; + + if (!fent) + { + return 0; + } + + if (strstr(fent->classname, "func_")) + { + return 1; //there's a func brush here + } + + return 0; +} + +//perform pvs check based on rmg or not +qboolean BotPVSCheck( const vec3_t p1, const vec3_t p2 ) +{ + if (g_RMG.integer && bot_pvstype.integer) + { + vec3_t subPoint; + VectorSubtract(p1, p2, subPoint); + + if (VectorLength(subPoint) > 5000) + { + return qfalse; + } + return qtrue; + } + + return trap_InPVS(p1, p2); +} + +//get the index to the nearest visible waypoint in the global trail +int GetNearestVisibleWP(vec3_t org, int ignore) +{ + int i; + float bestdist; + float flLen; + int bestindex; + vec3_t a, mins, maxs; + + i = 0; + if (g_RMG.integer) + { + bestdist = 300; + } + else + { + bestdist = 800;//99999; + //don't trace over 800 units away to avoid GIANT HORRIBLE SPEED HITS ^_^ + } + bestindex = -1; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -1; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 1; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(org, gWPArray[i]->origin, a); + flLen = VectorLength(a); + + if (flLen < bestdist && (g_RMG.integer || BotPVSCheck(org, gWPArray[i]->origin)) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore)) + { + bestdist = flLen; + bestindex = i; + } + } + + i++; + } + + return bestindex; +} + +//wpDirection +//0 == FORWARD +//1 == BACKWARD + +//see if this is a valid waypoint to pick up in our +//current state (whatever that may be) +int PassWayCheck(bot_state_t *bs, int windex) +{ + if (!gWPArray[windex] || !gWPArray[windex]->inuse) + { //bad point index + return 0; + } + + if (g_RMG.integer) + { + if ((gWPArray[windex]->flags & WPFLAG_RED_FLAG) || + (gWPArray[windex]->flags & WPFLAG_BLUE_FLAG)) + { //red or blue flag, we'd like to get here + return 1; + } + } + + if (bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_FWD)) + { //we're not travelling in a direction on the trail that will allow us to pass this point + return 0; + } + else if (!bs->wpDirection && (gWPArray[windex]->flags & WPFLAG_ONEWAY_BACK)) + { //we're not travelling in a direction on the trail that will allow us to pass this point + return 0; + } + + if (bs->wpCurrent && gWPArray[windex]->forceJumpTo && + gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) && + bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo) + { //waypoint requires force jump level greater than our current one to pass + return 0; + } + + return 1; +} + +//tally up the distance between two waypoints +float TotalTrailDistance(int start, int end, bot_state_t *bs) +{ + int beginat; + int endat; + float distancetotal; + + distancetotal = 0; + + if (start > end) + { + beginat = end; + endat = start; + } + else + { + beginat = start; + endat = end; + } + + while (beginat < endat) + { + if (beginat >= gWPNum || !gWPArray[beginat] || !gWPArray[beginat]->inuse) + { //invalid waypoint index + return -1; + } + + if (!g_RMG.integer) + { + if ((end > start && gWPArray[beginat]->flags & WPFLAG_ONEWAY_BACK) || + (start > end && gWPArray[beginat]->flags & WPFLAG_ONEWAY_FWD)) + { //a one-way point, this means this path cannot be travelled to the final point + return -1; + } + } + +#if 0 //disabled force jump checks for now + if (gWPArray[beginat]->forceJumpTo) + { + if (gWPArray[beginat-1] && gWPArray[beginat-1]->origin[2]+64 < gWPArray[beginat]->origin[2]) + { + gdif = gWPArray[beginat]->origin[2] - gWPArray[beginat-1]->origin[2]; + } + + if (gdif) + { + if (bs && bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[beginat]->forceJumpTo) + { + return -1; + } + } + } + + if (bs->wpCurrent && gWPArray[windex]->forceJumpTo && + gWPArray[windex]->origin[2] > (bs->wpCurrent->origin[2]+64) && + bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] < gWPArray[windex]->forceJumpTo) + { + return -1; + } +#endif + + distancetotal += gWPArray[beginat]->disttonext; + + beginat++; + } + + return distancetotal; +} + +//see if there's a route shorter than our current one to get +//to the final destination we currently desire +void CheckForShorterRoutes(bot_state_t *bs, int newwpindex) +{ + float bestlen; + float checklen; + int bestindex; + int i; + int fj; + + i = 0; + fj = 0; + + if (!bs->wpDestination) + { + return; + } + + //set our traversal direction based on the index of the point + if (newwpindex < bs->wpDestination->index) + { + bs->wpDirection = 0; + } + else if (newwpindex > bs->wpDestination->index) + { + bs->wpDirection = 1; + } + + //can't switch again yet + if (bs->wpSwitchTime > level.time) + { + return; + } + + //no neighboring points to check off of + if (!gWPArray[newwpindex]->neighbornum) + { + return; + } + + //get the trail distance for our wp + bestindex = newwpindex; + bestlen = TotalTrailDistance(newwpindex, bs->wpDestination->index, bs); + + while (i < gWPArray[newwpindex]->neighbornum) + { //now go through the neighbors and check the distance to the desired point from each neighbor + checklen = TotalTrailDistance(gWPArray[newwpindex]->neighbors[i].num, bs->wpDestination->index, bs); + + if (checklen < bestlen-64 || bestlen == -1) + { //this path covers less distance, let's take it instead + if (bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION] >= gWPArray[newwpindex]->neighbors[i].forceJumpTo) + { + bestlen = checklen; + bestindex = gWPArray[newwpindex]->neighbors[i].num; + + if (gWPArray[newwpindex]->neighbors[i].forceJumpTo) + { + fj = gWPArray[newwpindex]->neighbors[i].forceJumpTo; + } + else + { + fj = 0; + } + } + } + + i++; + } + + if (bestindex != newwpindex && bestindex != -1) + { //we found a path we want to switch to, let's do it + bs->wpCurrent = gWPArray[bestindex]; + bs->wpSwitchTime = level.time + 3000; + + if (fj) + { //do we have to force jump to get to this neighbor? +#ifndef FORCEJUMP_INSTANTMETHOD + bs->forceJumpChargeTime = level.time + 1000; + bs->beStill = level.time + 1000; + bs->forceJumping = bs->forceJumpChargeTime; +#else + bs->beStill = level.time + 500; + bs->jumpTime = level.time + fj*1200; + bs->jDelay = level.time + 200; + bs->forceJumping = bs->jumpTime; +#endif + } + } +} + +//check for flags on the waypoint we're currently travelling to +//and perform the desired behavior based on the flag +void WPConstantRoutine(bot_state_t *bs) +{ + if (!bs->wpCurrent) + { + return; + } + + if (bs->wpCurrent->flags & WPFLAG_DUCK) + { //duck while travelling to this point + bs->duckTime = level.time + 100; + } + +#ifndef FORCEJUMP_INSTANTMETHOD + if (bs->wpCurrent->flags & WPFLAG_JUMP) + { //jump while travelling to this point + float heightDif = (bs->wpCurrent->origin[2] - bs->origin[2]+16); + + if (bs->origin[2]+16 >= bs->wpCurrent->origin[2]) + { //don't need to jump, we're already higher than this point + heightDif = 0; + } + + if (heightDif > 40 && (bs->cur_ps.fd.forcePowersKnown & (1 << FP_LEVITATION)) && (bs->cur_ps.fd.forceJumpCharge < (forceJumpStrength[bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION]]-100) || bs->cur_ps.groundEntityNum == ENTITYNUM_NONE)) + { //alright, let's jump + bs->forceJumpChargeTime = level.time + 1000; + if (bs->cur_ps.groundEntityNum != ENTITYNUM_NONE && bs->jumpPrep < (level.time-300)) + { + bs->jumpPrep = level.time + 700; + } + bs->beStill = level.time + 300; + bs->jumpTime = 0; + + if (bs->wpSeenTime < (level.time + 600)) + { + bs->wpSeenTime = level.time + 600; + } + } + else if (heightDif > 64 && !(bs->cur_ps.fd.forcePowersKnown & (1 << FP_LEVITATION))) + { //this point needs force jump to reach and we don't have it + //Kill the current point and turn around + bs->wpCurrent = NULL; + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + + return; + } + } +#endif + + if (bs->wpCurrent->forceJumpTo) + { +#ifdef FORCEJUMP_INSTANTMETHOD + if (bs->origin[2]+16 < bs->wpCurrent->origin[2]) + { + bs->jumpTime = level.time + 100; + } +#else + float heightDif = (bs->wpCurrent->origin[2] - bs->origin[2]+16); + + if (bs->origin[2]+16 >= bs->wpCurrent->origin[2]) + { //then why exactly would we be force jumping? + heightDif = 0; + } + + if (bs->cur_ps.fd.forceJumpCharge < (forceJumpStrength[bs->cur_ps.fd.forcePowerLevel[FP_LEVITATION]]-100)) + { + bs->forceJumpChargeTime = level.time + 200; + } +#endif + } +} + +//check if our ctf state is to guard the base +qboolean BotCTFGuardDuty(bot_state_t *bs) +{ + if (g_gametype.integer != GT_CTF && + g_gametype.integer != GT_CTY) + { + return qfalse; + } + + if (bs->ctfState == CTFSTATE_DEFENDER) + { + return qtrue; + } + + return qfalse; +} + +//when we reach the waypoint we are travelling to, +//this function will be called. We will perform any +//checks for flags on the current wp and activate +//any "touch" events based on that. +void WPTouchRoutine(bot_state_t *bs) +{ + int lastNum; + + if (!bs->wpCurrent) + { + return; + } + + bs->wpTravelTime = level.time + 10000; + + if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC) + { //don't try to use any nearby map objects for a little while + bs->noUseTime = level.time + 4000; + } + +#ifdef FORCEJUMP_INSTANTMETHOD + if ((bs->wpCurrent->flags & WPFLAG_JUMP) && bs->wpCurrent->forceJumpTo) + { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is + //handled elsewhere. + bs->jumpTime = level.time + 100; + } +#else + if ((bs->wpCurrent->flags & WPFLAG_JUMP) && !bs->wpCurrent->forceJumpTo) + { //jump if we're flagged to but not if this indicates a force jump point. Force jumping is + //handled elsewhere. + bs->jumpTime = level.time + 100; + } +#endif + + if (bs->isCamper && bot_camp.integer && (BotIsAChickenWuss(bs) || BotCTFGuardDuty(bs) || bs->isCamper == 2) && ((bs->wpCurrent->flags & WPFLAG_SNIPEORCAMP) || (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND)) && + bs->cur_ps.weapon != WP_SABER && bs->cur_ps.weapon != WP_MELEE && bs->cur_ps.weapon != WP_STUN_BATON) + { //if we're a camper and a chicken then camp + if (bs->wpDirection) + { + lastNum = bs->wpCurrent->index+1; + } + else + { + lastNum = bs->wpCurrent->index-1; + } + + if (gWPArray[lastNum] && gWPArray[lastNum]->inuse && gWPArray[lastNum]->index && bs->isCamping < level.time) + { + bs->isCamping = level.time + rand()%15000 + 30000; + bs->wpCamping = bs->wpCurrent; + bs->wpCampingTo = gWPArray[lastNum]; + + if (bs->wpCurrent->flags & WPFLAG_SNIPEORCAMPSTAND) + { + bs->campStanding = qtrue; + } + else + { + bs->campStanding = qfalse; + } + } + + } + else if ((bs->cur_ps.weapon == WP_SABER || bs->cur_ps.weapon == WP_STUN_BATON || bs->cur_ps.weapon == WP_MELEE) && + bs->isCamping > level.time) + { //don't snipe/camp with a melee weapon, that would be silly + bs->isCamping = 0; + bs->wpCampingTo = NULL; + bs->wpCamping = NULL; + } + + if (bs->wpDestination) + { + if (bs->wpCurrent->index == bs->wpDestination->index) + { + bs->wpDestination = NULL; + + if (bs->runningLikeASissy) + { //this obviously means we're scared and running, so we'll want to keep our navigational priorities less delayed + bs->destinationGrabTime = level.time + 500; + } + else + { + bs->destinationGrabTime = level.time + 3500; + } + } + else + { + CheckForShorterRoutes(bs, bs->wpCurrent->index); + } + } +} + +//could also slowly lerp toward, but for now +//just copying straight over. +void MoveTowardIdealAngles(bot_state_t *bs) +{ + VectorCopy(bs->goalAngles, bs->ideal_viewangles); +} + +#define BOT_STRAFE_AVOIDANCE + +#ifdef BOT_STRAFE_AVOIDANCE +#define STRAFEAROUND_RIGHT 1 +#define STRAFEAROUND_LEFT 2 + +//do some trace checks for strafing to get an idea of where we +//are and if we should move to avoid obstacles. +int BotTrace_Strafe(bot_state_t *bs, vec3_t traceto) +{ + vec3_t playerMins = {-15, -15, /*DEFAULT_MINS_2*/-8}; + vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + vec3_t from, to; + vec3_t dirAng, dirDif; + vec3_t forward, right; + trace_t tr; + + if (bs->cur_ps.groundEntityNum == ENTITYNUM_NONE) + { //don't do this in the air, it can be.. dangerous. + return 0; + } + + VectorSubtract(traceto, bs->origin, dirAng); + VectorNormalize(dirAng); + vectoangles(dirAng, dirAng); + + if (AngleDifference(bs->viewangles[YAW], dirAng[YAW]) > 60 || + AngleDifference(bs->viewangles[YAW], dirAng[YAW]) < -60) + { //If we aren't facing the direction we're going here, then we've got enough excuse to be too stupid to strafe around anyway + return 0; + } + + VectorCopy(bs->origin, from); + VectorCopy(traceto, to); + + VectorSubtract(to, from, dirDif); + VectorNormalize(dirDif); + vectoangles(dirDif, dirDif); + + AngleVectors(dirDif, forward, 0, 0); + + to[0] = from[0] + forward[0]*32; + to[1] = from[1] + forward[1]*32; + to[2] = from[2] + forward[2]*32; + + trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return 0; + } + + AngleVectors(dirAng, 0, right, 0); + + from[0] += right[0]*32; + from[1] += right[1]*32; + from[2] += right[2]*16; + + to[0] += right[0]*32; + to[1] += right[1]*32; + to[2] += right[2]*32; + + trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return STRAFEAROUND_RIGHT; + } + + from[0] -= right[0]*64; + from[1] -= right[1]*64; + from[2] -= right[2]*64; + + to[0] -= right[0]*64; + to[1] -= right[1]*64; + to[2] -= right[2]*64; + + trap_Trace(&tr, from, playerMins, playerMaxs, to, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return STRAFEAROUND_LEFT; + } + + return 0; +} +#endif + +//Similar to the trace check, but we want to trace to see +//if there's anything we can jump over. +int BotTrace_Jump(bot_state_t *bs, vec3_t traceto) +{ + vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod; + trace_t tr; + int orTr; + + VectorSubtract(traceto, bs->origin, a); + vectoangles(a, a); + + AngleVectors(a, fwd, NULL, NULL); + + traceto_mod[0] = bs->origin[0] + fwd[0]*4; + traceto_mod[1] = bs->origin[1] + fwd[1]*4; + traceto_mod[2] = bs->origin[2] + fwd[2]*4; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -18; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + return 0; + } + + orTr = tr.entityNum; + + VectorCopy(bs->origin, tracefrom_mod); + + tracefrom_mod[2] += 41; + traceto_mod[2] += 41; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 8; + + trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction == 1) + { + if (orTr >= 0 && orTr < MAX_CLIENTS && botstates[orTr] && botstates[orTr]->jumpTime > level.time) + { + return 0; //so bots don't try to jump over each other at the same time + } + + if (bs->currentEnemy && bs->currentEnemy->s.number == orTr && (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER || BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE)) + { + return 0; + } + + return 1; + } + + return 0; +} + +//And yet another check to duck under any obstacles. +int BotTrace_Duck(bot_state_t *bs, vec3_t traceto) +{ + vec3_t mins, maxs, a, fwd, traceto_mod, tracefrom_mod; + trace_t tr; + + VectorSubtract(traceto, bs->origin, a); + vectoangles(a, a); + + AngleVectors(a, fwd, NULL, NULL); + + traceto_mod[0] = bs->origin[0] + fwd[0]*4; + traceto_mod[1] = bs->origin[1] + fwd[1]*4; + traceto_mod[2] = bs->origin[2] + fwd[2]*4; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -23; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 8; + + trap_Trace(&tr, bs->origin, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction != 1) + { + return 0; + } + + VectorCopy(bs->origin, tracefrom_mod); + + tracefrom_mod[2] += 31;//33; + traceto_mod[2] += 31;//33; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + trap_Trace(&tr, tracefrom_mod, mins, maxs, traceto_mod, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction != 1) + { + return 1; + } + + return 0; +} + +//check of the potential enemy is a valid one +int PassStandardEnemyChecks(bot_state_t *bs, gentity_t *en) +{ + if (!bs || !en) + { //shouldn't happen + return 0; + } + + if (!en->client) + { //not a client, don't care about him + return 0; + } + + if (en->health < 1) + { //he's already dead + return 0; + } + + if (!en->takedamage) + { //a client that can't take damage? + return 0; + } + + if (bs->doingFallback && + (gLevelFlags & LEVELFLAG_IGNOREINFALLBACK)) + { //we screwed up in our nav routines somewhere and we've reverted to a fallback state to + //try to get back on the trail. If the level specifies to ignore enemies in this state, + //then ignore them. + return 0; + } + + if (en->client->ps.pm_type == PM_INTERMISSION || + en->client->ps.pm_type == PM_SPECTATOR || + en->client->sess.sessionTeam == TEAM_SPECTATOR) + { //don't attack spectators + return 0; + } + + if (!en->client->pers.connected) + { //a "zombie" client? + return 0; + } + + if (!en->s.solid) + { //shouldn't happen + return 0; + } + + if (bs->client == en->s.number) + { //don't attack yourself + return 0; + } + + if (OnSameTeam(&g_entities[bs->client], en)) + { //don't attack teammates + return 0; + } + + if (BotMindTricked(bs->client, en->s.number)) + { + if (bs->currentEnemy && bs->currentEnemy->s.number == en->s.number) + { //if mindtricked by this enemy, then be less "aware" of them, even though + //we know they're there. + vec3_t vs; + float vLen = 0; + + VectorSubtract(bs->origin, en->client->ps.origin, vs); + vLen = VectorLength(vs); + + if (vLen > 64 /*&& (level.time - en->client->dangerTime) > 150*/) + { + return 0; + } + } + } + + if (en->client->ps.duelInProgress && en->client->ps.duelIndex != bs->client) + { //don't attack duelists unless you're dueling them + return 0; + } + + if (bs->cur_ps.duelInProgress && en->s.number != bs->cur_ps.duelIndex) + { //ditto, the other way around + return 0; + } + + if (g_gametype.integer == GT_JEDIMASTER && !en->client->ps.isJediMaster && !bs->cur_ps.isJediMaster) + { //rules for attacking non-JM in JM mode + vec3_t vs; + float vLen = 0; + + if (!g_friendlyFire.integer) + { //can't harm non-JM in JM mode if FF is off + return 0; + } + + VectorSubtract(bs->origin, en->client->ps.origin, vs); + vLen = VectorLength(vs); + + if (vLen > 350) + { + return 0; + } + } + + return 1; +} + +//Notifies the bot that he has taken damage from "attacker". +void BotDamageNotification(gclient_t *bot, gentity_t *attacker) +{ + bot_state_t *bs; + bot_state_t *bs_a; + int i; + + if (!bot || !attacker || !attacker->client) + { + return; + } + + if (bot->ps.clientNum >= MAX_CLIENTS) + { //an NPC.. do nothing for them. + return; + } + + if (attacker->s.number >= MAX_CLIENTS) + { //if attacker is an npc also don't care I suppose. + return; + } + + bs_a = botstates[attacker->s.number]; + + if (bs_a) + { //if the client attacking us is a bot as well + bs_a->lastAttacked = &g_entities[bot->ps.clientNum]; + i = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && + i != bs_a->client && + botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum]) + { + botstates[i]->lastAttacked = NULL; + } + + i++; + } + } + else //got attacked by a real client, so no one gets rights to lastAttacked + { + i = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && + botstates[i]->lastAttacked == &g_entities[bot->ps.clientNum]) + { + botstates[i]->lastAttacked = NULL; + } + + i++; + } + } + + bs = botstates[bot->ps.clientNum]; + + if (!bs) + { + return; + } + + bs->lastHurt = attacker; + + if (bs->currentEnemy) + { //we don't care about the guy attacking us if we have an enemy already + return; + } + + if (!PassStandardEnemyChecks(bs, attacker)) + { //the person that hurt us is not a valid enemy + return; + } + + if (PassLovedOneCheck(bs, attacker)) + { //the person that hurt us is the one we love! + bs->currentEnemy = attacker; + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } +} + +//perform cheap "hearing" checks based on the event catching +//system +int BotCanHear(bot_state_t *bs, gentity_t *en, float endist) +{ + float minlen; + + if (!en || !en->client) + { + return 0; + } + + if (en && en->client && en->client->ps.otherSoundTime > level.time) + { //they made a noise in recent time + minlen = en->client->ps.otherSoundLen; + goto checkStep; + } + + if (en && en->client && en->client->ps.footstepTime > level.time) + { //they made a footstep + minlen = 256; + goto checkStep; + } + + if (gBotEventTracker[en->s.number].eventTime < level.time) + { //no recent events to check + return 0; + } + + switch(gBotEventTracker[en->s.number].events[gBotEventTracker[en->s.number].eventSequence & (MAX_PS_EVENTS-1)]) + { //did the last event contain a sound? + case EV_GLOBAL_SOUND: + minlen = 256; + break; + case EV_FIRE_WEAPON: + case EV_ALT_FIRE: + case EV_SABER_ATTACK: + minlen = 512; + break; + case EV_STEP_4: + case EV_STEP_8: + case EV_STEP_12: + case EV_STEP_16: + case EV_FOOTSTEP: + case EV_FOOTSTEP_METAL: + case EV_FOOTWADE: + minlen = 256; + break; + case EV_JUMP: + case EV_ROLL: + minlen = 256; + break; + default: + minlen = 999999; + break; + } +checkStep: + if (BotMindTricked(bs->client, en->s.number)) + { //if mindtricked by this person, cut down on the minlen so they can't "hear" as well + minlen /= 4; + } + + if (endist <= minlen) + { //we heard it + return 1; + } + + return 0; +} + +//check for new events +void UpdateEventTracker(void) +{ + int i; + + i = 0; + + while (i < MAX_CLIENTS) + { + if (gBotEventTracker[i].eventSequence != level.clients[i].ps.eventSequence) + { //updated event + gBotEventTracker[i].eventSequence = level.clients[i].ps.eventSequence; + gBotEventTracker[i].events[0] = level.clients[i].ps.events[0]; + gBotEventTracker[i].events[1] = level.clients[i].ps.events[1]; + gBotEventTracker[i].eventTime = level.time + 0.5; + } + + i++; + } +} + +//check if said angles are within our fov +int 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 0; + } + } + else + { + if (diff < -fov * 0.5) + { + return 0; + } + } + } + return 1; +} + +//We cannot hurt the ones we love. Unless of course this +//function says we can. +int PassLovedOneCheck(bot_state_t *bs, gentity_t *ent) +{ + int i; + bot_state_t *loved; + + if (!bs->lovednum) + { + return 1; + } + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { //There is no love in 1-on-1 + return 1; + } + + i = 0; + + if (!botstates[ent->s.number]) + { //not a bot + return 1; + } + + if (!bot_attachments.integer) + { + return 1; + } + + loved = botstates[ent->s.number]; + + while (i < bs->lovednum) + { + if (strcmp(level.clients[loved->client].pers.netname, bs->loved[i].name) == 0) + { + if (!IsTeamplay() && bs->loved[i].level < 2) + { //if FFA and level of love is not greater than 1, just don't care + return 1; + } + else if (IsTeamplay() && !OnSameTeam(&g_entities[bs->client], &g_entities[loved->client]) && bs->loved[i].level < 2) + { //is teamplay, but not on same team and level < 2 + return 1; + } + else + { + return 0; + } + } + + i++; + } + + return 1; +} + +qboolean G_ThereIsAMaster(void); + +//standard check to find a new enemy. +int ScanForEnemies(bot_state_t *bs) +{ + vec3_t a; + float distcheck; + float closest; + int bestindex; + int i; + float hasEnemyDist = 0; + qboolean noAttackNonJM = qfalse; + + closest = 999999; + i = 0; + bestindex = -1; + + if (bs->currentEnemy) + { //only switch to a new enemy if he's significantly closer + hasEnemyDist = bs->frame_Enemy_Len; + } + + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->ps.isJediMaster) + { //The Jedi Master must die. + return -1; + } + + if (g_gametype.integer == GT_JEDIMASTER) + { + if (G_ThereIsAMaster() && !bs->cur_ps.isJediMaster) + { //if friendly fire is on in jedi master we can attack people that bug us + if (!g_friendlyFire.integer) + { + noAttackNonJM = qtrue; + } + else + { + closest = 128; //only get mad at people if they get close enough to you to anger you, or hurt you + } + } + } + + while (i <= MAX_CLIENTS) + { + if (i != bs->client && g_entities[i].client && !OnSameTeam(&g_entities[bs->client], &g_entities[i]) && PassStandardEnemyChecks(bs, &g_entities[i]) && BotPVSCheck(g_entities[i].client->ps.origin, bs->eye) && PassLovedOneCheck(bs, &g_entities[i])) + { + VectorSubtract(g_entities[i].client->ps.origin, bs->eye, a); + distcheck = VectorLength(a); + vectoangles(a, a); + + if (g_entities[i].client->ps.isJediMaster) + { //make us think the Jedi Master is close so we'll attack him above all + distcheck = 1; + } + + if (distcheck < closest && ((InFieldOfVision(bs->viewangles, 90, a) && !BotMindTricked(bs->client, i)) || BotCanHear(bs, &g_entities[i], distcheck)) && OrgVisible(bs->eye, g_entities[i].client->ps.origin, -1)) + { + if (BotMindTricked(bs->client, i)) + { + if (distcheck < 256 || (level.time - g_entities[i].client->dangerTime) < 100) + { + if (!hasEnemyDist || distcheck < (hasEnemyDist - 128)) + { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out + if (!noAttackNonJM || g_entities[i].client->ps.isJediMaster) + { + closest = distcheck; + bestindex = i; + } + } + } + } + else + { + if (!hasEnemyDist || distcheck < (hasEnemyDist - 128)) + { //if we have an enemy, only switch to closer if he is 128+ closer to avoid flipping out + if (!noAttackNonJM || g_entities[i].client->ps.isJediMaster) + { + closest = distcheck; + bestindex = i; + } + } + } + } + } + i++; + } + + return bestindex; +} + +int WaitingForNow(bot_state_t *bs, vec3_t goalpos) +{ //checks if the bot is doing something along the lines of waiting for an elevator to raise up + vec3_t xybot, xywp, a; + + if (!bs->wpCurrent) + { + return 0; + } + + if ((int)goalpos[0] != (int)bs->wpCurrent->origin[0] || + (int)goalpos[1] != (int)bs->wpCurrent->origin[1] || + (int)goalpos[2] != (int)bs->wpCurrent->origin[2]) + { + return 0; + } + + VectorCopy(bs->origin, xybot); + VectorCopy(bs->wpCurrent->origin, xywp); + + xybot[2] = 0; + xywp[2] = 0; + + VectorSubtract(xybot, xywp, a); + + if (VectorLength(a) < 16 && bs->frame_Waypoint_Len > 100) + { + if (CheckForFunc(bs->origin, bs->client)) + { + return 1; //we're probably standing on an elevator and riding up/down. Or at least we hope so. + } + } + else if (VectorLength(a) < 64 && bs->frame_Waypoint_Len > 64 && + CheckForFunc(bs->origin, bs->client)) + { + bs->noUseTime = level.time + 2000; + } + + return 0; +} + +//get an ideal distance for us to be at in relation to our opponent +//based on our weapon. +int BotGetWeaponRange(bot_state_t *bs) +{ + switch (bs->cur_ps.weapon) + { + case WP_STUN_BATON: + case WP_MELEE: + return BWEAPONRANGE_MELEE; + case WP_SABER: + return BWEAPONRANGE_SABER; + case WP_BRYAR_PISTOL: + return BWEAPONRANGE_MID; + case WP_BLASTER: + return BWEAPONRANGE_MID; + case WP_DISRUPTOR: + return BWEAPONRANGE_MID; + case WP_BOWCASTER: + return BWEAPONRANGE_LONG; + case WP_REPEATER: + return BWEAPONRANGE_MID; + case WP_DEMP2: + return BWEAPONRANGE_LONG; + case WP_FLECHETTE: + return BWEAPONRANGE_LONG; + case WP_ROCKET_LAUNCHER: + return BWEAPONRANGE_LONG; + case WP_THERMAL: + return BWEAPONRANGE_LONG; + case WP_TRIP_MINE: + return BWEAPONRANGE_LONG; + case WP_DET_PACK: + return BWEAPONRANGE_LONG; + default: + return BWEAPONRANGE_MID; + } +} + +//see if we want to run away from the opponent for whatever reason +int BotIsAChickenWuss(bot_state_t *bs) +{ + int bWRange; + + if (gLevelFlags & LEVELFLAG_IMUSTNTRUNAWAY) + { //The level says we mustn't run away! + return 0; + } + + if (g_gametype.integer == GT_SINGLE_PLAYER) + { //"coop" (not really) + return 0; + } + + if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster) + { //Then you may know no fear. + //Well, unless he's strong. + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->ps.isJediMaster && + bs->currentEnemy->health > 40 && + bs->cur_ps.weapon < WP_ROCKET_LAUNCHER) + { //explosive weapons are most effective against the Jedi Master + goto jmPass; + } + return 0; + } + + if (g_gametype.integer == GT_CTF && bs->currentEnemy && bs->currentEnemy->client) + { + if (bs->currentEnemy->client->ps.powerups[PW_REDFLAG] || + bs->currentEnemy->client->ps.powerups[PW_BLUEFLAG]) + { //don't be afraid of flag carriers, they must die! + return 0; + } + } + +jmPass: + if (bs->chickenWussCalculationTime > level.time) + { + return 2; //don't want to keep going between two points... + } + + if (bs->cur_ps.fd.forcePowersActive & (1 << FP_RAGE)) + { //don't run while raging + return 0; + } + + if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster) + { //be frightened of the jedi master? I guess in this case. + return 1; + } + + bs->chickenWussCalculationTime = level.time + MAX_CHICKENWUSS_TIME; + + if (g_entities[bs->client].health < BOT_RUN_HEALTH) + { //we're low on health, let's get away + return 1; + } + + bWRange = BotGetWeaponRange(bs); + + if (bWRange == BWEAPONRANGE_MELEE || bWRange == BWEAPONRANGE_SABER) + { + if (bWRange != BWEAPONRANGE_SABER || !bs->saberSpecialist) + { //run away if we're using melee, or if we're using a saber and not a "saber specialist" + return 1; + } + } + + if (bs->cur_ps.weapon == WP_BRYAR_PISTOL) + { //the bryar is a weak weapon, so just try to find a new one if it's what you're having to use + return 1; + } + + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->ps.weapon == WP_SABER && + bs->frame_Enemy_Len < 512 && bs->cur_ps.weapon != WP_SABER) + { //if close to an enemy with a saber and not using a saber, then try to back off + return 1; + } + + if ((level.time-bs->cur_ps.electrifyTime) < 16000) + { //lightning is dangerous. + return 1; + } + + //didn't run, reset the timer + bs->chickenWussCalculationTime = 0; + + return 0; +} + +//look for "bad things". bad things include detpacks, thermal detonators, +//and other dangerous explodey items. +gentity_t *GetNearestBadThing(bot_state_t *bs) +{ + int i = 0; + float glen; + vec3_t hold; + int bestindex = 0; + float bestdist = 800; //if not within a radius of 800, it's no threat anyway + int foundindex = 0; + float factor = 0; + gentity_t *ent; + trace_t tr; + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + if ( (ent && + !ent->client && + ent->inuse && + ent->damage && + /*(ent->s.weapon == WP_THERMAL || ent->s.weapon == WP_FLECHETTE)*/ + ent->s.weapon && + ent->splashDamage) || + (ent && + ent->genericValue5 == 1000 && + ent->inuse && + ent->health > 0 && + ent->genericValue3 != bs->client && + g_entities[ent->genericValue3].client && !OnSameTeam(&g_entities[bs->client], &g_entities[ent->genericValue3])) ) + { //try to escape from anything with a non-0 s.weapon and non-0 damage. This hopefully only means dangerous projectiles. + //Or a sentry gun if bolt_Head == 1000. This is a terrible hack, yes. + VectorSubtract(bs->origin, ent->r.currentOrigin, hold); + glen = VectorLength(hold); + + if (ent->s.weapon != WP_THERMAL && ent->s.weapon != WP_FLECHETTE && + ent->s.weapon != WP_DET_PACK && ent->s.weapon != WP_TRIP_MINE) + { + factor = 0.5; + + if (ent->s.weapon && glen <= 256 && bs->settings.skill > 2) + { //it's a projectile so push it away + bs->doForcePush = level.time + 700; + //G_Printf("PUSH PROJECTILE\n"); + } + } + else + { + factor = 1; + } + + if (ent->s.weapon == WP_ROCKET_LAUNCHER && + (ent->r.ownerNum == bs->client || + (ent->r.ownerNum > 0 && ent->r.ownerNum < MAX_CLIENTS && + g_entities[ent->r.ownerNum].client && OnSameTeam(&g_entities[bs->client], &g_entities[ent->r.ownerNum]))) ) + { //don't be afraid of your own rockets or your teammates' rockets + factor = 0; + } + + if (glen < bestdist*factor && BotPVSCheck(bs->origin, ent->s.pos.trBase)) + { + trap_Trace(&tr, bs->origin, NULL, NULL, ent->s.pos.trBase, bs->client, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == ent->s.number) + { + bestindex = i; + bestdist = glen; + foundindex = 1; + } + } + } + + if (ent && !ent->client && ent->inuse && ent->damage && ent->s.weapon && ent->r.ownerNum < MAX_CLIENTS && ent->r.ownerNum >= 0) + { //if we're in danger of a projectile belonging to someone and don't have an enemy, set the enemy to them + gentity_t *projOwner = &g_entities[ent->r.ownerNum]; + + if (projOwner && projOwner->inuse && projOwner->client) + { + if (!bs->currentEnemy) + { + if (PassStandardEnemyChecks(bs, projOwner)) + { + if (PassLovedOneCheck(bs, projOwner)) + { + VectorSubtract(bs->origin, ent->r.currentOrigin, hold); + glen = VectorLength(hold); + + if (glen < 512) + { + bs->currentEnemy = projOwner; + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } + } + } + } + } + } + + i++; + } + + if (foundindex) + { + bs->dontGoBack = level.time + 1500; + return &g_entities[bestindex]; + } + else + { + return NULL; + } +} + +//Keep our CTF priorities on defending our team's flag +int BotDefendFlag(bot_state_t *bs) +{ + wpobject_t *flagPoint; + vec3_t a; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + flagPoint = flagRed; + } + else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE) + { + flagPoint = flagBlue; + } + else + { + return 0; + } + + if (!flagPoint) + { + return 0; + } + + VectorSubtract(bs->origin, flagPoint->origin, a); + + if (VectorLength(a) > BASE_GUARD_DISTANCE) + { + bs->wpDestination = flagPoint; + } + + return 1; +} + +//Keep our CTF priorities on getting the other team's flag +int BotGetEnemyFlag(bot_state_t *bs) +{ + wpobject_t *flagPoint; + vec3_t a; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + flagPoint = flagBlue; + } + else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE) + { + flagPoint = flagRed; + } + else + { + return 0; + } + + if (!flagPoint) + { + return 0; + } + + VectorSubtract(bs->origin, flagPoint->origin, a); + + if (VectorLength(a) > BASE_GETENEMYFLAG_DISTANCE) + { + bs->wpDestination = flagPoint; + } + + return 1; +} + +//Our team's flag is gone, so try to get it back +int BotGetFlagBack(bot_state_t *bs) +{ + int i = 0; + int myFlag = 0; + int foundCarrier = 0; + int tempInt = 0; + gentity_t *ent = NULL; + vec3_t usethisvec; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + myFlag = PW_REDFLAG; + } + else + { + myFlag = PW_BLUEFLAG; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent)) + { + foundCarrier = 1; + break; + } + + i++; + } + + if (!foundCarrier) + { + return 0; + } + + if (!ent) + { + return 0; + } + + if (bs->wpDestSwitchTime < level.time) + { + if (ent->client) + { + VectorCopy(ent->client->ps.origin, usethisvec); + } + else + { + VectorCopy(ent->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000); + } + } + + return 1; +} + +//Someone else on our team has the enemy flag, so try to get +//to their assistance +int BotGuardFlagCarrier(bot_state_t *bs) +{ + int i = 0; + int enemyFlag = 0; + int foundCarrier = 0; + int tempInt = 0; + gentity_t *ent = NULL; + vec3_t usethisvec; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + enemyFlag = PW_BLUEFLAG; + } + else + { + enemyFlag = PW_REDFLAG; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent)) + { + foundCarrier = 1; + break; + } + + i++; + } + + if (!foundCarrier) + { + return 0; + } + + if (!ent) + { + return 0; + } + + if (bs->wpDestSwitchTime < level.time) + { + if (ent->client) + { + VectorCopy(ent->client->ps.origin, usethisvec); + } + else + { + VectorCopy(ent->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000); + } + } + + return 1; +} + +//We have the flag, let's get it home. +int BotGetFlagHome(bot_state_t *bs) +{ + wpobject_t *flagPoint; + vec3_t a; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + flagPoint = flagRed; + } + else if (level.clients[bs->client].sess.sessionTeam == TEAM_BLUE) + { + flagPoint = flagBlue; + } + else + { + return 0; + } + + if (!flagPoint) + { + return 0; + } + + VectorSubtract(bs->origin, flagPoint->origin, a); + + if (VectorLength(a) > BASE_FLAGWAIT_DISTANCE) + { + bs->wpDestination = flagPoint; + } + + return 1; +} + +void GetNewFlagPoint(wpobject_t *wp, gentity_t *flagEnt, int team) +{ //get the nearest possible waypoint to the flag since it's not in its original position + int i = 0; + vec3_t a, mins, maxs; + float bestdist; + float testdist; + int bestindex = 0; + int foundindex = 0; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -5; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 5; + + VectorSubtract(wp->origin, flagEnt->s.pos.trBase, a); + + bestdist = VectorLength(a); + + if (bestdist <= WP_KEEP_FLAG_DIST) + { + trap_Trace(&tr, wp->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID); + + if (tr.fraction == 1) + { //this point is good + return; + } + } + + while (i < gWPNum) + { + VectorSubtract(gWPArray[i]->origin, flagEnt->s.pos.trBase, a); + testdist = VectorLength(a); + + if (testdist < bestdist) + { + trap_Trace(&tr, gWPArray[i]->origin, mins, maxs, flagEnt->s.pos.trBase, flagEnt->s.number, MASK_SOLID); + + if (tr.fraction == 1) + { + foundindex = 1; + bestindex = i; + bestdist = testdist; + } + } + + i++; + } + + if (foundindex) + { + if (team == TEAM_RED) + { + flagRed = gWPArray[bestindex]; + } + else + { + flagBlue = gWPArray[bestindex]; + } + } +} + +//See if our CTF state should take priority in our nav routines +int CTFTakesPriority(bot_state_t *bs) +{ + gentity_t *ent = NULL; + int enemyFlag = 0; + int myFlag = 0; + int enemyHasOurFlag = 0; + int weHaveEnemyFlag = 0; + int numOnMyTeam = 0; + int numOnEnemyTeam = 0; + int numAttackers = 0; + int numDefenders = 0; + int i = 0; + int idleWP; + int dosw = 0; + wpobject_t *dest_sw = NULL; +#ifdef BOT_CTF_DEBUG + vec3_t t; + + G_Printf("CTFSTATE: %s\n", ctfStateNames[bs->ctfState]); +#endif + + if (g_gametype.integer != GT_CTF && g_gametype.integer != GT_CTY) + { + return 0; + } + + if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME) + { //get the nearest weapon laying around base before heading off for battle + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + if (bs->wpDestSwitchTime < level.time) + { + bs->wpDestination = gWPArray[idleWP]; + } + return 1; + } + } + else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_CTF && + bs->wpDestination && bs->wpDestination->weight) + { + dest_sw = bs->wpDestination; + dosw = 1; + } + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + myFlag = PW_REDFLAG; + } + else + { + myFlag = PW_BLUEFLAG; + } + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + enemyFlag = PW_BLUEFLAG; + } + else + { + enemyFlag = PW_REDFLAG; + } + + if (!flagRed || !flagBlue || + !flagRed->inuse || !flagBlue->inuse || + !eFlagRed || !eFlagBlue) + { + return 0; + } + +#ifdef BOT_CTF_DEBUG + VectorCopy(flagRed->origin, t); + t[2] += 128; + G_TestLine(flagRed->origin, t, 0x0000ff, 500); + + VectorCopy(flagBlue->origin, t); + t[2] += 128; + G_TestLine(flagBlue->origin, t, 0x0000ff, 500); +#endif + + if (droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM)) + { + GetNewFlagPoint(flagRed, droppedRedFlag, TEAM_RED); + } + else + { + flagRed = oFlagRed; + } + + if (droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM)) + { + GetNewFlagPoint(flagBlue, droppedBlueFlag, TEAM_BLUE); + } + else + { + flagBlue = oFlagBlue; + } + + if (!bs->ctfState) + { + return 0; + } + + i = 0; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client) + { + if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent)) + { + weHaveEnemyFlag = 1; + } + else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent)) + { + enemyHasOurFlag = 1; + } + + if (OnSameTeam(&g_entities[bs->client], ent)) + { + numOnMyTeam++; + } + else + { + numOnEnemyTeam++; + } + + if (botstates[ent->s.number]) + { + if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER || + botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL) + { + numAttackers++; + } + else + { + numDefenders++; + } + } + else + { //assume real players to be attackers in our logic + numAttackers++; + } + } + i++; + } + + if (bs->cur_ps.powerups[enemyFlag]) + { + if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag) + { + bs->ctfState = CTFSTATE_RETRIEVAL; + } + else + { + bs->ctfState = CTFSTATE_GETFLAGHOME; + } + } + else if (bs->ctfState == CTFSTATE_GETFLAGHOME) + { + bs->ctfState = 0; + } + + if (bs->state_Forced) + { + bs->ctfState = bs->state_Forced; + } + + if (bs->ctfState == CTFSTATE_DEFENDER) + { + if (BotDefendFlag(bs)) + { + goto success; + } + } + + if (bs->ctfState == CTFSTATE_ATTACKER) + { + if (BotGetEnemyFlag(bs)) + { + goto success; + } + } + + if (bs->ctfState == CTFSTATE_RETRIEVAL) + { + if (BotGetFlagBack(bs)) + { + goto success; + } + else + { //can't find anyone on another team being a carrier, so ignore this priority + bs->ctfState = 0; + } + } + + if (bs->ctfState == CTFSTATE_GUARDCARRIER) + { + if (BotGuardFlagCarrier(bs)) + { + goto success; + } + else + { //can't find anyone on our team being a carrier, so ignore this priority + bs->ctfState = 0; + } + } + + if (bs->ctfState == CTFSTATE_GETFLAGHOME) + { + if (BotGetFlagHome(bs)) + { + goto success; + } + } + + return 0; + +success: + if (dosw) + { //allow ctf code to run, but if after a particular item then keep going after it + bs->wpDestination = dest_sw; + } + + return 1; +} + +int EntityVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore, int ignore2) +{ + trace_t tr; + + trap_Trace(&tr, org1, mins, maxs, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + else if (tr.entityNum != ENTITYNUM_NONE && tr.entityNum == ignore2) + { + return 1; + } + + return 0; +} + +//Get the closest objective for siege and go after it +int Siege_TargetClosestObjective(bot_state_t *bs, int flag) +{ + int i = 0; + int bestindex = -1; + float testdistance = 0; + float bestdistance = 999999999; + gentity_t *goalent; + vec3_t a, dif; + vec3_t mins, maxs; + + mins[0] = -1; + mins[1] = -1; + mins[2] = -1; + + maxs[0] = 1; + maxs[1] = 1; + maxs[2] = 1; + + if ( bs->wpDestination && (bs->wpDestination->flags & flag) && bs->wpDestination->associated_entity != ENTITYNUM_NONE && + &g_entities[bs->wpDestination->associated_entity] && g_entities[bs->wpDestination->associated_entity].use ) + { + goto hasPoint; + } + + while (i < gWPNum) + { + if ( gWPArray[i] && gWPArray[i]->inuse && (gWPArray[i]->flags & flag) && gWPArray[i]->associated_entity != ENTITYNUM_NONE && + &g_entities[gWPArray[i]->associated_entity] && g_entities[gWPArray[i]->associated_entity].use ) + { + VectorSubtract(gWPArray[i]->origin, bs->origin, a); + testdistance = VectorLength(a); + + if (testdistance < bestdistance) + { + bestdistance = testdistance; + bestindex = i; + } + } + + i++; + } + + if (bestindex != -1) + { + bs->wpDestination = gWPArray[bestindex]; + } + else + { + return 0; + } +hasPoint: + goalent = &g_entities[bs->wpDestination->associated_entity]; + + if (!goalent) + { + return 0; + } + + VectorSubtract(bs->origin, bs->wpDestination->origin, a); + + testdistance = VectorLength(a); + + dif[0] = (goalent->r.absmax[0]+goalent->r.absmin[0])/2; + dif[1] = (goalent->r.absmax[1]+goalent->r.absmin[1])/2; + dif[2] = (goalent->r.absmax[2]+goalent->r.absmin[2])/2; + //brush models can have tricky origins, so this is our hacky method of getting the center point + + if (goalent->takedamage && testdistance < BOT_MIN_SIEGE_GOAL_SHOOT && + EntityVisibleBox(bs->origin, mins, maxs, dif, bs->client, goalent->s.number)) + { + bs->shootGoal = goalent; + bs->touchGoal = NULL; + } + else if (goalent->use && testdistance < BOT_MIN_SIEGE_GOAL_TRAVEL) + { + bs->shootGoal = NULL; + bs->touchGoal = goalent; + } + else + { //don't know how to handle this goal object! + bs->shootGoal = NULL; + bs->touchGoal = NULL; + } + + if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE || + BotGetWeaponRange(bs) == BWEAPONRANGE_SABER) + { + bs->shootGoal = NULL; //too risky + } + + if (bs->touchGoal) + { + //G_Printf("Please, master, let me touch it!\n"); + VectorCopy(dif, bs->goalPosition); + } + + return 1; +} + +void Siege_DefendFromAttackers(bot_state_t *bs) +{ //this may be a little cheap, but the best way to find our defending point is probably + //to just find the nearest person on the opposing team since they'll most likely + //be on offense in this situation + int wpClose = -1; + int i = 0; + float testdist = 999999; + int bestindex = -1; + float bestdist = 999999; + gentity_t *ent; + vec3_t a; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && ent->client->sess.sessionTeam != g_entities[bs->client].client->sess.sessionTeam && + ent->health > 0 && ent->client->sess.sessionTeam != TEAM_SPECTATOR) + { + VectorSubtract(ent->client->ps.origin, bs->origin, a); + + testdist = VectorLength(a); + + if (testdist < bestdist) + { + bestindex = i; + bestdist = testdist; + } + } + + i++; + } + + if (bestindex == -1) + { + return; + } + + wpClose = GetNearestVisibleWP(g_entities[bestindex].client->ps.origin, -1); + + if (wpClose != -1 && gWPArray[wpClose] && gWPArray[wpClose]->inuse) + { + bs->wpDestination = gWPArray[wpClose]; + bs->destinationGrabTime = level.time + 10000; + } +} + +//how many defenders on our team? +int Siege_CountDefenders(bot_state_t *bs) +{ + int i = 0; + int num = 0; + gentity_t *ent; + bot_state_t *bot; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + bot = botstates[i]; + + if (ent && ent->client && bot) + { + if (bot->siegeState == SIEGESTATE_DEFENDER && + ent->client->sess.sessionTeam == g_entities[bs->client].client->sess.sessionTeam) + { + num++; + } + } + + i++; + } + + return num; +} + +//how many other players on our team? +int Siege_CountTeammates(bot_state_t *bs) +{ + int i = 0; + int num = 0; + gentity_t *ent; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client) + { + if (ent->client->sess.sessionTeam == g_entities[bs->client].client->sess.sessionTeam) + { + num++; + } + } + + i++; + } + + return num; +} + +//see if siege objective completion should take priority in our +//nav routines. +int SiegeTakesPriority(bot_state_t *bs) +{ + int attacker; + int flagForDefendableObjective; + int flagForAttackableObjective; + int defenders, teammates; + int idleWP; + wpobject_t *dest_sw = NULL; + int dosw = 0; + gclient_t *bcl; + vec3_t dif; + trace_t tr; + + if (g_gametype.integer != GT_SIEGE) + { + return 0; + } + + bcl = g_entities[bs->client].client; + + if (!bcl) + { + return 0; + } + + if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_GATHER_TIME) + { //get the nearest weapon laying around base before heading off for battle + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + if (bs->wpDestSwitchTime < level.time) + { + bs->wpDestination = gWPArray[idleWP]; + } + return 1; + } + } + else if (bs->cur_ps.weapon == WP_BRYAR_PISTOL && + (level.time - bs->lastDeadTime) < BOT_MAX_WEAPON_CHASE_TIME && + bs->wpDestination && bs->wpDestination->weight) + { + dest_sw = bs->wpDestination; + dosw = 1; + } + + if (bcl->sess.sessionTeam == SIEGETEAM_TEAM1) + { + attacker = imperial_attackers; + flagForDefendableObjective = WPFLAG_SIEGE_REBELOBJ; + flagForAttackableObjective = WPFLAG_SIEGE_IMPERIALOBJ; + } + else + { + attacker = rebel_attackers; + flagForDefendableObjective = WPFLAG_SIEGE_IMPERIALOBJ; + flagForAttackableObjective = WPFLAG_SIEGE_REBELOBJ; + } + + if (attacker) + { + bs->siegeState = SIEGESTATE_ATTACKER; + } + else + { + bs->siegeState = SIEGESTATE_DEFENDER; + defenders = Siege_CountDefenders(bs); + teammates = Siege_CountTeammates(bs); + + if (defenders > teammates/3 && teammates > 1) + { //devote around 1/4 of our team to completing our own side goals even if we're a defender. + //If we have no side goals we will realize that later on and join the defenders + bs->siegeState = SIEGESTATE_ATTACKER; + } + } + + if (bs->state_Forced) + { + bs->siegeState = bs->state_Forced; + } + + if (bs->siegeState == SIEGESTATE_ATTACKER) + { + if (!Siege_TargetClosestObjective(bs, flagForAttackableObjective)) + { //looks like we have no goals other than to keep the other team from completing objectives + Siege_DefendFromAttackers(bs); + if (bs->shootGoal) + { + dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2; + dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2; + dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2; + + if (!BotPVSCheck(bs->origin, dif)) + { + bs->shootGoal = NULL; + } + else + { + trap_Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID); + + if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number) + { + bs->shootGoal = NULL; + } + } + } + } + } + else if (bs->siegeState == SIEGESTATE_DEFENDER) + { + Siege_DefendFromAttackers(bs); + if (bs->shootGoal) + { + dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2; + dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2; + dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2; + + if (!BotPVSCheck(bs->origin, dif)) + { + bs->shootGoal = NULL; + } + else + { + trap_Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID); + + if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number) + { + bs->shootGoal = NULL; + } + } + } + } + else + { //get busy! + Siege_TargetClosestObjective(bs, flagForAttackableObjective); + if (bs->shootGoal) + { + dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2; + dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2; + dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2; + + if (!BotPVSCheck(bs->origin, dif)) + { + bs->shootGoal = NULL; + } + else + { + trap_Trace(&tr, bs->origin, NULL, NULL, dif, bs->client, MASK_SOLID); + + if (tr.fraction != 1 && tr.entityNum != bs->shootGoal->s.number) + { + bs->shootGoal = NULL; + } + } + } + } + + if (dosw) + { //allow siege objective code to run, but if after a particular item then keep going after it + bs->wpDestination = dest_sw; + } + + return 1; +} + +//see if jedi master priorities should take priority in our nav +//routines. +int JMTakesPriority(bot_state_t *bs) +{ + int i = 0; + int wpClose = -1; + gentity_t *theImportantEntity = NULL; + + if (g_gametype.integer != GT_JEDIMASTER) + { + return 0; + } + + if (bs->cur_ps.isJediMaster) + { + return 0; + } + + //jmState becomes the index for the one who carries the saber. If jmState is -1 then the saber is currently + //without an owner + bs->jmState = -1; + + while (i < MAX_CLIENTS) + { + if (g_entities[i].client && g_entities[i].inuse && + g_entities[i].client->ps.isJediMaster) + { + bs->jmState = i; + break; + } + + i++; + } + + if (bs->jmState != -1) + { + theImportantEntity = &g_entities[bs->jmState]; + } + else + { + theImportantEntity = gJMSaberEnt; + } + + if (theImportantEntity && theImportantEntity->inuse && bs->destinationGrabTime < level.time) + { + if (theImportantEntity->client) + { + wpClose = GetNearestVisibleWP(theImportantEntity->client->ps.origin, theImportantEntity->s.number); + } + else + { + wpClose = GetNearestVisibleWP(theImportantEntity->r.currentOrigin, theImportantEntity->s.number); + } + + if (wpClose != -1 && gWPArray[wpClose] && gWPArray[wpClose]->inuse) + { + /* + Com_Printf("BOT GRABBED IDEAL JM LOCATION\n"); + if (bs->wpDestination != gWPArray[wpClose]) + { + Com_Printf("IDEAL WAS NOT ALREADY IDEAL\n"); + + if (!bs->wpDestination) + { + Com_Printf("IDEAL WAS NULL\n"); + } + } + */ + bs->wpDestination = gWPArray[wpClose]; + bs->destinationGrabTime = level.time + 4000; + } + } + + return 1; +} + +//see if we already have an item/powerup/etc. that is associated +//with this waypoint. +int BotHasAssociated(bot_state_t *bs, wpobject_t *wp) +{ + gentity_t *as; + + if (wp->associated_entity == ENTITYNUM_NONE) + { //make it think this is an item we have so we don't go after nothing + return 1; + } + + as = &g_entities[wp->associated_entity]; + + if (!as || !as->item) + { + return 0; + } + + if (as->item->giType == IT_WEAPON) + { + if (bs->cur_ps.stats[STAT_WEAPONS] & (1 << as->item->giTag)) + { + return 1; + } + + return 0; + } + else if (as->item->giType == IT_HOLDABLE) + { + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << as->item->giTag)) + { + return 1; + } + + return 0; + } + else if (as->item->giType == IT_POWERUP) + { + if (bs->cur_ps.powerups[as->item->giTag]) + { + return 1; + } + + return 0; + } + else if (as->item->giType == IT_AMMO) + { + if (bs->cur_ps.ammo[as->item->giTag] > 10) //hack + { + return 1; + } + + return 0; + } + + return 0; +} + +//we don't really have anything we want to do right now, +//let's just find the best thing to do given the current +//situation. +int GetBestIdleGoal(bot_state_t *bs) +{ + int i = 0; + int highestweight = 0; + int desiredindex = -1; + int dist_to_weight = 0; + int traildist; + + if (!bs->wpCurrent) + { + return -1; + } + + if (bs->isCamper != 2) + { + if (bs->randomNavTime < level.time) + { + if (Q_irand(1, 10) < 5) + { + bs->randomNav = 1; + } + else + { + bs->randomNav = 0; + } + + bs->randomNavTime = level.time + Q_irand(5000, 15000); + } + } + + if (bs->randomNav) + { //stop looking for items and/or camping on them + return -1; + } + + while (i < gWPNum) + { + if (gWPArray[i] && + gWPArray[i]->inuse && + (gWPArray[i]->flags & WPFLAG_GOALPOINT) && + gWPArray[i]->weight > highestweight && + !BotHasAssociated(bs, gWPArray[i])) + { + traildist = TotalTrailDistance(bs->wpCurrent->index, i, bs); + + if (traildist != -1) + { + dist_to_weight = (int)traildist/10000; + dist_to_weight = (gWPArray[i]->weight)-dist_to_weight; + + if (dist_to_weight > highestweight) + { + highestweight = dist_to_weight; + desiredindex = i; + } + } + } + + i++; + } + + return desiredindex; +} + +//go through the list of possible priorities for navigating +//and work out the best destination point. +void GetIdealDestination(bot_state_t *bs) +{ + int tempInt, cWPIndex, bChicken, idleWP; + float distChange, plusLen, minusLen; + vec3_t usethisvec, a; + gentity_t *badthing; + +#ifdef _DEBUG + trap_Cvar_Update(&bot_nogoals); + + if (bot_nogoals.integer) + { + return; + } +#endif + + if (!bs->wpCurrent) + { + return; + } + + if ((level.time - bs->escapeDirTime) > 4000) + { + badthing = GetNearestBadThing(bs); + } + else + { + badthing = NULL; + } + + if (badthing && badthing->inuse && + badthing->health > 0 && badthing->takedamage) + { + bs->dangerousObject = badthing; + } + else + { + bs->dangerousObject = NULL; + } + + if (!badthing && bs->wpDestIgnoreTime > level.time) + { + return; + } + + if (!badthing && bs->dontGoBack > level.time) + { + if (bs->wpDestination) + { + bs->wpStoreDest = bs->wpDestination; + } + bs->wpDestination = NULL; + return; + } + else if (!badthing && bs->wpStoreDest) + { //after we finish running away, switch back to our original destination + bs->wpDestination = bs->wpStoreDest; + bs->wpStoreDest = NULL; + } + + if (badthing && bs->wpCamping) + { + bs->wpCamping = NULL; + } + + if (bs->wpCamping) + { + bs->wpDestination = bs->wpCamping; + return; + } + + if (!badthing && CTFTakesPriority(bs)) + { + if (bs->ctfState) + { + bs->runningToEscapeThreat = 1; + } + return; + } + else if (!badthing && SiegeTakesPriority(bs)) + { + if (bs->siegeState) + { + bs->runningToEscapeThreat = 1; + } + return; + } + else if (!badthing && JMTakesPriority(bs)) + { + bs->runningToEscapeThreat = 1; + } + + if (badthing) + { + bs->runningLikeASissy = level.time + 100; + + if (bs->wpDestination) + { + bs->wpStoreDest = bs->wpDestination; + } + bs->wpDestination = NULL; + + if (bs->wpDirection) + { + tempInt = bs->wpCurrent->index+1; + } + else + { + tempInt = bs->wpCurrent->index-1; + } + + if (gWPArray[tempInt] && gWPArray[tempInt]->inuse && bs->escapeDirTime < level.time) + { + VectorSubtract(badthing->s.pos.trBase, bs->wpCurrent->origin, a); + plusLen = VectorLength(a); + VectorSubtract(badthing->s.pos.trBase, gWPArray[tempInt]->origin, a); + minusLen = VectorLength(a); + + if (plusLen < minusLen) + { + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + + bs->wpCurrent = gWPArray[tempInt]; + + bs->escapeDirTime = level.time + Q_irand(500, 1000);//Q_irand(1000, 1400); + + //G_Printf("Escaping from scary bad thing [%s]\n", badthing->classname); + } + } + //G_Printf("Run away run away run away!\n"); + return; + } + + distChange = 0; //keep the compiler from complaining + + tempInt = BotGetWeaponRange(bs); + + if (tempInt == BWEAPONRANGE_MELEE) + { + distChange = 1; + } + else if (tempInt == BWEAPONRANGE_SABER) + { + distChange = 1; + } + else if (tempInt == BWEAPONRANGE_MID) + { + distChange = 128; + } + else if (tempInt == BWEAPONRANGE_LONG) + { + distChange = 300; + } + + if (bs->revengeEnemy && bs->revengeEnemy->health > 0 && + bs->revengeEnemy->client && (bs->revengeEnemy->client->pers.connected == CA_ACTIVE || bs->revengeEnemy->client->pers.connected == CA_AUTHORIZING)) + { //if we hate someone, always try to get to them + if (bs->wpDestSwitchTime < level.time) + { + if (bs->revengeEnemy->client) + { + VectorCopy(bs->revengeEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->revengeEnemy->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000); + } + } + } + else if (bs->squadLeader && bs->squadLeader->health > 0 && + bs->squadLeader->client && (bs->squadLeader->client->pers.connected == CA_ACTIVE || bs->squadLeader->client->pers.connected == CA_AUTHORIZING)) + { + if (bs->wpDestSwitchTime < level.time) + { + if (bs->squadLeader->client) + { + VectorCopy(bs->squadLeader->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->squadLeader->s.origin, usethisvec); + } + + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + bs->wpDestSwitchTime = level.time + Q_irand(5000, 10000); + } + } + } + else if (bs->currentEnemy) + { + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, usethisvec); + } + + bChicken = BotIsAChickenWuss(bs); + bs->runningToEscapeThreat = bChicken; + + if (bs->frame_Enemy_Len < distChange || (bChicken && bChicken != 2)) + { + cWPIndex = bs->wpCurrent->index; + + if (bs->frame_Enemy_Len > 400) + { //good distance away, start running toward a good place for an item or powerup or whatever + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + bs->wpDestination = gWPArray[idleWP]; + } + } + else if (gWPArray[cWPIndex-1] && gWPArray[cWPIndex-1]->inuse && + gWPArray[cWPIndex+1] && gWPArray[cWPIndex+1]->inuse) + { + VectorSubtract(gWPArray[cWPIndex+1]->origin, usethisvec, a); + plusLen = VectorLength(a); + VectorSubtract(gWPArray[cWPIndex-1]->origin, usethisvec, a); + minusLen = VectorLength(a); + + if (minusLen > plusLen) + { + bs->wpDestination = gWPArray[cWPIndex-1]; + } + else + { + bs->wpDestination = gWPArray[cWPIndex+1]; + } + } + } + else if (bChicken != 2 && bs->wpDestSwitchTime < level.time) + { + tempInt = GetNearestVisibleWP(usethisvec, 0); + + if (tempInt != -1 && TotalTrailDistance(bs->wpCurrent->index, tempInt, bs) != -1) + { + bs->wpDestination = gWPArray[tempInt]; + + if (g_gametype.integer == GT_SINGLE_PLAYER) + { //be more aggressive + bs->wpDestSwitchTime = level.time + Q_irand(300, 1000); + } + else + { + bs->wpDestSwitchTime = level.time + Q_irand(1000, 5000); + } + } + } + } + + if (!bs->wpDestination && bs->wpDestSwitchTime < level.time) + { + //G_Printf("I need something to do\n"); + idleWP = GetBestIdleGoal(bs); + + if (idleWP != -1 && gWPArray[idleWP] && gWPArray[idleWP]->inuse) + { + bs->wpDestination = gWPArray[idleWP]; + } + } +} + +//commander CTF AI - tell other bots in the so-called +//"squad" what to do. +void CommanderBotCTFAI(bot_state_t *bs) +{ + int i = 0; + gentity_t *ent; + int squadmates = 0; + gentity_t *squad[MAX_CLIENTS]; + int defendAttackPriority = 0; //0 == attack, 1 == defend + int guardDefendPriority = 0; //0 == defend, 1 == guard + int attackRetrievePriority = 0; //0 == retrieve, 1 == attack + int myFlag = 0; + int enemyFlag = 0; + int enemyHasOurFlag = 0; + int weHaveEnemyFlag = 0; + int numOnMyTeam = 0; + int numOnEnemyTeam = 0; + int numAttackers = 0; + int numDefenders = 0; + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + myFlag = PW_REDFLAG; + } + else + { + myFlag = PW_BLUEFLAG; + } + + if (level.clients[bs->client].sess.sessionTeam == TEAM_RED) + { + enemyFlag = PW_BLUEFLAG; + } + else + { + enemyFlag = PW_REDFLAG; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client) + { + if (ent->client->ps.powerups[enemyFlag] && OnSameTeam(&g_entities[bs->client], ent)) + { + weHaveEnemyFlag = 1; + } + else if (ent->client->ps.powerups[myFlag] && !OnSameTeam(&g_entities[bs->client], ent)) + { + enemyHasOurFlag = 1; + } + + if (OnSameTeam(&g_entities[bs->client], ent)) + { + numOnMyTeam++; + } + else + { + numOnEnemyTeam++; + } + + if (botstates[ent->s.number]) + { + if (botstates[ent->s.number]->ctfState == CTFSTATE_ATTACKER || + botstates[ent->s.number]->ctfState == CTFSTATE_RETRIEVAL) + { + numAttackers++; + } + else + { + numDefenders++; + } + } + else + { //assume real players to be attackers in our logic + numAttackers++; + } + } + i++; + } + + i = 0; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && botstates[i] && botstates[i]->squadLeader && botstates[i]->squadLeader->s.number == bs->client && i != bs->client) + { + squad[squadmates] = ent; + squadmates++; + } + + i++; + } + + squad[squadmates] = &g_entities[bs->client]; + squadmates++; + + i = 0; + + if (enemyHasOurFlag && !weHaveEnemyFlag) + { //start off with an attacker instead of a retriever if we don't have the enemy flag yet so that they can't capture it first. + //after that we focus on getting our flag back. + attackRetrievePriority = 1; + } + + while (i < squadmates) + { + if (squad[i] && squad[i]->client && botstates[squad[i]->s.number]) + { + if (botstates[squad[i]->s.number]->ctfState != CTFSTATE_GETFLAGHOME) + { //never tell a bot to stop trying to bring the flag to the base + if (defendAttackPriority) + { + if (weHaveEnemyFlag) + { + if (guardDefendPriority) + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_GUARDCARRIER; + guardDefendPriority = 0; + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER; + guardDefendPriority = 1; + } + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_DEFENDER; + } + defendAttackPriority = 0; + } + else + { + if (enemyHasOurFlag) + { + if (attackRetrievePriority) + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER; + attackRetrievePriority = 0; + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL; + attackRetrievePriority = 1; + } + } + else + { + botstates[squad[i]->s.number]->ctfState = CTFSTATE_ATTACKER; + } + defendAttackPriority = 1; + } + } + else if ((numOnMyTeam < 2 || !numAttackers) && enemyHasOurFlag) + { //I'm the only one on my team who will attack and the enemy has my flag, I have to go after him + botstates[squad[i]->s.number]->ctfState = CTFSTATE_RETRIEVAL; + } + } + + i++; + } +} + +//similar to ctf ai, for siege +void CommanderBotSiegeAI(bot_state_t *bs) +{ + int i = 0; + int squadmates = 0; + int commanded = 0; + int teammates = 0; + gentity_t *squad[MAX_CLIENTS]; + gentity_t *ent; + bot_state_t *bst; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number]) + { + bst = botstates[ent->s.number]; + + if (bst && !bst->isSquadLeader && !bst->state_Forced) + { + squad[squadmates] = ent; + squadmates++; + } + else if (bst && !bst->isSquadLeader && bst->state_Forced) + { //count them as commanded + commanded++; + } + } + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent)) + { + teammates++; + } + + i++; + } + + if (!squadmates) + { + return; + } + + //tell squad mates to do what I'm doing, up to half of team, let the other half make their own decisions + i = 0; + + while (i < squadmates && squad[i]) + { + bst = botstates[squad[i]->s.number]; + + if (commanded > teammates/2) + { + break; + } + + if (bst) + { + bst->state_Forced = bs->siegeState; + bst->siegeState = bs->siegeState; + commanded++; + } + + i++; + } +} + +//teamplay ffa squad ai +void BotDoTeamplayAI(bot_state_t *bs) +{ + if (bs->state_Forced) + { + bs->teamplayState = bs->state_Forced; + } + + if (bs->teamplayState == TEAMPLAYSTATE_REGROUP) + { //force to find a new leader + bs->squadLeader = NULL; + bs->isSquadLeader = 0; + } +} + +//like ctf and siege commander ai, instruct the squad +void CommanderBotTeamplayAI(bot_state_t *bs) +{ + int i = 0; + int squadmates = 0; + int teammates = 0; + int teammate_indanger = -1; + int teammate_helped = 0; + int foundsquadleader = 0; + int worsthealth = 50; + gentity_t *squad[MAX_CLIENTS]; + gentity_t *ent; + bot_state_t *bst; + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent) && botstates[ent->s.number]) + { + bst = botstates[ent->s.number]; + + if (foundsquadleader && bst && bst->isSquadLeader) + { //never more than one squad leader + bst->isSquadLeader = 0; + } + + if (bst && !bst->isSquadLeader) + { + squad[squadmates] = ent; + squadmates++; + } + else if (bst) + { + foundsquadleader = 1; + } + } + + if (ent && ent->client && OnSameTeam(&g_entities[bs->client], ent)) + { + teammates++; + + if (ent->health < worsthealth) + { + teammate_indanger = ent->s.number; + worsthealth = ent->health; + } + } + + i++; + } + + if (!squadmates) + { + return; + } + + i = 0; + + while (i < squadmates && squad[i]) + { + bst = botstates[squad[i]->s.number]; + + if (bst && !bst->state_Forced) + { //only order if this guy is not being ordered directly by the real player team leader + if (teammate_indanger >= 0 && !teammate_helped) + { //send someone out to help whoever needs help most at the moment + bst->teamplayState = TEAMPLAYSTATE_ASSISTING; + bst->squadLeader = &g_entities[teammate_indanger]; + teammate_helped = 1; + } + else if ((teammate_indanger == -1 || teammate_helped) && bst->teamplayState == TEAMPLAYSTATE_ASSISTING) + { //no teammates need help badly, but this guy is trying to help them anyway, so stop + bst->teamplayState = TEAMPLAYSTATE_FOLLOWING; + bst->squadLeader = &g_entities[bs->client]; + } + + if (bs->squadRegroupInterval < level.time && Q_irand(1, 10) < 5) + { //every so often tell the squad to regroup for the sake of variation + if (bst->teamplayState == TEAMPLAYSTATE_FOLLOWING) + { + bst->teamplayState = TEAMPLAYSTATE_REGROUP; + } + + bs->isSquadLeader = 0; + bs->squadCannotLead = level.time + 500; + bs->squadRegroupInterval = level.time + Q_irand(45000, 65000); + } + } + + i++; + } +} + +//pick which commander ai to use based on gametype +void CommanderBotAI(bot_state_t *bs) +{ + if (g_gametype.integer == GT_CTF || g_gametype.integer == GT_CTY) + { + CommanderBotCTFAI(bs); + } + else if (g_gametype.integer == GT_SIEGE) + { + CommanderBotSiegeAI(bs); + } + else if (g_gametype.integer == GT_TEAM) + { + CommanderBotTeamplayAI(bs); + } +} + +//close range combat routines +void MeleeCombatHandling(bot_state_t *bs) +{ + vec3_t usethisvec; + vec3_t downvec; + vec3_t midorg; + vec3_t a; + vec3_t fwd; + vec3_t mins, maxs; + trace_t tr; + int en_down; + int me_down; + int mid_down; + + if (!bs->currentEnemy) + { + return; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, usethisvec); + } + + if (bs->meleeStrafeTime < level.time) + { + if (bs->meleeStrafeDir) + { + bs->meleeStrafeDir = 0; + } + else + { + bs->meleeStrafeDir = 1; + } + + bs->meleeStrafeTime = level.time + Q_irand(500, 1800); + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = -24; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + VectorCopy(usethisvec, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID); + + en_down = (int)tr.endpos[2]; + + VectorCopy(bs->origin, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID); + + me_down = (int)tr.endpos[2]; + + VectorSubtract(usethisvec, bs->origin, a); + vectoangles(a, a); + AngleVectors(a, fwd, NULL, NULL); + + midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2; + midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2; + midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2; + + VectorCopy(midorg, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID); + + mid_down = (int)tr.endpos[2]; + + if (me_down == en_down && + en_down == mid_down) + { + VectorCopy(usethisvec, bs->goalPosition); + } +} + +//saber combat routines (it's simple, but it works) +void SaberCombatHandling(bot_state_t *bs) +{ + vec3_t usethisvec; + vec3_t downvec; + vec3_t midorg; + vec3_t a; + vec3_t fwd; + vec3_t mins, maxs; + trace_t tr; + int en_down; + int me_down; + int mid_down; + + if (!bs->currentEnemy) + { + return; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, usethisvec); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, usethisvec); + } + + if (bs->meleeStrafeTime < level.time) + { + if (bs->meleeStrafeDir) + { + bs->meleeStrafeDir = 0; + } + else + { + bs->meleeStrafeDir = 1; + } + + bs->meleeStrafeTime = level.time + Q_irand(500, 1800); + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = -24; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + VectorCopy(usethisvec, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, usethisvec, mins, maxs, downvec, -1, MASK_SOLID); + + en_down = (int)tr.endpos[2]; + + if (tr.startsolid || tr.allsolid) + { + en_down = 1; + me_down = 2; + } + else + { + VectorCopy(bs->origin, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, bs->origin, mins, maxs, downvec, -1, MASK_SOLID); + + me_down = (int)tr.endpos[2]; + + if (tr.startsolid || tr.allsolid) + { + en_down = 1; + me_down = 2; + } + } + + VectorSubtract(usethisvec, bs->origin, a); + vectoangles(a, a); + AngleVectors(a, fwd, NULL, NULL); + + midorg[0] = bs->origin[0] + fwd[0]*bs->frame_Enemy_Len/2; + midorg[1] = bs->origin[1] + fwd[1]*bs->frame_Enemy_Len/2; + midorg[2] = bs->origin[2] + fwd[2]*bs->frame_Enemy_Len/2; + + VectorCopy(midorg, downvec); + downvec[2] -= 4096; + + trap_Trace(&tr, midorg, mins, maxs, downvec, -1, MASK_SOLID); + + mid_down = (int)tr.endpos[2]; + + if (me_down == en_down && + en_down == mid_down) + { + if (usethisvec[2] > (bs->origin[2]+32) && + bs->currentEnemy->client && + bs->currentEnemy->client->ps.groundEntityNum == ENTITYNUM_NONE) + { + bs->jumpTime = level.time + 100; + } + + if (bs->frame_Enemy_Len > 128) + { //be ready to attack + bs->saberDefending = 0; + bs->saberDefendDecideTime = level.time + Q_irand(1000, 2000); + } + else + { + if (bs->saberDefendDecideTime < level.time) + { + if (bs->saberDefending) + { + bs->saberDefending = 0; + } + else + { + bs->saberDefending = 1; + } + + bs->saberDefendDecideTime = level.time + Q_irand(500, 2000); + } + } + + if (bs->frame_Enemy_Len < 54) + { + VectorCopy(bs->origin, bs->goalPosition); + bs->saberBFTime = 0; + } + else + { + VectorCopy(usethisvec, bs->goalPosition); + } + + if (bs->currentEnemy && bs->currentEnemy->client) + { + if (!BG_SaberInSpecial(bs->currentEnemy->client->ps.saberMove) && bs->frame_Enemy_Len > 90 && bs->saberBFTime > level.time && bs->saberBTime > level.time && bs->beStill < level.time && bs->saberSTime < level.time) + { + bs->beStill = level.time + Q_irand(500, 1000); + bs->saberSTime = level.time + Q_irand(1200, 1800); + } + else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len < 80 && (Q_irand(1, 10) < 8 && bs->saberBFTime < level.time) || bs->saberBTime > level.time || BG_SaberInKata(bs->currentEnemy->client->ps.saberMove) || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK_DUAL) + { + vec3_t vs; + vec3_t groundcheck; + int idealDist; + int checkIncr = 0; + + VectorSubtract(bs->origin, usethisvec, vs); + VectorNormalize(vs); + + if (BG_SaberInKata(bs->currentEnemy->client->ps.saberMove) || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK || bs->currentEnemy->client->ps.saberMove == LS_SPINATTACK_DUAL) + { + idealDist = 256; + } + else + { + idealDist = 64; + } + + while (checkIncr < idealDist) + { + bs->goalPosition[0] = bs->origin[0] + vs[0]*checkIncr; + bs->goalPosition[1] = bs->origin[1] + vs[1]*checkIncr; + bs->goalPosition[2] = bs->origin[2] + vs[2]*checkIncr; + + if (bs->saberBTime < level.time) + { + bs->saberBFTime = level.time + Q_irand(900, 1300); + bs->saberBTime = level.time + Q_irand(300, 700); + } + + VectorCopy(bs->goalPosition, groundcheck); + + groundcheck[2] -= 64; + + trap_Trace(&tr, bs->goalPosition, NULL, NULL, groundcheck, bs->client, MASK_SOLID); + + if (tr.fraction == 1.0f) + { //don't back off of a ledge + VectorCopy(usethisvec, bs->goalPosition); + break; + } + checkIncr += 64; + } + } + else if (bs->currentEnemy->client->ps.weapon == WP_SABER && bs->frame_Enemy_Len >= 75) + { + bs->saberBFTime = level.time + Q_irand(700, 1300); + bs->saberBTime = 0; + } + } + + /*AngleVectors(bs->viewangles, NULL, fwd, NULL); + + if (bs->meleeStrafeDir) + { + bs->goalPosition[0] += fwd[0]*16; + bs->goalPosition[1] += fwd[1]*16; + bs->goalPosition[2] += fwd[2]*16; + } + else + { + bs->goalPosition[0] -= fwd[0]*16; + bs->goalPosition[1] -= fwd[1]*16; + bs->goalPosition[2] -= fwd[2]*16; + }*/ + } + else if (bs->frame_Enemy_Len <= 56) + { + bs->doAttack = 1; + bs->saberDefending = 0; + } +} + +//should we be "leading" our aim with this weapon? And if +//so, by how much? +float BotWeaponCanLead(bot_state_t *bs) +{ + int weap = bs->cur_ps.weapon; + + if (weap == WP_BRYAR_PISTOL) + { + return 0.5; + } + if (weap == WP_BLASTER) + { + return 0.35; + } + if (weap == WP_BOWCASTER) + { + return 0.5; + } + if (weap == WP_REPEATER) + { + return 0.45; + } + if (weap == WP_THERMAL) + { + return 0.5; + } + if (weap == WP_DEMP2) + { + return 0.35; + } + if (weap == WP_ROCKET_LAUNCHER) + { + return 0.7; + } + + return 0; +} + +//offset the desired view angles with aim leading in mind +void BotAimLeading(bot_state_t *bs, vec3_t headlevel, float leadAmount) +{ + int x; + vec3_t predictedSpot; + vec3_t movementVector; + vec3_t a, ang; + float vtotal; + + if (!bs->currentEnemy || + !bs->currentEnemy->client) + { + return; + } + + if (!bs->frame_Enemy_Len) + { + return; + } + + vtotal = 0; + + if (bs->currentEnemy->client->ps.velocity[0] < 0) + { + vtotal += -bs->currentEnemy->client->ps.velocity[0]; + } + else + { + vtotal += bs->currentEnemy->client->ps.velocity[0]; + } + + if (bs->currentEnemy->client->ps.velocity[1] < 0) + { + vtotal += -bs->currentEnemy->client->ps.velocity[1]; + } + else + { + vtotal += bs->currentEnemy->client->ps.velocity[1]; + } + + if (bs->currentEnemy->client->ps.velocity[2] < 0) + { + vtotal += -bs->currentEnemy->client->ps.velocity[2]; + } + else + { + vtotal += bs->currentEnemy->client->ps.velocity[2]; + } + + //G_Printf("Leadin target with a velocity total of %f\n", vtotal); + + VectorCopy(bs->currentEnemy->client->ps.velocity, movementVector); + + VectorNormalize(movementVector); + + x = bs->frame_Enemy_Len*leadAmount; //hardly calculated with an exact science, but it works + + if (vtotal > 400) + { + vtotal = 400; + } + + if (vtotal) + { + x = (bs->frame_Enemy_Len*0.9)*leadAmount*(vtotal*0.0012); //hardly calculated with an exact science, but it works + } + else + { + x = (bs->frame_Enemy_Len*0.9)*leadAmount; //hardly calculated with an exact science, but it works + } + + predictedSpot[0] = headlevel[0] + (movementVector[0]*x); + predictedSpot[1] = headlevel[1] + (movementVector[1]*x); + predictedSpot[2] = headlevel[2] + (movementVector[2]*x); + + VectorSubtract(predictedSpot, bs->eye, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); +} + +//wobble our aim around based on our sk1llz +void BotAimOffsetGoalAngles(bot_state_t *bs) +{ + int i; + float accVal; + i = 0; + + if (bs->skills.perfectaim) + { + return; + } + + if (bs->aimOffsetTime > level.time) + { + if (bs->aimOffsetAmtYaw) + { + bs->goalAngles[YAW] += bs->aimOffsetAmtYaw; + } + + if (bs->aimOffsetAmtPitch) + { + bs->goalAngles[PITCH] += bs->aimOffsetAmtPitch; + } + + while (i <= 2) + { + if (bs->goalAngles[i] > 360) + { + bs->goalAngles[i] -= 360; + } + + if (bs->goalAngles[i] < 0) + { + bs->goalAngles[i] += 360; + } + + i++; + } + return; + } + + accVal = bs->skills.accuracy/bs->settings.skill; + + if (bs->currentEnemy && BotMindTricked(bs->client, bs->currentEnemy->s.number)) + { //having to judge where they are by hearing them, so we should be quite inaccurate here + accVal *= 7; + + if (accVal < 30) + { + accVal = 30; + } + } + + if (bs->revengeEnemy && bs->revengeHateLevel && + bs->currentEnemy == bs->revengeEnemy) + { //bot becomes more skilled as anger level raises + accVal = accVal/bs->revengeHateLevel; + } + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { //assume our goal is aiming at the enemy, seeing as he's visible and all + if (!bs->currentEnemy->s.pos.trDelta[0] && + !bs->currentEnemy->s.pos.trDelta[1] && + !bs->currentEnemy->s.pos.trDelta[2]) + { + accVal = 0; //he's not even moving, so he shouldn't really be hard to hit. + } + else + { + accVal += accVal*0.25; //if he's moving he's this much harder to hit + } + + if (g_entities[bs->client].s.pos.trDelta[0] || + g_entities[bs->client].s.pos.trDelta[1] || + g_entities[bs->client].s.pos.trDelta[2]) + { + accVal += accVal*0.15; //make it somewhat harder to aim if we're moving also + } + } + + if (accVal > 90) + { + accVal = 90; + } + if (accVal < 1) + { + accVal = 0; + } + + if (!accVal) + { + bs->aimOffsetAmtYaw = 0; + bs->aimOffsetAmtPitch = 0; + return; + } + + if (rand()%10 <= 5) + { + bs->aimOffsetAmtYaw = rand()%(int)accVal; + } + else + { + bs->aimOffsetAmtYaw = -(rand()%(int)accVal); + } + + if (rand()%10 <= 5) + { + bs->aimOffsetAmtPitch = rand()%(int)accVal; + } + else + { + bs->aimOffsetAmtPitch = -(rand()%(int)accVal); + } + + bs->aimOffsetTime = level.time + rand()%500 + 200; +} + +//do we want to alt fire with this weapon? +int ShouldSecondaryFire(bot_state_t *bs) +{ + int weap; + int dif; + float rTime; + + weap = bs->cur_ps.weapon; + + if (bs->cur_ps.ammo[weaponData[weap].ammoIndex] < weaponData[weap].altEnergyPerShot) + { + return 0; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && bs->cur_ps.weapon == WP_ROCKET_LAUNCHER) + { + float heldTime = (level.time - bs->cur_ps.weaponChargeTime); + + rTime = bs->cur_ps.rocketLockTime; + + if (rTime < 1) + { + rTime = bs->cur_ps.rocketLastValidTime; + } + + if (heldTime > 5000) + { //just give up and release it if we can't manage a lock in 5 seconds + return 2; + } + + if (rTime > 0) + { + dif = ( level.time - rTime ) / ( 1200.0f / 16.0f ); + + if (dif >= 10) + { + return 2; + } + else if (bs->frame_Enemy_Len > 250) + { + return 1; + } + } + else if (bs->frame_Enemy_Len > 250) + { + return 1; + } + } + else if ((bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) && (level.time - bs->cur_ps.weaponChargeTime) > bs->altChargeTime) + { + return 2; + } + else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) + { + return 1; + } + + if (weap == WP_BRYAR_PISTOL && bs->frame_Enemy_Len < 300) + { + return 1; + } + else if (weap == WP_BOWCASTER && bs->frame_Enemy_Len > 300) + { + return 1; + } + else if (weap == WP_REPEATER && bs->frame_Enemy_Len < 600 && bs->frame_Enemy_Len > 250) + { + return 1; + } + else if (weap == WP_BLASTER && bs->frame_Enemy_Len < 300) + { + return 1; + } + else if (weap == WP_ROCKET_LAUNCHER && bs->frame_Enemy_Len > 250) + { + return 1; + } + + return 0; +} + +//standard weapon combat routines +int CombatBotAI(bot_state_t *bs, float thinktime) +{ + vec3_t eorg, a; + int secFire; + float fovcheck; + + if (!bs->currentEnemy) + { + return 0; + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, eorg); + } + else + { + VectorCopy(bs->currentEnemy->s.origin, eorg); + } + + VectorSubtract(eorg, bs->eye, a); + vectoangles(a, a); + + if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER) + { + if (bs->frame_Enemy_Len <= SABER_ATTACK_RANGE) + { + bs->doAttack = 1; + } + } + else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE) + { + if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE) + { + bs->doAttack = 1; + } + } + else + { + if (bs->cur_ps.weapon == WP_THERMAL || bs->cur_ps.weapon == WP_ROCKET_LAUNCHER) + { //be careful with the hurty weapons + fovcheck = 40; + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && + bs->cur_ps.weapon == WP_ROCKET_LAUNCHER) + { //if we're charging the weapon up then we can hold fire down within a normal fov + fovcheck = 60; + } + } + else + { + fovcheck = 60; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING || + bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) + { + fovcheck = 160; + } + + if (bs->frame_Enemy_Len < 128) + { + fovcheck *= 2; + } + + if (InFieldOfVision(bs->viewangles, fovcheck, a)) + { + if (bs->cur_ps.weapon == WP_THERMAL) + { + if (((level.time - bs->cur_ps.weaponChargeTime) < (bs->frame_Enemy_Len*2) && + (level.time - bs->cur_ps.weaponChargeTime) < 4000 && + bs->frame_Enemy_Len > 64) || + (bs->cur_ps.weaponstate != WEAPON_CHARGING && + bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT)) + { + if (bs->cur_ps.weaponstate != WEAPON_CHARGING && bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT) + { + if (bs->frame_Enemy_Len > 512 && bs->frame_Enemy_Len < 800) + { + bs->doAltAttack = 1; + //bs->doAttack = 1; + } + else + { + bs->doAttack = 1; + //bs->doAltAttack = 1; + } + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING) + { + bs->doAttack = 1; + } + else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) + { + bs->doAltAttack = 1; + } + } + } + else + { + secFire = ShouldSecondaryFire(bs); + + if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT && + bs->cur_ps.weaponstate != WEAPON_CHARGING) + { + bs->altChargeTime = Q_irand(500, 1000); + } + + if (secFire == 1) + { + bs->doAltAttack = 1; + } + else if (!secFire) + { + if (bs->cur_ps.weapon != WP_THERMAL) + { + if (bs->cur_ps.weaponstate != WEAPON_CHARGING || + bs->altChargeTime > (level.time - bs->cur_ps.weaponChargeTime)) + { + bs->doAttack = 1; + } + } + else + { + bs->doAttack = 1; + } + } + + if (secFire == 2) + { //released a charge + return 1; + } + } + } + } + + return 0; +} + +//we messed up and got off the normal path, let's fall +//back to jumping around and turning in random +//directions off walls to see if we can get back to a +//good place. +int BotFallbackNavigation(bot_state_t *bs) +{ + vec3_t b_angle, fwd, trto, mins, maxs; + trace_t tr; + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + return 2; //we're busy + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + bs->goalAngles[PITCH] = 0; + bs->goalAngles[ROLL] = 0; + + VectorCopy(bs->goalAngles, b_angle); + + AngleVectors(b_angle, fwd, NULL, NULL); + + trto[0] = bs->origin[0] + fwd[0]*16; + trto[1] = bs->origin[1] + fwd[1]*16; + trto[2] = bs->origin[2] + fwd[2]*16; + + trap_Trace(&tr, bs->origin, mins, maxs, trto, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction == 1) + { + VectorCopy(trto, bs->goalPosition); + return 1; //success! + } + else + { + bs->goalAngles[YAW] = rand()%360; + } + + return 0; +} + +int BotTryAnotherWeapon(bot_state_t *bs) +{ //out of ammo, resort to the first weapon we come across that has ammo + int i; + + i = 1; + + while (i < WP_NUM_WEAPONS) + { + if (bs->cur_ps.ammo[weaponData[i].ammoIndex] >= weaponData[i].energyPerShot && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i))) + { + bs->virtualWeapon = i; + BotSelectWeapon(bs->client, i); + //bs->cur_ps.weapon = i; + //level.clients[bs->client].ps.weapon = i; + return 1; + } + + i++; + } + + if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1) + { //should always have this.. shouldn't we? + bs->virtualWeapon = 1; + BotSelectWeapon(bs->client, 1); + //bs->cur_ps.weapon = 1; + //level.clients[bs->client].ps.weapon = 1; + return 1; + } + + return 0; +} + +//is this weapon available to us? +qboolean BotWeaponSelectable(bot_state_t *bs, int weapon) +{ + if (weapon == WP_NONE) + { + return qfalse; + } + + if (bs->cur_ps.ammo[weaponData[weapon].ammoIndex] >= weaponData[weapon].energyPerShot && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << weapon))) + { + return qtrue; + } + + return qfalse; +} + +//select the best weapon we can +int BotSelectIdealWeapon(bot_state_t *bs) +{ + int i; + int bestweight = -1; + int bestweapon = 0; + + i = 0; + + while (i < WP_NUM_WEAPONS) + { + if (bs->cur_ps.ammo[weaponData[i].ammoIndex] >= weaponData[i].energyPerShot && + bs->botWeaponWeights[i] > bestweight && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i))) + { + if (i == WP_THERMAL) + { //special case.. + if (bs->currentEnemy && bs->frame_Enemy_Len < 700) + { + bestweight = bs->botWeaponWeights[i]; + bestweapon = i; + } + } + else + { + bestweight = bs->botWeaponWeights[i]; + bestweapon = i; + } + } + + i++; + } + + if ( bs->currentEnemy && bs->frame_Enemy_Len < 300 && + (bestweapon == WP_BRYAR_PISTOL || bestweapon == WP_BLASTER || bestweapon == WP_BOWCASTER) && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) ) + { + bestweapon = WP_SABER; + bestweight = 1; + } + + if ( bs->currentEnemy && bs->frame_Enemy_Len > 300 && + bs->currentEnemy->client && bs->currentEnemy->client->ps.weapon != WP_SABER && + (bestweapon == WP_SABER) ) + { //if the enemy is far away, and we have our saber selected, see if we have any good distance weapons instead + if (BotWeaponSelectable(bs, WP_DISRUPTOR)) + { + bestweapon = WP_DISRUPTOR; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_ROCKET_LAUNCHER)) + { + bestweapon = WP_ROCKET_LAUNCHER; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_BOWCASTER)) + { + bestweapon = WP_BOWCASTER; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_BLASTER)) + { + bestweapon = WP_BLASTER; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_REPEATER)) + { + bestweapon = WP_REPEATER; + bestweight = 1; + } + else if (BotWeaponSelectable(bs, WP_DEMP2)) + { + bestweapon = WP_DEMP2; + bestweight = 1; + } + } + + //assert(bs->cur_ps.weapon > 0 && bestweapon > 0); + + if (bestweight != -1 && bs->cur_ps.weapon != bestweapon && bs->virtualWeapon != bestweapon) + { + bs->virtualWeapon = bestweapon; + BotSelectWeapon(bs->client, bestweapon); + //bs->cur_ps.weapon = bestweapon; + //level.clients[bs->client].ps.weapon = bestweapon; + return 1; + } + + //assert(bs->cur_ps.weapon > 0); + + return 0; +} + +//check/select the chosen weapon +int BotSelectChoiceWeapon(bot_state_t *bs, int weapon, int doselection) +{ //if !doselection then bot will only check if he has the specified weapon and return 1 (yes) or 0 (no) + int i; + int hasit = 0; + + i = 0; + + while (i < WP_NUM_WEAPONS) + { + if (bs->cur_ps.ammo[weaponData[i].ammoIndex] > weaponData[i].energyPerShot && + i == weapon && + (bs->cur_ps.stats[STAT_WEAPONS] & (1 << i))) + { + hasit = 1; + break; + } + + i++; + } + + if (hasit && bs->cur_ps.weapon != weapon && doselection && bs->virtualWeapon != weapon) + { + bs->virtualWeapon = weapon; + BotSelectWeapon(bs->client, weapon); + //bs->cur_ps.weapon = weapon; + //level.clients[bs->client].ps.weapon = weapon; + return 2; + } + + if (hasit) + { + return 1; + } + + return 0; +} + +//override our standard weapon choice with a melee weapon +int BotSelectMelee(bot_state_t *bs) +{ + if (bs->cur_ps.weapon != 1 && bs->virtualWeapon != 1) + { + bs->virtualWeapon = 1; + BotSelectWeapon(bs->client, 1); + //bs->cur_ps.weapon = 1; + //level.clients[bs->client].ps.weapon = 1; + return 1; + } + + return 0; +} + +//See if we our in love with the potential bot. +int GetLoveLevel(bot_state_t *bs, bot_state_t *love) +{ + int i = 0; + const char *lname = NULL; + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { //There is no love in 1-on-1 + return 0; + } + + if (!bs || !love || !g_entities[love->client].client) + { + return 0; + } + + if (!bs->lovednum) + { + return 0; + } + + if (!bot_attachments.integer) + { + return 1; + } + + lname = g_entities[love->client].client->pers.netname; + + if (!lname) + { + return 0; + } + + while (i < bs->lovednum) + { + if (strcmp(bs->loved[i].name, lname) == 0) + { + return bs->loved[i].level; + } + + i++; + } + + return 0; +} + +//Our loved one was killed. We must become infuriated! +void BotLovedOneDied(bot_state_t *bs, bot_state_t *loved, int lovelevel) +{ + if (!loved->lastHurt || !loved->lastHurt->client || + loved->lastHurt->s.number == loved->client) + { + return; + } + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { //There is no love in 1-on-1 + return; + } + + if (!IsTeamplay()) + { + if (lovelevel < 2) + { + return; + } + } + else if (OnSameTeam(&g_entities[bs->client], loved->lastHurt)) + { //don't hate teammates no matter what + return; + } + + if (loved->client == loved->lastHurt->s.number) + { + return; + } + + if (bs->client == loved->lastHurt->s.number) + { //oops! + return; + } + + if (!bot_attachments.integer) + { + return; + } + + if (!PassLovedOneCheck(bs, loved->lastHurt)) + { //a loved one killed a loved one.. you cannot hate them + bs->chatObject = loved->lastHurt; + bs->chatAltObject = &g_entities[loved->client]; + BotDoChat(bs, "LovedOneKilledLovedOne", 0); + return; + } + + if (bs->revengeEnemy == loved->lastHurt) + { + if (bs->revengeHateLevel < bs->loved_death_thresh) + { + bs->revengeHateLevel++; + + if (bs->revengeHateLevel == bs->loved_death_thresh) + { + //broke into the highest anger level + //CHAT: Hatred section + bs->chatObject = loved->lastHurt; + bs->chatAltObject = NULL; + BotDoChat(bs, "Hatred", 1); + } + } + } + else if (bs->revengeHateLevel < bs->loved_death_thresh-1) + { //only switch hatred if we don't hate the existing revenge-enemy too much + //CHAT: BelovedKilled section + bs->chatObject = &g_entities[loved->client]; + bs->chatAltObject = loved->lastHurt; + BotDoChat(bs, "BelovedKilled", 0); + bs->revengeHateLevel = 0; + bs->revengeEnemy = loved->lastHurt; + } +} + +void BotDeathNotify(bot_state_t *bs) +{ //in case someone has an emotional attachment to us, we'll notify them + int i = 0; + int ltest = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && botstates[i]->lovednum) + { + ltest = 0; + while (ltest < botstates[i]->lovednum) + { + if (strcmp(level.clients[bs->client].pers.netname, botstates[i]->loved[ltest].name) == 0) + { + BotLovedOneDied(botstates[i], bs, botstates[i]->loved[ltest].level); + break; + } + + ltest++; + } + } + + i++; + } +} + +//perform strafe trace checks +void StrafeTracing(bot_state_t *bs) +{ + vec3_t mins, maxs; + vec3_t right, rorg, drorg; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + //mins[2] = -24; + mins[2] = -22; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 32; + + AngleVectors(bs->viewangles, NULL, right, NULL); + + if (bs->meleeStrafeDir) + { + rorg[0] = bs->origin[0] - right[0]*32; + rorg[1] = bs->origin[1] - right[1]*32; + rorg[2] = bs->origin[2] - right[2]*32; + } + else + { + rorg[0] = bs->origin[0] + right[0]*32; + rorg[1] = bs->origin[1] + right[1]*32; + rorg[2] = bs->origin[2] + right[2]*32; + } + + trap_Trace(&tr, bs->origin, mins, maxs, rorg, bs->client, MASK_SOLID); + + if (tr.fraction != 1) + { + bs->meleeStrafeDisable = level.time + Q_irand(500, 1500); + } + + VectorCopy(rorg, drorg); + + drorg[2] -= 32; + + trap_Trace(&tr, rorg, NULL, NULL, drorg, bs->client, MASK_SOLID); + + if (tr.fraction == 1) + { //this may be a dangerous ledge, so don't strafe over it just in case + bs->meleeStrafeDisable = level.time + Q_irand(500, 1500); + } +} + +//doing primary weapon fire +int PrimFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING && + bs->doAttack) + { + return 1; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING && + !bs->doAttack) + { + return 1; + } + + return 0; +} + +//should we keep our primary weapon from firing? +int KeepPrimFromFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING && + bs->doAttack) + { + bs->doAttack = 0; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING && + !bs->doAttack) + { + bs->doAttack = 1; + } + + return 0; +} + +//doing secondary weapon fire +int AltFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT && + bs->doAltAttack) + { + return 1; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && + !bs->doAltAttack) + { + return 1; + } + + return 0; +} + +//should we keep our alt from firing? +int KeepAltFromFiring(bot_state_t *bs) +{ + if (bs->cur_ps.weaponstate != WEAPON_CHARGING_ALT && + bs->doAltAttack) + { + bs->doAltAttack = 0; + } + + if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT && + !bs->doAltAttack) + { + bs->doAltAttack = 1; + } + + return 0; +} + +//Try not to shoot our friends in the back. Or in the face. Or anywhere, really. +gentity_t *CheckForFriendInLOF(bot_state_t *bs) +{ + vec3_t fwd; + vec3_t trfrom, trto; + vec3_t mins, maxs; + gentity_t *trent; + trace_t tr; + + mins[0] = -3; + mins[1] = -3; + mins[2] = -3; + + maxs[0] = 3; + maxs[1] = 3; + maxs[2] = 3; + + AngleVectors(bs->viewangles, fwd, NULL, NULL); + + VectorCopy(bs->eye, trfrom); + + trto[0] = trfrom[0] + fwd[0]*2048; + trto[1] = trfrom[1] + fwd[1]*2048; + trto[2] = trfrom[2] + fwd[2]*2048; + + trap_Trace(&tr, trfrom, mins, maxs, trto, bs->client, MASK_PLAYERSOLID); + + if (tr.fraction != 1 && tr.entityNum <= MAX_CLIENTS) + { + trent = &g_entities[tr.entityNum]; + + if (trent && trent->client) + { + if (IsTeamplay() && OnSameTeam(&g_entities[bs->client], trent)) + { + return trent; + } + + if (botstates[trent->s.number] && GetLoveLevel(bs, botstates[trent->s.number]) > 1) + { + return trent; + } + } + } + + return NULL; +} + +void BotScanForLeader(bot_state_t *bs) +{ //bots will only automatically obtain a leader if it's another bot using this method. + int i = 0; + gentity_t *ent; + + if (bs->isSquadLeader) + { + return; + } + + while (i < MAX_CLIENTS) + { + ent = &g_entities[i]; + + if (ent && ent->client && botstates[i] && botstates[i]->isSquadLeader && bs->client != i) + { + if (OnSameTeam(&g_entities[bs->client], ent)) + { + bs->squadLeader = ent; + break; + } + if (GetLoveLevel(bs, botstates[i]) > 1 && !IsTeamplay()) + { //ignore love status regarding squad leaders if we're in teamplay + bs->squadLeader = ent; + break; + } + } + + i++; + } +} + +//w3rd to the p33pz. +void BotReplyGreetings(bot_state_t *bs) +{ + int i = 0; + int numhello = 0; + + while (i < MAX_CLIENTS) + { + if (botstates[i] && + botstates[i]->canChat && + i != bs->client) + { + botstates[i]->chatObject = &g_entities[bs->client]; + botstates[i]->chatAltObject = NULL; + if (BotDoChat(botstates[i], "ResponseGreetings", 0)) + { + numhello++; + } + } + + if (numhello > 3) + { //don't let more than 4 bots say hello at once + return; + } + + i++; + } +} + +//try to move in to grab a nearby flag +void CTFFlagMovement(bot_state_t *bs) +{ + int diddrop = 0; + gentity_t *desiredDrop = NULL; + vec3_t a, mins, maxs; + trace_t tr; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -7; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 7; + + if (bs->wantFlag && (bs->wantFlag->flags & FL_DROPPED_ITEM)) + { + if (bs->staticFlagSpot[0] == bs->wantFlag->s.pos.trBase[0] && + bs->staticFlagSpot[1] == bs->wantFlag->s.pos.trBase[1] && + bs->staticFlagSpot[2] == bs->wantFlag->s.pos.trBase[2]) + { + VectorSubtract(bs->origin, bs->wantFlag->s.pos.trBase, a); + + if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE) + { + VectorCopy(bs->wantFlag->s.pos.trBase, bs->goalPosition); + return; + } + else + { + bs->wantFlag = NULL; + } + } + else + { + bs->wantFlag = NULL; + } + } + else if (bs->wantFlag) + { + bs->wantFlag = NULL; + } + + if (flagRed && flagBlue) + { + if (bs->wpDestination == flagRed || + bs->wpDestination == flagBlue) + { + if (bs->wpDestination == flagRed && droppedRedFlag && (droppedRedFlag->flags & FL_DROPPED_ITEM) && droppedRedFlag->classname && strcmp(droppedRedFlag->classname, "freed") != 0) + { + desiredDrop = droppedRedFlag; + diddrop = 1; + } + if (bs->wpDestination == flagBlue && droppedBlueFlag && (droppedBlueFlag->flags & FL_DROPPED_ITEM) && droppedBlueFlag->classname && strcmp(droppedBlueFlag->classname, "freed") != 0) + { + desiredDrop = droppedBlueFlag; + diddrop = 1; + } + + if (diddrop && desiredDrop) + { + VectorSubtract(bs->origin, desiredDrop->s.pos.trBase, a); + + if (VectorLength(a) <= BOT_FLAG_GET_DISTANCE) + { + trap_Trace(&tr, bs->origin, mins, maxs, desiredDrop->s.pos.trBase, bs->client, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == desiredDrop->s.number) + { + VectorCopy(desiredDrop->s.pos.trBase, bs->goalPosition); + VectorCopy(desiredDrop->s.pos.trBase, bs->staticFlagSpot); + return; + } + } + } + } + } +} + +//see if we want to make our detpacks blow up +void BotCheckDetPacks(bot_state_t *bs) +{ + gentity_t *dp = NULL; + gentity_t *myDet = NULL; + vec3_t a; + float enLen; + float myLen; + + while ( (dp = G_Find( dp, FOFS(classname), "detpack") ) != NULL ) + { + if (dp && dp->parent && dp->parent->s.number == bs->client) + { + myDet = dp; + break; + } + } + + if (!myDet) + { + return; + } + + if (!bs->currentEnemy || !bs->currentEnemy->client || !bs->frame_Enemy_Vis) + { //require the enemy to be visilbe just to be fair.. + + //unless.. + if (bs->currentEnemy && bs->currentEnemy->client && + (level.time - bs->plantContinue) < 5000) + { //it's a fresh plant (within 5 seconds) so we should be able to guess + goto stillmadeit; + } + return; + } + +stillmadeit: + + VectorSubtract(bs->currentEnemy->client->ps.origin, myDet->s.pos.trBase, a); + enLen = VectorLength(a); + + VectorSubtract(bs->origin, myDet->s.pos.trBase, a); + myLen = VectorLength(a); + + if (enLen > myLen) + { + return; + } + + if (enLen < BOT_PLANT_BLOW_DISTANCE && OrgVisible(bs->currentEnemy->client->ps.origin, myDet->s.pos.trBase, bs->currentEnemy->s.number)) + { //we could just call the "blow all my detpacks" function here, but I guess that's cheating. + bs->plantKillEmAll = level.time + 500; + } +} + +//see if it would be beneficial at this time to use one of our inv items +int BotUseInventoryItem(bot_state_t *bs) +{ + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC)) + { + if (g_entities[bs->client].health <= 75) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC_BIG)) + { + if (g_entities[bs->client].health <= 50) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_MEDPAC_BIG, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER)) + { + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SEEKER, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN)) + { + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SENTRY_GUN, IT_HOLDABLE); + goto wantuseitem; + } + } + if (bs->cur_ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD)) + { + if (bs->currentEnemy && bs->frame_Enemy_Vis && bs->runningToEscapeThreat) + { //this will (hopefully) result in the bot placing the shield down while facing + //the enemy and running away + bs->cur_ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_SHIELD, IT_HOLDABLE); + goto wantuseitem; + } + } + + return 0; + +wantuseitem: + level.clients[bs->client].ps.stats[STAT_HOLDABLE_ITEM] = bs->cur_ps.stats[STAT_HOLDABLE_ITEM]; + + return 1; +} + +//trace forward to see if we can plant a detpack or something +int BotSurfaceNear(bot_state_t *bs) +{ + trace_t tr; + vec3_t fwd; + + AngleVectors(bs->viewangles, fwd, NULL, NULL); + + fwd[0] = bs->origin[0]+(fwd[0]*64); + fwd[1] = bs->origin[1]+(fwd[1]*64); + fwd[2] = bs->origin[2]+(fwd[2]*64); + + trap_Trace(&tr, bs->origin, NULL, NULL, fwd, bs->client, MASK_SOLID); + + if (tr.fraction != 1) + { + return 1; + } + + return 0; +} + +//could we block projectiles from the weapon potentially with a light saber? +int BotWeaponBlockable(int weapon) +{ + switch (weapon) + { + case WP_STUN_BATON: + case WP_MELEE: + return 0; + case WP_DISRUPTOR: + return 0; + case WP_DEMP2: + return 0; + case WP_ROCKET_LAUNCHER: + return 0; + case WP_THERMAL: + return 0; + case WP_TRIP_MINE: + return 0; + case WP_DET_PACK: + return 0; + default: + return 1; + } +} + +void Cmd_EngageDuel_f(gentity_t *ent); +void Cmd_ToggleSaber_f(gentity_t *ent); + +//movement overrides +void Bot_SetForcedMovement(int bot, int forward, int right, int up) +{ + bot_state_t *bs; + + bs = botstates[bot]; + + if (!bs) + { //not a bot + return; + } + + if (forward != -1) + { + if (bs->forceMove_Forward) + { + bs->forceMove_Forward = 0; + } + else + { + bs->forceMove_Forward = forward; + } + } + if (right != -1) + { + if (bs->forceMove_Right) + { + bs->forceMove_Right = 0; + } + else + { + bs->forceMove_Right = right; + } + } + if (up != -1) + { + if (bs->forceMove_Up) + { + bs->forceMove_Up = 0; + } + else + { + bs->forceMove_Up = up; + } + } +} + +//the main AI loop. +//please don't be too frightened. +void StandardBotAI(bot_state_t *bs, float thinktime) +{ + int wp, enemy; + int desiredIndex; + int goalWPIndex; + int doingFallback = 0; + int fjHalt; + vec3_t a, ang, headlevel, eorg, noz_x, noz_y, dif, a_fo; + float reaction; + float bLeadAmount; + int meleestrafe = 0; + int useTheForce = 0; + int forceHostile = 0; + int cBAI = 0; + gentity_t *friendInLOF = 0; + float mLen; + int visResult = 0; + int selResult = 0; + int mineSelect = 0; + int detSelect = 0; + vec3_t preFrameGAngles; + + if (gDeactivated) + { + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpDirection = 0; + return; + } + + if (g_entities[bs->client].inuse && + g_entities[bs->client].client && + g_entities[bs->client].client->sess.sessionTeam == TEAM_SPECTATOR) + { + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpDirection = 0; + return; + } + + +#ifndef FINAL_BUILD + if (bot_getinthecarrr.integer) + { //stupid vehicle debug, I tire of having to connect another client to test passengers. + gentity_t *botEnt = &g_entities[bs->client]; + + if (botEnt->inuse && botEnt->client && botEnt->client->ps.m_iVehicleNum) + { //in a vehicle, so... + bs->noUseTime = level.time + 5000; + + if (bot_getinthecarrr.integer != 2) + { + trap_EA_MoveForward(bs->client); + + if (bot_getinthecarrr.integer == 3) + { //use alt fire + trap_EA_Alt_Attack(bs->client); + } + } + } + else + { //find one, get in + int i = 0; + gentity_t *vehicle = NULL; + //find the nearest, manned vehicle + while (i < MAX_GENTITIES) + { + vehicle = &g_entities[i]; + + if (vehicle->inuse && vehicle->client && vehicle->s.eType == ET_NPC && + vehicle->s.NPC_class == CLASS_VEHICLE && vehicle->m_pVehicle && + (vehicle->client->ps.m_iVehicleNum || bot_getinthecarrr.integer == 2)) + { //ok, this is a vehicle, and it has a pilot/passengers + break; + } + i++; + } + if (i != MAX_GENTITIES && vehicle) + { //broke before end so we must've found something + vec3_t v; + + VectorSubtract(vehicle->client->ps.origin, bs->origin, v); + VectorNormalize(v); + vectoangles(v, bs->goalAngles); + MoveTowardIdealAngles(bs); + trap_EA_Move(bs->client, v, 5000.0f); + + if (bs->noUseTime < (level.time-400)) + { + bs->noUseTime = level.time + 500; + } + } + } + + return; + } +#endif + + if (bot_forgimmick.integer) + { + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpDirection = 0; + + if (bot_forgimmick.integer == 2) + { //for debugging saber stuff, this is handy + trap_EA_Attack(bs->client); + } + + if (bot_forgimmick.integer == 3) + { //for testing cpu usage moving around rmg terrain without AI + vec3_t mdir; + + VectorSubtract(bs->origin, vec3_origin, mdir); + VectorNormalize(mdir); + trap_EA_Attack(bs->client); + trap_EA_Move(bs->client, mdir, 5000); + } + + if (bot_forgimmick.integer == 4) + { //constantly move toward client 0 + if (g_entities[0].client && g_entities[0].inuse) + { + vec3_t mdir; + + VectorSubtract(g_entities[0].client->ps.origin, bs->origin, mdir); + VectorNormalize(mdir); + trap_EA_Move(bs->client, mdir, 5000); + } + } + + if (bs->forceMove_Forward) + { + if (bs->forceMove_Forward > 0) + { + trap_EA_MoveForward(bs->client); + } + else + { + trap_EA_MoveBack(bs->client); + } + } + if (bs->forceMove_Right) + { + if (bs->forceMove_Right > 0) + { + trap_EA_MoveRight(bs->client); + } + else + { + trap_EA_MoveLeft(bs->client); + } + } + if (bs->forceMove_Up) + { + trap_EA_Jump(bs->client); + } + return; + } + + if (!bs->lastDeadTime) + { //just spawned in? + bs->lastDeadTime = level.time; + } + + if (g_entities[bs->client].health < 1) + { + bs->lastDeadTime = level.time; + + if (!bs->deathActivitiesDone && bs->lastHurt && bs->lastHurt->client && bs->lastHurt->s.number != bs->client) + { + BotDeathNotify(bs); + if (PassLovedOneCheck(bs, bs->lastHurt)) + { + //CHAT: Died + bs->chatObject = bs->lastHurt; + bs->chatAltObject = NULL; + BotDoChat(bs, "Died", 0); + } + else if (!PassLovedOneCheck(bs, bs->lastHurt) && + botstates[bs->lastHurt->s.number] && + PassLovedOneCheck(botstates[bs->lastHurt->s.number], &g_entities[bs->client])) + { //killed by a bot that I love, but that does not love me + bs->chatObject = bs->lastHurt; + bs->chatAltObject = NULL; + BotDoChat(bs, "KilledOnPurposeByLove", 0); + } + + bs->deathActivitiesDone = 1; + } + + bs->wpCurrent = NULL; + bs->currentEnemy = NULL; + bs->wpDestination = NULL; + bs->wpCamping = NULL; + bs->wpCampingTo = NULL; + bs->wpStoreDest = NULL; + bs->wpDestIgnoreTime = 0; + bs->wpDestSwitchTime = 0; + bs->wpSeenTime = 0; + bs->wpDirection = 0; + + if (rand()%10 < 5 && + (!bs->doChat || bs->chatTime < level.time)) + { + trap_EA_Attack(bs->client); + } + + return; + } + + VectorCopy(bs->goalAngles, preFrameGAngles); + + bs->doAttack = 0; + bs->doAltAttack = 0; + //reset the attack states + + if (bs->isSquadLeader) + { + CommanderBotAI(bs); + } + else + { + BotDoTeamplayAI(bs); + } + + if (!bs->currentEnemy) + { + bs->frame_Enemy_Vis = 0; + } + + if (bs->revengeEnemy && bs->revengeEnemy->client && + bs->revengeEnemy->client->pers.connected != CA_ACTIVE && bs->revengeEnemy->client->pers.connected != CA_AUTHORIZING) + { + bs->revengeEnemy = NULL; + bs->revengeHateLevel = 0; + } + + if (bs->currentEnemy && bs->currentEnemy->client && + bs->currentEnemy->client->pers.connected != CA_ACTIVE && bs->currentEnemy->client->pers.connected != CA_AUTHORIZING) + { + bs->currentEnemy = NULL; + } + + fjHalt = 0; + +#ifndef FORCEJUMP_INSTANTMETHOD + if (bs->forceJumpChargeTime > level.time) + { + useTheForce = 1; + forceHostile = 0; + } + + if (bs->currentEnemy && bs->currentEnemy->client && bs->frame_Enemy_Vis && bs->forceJumpChargeTime < level.time) +#else + if (bs->currentEnemy && bs->currentEnemy->client && bs->frame_Enemy_Vis) +#endif + { + VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, a_fo); + vectoangles(a_fo, a_fo); + + //do this above all things + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PUSH)) && (bs->doForcePush > level.time || bs->cur_ps.fd.forceGripBeingGripped > level.time) && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PUSH]][FP_PUSH] /*&& InFieldOfVision(bs->viewangles, 50, a_fo)*/) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_PUSH; + useTheForce = 1; + forceHostile = 1; + } + else if (bs->cur_ps.fd.forceSide == FORCE_DARKSIDE) + { //try dark side powers + //in order of priority top to bottom + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_GRIP)) && (bs->cur_ps.fd.forcePowersActive & (1 << FP_GRIP)) && InFieldOfVision(bs->viewangles, 50, a_fo)) + { //already gripping someone, so hold it + level.clients[bs->client].ps.fd.forcePowerSelected = FP_GRIP; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_LIGHTNING)) && bs->frame_Enemy_Len < FORCE_LIGHTNING_RADIUS && level.clients[bs->client].ps.fd.forcePower > 50 && InFieldOfVision(bs->viewangles, 50, a_fo)) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_LIGHTNING; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_GRIP)) && bs->frame_Enemy_Len < MAX_GRIP_DISTANCE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_GRIP]][FP_GRIP] && InFieldOfVision(bs->viewangles, 50, a_fo)) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_GRIP; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_RAGE)) && g_entities[bs->client].health < 25 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_RAGE]][FP_RAGE]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_RAGE; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_DRAIN)) && bs->frame_Enemy_Len < MAX_DRAIN_DISTANCE && level.clients[bs->client].ps.fd.forcePower > 50 && InFieldOfVision(bs->viewangles, 50, a_fo) && bs->currentEnemy->client->ps.fd.forcePower > 10 && bs->currentEnemy->client->ps.fd.forceSide == FORCE_LIGHTSIDE) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_DRAIN; + useTheForce = 1; + forceHostile = 1; + } + } + else if (bs->cur_ps.fd.forceSide == FORCE_LIGHTSIDE) + { //try light side powers + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && bs->cur_ps.fd.forceGripCripple && + level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB]) + { //absorb to get out + level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && bs->cur_ps.electrifyTime >= level.time && + level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB]) + { //absorb lightning + level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_TELEPATHY)) && bs->frame_Enemy_Len < MAX_TRICK_DISTANCE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TELEPATHY]][FP_TELEPATHY] && InFieldOfVision(bs->viewangles, 50, a_fo) && !(bs->currentEnemy->client->ps.fd.forcePowersActive & (1 << FP_SEE))) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TELEPATHY; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_ABSORB)) && g_entities[bs->client].health < 75 && bs->currentEnemy->client->ps.fd.forceSide == FORCE_DARKSIDE && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_ABSORB]][FP_ABSORB]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_ABSORB; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PROTECT)) && g_entities[bs->client].health < 35 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PROTECT]][FP_PROTECT]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_PROTECT; + useTheForce = 1; + forceHostile = 0; + } + } + + if (!useTheForce) + { //try neutral powers + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PUSH)) && bs->cur_ps.fd.forceGripBeingGripped > level.time && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_PUSH]][FP_PUSH] && InFieldOfVision(bs->viewangles, 50, a_fo)) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_PUSH; + useTheForce = 1; + forceHostile = 1; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_SPEED)) && g_entities[bs->client].health < 25 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_SPEED]][FP_SPEED]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_SPEED; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_SEE)) && BotMindTricked(bs->client, bs->currentEnemy->s.number) && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_SEE]][FP_SEE]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_SEE; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_PULL)) && bs->frame_Enemy_Len < 256 && level.clients[bs->client].ps.fd.forcePower > 75 && InFieldOfVision(bs->viewangles, 50, a_fo)) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_PULL; + useTheForce = 1; + forceHostile = 1; + } + } + } + + if (!useTheForce) + { //try powers that we don't care if we have an enemy for + if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_HEAL)) && g_entities[bs->client].health < 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_HEAL]][FP_HEAL] && bs->cur_ps.fd.forcePowerLevel[FP_HEAL] > FORCE_LEVEL_1) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_HEAL; + useTheForce = 1; + forceHostile = 0; + } + else if ((bs->cur_ps.fd.forcePowersKnown & (1 << FP_HEAL)) && g_entities[bs->client].health < 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_HEAL]][FP_HEAL] && !bs->currentEnemy && bs->isCamping > level.time) + { //only meditate and heal if we're camping + level.clients[bs->client].ps.fd.forcePowerSelected = FP_HEAL; + useTheForce = 1; + forceHostile = 0; + } + } + + if (useTheForce && forceHostile) + { + if (bs->currentEnemy && bs->currentEnemy->client && + !ForcePowerUsableOn(&g_entities[bs->client], bs->currentEnemy, level.clients[bs->client].ps.fd.forcePowerSelected)) + { + useTheForce = 0; + forceHostile = 0; + } + } + + doingFallback = 0; + + bs->deathActivitiesDone = 0; + + if (BotUseInventoryItem(bs)) + { + if (rand()%10 < 5) + { + trap_EA_Use(bs->client); + } + } + + if (bs->cur_ps.ammo[weaponData[bs->cur_ps.weapon].ammoIndex] < weaponData[bs->cur_ps.weapon].energyPerShot) + { + if (BotTryAnotherWeapon(bs)) + { + return; + } + } + else + { + if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number && + bs->frame_Enemy_Vis && bs->forceWeaponSelect /*&& bs->plantContinue < level.time*/) + { + bs->forceWeaponSelect = 0; + } + + if (bs->plantContinue > level.time) + { + bs->doAttack = 1; + bs->destinationGrabTime = 0; + } + + if (!bs->forceWeaponSelect && bs->cur_ps.hasDetPackPlanted && bs->plantKillEmAll > level.time) + { + bs->forceWeaponSelect = WP_DET_PACK; + } + + if (bs->forceWeaponSelect) + { + selResult = BotSelectChoiceWeapon(bs, bs->forceWeaponSelect, 1); + } + + if (selResult) + { + if (selResult == 2) + { //newly selected + return; + } + } + else if (BotSelectIdealWeapon(bs)) + { + return; + } + } + /*if (BotSelectMelee(bs)) + { + return; + }*/ + + reaction = bs->skills.reflex/bs->settings.skill; + + if (reaction < 0) + { + reaction = 0; + } + if (reaction > 2000) + { + reaction = 2000; + } + + if (!bs->currentEnemy) + { + bs->timeToReact = level.time + reaction; + } + + if (bs->cur_ps.weapon == WP_DET_PACK && bs->cur_ps.hasDetPackPlanted && bs->plantKillEmAll > level.time) + { + bs->doAltAttack = 1; + } + + if (bs->wpCamping) + { + if (bs->isCamping < level.time) + { + bs->wpCamping = NULL; + bs->isCamping = 0; + } + + if (bs->currentEnemy && bs->frame_Enemy_Vis) + { + bs->wpCamping = NULL; + bs->isCamping = 0; + } + } + + if (bs->wpCurrent && + (bs->wpSeenTime < level.time || bs->wpTravelTime < level.time)) + { + bs->wpCurrent = NULL; + } + + if (bs->currentEnemy) + { + if (bs->enemySeenTime < level.time || + !PassStandardEnemyChecks(bs, bs->currentEnemy)) + { + if (bs->revengeEnemy == bs->currentEnemy && + bs->currentEnemy->health < 1 && + bs->lastAttacked && bs->lastAttacked == bs->currentEnemy) + { + //CHAT: Destroyed hated one [KilledHatedOne section] + bs->chatObject = bs->revengeEnemy; + bs->chatAltObject = NULL; + BotDoChat(bs, "KilledHatedOne", 1); + bs->revengeEnemy = NULL; + bs->revengeHateLevel = 0; + } + else if (bs->currentEnemy->health < 1 && PassLovedOneCheck(bs, bs->currentEnemy) && + bs->lastAttacked && bs->lastAttacked == bs->currentEnemy) + { + //CHAT: Killed + bs->chatObject = bs->currentEnemy; + bs->chatAltObject = NULL; + BotDoChat(bs, "Killed", 0); + } + + bs->currentEnemy = NULL; + } + } + + if (bot_honorableduelacceptance.integer) + { + if (bs->currentEnemy && bs->currentEnemy->client && + bs->cur_ps.weapon == WP_SABER && + g_privateDuel.integer && + bs->frame_Enemy_Vis && + bs->frame_Enemy_Len < 400 && + bs->currentEnemy->client->ps.weapon == WP_SABER && + bs->currentEnemy->client->ps.saberHolstered) + { + vec3_t e_ang_vec; + + VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, e_ang_vec); + + if (InFieldOfVision(bs->viewangles, 100, e_ang_vec)) + { //Our enemy has his saber holstered and has challenged us to a duel, so challenge him back + if (!bs->cur_ps.saberHolstered) + { + Cmd_ToggleSaber_f(&g_entities[bs->client]); + } + else + { + if (bs->currentEnemy->client->ps.duelIndex == bs->client && + bs->currentEnemy->client->ps.duelTime > level.time && + !bs->cur_ps.duelInProgress) + { + Cmd_EngageDuel_f(&g_entities[bs->client]); + } + } + + bs->doAttack = 0; + bs->doAltAttack = 0; + bs->botChallengingTime = level.time + 100; + bs->beStill = level.time + 100; + } + } + } + //Apparently this "allows you to cheese" when fighting against bots. I'm not sure why you'd want to con bots + //into an easy kill, since they're bots and all. But whatever. + + if (!bs->wpCurrent) + { + wp = GetNearestVisibleWP(bs->origin, bs->client); + + if (wp != -1) + { + bs->wpCurrent = gWPArray[wp]; + bs->wpSeenTime = level.time + 1500; + bs->wpTravelTime = level.time + 10000; //never take more than 10 seconds to travel to a waypoint + } + } + + if (bs->enemySeenTime < level.time || !bs->frame_Enemy_Vis || !bs->currentEnemy || + (bs->currentEnemy /*&& bs->cur_ps.weapon == WP_SABER && bs->frame_Enemy_Len > 300*/)) + { + enemy = ScanForEnemies(bs); + + if (enemy != -1) + { + bs->currentEnemy = &g_entities[enemy]; + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } + } + + if (!bs->squadLeader && !bs->isSquadLeader) + { + BotScanForLeader(bs); + } + + if (!bs->squadLeader && bs->squadCannotLead < level.time) + { //if still no leader after scanning, then become a squad leader + bs->isSquadLeader = 1; + } + + if (bs->isSquadLeader && bs->squadLeader) + { //we don't follow anyone if we are a leader + bs->squadLeader = NULL; + } + + //ESTABLISH VISIBILITIES AND DISTANCES FOR THE WHOLE FRAME HERE + if (bs->wpCurrent) + { + if (g_RMG.integer) + { //this is somewhat hacky, but in RMG we don't really care about vertical placement because points are scattered across only the terrain. + vec3_t vecB, vecC; + + vecB[0] = bs->origin[0]; + vecB[1] = bs->origin[1]; + vecB[2] = bs->origin[2]; + + vecC[0] = bs->wpCurrent->origin[0]; + vecC[1] = bs->wpCurrent->origin[1]; + vecC[2] = vecB[2]; + + + VectorSubtract(vecC, vecB, a); + } + else + { + VectorSubtract(bs->wpCurrent->origin, bs->origin, a); + } + bs->frame_Waypoint_Len = VectorLength(a); + + visResult = WPOrgVisible(&g_entities[bs->client], bs->origin, bs->wpCurrent->origin, bs->client); + + if (visResult == 2) + { + bs->frame_Waypoint_Vis = 0; + bs->wpSeenTime = 0; + bs->wpDestination = NULL; + bs->wpDestIgnoreTime = level.time + 5000; + + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + } + else if (visResult) + { + bs->frame_Waypoint_Vis = 1; + } + else + { + bs->frame_Waypoint_Vis = 0; + } + } + + if (bs->currentEnemy) + { + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, eorg); + eorg[2] += bs->currentEnemy->client->ps.viewheight; + } + else + { + VectorCopy(bs->currentEnemy->s.origin, eorg); + } + + VectorSubtract(eorg, bs->eye, a); + bs->frame_Enemy_Len = VectorLength(a); + + if (OrgVisible(bs->eye, eorg, bs->client)) + { + bs->frame_Enemy_Vis = 1; + VectorCopy(eorg, bs->lastEnemySpotted); + VectorCopy(bs->origin, bs->hereWhenSpotted); + bs->lastVisibleEnemyIndex = bs->currentEnemy->s.number; + //VectorCopy(bs->eye, bs->lastEnemySpotted); + bs->hitSpotted = 0; + } + else + { + bs->frame_Enemy_Vis = 0; + } + } + else + { + bs->lastVisibleEnemyIndex = ENTITYNUM_NONE; + } + //END + + if (bs->frame_Enemy_Vis) + { + bs->enemySeenTime = level.time + ENEMY_FORGET_MS; + } + + if (bs->wpCurrent) + { + int wpTouchDist = BOT_WPTOUCH_DISTANCE; + WPConstantRoutine(bs); + + if (!bs->wpCurrent) + { //WPConstantRoutine has the ability to nullify the waypoint if it fails certain checks, so.. + return; + } + + if (bs->wpCurrent->flags & WPFLAG_WAITFORFUNC) + { + if (!CheckForFunc(bs->wpCurrent->origin, -1)) + { + bs->beStill = level.time + 500; //no func brush under.. wait + } + } + if (bs->wpCurrent->flags & WPFLAG_NOMOVEFUNC) + { + if (CheckForFunc(bs->wpCurrent->origin, -1)) + { + bs->beStill = level.time + 500; //func brush under.. wait + } + } + + if (bs->frame_Waypoint_Vis || (bs->wpCurrent->flags & WPFLAG_NOVIS)) + { + if (g_RMG.integer) + { + bs->wpSeenTime = level.time + 5000; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it + } + else + { + bs->wpSeenTime = level.time + 1500; //if we lose sight of the point, we have 1.5 seconds to regain it before we drop it + } + } + VectorCopy(bs->wpCurrent->origin, bs->goalPosition); + if (bs->wpDirection) + { + goalWPIndex = bs->wpCurrent->index-1; + } + else + { + goalWPIndex = bs->wpCurrent->index+1; + } + + if (bs->wpCamping) + { + VectorSubtract(bs->wpCampingTo->origin, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + + VectorSubtract(bs->origin, bs->wpCamping->origin, a); + if (VectorLength(a) < 64) + { + VectorCopy(bs->wpCamping->origin, bs->goalPosition); + bs->beStill = level.time + 1000; + + if (!bs->campStanding) + { + bs->duckTime = level.time + 1000; + } + } + } + else if (gWPArray[goalWPIndex] && gWPArray[goalWPIndex]->inuse && + !(gLevelFlags & LEVELFLAG_NOPOINTPREDICTION)) + { + VectorSubtract(gWPArray[goalWPIndex]->origin, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + } + else + { + VectorSubtract(bs->wpCurrent->origin, bs->origin, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + } + + if (bs->destinationGrabTime < level.time /*&& (!bs->wpDestination || (bs->currentEnemy && bs->frame_Enemy_Vis))*/) + { + GetIdealDestination(bs); + } + + if (bs->wpCurrent && bs->wpDestination) + { + if (TotalTrailDistance(bs->wpCurrent->index, bs->wpDestination->index, bs) == -1) + { + bs->wpDestination = NULL; + bs->destinationGrabTime = level.time + 10000; + } + } + + if (g_RMG.integer) + { + if (bs->frame_Waypoint_Vis) + { + if (bs->wpCurrent && !bs->wpCurrent->flags) + { + wpTouchDist *= 3; + } + } + } + + if (bs->frame_Waypoint_Len < wpTouchDist || (g_RMG.integer && bs->frame_Waypoint_Len < wpTouchDist*2)) + { + WPTouchRoutine(bs); + + if (!bs->wpDirection) + { + desiredIndex = bs->wpCurrent->index+1; + } + else + { + desiredIndex = bs->wpCurrent->index-1; + } + + if (gWPArray[desiredIndex] && + gWPArray[desiredIndex]->inuse && + desiredIndex < gWPNum && + desiredIndex >= 0 && + PassWayCheck(bs, desiredIndex)) + { + bs->wpCurrent = gWPArray[desiredIndex]; + } + else + { + if (bs->wpDestination) + { + bs->wpDestination = NULL; + bs->destinationGrabTime = level.time + 10000; + } + + if (bs->wpDirection) + { + bs->wpDirection = 0; + } + else + { + bs->wpDirection = 1; + } + } + } + } + else //We can't find a waypoint, going to need a fallback routine. + { + /*if (g_gametype.integer == GT_DUEL)*/ + { //helps them get out of messy situations + /*if ((level.time - bs->forceJumpChargeTime) > 3500) + { + bs->forceJumpChargeTime = level.time + 2000; + trap_EA_MoveForward(bs->client); + } + */ + bs->jumpTime = level.time + 1500; + bs->jumpHoldTime = level.time + 1500; + bs->jDelay = 0; + } + doingFallback = BotFallbackNavigation(bs); + } + + if (g_RMG.integer) + { //for RMG if the bot sticks around an area too long, jump around randomly some to spread to a new area (horrible hacky method) + vec3_t vSubDif; + + VectorSubtract(bs->origin, bs->lastSignificantAreaChange, vSubDif); + if (VectorLength(vSubDif) > 1500) + { + VectorCopy(bs->origin, bs->lastSignificantAreaChange); + bs->lastSignificantChangeTime = level.time + 20000; + } + + if (bs->lastSignificantChangeTime < level.time) + { + bs->iHaveNoIdeaWhereIAmGoing = level.time + 17000; + } + } + + if (bs->iHaveNoIdeaWhereIAmGoing > level.time && !bs->currentEnemy) + { + VectorCopy(preFrameGAngles, bs->goalAngles); + bs->wpCurrent = NULL; + bs->wpSwitchTime = level.time + 150; + doingFallback = BotFallbackNavigation(bs); + bs->jumpTime = level.time + 150; + bs->jumpHoldTime = level.time + 150; + bs->jDelay = 0; + bs->lastSignificantChangeTime = level.time + 25000; + } + + if (bs->wpCurrent && g_RMG.integer) + { + qboolean doJ = qfalse; + + if (bs->wpCurrent->origin[2]-192 > bs->origin[2]) + { + doJ = qtrue; + } + else if ((bs->wpTravelTime - level.time) < 5000 && bs->wpCurrent->origin[2]-64 > bs->origin[2]) + { + doJ = qtrue; + } + else if ((bs->wpTravelTime - level.time) < 7000 && (bs->wpCurrent->flags & WPFLAG_RED_FLAG)) + { + if ((level.time - bs->jumpTime) > 200) + { + bs->jumpTime = level.time + 100; + bs->jumpHoldTime = level.time + 100; + bs->jDelay = 0; + } + } + else if ((bs->wpTravelTime - level.time) < 7000 && (bs->wpCurrent->flags & WPFLAG_BLUE_FLAG)) + { + if ((level.time - bs->jumpTime) > 200) + { + bs->jumpTime = level.time + 100; + bs->jumpHoldTime = level.time + 100; + bs->jDelay = 0; + } + } + else if (bs->wpCurrent->index > 0) + { + if ((bs->wpTravelTime - level.time) < 7000) + { + if ((gWPArray[bs->wpCurrent->index-1]->flags & WPFLAG_RED_FLAG) || + (gWPArray[bs->wpCurrent->index-1]->flags & WPFLAG_BLUE_FLAG)) + { + if ((level.time - bs->jumpTime) > 200) + { + bs->jumpTime = level.time + 100; + bs->jumpHoldTime = level.time + 100; + bs->jDelay = 0; + } + } + } + } + + if (doJ) + { + bs->jumpTime = level.time + 1500; + bs->jumpHoldTime = level.time + 1500; + bs->jDelay = 0; + } + } + + if (doingFallback) + { + bs->doingFallback = qtrue; + } + else + { + bs->doingFallback = qfalse; + } + + if (bs->timeToReact < level.time && bs->currentEnemy && bs->enemySeenTime > level.time + (ENEMY_FORGET_MS - (ENEMY_FORGET_MS*0.2))) + { + if (bs->frame_Enemy_Vis) + { + cBAI = CombatBotAI(bs, thinktime); + } + else if (bs->cur_ps.weaponstate == WEAPON_CHARGING_ALT) + { //keep charging in case we see him again before we lose track of him + bs->doAltAttack = 1; + } + else if (bs->cur_ps.weaponstate == WEAPON_CHARGING) + { //keep charging in case we see him again before we lose track of him + bs->doAttack = 1; + } + + if (bs->destinationGrabTime > level.time + 100) + { + bs->destinationGrabTime = level.time + 100; //assures that we will continue staying within a general area of where we want to be in a combat situation + } + + if (bs->currentEnemy->client) + { + VectorCopy(bs->currentEnemy->client->ps.origin, headlevel); + headlevel[2] += bs->currentEnemy->client->ps.viewheight; + } + else + { + VectorCopy(bs->currentEnemy->client->ps.origin, headlevel); + } + + if (!bs->frame_Enemy_Vis) + { + //if (!bs->hitSpotted && VectorLength(a) > 256) + if (OrgVisible(bs->eye, bs->lastEnemySpotted, -1)) + { + VectorCopy(bs->lastEnemySpotted, headlevel); + VectorSubtract(headlevel, bs->eye, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + + if (bs->cur_ps.weapon == WP_FLECHETTE && + bs->cur_ps.weaponstate == WEAPON_READY && + bs->currentEnemy && bs->currentEnemy->client) + { + mLen = VectorLength(a) > 128; + if (mLen > 128 && mLen < 1024) + { + VectorSubtract(bs->currentEnemy->client->ps.origin, bs->lastEnemySpotted, a); + + if (VectorLength(a) < 300) + { + bs->doAltAttack = 1; + } + } + } + } + } + else + { + bLeadAmount = BotWeaponCanLead(bs); + if ((bs->skills.accuracy/bs->settings.skill) <= 8 && + bLeadAmount) + { + BotAimLeading(bs, headlevel, bLeadAmount); + } + else + { + VectorSubtract(headlevel, bs->eye, a); + vectoangles(a, ang); + VectorCopy(ang, bs->goalAngles); + } + + BotAimOffsetGoalAngles(bs); + } + } + + if (bs->cur_ps.saberInFlight) + { + bs->saberThrowTime = level.time + Q_irand(4000, 10000); + } + + if (bs->currentEnemy) + { + if (BotGetWeaponRange(bs) == BWEAPONRANGE_SABER) + { + int saberRange = SABER_ATTACK_RANGE; + + VectorSubtract(bs->currentEnemy->client->ps.origin, bs->eye, a_fo); + vectoangles(a_fo, a_fo); + + if (bs->saberPowerTime < level.time) + { //Don't just use strong attacks constantly, switch around a bit + if (Q_irand(1, 10) <= 5) + { + bs->saberPower = qtrue; + } + else + { + bs->saberPower = qfalse; + } + + bs->saberPowerTime = level.time + Q_irand(3000, 15000); + } + + if ( g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_STAFF + && g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_DUAL ) + { + if (bs->currentEnemy->health > 75 + && g_entities[bs->client].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > 2) + { + if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_STRONG + && bs->saberPower) + { //if we are up against someone with a lot of health and we have a strong attack available, then h4q them + Cmd_SaberAttackCycle_f(&g_entities[bs->client]); + } + } + else if (bs->currentEnemy->health > 40 + && g_entities[bs->client].client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] > 1) + { + if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_MEDIUM) + { //they're down on health a little, use level 2 if we can + Cmd_SaberAttackCycle_f(&g_entities[bs->client]); + } + } + else + { + if (g_entities[bs->client].client->ps.fd.saberAnimLevel != SS_FAST) + { //they've gone below 40 health, go at them with quick attacks + Cmd_SaberAttackCycle_f(&g_entities[bs->client]); + } + } + } + + if (g_gametype.integer == GT_SINGLE_PLAYER) + { + saberRange *= 3; + } + + if (bs->frame_Enemy_Len <= saberRange) + { + SaberCombatHandling(bs); + + if (bs->frame_Enemy_Len < 80) + { + meleestrafe = 1; + } + } + else if (bs->saberThrowTime < level.time && !bs->cur_ps.saberInFlight && + (bs->cur_ps.fd.forcePowersKnown & (1 << FP_SABERTHROW)) && + InFieldOfVision(bs->viewangles, 30, a_fo) && + bs->frame_Enemy_Len < BOT_SABER_THROW_RANGE && + bs->cur_ps.fd.saberAnimLevel != SS_STAFF) + { + bs->doAltAttack = 1; + bs->doAttack = 0; + } + else if (bs->cur_ps.saberInFlight && bs->frame_Enemy_Len > 300 && bs->frame_Enemy_Len < BOT_SABER_THROW_RANGE) + { + bs->doAltAttack = 1; + bs->doAttack = 0; + } + } + else if (BotGetWeaponRange(bs) == BWEAPONRANGE_MELEE) + { + if (bs->frame_Enemy_Len <= MELEE_ATTACK_RANGE) + { + MeleeCombatHandling(bs); + meleestrafe = 1; + } + } + } + + if (doingFallback && bs->currentEnemy) //just stand and fire if we have no idea where we are + { + VectorCopy(bs->origin, bs->goalPosition); + } + + if (bs->forceJumping > level.time) + { + VectorCopy(bs->origin, noz_x); + VectorCopy(bs->goalPosition, noz_y); + + noz_x[2] = noz_y[2]; + + VectorSubtract(noz_x, noz_y, noz_x); + + if (VectorLength(noz_x) < 32) + { + fjHalt = 1; + } + } + + if (bs->doChat && bs->chatTime > level.time && (!bs->currentEnemy || !bs->frame_Enemy_Vis)) + { + return; + } + else if (bs->doChat && bs->currentEnemy && bs->frame_Enemy_Vis) + { + //bs->chatTime = level.time + bs->chatTime_stored; + bs->doChat = 0; //do we want to keep the bot waiting to chat until after the enemy is gone? + bs->chatTeam = 0; + } + else if (bs->doChat && bs->chatTime <= level.time) + { + if (bs->chatTeam) + { + trap_EA_SayTeam(bs->client, bs->currentChat); + bs->chatTeam = 0; + } + else + { + trap_EA_Say(bs->client, bs->currentChat); + } + if (bs->doChat == 2) + { + BotReplyGreetings(bs); + } + bs->doChat = 0; + } + + CTFFlagMovement(bs); + + if (/*bs->wpDestination &&*/ bs->shootGoal && + /*bs->wpDestination->associated_entity == bs->shootGoal->s.number &&*/ + bs->shootGoal->health > 0 && bs->shootGoal->takedamage) + { + dif[0] = (bs->shootGoal->r.absmax[0]+bs->shootGoal->r.absmin[0])/2; + dif[1] = (bs->shootGoal->r.absmax[1]+bs->shootGoal->r.absmin[1])/2; + dif[2] = (bs->shootGoal->r.absmax[2]+bs->shootGoal->r.absmin[2])/2; + + if (!bs->currentEnemy || bs->frame_Enemy_Len > 256) + { //if someone is close then don't stop shooting them for this + VectorSubtract(dif, bs->eye, a); + vectoangles(a, a); + VectorCopy(a, bs->goalAngles); + + if (InFieldOfVision(bs->viewangles, 30, a) && + EntityVisibleBox(bs->origin, NULL, NULL, dif, bs->client, bs->shootGoal->s.number)) + { + bs->doAttack = 1; + } + } + } + + if (bs->cur_ps.hasDetPackPlanted) + { //check if our enemy gets near it and detonate if he does + BotCheckDetPacks(bs); + } + else if (bs->currentEnemy && bs->lastVisibleEnemyIndex == bs->currentEnemy->s.number && !bs->frame_Enemy_Vis && bs->plantTime < level.time && + !bs->doAttack && !bs->doAltAttack) + { + VectorSubtract(bs->origin, bs->hereWhenSpotted, a); + + if (bs->plantDecided > level.time || (bs->frame_Enemy_Len < BOT_PLANT_DISTANCE*2 && VectorLength(a) < BOT_PLANT_DISTANCE)) + { + mineSelect = BotSelectChoiceWeapon(bs, WP_TRIP_MINE, 0); + detSelect = BotSelectChoiceWeapon(bs, WP_DET_PACK, 0); + if (bs->cur_ps.hasDetPackPlanted) + { + detSelect = 0; + } + + if (bs->plantDecided > level.time && bs->forceWeaponSelect && + bs->cur_ps.weapon == bs->forceWeaponSelect) + { + bs->doAttack = 1; + bs->plantDecided = 0; + bs->plantTime = level.time + BOT_PLANT_INTERVAL; + bs->plantContinue = level.time + 500; + bs->beStill = level.time + 500; + } + else if (mineSelect || detSelect) + { + if (BotSurfaceNear(bs)) + { + if (!mineSelect) + { //if no mines use detpacks, otherwise use mines + mineSelect = WP_DET_PACK; + } + else + { + mineSelect = WP_TRIP_MINE; + } + + detSelect = BotSelectChoiceWeapon(bs, mineSelect, 1); + + if (detSelect && detSelect != 2) + { //We have it and it is now our weapon + bs->plantDecided = level.time + 1000; + bs->forceWeaponSelect = mineSelect; + return; + } + else if (detSelect == 2) + { + bs->forceWeaponSelect = mineSelect; + return; + } + } + } + } + } + else if (bs->plantContinue < level.time) + { + bs->forceWeaponSelect = 0; + } + + if (g_gametype.integer == GT_JEDIMASTER && !bs->cur_ps.isJediMaster && bs->jmState == -1 && gJMSaberEnt && gJMSaberEnt->inuse) + { + vec3_t saberLen; + float fSaberLen = 0; + + VectorSubtract(bs->origin, gJMSaberEnt->r.currentOrigin, saberLen); + fSaberLen = VectorLength(saberLen); + + if (fSaberLen < 256) + { + if (OrgVisible(bs->origin, gJMSaberEnt->r.currentOrigin, bs->client)) + { + VectorCopy(gJMSaberEnt->r.currentOrigin, bs->goalPosition); + } + } + } + + if (bs->beStill < level.time && !WaitingForNow(bs, bs->goalPosition) && !fjHalt) + { + VectorSubtract(bs->goalPosition, bs->origin, bs->goalMovedir); + VectorNormalize(bs->goalMovedir); + + if (bs->jumpTime > level.time && bs->jDelay < level.time && + level.clients[bs->client].pers.cmd.upmove > 0) + { + // trap_EA_Move(bs->client, bs->origin, 5000); + bs->beStill = level.time + 200; + } + else + { + trap_EA_Move(bs->client, bs->goalMovedir, 5000); + } + + if (meleestrafe) + { + StrafeTracing(bs); + } + + if (bs->meleeStrafeDir && meleestrafe && bs->meleeStrafeDisable < level.time) + { + trap_EA_MoveRight(bs->client); + } + else if (meleestrafe && bs->meleeStrafeDisable < level.time) + { + trap_EA_MoveLeft(bs->client); + } + + if (BotTrace_Jump(bs, bs->goalPosition)) + { + bs->jumpTime = level.time + 100; + } + else if (BotTrace_Duck(bs, bs->goalPosition)) + { + bs->duckTime = level.time + 100; + } +#ifdef BOT_STRAFE_AVOIDANCE + else + { + int strafeAround = BotTrace_Strafe(bs, bs->goalPosition); + + if (strafeAround == STRAFEAROUND_RIGHT) + { + trap_EA_MoveRight(bs->client); + } + else if (strafeAround == STRAFEAROUND_LEFT) + { + trap_EA_MoveLeft(bs->client); + } + } +#endif + } + +#ifndef FORCEJUMP_INSTANTMETHOD + if (bs->forceJumpChargeTime > level.time) + { + bs->jumpTime = 0; + } +#endif + + if (bs->jumpPrep > level.time) + { + bs->forceJumpChargeTime = 0; + } + + if (bs->forceJumpChargeTime > level.time) + { + bs->jumpHoldTime = ((bs->forceJumpChargeTime - level.time)/2) + level.time; + bs->forceJumpChargeTime = 0; + } + + if (bs->jumpHoldTime > level.time) + { + bs->jumpTime = bs->jumpHoldTime; + } + + if (bs->jumpTime > level.time && bs->jDelay < level.time) + { + if (bs->jumpHoldTime > level.time) + { + trap_EA_Jump(bs->client); + if (bs->wpCurrent) + { + if ((bs->wpCurrent->origin[2] - bs->origin[2]) < 64) + { + trap_EA_MoveForward(bs->client); + } + } + else + { + trap_EA_MoveForward(bs->client); + } + if (g_entities[bs->client].client->ps.groundEntityNum == ENTITYNUM_NONE) + { + g_entities[bs->client].client->ps.pm_flags |= PMF_JUMP_HELD; + } + } + else if (!(bs->cur_ps.pm_flags & PMF_JUMP_HELD)) + { + trap_EA_Jump(bs->client); + } + } + + if (bs->duckTime > level.time) + { + trap_EA_Crouch(bs->client); + } + + if ( bs->dangerousObject && bs->dangerousObject->inuse && bs->dangerousObject->health > 0 && + bs->dangerousObject->takedamage && (!bs->frame_Enemy_Vis || !bs->currentEnemy) && + (BotGetWeaponRange(bs) == BWEAPONRANGE_MID || BotGetWeaponRange(bs) == BWEAPONRANGE_LONG) && + bs->cur_ps.weapon != WP_DET_PACK && bs->cur_ps.weapon != WP_TRIP_MINE && + !bs->shootGoal ) + { + float danLen; + + VectorSubtract(bs->dangerousObject->r.currentOrigin, bs->eye, a); + + danLen = VectorLength(a); + + if (danLen > 256) + { + vectoangles(a, a); + VectorCopy(a, bs->goalAngles); + + if (Q_irand(1, 10) < 5) + { + bs->goalAngles[YAW] += Q_irand(0, 3); + bs->goalAngles[PITCH] += Q_irand(0, 3); + } + else + { + bs->goalAngles[YAW] -= Q_irand(0, 3); + bs->goalAngles[PITCH] -= Q_irand(0, 3); + } + + if (InFieldOfVision(bs->viewangles, 30, a) && + EntityVisibleBox(bs->origin, NULL, NULL, bs->dangerousObject->r.currentOrigin, bs->client, bs->dangerousObject->s.number)) + { + bs->doAttack = 1; + } + } + } + + if (PrimFiring(bs) || + AltFiring(bs)) + { + friendInLOF = CheckForFriendInLOF(bs); + + if (friendInLOF) + { + if (PrimFiring(bs)) + { + KeepPrimFromFiring(bs); + } + if (AltFiring(bs)) + { + KeepAltFromFiring(bs); + } + if (useTheForce && forceHostile) + { + useTheForce = 0; + } + + if (!useTheForce && friendInLOF->client) + { //we have a friend here and are not currently using force powers, see if we can help them out + if (friendInLOF->health <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_HEAL; + useTheForce = 1; + forceHostile = 0; + } + else if (friendInLOF->client->ps.fd.forcePower <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_FORCE; + useTheForce = 1; + forceHostile = 0; + } + } + } + } + else if (g_gametype.integer >= GT_TEAM) + { //still check for anyone to help.. + friendInLOF = CheckForFriendInLOF(bs); + + if (!useTheForce && friendInLOF) + { + if (friendInLOF->health <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_HEAL]][FP_TEAM_HEAL]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_HEAL; + useTheForce = 1; + forceHostile = 0; + } + else if (friendInLOF->client->ps.fd.forcePower <= 50 && level.clients[bs->client].ps.fd.forcePower > forcePowerNeeded[level.clients[bs->client].ps.fd.forcePowerLevel[FP_TEAM_FORCE]][FP_TEAM_FORCE]) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_TEAM_FORCE; + useTheForce = 1; + forceHostile = 0; + } + } + } + + if (bs->doAttack && bs->cur_ps.weapon == WP_DET_PACK && + bs->cur_ps.hasDetPackPlanted) + { //maybe a bit hackish, but bots only want to plant one of these at any given time to avoid complications + bs->doAttack = 0; + } + + if (bs->doAttack && bs->cur_ps.weapon == WP_SABER && + bs->saberDefending && bs->currentEnemy && bs->currentEnemy->client && + BotWeaponBlockable(bs->currentEnemy->client->ps.weapon) ) + { + bs->doAttack = 0; + } + + if (bs->cur_ps.saberLockTime > level.time) + { + if (rand()%10 < 5) + { + bs->doAttack = 1; + } + else + { + bs->doAttack = 0; + } + } + + if (bs->botChallengingTime > level.time) + { + bs->doAttack = 0; + bs->doAltAttack = 0; + } + + if (bs->cur_ps.weapon == WP_SABER && + bs->cur_ps.saberInFlight && + !bs->cur_ps.saberEntityNum) + { //saber knocked away, keep trying to get it back + bs->doAttack = 1; + bs->doAltAttack = 0; + } + + if (bs->doAttack) + { + trap_EA_Attack(bs->client); + } + else if (bs->doAltAttack) + { + trap_EA_Alt_Attack(bs->client); + } + + if (useTheForce && forceHostile && bs->botChallengingTime > level.time) + { + useTheForce = qfalse; + } + + if (useTheForce) + { +#ifndef FORCEJUMP_INSTANTMETHOD + if (bs->forceJumpChargeTime > level.time) + { + level.clients[bs->client].ps.fd.forcePowerSelected = FP_LEVITATION; + trap_EA_ForcePower(bs->client); + } + else + { +#endif + if (bot_forcepowers.integer && !g_forcePowerDisable.integer) + { + trap_EA_ForcePower(bs->client); + } +#ifndef FORCEJUMP_INSTANTMETHOD + } +#endif + } + + MoveTowardIdealAngles(bs); +} + +int gUpdateVars = 0; + +/* +================== +BotAIStartFrame +================== +*/ +int BotAIStartFrame(int time) { + int i; + int elapsed_time, thinktime; + static int local_time; + static int botlib_residual; + static int lastbotthink_time; + + if (gUpdateVars < level.time) + { + trap_Cvar_Update(&bot_pvstype); + trap_Cvar_Update(&bot_camp); + trap_Cvar_Update(&bot_attachments); + trap_Cvar_Update(&bot_forgimmick); + trap_Cvar_Update(&bot_honorableduelacceptance); +#ifndef FINAL_BUILD + trap_Cvar_Update(&bot_getinthecarrr); +#endif + gUpdateVars = level.time + 1000; + } + + G_CheckBotSpawn(); + + //rww - addl bot frame functions + if (gBotEdit) + { + trap_Cvar_Update(&bot_wp_info); + BotWaypointRender(); + } + + UpdateEventTracker(); + //end rww + + //cap the bot think time + //if the bot think time changed we should reschedule the bots + if (BOT_THINK_TIME != lastbotthink_time) { + lastbotthink_time = BOT_THINK_TIME; + BotScheduleBotThink(); + } + + elapsed_time = time - local_time; + local_time = time; + + if (elapsed_time > BOT_THINK_TIME) thinktime = elapsed_time; + else thinktime = BOT_THINK_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 (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; +} + +/* +============== +BotAISetup +============== +*/ +int BotAISetup( int restart ) { + //rww - new bot cvars.. + trap_Cvar_Register(&bot_forcepowers, "bot_forcepowers", "1", CVAR_CHEAT); + trap_Cvar_Register(&bot_forgimmick, "bot_forgimmick", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_honorableduelacceptance, "bot_honorableduelacceptance", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_pvstype, "bot_pvstype", "1", CVAR_CHEAT); +#ifndef FINAL_BUILD + trap_Cvar_Register(&bot_getinthecarrr, "bot_getinthecarrr", "0", 0); +#endif + +#ifdef _DEBUG + trap_Cvar_Register(&bot_nogoals, "bot_nogoals", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_debugmessages, "bot_debugmessages", "0", CVAR_CHEAT); +#endif + + trap_Cvar_Register(&bot_attachments, "bot_attachments", "1", 0); + trap_Cvar_Register(&bot_camp, "bot_camp", "1", 0); + + trap_Cvar_Register(&bot_wp_info, "bot_wp_info", "1", 0); + trap_Cvar_Register(&bot_wp_edit, "bot_wp_edit", "0", CVAR_CHEAT); + trap_Cvar_Register(&bot_wp_clearweight, "bot_wp_clearweight", "1", 0); + trap_Cvar_Register(&bot_wp_distconnect, "bot_wp_distconnect", "1", 0); + trap_Cvar_Register(&bot_wp_visconnect, "bot_wp_visconnect", "1", 0); + + trap_Cvar_Update(&bot_forcepowers); + //end rww + + //if the game is restarted for a tournament + if (restart) { + return qtrue; + } + + //initialize the bot states + memset( botstates, 0, sizeof(botstates) ); + + if (!trap_BotLibSetup()) + { + return qfalse; //wts?! + } + + return qtrue; +} + +/* +============== +BotAIShutdown +============== +*/ +int BotAIShutdown( int restart ) { + + int i; + + //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) { + BotAIShutdownClient(botstates[i]->client, restart); + } + } + //don't shutdown the bot library + } + else { + trap_BotLibShutdown(); + } + return qtrue; +} + diff --git a/code/game/ai_main.h b/code/game/ai_main.h new file mode 100644 index 0000000..ad72a47 --- /dev/null +++ b/code/game/ai_main.h @@ -0,0 +1,411 @@ +#include "bg_saga.h" + +#define DEFAULT_FORCEPOWERS "5-1-000000000000000000" + +//#define FORCEJUMP_INSTANTMETHOD 1 + +#ifdef _XBOX // No bot has more than 150 bytes of chat right now +#define MAX_CHAT_BUFFER_SIZE 256 +#else +#define MAX_CHAT_BUFFER_SIZE 8192 +#endif +#define MAX_CHAT_LINE_SIZE 128 + +#define TABLE_BRANCH_DISTANCE 32 +#define MAX_NODETABLE_SIZE 16384 + +#define MAX_LOVED_ONES 4 +#define MAX_ATTACHMENT_NAME 64 + +#define MAX_FORCE_INFO_SIZE 2048 + +#define WPFLAG_JUMP 0x00000010 //jump when we hit this +#define WPFLAG_DUCK 0x00000020 //duck while moving around here +#define WPFLAG_NOVIS 0x00000400 //go here for a bit even with no visibility +#define WPFLAG_SNIPEORCAMPSTAND 0x00000800 //a good position to snipe or camp - stand +#define WPFLAG_WAITFORFUNC 0x00001000 //wait for a func brushent under this point before moving here +#define WPFLAG_SNIPEORCAMP 0x00002000 //a good position to snipe or camp - crouch +#define WPFLAG_ONEWAY_FWD 0x00004000 //can only go forward on the trial from here (e.g. went over a ledge) +#define WPFLAG_ONEWAY_BACK 0x00008000 //can only go backward on the trail from here +#define WPFLAG_GOALPOINT 0x00010000 //make it a goal to get here.. goal points will be decided by setting "weight" values +#define WPFLAG_RED_FLAG 0x00020000 //red flag +#define WPFLAG_BLUE_FLAG 0x00040000 //blue flag +#define WPFLAG_SIEGE_REBELOBJ 0x00080000 //rebel siege objective +#define WPFLAG_SIEGE_IMPERIALOBJ 0x00100000 //imperial siege objective +#define WPFLAG_NOMOVEFUNC 0x00200000 //don't move over if a func is under + +#define WPFLAG_CALCULATED 0x00400000 //don't calculate it again +#define WPFLAG_NEVERONEWAY 0x00800000 //never flag it as one-way + +#define LEVELFLAG_NOPOINTPREDICTION 1 //don't take waypoint beyond current into account when adjusting path view angles +#define LEVELFLAG_IGNOREINFALLBACK 2 //ignore enemies when in a fallback navigation routine +#define LEVELFLAG_IMUSTNTRUNAWAY 4 //don't be scared + +#define WP_KEEP_FLAG_DIST 128 + +#define BWEAPONRANGE_MELEE 1 +#define BWEAPONRANGE_MID 2 +#define BWEAPONRANGE_LONG 3 +#define BWEAPONRANGE_SABER 4 + +#define MELEE_ATTACK_RANGE 256 +#define SABER_ATTACK_RANGE 128 +#define MAX_CHICKENWUSS_TIME 10000 //wait 10 secs between checking which run-away path to take + +#define BOT_RUN_HEALTH 40 +#define BOT_WPTOUCH_DISTANCE 32 +#define ENEMY_FORGET_MS 10000 +//if our enemy isn't visible within 10000ms (aprx 10sec) then "forget" about him and treat him like every other threat, but still look for +//more immediate threats while main enemy is not visible + +#define BOT_PLANT_DISTANCE 256 //plant if within this radius from the last spotted enemy position +#define BOT_PLANT_INTERVAL 15000 //only plant once per 15 seconds at max +#define BOT_PLANT_BLOW_DISTANCE 256 //blow det packs if enemy is within this radius and I am further away than the enemy + +#define BOT_MAX_WEAPON_GATHER_TIME 1000 //spend a max of 1 second after spawn issuing orders to gather weapons before attacking enemy base +#define BOT_MAX_WEAPON_CHASE_TIME 15000 //time to spend gathering the weapon before persuing the enemy base (in case it takes longer than expected) + +#define BOT_MAX_WEAPON_CHASE_CTF 5000 //time to spend gathering the weapon before persuing the enemy base (in case it takes longer than expected) [ctf-only] + +#define BOT_MIN_SIEGE_GOAL_SHOOT 1024 +#define BOT_MIN_SIEGE_GOAL_TRAVEL 128 + +#define BASE_GUARD_DISTANCE 256 //guarding the flag +#define BASE_FLAGWAIT_DISTANCE 256 //has the enemy flag and is waiting in his own base for his flag to be returned +#define BASE_GETENEMYFLAG_DISTANCE 256 //waiting around to get the enemy's flag + +#define BOT_FLAG_GET_DISTANCE 256 + +#define BOT_SABER_THROW_RANGE 800 + +typedef enum +{ + CTFSTATE_NONE, + CTFSTATE_ATTACKER, + CTFSTATE_DEFENDER, + CTFSTATE_RETRIEVAL, + CTFSTATE_GUARDCARRIER, + CTFSTATE_GETFLAGHOME, + CTFSTATE_MAXCTFSTATES +} bot_ctf_state_t; + +typedef enum +{ + SIEGESTATE_NONE, + SIEGESTATE_ATTACKER, + SIEGESTATE_DEFENDER, + SIEGESTATE_MAXSIEGESTATES +} bot_siege_state_t; + +typedef enum +{ + TEAMPLAYSTATE_NONE, + TEAMPLAYSTATE_FOLLOWING, + TEAMPLAYSTATE_ASSISTING, + TEAMPLAYSTATE_REGROUP, + TEAMPLAYSTATE_MAXTPSTATES +} bot_teamplay_state_t; + +typedef struct botattachment_s +{ + int level; + char name[MAX_ATTACHMENT_NAME]; +} botattachment_t; + +typedef struct nodeobject_s +{ + vec3_t origin; +// int index; + float weight; + int flags; +#ifdef _XBOX + short neighbornum; + short inuse; +#else + int neighbornum; + int inuse; +#endif +} nodeobject_t; + +typedef struct boteventtracker_s +{ + int eventSequence; + int events[MAX_PS_EVENTS]; + float eventTime; +} boteventtracker_t; + +typedef struct botskills_s +{ + int reflex; + float accuracy; + float turnspeed; + float turnspeed_combat; + float maxturn; + int perfectaim; +} botskills_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 + usercmd_t lastucmd; //usercmd from last frame + bot_settings_t settings; //several bot settings + float thinktime; //time the bot thinks this frame + vec3_t origin; //origin of the bot + vec3_t velocity; //velocity of the bot + vec3_t eye; //eye coordinates of the bot + int setupcount; //true when the bot has just been setup + float ltime; //local bot time + float entergame_time; //time the bot entered the game + int ms; //move state of the bot + int gs; //goal state of the bot + int ws; //weapon state of the bot + vec3_t viewangles; //current view angles + vec3_t ideal_viewangles; //ideal view angles + vec3_t viewanglespeed; + + //rww - new AI values + gentity_t *currentEnemy; + gentity_t *revengeEnemy; + + gentity_t *squadLeader; + + gentity_t *lastHurt; + gentity_t *lastAttacked; + + gentity_t *wantFlag; + + gentity_t *touchGoal; + gentity_t *shootGoal; + + gentity_t *dangerousObject; + + vec3_t staticFlagSpot; + + int revengeHateLevel; + int isSquadLeader; + + int squadRegroupInterval; + int squadCannotLead; + + int lastDeadTime; + + wpobject_t *wpCurrent; + wpobject_t *wpDestination; + wpobject_t *wpStoreDest; + vec3_t goalAngles; + vec3_t goalMovedir; + vec3_t goalPosition; + + vec3_t lastEnemySpotted; + vec3_t hereWhenSpotted; + int lastVisibleEnemyIndex; + int hitSpotted; + + int wpDirection; + + float destinationGrabTime; + float wpSeenTime; + float wpTravelTime; + float wpDestSwitchTime; + float wpSwitchTime; + float wpDestIgnoreTime; + + float timeToReact; + + float enemySeenTime; + + float chickenWussCalculationTime; + + float beStill; + float duckTime; + float jumpTime; + float jumpHoldTime; + float jumpPrep; + float forceJumping; + float jDelay; + + float aimOffsetTime; + float aimOffsetAmtYaw; + float aimOffsetAmtPitch; + + float frame_Waypoint_Len; + int frame_Waypoint_Vis; + float frame_Enemy_Len; + int frame_Enemy_Vis; + + int isCamper; + float isCamping; + wpobject_t *wpCamping; + wpobject_t *wpCampingTo; + qboolean campStanding; + + int randomNavTime; + int randomNav; + + int saberSpecialist; + + int canChat; + int chatFrequency; + char currentChat[MAX_CHAT_LINE_SIZE]; + float chatTime; + float chatTime_stored; + int doChat; + int chatTeam; + gentity_t *chatObject; + gentity_t *chatAltObject; + + float meleeStrafeTime; + int meleeStrafeDir; + float meleeStrafeDisable; + + int altChargeTime; + + float escapeDirTime; + + float dontGoBack; + + int doAttack; + int doAltAttack; + + int forceWeaponSelect; + int virtualWeapon; + + int plantTime; + int plantDecided; + int plantContinue; + int plantKillEmAll; + + int runningLikeASissy; + int runningToEscapeThreat; + + //char chatBuffer[MAX_CHAT_BUFFER_SIZE]; + //Since we're once again not allocating bot structs dynamically, + //shoving a 64k chat buffer into one is a bad thing. + + botskills_t skills; + + botattachment_t loved[MAX_LOVED_ONES]; + int lovednum; + + int loved_death_thresh; + + int deathActivitiesDone; + + float botWeaponWeights[WP_NUM_WEAPONS]; + + int ctfState; + + int siegeState; + + int teamplayState; + + int jmState; + + int state_Forced; //set by player ordering menu + + int saberDefending; + int saberDefendDecideTime; + int saberBFTime; + int saberBTime; + int saberSTime; + int saberThrowTime; + + qboolean saberPower; + int saberPowerTime; + + int botChallengingTime; + + char forceinfo[MAX_FORCE_INFO_SIZE]; + +#ifndef FORCEJUMP_INSTANTMETHOD + int forceJumpChargeTime; +#endif + + int doForcePush; + + int noUseTime; + qboolean doingFallback; + + int iHaveNoIdeaWhereIAmGoing; + vec3_t lastSignificantAreaChange; + int lastSignificantChangeTime; + + int forceMove_Forward; + int forceMove_Right; + int forceMove_Up; + //end rww +} bot_state_t; + +void *B_TempAlloc(int size); +void B_TempFree(int size); + +void *B_Alloc(int size); +void B_Free(void *ptr); + +//resets the whole bot state +void BotResetState(bot_state_t *bs); +//returns the number of bots in the game +int NumBots(void); + +void BotUtilizePersonality(bot_state_t *bs); +int BotDoChat(bot_state_t *bs, char *section, int always); +void StandardBotAI(bot_state_t *bs, float thinktime); +void BotWaypointRender(void); +int OrgVisibleBox(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore); +int BotIsAChickenWuss(bot_state_t *bs); +int GetNearestVisibleWP(vec3_t org, int ignore); +int GetBestIdleGoal(bot_state_t *bs); + +char *ConcatArgs( int start ); + +extern vmCvar_t bot_forcepowers; +extern vmCvar_t bot_forgimmick; +extern vmCvar_t bot_honorableduelacceptance; +#ifdef _DEBUG +extern vmCvar_t bot_nogoals; +extern vmCvar_t bot_debugmessages; +#endif + +extern vmCvar_t bot_attachments; +extern vmCvar_t bot_camp; + +extern vmCvar_t bot_wp_info; +extern vmCvar_t bot_wp_edit; +extern vmCvar_t bot_wp_clearweight; +extern vmCvar_t bot_wp_distconnect; +extern vmCvar_t bot_wp_visconnect; + +extern wpobject_t *flagRed; +extern wpobject_t *oFlagRed; +extern wpobject_t *flagBlue; +extern wpobject_t *oFlagBlue; + +extern gentity_t *eFlagRed; +extern gentity_t *eFlagBlue; + +extern char gBotChatBuffer[MAX_CLIENTS][MAX_CHAT_BUFFER_SIZE]; +extern float gWPRenderTime; +extern float gDeactivated; +extern float gBotEdit; +extern int gWPRenderedFrame; + +#include "../namespace_begin.h" +extern wpobject_t *gWPArray[MAX_WPARRAY_SIZE]; +extern int gWPNum; +#include "../namespace_end.h" + +extern int gLastPrintedIndex; +#ifndef _XBOX +extern nodeobject_t nodetable[MAX_NODETABLE_SIZE]; +#endif +extern int nodenum; + +extern int gLevelFlags; + +extern float floattime; +#define FloatTime() floattime diff --git a/code/game/ai_util.c b/code/game/ai_util.c new file mode 100644 index 0000000..8072094 --- /dev/null +++ b/code/game/ai_util.c @@ -0,0 +1,867 @@ +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" +#include "ai_main.h" + +#ifdef BOT_ZMALLOC +#define MAX_BALLOC 8192 + +void *BAllocList[MAX_BALLOC]; +#endif + +char gBotChatBuffer[MAX_CLIENTS][MAX_CHAT_BUFFER_SIZE]; + +void *B_TempAlloc(int size) +{ + return BG_TempAlloc(size); +} + +void B_TempFree(int size) +{ + BG_TempFree(size); +} + + +void *B_Alloc(int size) +{ +#ifdef BOT_ZMALLOC + void *ptr = NULL; + int i = 0; + +#ifdef BOTMEMTRACK + int free = 0; + int used = 0; + + while (i < MAX_BALLOC) + { + if (!BAllocList[i]) + { + free++; + } + else + { + used++; + } + + i++; + } + + G_Printf("Allocations used: %i\nFree allocation slots: %i\n", used, free); + + i = 0; +#endif + + ptr = trap_BotGetMemoryGame(size); + + while (i < MAX_BALLOC) + { + if (!BAllocList[i]) + { + BAllocList[i] = ptr; + break; + } + i++; + } + + if (i == MAX_BALLOC) + { + //If this happens we'll have to rely on this chunk being freed manually with B_Free, which it hopefully will be +#ifdef DEBUG + G_Printf("WARNING: MAXIMUM B_ALLOC ALLOCATIONS EXCEEDED\n"); +#endif + } + + return ptr; +#else + + return BG_Alloc(size); + +#endif +} + +void B_Free(void *ptr) +{ +#ifdef BOT_ZMALLOC + int i = 0; + +#ifdef BOTMEMTRACK + int free = 0; + int used = 0; + + while (i < MAX_BALLOC) + { + if (!BAllocList[i]) + { + free++; + } + else + { + used++; + } + + i++; + } + + G_Printf("Allocations used: %i\nFree allocation slots: %i\n", used, free); + + i = 0; +#endif + + while (i < MAX_BALLOC) + { + if (BAllocList[i] == ptr) + { + BAllocList[i] = NULL; + break; + } + + i++; + } + + if (i == MAX_BALLOC) + { + //Likely because the limit was exceeded and we're now freeing the chunk manually as we hoped would happen +#ifdef DEBUG + G_Printf("WARNING: Freeing allocation which is not in the allocation structure\n"); +#endif + } + + trap_BotFreeMemoryGame(ptr); +#endif +} + +void B_InitAlloc(void) +{ +#ifdef BOT_ZMALLOC + memset(BAllocList, 0, sizeof(BAllocList)); +#endif + + memset(gWPArray, 0, sizeof(gWPArray)); +} + +void B_CleanupAlloc(void) +{ +#ifdef BOT_ZMALLOC + int i = 0; + + while (i < MAX_BALLOC) + { + if (BAllocList[i]) + { + trap_BotFreeMemoryGame(BAllocList[i]); + BAllocList[i] = NULL; + } + + i++; + } +#endif +} + +int GetValueGroup(char *buf, char *group, char *outbuf) +{ + char *place, *placesecond; + int iplace; + int failure; + int i; + int startpoint, startletter; + int subg = 0; + + i = 0; + + iplace = 0; + + place = strstr(buf, group); + + if (!place) + { + return 0; + } + + startpoint = place - buf + strlen(group) + 1; + startletter = (place - buf) - 1; + + failure = 0; + + while (buf[startpoint+1] != '{' || buf[startletter] != '\n') + { + placesecond = strstr(place+1, group); + + if (placesecond) + { + startpoint += (placesecond - place); + startletter += (placesecond - place); + place = placesecond; + } + else + { + failure = 1; + break; + } + } + + if (failure) + { + return 0; + } + + //we have found the proper group name if we made it here, so find the opening brace and read into the outbuf + //until hitting the end brace + + while (buf[startpoint] != '{') + { + startpoint++; + } + + startpoint++; + + while (buf[startpoint] != '}' || subg) + { + if (buf[startpoint] == '{') + { + subg++; + } + else if (buf[startpoint] == '}') + { + subg--; + } + outbuf[i] = buf[startpoint]; + i++; + startpoint++; + } + outbuf[i] = '\0'; + + return 1; +} + +int GetPairedValue(char *buf, char *key, char *outbuf) +{ + char *place, *placesecond; + int startpoint, startletter; + int i, found; + + if (!buf || !key || !outbuf) + { + return 0; + } + + i = 0; + + while (buf[i] && buf[i] != '\0') + { + if (buf[i] == '/') + { + if (buf[i+1] && buf[i+1] != '\0' && buf[i+1] == '/') + { + while (buf[i] != '\n') + { + buf[i] = '/'; + i++; + } + } + } + i++; + } + + place = strstr(buf, key); + + if (!place) + { + return 0; + } + //tab == 9 + startpoint = place - buf + strlen(key); + startletter = (place - buf) - 1; + + found = 0; + + while (!found) + { + if (startletter == 0 || !buf[startletter] || buf[startletter] == '\0' || buf[startletter] == 9 || buf[startletter] == ' ' || buf[startletter] == '\n') + { + if (buf[startpoint] == '\0' || buf[startpoint] == 9 || buf[startpoint] == ' ' || buf[startpoint] == '\n') + { + found = 1; + break; + } + } + + placesecond = strstr(place+1, key); + + if (placesecond) + { + startpoint += placesecond - place; + startletter += placesecond - place; + place = placesecond; + } + else + { + place = NULL; + break; + } + + } + + if (!found || !place || !buf[startpoint] || buf[startpoint] == '\0') + { + return 0; + } + + while (buf[startpoint] == ' ' || buf[startpoint] == 9 || buf[startpoint] == '\n') + { + startpoint++; + } + + i = 0; + + while (buf[startpoint] && buf[startpoint] != '\0' && buf[startpoint] != '\n') + { + outbuf[i] = buf[startpoint]; + i++; + startpoint++; + } + + outbuf[i] = '\0'; + + return 1; +} + +int BotDoChat(bot_state_t *bs, char *section, int always) +{ + char *chatgroup; + int rVal; + int inc_1; + int inc_2; + int inc_n; + int lines; + int checkedline; + int getthisline; + gentity_t *cobject; + + if (!bs->canChat) + { + return 0; + } + + if (bs->doChat) + { //already have a chat scheduled + return 0; + } + + if (trap_Cvar_VariableIntegerValue("se_language")) + { //no chatting unless English. + return 0; + } + + if (Q_irand(1, 10) > bs->chatFrequency && !always) + { + return 0; + } + + bs->chatTeam = 0; + + chatgroup = (char *)B_TempAlloc(MAX_CHAT_BUFFER_SIZE); + + rVal = GetValueGroup(gBotChatBuffer[bs->client], section, chatgroup); + + if (!rVal) //the bot has no group defined for the specified chat event + { + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + return 0; + } + + inc_1 = 0; + inc_2 = 2; + + while (chatgroup[inc_2] && chatgroup[inc_2] != '\0') + { + if (chatgroup[inc_2] != 13 && chatgroup[inc_2] != 9) + { + chatgroup[inc_1] = chatgroup[inc_2]; + inc_1++; + } + inc_2++; + } + chatgroup[inc_1] = '\0'; + + inc_1 = 0; + + lines = 0; + + while (chatgroup[inc_1] && chatgroup[inc_1] != '\0') + { + if (chatgroup[inc_1] == '\n') + { + lines++; + } + inc_1++; + } + + if (!lines) + { + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + return 0; + } + + getthisline = Q_irand(0, (lines+1)); + + if (getthisline < 1) + { + getthisline = 1; + } + if (getthisline > lines) + { + getthisline = lines; + } + + checkedline = 1; + + inc_1 = 0; + + while (checkedline != getthisline) + { + if (chatgroup[inc_1] && chatgroup[inc_1] != '\0') + { + if (chatgroup[inc_1] == '\n') + { + inc_1++; + checkedline++; + } + } + + if (checkedline == getthisline) + { + break; + } + + inc_1++; + } + + //we're at the starting position of the desired line here + inc_2 = 0; + + while (chatgroup[inc_1] != '\n') + { + chatgroup[inc_2] = chatgroup[inc_1]; + inc_2++; + inc_1++; + } + chatgroup[inc_2] = '\0'; + + //trap_EA_Say(bs->client, chatgroup); + inc_1 = 0; + inc_2 = 0; + + if (strlen(chatgroup) > MAX_CHAT_LINE_SIZE) + { + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + return 0; + } + + while (chatgroup[inc_1]) + { + if (chatgroup[inc_1] == '%' && chatgroup[inc_1+1] != '%') + { + inc_1++; + + if (chatgroup[inc_1] == 's' && bs->chatObject) + { + cobject = bs->chatObject; + } + else if (chatgroup[inc_1] == 'a' && bs->chatAltObject) + { + cobject = bs->chatAltObject; + } + else + { + cobject = NULL; + } + + if (cobject && cobject->client) + { + inc_n = 0; + + while (cobject->client->pers.netname[inc_n]) + { + bs->currentChat[inc_2] = cobject->client->pers.netname[inc_n]; + inc_2++; + inc_n++; + } + inc_2--; //to make up for the auto-increment below + } + } + else + { + bs->currentChat[inc_2] = chatgroup[inc_1]; + } + inc_2++; + inc_1++; + } + bs->currentChat[inc_2] = '\0'; + + if (strcmp(section, "GeneralGreetings") == 0) + { + bs->doChat = 2; + } + else + { + bs->doChat = 1; + } + bs->chatTime_stored = (strlen(bs->currentChat)*45)+Q_irand(1300, 1500); + bs->chatTime = level.time + bs->chatTime_stored; + + B_TempFree(MAX_CHAT_BUFFER_SIZE); //chatgroup + + return 1; +} + +void ParseEmotionalAttachments(bot_state_t *bs, char *buf) +{ + int i = 0; + int i_c = 0; + char tbuf[16]; + + while (buf[i] && buf[i] != '}') + { + while (buf[i] == ' ' || buf[i] == '{' || buf[i] == 9 || buf[i] == 13 || buf[i] == '\n') + { + i++; + } + + if (buf[i] && buf[i] != '}') + { + i_c = 0; + while (buf[i] != '{' && buf[i] != 9 && buf[i] != 13 && buf[i] != '\n') + { + bs->loved[bs->lovednum].name[i_c] = buf[i]; + i_c++; + i++; + } + bs->loved[bs->lovednum].name[i_c] = '\0'; + + while (buf[i] == ' ' || buf[i] == '{' || buf[i] == 9 || buf[i] == 13 || buf[i] == '\n') + { + i++; + } + + i_c = 0; + + while (buf[i] != '{' && buf[i] != 9 && buf[i] != 13 && buf[i] != '\n') + { + tbuf[i_c] = buf[i]; + i_c++; + i++; + } + tbuf[i_c] = '\0'; + + bs->loved[bs->lovednum].level = atoi(tbuf); + + bs->lovednum++; + } + else + { + break; + } + + if (bs->lovednum >= MAX_LOVED_ONES) + { + return; + } + + i++; + } +} + +int ReadChatGroups(bot_state_t *bs, char *buf) +{ + char *cgroupbegin; + int cgbplace; + int i; + + cgroupbegin = strstr(buf, "BEGIN_CHAT_GROUPS"); + + if (!cgroupbegin) + { + return 0; + } + + if (strlen(cgroupbegin) >= MAX_CHAT_BUFFER_SIZE) + { + G_Printf(S_COLOR_RED "Error: Personality chat section exceeds max size\n"); + return 0; + } + + cgbplace = cgroupbegin - buf+1; + + while (buf[cgbplace] != '\n') + { + cgbplace++; + } + + i = 0; + + while (buf[cgbplace] && buf[cgbplace] != '\0') + { + gBotChatBuffer[bs->client][i] = buf[cgbplace]; + i++; + cgbplace++; + } + + gBotChatBuffer[bs->client][i] = '\0'; + + return 1; +} + +void BotUtilizePersonality(bot_state_t *bs) +{ + fileHandle_t f; + int len, rlen; + int failed; + int i; + //char buf[131072]; + char *buf = (char *)B_TempAlloc(131072); + char *readbuf, *group; + + len = trap_FS_FOpenFile(bs->settings.personalityfile, &f, FS_READ); + + failed = 0; + + if (!f) + { + G_Printf(S_COLOR_RED "Error: Specified personality not found\n"); + B_TempFree(131072); //buf + return; + } + + if (len >= 131072) + { + G_Printf(S_COLOR_RED "Personality file exceeds maximum length\n"); + B_TempFree(131072); //buf + return; + } + + trap_FS_Read(buf, len, f); + + rlen = len; + + while (len < 131072) + { //kill all characters after the file length, since sometimes FS_Read doesn't do that entirely (or so it seems) + buf[len] = '\0'; + len++; + } + + len = rlen; + + readbuf = (char *)B_TempAlloc(1024); + group = (char *)B_TempAlloc(65536); + + if (!GetValueGroup(buf, "GeneralBotInfo", group)) + { + G_Printf(S_COLOR_RED "Personality file contains no GeneralBotInfo group\n"); + failed = 1; //set failed so we know to set everything to default values + } + + if (!failed && GetPairedValue(group, "reflex", readbuf)) + { + bs->skills.reflex = atoi(readbuf); + } + else + { + bs->skills.reflex = 100; //default + } + + if (!failed && GetPairedValue(group, "accuracy", readbuf)) + { + bs->skills.accuracy = atof(readbuf); + } + else + { + bs->skills.accuracy = 10; //default + } + + if (!failed && GetPairedValue(group, "turnspeed", readbuf)) + { + bs->skills.turnspeed = atof(readbuf); + } + else + { + bs->skills.turnspeed = 0.01f; //default + } + + if (!failed && GetPairedValue(group, "turnspeed_combat", readbuf)) + { + bs->skills.turnspeed_combat = atof(readbuf); + } + else + { + bs->skills.turnspeed_combat = 0.05f; //default + } + + if (!failed && GetPairedValue(group, "maxturn", readbuf)) + { + bs->skills.maxturn = atof(readbuf); + } + else + { + bs->skills.maxturn = 360; //default + } + + if (!failed && GetPairedValue(group, "perfectaim", readbuf)) + { + bs->skills.perfectaim = atoi(readbuf); + } + else + { + bs->skills.perfectaim = 0; //default + } + + if (!failed && GetPairedValue(group, "chatability", readbuf)) + { + bs->canChat = atoi(readbuf); + } + else + { + bs->canChat = 0; //default + } + + if (!failed && GetPairedValue(group, "chatfrequency", readbuf)) + { + bs->chatFrequency = atoi(readbuf); + } + else + { + bs->chatFrequency = 5; //default + } + + if (!failed && GetPairedValue(group, "hatelevel", readbuf)) + { + bs->loved_death_thresh = atoi(readbuf); + } + else + { + bs->loved_death_thresh = 3; //default + } + + if (!failed && GetPairedValue(group, "camper", readbuf)) + { + bs->isCamper = atoi(readbuf); + } + else + { + bs->isCamper = 0; //default + } + + if (!failed && GetPairedValue(group, "saberspecialist", readbuf)) + { + bs->saberSpecialist = atoi(readbuf); + } + else + { + bs->saberSpecialist = 0; //default + } + + if (!failed && GetPairedValue(group, "forceinfo", readbuf)) + { + Com_sprintf(bs->forceinfo, sizeof(bs->forceinfo), "%s\0", readbuf); + } + else + { + Com_sprintf(bs->forceinfo, sizeof(bs->forceinfo), "%s\0", DEFAULT_FORCEPOWERS); + } + + i = 0; + + while (i < MAX_CHAT_BUFFER_SIZE) + { //clear out the chat buffer for this bot + gBotChatBuffer[bs->client][i] = '\0'; + i++; + } + + if (bs->canChat) + { + if (!ReadChatGroups(bs, buf)) + { + bs->canChat = 0; + } + } + + if (GetValueGroup(buf, "BotWeaponWeights", group)) + { + if (GetPairedValue(group, "WP_STUN_BATON", readbuf)) + { + bs->botWeaponWeights[WP_STUN_BATON] = atoi(readbuf); + bs->botWeaponWeights[WP_MELEE] = bs->botWeaponWeights[WP_STUN_BATON]; + } + + if (GetPairedValue(group, "WP_SABER", readbuf)) + { + bs->botWeaponWeights[WP_SABER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_BRYAR_PISTOL", readbuf)) + { + bs->botWeaponWeights[WP_BRYAR_PISTOL] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_BLASTER", readbuf)) + { + bs->botWeaponWeights[WP_BLASTER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_DISRUPTOR", readbuf)) + { + bs->botWeaponWeights[WP_DISRUPTOR] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_BOWCASTER", readbuf)) + { + bs->botWeaponWeights[WP_BOWCASTER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_REPEATER", readbuf)) + { + bs->botWeaponWeights[WP_REPEATER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_DEMP2", readbuf)) + { + bs->botWeaponWeights[WP_DEMP2] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_FLECHETTE", readbuf)) + { + bs->botWeaponWeights[WP_FLECHETTE] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_ROCKET_LAUNCHER", readbuf)) + { + bs->botWeaponWeights[WP_ROCKET_LAUNCHER] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_THERMAL", readbuf)) + { + bs->botWeaponWeights[WP_THERMAL] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_TRIP_MINE", readbuf)) + { + bs->botWeaponWeights[WP_TRIP_MINE] = atoi(readbuf); + } + + if (GetPairedValue(group, "WP_DET_PACK", readbuf)) + { + bs->botWeaponWeights[WP_DET_PACK] = atoi(readbuf); + } + } + + bs->lovednum = 0; + + if (GetValueGroup(buf, "EmotionalAttachments", group)) + { + ParseEmotionalAttachments(bs, group); + } + + B_TempFree(131072); //buf + B_TempFree(1024); //readbuf + B_TempFree(65536); //group + trap_FS_FCloseFile(f); +} diff --git a/code/game/ai_wpnav.c b/code/game/ai_wpnav.c new file mode 100644 index 0000000..8720b69 --- /dev/null +++ b/code/game/ai_wpnav.c @@ -0,0 +1,3813 @@ +#include "g_local.h" +#include "q_shared.h" +#include "botlib.h" +#include "ai_main.h" + +float gWPRenderTime = 0; +float gDeactivated = 0; +float gBotEdit = 0; +int gWPRenderedFrame = 0; + +//#include "../namespace_begin.h" +wpobject_t *gWPArray[MAX_WPARRAY_SIZE]; +int gWPNum = 0; +//#include "../namespace_end.h" + +int gLastPrintedIndex = -1; + +#ifndef _XBOX +nodeobject_t nodetable[MAX_NODETABLE_SIZE]; +int nodenum; //so we can connect broken trails +#endif + +int gLevelFlags = 0; + +char *GetFlagStr( int flags ) +{ + char *flagstr; + int i; + + flagstr = (char *)B_TempAlloc(128); + i = 0; + + if (!flags) + { + strcpy(flagstr, "none\0"); + goto fend; + } + + if (flags & WPFLAG_JUMP) + { + flagstr[i] = 'j'; + i++; + } + + if (flags & WPFLAG_DUCK) + { + flagstr[i] = 'd'; + i++; + } + + if (flags & WPFLAG_SNIPEORCAMPSTAND) + { + flagstr[i] = 'c'; + i++; + } + + if (flags & WPFLAG_WAITFORFUNC) + { + flagstr[i] = 'f'; + i++; + } + + if (flags & WPFLAG_SNIPEORCAMP) + { + flagstr[i] = 's'; + i++; + } + + if (flags & WPFLAG_ONEWAY_FWD) + { + flagstr[i] = 'x'; + i++; + } + + if (flags & WPFLAG_ONEWAY_BACK) + { + flagstr[i] = 'y'; + i++; + } + + if (flags & WPFLAG_GOALPOINT) + { + flagstr[i] = 'g'; + i++; + } + + if (flags & WPFLAG_NOVIS) + { + flagstr[i] = 'n'; + i++; + } + + if (flags & WPFLAG_NOMOVEFUNC) + { + flagstr[i] = 'm'; + i++; + } + + if (flags & WPFLAG_RED_FLAG) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 'r'; + i++; + flagstr[i] = 'e'; + i++; + flagstr[i] = 'd'; + i++; + flagstr[i] = ' '; + i++; + flagstr[i] = 'f'; + i++; + flagstr[i] = 'l'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + } + + if (flags & WPFLAG_BLUE_FLAG) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 'b'; + i++; + flagstr[i] = 'l'; + i++; + flagstr[i] = 'u'; + i++; + flagstr[i] = 'e'; + i++; + flagstr[i] = ' '; + i++; + flagstr[i] = 'f'; + i++; + flagstr[i] = 'l'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + } + + if (flags & WPFLAG_SIEGE_IMPERIALOBJ) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 's'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = '_'; + i++; + flagstr[i] = 'i'; + i++; + flagstr[i] = 'm'; + i++; + flagstr[i] = 'p'; + i++; + } + + if (flags & WPFLAG_SIEGE_REBELOBJ) + { + if (i) + { + flagstr[i] = ' '; + i++; + } + flagstr[i] = 's'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = 'g'; + i++; + flagstr[i] = 'a'; + i++; + flagstr[i] = '_'; + i++; + flagstr[i] = 'r'; + i++; + flagstr[i] = 'e'; + i++; + flagstr[i] = 'b'; + i++; + } + + flagstr[i] = '\0'; + + if (i == 0) + { + strcpy(flagstr, "unknown\0"); + } + +fend: + return flagstr; +} + +void G_TestLine(vec3_t start, vec3_t end, int color, int time) +{ + gentity_t *te; + + te = G_TempEntity( start, EV_TESTLINE ); + VectorCopy(start, te->s.origin); + VectorCopy(end, te->s.origin2); + te->s.time2 = time; + te->s.weapon = color; + te->r.svFlags |= SVF_BROADCAST; +} + +void BotWaypointRender(void) +{ + int i, n; + int inc_checker; + int bestindex; + int gotbestindex; + float bestdist; + float checkdist; + gentity_t *plum; + gentity_t *viewent; + char *flagstr; + vec3_t a; + + if (!gBotEdit) + { + return; + } + + bestindex = 0; + + if (gWPRenderTime > level.time) + { + goto checkprint; + } + + gWPRenderTime = level.time + 100; + + i = gWPRenderedFrame; + inc_checker = gWPRenderedFrame; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + plum = G_TempEntity( gWPArray[i]->origin, EV_SCOREPLUM ); + plum->r.svFlags |= SVF_BROADCAST; + plum->s.time = i; + + n = 0; + + while (n < gWPArray[i]->neighbornum) + { + if (gWPArray[i]->neighbors[n].forceJumpTo && gWPArray[gWPArray[i]->neighbors[n].num]) + { + G_TestLine(gWPArray[i]->origin, gWPArray[gWPArray[i]->neighbors[n].num]->origin, 0x0000ff, 5000); + } + n++; + } + + gWPRenderedFrame++; + } + else + { + gWPRenderedFrame = 0; + break; + } + + if ((i - inc_checker) > 4) + { + break; //don't render too many at once + } + i++; + } + + if (i >= gWPNum) + { + gWPRenderTime = level.time + 1500; //wait a bit after we finish doing the whole trail + gWPRenderedFrame = 0; + } + +checkprint: + + if (!bot_wp_info.value) + { + return; + } + + viewent = &g_entities[0]; //only show info to the first client + + if (!viewent || !viewent->client) + { //client isn't in the game yet? + return; + } + + bestdist = 256; //max distance for showing point info + gotbestindex = 0; + + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(viewent->client->ps.origin, gWPArray[i]->origin, a); + + checkdist = VectorLength(a); + + if (checkdist < bestdist) + { + bestdist = checkdist; + bestindex = i; + gotbestindex = 1; + } + } + i++; + } + + if (gotbestindex && bestindex != gLastPrintedIndex) + { + flagstr = GetFlagStr(gWPArray[bestindex]->flags); + gLastPrintedIndex = bestindex; + G_Printf(S_COLOR_YELLOW "Waypoint %i\nFlags - %i (%s) (w%f)\nOrigin - (%i %i %i)\n", (int)(gWPArray[bestindex]->index), (int)(gWPArray[bestindex]->flags), flagstr, gWPArray[bestindex]->weight, (int)(gWPArray[bestindex]->origin[0]), (int)(gWPArray[bestindex]->origin[1]), (int)(gWPArray[bestindex]->origin[2])); + //GetFlagStr allocates 128 bytes for this, if it's changed then obviously this must be as well + B_TempFree(128); //flagstr + + plum = G_TempEntity( gWPArray[bestindex]->origin, EV_SCOREPLUM ); + plum->r.svFlags |= SVF_BROADCAST; + plum->s.time = bestindex; //render it once + } + else if (!gotbestindex) + { + gLastPrintedIndex = -1; + } +} + +void TransferWPData(int from, int to) +{ + if (!gWPArray[to]) + { + gWPArray[to] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + if (!gWPArray[to]) + { + G_Printf(S_COLOR_RED "FATAL ERROR: Could not allocated memory for waypoint\n"); + } + + gWPArray[to]->flags = gWPArray[from]->flags; + gWPArray[to]->weight = gWPArray[from]->weight; + gWPArray[to]->associated_entity = gWPArray[from]->associated_entity; + gWPArray[to]->disttonext = gWPArray[from]->disttonext; + gWPArray[to]->forceJumpTo = gWPArray[from]->forceJumpTo; + gWPArray[to]->index = to; + gWPArray[to]->inuse = gWPArray[from]->inuse; + VectorCopy(gWPArray[from]->origin, gWPArray[to]->origin); +} + +void CreateNewWP(vec3_t origin, int flags) +{ + if (gWPNum >= MAX_WPARRAY_SIZE) + { + if (!g_RMG.integer) + { + G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); + } + return; + } + + if (!gWPArray[gWPNum]) + { + gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + if (!gWPArray[gWPNum]) + { + G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n"); + } + + gWPArray[gWPNum]->flags = flags; + gWPArray[gWPNum]->weight = 0; //calculated elsewhere + gWPArray[gWPNum]->associated_entity = ENTITYNUM_NONE; //set elsewhere + gWPArray[gWPNum]->forceJumpTo = 0; + gWPArray[gWPNum]->disttonext = 0; //calculated elsewhere + gWPArray[gWPNum]->index = gWPNum; + gWPArray[gWPNum]->inuse = 1; + VectorCopy(origin, gWPArray[gWPNum]->origin); + gWPNum++; +} + +void CreateNewWP_FromObject(wpobject_t *wp) +{ + int i; + + if (gWPNum >= MAX_WPARRAY_SIZE) + { + return; + } + + if (!gWPArray[gWPNum]) + { + gWPArray[gWPNum] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + if (!gWPArray[gWPNum]) + { + G_Printf(S_COLOR_RED "ERROR: Could not allocated memory for waypoint\n"); + } + + gWPArray[gWPNum]->flags = wp->flags; + gWPArray[gWPNum]->weight = wp->weight; + gWPArray[gWPNum]->associated_entity = wp->associated_entity; + gWPArray[gWPNum]->disttonext = wp->disttonext; + gWPArray[gWPNum]->forceJumpTo = wp->forceJumpTo; + gWPArray[gWPNum]->index = gWPNum; + gWPArray[gWPNum]->inuse = 1; + VectorCopy(wp->origin, gWPArray[gWPNum]->origin); + gWPArray[gWPNum]->neighbornum = wp->neighbornum; + + i = wp->neighbornum; + + while (i >= 0) + { + gWPArray[gWPNum]->neighbors[i].num = wp->neighbors[i].num; + gWPArray[gWPNum]->neighbors[i].forceJumpTo = wp->neighbors[i].forceJumpTo; + + i--; + } + + if (gWPArray[gWPNum]->flags & WPFLAG_RED_FLAG) + { + flagRed = gWPArray[gWPNum]; + oFlagRed = flagRed; + } + else if (gWPArray[gWPNum]->flags & WPFLAG_BLUE_FLAG) + { + flagBlue = gWPArray[gWPNum]; + oFlagBlue = flagBlue; + } + + gWPNum++; +} + +void RemoveWP(void) +{ + if (gWPNum <= 0) + { + return; + } + + gWPNum--; + + if (!gWPArray[gWPNum] || !gWPArray[gWPNum]->inuse) + { + return; + } + + //B_Free((wpobject_t *)gWPArray[gWPNum]); + if (gWPArray[gWPNum]) + { + memset( gWPArray[gWPNum], 0, sizeof(gWPArray[gWPNum]) ); + } + + //gWPArray[gWPNum] = NULL; + + if (gWPArray[gWPNum]) + { + gWPArray[gWPNum]->inuse = 0; + } +} + +void RemoveAllWP(void) +{ + while(gWPNum) { + RemoveWP(); + } +} + +void RemoveWP_InTrail(int afterindex) +{ + int foundindex; + int foundanindex; + int didchange; + int i; + + foundindex = 0; + foundanindex = 0; + didchange = 0; + i = 0; + + if (afterindex < 0 || afterindex >= gWPNum) + { + G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return; + } + + i = 0; + + while (i <= gWPNum) + { + if (gWPArray[i] && gWPArray[i]->index == foundindex) + { + //B_Free(gWPArray[i]); + + //Keep reusing the memory + memset( gWPArray[i], 0, sizeof(gWPArray[i]) ); + + //gWPArray[i] = NULL; + gWPArray[i]->inuse = 0; + didchange = 1; + } + else if (gWPArray[i] && didchange) + { + TransferWPData(i, i-1); + //B_Free(gWPArray[i]); + + //Keep reusing the memory + memset( gWPArray[i], 0, sizeof(gWPArray[i]) ); + + //gWPArray[i] = NULL; + gWPArray[i]->inuse = 0; + } + + i++; + } + gWPNum--; +} + +int CreateNewWP_InTrail(vec3_t origin, int flags, int afterindex) +{ + int foundindex; + int foundanindex; + int i; + + foundindex = 0; + foundanindex = 0; + i = 0; + + if (gWPNum >= MAX_WPARRAY_SIZE) + { + if (!g_RMG.integer) + { + G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); + } + return 0; + } + + if (afterindex < 0 || afterindex >= gWPNum) + { + G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return 0; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return 0; + } + + i = gWPNum; + + while (i >= 0) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex) + { + TransferWPData(i, i+1); + } + else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex) + { + i++; + + if (!gWPArray[i]) + { + gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + gWPArray[i]->flags = flags; + gWPArray[i]->weight = 0; //calculated elsewhere + gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere + gWPArray[i]->disttonext = 0; //calculated elsewhere + gWPArray[i]->forceJumpTo = 0; + gWPArray[i]->index = i; + gWPArray[i]->inuse = 1; + VectorCopy(origin, gWPArray[i]->origin); + gWPNum++; + break; + } + + i--; + } + + return 1; +} + +int CreateNewWP_InsertUnder(vec3_t origin, int flags, int afterindex) +{ + int foundindex; + int foundanindex; + int i; + + foundindex = 0; + foundanindex = 0; + i = 0; + + if (gWPNum >= MAX_WPARRAY_SIZE) + { + if (!g_RMG.integer) + { + G_Printf(S_COLOR_YELLOW "Warning: Waypoint limit hit (%i)\n", MAX_WPARRAY_SIZE); + } + return 0; + } + + if (afterindex < 0 || afterindex >= gWPNum) + { + G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return 0; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return 0; + } + + i = gWPNum; + + while (i >= 0) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index != foundindex) + { + TransferWPData(i, i+1); + } + else if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == foundindex) + { + //i++; + TransferWPData(i, i+1); + + if (!gWPArray[i]) + { + gWPArray[i] = (wpobject_t *)B_Alloc(sizeof(wpobject_t)); + } + + gWPArray[i]->flags = flags; + gWPArray[i]->weight = 0; //calculated elsewhere + gWPArray[i]->associated_entity = ENTITYNUM_NONE; //set elsewhere + gWPArray[i]->disttonext = 0; //calculated elsewhere + gWPArray[i]->forceJumpTo = 0; + gWPArray[i]->index = i; + gWPArray[i]->inuse = 1; + VectorCopy(origin, gWPArray[i]->origin); + gWPNum++; + break; + } + + i--; + } + + return 1; +} + +void TeleportToWP(gentity_t *pl, int afterindex) +{ + int foundindex; + int foundanindex; + int i; + + if (!pl || !pl->client) + { + return; + } + + foundindex = 0; + foundanindex = 0; + i = 0; + + if (afterindex < 0 || afterindex >= gWPNum) + { + G_Printf(S_COLOR_YELLOW "Waypoint number %i does not exist\n", afterindex); + return; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->index == afterindex) + { + foundindex = i; + foundanindex = 1; + break; + } + + i++; + } + + if (!foundanindex) + { + G_Printf(S_COLOR_YELLOW "Waypoint index %i should exist, but does not (?)\n", afterindex); + return; + } + + VectorCopy(gWPArray[foundindex]->origin, pl->client->ps.origin); + + return; +} + +void WPFlagsModify(int wpnum, int flags) +{ + if (wpnum < 0 || wpnum >= gWPNum || !gWPArray[wpnum] || !gWPArray[wpnum]->inuse) + { + G_Printf(S_COLOR_YELLOW "WPFlagsModify: Waypoint %i does not exist\n", wpnum); + return; + } + + gWPArray[wpnum]->flags = flags; +} + +static int NotWithinRange(int base, int extent) +{ + if (extent > base && base+5 >= extent) + { + return 0; + } + + if (extent < base && base-5 <= extent) + { + return 0; + } + + return 1; +} + +#ifndef _XBOX +int NodeHere(vec3_t spot) +{ + int i; + + i = 0; + + while (i < nodenum) + { + if ((int)nodetable[i].origin[0] == (int)spot[0] && + (int)nodetable[i].origin[1] == (int)spot[1]) + { + if ((int)nodetable[i].origin[2] == (int)spot[2] || + ((int)nodetable[i].origin[2] < (int)spot[2] && (int)nodetable[i].origin[2]+5 > (int)spot[2]) || + ((int)nodetable[i].origin[2] > (int)spot[2] && (int)nodetable[i].origin[2]-5 < (int)spot[2])) + { + return 1; + } + } + i++; + } + + return 0; +} +#endif + +int CanGetToVector(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs) +{ + trace_t tr; + + trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + + return 0; +} + +#if 0 +int CanGetToVectorTravel(vec3_t org1, vec3_t org2, vec3_t mins, vec3_t maxs) +{ + trace_t tr; + vec3_t a, ang, fwd; + vec3_t midpos, dmid; + float startheight, midheight, fLen; + + mins[2] = -13; + maxs[2] = 13; + + trap_Trace(&tr, org1, mins, maxs, org2, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction != 1 || tr.startsolid || tr.allsolid) + { + return 0; + } + + VectorSubtract(org2, org1, a); + + vectoangles(a, ang); + + AngleVectors(ang, fwd, NULL, NULL); + + fLen = VectorLength(a)/2; + + midpos[0] = org1[0] + fwd[0]*fLen; + midpos[1] = org1[1] + fwd[1]*fLen; + midpos[2] = org1[2] + fwd[2]*fLen; + + VectorCopy(org1, dmid); + dmid[2] -= 1024; + + trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID); + + startheight = org1[2] - tr.endpos[2]; + + VectorCopy(midpos, dmid); + dmid[2] -= 1024; + + trap_Trace(&tr, midpos, NULL, NULL, dmid, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.startsolid || tr.allsolid) + { + return 1; + } + + midheight = midpos[2] - tr.endpos[2]; + + if (midheight > startheight*2) + { + return 0; //too steep of a drop.. can't go on + } + + return 1; +} +#else +int CanGetToVectorTravel(vec3_t org1, vec3_t moveTo, vec3_t mins, vec3_t maxs) +//int ExampleAnimEntMove(gentity_t *self, vec3_t moveTo, float stepSize) +{ + trace_t tr; + vec3_t stepTo; + vec3_t stepSub; + vec3_t stepGoal; + vec3_t workingOrg; + vec3_t lastIncrement; + vec3_t finalMeasure; + float stepSize = 0; + float measureLength = 0; + int didMove = 0; + int traceMask = MASK_PLAYERSOLID; + qboolean initialDone = qfalse; + + VectorCopy(org1, workingOrg); + VectorCopy(org1, lastIncrement); + + VectorCopy(moveTo, stepTo); + stepTo[2] = workingOrg[2]; + + VectorSubtract(stepTo, workingOrg, stepSub); + stepSize = VectorLength(stepSub); //make the step size the length of the original positions without Z + + VectorNormalize(stepSub); + + while (!initialDone || didMove) + { + initialDone = qtrue; + didMove = 0; + + stepGoal[0] = workingOrg[0] + stepSub[0]*stepSize; + stepGoal[1] = workingOrg[1] + stepSub[1]*stepSize; + stepGoal[2] = workingOrg[2] + stepSub[2]*stepSize; + + trap_Trace(&tr, workingOrg, mins, maxs, stepGoal, ENTITYNUM_NONE, traceMask); + + if (!tr.startsolid && !tr.allsolid && tr.fraction) + { + vec3_t vecSub; + VectorSubtract(workingOrg, tr.endpos, vecSub); + + if (VectorLength(vecSub) > (stepSize/2)) + { + workingOrg[0] = tr.endpos[0]; + workingOrg[1] = tr.endpos[1]; + //trap_LinkEntity(self); + didMove = 1; + } + } + + if (didMove != 1) + { //stair check + vec3_t trFrom; + vec3_t trTo; + vec3_t trDir; + vec3_t vecMeasure; + + VectorCopy(tr.endpos, trFrom); + trFrom[2] += 16; + + VectorSubtract(/*tr.endpos*/stepGoal, workingOrg, trDir); + VectorNormalize(trDir); + trTo[0] = tr.endpos[0] + trDir[0]*2; + trTo[1] = tr.endpos[1] + trDir[1]*2; + trTo[2] = tr.endpos[2] + trDir[2]*2; + trTo[2] += 16; + + VectorSubtract(trFrom, trTo, vecMeasure); + + if (VectorLength(vecMeasure) > 1) + { + trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask); + + if (!tr.startsolid && !tr.allsolid && tr.fraction == 1) + { //clear trace here, probably up a step + vec3_t trDown; + vec3_t trUp; + VectorCopy(tr.endpos, trUp); + VectorCopy(tr.endpos, trDown); + trDown[2] -= 16; + + trap_Trace(&tr, trFrom, mins, maxs, trTo, ENTITYNUM_NONE, traceMask); + + if (!tr.startsolid && !tr.allsolid) + { //plop us down on the step after moving up + VectorCopy(tr.endpos, workingOrg); + //trap_LinkEntity(self); + didMove = 1; + } + } + } + } + + VectorSubtract(lastIncrement, workingOrg, finalMeasure); + measureLength = VectorLength(finalMeasure); + + if (!measureLength) + { //no progress, break out. If last movement was a sucess didMove will equal 1. + break; + } + + stepSize -= measureLength; //subtract the progress distance from the step size so we don't overshoot the mark. + if (stepSize <= 0) + { + break; + } + + VectorCopy(workingOrg, lastIncrement); + } + + return didMove; +} +#endif + +#ifndef _XBOX +int ConnectTrail(int startindex, int endindex, qboolean behindTheScenes) +{ + int foundit; + int cancontinue; + int i; + int failsafe; + int successnodeindex; + int insertindex; + int prenodestart; + byte extendednodes[MAX_NODETABLE_SIZE]; //for storing checked nodes and not trying to extend them each a bazillion times + float fvecmeas; + float baseheight; + float branchDistance; + float maxDistFactor = 256; + vec3_t a; + vec3_t startplace, starttrace; + vec3_t mins, maxs; + vec3_t testspot; + vec3_t validspotpos; + trace_t tr; + + if (g_RMG.integer) + { //this might be temporary. Or not. + if (!(gWPArray[startindex]->flags & WPFLAG_NEVERONEWAY) && + !(gWPArray[endindex]->flags & WPFLAG_NEVERONEWAY)) + { + gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD; + gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK; + } + return 0; + } + + if (!g_RMG.integer) + { + branchDistance = TABLE_BRANCH_DISTANCE; + } + else + { + branchDistance = 512; //be less precise here, terrain is fairly broad, and we don't want to take an hour precalculating + } + + if (g_RMG.integer) + { + maxDistFactor = 700; + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 0; + + nodenum = 0; + foundit = 0; + + i = 0; + + successnodeindex = 0; + + while (i < MAX_NODETABLE_SIZE) //clear it out before using it + { + nodetable[i].flags = 0; +// nodetable[i].index = 0; + nodetable[i].inuse = 0; + nodetable[i].neighbornum = 0; + nodetable[i].origin[0] = 0; + nodetable[i].origin[1] = 0; + nodetable[i].origin[2] = 0; + nodetable[i].weight = 0; + + extendednodes[i] = 0; + + i++; + } + + i = 0; + + if (!behindTheScenes) + { + G_Printf(S_COLOR_YELLOW "Point %i is not connected to %i - Repairing...\n", startindex, endindex); + } + + VectorCopy(gWPArray[startindex]->origin, startplace); + + VectorCopy(startplace, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, startplace, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + baseheight = startplace[2] - tr.endpos[2]; + + cancontinue = 1; + + VectorCopy(startplace, nodetable[nodenum].origin); + nodetable[nodenum].weight = 1; + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodenum++; + + while (nodenum < MAX_NODETABLE_SIZE && !foundit && cancontinue) + { + if (g_RMG.integer) + { //adjust the branch distance dynamically depending on the distance from the start and end points. + vec3_t startDist; + vec3_t endDist; + float startDistf; + float endDistf; + + VectorSubtract(nodetable[nodenum-1].origin, gWPArray[startindex]->origin, startDist); + VectorSubtract(nodetable[nodenum-1].origin, gWPArray[endindex]->origin, endDist); + + startDistf = VectorLength(startDist); + endDistf = VectorLength(endDist); + + if (startDistf < 64 || endDistf < 64) + { + branchDistance = 64; + } + else if (startDistf < 128 || endDistf < 128) + { + branchDistance = 128; + } + else if (startDistf < 256 || endDistf < 256) + { + branchDistance = 256; + } + else if (startDistf < 512 || endDistf < 512) + { + branchDistance = 512; + } + else + { + branchDistance = 800; + } + } + cancontinue = 0; + i = 0; + prenodestart = nodenum; + + while (i < prenodestart) + { + if (extendednodes[i] != 1) + { + VectorSubtract(gWPArray[endindex]->origin, nodetable[i].origin, a); + fvecmeas = VectorLength(a); + + if (fvecmeas < 128 && CanGetToVector(gWPArray[endindex]->origin, nodetable[i].origin, mins, maxs)) + { + foundit = 1; + successnodeindex = i; + break; + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[0] += branchDistance; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[0] -= branchDistance; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[1] += branchDistance; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + VectorCopy(nodetable[i].origin, testspot); + testspot[1] -= branchDistance; + + VectorCopy(testspot, starttrace); + + starttrace[2] -= 4096; + + trap_Trace(&tr, testspot, NULL, NULL, starttrace, ENTITYNUM_NONE, MASK_SOLID); + + testspot[2] = tr.endpos[2]+baseheight; + + if (!NodeHere(testspot) && !tr.startsolid && !tr.allsolid && CanGetToVector(nodetable[i].origin, testspot, mins, maxs)) + { + VectorCopy(testspot, nodetable[nodenum].origin); + nodetable[nodenum].inuse = 1; +// nodetable[nodenum].index = nodenum; + nodetable[nodenum].weight = nodetable[i].weight+1; + nodetable[nodenum].neighbornum = i; + if ((nodetable[i].origin[2] - nodetable[nodenum].origin[2]) > 50) + { //if there's a big drop, make sure we know we can't just magically fly back up + nodetable[nodenum].flags = WPFLAG_ONEWAY_FWD; + } + nodenum++; + cancontinue = 1; + } + + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; //failure + } + + extendednodes[i] = 1; + } + + i++; + } + } + + if (!foundit) + { +#ifndef _DEBUG //if debug just always print this. + if (!behindTheScenes) +#endif + { + G_Printf(S_COLOR_RED "Could not link %i to %i, unreachable by node branching.\n", startindex, endindex); + } + gWPArray[startindex]->flags |= WPFLAG_ONEWAY_FWD; + gWPArray[endindex]->flags |= WPFLAG_ONEWAY_BACK; + if (!behindTheScenes) + { + G_Printf(S_COLOR_YELLOW "Since points cannot be connected, point %i has been flagged as only-forward and point %i has been flagged as only-backward.\n", startindex, endindex); + } + + /*while (nodenum >= 0) + { + if (nodetable[nodenum].origin[0] || nodetable[nodenum].origin[1] || nodetable[nodenum].origin[2]) + { + CreateNewWP(nodetable[nodenum].origin, nodetable[nodenum].flags); + } + + nodenum--; + }*/ + //The above code transfers nodes into the "rendered" waypoint array. Strictly for debugging. + + if (!behindTheScenes) + { //just use what we have if we're auto-pathing the level + return 0; + } + else + { + vec3_t endDist; + int nCount = 0; + int idealNode = -1; + float bestDist = 0; + float testDist; + + if (nodenum <= 10) + { //not enough to even really bother. + return 0; + } + + //Since it failed, find whichever node is closest to the desired end. + while (nCount < nodenum) + { + VectorSubtract(nodetable[nCount].origin, gWPArray[endindex]->origin, endDist); + testDist = VectorLength(endDist); + if (idealNode == -1) + { + idealNode = nCount; + bestDist = testDist; + nCount++; + continue; + } + + if (testDist < bestDist) + { + idealNode = nCount; + bestDist = testDist; + } + + nCount++; + } + + if (idealNode == -1) + { + return 0; + } + + successnodeindex = idealNode; + } + } + + i = successnodeindex; + insertindex = startindex; + failsafe = 0; + VectorCopy(gWPArray[startindex]->origin, validspotpos); + + while (failsafe < MAX_NODETABLE_SIZE && i < MAX_NODETABLE_SIZE && i >= 0) + { + VectorSubtract(validspotpos, nodetable[i].origin, a); + if (!nodetable[nodetable[i].neighbornum].inuse || !CanGetToVectorTravel(validspotpos, /*nodetable[nodetable[i].neighbornum].origin*/nodetable[i].origin, mins, maxs) || VectorLength(a) > maxDistFactor || (!CanGetToVectorTravel(validspotpos, gWPArray[endindex]->origin, mins, maxs) && CanGetToVectorTravel(nodetable[i].origin, gWPArray[endindex]->origin, mins, maxs)) ) + { + nodetable[i].flags |= WPFLAG_CALCULATED; + if (!CreateNewWP_InTrail(nodetable[i].origin, nodetable[i].flags, insertindex)) + { + if (!behindTheScenes) + { + G_Printf(S_COLOR_RED "Could not link %i to %i, waypoint limit hit.\n", startindex, endindex); + } + return 0; + } + + VectorCopy(nodetable[i].origin, validspotpos); + } + + if (i == 0) + { + break; + } + + i = nodetable[i].neighbornum; + + failsafe++; + } + + if (!behindTheScenes) + { + G_Printf(S_COLOR_YELLOW "Finished connecting %i to %i.\n", startindex, endindex); + } + + return 1; +} +#endif + +int OpposingEnds(int start, int end) +{ + if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse) + { + return 0; + } + + if ((gWPArray[start]->flags & WPFLAG_ONEWAY_FWD) && + (gWPArray[end]->flags & WPFLAG_ONEWAY_BACK)) + { + return 1; + } + + return 0; +} + +int DoorBlockingSection(int start, int end) +{ //if a door blocks the trail, we'll just have to assume the points on each side are in visibility when it's open + trace_t tr; + gentity_t *testdoor; + int start_trace_index; + + if (!gWPArray[start] || !gWPArray[start]->inuse || !gWPArray[end] || !gWPArray[end]->inuse) + { + return 0; + } + + trap_Trace(&tr, gWPArray[start]->origin, NULL, NULL, gWPArray[end]->origin, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction == 1) + { + return 0; + } + + testdoor = &g_entities[tr.entityNum]; + + if (!testdoor) + { + return 0; + } + + if (!strstr(testdoor->classname, "func_")) + { + return 0; + } + + start_trace_index = tr.entityNum; + + trap_Trace(&tr, gWPArray[end]->origin, NULL, NULL, gWPArray[start]->origin, ENTITYNUM_NONE, MASK_SOLID); + + if (tr.fraction == 1) + { + return 0; + } + + if (start_trace_index == tr.entityNum) + { + return 1; + } + + return 0; +} + +#ifndef _XBOX +int RepairPaths(qboolean behindTheScenes) +{ + int i; + int preAmount = 0; + int ctRet; + vec3_t a; + float maxDistFactor = 400; + + if (!gWPNum) + { + return 0; + } + + if (g_RMG.integer) + { + maxDistFactor = 800; //higher tolerance here. + } + + i = 0; + + preAmount = gWPNum; + + trap_Cvar_Update(&bot_wp_distconnect); + trap_Cvar_Update(&bot_wp_visconnect); + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i+1] && gWPArray[i+1]->inuse) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); + + if (!(gWPArray[i+1]->flags & WPFLAG_NOVIS) && + !(gWPArray[i+1]->flags & WPFLAG_JUMP) && //don't calculate on jump points because they might not always want to be visible (in cases of force jumping) + !(gWPArray[i]->flags & WPFLAG_CALCULATED) && //don't calculate it again + !OpposingEnds(i, i+1) && + ((bot_wp_distconnect.value && VectorLength(a) > maxDistFactor) || (!OrgVisible(gWPArray[i]->origin, gWPArray[i+1]->origin, ENTITYNUM_NONE) && bot_wp_visconnect.value) ) && + !DoorBlockingSection(i, i+1)) + { + ctRet = ConnectTrail(i, i+1, behindTheScenes); + + if (gWPNum >= MAX_WPARRAY_SIZE) + { //Bad! + gWPNum = MAX_WPARRAY_SIZE; + break; + } + + /*if (!ctRet) + { + return 0; + }*/ //we still want to write it.. + } + } + + i++; + } + + return 1; +} +#endif + +int OrgVisibleCurve(vec3_t org1, vec3_t mins, vec3_t maxs, vec3_t org2, int ignore) +{ + trace_t tr; + vec3_t evenorg1; + + VectorCopy(org1, evenorg1); + evenorg1[2] = org2[2]; + + trap_Trace(&tr, evenorg1, mins, maxs, org2, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + trap_Trace(&tr, evenorg1, mins, maxs, org1, ignore, MASK_SOLID); + + if (tr.fraction == 1 && !tr.startsolid && !tr.allsolid) + { + return 1; + } + } + + return 0; +} + +int CanForceJumpTo(int baseindex, int testingindex, float distance) +{ + float heightdif; + vec3_t xy_base, xy_test, v, mins, maxs; + wpobject_t *wpBase = gWPArray[baseindex]; + wpobject_t *wpTest = gWPArray[testingindex]; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -15; //-1 + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 15; //1 + + if (!wpBase || !wpBase->inuse || !wpTest || !wpTest->inuse) + { + return 0; + } + + if (distance > 400) + { + return 0; + } + + VectorCopy(wpBase->origin, xy_base); + VectorCopy(wpTest->origin, xy_test); + + xy_base[2] = xy_test[2]; + + VectorSubtract(xy_base, xy_test, v); + + if (VectorLength(v) > MAX_NEIGHBOR_LINK_DISTANCE) + { + return 0; + } + + if ((int)wpBase->origin[2] < (int)wpTest->origin[2]) + { + heightdif = wpTest->origin[2] - wpBase->origin[2]; + } + else + { + return 0; //err.. + } + + if (heightdif < 128) + { //don't bother.. + return 0; + } + + if (heightdif > 512) + { //too high + return 0; + } + + if (!OrgVisibleCurve(wpBase->origin, mins, maxs, wpTest->origin, ENTITYNUM_NONE)) + { + return 0; + } + + if (heightdif > 400) + { + return 3; + } + else if (heightdif > 256) + { + return 2; + } + else + { + return 1; + } +} + +void CalculatePaths(void) +{ + int i; + int c; + int forceJumpable; + int maxNeighborDist = MAX_NEIGHBOR_LINK_DISTANCE; + float nLDist; + vec3_t a; + vec3_t mins, maxs; + + if (!gWPNum) + { + return; + } + + if (g_RMG.integer) + { + maxNeighborDist = DEFAULT_GRID_SPACING + (DEFAULT_GRID_SPACING*0.5); + } + + mins[0] = -15; + mins[1] = -15; + mins[2] = -15; //-1 + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 15; //1 + + //now clear out all the neighbor data before we recalculate + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && gWPArray[i]->neighbornum) + { + while (gWPArray[i]->neighbornum >= 0) + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = 0; + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0; + gWPArray[i]->neighbornum--; + } + gWPArray[i]->neighbornum = 0; + } + + i++; + } + + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + c = 0; + + while (c < gWPNum) + { + if (gWPArray[c] && gWPArray[c]->inuse && i != c && + NotWithinRange(i, c)) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[c]->origin, a); + + nLDist = VectorLength(a); + forceJumpable = CanForceJumpTo(i, c, nLDist); + + if ((nLDist < maxNeighborDist || forceJumpable) && + ((int)gWPArray[i]->origin[2] == (int)gWPArray[c]->origin[2] || forceJumpable) && + (OrgVisibleBox(gWPArray[i]->origin, mins, maxs, gWPArray[c]->origin, ENTITYNUM_NONE) || forceJumpable)) + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].num = c; + if (forceJumpable && ((int)gWPArray[i]->origin[2] != (int)gWPArray[c]->origin[2] || nLDist < maxNeighborDist)) + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 999;//forceJumpable; //FJSR + } + else + { + gWPArray[i]->neighbors[gWPArray[i]->neighbornum].forceJumpTo = 0; + } + gWPArray[i]->neighbornum++; + } + + if (gWPArray[i]->neighbornum >= MAX_NEIGHBOR_SIZE) + { + break; + } + } + c++; + } + } + i++; + } +} + +gentity_t *GetObjectThatTargets(gentity_t *ent) +{ + gentity_t *next = NULL; + + if (!ent->targetname) + { + return NULL; + } + + next = G_Find( next, FOFS(target), ent->targetname ); + + if (next) + { + return next; + } + + return NULL; +} + +void CalculateSiegeGoals(void) +{ + int i = 0; + int looptracker = 0; + int wpindex = 0; + vec3_t dif; + gentity_t *ent; + gentity_t *tent = NULL, *t2ent = NULL; + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + tent = NULL; + + if (ent && ent->classname && strcmp(ent->classname, "info_siege_objective") == 0) + { + tent = ent; + t2ent = GetObjectThatTargets(tent); + looptracker = 0; + + while (t2ent && looptracker < 2048) + { //looptracker keeps us from getting stuck in case something is set up weird on this map + tent = t2ent; + t2ent = GetObjectThatTargets(tent); + looptracker++; + } + + if (looptracker >= 2048) + { //something unpleasent has happened + tent = NULL; + break; + } + } + + if (tent && ent && tent != ent) + { //tent should now be the object attached to the mission objective + dif[0] = (tent->r.absmax[0]+tent->r.absmin[0])/2; + dif[1] = (tent->r.absmax[1]+tent->r.absmin[1])/2; + dif[2] = (tent->r.absmax[2]+tent->r.absmin[2])/2; + + wpindex = GetNearestVisibleWP(dif, tent->s.number); + + if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse) + { //found the waypoint nearest the center of this objective-related object + if (ent->side == SIEGETEAM_TEAM1) + { + gWPArray[wpindex]->flags |= WPFLAG_SIEGE_IMPERIALOBJ; + } + else + { + gWPArray[wpindex]->flags |= WPFLAG_SIEGE_REBELOBJ; + } + + gWPArray[wpindex]->associated_entity = tent->s.number; + } + } + + i++; + } +} + +float botGlobalNavWeaponWeights[WP_NUM_WEAPONS] = +{ + 0,//WP_NONE, + + 0,//WP_STUN_BATON, + 0,//WP_MELEE + 0,//WP_SABER, // NOTE: lots of code assumes this is the first weapon (... which is crap) so be careful -Ste. + 0,//WP_BRYAR_PISTOL, + 3,//WP_BLASTER, + 5,//WP_DISRUPTOR, + 4,//WP_BOWCASTER, + 6,//WP_REPEATER, + 7,//WP_DEMP2, + 8,//WP_FLECHETTE, + 9,//WP_ROCKET_LAUNCHER, + 3,//WP_THERMAL, + 3,//WP_TRIP_MINE, + 3,//WP_DET_PACK, + 0//WP_EMPLACED_GUN, +}; + +int GetNearestVisibleWPToItem(vec3_t org, int ignore) +{ + int i; + float bestdist; + float flLen; + int bestindex; + vec3_t a, mins, maxs; + + i = 0; + bestdist = 64; //has to be less than 64 units to the item or it isn't safe enough + bestindex = -1; + + mins[0] = -15; + mins[1] = -15; + mins[2] = 0; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse && + gWPArray[i]->origin[2]-15 < org[2] && + gWPArray[i]->origin[2]+15 > org[2]) + { + VectorSubtract(org, gWPArray[i]->origin, a); + flLen = VectorLength(a); + + if (flLen < bestdist && trap_InPVS(org, gWPArray[i]->origin) && OrgVisibleBox(org, mins, maxs, gWPArray[i]->origin, ignore)) + { + bestdist = flLen; + bestindex = i; + } + } + + i++; + } + + return bestindex; +} + +void CalculateWeightGoals(void) +{ //set waypoint weights depending on weapon and item placement + int i = 0; + int wpindex = 0; + gentity_t *ent; + float weight; + + trap_Cvar_Update(&bot_wp_clearweight); + + if (bot_wp_clearweight.integer) + { //if set then flush out all weight/goal values before calculating them again + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + gWPArray[i]->weight = 0; + + if (gWPArray[i]->flags & WPFLAG_GOALPOINT) + { + gWPArray[i]->flags -= WPFLAG_GOALPOINT; + } + } + + i++; + } + } + + i = 0; + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + weight = 0; + + if (ent && ent->classname) + { + if (strcmp(ent->classname, "item_seeker") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "item_shield") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "item_medpac") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "item_sentry_gun") == 0) + { + weight = 2; + } + else if (strcmp(ent->classname, "item_force_enlighten_dark") == 0) + { + weight = 5; + } + else if (strcmp(ent->classname, "item_force_enlighten_light") == 0) + { + weight = 5; + } + else if (strcmp(ent->classname, "item_force_boon") == 0) + { + weight = 5; + } + else if (strcmp(ent->classname, "item_ysalimari") == 0) + { + weight = 2; + } + else if (strstr(ent->classname, "weapon_") && ent->item) + { + weight = botGlobalNavWeaponWeights[ent->item->giTag]; + } + else if (ent->item && ent->item->giType == IT_AMMO) + { + weight = 3; + } + } + + if (ent && weight) + { + wpindex = GetNearestVisibleWPToItem(ent->s.pos.trBase, ent->s.number); + + if (wpindex != -1 && gWPArray[wpindex] && gWPArray[wpindex]->inuse) + { //found the waypoint nearest the center of this object + gWPArray[wpindex]->weight = weight; + gWPArray[wpindex]->flags |= WPFLAG_GOALPOINT; + gWPArray[wpindex]->associated_entity = ent->s.number; + } + } + + i++; + } +} + +void CalculateJumpRoutes(void) +{ + int i = 0; + float nheightdif = 0; + float pheightdif = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + if (gWPArray[i]->flags & WPFLAG_JUMP) + { + nheightdif = 0; + pheightdif = 0; + + gWPArray[i]->forceJumpTo = 0; + + if (gWPArray[i-1] && gWPArray[i-1]->inuse && (gWPArray[i-1]->origin[2]+16) < gWPArray[i]->origin[2]) + { + nheightdif = (gWPArray[i]->origin[2] - gWPArray[i-1]->origin[2]); + } + + if (gWPArray[i+1] && gWPArray[i+1]->inuse && (gWPArray[i+1]->origin[2]+16) < gWPArray[i]->origin[2]) + { + pheightdif = (gWPArray[i]->origin[2] - gWPArray[i+1]->origin[2]); + } + + if (nheightdif > pheightdif) + { + pheightdif = nheightdif; + } + + if (pheightdif) + { + if (pheightdif > 500) + { + gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_3; //FJSR + } + else if (pheightdif > 256) + { + gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_2; //FJSR + } + else if (pheightdif > 128) + { + gWPArray[i]->forceJumpTo = 999; //FORCE_LEVEL_1; //FJSR + } + } + } + } + + i++; + } +} + +int LoadPathData(const char *filename) +{ + fileHandle_t f; + char *fileString; + char *currentVar; + char *routePath; + wpobject_t thiswp; + int len; + int i, i_cv; + int nei_num; + + i = 0; + i_cv = 0; + + routePath = (char *)B_TempAlloc(1024); + + Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename); + + len = trap_FS_FOpenFile(routePath, &f, FS_READ); + + B_TempFree(1024); //routePath + + if (!f) + { + G_Printf(S_COLOR_YELLOW "Bot route data not found for %s\n", filename); + return 2; + } + + if (len >= 524288) + { + G_Printf(S_COLOR_RED "Route file exceeds maximum length\n"); + return 0; + } + + fileString = (char *)B_TempAlloc(524288); + currentVar = (char *)B_TempAlloc(2048); + + trap_FS_Read(fileString, len, f); + + if (fileString[i] == 'l') + { //contains a "levelflags" entry.. + char readLFlags[64]; + i_cv = 0; + + while (fileString[i] != ' ') + { + i++; + } + i++; + while (fileString[i] != '\n') + { + readLFlags[i_cv] = fileString[i]; + i_cv++; + i++; + } + readLFlags[i_cv] = 0; + i++; + + gLevelFlags = atoi(readLFlags); + } + else + { + gLevelFlags = 0; + } + + while (i < len) + { + i_cv = 0; + + thiswp.index = 0; + thiswp.flags = 0; + thiswp.inuse = 0; + thiswp.neighbornum = 0; + thiswp.origin[0] = 0; + thiswp.origin[1] = 0; + thiswp.origin[2] = 0; + thiswp.weight = 0; + thiswp.associated_entity = ENTITYNUM_NONE; + thiswp.forceJumpTo = 0; + thiswp.disttonext = 0; + nei_num = 0; + + while (nei_num < MAX_NEIGHBOR_SIZE) + { + thiswp.neighbors[nei_num].num = 0; + thiswp.neighbors[nei_num].forceJumpTo = 0; + + nei_num++; + } + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.index = atoi(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.flags = atoi(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.weight = atof(currentVar); + + i_cv = 0; + i++; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.origin[0] = atof(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.origin[1] = atof(currentVar); + + i_cv = 0; + i++; + + while (fileString[i] != ')') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.origin[2] = atof(currentVar); + + i += 4; + + while (fileString[i] != '}') + { + i_cv = 0; + while (fileString[i] != ' ' && fileString[i] != '-') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.neighbors[thiswp.neighbornum].num = atoi(currentVar); + + if (fileString[i] == '-') + { + i_cv = 0; + i++; + + while (fileString[i] != ' ') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 999; //atoi(currentVar); //FJSR + } + else + { + thiswp.neighbors[thiswp.neighbornum].forceJumpTo = 0; + } + + thiswp.neighbornum++; + + i++; + } + + i_cv = 0; + i++; + i++; + + while (fileString[i] != '\n') + { + currentVar[i_cv] = fileString[i]; + i_cv++; + i++; + } + currentVar[i_cv] = '\0'; + + thiswp.disttonext = atof(currentVar); + + CreateNewWP_FromObject(&thiswp); + i++; + } + + B_TempFree(524288); //fileString + B_TempFree(2048); //currentVar + + trap_FS_FCloseFile(f); + + if (g_gametype.integer == GT_SIEGE) + { + CalculateSiegeGoals(); + } + + CalculateWeightGoals(); + //calculate weights for idle activity goals when + //the bot has absolutely nothing else to do + + CalculateJumpRoutes(); + //Look at jump points and mark them as requiring + //force jumping as needed + + return 1; +} + +void FlagObjects(void) +{ + int i = 0, bestindex = 0, found = 0; + float bestdist = 999999, tlen = 0; + gentity_t *flag_red, *flag_blue, *ent; + vec3_t a, mins, maxs; + trace_t tr; + + flag_red = NULL; + flag_blue = NULL; + + mins[0] = -15; + mins[1] = -15; + mins[2] = -5; + maxs[0] = 15; + maxs[1] = 15; + maxs[2] = 5; + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + if (ent && ent->inuse && ent->classname) + { + if (!flag_red && strcmp(ent->classname, "team_CTF_redflag") == 0) + { + flag_red = ent; + } + else if (!flag_blue && strcmp(ent->classname, "team_CTF_blueflag") == 0) + { + flag_blue = ent; + } + + if (flag_red && flag_blue) + { + break; + } + } + + i++; + } + + i = 0; + + if (!flag_red || !flag_blue) + { + return; + } + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(flag_red->s.pos.trBase, gWPArray[i]->origin, a); + tlen = VectorLength(a); + + if (tlen < bestdist) + { + trap_Trace(&tr, flag_red->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_red->s.number, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == flag_red->s.number) + { + bestdist = tlen; + bestindex = i; + found = 1; + } + } + + } + + i++; + } + + if (found) + { + gWPArray[bestindex]->flags |= WPFLAG_RED_FLAG; + flagRed = gWPArray[bestindex]; + oFlagRed = flagRed; + eFlagRed = flag_red; + } + + bestdist = 999999; + bestindex = 0; + found = 0; + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + VectorSubtract(flag_blue->s.pos.trBase, gWPArray[i]->origin, a); + tlen = VectorLength(a); + + if (tlen < bestdist) + { + trap_Trace(&tr, flag_blue->s.pos.trBase, mins, maxs, gWPArray[i]->origin, flag_blue->s.number, MASK_SOLID); + + if (tr.fraction == 1 || tr.entityNum == flag_blue->s.number) + { + bestdist = tlen; + bestindex = i; + found = 1; + } + } + + } + + i++; + } + + if (found) + { + gWPArray[bestindex]->flags |= WPFLAG_BLUE_FLAG; + flagBlue = gWPArray[bestindex]; + oFlagBlue = flagBlue; + eFlagBlue = flag_blue; + } +} + +#ifndef _XBOX +int SavePathData(const char *filename) +{ + fileHandle_t f; + char *fileString; + char *storeString; + char *routePath; + vec3_t a; + float flLen; + int i, s, n; + + fileString = NULL; + i = 0; + s = 0; + + if (!gWPNum) + { + return 0; + } + + routePath = (char *)B_TempAlloc(1024); + + Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename); + + trap_FS_FOpenFile(routePath, &f, FS_WRITE); + + B_TempFree(1024); //routePath + + if (!f) + { + G_Printf(S_COLOR_RED "ERROR: Could not open file to write path data\n"); + return 0; + } + + if (!RepairPaths(qfalse)) //check if we can see all waypoints from the last. If not, try to branch over. + { + trap_FS_FCloseFile(f); + return 0; + } + + CalculatePaths(); //make everything nice and connected before saving + + FlagObjects(); //currently only used for flagging waypoints nearest CTF flags + + fileString = (char *)B_TempAlloc(524288); + storeString = (char *)B_TempAlloc(4096); + + Com_sprintf(fileString, 524288, "%i %i %f (%f %f %f) { ", gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]); + + n = 0; + + while (n < gWPArray[i]->neighbornum) + { + if (gWPArray[i]->neighbors[n].forceJumpTo) + { + Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo); + } + else + { + Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num); + } + n++; + } + + if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); + flLen = VectorLength(a); + } + else + { + flLen = 0; + } + + gWPArray[i]->disttonext = flLen; + + Com_sprintf(fileString, 524288, "%s} %f\n", fileString, flLen); + + i++; + + while (i < gWPNum) + { + //sprintf(fileString, "%s%i %i %f (%f %f %f) { ", fileString, gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]); + Com_sprintf(storeString, 4096, "%i %i %f (%f %f %f) { ", gWPArray[i]->index, gWPArray[i]->flags, gWPArray[i]->weight, gWPArray[i]->origin[0], gWPArray[i]->origin[1], gWPArray[i]->origin[2]); + + n = 0; + + while (n < gWPArray[i]->neighbornum) + { + if (gWPArray[i]->neighbors[n].forceJumpTo) + { + Com_sprintf(storeString, 4096, "%s%i-%i ", storeString, gWPArray[i]->neighbors[n].num, gWPArray[i]->neighbors[n].forceJumpTo); + } + else + { + Com_sprintf(storeString, 4096, "%s%i ", storeString, gWPArray[i]->neighbors[n].num); + } + n++; + } + + if (gWPArray[i+1] && gWPArray[i+1]->inuse && gWPArray[i+1]->index) + { + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, a); + flLen = VectorLength(a); + } + else + { + flLen = 0; + } + + gWPArray[i]->disttonext = flLen; + + Com_sprintf(storeString, 4096, "%s} %f\n", storeString, flLen); + + strcat(fileString, storeString); + + i++; + } + + trap_FS_Write(fileString, strlen(fileString), f); + + B_TempFree(524288); //fileString + B_TempFree(4096); //storeString + + trap_FS_FCloseFile(f); + + G_Printf("Path data has been saved and updated. You may need to restart the level for some things to be properly calculated.\n"); + + return 1; +} +#endif + +//#define PAINFULLY_DEBUGGING_THROUGH_VM + +#define MAX_SPAWNPOINT_ARRAY 64 +int gSpawnPointNum = 0; +gentity_t *gSpawnPoints[MAX_SPAWNPOINT_ARRAY]; + +#ifndef _XBOX +int G_NearestNodeToPoint(vec3_t point) +{ //gets the node on the entire grid which is nearest to the specified coordinates. + vec3_t vSub; + int bestIndex = -1; + int i = 0; + float bestDist = 0; + float testDist = 0; + + while (i < nodenum) + { + VectorSubtract(nodetable[i].origin, point, vSub); + testDist = VectorLength(vSub); + + if (bestIndex == -1) + { + bestIndex = i; + bestDist = testDist; + + i++; + continue; + } + + if (testDist < bestDist) + { + bestIndex = i; + bestDist = testDist; + } + i++; + } + + return bestIndex; +} +#endif + +#ifndef _XBOX +void G_NodeClearForNext(void) +{ //reset nodes for the next trail connection. + int i = 0; + + while (i < nodenum) + { + nodetable[i].flags = 0; + nodetable[i].weight = 99999; + + i++; + } +} + +void G_NodeClearFlags(void) +{ //only clear out flags so nodes can be reused. + int i = 0; + + while (i < nodenum) + { + nodetable[i].flags = 0; + + i++; + } +} + +int G_NodeMatchingXY(float x, float y) +{ //just get the first unflagged node with the matching x,y coordinates. + int i = 0; + + while (i < nodenum) + { + if (nodetable[i].origin[0] == x && + nodetable[i].origin[1] == y && + !nodetable[i].flags) + { + return i; + } + + i++; + } + + return -1; +} + +int G_NodeMatchingXY_BA(int x, int y, int final) +{ //return the node with the lowest weight that matches the specified x,y coordinates. + int i = 0; + int bestindex = -1; + float bestWeight = 9999; + + while (i < nodenum) + { + if ((int)nodetable[i].origin[0] == x && + (int)nodetable[i].origin[1] == y && + !nodetable[i].flags && + ((nodetable[i].weight < bestWeight) || (i == final))) + { + if (i == final) + { + return i; + } + bestindex = i; + bestWeight = nodetable[i].weight; + } + + i++; + } + + return bestindex; +} + +int G_RecursiveConnection(int start, int end, int weight, qboolean traceCheck, float baseHeight) +{ + int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right + int recursiveIndex = -1; + int i = 0; + int passWeight = weight; + vec2_t givenXY; + trace_t tr; + + passWeight++; + nodetable[start].weight = passWeight; + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[0] -= DEFAULT_GRID_SPACING; + indexDirections[0] = G_NodeMatchingXY(givenXY[0], givenXY[1]); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[0] += DEFAULT_GRID_SPACING; + indexDirections[1] = G_NodeMatchingXY(givenXY[0], givenXY[1]); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[1] -= DEFAULT_GRID_SPACING; + indexDirections[2] = G_NodeMatchingXY(givenXY[0], givenXY[1]); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[1] += DEFAULT_GRID_SPACING; + indexDirections[3] = G_NodeMatchingXY(givenXY[0], givenXY[1]); + + i = 0; + while (i < 4) + { + if (indexDirections[i] == end) + { //we've connected all the way to the destination. + return indexDirections[i]; + } + + if (indexDirections[i] != -1 && nodetable[indexDirections[i]].flags) + { //this point is already used, so it's not valid. + indexDirections[i] = -1; + } + else if (indexDirections[i] != -1) + { //otherwise mark it as used. + nodetable[indexDirections[i]].flags = 1; + } + + if (indexDirections[i] != -1 && traceCheck) + { //if we care about trace visibility between nodes, perform the check and mark as not valid if the trace isn't clear. + trap_Trace(&tr, nodetable[start].origin, NULL, NULL, nodetable[indexDirections[i]].origin, ENTITYNUM_NONE, CONTENTS_SOLID); + + if (tr.fraction != 1) + { + indexDirections[i] = -1; + } + } + + if (indexDirections[i] != -1) + { //it's still valid, so keep connecting via this point. + recursiveIndex = G_RecursiveConnection(indexDirections[i], end, passWeight, traceCheck, baseHeight); + } + + if (recursiveIndex != -1) + { //the result of the recursive check was valid, so return it. + return recursiveIndex; + } + + i++; + } + + return recursiveIndex; +} + +#ifdef DEBUG_NODE_FILE +void G_DebugNodeFile() +{ + fileHandle_t f; + int i = 0; + float placeX; + char fileString[131072]; + gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); + + fileString[0] = 0; + + placeX = terrain->r.absmin[0]; + + while (i < nodenum) + { + strcat(fileString, va("%i-%f ", i, nodetable[i].weight)); + placeX += DEFAULT_GRID_SPACING; + + if (placeX >= terrain->r.absmax[0]) + { + strcat(fileString, "\n"); + placeX = terrain->r.absmin[0]; + } + i++; + } + + trap_FS_FOpenFile("ROUTEDEBUG.txt", &f, FS_WRITE); + trap_FS_Write(fileString, strlen(fileString), f); + trap_FS_FCloseFile(f); +} +#endif + +#endif +//#define ASCII_ART_DEBUG +//#define ASCII_ART_NODE_DEBUG + +#ifdef ASCII_ART_DEBUG + +#define ALLOWABLE_DEBUG_FILE_SIZE 1048576 + +void CreateAsciiTableRepresentation() +{ //Draw a text grid of the entire waypoint array (useful for debugging final waypoint placement) + fileHandle_t f; + int i = 0; + int sP = 0; + int placeX; + int placeY; + int oldX; + int oldY; + char fileString[ALLOWABLE_DEBUG_FILE_SIZE]; + char bChr = '+'; + gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); + + placeX = terrain->r.absmin[0]; + placeY = terrain->r.absmin[1]; + + oldX = placeX-1; + oldY = placeY-1; + + while (placeY < terrain->r.absmax[1]) + { + while (placeX < terrain->r.absmax[0]) + { + qboolean gotit = qfalse; + + i = 0; + while (i < gWPNum) + { + if (((int)gWPArray[i]->origin[0] <= placeX && (int)gWPArray[i]->origin[0] > oldX) && + ((int)gWPArray[i]->origin[1] <= placeY && (int)gWPArray[i]->origin[1] > oldY)) + { + gotit = qtrue; + break; + } + i++; + } + + if (gotit) + { + if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD) + { + bChr = 'F'; + } + else if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK) + { + bChr = 'B'; + } + else + { + bChr = '+'; + } + + if (gWPArray[i]->index < 10) + { + fileString[sP] = bChr; + fileString[sP+1] = '0'; + fileString[sP+2] = '0'; + fileString[sP+3] = va("%i", gWPArray[i]->index)[0]; + } + else if (gWPArray[i]->index < 100) + { + char *vastore = va("%i", gWPArray[i]->index); + + fileString[sP] = bChr; + fileString[sP+1] = '0'; + fileString[sP+2] = vastore[0]; + fileString[sP+3] = vastore[1]; + } + else if (gWPArray[i]->index < 1000) + { + char *vastore = va("%i", gWPArray[i]->index); + + fileString[sP] = bChr; + fileString[sP+1] = vastore[0]; + fileString[sP+2] = vastore[1]; + fileString[sP+3] = vastore[2]; + } + else + { + fileString[sP] = 'X'; + fileString[sP+1] = 'X'; + fileString[sP+2] = 'X'; + fileString[sP+3] = 'X'; + } + } + else + { + fileString[sP] = '-'; + fileString[sP+1] = '-'; + fileString[sP+2] = '-'; + fileString[sP+3] = '-'; + } + + sP += 4; + + if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) + { + break; + } + oldX = placeX; + placeX += DEFAULT_GRID_SPACING; + } + + placeX = terrain->r.absmin[0]; + oldX = placeX-1; + fileString[sP] = '\n'; + sP++; + + if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) + { + break; + } + + oldY = placeY; + placeY += DEFAULT_GRID_SPACING; + } + + fileString[sP] = 0; + + trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE); + trap_FS_Write(fileString, strlen(fileString), f); + trap_FS_FCloseFile(f); +} + +void CreateAsciiNodeTableRepresentation(int start, int end) +{ //draw a text grid of a single node path, from point A to Z. + fileHandle_t f; + int i = 0; + int sP = 0; + int placeX; + int placeY; + int oldX; + int oldY; + char fileString[ALLOWABLE_DEBUG_FILE_SIZE]; + gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); + + placeX = terrain->r.absmin[0]; + placeY = terrain->r.absmin[1]; + + oldX = placeX-1; + oldY = placeY-1; + + while (placeY < terrain->r.absmax[1]) + { + while (placeX < terrain->r.absmax[0]) + { + qboolean gotit = qfalse; + + i = 0; + while (i < nodenum) + { + if (((int)nodetable[i].origin[0] <= placeX && (int)nodetable[i].origin[0] > oldX) && + ((int)nodetable[i].origin[1] <= placeY && (int)nodetable[i].origin[1] > oldY)) + { + gotit = qtrue; + break; + } + i++; + } + + if (gotit) + { + if (i == start) + { //beginning of the node trail + fileString[sP] = 'A'; + fileString[sP+1] = 'A'; + fileString[sP+2] = 'A'; + fileString[sP+3] = 'A'; + } + else if (i == end) + { //destination of the node trail + fileString[sP] = 'Z'; + fileString[sP+1] = 'Z'; + fileString[sP+2] = 'Z'; + fileString[sP+3] = 'Z'; + } + else if (nodetable[i].weight < 10) + { + fileString[sP] = '+'; + fileString[sP+1] = '0'; + fileString[sP+2] = '0'; + fileString[sP+3] = va("%f", nodetable[i].weight)[0]; + } + else if (nodetable[i].weight < 100) + { + char *vastore = va("%f", nodetable[i].weight); + + fileString[sP] = '+'; + fileString[sP+1] = '0'; + fileString[sP+2] = vastore[0]; + fileString[sP+3] = vastore[1]; + } + else if (nodetable[i].weight < 1000) + { + char *vastore = va("%f", nodetable[i].weight); + + fileString[sP] = '+'; + fileString[sP+1] = vastore[0]; + fileString[sP+2] = vastore[1]; + fileString[sP+3] = vastore[2]; + } + else + { + fileString[sP] = 'X'; + fileString[sP+1] = 'X'; + fileString[sP+2] = 'X'; + fileString[sP+3] = 'X'; + } + } + else + { + fileString[sP] = '-'; + fileString[sP+1] = '-'; + fileString[sP+2] = '-'; + fileString[sP+3] = '-'; + } + + sP += 4; + + if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) + { + break; + } + oldX = placeX; + placeX += DEFAULT_GRID_SPACING; + } + + placeX = terrain->r.absmin[0]; + oldX = placeX-1; + fileString[sP] = '\n'; + sP++; + + if (sP >= ALLOWABLE_DEBUG_FILE_SIZE-16) + { + break; + } + + oldY = placeY; + placeY += DEFAULT_GRID_SPACING; + } + + fileString[sP] = 0; + + trap_FS_FOpenFile("ROUTEDRAWN.txt", &f, FS_WRITE); + trap_FS_Write(fileString, strlen(fileString), f); + trap_FS_FCloseFile(f); +} +#endif + +#ifndef _XBOX +qboolean G_BackwardAttachment(int start, int finalDestination, int insertAfter) +{ //After creating a node path between 2 points, this function links the 2 points with actual waypoint data. + int indexDirections[4]; //0 == down, 1 == up, 2 == left, 3 == right + int i = 0; + int lowestWeight = 9999; + int desiredIndex = -1; + vec2_t givenXY; + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[0] -= DEFAULT_GRID_SPACING; + indexDirections[0] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[0] += DEFAULT_GRID_SPACING; + indexDirections[1] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[1] -= DEFAULT_GRID_SPACING; + indexDirections[2] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); + + givenXY[0] = nodetable[start].origin[0]; + givenXY[1] = nodetable[start].origin[1]; + givenXY[1] += DEFAULT_GRID_SPACING; + indexDirections[3] = G_NodeMatchingXY_BA(givenXY[0], givenXY[1], finalDestination); + + while (i < 4) + { + if (indexDirections[i] != -1) + { + if (indexDirections[i] == finalDestination) + { //hooray, we've found the original point and linked all the way back to it. + CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter); + CreateNewWP_InsertUnder(nodetable[indexDirections[i]].origin, 0, insertAfter); + return qtrue; + } + + if (nodetable[indexDirections[i]].weight < lowestWeight && nodetable[indexDirections[i]].weight && !nodetable[indexDirections[i]].flags /*&& (nodetable[indexDirections[i]].origin[2]-64 < nodetable[start].origin[2])*/) + { + desiredIndex = indexDirections[i]; + lowestWeight = nodetable[indexDirections[i]].weight; + } + } + i++; + } + + if (desiredIndex != -1) + { //Create a waypoint here, and then recursively call this function for the next neighbor with the lowest weight. + if (gWPNum < 3900) + { + CreateNewWP_InsertUnder(nodetable[start].origin, 0, insertAfter); + } + else + { +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("WAYPOINTS FULL\n"); +#endif + return qfalse; + } + + nodetable[start].flags = 1; + return G_BackwardAttachment(desiredIndex, finalDestination, insertAfter); + } + + return qfalse; +} + + +#ifdef _DEBUG +#define PATH_TIME_DEBUG +#endif + +void G_RMGPathing(void) +{ //Generate waypoint information on-the-fly for the random mission. + float placeX, placeY, placeZ; + int i = 0; + int gridSpacing = DEFAULT_GRID_SPACING; + int nearestIndex = 0; + int nearestIndexForNext = 0; +#ifdef PATH_TIME_DEBUG + int startTime = 0; + int endTime = 0; +#endif + vec3_t downVec, trMins, trMaxs; + trace_t tr; + gentity_t *terrain = G_Find( NULL, FOFS(classname), "terrain" ); + + if (!terrain || !terrain->inuse || terrain->s.eType != ET_TERRAIN) + { + G_Printf("Error: RMG with no terrain!\n"); + return; + } + +#ifdef PATH_TIME_DEBUG + startTime = trap_Milliseconds(); +#endif + + nodenum = 0; + memset(&nodetable, 0, sizeof(nodetable)); + + VectorSet(trMins, -15, -15, DEFAULT_MINS_2); + VectorSet(trMaxs, 15, 15, DEFAULT_MAXS_2); + + placeX = terrain->r.absmin[0]; + placeY = terrain->r.absmin[1]; + placeZ = terrain->r.absmax[2]-400; + + //skim through the entirety of the terrain limits and drop nodes, removing + //nodes that start in solid or fall too high on the terrain. + while (placeY < terrain->r.absmax[1]) + { + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; + } + + while (placeX < terrain->r.absmax[0]) + { + if (nodenum >= MAX_NODETABLE_SIZE) + { + break; + } + + nodetable[nodenum].origin[0] = placeX; + nodetable[nodenum].origin[1] = placeY; + nodetable[nodenum].origin[2] = placeZ; + + VectorCopy(nodetable[nodenum].origin, downVec); + downVec[2] -= 3000; + trap_Trace(&tr, nodetable[nodenum].origin, trMins, trMaxs, downVec, ENTITYNUM_NONE, MASK_SOLID); + + if ((tr.entityNum >= ENTITYNUM_WORLD || g_entities[tr.entityNum].s.eType == ET_TERRAIN) && tr.endpos[2] < terrain->r.absmin[2]+750) + { //only drop nodes on terrain directly + VectorCopy(tr.endpos, nodetable[nodenum].origin); + nodenum++; + } + else + { + VectorClear(nodetable[nodenum].origin); + } + + placeX += gridSpacing; + } + + placeX = terrain->r.absmin[0]; + placeY += gridSpacing; + } + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("NODE GRID PLACED ON TERRAIN\n"); +#endif + + G_NodeClearForNext(); + + //The grid has been placed down, now use it to connect the points in the level. + while (i < gSpawnPointNum-1) + { + if (!gSpawnPoints[i] || !gSpawnPoints[i]->inuse || !gSpawnPoints[i+1] || !gSpawnPoints[i+1]->inuse) + { + i++; + continue; + } + + nearestIndex = G_NearestNodeToPoint(gSpawnPoints[i]->s.origin); + nearestIndexForNext = G_NearestNodeToPoint(gSpawnPoints[i+1]->s.origin); + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("%i GOT %i INDEX WITH %i INDEX FOR NEXT\n", nearestIndex, nearestIndexForNext); +#endif + + if (nearestIndex == -1 || nearestIndexForNext == -1) + { //Looks like there is no grid data near one of the points. Ideally, this will never happen. + i++; + continue; + } + + if (nearestIndex == nearestIndexForNext) + { //Two spawn points on top of each other? We don't need to do both points, keep going until the next differs. + i++; + continue; + } + + //So, nearestIndex is now the node for the spawn point we're on, and nearestIndexForNext is the + //node we want to get to from here. + + //For now I am going to branch out mindlessly, but I will probably want to use some sort of A* algorithm + //here to lessen the time taken. + if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qtrue, terrain->r.absmin[2]) != nearestIndexForNext) + { //failed to branch to where we want. Oh well, try it without trace checks. + G_NodeClearForNext(); + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FAILED RECURSIVE WITH TRACES\n"); +#endif + + if (G_RecursiveConnection(nearestIndex, nearestIndexForNext, 0, qfalse, terrain->r.absmin[2]) != nearestIndexForNext) + { //still failed somehow. Just disregard this point. +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FAILED RECURSIVE -WITHOUT- TRACES (?!?!)\n"); +#endif + G_NodeClearForNext(); + i++; + continue; + } + } + + //Now our node array is set up so that highest reasonable weight is the destination node, and 2 is next to the original index, + //so trace back to that point. + G_NodeClearFlags(); + +#ifdef ASCII_ART_DEBUG +#ifdef ASCII_ART_NODE_DEBUG + CreateAsciiNodeTableRepresentation(nearestIndex, nearestIndexForNext); +#endif +#endif + if (G_BackwardAttachment(nearestIndexForNext, nearestIndex, gWPNum-1)) + { //successfully connected the trail from nearestIndex to nearestIndexForNext + if (gSpawnPoints[i+1]->inuse && gSpawnPoints[i+1]->item && + gSpawnPoints[i+1]->item->giType == IT_TEAM) + { //This point is actually a CTF flag. + if (gSpawnPoints[i+1]->item->giTag == PW_REDFLAG || gSpawnPoints[i+1]->item->giTag == PW_BLUEFLAG) + { //Place a waypoint on the flag next in the trail, so the nearest grid point will link to it. + CreateNewWP_InsertUnder(gSpawnPoints[i+1]->s.origin, WPFLAG_NEVERONEWAY, gWPNum-1); + } + } + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("BACKWARD ATTACHMENT %i SUCCESS\n", i); +#endif + } + else + { +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("BACKWARD ATTACHMENT FAILED\n"); +#endif + break; + } + +#ifdef DEBUG_NODE_FILE + G_DebugNodeFile(); +#endif + + G_NodeClearForNext(); + i++; + } + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED RMG AUTOPATH\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("BEGINNING PATH REPAIR...\n"); +#endif + RepairPaths(qtrue); //this has different behaviour for RMG and will just flag all points one way that don't trace to each other. +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED PATH REPAIR.\n"); +#endif + +#ifdef PATH_TIME_DEBUG + endTime = trap_Milliseconds(); + + G_Printf("Total routing time taken: %ims\n", (endTime - startTime)); +#endif + +#ifdef ASCII_ART_DEBUG + CreateAsciiTableRepresentation(); +#endif +} +#endif + +#ifndef _XBOX +void BeginAutoPathRoutine(void) +{ //Called for RMG levels. + int i = 0; + gentity_t *ent = NULL; + vec3_t v; + + gSpawnPointNum = 0; + + CreateNewWP(vec3_origin, 0); //create a dummy waypoint to insert under + + while (i < level.num_entities) + { + ent = &g_entities[i]; + + if (ent && ent->inuse && ent->classname && ent->classname[0] && !Q_stricmp(ent->classname, "info_player_deathmatch")) + { + if (ent->s.origin[2] < 1280) + { //h4x + gSpawnPoints[gSpawnPointNum] = ent; + gSpawnPointNum++; + } + } + else if (ent && ent->inuse && ent->item && ent->item->giType == IT_TEAM && + (ent->item->giTag == PW_REDFLAG || ent->item->giTag == PW_BLUEFLAG)) + { //also make it path to flags in CTF. + gSpawnPoints[gSpawnPointNum] = ent; + gSpawnPointNum++; + } + + i++; + } + + if (gSpawnPointNum < 1) + { + return; + } + + G_RMGPathing(); +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("LINKING PATHS...\n"); +#endif + + //rww - Using a faster in-engine version because we're having to wait for this stuff to get done as opposed to just saving it once. + trap_Bot_UpdateWaypoints(gWPNum, gWPArray); + trap_Bot_CalculatePaths(g_RMG.integer); + //CalculatePaths(); //make everything nice and connected + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED LINKING PATHS.\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FLAGGING OBJECTS...\n"); +#endif + FlagObjects(); //currently only used for flagging waypoints nearest CTF flags +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED FLAGGING OBJECTS.\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("CALCULATING WAYPOINT DISTANCES...\n"); +#endif + i = 0; + + while (i < gWPNum-1) + { //disttonext is normally set on save, and when a file is loaded. For RMG we must do it after calc'ing. + VectorSubtract(gWPArray[i]->origin, gWPArray[i+1]->origin, v); + gWPArray[i]->disttonext = VectorLength(v); + i++; + } +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINISHED CALCULATING.\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("FINAL STEP...\n"); +#endif + RemoveWP(); //remove the dummy point at the end of the trail +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("COMPLETE.\n"); +#endif + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + if (gWPNum >= 4096-1) + { + Com_Printf("%i waypoints say that YOU ARE A TERRIBLE MAN.\n", gWPNum); + } +#endif +} + +#endif +extern vmCvar_t bot_normgpath; + +void LoadPath_ThisLevel(void) +{ + vmCvar_t mapname; + int i = 0; + gentity_t *ent = NULL; + + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + + if (g_RMG.integer) + { //If RMG, generate the path on-the-fly +#ifdef _XBOX + assert(0); +#else + trap_Cvar_Register(&bot_normgpath, "bot_normgpath", "1", CVAR_CHEAT); + //note: This is disabled for now as I'm using standard bot nav + //on premade terrain levels. + + if (!bot_normgpath.integer) + { //autopath the random map + BeginAutoPathRoutine(); + } + else + { //try loading standard nav data + LoadPathData(mapname.string); + } + + gLevelFlags |= LEVELFLAG_NOPOINTPREDICTION; +#endif + } + else + { + if (LoadPathData(mapname.string) == 2) + { + //enter "edit" mode if cheats enabled? + } + } + + trap_Cvar_Update(&bot_wp_edit); + + if (bot_wp_edit.value) + { + gBotEdit = 1; + } + else + { + gBotEdit = 0; + } + + //set the flag entities + while (i < level.num_entities) + { + ent = &g_entities[i]; + + if (ent && ent->inuse && ent->classname) + { + if (!eFlagRed && strcmp(ent->classname, "team_CTF_redflag") == 0) + { + eFlagRed = ent; + } + else if (!eFlagBlue && strcmp(ent->classname, "team_CTF_blueflag") == 0) + { + eFlagBlue = ent; + } + + if (eFlagRed && eFlagBlue) + { + break; + } + } + + i++; + } + +#ifdef PAINFULLY_DEBUGGING_THROUGH_VM + Com_Printf("BOT PATHING IS COMPLETE.\n"); +#endif +} + +gentity_t *GetClosestSpawn(gentity_t *ent) +{ + gentity_t *spawn; + gentity_t *closestSpawn = NULL; + float closestDist = -1; + int i = MAX_CLIENTS; + + spawn = NULL; + + while (i < level.num_entities) + { + spawn = &g_entities[i]; + + if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) ) + { + float checkDist; + vec3_t vSub; + + VectorSubtract(ent->client->ps.origin, spawn->r.currentOrigin, vSub); + checkDist = VectorLength(vSub); + + if (closestDist == -1 || checkDist < closestDist) + { + closestSpawn = spawn; + closestDist = checkDist; + } + } + + i++; + } + + return closestSpawn; +} + +gentity_t *GetNextSpawnInIndex(gentity_t *currentSpawn) +{ + gentity_t *spawn; + gentity_t *nextSpawn = NULL; + int i = currentSpawn->s.number+1; + + spawn = NULL; + + while (i < level.num_entities) + { + spawn = &g_entities[i]; + + if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) ) + { + nextSpawn = spawn; + break; + } + + i++; + } + + if (!nextSpawn) + { //loop back around to 0 + i = MAX_CLIENTS; + + while (i < level.num_entities) + { + spawn = &g_entities[i]; + + if (spawn && spawn->inuse && (!Q_stricmp(spawn->classname, "info_player_start") || !Q_stricmp(spawn->classname, "info_player_deathmatch")) ) + { + nextSpawn = spawn; + break; + } + + i++; + } + } + + return nextSpawn; +} + +int AcceptBotCommand(char *cmd, gentity_t *pl) +{ + int OptionalArgument, i; + int FlagsFromArgument; + char *OptionalSArgument, *RequiredSArgument; +#ifndef _XBOX + vmCvar_t mapname; +#endif + + if (!gBotEdit) + { + return 0; + } + + OptionalArgument = 0; + i = 0; + FlagsFromArgument = 0; + OptionalSArgument = NULL; + RequiredSArgument = NULL; + + //if a waypoint editing related command is issued, bots will deactivate. + //once bot_wp_save is issued and the trail is recalculated, bots will activate again. + + if (!pl || !pl->client) + { + return 0; + } + + if (Q_stricmp (cmd, "bot_wp_cmdlist") == 0) //lists all the bot waypoint commands. + { + G_Printf(S_COLOR_YELLOW "bot_wp_add" S_COLOR_WHITE " - Add a waypoint (optional int parameter will insert the point after the specified waypoint index in a trail)\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_rem" S_COLOR_WHITE " - Remove a waypoint (removes last unless waypoint index is specified as a parameter)\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_addflagged" S_COLOR_WHITE " - Same as wp_add, but adds a flagged point (type bot_wp_addflagged for help)\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_switchflags" S_COLOR_WHITE " - Switches flags on an existing waypoint (type bot_wp_switchflags for help)\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_tele" S_COLOR_WHITE " - Teleport yourself to the specified waypoint's location\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_killoneways" S_COLOR_WHITE " - Removes oneway (backward and forward) flags on all waypoints in the level\n\n"); + G_Printf(S_COLOR_YELLOW "bot_wp_save" S_COLOR_WHITE " - Saves all waypoint data into a file for later use\n"); + + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_add") == 0) + { + gDeactivated = 1; + OptionalSArgument = ConcatArgs( 1 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + CreateNewWP_InTrail(pl->client->ps.origin, 0, OptionalArgument); + } + else + { + CreateNewWP(pl->client->ps.origin, 0); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_rem") == 0) + { + gDeactivated = 1; + + OptionalSArgument = ConcatArgs( 1 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + RemoveWP_InTrail(OptionalArgument); + } + else + { + RemoveWP(); + } + + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_tele") == 0) + { + gDeactivated = 1; + OptionalSArgument = ConcatArgs( 1 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + TeleportToWP(pl, OptionalArgument); + } + else + { + G_Printf(S_COLOR_YELLOW "You didn't specify an index. Assuming last.\n"); + TeleportToWP(pl, gWPNum-1); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_spawntele") == 0) + { + gentity_t *closestSpawn = GetClosestSpawn(pl); + + if (!closestSpawn) + { //There should always be a spawn point.. + return 1; + } + + closestSpawn = GetNextSpawnInIndex(closestSpawn); + + if (closestSpawn) + { + VectorCopy(closestSpawn->r.currentOrigin, pl->client->ps.origin); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_addflagged") == 0) + { + gDeactivated = 1; + + RequiredSArgument = ConcatArgs( 1 ); + + if (!RequiredSArgument || !RequiredSArgument[0]) + { + G_Printf(S_COLOR_YELLOW "Flag string needed for bot_wp_addflagged\nj - Jump point\nd - Duck point\nc - Snipe or camp standing\nf - Wait for func\nm - Do not move to when func is under\ns - Snipe or camp\nx - Oneway, forward\ny - Oneway, back\ng - Mission goal\nn - No visibility\nExample (for a point the bot would jump at, and reverse on when traveling a trail backwards):\nbot_wp_addflagged jx\n"); + return 1; + } + + while (RequiredSArgument[i]) + { + if (RequiredSArgument[i] == 'j') + { + FlagsFromArgument |= WPFLAG_JUMP; + } + else if (RequiredSArgument[i] == 'd') + { + FlagsFromArgument |= WPFLAG_DUCK; + } + else if (RequiredSArgument[i] == 'c') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND; + } + else if (RequiredSArgument[i] == 'f') + { + FlagsFromArgument |= WPFLAG_WAITFORFUNC; + } + else if (RequiredSArgument[i] == 's') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMP; + } + else if (RequiredSArgument[i] == 'x') + { + FlagsFromArgument |= WPFLAG_ONEWAY_FWD; + } + else if (RequiredSArgument[i] == 'y') + { + FlagsFromArgument |= WPFLAG_ONEWAY_BACK; + } + else if (RequiredSArgument[i] == 'g') + { + FlagsFromArgument |= WPFLAG_GOALPOINT; + } + else if (RequiredSArgument[i] == 'n') + { + FlagsFromArgument |= WPFLAG_NOVIS; + } + else if (RequiredSArgument[i] == 'm') + { + FlagsFromArgument |= WPFLAG_NOMOVEFUNC; + } + + i++; + } + + OptionalSArgument = ConcatArgs( 2 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + CreateNewWP_InTrail(pl->client->ps.origin, FlagsFromArgument, OptionalArgument); + } + else + { + CreateNewWP(pl->client->ps.origin, FlagsFromArgument); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_switchflags") == 0) + { + gDeactivated = 1; + + RequiredSArgument = ConcatArgs( 1 ); + + if (!RequiredSArgument || !RequiredSArgument[0]) + { + G_Printf(S_COLOR_YELLOW "Flag string needed for bot_wp_switchflags\nType bot_wp_addflagged for a list of flags and their corresponding characters, or use 0 for no flags.\nSyntax: bot_wp_switchflags \n"); + return 1; + } + + while (RequiredSArgument[i]) + { + if (RequiredSArgument[i] == 'j') + { + FlagsFromArgument |= WPFLAG_JUMP; + } + else if (RequiredSArgument[i] == 'd') + { + FlagsFromArgument |= WPFLAG_DUCK; + } + else if (RequiredSArgument[i] == 'c') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMPSTAND; + } + else if (RequiredSArgument[i] == 'f') + { + FlagsFromArgument |= WPFLAG_WAITFORFUNC; + } + else if (RequiredSArgument[i] == 's') + { + FlagsFromArgument |= WPFLAG_SNIPEORCAMP; + } + else if (RequiredSArgument[i] == 'x') + { + FlagsFromArgument |= WPFLAG_ONEWAY_FWD; + } + else if (RequiredSArgument[i] == 'y') + { + FlagsFromArgument |= WPFLAG_ONEWAY_BACK; + } + else if (RequiredSArgument[i] == 'g') + { + FlagsFromArgument |= WPFLAG_GOALPOINT; + } + else if (RequiredSArgument[i] == 'n') + { + FlagsFromArgument |= WPFLAG_NOVIS; + } + else if (RequiredSArgument[i] == 'm') + { + FlagsFromArgument |= WPFLAG_NOMOVEFUNC; + } + + i++; + } + + OptionalSArgument = ConcatArgs( 2 ); + + if (OptionalSArgument) + { + OptionalArgument = atoi(OptionalSArgument); + } + + if (OptionalSArgument && OptionalSArgument[0]) + { + WPFlagsModify(OptionalArgument, FlagsFromArgument); + } + else + { + G_Printf(S_COLOR_YELLOW "Waypoint number (to modify) needed for bot_wp_switchflags\nSyntax: bot_wp_switchflags \n"); + } + return 1; + } + + if (Q_stricmp (cmd, "bot_wp_killoneways") == 0) + { + i = 0; + + while (i < gWPNum) + { + if (gWPArray[i] && gWPArray[i]->inuse) + { + if (gWPArray[i]->flags & WPFLAG_ONEWAY_FWD) + { + gWPArray[i]->flags -= WPFLAG_ONEWAY_FWD; + } + if (gWPArray[i]->flags & WPFLAG_ONEWAY_BACK) + { + gWPArray[i]->flags -= WPFLAG_ONEWAY_BACK; + } + } + + i++; + } + + return 1; + } + +#ifndef _XBOX + if (Q_stricmp (cmd, "bot_wp_save") == 0) + { + gDeactivated = 0; + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + SavePathData(mapname.string); + return 1; + } +#endif + + return 0; +} diff --git a/code/game/anims.h b/code/game/anims.h new file mode 100644 index 0000000..8ad06f5 --- /dev/null +++ b/code/game/anims.h @@ -0,0 +1,1797 @@ +#ifndef __ANIMS_H__ +#define __ANIMS_H__ +// playerAnimations + + +typedef enum //# animNumber_e +{ + //================================================= + //HEAD ANIMS + //================================================= + //# #sep Head-only anims + FACE_TALK0, //# silent + FACE_TALK1, //# quiet + FACE_TALK2, //# semi-quiet + FACE_TALK3, //# semi-loud + FACE_TALK4, //# loud + FACE_ALERT, //# + FACE_SMILE, //# + FACE_FROWN, //# + FACE_DEAD, //# + + //================================================= + //ANIMS IN WHICH UPPER AND LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep BOTH_ DEATHS + BOTH_DEATH1, //# First Death anim + BOTH_DEATH2, //# Second Death anim + BOTH_DEATH3, //# Third Death anim + BOTH_DEATH4, //# Fourth Death anim + BOTH_DEATH5, //# Fifth Death anim + BOTH_DEATH6, //# Sixth Death anim + BOTH_DEATH7, //# Seventh Death anim + BOTH_DEATH8, //# + BOTH_DEATH9, //# + BOTH_DEATH10, //# + BOTH_DEATH11, //# + BOTH_DEATH12, //# + BOTH_DEATH13, //# + BOTH_DEATH14, //# + BOTH_DEATH15, //# + BOTH_DEATH16, //# + BOTH_DEATH17, //# + BOTH_DEATH18, //# + BOTH_DEATH19, //# + BOTH_DEATH20, //# + BOTH_DEATH21, //# + BOTH_DEATH22, //# + BOTH_DEATH23, //# + BOTH_DEATH24, //# + BOTH_DEATH25, //# + + BOTH_DEATHFORWARD1, //# First Death in which they get thrown forward + BOTH_DEATHFORWARD2, //# Second Death in which they get thrown forward + BOTH_DEATHFORWARD3, //# Tavion's falling in cin# 23 + BOTH_DEATHBACKWARD1, //# First Death in which they get thrown backward + BOTH_DEATHBACKWARD2, //# Second Death in which they get thrown backward + + BOTH_DEATH1IDLE, //# Idle while close to death + BOTH_LYINGDEATH1, //# Death to play when killed lying down + BOTH_STUMBLEDEATH1, //# Stumble forward and fall face first death + BOTH_FALLDEATH1, //# Fall forward off a high cliff and splat death - start + BOTH_FALLDEATH1INAIR, //# Fall forward off a high cliff and splat death - loop + BOTH_FALLDEATH1LAND, //# Fall forward off a high cliff and splat death - hit bottom + BOTH_DEATH_ROLL, //# Death anim from a roll + BOTH_DEATH_FLIP, //# Death anim from a flip + BOTH_DEATH_SPIN_90_R, //# Death anim when facing 90 degrees right + BOTH_DEATH_SPIN_90_L, //# Death anim when facing 90 degrees left + BOTH_DEATH_SPIN_180, //# Death anim when facing backwards + BOTH_DEATH_LYING_UP, //# Death anim when lying on back + BOTH_DEATH_LYING_DN, //# Death anim when lying on front + BOTH_DEATH_FALLING_DN, //# Death anim when falling on face + BOTH_DEATH_FALLING_UP, //# Death anim when falling on back + BOTH_DEATH_CROUCHED, //# Death anim when crouched + //# #sep BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + BOTH_DEAD1, //# First Death finished pose + BOTH_DEAD2, //# Second Death finished pose + BOTH_DEAD3, //# Third Death finished pose + BOTH_DEAD4, //# Fourth Death finished pose + BOTH_DEAD5, //# Fifth Death finished pose + BOTH_DEAD6, //# Sixth Death finished pose + BOTH_DEAD7, //# Seventh Death finished pose + BOTH_DEAD8, //# + BOTH_DEAD9, //# + BOTH_DEAD10, //# + BOTH_DEAD11, //# + BOTH_DEAD12, //# + BOTH_DEAD13, //# + BOTH_DEAD14, //# + BOTH_DEAD15, //# + BOTH_DEAD16, //# + BOTH_DEAD17, //# + BOTH_DEAD18, //# + BOTH_DEAD19, //# + BOTH_DEAD20, //# + BOTH_DEAD21, //# + BOTH_DEAD22, //# + BOTH_DEAD23, //# + BOTH_DEAD24, //# + BOTH_DEAD25, //# + BOTH_DEADFORWARD1, //# First thrown forward death finished pose + BOTH_DEADFORWARD2, //# Second thrown forward death finished pose + BOTH_DEADBACKWARD1, //# First thrown backward death finished pose + BOTH_DEADBACKWARD2, //# Second thrown backward death finished pose + BOTH_LYINGDEAD1, //# Killed lying down death finished pose + BOTH_STUMBLEDEAD1, //# Stumble forward death finished pose + BOTH_FALLDEAD1LAND, //# Fall forward and splat death finished pose + //# #sep BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + BOTH_DEADFLOP1, //# React to being shot from First Death finished pose + BOTH_DEADFLOP2, //# React to being shot from Second Death finished pose + BOTH_DISMEMBER_HEAD1, //# + BOTH_DISMEMBER_TORSO1, //# + BOTH_DISMEMBER_LLEG, //# + BOTH_DISMEMBER_RLEG, //# + BOTH_DISMEMBER_RARM, //# + BOTH_DISMEMBER_LARM, //# + //# #sep BOTH_ PAINS + BOTH_PAIN1, //# First take pain anim + BOTH_PAIN2, //# Second take pain anim + BOTH_PAIN3, //# Third take pain anim + BOTH_PAIN4, //# Fourth take pain anim + BOTH_PAIN5, //# Fifth take pain anim - from behind + BOTH_PAIN6, //# Sixth take pain anim - from behind + BOTH_PAIN7, //# Seventh take pain anim - from behind + BOTH_PAIN8, //# Eigth take pain anim - from behind + BOTH_PAIN9, //# + BOTH_PAIN10, //# + BOTH_PAIN11, //# + BOTH_PAIN12, //# + BOTH_PAIN13, //# + BOTH_PAIN14, //# + BOTH_PAIN15, //# + BOTH_PAIN16, //# + BOTH_PAIN17, //# + BOTH_PAIN18, //# + + //# #sep BOTH_ ATTACKS + BOTH_ATTACK1, //# Attack with stun baton + BOTH_ATTACK2, //# Attack with one-handed pistol + BOTH_ATTACK3, //# Attack with blaster rifle + BOTH_ATTACK4, //# Attack with disruptor + BOTH_ATTACK5, //# Another Rancor Attack + BOTH_ATTACK6, //# Yet Another Rancor Attack + BOTH_ATTACK7, //# Yet Another Rancor Attack + BOTH_ATTACK10, //# Attack with thermal det + BOTH_ATTACK11, //# "Attack" with tripmine and detpack + BOTH_MELEE1, //# First melee attack + BOTH_MELEE2, //# Second melee attack + BOTH_THERMAL_READY, //# pull back with thermal + BOTH_THERMAL_THROW, //# throw thermal + //* #sep BOTH_ SABER ANIMS + //Saber attack anims - power level 1 + BOTH_A1_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A1__L__R, //# Fast weak horizontal attack left to right + BOTH_A1__R__L, //# Fast weak horizontal attack right to left + BOTH_A1_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A1_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A1_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A1_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T1_BR__R, //# Fast arc bottom right to right + BOTH_T1_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T1_BR__L, //# Fast weak spin bottom right to left + BOTH_T1_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T1__R_TR, //# Fast arc right to top right + BOTH_T1__R_TL, //# Fast arc right to top left + BOTH_T1__R__L, //# Fast weak spin right to left + BOTH_T1__R_BL, //# Fast weak spin right to bottom left + BOTH_T1_TR_BR, //# Fast arc top right to bottom right + BOTH_T1_TR_TL, //# Fast arc top right to top left + BOTH_T1_TR__L, //# Fast arc top right to left + BOTH_T1_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T1_T__BR, //# Fast arc top to bottom right + BOTH_T1_T___R, //# Fast arc top to right + BOTH_T1_T__TR, //# Fast arc top to top right + BOTH_T1_T__TL, //# Fast arc top to top left + BOTH_T1_T___L, //# Fast arc top to left + BOTH_T1_T__BL, //# Fast arc top to bottom left + BOTH_T1_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T1_TL_BL, //# Fast arc top left to bottom left + BOTH_T1__L_BR, //# Fast weak spin left to bottom right + BOTH_T1__L__R, //# Fast weak spin left to right + BOTH_T1__L_TL, //# Fast arc left to top left + BOTH_T1_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T1_BL__R, //# Fast weak spin bottom left to right + BOTH_T1_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T1_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T1_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T1_TR_BR) + BOTH_T1_BR_T_, //# Fast arc bottom right to top (use: BOTH_T1_T__BR) + BOTH_T1__R_BR, //# Fast arc right to bottom right (use: BOTH_T1_BR__R) + BOTH_T1__R_T_, //# Fast ar right to top (use: BOTH_T1_T___R) + BOTH_T1_TR__R, //# Fast arc top right to right (use: BOTH_T1__R_TR) + BOTH_T1_TR_T_, //# Fast arc top right to top (use: BOTH_T1_T__TR) + BOTH_T1_TL__R, //# Fast arc top left to right (use: BOTH_T1__R_TL) + BOTH_T1_TL_TR, //# Fast arc top left to top right (use: BOTH_T1_TR_TL) + BOTH_T1_TL_T_, //# Fast arc top left to top (use: BOTH_T1_T__TL) + BOTH_T1_TL__L, //# Fast arc top left to left (use: BOTH_T1__L_TL) + BOTH_T1__L_TR, //# Fast arc left to top right (use: BOTH_T1_TR__L) + BOTH_T1__L_T_, //# Fast arc left to top (use: BOTH_T1_T___L) + BOTH_T1__L_BL, //# Fast arc left to bottom left (use: BOTH_T1_BL__L) + BOTH_T1_BL_T_, //# Fast arc bottom left to top (use: BOTH_T1_T__BL) + BOTH_T1_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T1_TL_BL) + //Saber Attack Start Transitions + BOTH_S1_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S1_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S1_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S1_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S1_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S1_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S1_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R1_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R1__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R1__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R1_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R1_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R1_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R1_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B1_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B1__R___, //# Bounce-back if attack from R is blocked + BOTH_B1_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B1_T____, //# Bounce-back if attack from T is blocked + BOTH_B1_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B1__L___, //# Bounce-back if attack from L is blocked + BOTH_B1_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D1_BR___, //# Deflection toward BR + BOTH_D1__R___, //# Deflection toward R + BOTH_D1_TR___, //# Deflection toward TR + BOTH_D1_TL___, //# Deflection toward TL + BOTH_D1__L___, //# Deflection toward L + BOTH_D1_BL___, //# Deflection toward BL + BOTH_D1_B____, //# Deflection toward B + //Saber attack anims - power level 2 + BOTH_A2_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A2__L__R, //# Fast weak horizontal attack left to right + BOTH_A2__R__L, //# Fast weak horizontal attack right to left + BOTH_A2_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A2_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A2_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A2_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T2_BR__R, //# Fast arc bottom right to right + BOTH_T2_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T2_BR__L, //# Fast weak spin bottom right to left + BOTH_T2_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T2__R_TR, //# Fast arc right to top right + BOTH_T2__R_TL, //# Fast arc right to top left + BOTH_T2__R__L, //# Fast weak spin right to left + BOTH_T2__R_BL, //# Fast weak spin right to bottom left + BOTH_T2_TR_BR, //# Fast arc top right to bottom right + BOTH_T2_TR_TL, //# Fast arc top right to top left + BOTH_T2_TR__L, //# Fast arc top right to left + BOTH_T2_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T2_T__BR, //# Fast arc top to bottom right + BOTH_T2_T___R, //# Fast arc top to right + BOTH_T2_T__TR, //# Fast arc top to top right + BOTH_T2_T__TL, //# Fast arc top to top left + BOTH_T2_T___L, //# Fast arc top to left + BOTH_T2_T__BL, //# Fast arc top to bottom left + BOTH_T2_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T2_TL_BL, //# Fast arc top left to bottom left + BOTH_T2__L_BR, //# Fast weak spin left to bottom right + BOTH_T2__L__R, //# Fast weak spin left to right + BOTH_T2__L_TL, //# Fast arc left to top left + BOTH_T2_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T2_BL__R, //# Fast weak spin bottom left to right + BOTH_T2_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T2_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T2_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T2_TR_BR) + BOTH_T2_BR_T_, //# Fast arc bottom right to top (use: BOTH_T2_T__BR) + BOTH_T2__R_BR, //# Fast arc right to bottom right (use: BOTH_T2_BR__R) + BOTH_T2__R_T_, //# Fast ar right to top (use: BOTH_T2_T___R) + BOTH_T2_TR__R, //# Fast arc top right to right (use: BOTH_T2__R_TR) + BOTH_T2_TR_T_, //# Fast arc top right to top (use: BOTH_T2_T__TR) + BOTH_T2_TL__R, //# Fast arc top left to right (use: BOTH_T2__R_TL) + BOTH_T2_TL_TR, //# Fast arc top left to top right (use: BOTH_T2_TR_TL) + BOTH_T2_TL_T_, //# Fast arc top left to top (use: BOTH_T2_T__TL) + BOTH_T2_TL__L, //# Fast arc top left to left (use: BOTH_T2__L_TL) + BOTH_T2__L_TR, //# Fast arc left to top right (use: BOTH_T2_TR__L) + BOTH_T2__L_T_, //# Fast arc left to top (use: BOTH_T2_T___L) + BOTH_T2__L_BL, //# Fast arc left to bottom left (use: BOTH_T2_BL__L) + BOTH_T2_BL_T_, //# Fast arc bottom left to top (use: BOTH_T2_T__BL) + BOTH_T2_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T2_TL_BL) + //Saber Attack Start Transitions + BOTH_S2_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S2_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S2_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S2_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S2_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S2_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S2_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R2_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R2__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R2__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R2_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R2_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R2_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R2_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B2_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B2__R___, //# Bounce-back if attack from R is blocked + BOTH_B2_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B2_T____, //# Bounce-back if attack from T is blocked + BOTH_B2_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B2__L___, //# Bounce-back if attack from L is blocked + BOTH_B2_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D2_BR___, //# Deflection toward BR + BOTH_D2__R___, //# Deflection toward R + BOTH_D2_TR___, //# Deflection toward TR + BOTH_D2_TL___, //# Deflection toward TL + BOTH_D2__L___, //# Deflection toward L + BOTH_D2_BL___, //# Deflection toward BL + BOTH_D2_B____, //# Deflection toward B + //Saber attack anims - power level 3 + BOTH_A3_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A3__L__R, //# Fast weak horizontal attack left to right + BOTH_A3__R__L, //# Fast weak horizontal attack right to left + BOTH_A3_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A3_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A3_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A3_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T3_BR__R, //# Fast arc bottom right to right + BOTH_T3_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T3_BR__L, //# Fast weak spin bottom right to left + BOTH_T3_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T3__R_TR, //# Fast arc right to top right + BOTH_T3__R_TL, //# Fast arc right to top left + BOTH_T3__R__L, //# Fast weak spin right to left + BOTH_T3__R_BL, //# Fast weak spin right to bottom left + BOTH_T3_TR_BR, //# Fast arc top right to bottom right + BOTH_T3_TR_TL, //# Fast arc top right to top left + BOTH_T3_TR__L, //# Fast arc top right to left + BOTH_T3_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T3_T__BR, //# Fast arc top to bottom right + BOTH_T3_T___R, //# Fast arc top to right + BOTH_T3_T__TR, //# Fast arc top to top right + BOTH_T3_T__TL, //# Fast arc top to top left + BOTH_T3_T___L, //# Fast arc top to left + BOTH_T3_T__BL, //# Fast arc top to bottom left + BOTH_T3_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T3_TL_BL, //# Fast arc top left to bottom left + BOTH_T3__L_BR, //# Fast weak spin left to bottom right + BOTH_T3__L__R, //# Fast weak spin left to right + BOTH_T3__L_TL, //# Fast arc left to top left + BOTH_T3_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T3_BL__R, //# Fast weak spin bottom left to right + BOTH_T3_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T3_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T3_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T3_TR_BR) + BOTH_T3_BR_T_, //# Fast arc bottom right to top (use: BOTH_T3_T__BR) + BOTH_T3__R_BR, //# Fast arc right to bottom right (use: BOTH_T3_BR__R) + BOTH_T3__R_T_, //# Fast ar right to top (use: BOTH_T3_T___R) + BOTH_T3_TR__R, //# Fast arc top right to right (use: BOTH_T3__R_TR) + BOTH_T3_TR_T_, //# Fast arc top right to top (use: BOTH_T3_T__TR) + BOTH_T3_TL__R, //# Fast arc top left to right (use: BOTH_T3__R_TL) + BOTH_T3_TL_TR, //# Fast arc top left to top right (use: BOTH_T3_TR_TL) + BOTH_T3_TL_T_, //# Fast arc top left to top (use: BOTH_T3_T__TL) + BOTH_T3_TL__L, //# Fast arc top left to left (use: BOTH_T3__L_TL) + BOTH_T3__L_TR, //# Fast arc left to top right (use: BOTH_T3_TR__L) + BOTH_T3__L_T_, //# Fast arc left to top (use: BOTH_T3_T___L) + BOTH_T3__L_BL, //# Fast arc left to bottom left (use: BOTH_T3_BL__L) + BOTH_T3_BL_T_, //# Fast arc bottom left to top (use: BOTH_T3_T__BL) + BOTH_T3_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T3_TL_BL) + //Saber Attack Start Transitions + BOTH_S3_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S3_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S3_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S3_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S3_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S3_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S3_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R3_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R3__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R3__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R3_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R3_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R3_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R3_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B3_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B3__R___, //# Bounce-back if attack from R is blocked + BOTH_B3_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B3_T____, //# Bounce-back if attack from T is blocked + BOTH_B3_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B3__L___, //# Bounce-back if attack from L is blocked + BOTH_B3_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D3_BR___, //# Deflection toward BR + BOTH_D3__R___, //# Deflection toward R + BOTH_D3_TR___, //# Deflection toward TR + BOTH_D3_TL___, //# Deflection toward TL + BOTH_D3__L___, //# Deflection toward L + BOTH_D3_BL___, //# Deflection toward BL + BOTH_D3_B____, //# Deflection toward B + //Saber attack anims - power level 4 - Desann's + BOTH_A4_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A4__L__R, //# Fast weak horizontal attack left to right + BOTH_A4__R__L, //# Fast weak horizontal attack right to left + BOTH_A4_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A4_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A4_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A4_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T4_BR__R, //# Fast arc bottom right to right + BOTH_T4_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T4_BR__L, //# Fast weak spin bottom right to left + BOTH_T4_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T4__R_TR, //# Fast arc right to top right + BOTH_T4__R_TL, //# Fast arc right to top left + BOTH_T4__R__L, //# Fast weak spin right to left + BOTH_T4__R_BL, //# Fast weak spin right to bottom left + BOTH_T4_TR_BR, //# Fast arc top right to bottom right + BOTH_T4_TR_TL, //# Fast arc top right to top left + BOTH_T4_TR__L, //# Fast arc top right to left + BOTH_T4_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T4_T__BR, //# Fast arc top to bottom right + BOTH_T4_T___R, //# Fast arc top to right + BOTH_T4_T__TR, //# Fast arc top to top right + BOTH_T4_T__TL, //# Fast arc top to top left + BOTH_T4_T___L, //# Fast arc top to left + BOTH_T4_T__BL, //# Fast arc top to bottom left + BOTH_T4_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T4_TL_BL, //# Fast arc top left to bottom left + BOTH_T4__L_BR, //# Fast weak spin left to bottom right + BOTH_T4__L__R, //# Fast weak spin left to right + BOTH_T4__L_TL, //# Fast arc left to top left + BOTH_T4_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T4_BL__R, //# Fast weak spin bottom left to right + BOTH_T4_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T4_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T4_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T4_TR_BR) + BOTH_T4_BR_T_, //# Fast arc bottom right to top (use: BOTH_T4_T__BR) + BOTH_T4__R_BR, //# Fast arc right to bottom right (use: BOTH_T4_BR__R) + BOTH_T4__R_T_, //# Fast ar right to top (use: BOTH_T4_T___R) + BOTH_T4_TR__R, //# Fast arc top right to right (use: BOTH_T4__R_TR) + BOTH_T4_TR_T_, //# Fast arc top right to top (use: BOTH_T4_T__TR) + BOTH_T4_TL__R, //# Fast arc top left to right (use: BOTH_T4__R_TL) + BOTH_T4_TL_TR, //# Fast arc top left to top right (use: BOTH_T4_TR_TL) + BOTH_T4_TL_T_, //# Fast arc top left to top (use: BOTH_T4_T__TL) + BOTH_T4_TL__L, //# Fast arc top left to left (use: BOTH_T4__L_TL) + BOTH_T4__L_TR, //# Fast arc left to top right (use: BOTH_T4_TR__L) + BOTH_T4__L_T_, //# Fast arc left to top (use: BOTH_T4_T___L) + BOTH_T4__L_BL, //# Fast arc left to bottom left (use: BOTH_T4_BL__L) + BOTH_T4_BL_T_, //# Fast arc bottom left to top (use: BOTH_T4_T__BL) + BOTH_T4_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T4_TL_BL) + //Saber Attack Start Transitions + BOTH_S4_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S4_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S4_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S4_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S4_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S4_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S4_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R4_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R4__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R4__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R4_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R4_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R4_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R4_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B4_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B4__R___, //# Bounce-back if attack from R is blocked + BOTH_B4_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B4_T____, //# Bounce-back if attack from T is blocked + BOTH_B4_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B4__L___, //# Bounce-back if attack from L is blocked + BOTH_B4_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D4_BR___, //# Deflection toward BR + BOTH_D4__R___, //# Deflection toward R + BOTH_D4_TR___, //# Deflection toward TR + BOTH_D4_TL___, //# Deflection toward TL + BOTH_D4__L___, //# Deflection toward L + BOTH_D4_BL___, //# Deflection toward BL + BOTH_D4_B____, //# Deflection toward B + //Saber attack anims - power level 5 - Tavion's + BOTH_A5_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A5__L__R, //# Fast weak horizontal attack left to right + BOTH_A5__R__L, //# Fast weak horizontal attack right to left + BOTH_A5_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A5_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A5_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A5_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T5_BR__R, //# Fast arc bottom right to right + BOTH_T5_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T5_BR__L, //# Fast weak spin bottom right to left + BOTH_T5_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T5__R_TR, //# Fast arc right to top right + BOTH_T5__R_TL, //# Fast arc right to top left + BOTH_T5__R__L, //# Fast weak spin right to left + BOTH_T5__R_BL, //# Fast weak spin right to bottom left + BOTH_T5_TR_BR, //# Fast arc top right to bottom right + BOTH_T5_TR_TL, //# Fast arc top right to top left + BOTH_T5_TR__L, //# Fast arc top right to left + BOTH_T5_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T5_T__BR, //# Fast arc top to bottom right + BOTH_T5_T___R, //# Fast arc top to right + BOTH_T5_T__TR, //# Fast arc top to top right + BOTH_T5_T__TL, //# Fast arc top to top left + BOTH_T5_T___L, //# Fast arc top to left + BOTH_T5_T__BL, //# Fast arc top to bottom left + BOTH_T5_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T5_TL_BL, //# Fast arc top left to bottom left + BOTH_T5__L_BR, //# Fast weak spin left to bottom right + BOTH_T5__L__R, //# Fast weak spin left to right + BOTH_T5__L_TL, //# Fast arc left to top left + BOTH_T5_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T5_BL__R, //# Fast weak spin bottom left to right + BOTH_T5_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T5_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T5_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T5_TR_BR) + BOTH_T5_BR_T_, //# Fast arc bottom right to top (use: BOTH_T5_T__BR) + BOTH_T5__R_BR, //# Fast arc right to bottom right (use: BOTH_T5_BR__R) + BOTH_T5__R_T_, //# Fast ar right to top (use: BOTH_T5_T___R) + BOTH_T5_TR__R, //# Fast arc top right to right (use: BOTH_T5__R_TR) + BOTH_T5_TR_T_, //# Fast arc top right to top (use: BOTH_T5_T__TR) + BOTH_T5_TL__R, //# Fast arc top left to right (use: BOTH_T5__R_TL) + BOTH_T5_TL_TR, //# Fast arc top left to top right (use: BOTH_T5_TR_TL) + BOTH_T5_TL_T_, //# Fast arc top left to top (use: BOTH_T5_T__TL) + BOTH_T5_TL__L, //# Fast arc top left to left (use: BOTH_T5__L_TL) + BOTH_T5__L_TR, //# Fast arc left to top right (use: BOTH_T5_TR__L) + BOTH_T5__L_T_, //# Fast arc left to top (use: BOTH_T5_T___L) + BOTH_T5__L_BL, //# Fast arc left to bottom left (use: BOTH_T5_BL__L) + BOTH_T5_BL_T_, //# Fast arc bottom left to top (use: BOTH_T5_T__BL) + BOTH_T5_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T5_TL_BL) + //Saber Attack Start Transitions + BOTH_S5_S1_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S5_S1__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S5_S1__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S5_S1_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S5_S1_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S5_S1_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S5_S1_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R5_B__S1, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R5__L_S1, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R5__R_S1, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R5_TL_S1, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R5_BR_S1, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R5_BL_S1, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R5_TR_S1, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B5_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B5__R___, //# Bounce-back if attack from R is blocked + BOTH_B5_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B5_T____, //# Bounce-back if attack from T is blocked + BOTH_B5_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B5__L___, //# Bounce-back if attack from L is blocked + BOTH_B5_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D5_BR___, //# Deflection toward BR + BOTH_D5__R___, //# Deflection toward R + BOTH_D5_TR___, //# Deflection toward TR + BOTH_D5_TL___, //# Deflection toward TL + BOTH_D5__L___, //# Deflection toward L + BOTH_D5_BL___, //# Deflection toward BL + BOTH_D5_B____, //# Deflection toward B + //Saber attack anims - power level 6 + BOTH_A6_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A6__L__R, //# Fast weak horizontal attack left to right + BOTH_A6__R__L, //# Fast weak horizontal attack right to left + BOTH_A6_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A6_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A6_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A6_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T6_BR__R, //# Fast arc bottom right to right + BOTH_T6_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T6_BR__L, //# Fast weak spin bottom right to left + BOTH_T6_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T6__R_TR, //# Fast arc right to top right + BOTH_T6__R_TL, //# Fast arc right to top left + BOTH_T6__R__L, //# Fast weak spin right to left + BOTH_T6__R_BL, //# Fast weak spin right to bottom left + BOTH_T6_TR_BR, //# Fast arc top right to bottom right + BOTH_T6_TR_TL, //# Fast arc top right to top left + BOTH_T6_TR__L, //# Fast arc top right to left + BOTH_T6_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T6_T__BR, //# Fast arc top to bottom right + BOTH_T6_T___R, //# Fast arc top to right + BOTH_T6_T__TR, //# Fast arc top to top right + BOTH_T6_T__TL, //# Fast arc top to top left + BOTH_T6_T___L, //# Fast arc top to left + BOTH_T6_T__BL, //# Fast arc top to bottom left + BOTH_T6_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T6_TL_BL, //# Fast arc top left to bottom left + BOTH_T6__L_BR, //# Fast weak spin left to bottom right + BOTH_T6__L__R, //# Fast weak spin left to right + BOTH_T6__L_TL, //# Fast arc left to top left + BOTH_T6_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T6_BL__R, //# Fast weak spin bottom left to right + BOTH_T6_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T6_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T6_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T6_TR_BR) + BOTH_T6_BR_T_, //# Fast arc bottom right to top (use: BOTH_T6_T__BR) + BOTH_T6__R_BR, //# Fast arc right to bottom right (use: BOTH_T6_BR__R) + BOTH_T6__R_T_, //# Fast ar right to top (use: BOTH_T6_T___R) + BOTH_T6_TR__R, //# Fast arc top right to right (use: BOTH_T6__R_TR) + BOTH_T6_TR_T_, //# Fast arc top right to top (use: BOTH_T6_T__TR) + BOTH_T6_TL__R, //# Fast arc top left to right (use: BOTH_T6__R_TL) + BOTH_T6_TL_TR, //# Fast arc top left to top right (use: BOTH_T6_TR_TL) + BOTH_T6_TL_T_, //# Fast arc top left to top (use: BOTH_T6_T__TL) + BOTH_T6_TL__L, //# Fast arc top left to left (use: BOTH_T6__L_TL) + BOTH_T6__L_TR, //# Fast arc left to top right (use: BOTH_T6_TR__L) + BOTH_T6__L_T_, //# Fast arc left to top (use: BOTH_T6_T___L) + BOTH_T6__L_BL, //# Fast arc left to bottom left (use: BOTH_T6_BL__L) + BOTH_T6_BL_T_, //# Fast arc bottom left to top (use: BOTH_T6_T__BL) + BOTH_T6_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T6_TL_BL) + //Saber Attack Start Transitions + BOTH_S6_S6_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S6_S6__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S6_S6__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S6_S6_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S6_S6_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S6_S6_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S6_S6_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R6_B__S6, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R6__L_S6, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R6__R_S6, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R6_TL_S6, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R6_BR_S6, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R6_BL_S6, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R6_TR_S6, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B6_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B6__R___, //# Bounce-back if attack from R is blocked + BOTH_B6_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B6_T____, //# Bounce-back if attack from T is blocked + BOTH_B6_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B6__L___, //# Bounce-back if attack from L is blocked + BOTH_B6_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D6_BR___, //# Deflection toward BR + BOTH_D6__R___, //# Deflection toward R + BOTH_D6_TR___, //# Deflection toward TR + BOTH_D6_TL___, //# Deflection toward TL + BOTH_D6__L___, //# Deflection toward L + BOTH_D6_BL___, //# Deflection toward BL + BOTH_D6_B____, //# Deflection toward B + //Saber attack anims - power level 7 + BOTH_A7_T__B_, //# Fast weak vertical attack top to bottom + BOTH_A7__L__R, //# Fast weak horizontal attack left to right + BOTH_A7__R__L, //# Fast weak horizontal attack right to left + BOTH_A7_TL_BR, //# Fast weak diagonal attack top left to botom right + BOTH_A7_BR_TL, //# Fast weak diagonal attack top left to botom right + BOTH_A7_BL_TR, //# Fast weak diagonal attack bottom left to top right + BOTH_A7_TR_BL, //# Fast weak diagonal attack bottom left to right + //Saber Arc and Spin Transitions + BOTH_T7_BR__R, //# Fast arc bottom right to right + BOTH_T7_BR_TL, //# Fast weak spin bottom right to top left + BOTH_T7_BR__L, //# Fast weak spin bottom right to left + BOTH_T7_BR_BL, //# Fast weak spin bottom right to bottom left + BOTH_T7__R_TR, //# Fast arc right to top right + BOTH_T7__R_TL, //# Fast arc right to top left + BOTH_T7__R__L, //# Fast weak spin right to left + BOTH_T7__R_BL, //# Fast weak spin right to bottom left + BOTH_T7_TR_BR, //# Fast arc top right to bottom right + BOTH_T7_TR_TL, //# Fast arc top right to top left + BOTH_T7_TR__L, //# Fast arc top right to left + BOTH_T7_TR_BL, //# Fast weak spin top right to bottom left + BOTH_T7_T__BR, //# Fast arc top to bottom right + BOTH_T7_T___R, //# Fast arc top to right + BOTH_T7_T__TR, //# Fast arc top to top right + BOTH_T7_T__TL, //# Fast arc top to top left + BOTH_T7_T___L, //# Fast arc top to left + BOTH_T7_T__BL, //# Fast arc top to bottom left + BOTH_T7_TL_BR, //# Fast weak spin top left to bottom right + BOTH_T7_TL_BL, //# Fast arc top left to bottom left + BOTH_T7__L_BR, //# Fast weak spin left to bottom right + BOTH_T7__L__R, //# Fast weak spin left to right + BOTH_T7__L_TL, //# Fast arc left to top left + BOTH_T7_BL_BR, //# Fast weak spin bottom left to bottom right + BOTH_T7_BL__R, //# Fast weak spin bottom left to right + BOTH_T7_BL_TR, //# Fast weak spin bottom left to top right + BOTH_T7_BL__L, //# Fast arc bottom left to left + //Saber Arc Transitions that use existing animations played backwards + BOTH_T7_BR_TR, //# Fast arc bottom right to top right (use: BOTH_T7_TR_BR) + BOTH_T7_BR_T_, //# Fast arc bottom right to top (use: BOTH_T7_T__BR) + BOTH_T7__R_BR, //# Fast arc right to bottom right (use: BOTH_T7_BR__R) + BOTH_T7__R_T_, //# Fast ar right to top (use: BOTH_T7_T___R) + BOTH_T7_TR__R, //# Fast arc top right to right (use: BOTH_T7__R_TR) + BOTH_T7_TR_T_, //# Fast arc top right to top (use: BOTH_T7_T__TR) + BOTH_T7_TL__R, //# Fast arc top left to right (use: BOTH_T7__R_TL) + BOTH_T7_TL_TR, //# Fast arc top left to top right (use: BOTH_T7_TR_TL) + BOTH_T7_TL_T_, //# Fast arc top left to top (use: BOTH_T7_T__TL) + BOTH_T7_TL__L, //# Fast arc top left to left (use: BOTH_T7__L_TL) + BOTH_T7__L_TR, //# Fast arc left to top right (use: BOTH_T7_TR__L) + BOTH_T7__L_T_, //# Fast arc left to top (use: BOTH_T7_T___L) + BOTH_T7__L_BL, //# Fast arc left to bottom left (use: BOTH_T7_BL__L) + BOTH_T7_BL_T_, //# Fast arc bottom left to top (use: BOTH_T7_T__BL) + BOTH_T7_BL_TL, //# Fast arc bottom left to top left (use: BOTH_T7_TL_BL) + //Saber Attack Start Transitions + BOTH_S7_S7_T_, //# Fast plain transition from stance1 to top-to-bottom Fast weak attack + BOTH_S7_S7__L, //# Fast plain transition from stance1 to left-to-right Fast weak attack + BOTH_S7_S7__R, //# Fast plain transition from stance1 to right-to-left Fast weak attack + BOTH_S7_S7_TL, //# Fast plain transition from stance1 to top-left-to-bottom-right Fast weak attack + BOTH_S7_S7_BR, //# Fast plain transition from stance1 to bottom-right-to-top-left Fast weak attack + BOTH_S7_S7_BL, //# Fast plain transition from stance1 to bottom-left-to-top-right Fast weak attack + BOTH_S7_S7_TR, //# Fast plain transition from stance1 to top-right-to-bottom-left Fast weak attack + //Saber Attack Return Transitions + BOTH_R7_B__S7, //# Fast plain transition from top-to-bottom Fast weak attack to stance1 + BOTH_R7__L_S7, //# Fast plain transition from left-to-right Fast weak attack to stance1 + BOTH_R7__R_S7, //# Fast plain transition from right-to-left Fast weak attack to stance1 + BOTH_R7_TL_S7, //# Fast plain transition from top-left-to-bottom-right Fast weak attack to stance1 + BOTH_R7_BR_S7, //# Fast plain transition from bottom-right-to-top-left Fast weak attack to stance1 + BOTH_R7_BL_S7, //# Fast plain transition from bottom-left-to-top-right Fast weak attack to stance1 + BOTH_R7_TR_S7, //# Fast plain transition from top-right-to-bottom-left Fast weak attack + //Saber Attack Bounces (first 4 frames of an attack, played backwards) + BOTH_B7_BR___, //# Bounce-back if attack from BR is blocked + BOTH_B7__R___, //# Bounce-back if attack from R is blocked + BOTH_B7_TR___, //# Bounce-back if attack from TR is blocked + BOTH_B7_T____, //# Bounce-back if attack from T is blocked + BOTH_B7_TL___, //# Bounce-back if attack from TL is blocked + BOTH_B7__L___, //# Bounce-back if attack from L is blocked + BOTH_B7_BL___, //# Bounce-back if attack from BL is blocked + //Saber Attack Deflections (last 4 frames of an attack) + BOTH_D7_BR___, //# Deflection toward BR + BOTH_D7__R___, //# Deflection toward R + BOTH_D7_TR___, //# Deflection toward TR + BOTH_D7_TL___, //# Deflection toward TL + BOTH_D7__L___, //# Deflection toward L + BOTH_D7_BL___, //# Deflection toward BL + BOTH_D7_B____, //# Deflection toward B + //Saber parry anims + BOTH_P1_S1_T_, //# Block shot/saber top + BOTH_P1_S1_TR, //# Block shot/saber top right + BOTH_P1_S1_TL, //# Block shot/saber top left + BOTH_P1_S1_BL, //# Block shot/saber bottom left + BOTH_P1_S1_BR, //# Block shot/saber bottom right + //Saber knockaway + BOTH_K1_S1_T_, //# knockaway saber top + BOTH_K1_S1_TR, //# knockaway saber top right + BOTH_K1_S1_TL, //# knockaway saber top left + BOTH_K1_S1_BL, //# knockaway saber bottom left + BOTH_K1_S1_B_, //# knockaway saber bottom + BOTH_K1_S1_BR, //# knockaway saber bottom right + //Saber attack knocked away + BOTH_V1_BR_S1, //# BR attack knocked away + BOTH_V1__R_S1, //# R attack knocked away + BOTH_V1_TR_S1, //# TR attack knocked away + BOTH_V1_T__S1, //# T attack knocked away + BOTH_V1_TL_S1, //# TL attack knocked away + BOTH_V1__L_S1, //# L attack knocked away + BOTH_V1_BL_S1, //# BL attack knocked away + BOTH_V1_B__S1, //# B attack knocked away + //Saber parry broken + BOTH_H1_S1_T_, //# saber knocked down from top parry + BOTH_H1_S1_TR, //# saber knocked down-left from TR parry + BOTH_H1_S1_TL, //# saber knocked down-right from TL parry + BOTH_H1_S1_BL, //# saber knocked up-right from BL parry + BOTH_H1_S1_B_, //# saber knocked up over head from ready? + BOTH_H1_S1_BR, //# saber knocked up-left from BR parry + //Dual Saber parry anims + BOTH_P6_S6_T_, //# Block shot/saber top + BOTH_P6_S6_TR, //# Block shot/saber top right + BOTH_P6_S6_TL, //# Block shot/saber top left + BOTH_P6_S6_BL, //# Block shot/saber bottom left + BOTH_P6_S6_BR, //# Block shot/saber bottom right + //Dual Saber knockaway + BOTH_K6_S6_T_, //# knockaway saber top + BOTH_K6_S6_TR, //# knockaway saber top right + BOTH_K6_S6_TL, //# knockaway saber top left + BOTH_K6_S6_BL, //# knockaway saber bottom left + BOTH_K6_S6_B_, //# knockaway saber bottom + BOTH_K6_S6_BR, //# knockaway saber bottom right + //Dual Saber attack knocked away + BOTH_V6_BR_S6, //# BR attack knocked away + BOTH_V6__R_S6, //# R attack knocked away + BOTH_V6_TR_S6, //# TR attack knocked away + BOTH_V6_T__S6, //# T attack knocked away + BOTH_V6_TL_S6, //# TL attack knocked away + BOTH_V6__L_S6, //# L attack knocked away + BOTH_V6_BL_S6, //# BL attack knocked away + BOTH_V6_B__S6, //# B attack knocked away + //Dual Saber parry broken + BOTH_H6_S6_T_, //# saber knocked down from top parry + BOTH_H6_S6_TR, //# saber knocked down-left from TR parry + BOTH_H6_S6_TL, //# saber knocked down-right from TL parry + BOTH_H6_S6_BL, //# saber knocked up-right from BL parry + BOTH_H6_S6_B_, //# saber knocked up over head from ready? + BOTH_H6_S6_BR, //# saber knocked up-left from BR parry + //SaberStaff parry anims + BOTH_P7_S7_T_, //# Block shot/saber top + BOTH_P7_S7_TR, //# Block shot/saber top right + BOTH_P7_S7_TL, //# Block shot/saber top left + BOTH_P7_S7_BL, //# Block shot/saber bottom left + BOTH_P7_S7_BR, //# Block shot/saber bottom right + //SaberStaff knockaway + BOTH_K7_S7_T_, //# knockaway saber top + BOTH_K7_S7_TR, //# knockaway saber top right + BOTH_K7_S7_TL, //# knockaway saber top left + BOTH_K7_S7_BL, //# knockaway saber bottom left + BOTH_K7_S7_B_, //# knockaway saber bottom + BOTH_K7_S7_BR, //# knockaway saber bottom right + //SaberStaff attack knocked away + BOTH_V7_BR_S7, //# BR attack knocked away + BOTH_V7__R_S7, //# R attack knocked away + BOTH_V7_TR_S7, //# TR attack knocked away + BOTH_V7_T__S7, //# T attack knocked away + BOTH_V7_TL_S7, //# TL attack knocked away + BOTH_V7__L_S7, //# L attack knocked away + BOTH_V7_BL_S7, //# BL attack knocked away + BOTH_V7_B__S7, //# B attack knocked away + //SaberStaff parry broken + BOTH_H7_S7_T_, //# saber knocked down from top parry + BOTH_H7_S7_TR, //# saber knocked down-left from TR parry + BOTH_H7_S7_TL, //# saber knocked down-right from TL parry + BOTH_H7_S7_BL, //# saber knocked up-right from BL parry + BOTH_H7_S7_B_, //# saber knocked up over head from ready? + BOTH_H7_S7_BR, //# saber knocked up-left from BR parry + //Sabers locked anims + //* #sep BOTH_ SABER LOCKED ANIMS + //BOTH_(DL, S, ST)_(DL, S, ST)_(T, S)_(L, B, SB)_1(_W, _L) +//===Single locks================================================================== +//SINGLE vs. DUAL + //side locks - I'm using a single and they're using dual + BOTH_LK_S_DL_S_B_1_L, //normal break I lost + BOTH_LK_S_DL_S_B_1_W, //normal break I won + BOTH_LK_S_DL_S_L_1, //lock if I'm using single vs. a dual + BOTH_LK_S_DL_S_SB_1_L, //super break I lost + BOTH_LK_S_DL_S_SB_1_W, //super break I won + //top locks + BOTH_LK_S_DL_T_B_1_L, //normal break I lost + BOTH_LK_S_DL_T_B_1_W, //normal break I won + BOTH_LK_S_DL_T_L_1, //lock if I'm using single vs. a dual + BOTH_LK_S_DL_T_SB_1_L, //super break I lost + BOTH_LK_S_DL_T_SB_1_W, //super break I won +//SINGLE vs. STAFF + //side locks + BOTH_LK_S_ST_S_B_1_L, //normal break I lost + BOTH_LK_S_ST_S_B_1_W, //normal break I won + BOTH_LK_S_ST_S_L_1, //lock if I'm using single vs. a staff + BOTH_LK_S_ST_S_SB_1_L, //super break I lost + BOTH_LK_S_ST_S_SB_1_W, //super break I won + //top locks + BOTH_LK_S_ST_T_B_1_L, //normal break I lost + BOTH_LK_S_ST_T_B_1_W, //normal break I won + BOTH_LK_S_ST_T_L_1, //lock if I'm using single vs. a staff + BOTH_LK_S_ST_T_SB_1_L, //super break I lost + BOTH_LK_S_ST_T_SB_1_W, //super break I won +//SINGLE vs. SINGLE + //side locks + BOTH_LK_S_S_S_B_1_L, //normal break I lost + BOTH_LK_S_S_S_B_1_W, //normal break I won + BOTH_LK_S_S_S_L_1, //lock if I'm using single vs. a single and I initiated + BOTH_LK_S_S_S_SB_1_L, //super break I lost + BOTH_LK_S_S_S_SB_1_W, //super break I won + //top locks + BOTH_LK_S_S_T_B_1_L, //normal break I lost + BOTH_LK_S_S_T_B_1_W, //normal break I won + BOTH_LK_S_S_T_L_1, //lock if I'm using single vs. a single and I initiated + BOTH_LK_S_S_T_SB_1_L, //super break I lost + BOTH_LK_S_S_T_SB_1_W, //super break I won +//===Dual Saber locks================================================================== +//DUAL vs. DUAL + //side locks + BOTH_LK_DL_DL_S_B_1_L, //normal break I lost + BOTH_LK_DL_DL_S_B_1_W, //normal break I won + BOTH_LK_DL_DL_S_L_1, //lock if I'm using dual vs. dual and I initiated + BOTH_LK_DL_DL_S_SB_1_L, //super break I lost + BOTH_LK_DL_DL_S_SB_1_W, //super break I won + //top locks + BOTH_LK_DL_DL_T_B_1_L, //normal break I lost + BOTH_LK_DL_DL_T_B_1_W, //normal break I won + BOTH_LK_DL_DL_T_L_1, //lock if I'm using dual vs. dual and I initiated + BOTH_LK_DL_DL_T_SB_1_L, //super break I lost + BOTH_LK_DL_DL_T_SB_1_W, //super break I won +//DUAL vs. STAFF + //side locks + BOTH_LK_DL_ST_S_B_1_L, //normal break I lost + BOTH_LK_DL_ST_S_B_1_W, //normal break I won + BOTH_LK_DL_ST_S_L_1, //lock if I'm using dual vs. a staff + BOTH_LK_DL_ST_S_SB_1_L, //super break I lost + BOTH_LK_DL_ST_S_SB_1_W, //super break I won + //top locks + BOTH_LK_DL_ST_T_B_1_L, //normal break I lost + BOTH_LK_DL_ST_T_B_1_W, //normal break I won + BOTH_LK_DL_ST_T_L_1, //lock if I'm using dual vs. a staff + BOTH_LK_DL_ST_T_SB_1_L, //super break I lost + BOTH_LK_DL_ST_T_SB_1_W, //super break I won +//DUAL vs. SINGLE + //side locks + BOTH_LK_DL_S_S_B_1_L, //normal break I lost + BOTH_LK_DL_S_S_B_1_W, //normal break I won + BOTH_LK_DL_S_S_L_1, //lock if I'm using dual vs. a single + BOTH_LK_DL_S_S_SB_1_L, //super break I lost + BOTH_LK_DL_S_S_SB_1_W, //super break I won + //top locks + BOTH_LK_DL_S_T_B_1_L, //normal break I lost + BOTH_LK_DL_S_T_B_1_W, //normal break I won + BOTH_LK_DL_S_T_L_1, //lock if I'm using dual vs. a single + BOTH_LK_DL_S_T_SB_1_L, //super break I lost + BOTH_LK_DL_S_T_SB_1_W, //super break I won +//===Saber Staff locks================================================================== +//STAFF vs. DUAL + //side locks + BOTH_LK_ST_DL_S_B_1_L, //normal break I lost + BOTH_LK_ST_DL_S_B_1_W, //normal break I won + BOTH_LK_ST_DL_S_L_1, //lock if I'm using staff vs. dual + BOTH_LK_ST_DL_S_SB_1_L, //super break I lost + BOTH_LK_ST_DL_S_SB_1_W, //super break I won + //top locks + BOTH_LK_ST_DL_T_B_1_L, //normal break I lost + BOTH_LK_ST_DL_T_B_1_W, //normal break I won + BOTH_LK_ST_DL_T_L_1, //lock if I'm using staff vs. dual + BOTH_LK_ST_DL_T_SB_1_L, //super break I lost + BOTH_LK_ST_DL_T_SB_1_W, //super break I won +//STAFF vs. STAFF + //side locks + BOTH_LK_ST_ST_S_B_1_L, //normal break I lost + BOTH_LK_ST_ST_S_B_1_W, //normal break I won + BOTH_LK_ST_ST_S_L_1, //lock if I'm using staff vs. a staff and I initiated + BOTH_LK_ST_ST_S_SB_1_L, //super break I lost + BOTH_LK_ST_ST_S_SB_1_W, //super break I won + //top locks + BOTH_LK_ST_ST_T_B_1_L, //normal break I lost + BOTH_LK_ST_ST_T_B_1_W, //normal break I won + BOTH_LK_ST_ST_T_L_1, //lock if I'm using staff vs. a staff and I initiated + BOTH_LK_ST_ST_T_SB_1_L, //super break I lost + BOTH_LK_ST_ST_T_SB_1_W, //super break I won +//STAFF vs. SINGLE + //side locks + BOTH_LK_ST_S_S_B_1_L, //normal break I lost + BOTH_LK_ST_S_S_B_1_W, //normal break I won + BOTH_LK_ST_S_S_L_1, //lock if I'm using staff vs. a single + BOTH_LK_ST_S_S_SB_1_L, //super break I lost + BOTH_LK_ST_S_S_SB_1_W, //super break I won + //top locks + BOTH_LK_ST_S_T_B_1_L, //normal break I lost + BOTH_LK_ST_S_T_B_1_W, //normal break I won + BOTH_LK_ST_S_T_L_1, //lock if I'm using staff vs. a single + BOTH_LK_ST_S_T_SB_1_L, //super break I lost + BOTH_LK_ST_S_T_SB_1_W, //super break I won +//Special cases for same saber style vs. each other (won't fit in nice 5-anim size lists above) + BOTH_LK_S_S_S_L_2, //lock if I'm using single vs. a single and other intitiated + BOTH_LK_S_S_T_L_2, //lock if I'm using single vs. a single and other initiated + BOTH_LK_DL_DL_S_L_2, //lock if I'm using dual vs. dual and other initiated + BOTH_LK_DL_DL_T_L_2, //lock if I'm using dual vs. dual and other initiated + BOTH_LK_ST_ST_S_L_2, //lock if I'm using staff vs. a staff and other initiated + BOTH_LK_ST_ST_T_L_2, //lock if I'm using staff vs. a staff and other initiated +//===End Saber locks================================================================== + //old locks + BOTH_BF2RETURN, //# + BOTH_BF2BREAK, //# + BOTH_BF2LOCK, //# + BOTH_BF1RETURN, //# + BOTH_BF1BREAK, //# + BOTH_BF1LOCK, //# + BOTH_CWCIRCLE_R2__R_S1, //# + BOTH_CCWCIRCLE_R2__L_S1, //# + BOTH_CWCIRCLE_A2__L__R, //# + BOTH_CCWCIRCLE_A2__R__L, //# + BOTH_CWCIRCLEBREAK, //# + BOTH_CCWCIRCLEBREAK, //# + BOTH_CWCIRCLELOCK, //# + BOTH_CCWCIRCLELOCK, //# + //other saber anims + //* #sep BOTH_ SABER MISC ANIMS + BOTH_SABERFAST_STANCE, + BOTH_SABERSLOW_STANCE, + BOTH_SABERDUAL_STANCE, + BOTH_SABERSTAFF_STANCE, + BOTH_A2_STABBACK1, //# Stab saber backward + BOTH_ATTACK_BACK, //# Swing around backwards and attack + BOTH_JUMPFLIPSLASHDOWN1,//# + BOTH_JUMPFLIPSTABDOWN,//# + BOTH_FORCELEAP2_T__B_,//# + BOTH_LUNGE2_B__T_,//# + BOTH_CROUCHATTACKBACK1,//# + //New specials for JKA: + BOTH_JUMPATTACK6,//# + BOTH_JUMPATTACK7,//# + BOTH_SPINATTACK6,//# + BOTH_SPINATTACK7,//# + BOTH_S1_S6,//# From stand1 to saberdual stance - turning on your dual sabers + BOTH_S6_S1,//# From dualstaff stance to stand1 - turning off your dual sabers + BOTH_S1_S7,//# From stand1 to saberstaff stance - turning on your saberstaff + BOTH_S7_S1,//# From saberstaff stance to stand1 - turning off your saberstaff + BOTH_FORCELONGLEAP_START, + BOTH_FORCELONGLEAP_ATTACK, + BOTH_FORCELONGLEAP_LAND, + BOTH_FORCEWALLRUNFLIP_START, + BOTH_FORCEWALLRUNFLIP_END, + BOTH_FORCEWALLRUNFLIP_ALT, + BOTH_FORCEWALLREBOUND_FORWARD, + BOTH_FORCEWALLREBOUND_LEFT, + BOTH_FORCEWALLREBOUND_BACK, + BOTH_FORCEWALLREBOUND_RIGHT, + BOTH_FORCEWALLHOLD_FORWARD, + BOTH_FORCEWALLHOLD_LEFT, + BOTH_FORCEWALLHOLD_BACK, + BOTH_FORCEWALLHOLD_RIGHT, + BOTH_FORCEWALLRELEASE_FORWARD, + BOTH_FORCEWALLRELEASE_LEFT, + BOTH_FORCEWALLRELEASE_BACK, + BOTH_FORCEWALLRELEASE_RIGHT, + BOTH_A7_KICK_F, + BOTH_A7_KICK_B, + BOTH_A7_KICK_R, + BOTH_A7_KICK_L, + BOTH_A7_KICK_S, + BOTH_A7_KICK_BF, + BOTH_A7_KICK_BF_STOP, + BOTH_A7_KICK_RL, + BOTH_A7_KICK_F_AIR, + BOTH_A7_KICK_B_AIR, + BOTH_A7_KICK_R_AIR, + BOTH_A7_KICK_L_AIR, + BOTH_FLIP_ATTACK7, + BOTH_FLIP_HOLD7, + BOTH_FLIP_LAND, + BOTH_PULL_IMPALE_STAB, + BOTH_PULL_IMPALE_SWING, + BOTH_PULLED_INAIR_B, + BOTH_PULLED_INAIR_F, + BOTH_STABDOWN, + BOTH_STABDOWN_STAFF, + BOTH_STABDOWN_DUAL, + BOTH_A6_SABERPROTECT, + BOTH_A7_SOULCAL, + BOTH_A1_SPECIAL, + BOTH_A2_SPECIAL, + BOTH_A3_SPECIAL, + BOTH_ROLL_STAB, + + //# #sep BOTH_ STANDING + BOTH_STAND1, //# Standing idle, no weapon, hands down + BOTH_STAND1IDLE1, //# Random standing idle + BOTH_STAND2, //# Standing idle with a saber + BOTH_STAND2IDLE1, //# Random standing idle + BOTH_STAND2IDLE2, //# Random standing idle + BOTH_STAND3, //# Standing idle with 2-handed weapon + BOTH_STAND3IDLE1, //# Random standing idle + BOTH_STAND4, //# hands clasp behind back + BOTH_STAND5, //# standing idle, no weapon, hand down, back straight + BOTH_STAND5IDLE1, //# Random standing idle + BOTH_STAND6, //# one handed, gun at side, relaxed stand + BOTH_STAND8, //# both hands on hips (male) + BOTH_STAND1TO2, //# Transition from stand1 to stand2 + BOTH_STAND2TO1, //# Transition from stand2 to stand1 + BOTH_STAND2TO4, //# Transition from stand2 to stand4 + BOTH_STAND4TO2, //# Transition from stand4 to stand2 + BOTH_STAND4TOATTACK2, //# relaxed stand to 1-handed pistol ready + BOTH_STANDUP2, //# Luke standing up from his meditation platform (cin # 37) + BOTH_STAND5TOSIT3, //# transition from stand 5 to sit 3 + BOTH_STAND1TOSTAND5, //# Transition from stand1 to stand5 + BOTH_STAND5TOSTAND1, //# Transition from stand5 to stand1 + BOTH_STAND5TOAIM, //# Transition of Kye aiming his gun at Desann (cin #9) + BOTH_STAND5STARTLEDLOOKLEFT, //# Kyle turning to watch the bridge drop (cin #9) + BOTH_STARTLEDLOOKLEFTTOSTAND5, //# Kyle returning to stand 5 from watching the bridge drop (cin #9) + BOTH_STAND5TOSTAND8, //# Transition from stand5 to stand8 + BOTH_STAND7TOSTAND8, //# Tavion putting hands on back of chair (cin #11) + BOTH_STAND8TOSTAND5, //# Transition from stand8 to stand5 + BOTH_STAND9, //# Kyle's standing idle, no weapon, hands down + BOTH_STAND9IDLE1, //# Kyle's random standing idle + BOTH_STAND5SHIFTWEIGHT, //# Weightshift from stand5 to side and back to stand5 + BOTH_STAND5SHIFTWEIGHTSTART, //# From stand5 to side + BOTH_STAND5SHIFTWEIGHTSTOP, //# From side to stand5 + BOTH_STAND5TURNLEFTSTART, //# Start turning left from stand5 + BOTH_STAND5TURNLEFTSTOP, //# Stop turning left from stand5 + BOTH_STAND5TURNRIGHTSTART, //# Start turning right from stand5 + BOTH_STAND5TURNRIGHTSTOP, //# Stop turning right from stand5 + BOTH_STAND5LOOK180LEFTSTART, //# Start looking over left shoulder (cin #17) + BOTH_STAND5LOOK180LEFTSTOP, //# Stop looking over left shoulder (cin #17) + + BOTH_CONSOLE1START, //# typing at a console + BOTH_CONSOLE1, //# typing at a console + BOTH_CONSOLE1STOP, //# typing at a console + BOTH_CONSOLE2START, //# typing at a console with comm link in hand (cin #5) + BOTH_CONSOLE2, //# typing at a console with comm link in hand (cin #5) + BOTH_CONSOLE2STOP, //# typing at a console with comm link in hand (cin #5) + BOTH_CONSOLE2HOLDCOMSTART, //# lean in to type at console while holding comm link in hand (cin #5) + BOTH_CONSOLE2HOLDCOMSTOP, //# lean away after typing at console while holding comm link in hand (cin #5) + + BOTH_GUARD_LOOKAROUND1, //# Cradling weapon and looking around + BOTH_GUARD_IDLE1, //# Cradling weapon and standing + BOTH_GESTURE1, //# Generic gesture, non-specific + BOTH_GESTURE2, //# Generic gesture, non-specific + BOTH_WALK1TALKCOMM1, //# Talking into coom link while walking + BOTH_TALK1, //# Generic talk anim + BOTH_TALK2, //# Generic talk anim + BOTH_TALKCOMM1START, //# Start talking into a comm link + BOTH_TALKCOMM1, //# Talking into a comm link + BOTH_TALKCOMM1STOP, //# Stop talking into a comm link + BOTH_TALKGESTURE1, //# Generic talk anim + + BOTH_HEADTILTLSTART, //# Head tilt to left + BOTH_HEADTILTLSTOP, //# Head tilt to left + BOTH_HEADTILTRSTART, //# Head tilt to right + BOTH_HEADTILTRSTOP, //# Head tilt to right + BOTH_HEADNOD, //# Head shake YES + BOTH_HEADSHAKE, //# Head shake NO + BOTH_SIT2HEADTILTLSTART, //# Head tilt to left from seated position 2 + BOTH_SIT2HEADTILTLSTOP, //# Head tilt to left from seated position 2 + + BOTH_REACH1START, //# Monmothma reaching for crystal + BOTH_REACH1STOP, //# Monmothma reaching for crystal + + BOTH_COME_ON1, //# Jan gesturing to Kyle (cin #32a) + BOTH_STEADYSELF1, //# Jan trying to keep footing (cin #32a) + BOTH_STEADYSELF1END, //# Return hands to side from STEADSELF1 Kyle (cin#5) + BOTH_SILENCEGESTURE1, //# Luke silencing Kyle with a raised hand (cin #37) + BOTH_REACHFORSABER1, //# Luke holding hand out for Kyle's saber (cin #37) + BOTH_SABERKILLER1, //# Tavion about to strike Jan with saber (cin #9) + BOTH_SABERKILLEE1, //# Jan about to be struck by Tavion with saber (cin #9) + BOTH_HUGGER1, //# Kyle hugging Jan (cin #29) + BOTH_HUGGERSTOP1, //# Kyle stop hugging Jan but don't let her go (cin #29) + BOTH_HUGGEE1, //# Jan being hugged (cin #29) + BOTH_HUGGEESTOP1, //# Jan stop being hugged but don't let go (cin #29) + + BOTH_SABERTHROW1START, //# Desann throwing his light saber (cin #26) + BOTH_SABERTHROW1STOP, //# Desann throwing his light saber (cin #26) + BOTH_SABERTHROW2START, //# Kyle throwing his light saber (cin #32) + BOTH_SABERTHROW2STOP, //# Kyle throwing his light saber (cin #32) + + //# #sep BOTH_ SITTING/CROUCHING + BOTH_SIT1, //# Normal chair sit. + BOTH_SIT2, //# Lotus position. + BOTH_SIT3, //# Sitting in tired position, elbows on knees + + BOTH_SIT2TOSTAND5, //# Transition from sit 2 to stand 5 + BOTH_STAND5TOSIT2, //# Transition from stand 5 to sit 2 + BOTH_SIT2TOSIT4, //# Trans from sit2 to sit4 (cin #12) Luke leaning back from lotus position. + BOTH_SIT3TOSTAND5, //# transition from sit 3 to stand 5 + + BOTH_CROUCH1, //# Transition from standing to crouch + BOTH_CROUCH1IDLE, //# Crouching idle + BOTH_CROUCH1WALK, //# Walking while crouched + BOTH_CROUCH1WALKBACK, //# Walking while crouched + BOTH_UNCROUCH1, //# Transition from crouch to standing + BOTH_CROUCH2TOSTAND1, //# going from crouch2 to stand1 + BOTH_CROUCH3, //# Desann crouching down to Kyle (cin 9) + BOTH_UNCROUCH3, //# Desann uncrouching down to Kyle (cin 9) + BOTH_CROUCH4, //# Slower version of crouch1 for cinematics + BOTH_UNCROUCH4, //# Slower version of uncrouch1 for cinematics + + BOTH_GUNSIT1, //# sitting on an emplaced gun. + + // Swoop Vehicle animations. + //* #sep BOTH_ SWOOP ANIMS + BOTH_VS_MOUNT_L, //# Mount from left + BOTH_VS_DISMOUNT_L, //# Dismount to left + BOTH_VS_MOUNT_R, //# Mount from right (symmetry) + BOTH_VS_DISMOUNT_R, //# DISMOUNT TO RIGHT (SYMMETRY) + + BOTH_VS_MOUNTJUMP_L, //# + BOTH_VS_MOUNTTHROW, //# Land on an occupied vehicle & throw off current pilot + BOTH_VS_MOUNTTHROW_L, //# Land on an occupied vehicle & throw off current pilot + BOTH_VS_MOUNTTHROW_R, //# Land on an occupied vehicle & throw off current pilot + BOTH_VS_MOUNTTHROWEE, //# Current pilot getting thrown off by another guy + + BOTH_VS_LOOKLEFT, //# Turn & Look behind and to the left (no weapon) + BOTH_VS_LOOKRIGHT, //# Turn & Look behind and to the right (no weapon) + + BOTH_VS_TURBO, //# Hit The Turbo Button + + BOTH_VS_REV, //# Player looks back as swoop reverses + + BOTH_VS_AIR, //# Player stands up when swoop is airborn + BOTH_VS_AIR_G, //# "" with Gun + BOTH_VS_AIR_SL, //# "" with Saber Left + BOTH_VS_AIR_SR, //# "" with Saber Right + + BOTH_VS_LAND, //# Player bounces down when swoop lands + BOTH_VS_LAND_G, //# "" with Gun + BOTH_VS_LAND_SL, //# "" with Saber Left + BOTH_VS_LAND_SR, //# "" with Saber Right + + BOTH_VS_IDLE, //# Sit + BOTH_VS_IDLE_G, //# Sit (gun) + BOTH_VS_IDLE_SL, //# Sit (saber left) + BOTH_VS_IDLE_SR, //# Sit (saber right) + + BOTH_VS_LEANL, //# Lean left + BOTH_VS_LEANL_G, //# Lean left (gun) + BOTH_VS_LEANL_SL, //# Lean left (saber left) + BOTH_VS_LEANL_SR, //# Lean left (saber right) + + BOTH_VS_LEANR, //# Lean right + BOTH_VS_LEANR_G, //# Lean right (gun) + BOTH_VS_LEANR_SL, //# Lean right (saber left) + BOTH_VS_LEANR_SR, //# Lean right (saber right) + + BOTH_VS_ATL_S, //# Attack left with saber + BOTH_VS_ATR_S, //# Attack right with saber + BOTH_VS_ATR_TO_L_S, //# Attack toss saber from right to left hand + BOTH_VS_ATL_TO_R_S, //# Attack toss saber from left to right hand + BOTH_VS_ATR_G, //# Attack right with gun (90) + BOTH_VS_ATL_G, //# Attack left with gun (90) + BOTH_VS_ATF_G, //# Attack forward with gun + + BOTH_VS_PAIN1, //# Pain + + // Added 12/04/02 by Aurelio. + //* #sep BOTH_ TAUNTAUN ANIMS + BOTH_VT_MOUNT_L, //# Mount from left + BOTH_VT_MOUNT_R, //# Mount from right + BOTH_VT_MOUNT_B, //# Mount from air, behind + BOTH_VT_DISMOUNT, //# Dismount for tauntaun + BOTH_VT_DISMOUNT_L, //# Dismount to tauntauns left + BOTH_VT_DISMOUNT_R, //# Dismount to tauntauns right (symmetry) + + BOTH_VT_WALK_FWD, //# Walk forward + BOTH_VT_WALK_REV, //# Walk backward + BOTH_VT_WALK_FWD_L, //# walk lean left + BOTH_VT_WALK_FWD_R, //# Walk lean right + BOTH_VT_RUN_FWD, //# Run forward + BOTH_VT_RUN_REV, //# Look backwards while running (not weapon specific) + BOTH_VT_RUN_FWD_L, //# Run lean left + BOTH_VT_RUN_FWD_R, //# Run lean right + + BOTH_VT_SLIDEF, //# Tauntaun slides forward with abrupt stop + BOTH_VT_AIR, //# Tauntaun jump + BOTH_VT_ATB, //# Tauntaun tail swipe + BOTH_VT_PAIN1, //# Pain + BOTH_VT_DEATH1, //# Die + BOTH_VT_STAND, //# Stand still and breath + BOTH_VT_BUCK, //# Tauntaun bucking loop animation + + BOTH_VT_LAND, //# Player bounces down when tauntaun lands + BOTH_VT_TURBO, //# Hit The Turbo Button + BOTH_VT_IDLE_SL, //# Sit (saber left) + BOTH_VT_IDLE_SR, //# Sit (saber right) + + BOTH_VT_IDLE, //# Sit with no weapon selected + BOTH_VT_IDLE1, //# Sit with no weapon selected + BOTH_VT_IDLE_S, //# Sit with saber selected + BOTH_VT_IDLE_G, //# Sit with gun selected + BOTH_VT_IDLE_T, //# Sit with thermal grenade selected + + BOTH_VT_ATL_S, //# Attack left with saber + BOTH_VT_ATR_S, //# Attack right with saber + BOTH_VT_ATR_TO_L_S, //# Attack toss saber from right to left hand + BOTH_VT_ATL_TO_R_S, //# Attack toss saber from left to right hand + BOTH_VT_ATR_G, //# Attack right with gun (90) + BOTH_VT_ATL_G, //# Attack left with gun (90) + BOTH_VT_ATF_G, //# Attack forward with gun + + + // Added 2/26/02 by Aurelio. + //* #sep BOTH_ FIGHTER ANIMS + BOTH_GEARS_OPEN, + BOTH_GEARS_CLOSE, + BOTH_WINGS_OPEN, + BOTH_WINGS_CLOSE, + + BOTH_DEATH14_UNGRIP, //# Desann's end death (cin #35) + BOTH_DEATH14_SITUP, //# Tavion sitting up after having been thrown (cin #23) + BOTH_KNEES1, //# Tavion on her knees + BOTH_KNEES2, //# Tavion on her knees looking down + BOTH_KNEES2TO1, //# Transition of KNEES2 to KNEES1 + + //# #sep BOTH_ MOVING + BOTH_WALK1, //# Normal walk + BOTH_WALK2, //# Normal walk + BOTH_WALK_STAFF, //# Walk with saberstaff turned on + BOTH_WALKBACK_STAFF, //# Walk backwards with saberstaff turned on + BOTH_WALK_DUAL, //# Walk with dual turned on + BOTH_WALKBACK_DUAL, //# Walk backwards with dual turned on + BOTH_WALK5, //# Tavion taunting Kyle (cin 22) + BOTH_WALK6, //# Slow walk for Luke (cin 12) + BOTH_WALK7, //# Fast walk + BOTH_RUN1, //# Full run + BOTH_RUN1START, //# Start into full run1 + BOTH_RUN1STOP, //# Stop from full run1 + BOTH_RUN2, //# Full run + BOTH_RUN1TORUN2, //# Wampa run anim transition + BOTH_RUN2TORUN1, //# Wampa run anim transition + BOTH_RUN4, //# Jawa Run + BOTH_RUN_STAFF, //# Run with saberstaff turned on + BOTH_RUNBACK_STAFF, //# Run backwards with saberstaff turned on + BOTH_RUN_DUAL, //# Run with dual turned on + BOTH_RUNBACK_DUAL, //# Run backwards with dual turned on + BOTH_STRAFE_LEFT1, //# Sidestep left, should loop + BOTH_STRAFE_RIGHT1, //# Sidestep right, should loop + BOTH_RUNSTRAFE_LEFT1, //# Sidestep left, should loop + BOTH_RUNSTRAFE_RIGHT1, //# Sidestep right, should loop + BOTH_TURN_LEFT1, //# Turn left, should loop + BOTH_TURN_RIGHT1, //# Turn right, should loop + BOTH_TURNSTAND1, //# Turn from STAND1 position + BOTH_TURNSTAND2, //# Turn from STAND2 position + BOTH_TURNSTAND3, //# Turn from STAND3 position + BOTH_TURNSTAND4, //# Turn from STAND4 position + BOTH_TURNSTAND5, //# Turn from STAND5 position + BOTH_TURNCROUCH1, //# Turn from CROUCH1 position + + BOTH_WALKBACK1, //# Walk1 backwards + BOTH_WALKBACK2, //# Walk2 backwards + BOTH_RUNBACK1, //# Run1 backwards + BOTH_RUNBACK2, //# Run1 backwards + + //# #sep BOTH_ JUMPING + BOTH_JUMP1, //# Jump - wind-up and leave ground + BOTH_INAIR1, //# In air loop (from jump) + BOTH_LAND1, //# Landing (from in air loop) + BOTH_LAND2, //# Landing Hard (from a great height) + + BOTH_JUMPBACK1, //# Jump backwards - wind-up and leave ground + BOTH_INAIRBACK1, //# In air loop (from jump back) + BOTH_LANDBACK1, //# Landing backwards(from in air loop) + + BOTH_JUMPLEFT1, //# Jump left - wind-up and leave ground + BOTH_INAIRLEFT1, //# In air loop (from jump left) + BOTH_LANDLEFT1, //# Landing left(from in air loop) + + BOTH_JUMPRIGHT1, //# Jump right - wind-up and leave ground + BOTH_INAIRRIGHT1, //# In air loop (from jump right) + BOTH_LANDRIGHT1, //# Landing right(from in air loop) + + BOTH_FORCEJUMP1, //# Jump - wind-up and leave ground + BOTH_FORCEINAIR1, //# In air loop (from jump) + BOTH_FORCELAND1, //# Landing (from in air loop) + + BOTH_FORCEJUMPBACK1, //# Jump backwards - wind-up and leave ground + BOTH_FORCEINAIRBACK1, //# In air loop (from jump back) + BOTH_FORCELANDBACK1, //# Landing backwards(from in air loop) + + BOTH_FORCEJUMPLEFT1, //# Jump left - wind-up and leave ground + BOTH_FORCEINAIRLEFT1, //# In air loop (from jump left) + BOTH_FORCELANDLEFT1, //# Landing left(from in air loop) + + BOTH_FORCEJUMPRIGHT1, //# Jump right - wind-up and leave ground + BOTH_FORCEINAIRRIGHT1, //# In air loop (from jump right) + BOTH_FORCELANDRIGHT1, //# Landing right(from in air loop) + //# #sep BOTH_ ACROBATICS + BOTH_FLIP_F, //# Flip forward + BOTH_FLIP_B, //# Flip backwards + BOTH_FLIP_L, //# Flip left + BOTH_FLIP_R, //# Flip right + + BOTH_ROLL_F, //# Roll forward + BOTH_ROLL_B, //# Roll backward + BOTH_ROLL_L, //# Roll left + BOTH_ROLL_R, //# Roll right + + BOTH_HOP_F, //# quickstep forward + BOTH_HOP_B, //# quickstep backwards + BOTH_HOP_L, //# quickstep left + BOTH_HOP_R, //# quickstep right + + BOTH_DODGE_FL, //# lean-dodge forward left + BOTH_DODGE_FR, //# lean-dodge forward right + BOTH_DODGE_BL, //# lean-dodge backwards left + BOTH_DODGE_BR, //# lean-dodge backwards right + BOTH_DODGE_L, //# lean-dodge left + BOTH_DODGE_R, //# lean-dodge right + BOTH_DODGE_HOLD_FL, //# lean-dodge pose forward left + BOTH_DODGE_HOLD_FR, //# lean-dodge pose forward right + BOTH_DODGE_HOLD_BL, //# lean-dodge pose backwards left + BOTH_DODGE_HOLD_BR, //# lean-dodge pose backwards right + BOTH_DODGE_HOLD_L, //# lean-dodge pose left + BOTH_DODGE_HOLD_R, //# lean-dodge pose right + + //MP taunt anims + BOTH_ENGAGETAUNT, + BOTH_BOW, + BOTH_MEDITATE, + BOTH_MEDITATE_END, + BOTH_SHOWOFF_FAST, + BOTH_SHOWOFF_MEDIUM, + BOTH_SHOWOFF_STRONG, + BOTH_SHOWOFF_DUAL, + BOTH_SHOWOFF_STAFF, + BOTH_VICTORY_FAST, + BOTH_VICTORY_MEDIUM, + BOTH_VICTORY_STRONG, + BOTH_VICTORY_DUAL, + BOTH_VICTORY_STAFF, + //other saber/acro anims + BOTH_ARIAL_LEFT, //# + BOTH_ARIAL_RIGHT, //# + BOTH_CARTWHEEL_LEFT, //# + BOTH_CARTWHEEL_RIGHT, //# + BOTH_FLIP_LEFT, //# + BOTH_FLIP_BACK1, //# + BOTH_FLIP_BACK2, //# + BOTH_FLIP_BACK3, //# + BOTH_BUTTERFLY_LEFT, //# + BOTH_BUTTERFLY_RIGHT, //# + BOTH_WALL_RUN_RIGHT, //# + BOTH_WALL_RUN_RIGHT_FLIP,//# + BOTH_WALL_RUN_RIGHT_STOP,//# + BOTH_WALL_RUN_LEFT, //# + BOTH_WALL_RUN_LEFT_FLIP,//# + BOTH_WALL_RUN_LEFT_STOP,//# + BOTH_WALL_FLIP_RIGHT, //# + BOTH_WALL_FLIP_LEFT, //# + BOTH_KNOCKDOWN1, //# knocked backwards + BOTH_KNOCKDOWN2, //# knocked backwards hard + BOTH_KNOCKDOWN3, //# knocked forwards + BOTH_KNOCKDOWN4, //# knocked backwards from crouch + BOTH_KNOCKDOWN5, //# dupe of 3 - will be removed + BOTH_GETUP1, //# + BOTH_GETUP2, //# + BOTH_GETUP3, //# + BOTH_GETUP4, //# + BOTH_GETUP5, //# + BOTH_GETUP_CROUCH_F1, //# + BOTH_GETUP_CROUCH_B1, //# + BOTH_FORCE_GETUP_F1, //# + BOTH_FORCE_GETUP_F2, //# + BOTH_FORCE_GETUP_B1, //# + BOTH_FORCE_GETUP_B2, //# + BOTH_FORCE_GETUP_B3, //# + BOTH_FORCE_GETUP_B4, //# + BOTH_FORCE_GETUP_B5, //# + BOTH_FORCE_GETUP_B6, //# + BOTH_GETUP_BROLL_B, //# + BOTH_GETUP_BROLL_F, //# + BOTH_GETUP_BROLL_L, //# + BOTH_GETUP_BROLL_R, //# + BOTH_GETUP_FROLL_B, //# + BOTH_GETUP_FROLL_F, //# + BOTH_GETUP_FROLL_L, //# + BOTH_GETUP_FROLL_R, //# + BOTH_WALL_FLIP_BACK1, //# + BOTH_WALL_FLIP_BACK2, //# + BOTH_SPIN1, //# + BOTH_CEILING_CLING, //# clinging to ceiling + BOTH_CEILING_DROP, //# dropping from ceiling cling + + //TESTING + BOTH_FJSS_TR_BL, //# jump spin slash tr to bl + BOTH_FJSS_TL_BR, //# jump spin slash bl to tr + BOTH_RIGHTHANDCHOPPEDOFF,//# + BOTH_DEFLECTSLASH__R__L_FIN,//# + BOTH_BASHED1,//# + BOTH_ARIAL_F1,//# + BOTH_BUTTERFLY_FR1,//# + BOTH_BUTTERFLY_FL1,//# + + //NEW SABER/JEDI/FORCE ANIMS + BOTH_BACK_FLIP_UP, //# back flip up Bonus Animation!!!! + BOTH_LOSE_SABER, //# player losing saber (pulled from hand by force pull 4 - Kyle?) + BOTH_STAFF_TAUNT, //# taunt saberstaff + BOTH_DUAL_TAUNT, //# taunt dual + BOTH_A6_FB, //# dual attack front/back + BOTH_A6_LR, //# dual attack left/right + BOTH_A7_HILT, //# saber knock (alt + stand still) + //Alora + BOTH_ALORA_SPIN, //#jump spin attack death ballet + BOTH_ALORA_FLIP_1, //# gymnast move 1 + BOTH_ALORA_FLIP_2, //# gymnast move 2 + BOTH_ALORA_FLIP_3, //# gymnast move3 + BOTH_ALORA_FLIP_B, //# gymnast move back + BOTH_ALORA_SPIN_THROW, //# dual saber throw + BOTH_ALORA_SPIN_SLASH, //# spin slash special bonus animation!! :) + BOTH_ALORA_TAUNT, //# special taunt + //Rosh (Kothos battle) + BOTH_ROSH_PAIN, //# hurt animation (exhausted) + BOTH_ROSH_HEAL, //# healed/rejuvenated + //Tavion + BOTH_TAVION_SCEPTERGROUND, //# stabbing ground with sith sword shoots electricity everywhere + BOTH_TAVION_SWORDPOWER,//# Tavion doing the He-Man(tm) thing + BOTH_SCEPTER_START, //#Point scepter and attack start + BOTH_SCEPTER_HOLD, //#Point scepter and attack hold + BOTH_SCEPTER_STOP, //#Point scepter and attack stop + //Kyle Boss + BOTH_KYLE_GRAB, //# grab + BOTH_KYLE_MISS, //# miss + BOTH_KYLE_PA_1, //# hold 1 + BOTH_PLAYER_PA_1, //# player getting held 1 + BOTH_KYLE_PA_2, //# hold 2 + BOTH_PLAYER_PA_2, //# player getting held 2 + BOTH_PLAYER_PA_FLY, //# player getting knocked back from punch at end of hold 1 + BOTH_KYLE_PA_3, //# hold 3 + BOTH_PLAYER_PA_3, //# player getting held 3 + BOTH_PLAYER_PA_3_FLY,//# player getting thrown at end of hold 3 + //Rancor + BOTH_BUCK_RIDER, //# Rancor bucks when someone is on him + //WAMPA Grabbing enemy + BOTH_HOLD_START, //# + BOTH_HOLD_MISS, //# + BOTH_HOLD_IDLE, //# + BOTH_HOLD_END, //# + BOTH_HOLD_ATTACK, //# + BOTH_HOLD_SNIFF, //# Sniff the guy you're holding + BOTH_HOLD_DROP, //# just drop 'em + //BEING GRABBED BY WAMPA + BOTH_GRABBED, //# + BOTH_RELEASED, //# + BOTH_HANG_IDLE, //# + BOTH_HANG_ATTACK, //# + BOTH_HANG_PAIN, //# + + //# #sep BOTH_ MISC MOVEMENT + BOTH_HIT1, //# Kyle hit by crate in cin #9 + BOTH_LADDER_UP1, //# Climbing up a ladder with rungs at 16 unit intervals + BOTH_LADDER_DWN1, //# Climbing down a ladder with rungs at 16 unit intervals + BOTH_LADDER_IDLE, //# Just sitting on the ladder + + //# #sep BOTH_ FLYING IDLE + BOTH_FLY_SHIELDED, //# For sentry droid, shields in + + //# #sep BOTH_ SWIMMING + BOTH_SWIM_IDLE1, //# Swimming Idle 1 + BOTH_SWIMFORWARD, //# Swim forward loop + BOTH_SWIMBACKWARD, //# Swim backward loop + + //# #sep BOTH_ LYING + BOTH_SLEEP1, //# laying on back-rknee up-rhand on torso + BOTH_SLEEP6START, //# Kyle leaning back to sleep (cin 20) + BOTH_SLEEP6STOP, //# Kyle waking up and shaking his head (cin 21) + BOTH_SLEEP1GETUP, //# alarmed and getting up out of sleep1 pose to stand + BOTH_SLEEP1GETUP2, //# + + BOTH_CHOKE1START, //# tavion in force grip choke + BOTH_CHOKE1STARTHOLD, //# loop of tavion in force grip choke + BOTH_CHOKE1, //# tavion in force grip choke + + BOTH_CHOKE2, //# tavion recovering from force grip choke + BOTH_CHOKE3, //# left-handed choke (for people still holding a weapon) + + //# #sep BOTH_ HUNTER-SEEKER BOT-SPECIFIC + BOTH_POWERUP1, //# Wakes up + + BOTH_TURNON, //# Protocol Droid wakes up + BOTH_TURNOFF, //# Protocol Droid shuts off + + BOTH_BUTTON1, //# Single button push with right hand + BOTH_BUTTON2, //# Single button push with left finger + BOTH_BUTTON_HOLD, //# Single button hold with left hand + BOTH_BUTTON_RELEASE, //# Single button release with left hand + + //# JEDI-SPECIFIC + //# #sep BOTH_ FORCE ANIMS + BOTH_RESISTPUSH, //# plant yourself to resist force push/pulls. + BOTH_FORCEPUSH, //# Use off-hand to do force power. + BOTH_FORCEPULL, //# Use off-hand to do force power. + BOTH_MINDTRICK1, //# Use off-hand to do mind trick + BOTH_MINDTRICK2, //# Use off-hand to do distraction + BOTH_FORCELIGHTNING, //# Use off-hand to do lightning + BOTH_FORCELIGHTNING_START, //# Use off-hand to do lightning - start + BOTH_FORCELIGHTNING_HOLD, //# Use off-hand to do lightning - hold + BOTH_FORCELIGHTNING_RELEASE,//# Use off-hand to do lightning - release + BOTH_FORCEHEAL_START, //# Healing meditation pose start + BOTH_FORCEHEAL_STOP, //# Healing meditation pose end + BOTH_FORCEHEAL_QUICK, //# Healing meditation gesture + BOTH_SABERPULL, //# Use off-hand to do force power. + BOTH_FORCEGRIP1, //# force-gripping (no anim?) + BOTH_FORCEGRIP3, //# force-gripping (right hand) + BOTH_FORCEGRIP3THROW, //# throwing while force-gripping (right hand) + BOTH_FORCEGRIP_HOLD, //# Use off-hand to do grip - hold + BOTH_FORCEGRIP_RELEASE,//# Use off-hand to do grip - release + BOTH_TOSS1, //# throwing to left after force gripping + BOTH_TOSS2, //# throwing to right after force gripping + //NEW force anims for JKA: + BOTH_FORCE_RAGE, + BOTH_FORCE_2HANDEDLIGHTNING, + BOTH_FORCE_2HANDEDLIGHTNING_START, + BOTH_FORCE_2HANDEDLIGHTNING_HOLD, + BOTH_FORCE_2HANDEDLIGHTNING_RELEASE, + BOTH_FORCE_DRAIN, + BOTH_FORCE_DRAIN_START, + BOTH_FORCE_DRAIN_HOLD, + BOTH_FORCE_DRAIN_RELEASE, + BOTH_FORCE_DRAIN_GRAB_START, + BOTH_FORCE_DRAIN_GRAB_HOLD, + BOTH_FORCE_DRAIN_GRAB_END, + BOTH_FORCE_DRAIN_GRABBED, + BOTH_FORCE_ABSORB, + BOTH_FORCE_ABSORB_START, + BOTH_FORCE_ABSORB_END, + BOTH_FORCE_PROTECT, + BOTH_FORCE_PROTECT_FAST, + + BOTH_WIND, + + BOTH_STAND_TO_KNEEL, + BOTH_KNEEL_TO_STAND, + + BOTH_TUSKENATTACK1, + BOTH_TUSKENATTACK2, + BOTH_TUSKENATTACK3, + BOTH_TUSKENLUNGE1, + BOTH_TUSKENTAUNT1, + + BOTH_COWER1_START, //# cower start + BOTH_COWER1, //# cower loop + BOTH_COWER1_STOP, //# cower stop + BOTH_SONICPAIN_START, + BOTH_SONICPAIN_HOLD, + BOTH_SONICPAIN_END, + + //new anim slots per Jarrod's request + BOTH_STAND10, + BOTH_STAND10_TALK1, + BOTH_STAND10_TALK2, + BOTH_STAND10TOSTAND1, + + BOTH_STAND1_TALK1, + BOTH_STAND1_TALK2, + BOTH_STAND1_TALK3, + + BOTH_SIT4, + BOTH_SIT5, + BOTH_SIT5_TALK1, + BOTH_SIT5_TALK2, + BOTH_SIT5_TALK3, + + BOTH_SIT6, + BOTH_SIT7, + + //================================================= + //ANIMS IN WHICH ONLY THE UPPER OBJECTS ARE IN MD3 + //================================================= + //# #sep TORSO_ WEAPON-RELATED + TORSO_DROPWEAP1, //# Put weapon away + TORSO_DROPWEAP4, //# Put weapon away + TORSO_RAISEWEAP1, //# Draw Weapon + TORSO_RAISEWEAP4, //# Draw Weapon + TORSO_WEAPONREADY1, //# Ready to fire stun baton + TORSO_WEAPONREADY2, //# Ready to fire one-handed blaster pistol + TORSO_WEAPONREADY3, //# Ready to fire blaster rifle + TORSO_WEAPONREADY4, //# Ready to fire sniper rifle + TORSO_WEAPONREADY10, //# Ready to fire thermal det + TORSO_WEAPONIDLE2, //# Holding one-handed blaster + TORSO_WEAPONIDLE3, //# Holding blaster rifle + TORSO_WEAPONIDLE4, //# Holding sniper rifle + TORSO_WEAPONIDLE10, //# Holding thermal det + + //# #sep TORSO_ MISC + TORSO_SURRENDER_START, //# arms up + TORSO_SURRENDER_STOP, //# arms back down + + TORSO_CHOKING1, //# TEMP + + TORSO_HANDSIGNAL1, + TORSO_HANDSIGNAL2, + TORSO_HANDSIGNAL3, + TORSO_HANDSIGNAL4, + TORSO_HANDSIGNAL5, + + + //================================================= + //ANIMS IN WHICH ONLY THE LOWER OBJECTS ARE IN MD3 + //================================================= + //# #sep Legs-only anims + LEGS_TURN1, //# What legs do when you turn your lower body to match your upper body facing + LEGS_TURN2, //# Leg turning from stand2 + LEGS_LEAN_LEFT1, //# Lean left + LEGS_LEAN_RIGHT1, //# Lean Right + LEGS_CHOKING1, //# TEMP + LEGS_LEFTUP1, //# On a slope with left foot 4 higher than right + LEGS_LEFTUP2, //# On a slope with left foot 8 higher than right + LEGS_LEFTUP3, //# On a slope with left foot 12 higher than right + LEGS_LEFTUP4, //# On a slope with left foot 16 higher than right + LEGS_LEFTUP5, //# On a slope with left foot 20 higher than right + LEGS_RIGHTUP1, //# On a slope with RIGHT foot 4 higher than left + LEGS_RIGHTUP2, //# On a slope with RIGHT foot 8 higher than left + LEGS_RIGHTUP3, //# On a slope with RIGHT foot 12 higher than left + LEGS_RIGHTUP4, //# On a slope with RIGHT foot 16 higher than left + LEGS_RIGHTUP5, //# On a slope with RIGHT foot 20 higher than left + LEGS_S1_LUP1, + LEGS_S1_LUP2, + LEGS_S1_LUP3, + LEGS_S1_LUP4, + LEGS_S1_LUP5, + LEGS_S1_RUP1, + LEGS_S1_RUP2, + LEGS_S1_RUP3, + LEGS_S1_RUP4, + LEGS_S1_RUP5, + LEGS_S3_LUP1, + LEGS_S3_LUP2, + LEGS_S3_LUP3, + LEGS_S3_LUP4, + LEGS_S3_LUP5, + LEGS_S3_RUP1, + LEGS_S3_RUP2, + LEGS_S3_RUP3, + LEGS_S3_RUP4, + LEGS_S3_RUP5, + LEGS_S4_LUP1, + LEGS_S4_LUP2, + LEGS_S4_LUP3, + LEGS_S4_LUP4, + LEGS_S4_LUP5, + LEGS_S4_RUP1, + LEGS_S4_RUP2, + LEGS_S4_RUP3, + LEGS_S4_RUP4, + LEGS_S4_RUP5, + LEGS_S5_LUP1, + LEGS_S5_LUP2, + LEGS_S5_LUP3, + LEGS_S5_LUP4, + LEGS_S5_LUP5, + LEGS_S5_RUP1, + LEGS_S5_RUP2, + LEGS_S5_RUP3, + LEGS_S5_RUP4, + LEGS_S5_RUP5, + LEGS_S6_LUP1, + LEGS_S6_LUP2, + LEGS_S6_LUP3, + LEGS_S6_LUP4, + LEGS_S6_LUP5, + LEGS_S6_RUP1, + LEGS_S6_RUP2, + LEGS_S6_RUP3, + LEGS_S6_RUP4, + LEGS_S6_RUP5, + LEGS_S7_LUP1, + LEGS_S7_LUP2, + LEGS_S7_LUP3, + LEGS_S7_LUP4, + LEGS_S7_LUP5, + LEGS_S7_RUP1, + LEGS_S7_RUP2, + LEGS_S7_RUP3, + LEGS_S7_RUP4, + LEGS_S7_RUP5, + + //New anim as per Jarrod's request + LEGS_TURN180, + + //====================================================== + //cinematic anims + //====================================================== + //# #sep BOTH_ CINEMATIC-ONLY + BOTH_CIN_1, //# Level specific cinematic 1 + BOTH_CIN_2, //# Level specific cinematic 2 + BOTH_CIN_3, //# Level specific cinematic 3 + BOTH_CIN_4, //# Level specific cinematic 4 + BOTH_CIN_5, //# Level specific cinematic 5 + BOTH_CIN_6, //# Level specific cinematic 6 + BOTH_CIN_7, //# Level specific cinematic 7 + BOTH_CIN_8, //# Level specific cinematic 8 + BOTH_CIN_9, //# Level specific cinematic 9 + BOTH_CIN_10, //# Level specific cinematic 10 + BOTH_CIN_11, //# Level specific cinematic 11 + BOTH_CIN_12, //# Level specific cinematic 12 + BOTH_CIN_13, //# Level specific cinematic 13 + BOTH_CIN_14, //# Level specific cinematic 14 + BOTH_CIN_15, //# Level specific cinematic 15 + BOTH_CIN_16, //# Level specific cinematic 16 + BOTH_CIN_17, //# Level specific cinematic 17 + BOTH_CIN_18, //# Level specific cinematic 18 + BOTH_CIN_19, //# Level specific cinematic 19 + BOTH_CIN_20, //# Level specific cinematic 20 + BOTH_CIN_21, //# Level specific cinematic 21 + BOTH_CIN_22, //# Level specific cinematic 22 + BOTH_CIN_23, //# Level specific cinematic 23 + BOTH_CIN_24, //# Level specific cinematic 24 + BOTH_CIN_25, //# Level specific cinematic 25 + BOTH_CIN_26, //# Level specific cinematic + BOTH_CIN_27, //# Level specific cinematic + BOTH_CIN_28, //# Level specific cinematic + BOTH_CIN_29, //# Level specific cinematic + BOTH_CIN_30, //# Level specific cinematic + BOTH_CIN_31, //# Level specific cinematic + BOTH_CIN_32, //# Level specific cinematic + BOTH_CIN_33, //# Level specific cinematic + BOTH_CIN_34, //# Level specific cinematic + BOTH_CIN_35, //# Level specific cinematic + BOTH_CIN_36, //# Level specific cinematic + BOTH_CIN_37, //# Level specific cinematic + BOTH_CIN_38, //# Level specific cinematic + BOTH_CIN_39, //# Level specific cinematic + BOTH_CIN_40, //# Level specific cinematic + BOTH_CIN_41, //# Level specific cinematic + BOTH_CIN_42, //# Level specific cinematic + BOTH_CIN_43, //# Level specific cinematic + BOTH_CIN_44, //# Level specific cinematic + BOTH_CIN_45, //# Level specific cinematic + BOTH_CIN_46, //# Level specific cinematic + BOTH_CIN_47, //# Level specific cinematic + BOTH_CIN_48, //# Level specific cinematic + BOTH_CIN_49, //# Level specific cinematic + BOTH_CIN_50, //# Level specific cinematic + + //# #eol + MAX_ANIMATIONS, + MAX_TOTALANIMATIONS, +} animNumber_t; + +#define SABER_ANIM_GROUP_SIZE (BOTH_A2_T__B_ - BOTH_A1_T__B_) + + +#endif// #ifndef __ANIMS_H__ + diff --git a/code/game/b_local.h b/code/game/b_local.h new file mode 100644 index 0000000..2009869 --- /dev/null +++ b/code/game/b_local.h @@ -0,0 +1,329 @@ +//B_local.h +//re-added by MCG +#ifndef __B_LOCAL_H__ +#define __B_LOCAL_H__ + +#include "g_local.h" +#include "b_public.h" +#include "say.h" + +#include "ai.h" + +#define AI_TIMERS 0//turn on to see print-outs of AI/nav timing +// +// Navigation susbsystem +// + +#define NAVF_DUCK 0x00000001 +#define NAVF_JUMP 0x00000002 +#define NAVF_HOLD 0x00000004 +#define NAVF_SLOW 0x00000008 + +#define DEBUG_LEVEL_DETAIL 4 +#define DEBUG_LEVEL_INFO 3 +#define DEBUG_LEVEL_WARNING 2 +#define DEBUG_LEVEL_ERROR 1 +#define DEBUG_LEVEL_NONE 0 + +#define MAX_GOAL_REACHED_DIST_SQUARED 256//16 squared +#define MIN_ANGLE_ERROR 0.01f + +#define MIN_ROCKET_DIST_SQUARED 16384//128*128 +// +// NPC.cpp +// +// ai debug cvars +void SetNPCGlobals( gentity_t *ent ); +void SaveNPCGlobals(void); +void RestoreNPCGlobals(void); +extern vmCvar_t debugNPCAI; // used to print out debug info about the NPC AI +extern vmCvar_t debugNPCFreeze; // set to disable NPC ai and temporarily freeze them in place +extern vmCvar_t debugNPCAimingBeam; +extern vmCvar_t debugBreak; +extern vmCvar_t debugNoRoam; +extern vmCvar_t d_JediAI; +extern vmCvar_t d_saberCombat; + +extern void NPC_Think ( gentity_t *self); + +//NPC_reactions.cpp +extern void NPC_Pain(gentity_t *self, gentity_t *attacker, int damage); +extern void NPC_Touch( gentity_t *self, gentity_t *other, trace_t *trace ); +extern void NPC_Use( gentity_t *self, gentity_t *other, gentity_t *activator ); +extern float NPC_GetPainChance( gentity_t *self, int damage ); + +// +// NPC_misc.cpp +// +extern void Debug_Printf( vmCvar_t *cv, int level, char *fmt, ... ); +extern void Debug_NPCPrintf( gentity_t *printNPC, vmCvar_t *cv, int debugLevel, char *fmt, ... ); + +//MCG - Begin============================================================ +//NPC_ai variables - shared by NPC.cpp andf the following modules +extern gentity_t *NPC; +extern gNPC_t *NPCInfo; +extern gclient_t *client; +extern usercmd_t ucmd; +extern visibility_t enemyVisibility; + +//AI_Default +extern qboolean NPC_CheckInvestigate( int alertEventNum ); +extern qboolean NPC_StandTrackAndShoot (gentity_t *NPC, qboolean canDuck); +extern void NPC_BSIdle( void ); +extern void NPC_BSPointShoot(qboolean shoot); +extern void NPC_BSStandGuard (void); +extern void NPC_BSPatrol (void); +extern void NPC_BSHuntAndKill (void); +extern void NPC_BSStandAndShoot (void); +extern void NPC_BSRunAndShoot (void); +extern void NPC_BSWait( void ); +extern void NPC_BSDefault( void ); + +//NPC_behavior +extern void NPC_BSAdvanceFight (void); +extern void NPC_BSInvestigate (void); +extern void NPC_BSSleep( void ); +extern void NPC_BSFlee (void); +extern void NPC_BSFollowLeader (void); +extern void NPC_BSJump (void); +extern void NPC_BSRemove (void); +extern void NPC_BSSearch (void); +extern void NPC_BSSearchStart (int homeWp, bState_t bState); +extern void NPC_BSWander (void); +extern void NPC_BSFlee( void ); +extern void NPC_StartFlee( gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ); +extern void G_StartFlee( gentity_t *self, gentity_t *enemy, vec3_t dangerPoint, int dangerLevel, int fleeTimeMin, int fleeTimeMax ); + +//NPC_combat +extern int ChooseBestWeapon( void ); +extern void NPC_ChangeWeapon( int newWeapon ); +extern void ShootThink( void ); +extern void WeaponThink( qboolean inCombat ); +extern qboolean HaveWeapon( int weapon ); +extern qboolean CanShoot ( gentity_t *ent, gentity_t *shooter ); +extern void NPC_CheckPossibleEnemy( gentity_t *other, visibility_t vis ); +extern gentity_t *NPC_PickEnemy (gentity_t *closestTo, int enemyTeam, qboolean checkVis, qboolean findPlayersFirst, qboolean findClosest); +extern gentity_t *NPC_CheckEnemy (qboolean findNew, qboolean tooFarOk, qboolean setEnemy ); //setEnemy = qtrue +extern qboolean NPC_CheckAttack (float scale); +extern qboolean NPC_CheckDefend (float scale); +extern qboolean NPC_CheckCanAttack (float attack_scale, qboolean stationary); +extern int NPC_AttackDebounceForWeapon (void); +extern qboolean EntIsGlass (gentity_t *check); +extern qboolean ShotThroughGlass (trace_t *tr, gentity_t *target, vec3_t spot, int mask); +extern qboolean ValidEnemy (gentity_t *ent); +extern void G_ClearEnemy (gentity_t *self); +extern gentity_t *NPC_PickAlly ( qboolean facingEachOther, float range, qboolean ignoreGroup, qboolean movingOnly ); +extern void NPC_LostEnemyDecideChase(void); +extern float NPC_MaxDistSquaredForWeapon( void ); +extern qboolean NPC_EvaluateShot( int hit, qboolean glassOK ); +extern int NPC_ShotEntity( gentity_t *ent, vec3_t impactPos ); //impactedPos = NULL + +//NPC_formation +extern qboolean NPC_SlideMoveToGoal (void); +extern float NPC_FindClosestTeammate (gentity_t *self); +extern void NPC_CalcClosestFormationSpot(gentity_t *self); +extern void G_MaintainFormations (gentity_t *self); +extern void NPC_BSFormation (void); +extern void NPC_CreateFormation (gentity_t *self); +extern void NPC_DropFormation (gentity_t *self); +extern void NPC_ReorderFormation (gentity_t *self); +extern void NPC_InsertIntoFormation (gentity_t *self); +extern void NPC_DeleteFromFormation (gentity_t *self); + +#define COLLISION_RADIUS 32 +#define NUM_POSITIONS 30 + +//NPC spawnflags +#define SFB_SMALLHULL 1 + +#define SFB_RIFLEMAN 2 +#define SFB_OLDBORG 2//Borg +#define SFB_PHASER 4 +#define SFB_GUN 4//Borg +#define SFB_TRICORDER 8 +#define SFB_TASER 8//Borg +#define SFB_DRILL 16//Borg + +#define SFB_CINEMATIC 32 +#define SFB_NOTSOLID 64 +#define SFB_STARTINSOLID 128 + +//NPC_goal +extern void SetGoal( gentity_t *goal, float rating ); +extern void NPC_SetGoal( gentity_t *goal, float rating ); +extern void NPC_ClearGoal( void ); +extern void NPC_ReachedGoal( void ); +extern qboolean ReachedGoal( gentity_t *goal ); +extern gentity_t *UpdateGoal( void ); +extern qboolean NPC_ClearPathToGoal(vec3_t dir, gentity_t *goal); +extern qboolean NPC_MoveToGoal( qboolean tryStraight ); + +//NPC_reactions + +//NPC_senses +#define ALERT_CLEAR_TIME 200 +#define CHECK_PVS 1 +#define CHECK_360 2 +#define CHECK_FOV 4 +#define CHECK_SHOOT 8 +#define CHECK_VISRANGE 16 +extern qboolean CanSee ( gentity_t *ent ); +extern qboolean InFOV ( gentity_t *ent, gentity_t *from, int hFOV, int vFOV ); +extern qboolean InFOV2( vec3_t origin, gentity_t *from, int hFOV, int vFOV ); +extern qboolean InFOV3( vec3_t spot, vec3_t from, vec3_t fromAngles, int hFOV, int vFOV ); +extern visibility_t NPC_CheckVisibility ( gentity_t *ent, int flags ); +extern qboolean InVisrange ( gentity_t *ent ); + +//NPC_sounds +//extern void NPC_AngerSound(void); + +//NPC_spawn +extern void NPC_Spawn ( gentity_t *ent, gentity_t *other, gentity_t *activator ); + +//NPC_stats +extern int NPC_ReactionTime ( void ); +extern qboolean NPC_ParseParms( const char *NPCName, gentity_t *NPC ); +extern void NPC_LoadParms( void ); + +//NPC_utils +extern int teamNumbers[TEAM_NUM_TEAMS]; +extern int teamStrength[TEAM_NUM_TEAMS]; +extern int teamCounter[TEAM_NUM_TEAMS]; +extern void CalcEntitySpot ( const gentity_t *ent, const spot_t spot, vec3_t point ); +extern qboolean NPC_UpdateAngles ( qboolean doPitch, qboolean doYaw ); +extern void NPC_UpdateShootAngles (vec3_t angles, qboolean doPitch, qboolean doYaw ); +extern qboolean NPC_UpdateFiringAngles ( qboolean doPitch, qboolean doYaw ); +extern void SetTeamNumbers (void); +extern qboolean G_ActivateBehavior (gentity_t *self, int bset ); +extern void NPC_AimWiggle( vec3_t enemy_org ); +extern void NPC_SetLookTarget( gentity_t *self, int entNum, int clearTime ); + +//g_nav.cpp +extern int NAV_FindClosestWaypointForEnt (gentity_t *ent, int targWp); +extern qboolean NAV_CheckAhead( gentity_t *self, vec3_t end, trace_t *trace, int clipmask ); + +//NPC_combat +extern float IdealDistance ( gentity_t *self ); + +//g_squad +extern void NPC_SetSayState (gentity_t *self, gentity_t *to, int saying); + +//g_utils +extern qboolean G_CheckInSolid (gentity_t *self, qboolean fix); + +//MCG - End============================================================ + +// NPC.cpp +extern void NPC_SetAnim(gentity_t *ent, int type, int anim, int priority); +extern qboolean NPC_EnemyTooFar(gentity_t *enemy, float dist, qboolean toShoot); + +// ================================================================== + +//rww - special system for sync'ing bone angles between client and server. +void NPC_SetBoneAngles(gentity_t *ent, char *bone, vec3_t angles); + +//rww - and another method of automatically managing surface status for the client and server at once +void NPC_SetSurfaceOnOff(gentity_t *ent, const char *surfaceName, int surfaceFlags); + +extern qboolean NPC_ClearLOS( const vec3_t start, const vec3_t end ); +extern qboolean NPC_ClearLOS5( const vec3_t end ); +extern qboolean NPC_ClearLOS4( gentity_t *ent ) ; +extern qboolean NPC_ClearLOS3( const vec3_t start, gentity_t *ent ); +extern qboolean NPC_ClearLOS2( gentity_t *ent, const vec3_t end ); + +extern qboolean NPC_ClearShot( gentity_t *ent ); + +extern int NPC_FindCombatPoint( const vec3_t position, const vec3_t avoidPosition, vec3_t enemyPosition, const int flags, const float avoidDist, const int ignorePoint ); //ignorePoint = -1 + + +extern qboolean NPC_ReserveCombatPoint( int combatPointID ); +extern qboolean NPC_FreeCombatPoint( int combatPointID, qboolean failed ); //failed = qfalse +extern qboolean NPC_SetCombatPoint( int combatPointID ); + +#define CP_ANY 0 //No flags +#define CP_COVER 0x00000001 //The enemy cannot currently shoot this position +#define CP_CLEAR 0x00000002 //This cover point has a clear shot to the enemy +#define CP_FLEE 0x00000004 //This cover point is marked as a flee point +#define CP_DUCK 0x00000008 //This cover point is marked as a duck point +#define CP_NEAREST 0x00000010 //Find the nearest combat point +#define CP_AVOID_ENEMY 0x00000020 //Avoid our enemy +#define CP_INVESTIGATE 0x00000040 //A special point worth enemy investigation if searching +#define CP_SQUAD 0x00000080 //Squad path +#define CP_AVOID 0x00000100 //Avoid supplied position +#define CP_APPROACH_ENEMY 0x00000200 //Try to get closer to enemy +#define CP_CLOSEST 0x00000400 //Take the closest combatPoint to the enemy that's available +#define CP_FLANK 0x00000800 //Pick a combatPoint behind the enemy +#define CP_HAS_ROUTE 0x00001000 //Pick a combatPoint that we have a route to +#define CP_SNIPE 0x00002000 //Pick a combatPoint that is marked as a sniper spot +#define CP_SAFE 0x00004000 //Pick a combatPoint that is not have dangerTime +#define CP_HORZ_DIST_COLL 0x00008000 //Collect combat points within *horizontal* dist +#define CP_NO_PVS 0x00010000 //A combat point out of the PVS of enemy pos +#define CP_RETREAT 0x00020000 //Try to get farther from enemy + +#define CPF_NONE 0 +#define CPF_DUCK 0x00000001 +#define CPF_FLEE 0x00000002 +#define CPF_INVESTIGATE 0x00000004 +#define CPF_SQUAD 0x00000008 +#define CPF_LEAN 0x00000010 +#define CPF_SNIPE 0x00000020 + +#define MAX_COMBAT_POINT_CHECK 32 + +extern qboolean NPC_ValidEnemy( gentity_t *ent ); +extern qboolean NPC_CheckEnemyExt( qboolean checkAlerts ); //checkAlerts = qfalse +extern qboolean NPC_FindPlayer( void ); +extern qboolean NPC_CheckCanAttackExt( void ); + +extern int NPC_CheckAlertEvents( qboolean checkSight, qboolean checkSound, int ignoreAlert, qboolean mustHaveOwner, int minAlertLevel ); //ignoreAlert = -1, mustHaveOwner = qfalse, minAlertLevel = AEL_MINOR +extern qboolean NPC_CheckForDanger( int alertEvent ); +extern void G_AlertTeam( gentity_t *victim, gentity_t *attacker, float radius, float soundDist ); + +extern int NPC_FindSquadPoint( vec3_t position ); + +extern void ClearPlayerAlertEvents( void ); + +extern qboolean G_BoundsOverlap(const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2); +extern qboolean NAV_HitNavGoal( vec3_t point, vec3_t mins, vec3_t maxs, vec3_t dest, int radius, qboolean flying ); + +extern void NPC_SetMoveGoal( gentity_t *ent, vec3_t point, int radius, qboolean isNavGoal, int combatPoint, gentity_t *targetEnt ); //isNavGoal = qfalse, combatPoint = -1, targetEnt = NULL + +extern qboolean NAV_ClearPathToPoint(gentity_t *self, vec3_t pmins, vec3_t pmaxs, vec3_t point, int clipmask, int okToHitEnt ); +extern void NPC_ApplyWeaponFireDelay(void); + +//NPC_FaceXXX suite +extern qboolean NPC_FacePosition( vec3_t position, qboolean doPitch ); //doPitch = qtrue +extern qboolean NPC_FaceEntity( gentity_t *ent, qboolean doPitch ); //doPitch = qtrue +extern qboolean NPC_FaceEnemy( qboolean doPitch ); //doPitch = qtrue + +//Skill level cvar +extern vmCvar_t g_spskill; + +#define NIF_NONE 0x00000000 +#define NIF_FAILED 0x00000001 //failed to find a way to the goal +#define NIF_MACRO_NAV 0x00000002 //using macro navigation +#define NIF_COLLISION 0x00000004 //resolving collision with an entity +#define NIF_BLOCKED 0x00000008 //blocked from moving + +/* +------------------------- +struct navInfo_s +------------------------- +*/ + +typedef struct navInfo_s +{ + gentity_t *blocker; + vec3_t direction; + vec3_t pathDirection; + float distance; + trace_t trace; + int flags; +} navInfo_t; + +extern int NAV_MoveToGoal( gentity_t *self, navInfo_t *info ); +extern void NAV_GetLastMove( navInfo_t *info ); +extern qboolean NAV_AvoidCollision( gentity_t *self, gentity_t *goal, navInfo_t *info ); + + +#endif diff --git a/code/game/b_public.h b/code/game/b_public.h new file mode 100644 index 0000000..e49e259 --- /dev/null +++ b/code/game/b_public.h @@ -0,0 +1,355 @@ +#ifndef __B_PUBLIC_H__ +#define __B_PUBLIC_H__ + +#include "ai.h" + +#define NPCAI_CHECK_WEAPON 0x00000001 +#define NPCAI_BURST_WEAPON 0x00000002 +#define NPCAI_MOVING 0x00000004 +#define NPCAI_TOUCHED_GOAL 0x00000008 +#define NPCAI_PUSHED 0x00000010 +#define NPCAI_NO_COLL_AVOID 0x00000020 +#define NPCAI_BLOCKED 0x00000040 +#define NPCAI_OFF_PATH 0x00000100 +#define NPCAI_IN_SQUADPOINT 0x00000200 +#define NPCAI_STRAIGHT_TO_DESTPOS 0x00000400 +#define NPCAI_NO_SLOWDOWN 0x00001000 +#define NPCAI_LOST 0x00002000 //Can't nav to his goal +#define NPCAI_SHIELDS 0x00004000 //Has shields, borg can adapt +#define NPCAI_GREET_ALLIES 0x00008000 //Say hi to nearby allies +#define NPCAI_FORM_TELE_NAV 0x00010000 //Tells formation people to use nav info to get to +#define NPCAI_ENROUTE_TO_HOMEWP 0x00020000 //Lets us know to run our lostenemyscript when we get to homeWp +#define NPCAI_MATCHPLAYERWEAPON 0x00040000 //Match the player's weapon except when it changes during cinematics +#define NPCAI_DIE_ON_IMPACT 0x00100000 //Next time you crashland, die! +#define NPCAI_CUSTOM_GRAVITY 0x00200000 //Don't use g_gravity, I fly! + +//Script flags +#define SCF_CROUCHED 0x00000001 //Force ucmd.upmove to be -127 +#define SCF_WALKING 0x00000002 //Force BUTTON_WALKING to be pressed +#define SCF_MORELIGHT 0x00000004 //NPC will have a minlight of 96 +#define SCF_LEAN_RIGHT 0x00000008 //Force rightmove+BUTTON_USE +#define SCF_LEAN_LEFT 0x00000010 //Force leftmove+BUTTON_USE +#define SCF_RUNNING 0x00000020 //Takes off walking button, overrides SCF_WALKING +#define SCF_ALT_FIRE 0x00000040 //Force to use alt-fire when firing +#define SCF_NO_RESPONSE 0x00000080 //NPC will not do generic responses to being used +#define SCF_FFDEATH 0x00000100 //Just tells player_die to run the friendly fire deathscript +#define SCF_NO_COMBAT_TALK 0x00000200 //NPC will not use their generic combat chatter stuff +#define SCF_CHASE_ENEMIES 0x00000400 //NPC chase enemies - FIXME: right now this is synonymous with using combat points... should it be? +#define SCF_LOOK_FOR_ENEMIES 0x00000800 //NPC be on the lookout for enemies +#define SCF_FACE_MOVE_DIR 0x00001000 //NPC face direction it's moving - FIXME: not really implemented right now +#define SCF_IGNORE_ALERTS 0x00002000 //NPC ignore alert events +#define SCF_DONT_FIRE 0x00004000 //NPC won't shoot +#define SCF_DONT_FLEE 0x00008000 //NPC never flees +#define SCF_FORCED_MARCH 0x00010000 //NPC that the player must aim at to make him walk +#define SCF_NO_GROUPS 0x00020000 //NPC cannot alert groups or be part of a group +#define SCF_FIRE_WEAPON 0x00040000 //NPC will fire his (her) weapon +#define SCF_NO_MIND_TRICK 0x00080000 //Not succeptible to mind tricks +#define SCF_USE_CP_NEAREST 0x00100000 //Will use combat point close to it, not next to player or try and flank player +#define SCF_NO_FORCE 0x00200000 //Not succeptible to force powers +#define SCF_NO_FALLTODEATH 0x00400000 //NPC will not scream and tumble and fall to hit death over large drops +#define SCF_NO_ACROBATICS 0x00800000 //Jedi won't jump, roll or cartwheel +#define SCF_USE_SUBTITLES 0x01000000 //Regardless of subtitle setting, this NPC will display subtitles when it speaks lines +#define SCF_NO_ALERT_TALK 0x02000000 //Will not say alert sounds, but still can be woken up by alerts + +//#ifdef __DEBUG + +//Debug flag definitions + +#define AID_IDLE 0x00000000 //Nothing is happening +#define AID_ACQUIRED 0x00000001 //A target has been found +#define AID_LOST 0x00000002 //Alert, but no target is in sight +#define AID_CONFUSED 0x00000004 //Is unable to come up with a course of action +#define AID_LOSTPATH 0x00000008 //Cannot make a valid movement due to lack of connections + +//#endif //__DEBUG + +//extern qboolean showWaypoints; + +typedef enum {VIS_UNKNOWN, VIS_NOT, VIS_PVS, VIS_360, VIS_FOV, VIS_SHOOT} visibility_t; +typedef enum {SPOT_ORIGIN, SPOT_CHEST, SPOT_HEAD, SPOT_HEAD_LEAN, SPOT_WEAPON, SPOT_LEGS, SPOT_GROUND} spot_t; + +typedef enum //# lookMode_e +{ + LM_ENT = 0, + LM_INTEREST +} lookMode_t; + +typedef enum //# jumpState_e +{ + JS_WAITING = 0, + JS_FACING, + JS_CROUCHING, + JS_JUMPING, + JS_LANDING +} jumpState_t; + +typedef struct gNPCstats_e +{//Stats, loaded in, and can be set by scripts + //AI + int aggression; // " + int aim; // " + float earshot; // " + int evasion; // " + int hfov; // horizontal field of view + int intelligence; // " + int move; // " + int reactions; // 1-5, higher is better + float shootDistance; //Maximum range- overrides range set for weapon if nonzero + int vfov; // vertical field of view + float vigilance; // " + float visrange; // " + //Movement + int runSpeed; + int walkSpeed; + float yawSpeed; // 1 - whatever, default is 50 + int health; + int acceleration; +} gNPCstats_t; + +// NOTE!!! If you add any ptr fields into this structure could you please tell me so I can update the load/save code? +// so far the only things I've got to cope with are a bunch of gentity_t*'s, but tell me if any more get added -slc +// + +#define MAX_ENEMY_POS_LAG 2400 +#define ENEMY_POS_LAG_INTERVAL 100 +#define ENEMY_POS_LAG_STEPS (MAX_ENEMY_POS_LAG/ENEMY_POS_LAG_INTERVAL) +typedef struct +{ + //FIXME: Put in playerInfo or something + int timeOfDeath; //FIXME do we really need both of these + gentity_t *touchedByPlayer; + + visibility_t enemyLastVisibility; + + int aimTime; + float desiredYaw; + float desiredPitch; + float lockedDesiredYaw; + float lockedDesiredPitch; + gentity_t *aimingBeam; // debugging aid + + vec3_t enemyLastSeenLocation; + int enemyLastSeenTime; + vec3_t enemyLastHeardLocation; + int enemyLastHeardTime; + int lastAlertID; //unique ID + + int eFlags; + int aiFlags; + + int currentAmmo; // this sucks, need to find a better way + int shotTime; + int burstCount; + int burstMin; + int burstMean; + int burstMax; + int burstSpacing; + int attackHold; + int attackHoldTime; + vec3_t shootAngles; //Angles to where bot is shooting - fixme: make he torso turn to reflect these + + //extra character info + rank_t rank; //for pips + + //Behavior state info + bState_t behaviorState; //determines what actions he should be doing + bState_t defaultBehavior;//State bot will default to if none other set + bState_t tempBehavior;//While valid, overrides other behavior + + qboolean ignorePain; //only play pain scripts when take pain + + int duckDebounceTime;//Keeps them ducked for a certain time + int walkDebounceTime; + int enemyCheckDebounceTime; + int investigateDebounceTime; + int investigateCount; + vec3_t investigateGoal; + int investigateSoundDebounceTime; + int greetingDebounceTime;//when we can greet someone next + gentity_t *eventOwner; + + //bState-specific fields + gentity_t *coverTarg; + jumpState_t jumpState; + float followDist; + + // goal, navigation & pathfinding + gentity_t *tempGoal; // used for locational goals (player's last seen/heard position) + gentity_t *goalEntity; + gentity_t *lastGoalEntity; + gentity_t *eventualGoal; + gentity_t *captureGoal; //Where we should try to capture + gentity_t *defendEnt; //Who we're trying to protect + gentity_t *greetEnt; //Who we're greeting + int goalTime; //FIXME: This is never actually used + qboolean straightToGoal; //move straight at navgoals + float distToGoal; + int navTime; + int blockingEntNum; + int blockedSpeechDebounceTime; + int lastSideStepSide; + int sideStepHoldTime; + int homeWp; + AIGroupInfo_t *group; + + vec3_t lastPathAngles; //So we know which way to face generally when we stop + + //stats + gNPCstats_t stats; + int aimErrorDebounceTime; + float lastAimErrorYaw; + float lastAimErrorPitch; + vec3_t aimOfs; + int currentAim; + int currentAggression; + + //scriptflags + int scriptFlags;//in b_local.h + + //moveInfo + int desiredSpeed; + int currentSpeed; + char last_forwardmove; + char last_rightmove; + vec3_t lastClearOrigin; + int consecutiveBlockedMoves; + int blockedDebounceTime; + int shoveCount; + vec3_t blockedDest; + + // + int combatPoint;//NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + int lastFailedCombatPoint;//NPCs in bState BS_COMBAT_POINT will find their closest empty combat_point + int movementSpeech; //what to say when you first successfully move + float movementSpeechChance;//how likely you are to say it + + //Testing physics at 20fps + int nextBStateThink; + usercmd_t last_ucmd; + + // + //JWEIER ADDITIONS START + + qboolean combatMove; + int goalRadius; + + //FIXME: These may be redundant + + /* + int weaponTime; //Time until refire is valid + int jumpTime; + */ + int pauseTime; //Time to stand still + int standTime; + + int localState; //Tracking information local to entity + int squadState; //Tracking information for team level interaction + + //JWEIER ADDITIONS END + // + + int confusionTime; //Doesn't respond to alerts or pick up enemies (unless shot) until this time is up + int charmedTime; //charmed to enemy team + int controlledTime; //controlled by player + int surrenderTime; //Hands up + + //Lagging enemy position - FIXME: seems awful wasteful... + vec3_t enemyLaggedPos[ENEMY_POS_LAG_STEPS]; + + gentity_t *watchTarget; //for BS_CINEMATIC, keeps facing this ent + + int ffireCount; //sigh... you'd think I'd be able to find a way to do this without having to use 3 int fields, but... + int ffireDebounce; + int ffireFadeDebounce; +} gNPC_t; + +void G_SquadPathsInit(void); +void NPC_InitGame( void ); +void G_LoadBoltOns( void ); +void Svcmd_NPC_f( void ); +void NAV_DebugShowWaypoints (void); +void NAV_DebugShowBoxes (void); +void NAV_DebugShowSquadPaths (void); +/* +void Bot_InitGame( void ); +void Bot_InitPreSpawn( void ); +void Bot_InitPostSpawn( void ); +void Bot_Shutdown( void ); +void Bot_Think( gentity_t *ent, int msec ); +void Bot_Connect( gentity_t *bot, char *botName ); +void Bot_Begin( gentity_t *bot ); +void Bot_Disconnect( gentity_t *bot ); +void Svcmd_Bot_f( void ); +void Nav_ItemSpawn( gentity_t *ent, int remaining ); +*/ + +// +// This section should be moved to QFILES.H +// +/* +#define NAVFILE_ID (('I')+('N'<<8)+('A'<<16)+('V'<<24)) +#define NAVFILE_VERSION 6 + +typedef struct { + unsigned id; + unsigned version; + unsigned checksum; + unsigned surfaceCount; + unsigned edgeCount; +} navheader_t; + + +#define MAX_SURFACES 4096 + +#define NSF_PUSH 0x00000001 +#define NSF_WATERLEVEL1 0x00000002 +#define NSF_WATERLEVEL2 0x00000004 +#define NSF_WATER_NOAIR 0x00000008 +#define NSF_DUCK 0x00000010 +#define NSF_PAIN 0x00000020 +#define NSF_TELEPORTER 0x00000040 +#define NSF_PLATHIGH 0x00000080 +#define NSF_PLATLOW 0x00000100 +#define NSF_DOOR_FLOOR 0x00000200 +#define NSF_DOOR_SHOOT 0x00000400 +#define NSF_DOOR_BUTTON 0x00000800 +#define NSF_BUTTON 0x00001000 + +typedef struct { + vec3_t origin; + vec2_t absmin; + vec2_t absmax; + int parm; + unsigned flags; + unsigned edgeCount; + unsigned edgeIndex; +} nsurface_t; + + +#define NEF_DUCK 0x00000001 +#define NEF_JUMP 0x00000002 +#define NEF_HOLD 0x00000004 +#define NEF_WALK 0x00000008 +#define NEF_RUN 0x00000010 +#define NEF_NOAIRMOVE 0x00000020 +#define NEF_LEFTGROUND 0x00000040 +#define NEF_PLAT 0x00000080 +#define NEF_FALL1 0x00000100 +#define NEF_FALL2 0x00000200 +#define NEF_DOOR_SHOOT 0x00000400 +#define NEF_DOOR_BUTTON 0x00000800 +#define NEF_BUTTON 0x00001000 + +typedef struct { + vec3_t origin; + vec2_t absmin; // region within this surface that is the portal to the other surface + vec2_t absmax; + int surfaceNum; + unsigned flags; // jump, prerequisite button, will take falling damage, etc... + float cost; + int dirIndex; + vec3_t endSpot; + int parm; +} nedge_t; +*/ +#endif diff --git a/code/game/be_aas.h b/code/game/be_aas.h new file mode 100644 index 0000000..4132daf --- /dev/null +++ b/code/game/be_aas.h @@ -0,0 +1,205 @@ +// 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 $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:43:59 $ + * + *****************************************************************************/ + +#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; // current legs anim + int torsoAnim; // current torso anim +} 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/code/game/be_ai_char.h b/code/game/be_ai_char.h new file mode 100644 index 0000000..b86c67c --- /dev/null +++ b/code/game/be_ai_char.h @@ -0,0 +1,32 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_char.h + * + * desc: bot characters + * + * $Archive: /source/code/botlib/be_ai_char.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:43:59 $ + * + *****************************************************************************/ + +//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/code/game/be_ai_chat.h b/code/game/be_ai_chat.h new file mode 100644 index 0000000..4d16e7b --- /dev/null +++ b/code/game/be_ai_chat.h @@ -0,0 +1,97 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: be_ai_chat.h + * + * desc: char AI + * + * $Archive: /source/code/botlib/be_ai_chat.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:43:59 $ + * + *****************************************************************************/ + +#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/code/game/be_ai_gen.h b/code/game/be_ai_gen.h new file mode 100644 index 0000000..75d51e2 --- /dev/null +++ b/code/game/be_ai_gen.h @@ -0,0 +1,17 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_gen.h + * + * desc: genetic selection + * + * $Archive: /source/code/botlib/be_ai_gen.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +int GeneticParentsAndChildSelection(int numranks, float *ranks, int *parent1, int *parent2, int *child); diff --git a/code/game/be_ai_goal.h b/code/game/be_ai_goal.h new file mode 100644 index 0000000..000ef91 --- /dev/null +++ b/code/game/be_ai_goal.h @@ -0,0 +1,102 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: be_ai_goal.h + * + * desc: goal AI + * + * $Archive: /source/code/botlib/be_ai_goal.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +#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/code/game/be_ai_move.h b/code/game/be_ai_move.h new file mode 100644 index 0000000..03ed5fe --- /dev/null +++ b/code/game/be_ai_move.h @@ -0,0 +1,126 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_move.h + * + * desc: movement AI + * + * $Archive: /source/code/botlib/be_ai_move.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +//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/code/game/be_ai_weap.h b/code/game/be_ai_weap.h new file mode 100644 index 0000000..80bfdf9 --- /dev/null +++ b/code/game/be_ai_weap.h @@ -0,0 +1,88 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ai_weap.h + * + * desc: weapon AI + * + * $Archive: /source/code/botlib/be_ai_weap.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +//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/code/game/be_ea.h b/code/game/be_ea.h new file mode 100644 index 0000000..38180ff --- /dev/null +++ b/code/game/be_ea.h @@ -0,0 +1,52 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +/***************************************************************************** + * name: be_ea.h + * + * desc: elementary actions + * + * $Archive: /source/code/botlib/be_ea.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 10/05/99 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +//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_Alt_Attack(int client); +void EA_ForcePower(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/code/game/bg_g2_utils.c b/code/game/bg_g2_utils.c new file mode 100644 index 0000000..d72aa76 --- /dev/null +++ b/code/game/bg_g2_utils.c @@ -0,0 +1,124 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_g2_utils.c -- both games misc functions, all completely stateless + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_strap.h" + +#ifdef QAGAME +#include "g_local.h" +#endif + +#ifdef UI_EXPORTS +#include "../ui/ui_local.h" +#endif + +#ifndef UI_EXPORTS +#ifndef QAGAME +#include "../cgame/cg_local.h" +#endif +#endif + +#include "../namespace_begin.h" + +void BG_AttachToRancor( void *ghoul2, + float rancYaw, + vec3_t rancOrigin, + int time, + qhandle_t *modelList, + vec3_t modelScale, + qboolean inMouth, + vec3_t out_origin, + vec3_t out_angles, + vec3_t out_axis[3] ) +{ + mdxaBone_t boltMatrix; + int boltIndex; + vec3_t rancAngles; + vec3_t temp_angles; + // Getting the bolt here + if ( inMouth ) + {//in mouth + boltIndex = trap_G2API_AddBolt(ghoul2, 0, "jaw_bone"); + } + else + {//in right hand + boltIndex = trap_G2API_AddBolt(ghoul2, 0, "*r_hand"); + } + VectorSet( rancAngles, 0, rancYaw, 0 ); + trap_G2API_GetBoltMatrix( ghoul2, 0, boltIndex, + &boltMatrix, rancAngles, rancOrigin, time, + modelList, modelScale ); + // Storing ent position, bolt position, and bolt axis + if ( out_origin ) + { + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, out_origin ); + } + if ( out_axis ) + { + if ( inMouth ) + {//in mouth + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Z, out_axis[0] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, out_axis[1] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, out_axis[2] ); + } + else + {//in hand + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, out_axis[0] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_X, out_axis[1] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Z, out_axis[2] ); + } + //FIXME: this is messing up our axis and turning us inside-out? + if ( out_angles ) + { + vectoangles( out_axis[0], out_angles ); + vectoangles( out_axis[2], temp_angles ); + out_angles[ROLL] = -temp_angles[PITCH]; + } + } + else if ( out_angles ) + { + vec3_t temp_axis[3]; + if ( inMouth ) + {//in mouth + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Z, temp_axis[0] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, temp_axis[2] ); + } + else + {//in hand + BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, temp_axis[0] ); + BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_Z, temp_axis[2] ); + } + //FIXME: this is messing up our axis and turning us inside-out? + vectoangles( temp_axis[0], out_angles ); + vectoangles( temp_axis[2], temp_angles ); + out_angles[ROLL] = -temp_angles[PITCH]; + } +} + +#define MAX_VARIANTS 8 +qboolean BG_GetRootSurfNameWithVariant( void *ghoul2, const char *rootSurfName, char *returnSurfName, int returnSize ) +{ + if ( !ghoul2 || !trap_G2API_GetSurfaceRenderStatus( ghoul2, 0, rootSurfName ) ) + {//see if the basic name without variants is on + Q_strncpyz( returnSurfName, rootSurfName, returnSize ); + return qtrue; + } + else + {//check variants + int i; + for ( i = 0; i < MAX_VARIANTS; i++ ) + { + Com_sprintf( returnSurfName, returnSize, "%s%c", rootSurfName, 'a'+i ); + if ( !trap_G2API_GetSurfaceRenderStatus( ghoul2, 0, returnSurfName ) ) + { + return qtrue; + } + } + } + Q_strncpyz( returnSurfName, rootSurfName, returnSize ); + return qfalse; +} + +#include "../namespace_end.h" diff --git a/code/game/bg_lib.c b/code/game/bg_lib.c new file mode 100644 index 0000000..4f805d4 --- /dev/null +++ b/code/game/bg_lib.c @@ -0,0 +1,1318 @@ +// 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[] = + "$Id: bg_lib.c,v 1.4 2003/03/15 23:44:00 osman Exp $"; +#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( char* a, char* b, int n, int 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(char* a, char* b, char* 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( void* a, size_t n, size_t 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/code/game/bg_lib.h b/code/game/bg_lib.h new file mode 100644 index 0000000..7e1d089 --- /dev/null +++ b/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/code/game/bg_local.h b/code/game/bg_local.h new file mode 100644 index 0000000..38f6690 --- /dev/null +++ b/code/game/bg_local.h @@ -0,0 +1,109 @@ +// 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 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; + +//#include "../namespace_begin.h" + +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; + +extern int forcePowerNeeded[NUM_FORCE_POWER_LEVELS][NUM_FORCE_POWERS]; + +// Had to add these here because there was no file access within the BG right now. +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 ); +//#include "../namespace_end.h" + +//PM anim utility functions: +//#include "../namespace_begin.h" +qboolean PM_SaberInParry( int move ); +qboolean PM_SaberInKnockaway( int move ); +qboolean PM_SaberInReflect( int move ); +qboolean PM_SaberInStart( int move ); +qboolean PM_InSaberAnim( int anim ); +qboolean PM_InKnockDown( playerState_t *ps ); +qboolean PM_PainAnim( int anim ); +qboolean PM_JumpingAnim( int anim ); +qboolean PM_LandingAnim( int anim ); +qboolean PM_SpinningAnim( int anim ); +qboolean PM_InOnGroundAnim ( int anim ); +qboolean PM_InRollComplete( playerState_t *ps, int anim ); + +int PM_AnimLength( int index, animNumber_t anim ); + +int PM_GetSaberStance(void); +float PM_GroundDistance(void); +qboolean PM_SomeoneInFront(trace_t *tr); +saberMoveName_t PM_SaberFlipOverAttackMove(void); +saberMoveName_t PM_SaberJumpAttackMove( void ); + +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 ); + +void PM_StartTorsoAnim( int anim ); +void PM_ContinueLegsAnim( int anim ); +void PM_ForceLegsAnim( int anim ); + +void PM_BeginWeaponChange( int weapon ); +void PM_FinishWeaponChange( void ); + +void PM_SetAnim(int setAnimParts,int anim,int setAnimFlags, int blendTime); + +void PM_WeaponLightsaber(void); +void PM_SetSaberMove(short newMove); + +void PM_SetForceJumpZStart(float value); + +void BG_CycleInven(playerState_t *ps, int direction); + +//#include "../namespace_end.h" diff --git a/code/game/bg_misc.c b/code/game/bg_misc.c new file mode 100644 index 0000000..5093042 --- /dev/null +++ b/code/game/bg_misc.c @@ -0,0 +1,3399 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +// bg_misc.c -- both games misc functions, all completely stateless + +#include "../qcommon/q_shared.h" +#include "../game/bg_public.h" +#include "../game/bg_strap.h" + +#ifdef QAGAME +#include "g_local.h" +#endif + +#ifdef UI_EXPORTS +#include "../ui/ui_local.h" +#endif + +#ifndef UI_EXPORTS +#ifndef QAGAME +#include "../cgame/cg_local.h" +#endif +#endif + +#ifdef _XBOX +extern void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int iAlign); +extern void Z_Free(void *pvAddress); +#endif + +#ifdef QAGAME +extern void Q3_SetParm (int entID, int parmNum, const char *parmValue); +#endif + +//#include "../namespace_begin.h" + +const char *bgToggleableSurfaces[BG_NUM_TOGGLEABLE_SURFACES] = +{ + "l_arm_key", //0 + "torso_canister1", + "torso_canister2", + "torso_canister3", + "torso_tube1", + "torso_tube2", //5 + "torso_tube3", + "torso_tube4", + "torso_tube5", + "torso_tube6", + "r_arm", //10 + "l_arm", + "torso_shield", + "torso_galaktorso", + "torso_collar", +// "torso_eyes_mouth", //15 +// "torso_galakhead", +// "torso_galakface", +// "torso_antenna_base_cap", +// "torso_antenna", +// "l_arm_augment", //20 +// "l_arm_middle", +// "l_arm_wrist", +// "r_arm_middle", //yeah.. galak's surf stuff is no longer auto, sorry! need the space for vehicle surfs. + "r_wing1", //15 + "r_wing2", + "l_wing1", + "l_wing2", + "r_gear", + "l_gear", //20 + "nose", + "blah4", + "blah5", + "l_hand", + "r_hand", //25 + "helmet", + "head", + "head_concussion_charger", + "head_light_blaster_cann", //29 + NULL +}; + +const int bgToggleableSurfaceDebris[BG_NUM_TOGGLEABLE_SURFACES] = +{ + 0, //0 + 0, + 0, + 0, + 0, + 0, //5 + 0, + 0, + 0, + 0, + 0, //10 + 0, + 0, + 0, + 0, //>= 2 means it should create a flame trail when destroyed (for vehicles) + 3, //15 + 5, //rwing2 + 4, + 6, //lwing2 + 0, //rgear + 0, //lgear //20 + 7, //nose + 0, //blah + 0, //blah + 0, + 0, //25 + 0, + 0, + 0, + 0, //29 + -1 +}; + +const char *bg_customSiegeSoundNames[MAX_CUSTOM_SIEGE_SOUNDS] = +{ + "*att_attack", + "*att_primary", + "*att_second", + "*def_guns", + "*def_position", + "*def_primary", + "*def_second", + "*reply_coming", + "*reply_go", + "*reply_no", + "*reply_stay", + "*reply_yes", + "*req_assist", + "*req_demo", + "*req_hvy", + "*req_medic", + "*req_sup", + "*req_tech", + "*spot_air", + "*spot_defenses", + "*spot_emplaced", + "*spot_sniper", + "*spot_troops", + "*tac_cover", + "*tac_fallback", + "*tac_follow", + "*tac_hold", + "*tac_split", + "*tac_together", + NULL +}; + +//rww - not putting @ in front of these because +//we don't need them in a cgame StringEd lookup. +//Let me know if this causes problems, pat. +char *forceMasteryLevels[NUM_FORCE_MASTERY_LEVELS] = +{ + "MASTERY0", //"Uninitiated", // FORCE_MASTERY_UNINITIATED, + "MASTERY1", //"Initiate", // FORCE_MASTERY_INITIATE, + "MASTERY2", //"Padawan", // FORCE_MASTERY_PADAWAN, + "MASTERY3", //"Jedi", // FORCE_MASTERY_JEDI, + "MASTERY4", //"Jedi Adept", // FORCE_MASTERY_JEDI_GUARDIAN, + "MASTERY5", //"Jedi Guardian", // FORCE_MASTERY_JEDI_ADEPT, + "MASTERY6", //"Jedi Knight", // FORCE_MASTERY_JEDI_KNIGHT, + "MASTERY7", //"Jedi Master" // FORCE_MASTERY_JEDI_MASTER, +}; + +int forceMasteryPoints[NUM_FORCE_MASTERY_LEVELS] = +{ + 0, // FORCE_MASTERY_UNINITIATED, + 5, // FORCE_MASTERY_INITIATE, + 10, // FORCE_MASTERY_PADAWAN, + 20, // FORCE_MASTERY_JEDI, + 30, // FORCE_MASTERY_JEDI_GUARDIAN, + 50, // FORCE_MASTERY_JEDI_ADEPT, + 75, // FORCE_MASTERY_JEDI_KNIGHT, + 100 // FORCE_MASTERY_JEDI_MASTER, +}; + +int bgForcePowerCost[NUM_FORCE_POWERS][NUM_FORCE_POWER_LEVELS] = //0 == neutral +{ + { 0, 2, 4, 6 }, // Heal // FP_HEAL + { 0, 0, 2, 6 }, // Jump //FP_LEVITATION,//hold/duration + { 0, 2, 4, 6 }, // Speed //FP_SPEED,//duration + { 0, 1, 3, 6 }, // Push //FP_PUSH,//hold/duration + { 0, 1, 3, 6 }, // Pull //FP_PULL,//hold/duration + { 0, 4, 6, 8 }, // Mind Trick //FP_TELEPATHY,//instant + { 0, 1, 3, 6 }, // Grip //FP_GRIP,//hold/duration + { 0, 2, 5, 8 }, // Lightning //FP_LIGHTNING,//hold/duration + { 0, 4, 6, 8 }, // Dark Rage //FP_RAGE,//duration + { 0, 2, 5, 8 }, // Protection //FP_PROTECT,//duration + { 0, 1, 3, 6 }, // Absorb //FP_ABSORB,//duration + { 0, 1, 3, 6 }, // Team Heal //FP_TEAM_HEAL,//instant + { 0, 1, 3, 6 }, // Team Force //FP_TEAM_FORCE,//instant + { 0, 2, 4, 6 }, // Drain //FP_DRAIN,//hold/duration + { 0, 2, 5, 8 }, // Sight //FP_SEE,//duration + { 0, 1, 5, 8 }, // Saber Attack //FP_SABER_OFFENSE, + { 0, 1, 5, 8 }, // Saber Defend //FP_SABER_DEFENSE, + { 0, 4, 6, 8 } // Saber Throw //FP_SABERTHROW, + //NUM_FORCE_POWERS +}; + +int forcePowerSorted[NUM_FORCE_POWERS] = +{ //rww - always use this order when drawing force powers for any reason + FP_TELEPATHY, + FP_HEAL, + FP_ABSORB, + FP_PROTECT, + FP_TEAM_HEAL, + FP_LEVITATION, + FP_SPEED, + FP_PUSH, + FP_PULL, + FP_SEE, + FP_LIGHTNING, + FP_DRAIN, + FP_RAGE, + FP_GRIP, + FP_TEAM_FORCE, + FP_SABER_OFFENSE, + FP_SABER_DEFENSE, + FP_SABERTHROW +}; + +int forcePowerDarkLight[NUM_FORCE_POWERS] = //0 == neutral +{ //nothing should be usable at rank 0.. + FORCE_LIGHTSIDE,//FP_HEAL,//instant + 0,//FP_LEVITATION,//hold/duration + 0,//FP_SPEED,//duration + 0,//FP_PUSH,//hold/duration + 0,//FP_PULL,//hold/duration + FORCE_LIGHTSIDE,//FP_TELEPATHY,//instant + FORCE_DARKSIDE,//FP_GRIP,//hold/duration + FORCE_DARKSIDE,//FP_LIGHTNING,//hold/duration + FORCE_DARKSIDE,//FP_RAGE,//duration + FORCE_LIGHTSIDE,//FP_PROTECT,//duration + FORCE_LIGHTSIDE,//FP_ABSORB,//duration + FORCE_LIGHTSIDE,//FP_TEAM_HEAL,//instant + FORCE_DARKSIDE,//FP_TEAM_FORCE,//instant + FORCE_DARKSIDE,//FP_DRAIN,//hold/duration + 0,//FP_SEE,//duration + 0,//FP_SABER_OFFENSE, + 0,//FP_SABER_DEFENSE, + 0//FP_SABERTHROW, + //NUM_FORCE_POWERS +}; + +int WeaponReadyAnim[WP_NUM_WEAPONS] = +{ + TORSO_DROPWEAP1,//WP_NONE, + + TORSO_WEAPONREADY3,//WP_STUN_BATON, + TORSO_WEAPONREADY3,//WP_MELEE, + BOTH_STAND2,//WP_SABER, + TORSO_WEAPONREADY2,//WP_BRYAR_PISTOL, + TORSO_WEAPONREADY3,//WP_BLASTER, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY4,//WP_DISRUPTOR, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY5,//WP_BOWCASTER, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY6,//WP_REPEATER, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY7,//WP_DEMP2, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY8,//WP_FLECHETTE, + TORSO_WEAPONREADY3,//TORSO_WEAPONREADY9,//WP_ROCKET_LAUNCHER, + TORSO_WEAPONREADY10,//WP_THERMAL, + TORSO_WEAPONREADY10,//TORSO_WEAPONREADY11,//WP_TRIP_MINE, + TORSO_WEAPONREADY10,//TORSO_WEAPONREADY12,//WP_DET_PACK, + TORSO_WEAPONREADY3,//WP_CONCUSSION + TORSO_WEAPONREADY2,//WP_BRYAR_OLD, + + //NOT VALID (e.g. should never really be used): + BOTH_STAND1,//WP_EMPLACED_GUN, + TORSO_WEAPONREADY1//WP_TURRET, +}; + +int WeaponReadyLegsAnim[WP_NUM_WEAPONS] = +{ + BOTH_STAND1,//WP_NONE, + + BOTH_STAND1,//WP_STUN_BATON, + BOTH_STAND1,//WP_MELEE, + BOTH_STAND2,//WP_SABER, + BOTH_STAND1,//WP_BRYAR_PISTOL, + BOTH_STAND1,//WP_BLASTER, + BOTH_STAND1,//TORSO_WEAPONREADY4,//WP_DISRUPTOR, + BOTH_STAND1,//TORSO_WEAPONREADY5,//WP_BOWCASTER, + BOTH_STAND1,//TORSO_WEAPONREADY6,//WP_REPEATER, + BOTH_STAND1,//TORSO_WEAPONREADY7,//WP_DEMP2, + BOTH_STAND1,//TORSO_WEAPONREADY8,//WP_FLECHETTE, + BOTH_STAND1,//TORSO_WEAPONREADY9,//WP_ROCKET_LAUNCHER, + BOTH_STAND1,//WP_THERMAL, + BOTH_STAND1,//TORSO_WEAPONREADY11,//WP_TRIP_MINE, + BOTH_STAND1,//TORSO_WEAPONREADY12,//WP_DET_PACK, + BOTH_STAND1,//WP_CONCUSSION + BOTH_STAND1,//WP_BRYAR_OLD, + + //NOT VALID (e.g. should never really be used): + BOTH_STAND1,//WP_EMPLACED_GUN, + BOTH_STAND1//WP_TURRET, +}; + +int WeaponAttackAnim[WP_NUM_WEAPONS] = +{ + BOTH_ATTACK1,//WP_NONE, //(shouldn't happen) + + BOTH_ATTACK3,//WP_STUN_BATON, + BOTH_ATTACK3,//WP_MELEE, + BOTH_STAND2,//WP_SABER, //(has its own handling) + BOTH_ATTACK2,//WP_BRYAR_PISTOL, + BOTH_ATTACK3,//WP_BLASTER, + BOTH_ATTACK3,//BOTH_ATTACK4,//WP_DISRUPTOR, + BOTH_ATTACK3,//BOTH_ATTACK5,//WP_BOWCASTER, + BOTH_ATTACK3,//BOTH_ATTACK6,//WP_REPEATER, + BOTH_ATTACK3,//BOTH_ATTACK7,//WP_DEMP2, + BOTH_ATTACK3,//BOTH_ATTACK8,//WP_FLECHETTE, + BOTH_ATTACK3,//BOTH_ATTACK9,//WP_ROCKET_LAUNCHER, + BOTH_THERMAL_THROW,//WP_THERMAL, + BOTH_ATTACK3,//BOTH_ATTACK11,//WP_TRIP_MINE, + BOTH_ATTACK3,//BOTH_ATTACK12,//WP_DET_PACK, + BOTH_ATTACK2,//WP_BRYAR_OLD, + + //NOT VALID (e.g. should never really be used): + BOTH_STAND1,//WP_EMPLACED_GUN, + BOTH_ATTACK1//WP_TURRET, +}; + +qboolean BG_FileExists(const char *fileName) +{ + if (fileName && fileName[0]) + { + int fh = 0; + trap_FS_FOpenFile(fileName, &fh, FS_READ); + if (fh > 0) + { + trap_FS_FCloseFile(fh); + return qtrue; + } + } + + return qfalse; +} + +#ifndef UI_EXPORTS //don't need this stuff in the ui + +// Following functions don't need to be in namespace, they're already +// different per-module +//#include "../namespace_end.h" + +#ifdef QAGAME +char *G_NewString( const char *string ); +#else +char *CG_NewString( const char *string ); +#endif + +//#include "../namespace_begin.h" + +/* +=============== +BG_ParseField + +Takes a key/value pair and sets the binary values +in a gentity/centity/whatever the hell you want +=============== +*/ + +void BG_ParseField( BG_field_t *l_fields, const char *key, const char *value, byte *ent ) +{ + BG_field_t *f; + byte *b; + float v; + vec3_t vec; + + for ( f=l_fields ; f->name ; f++ ) { + if ( !Q_stricmp(f->name, key) ) { + // found it + b = (byte *)ent; + + switch( f->type ) { + case F_LSTRING: +#ifdef QAGAME + *(char **)(b+f->ofs) = G_NewString (value); +#else + *(char **)(b+f->ofs) = CG_NewString (value); +#endif + break; + case F_VECTOR: + sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + ((float *)(b+f->ofs))[0] = vec[0]; + ((float *)(b+f->ofs))[1] = vec[1]; + ((float *)(b+f->ofs))[2] = vec[2]; + break; + case F_INT: + *(int *)(b+f->ofs) = atoi(value); + break; + case F_FLOAT: + *(float *)(b+f->ofs) = atof(value); + break; + case F_ANGLEHACK: + v = atof(value); + ((float *)(b+f->ofs))[0] = 0; + ((float *)(b+f->ofs))[1] = v; + ((float *)(b+f->ofs))[2] = 0; + break; +#ifdef QAGAME + case F_PARM1: + case F_PARM2: + case F_PARM3: + case F_PARM4: + case F_PARM5: + case F_PARM6: + case F_PARM7: + case F_PARM8: + case F_PARM9: + case F_PARM10: + case F_PARM11: + case F_PARM12: + case F_PARM13: + case F_PARM14: + case F_PARM15: + case F_PARM16: + Q3_SetParm( ((gentity_t *)(ent))->s.number, (f->type - F_PARM1), (char *) value ); + break; +#endif + default: + case F_IGNORE: + break; + } + return; + } + } +} + +#endif + +/* +================ +BG_LegalizedForcePowers + +The magical function to end all functions. +This will take the force power string in powerOut and parse through it, then legalize +it based on the supposed rank and spit it into powerOut, returning true if it was legal +to begin with and false if not. +fpDisabled is actually only expected (needed) from the server, because the ui disables +force power selection anyway when force powers are disabled on the server. +================ +*/ +qboolean BG_LegalizedForcePowers(char *powerOut, int maxRank, qboolean freeSaber, int teamForce, int gametype, int fpDisabled) +{ + char powerBuf[128]; + char readBuf[128]; + qboolean maintainsValidity = qtrue; + int powerLen = strlen(powerOut); + int i = 0; + int c = 0; + int allowedPoints = 0; + int usedPoints = 0; + int countDown = 0; + + int final_Side; + int final_Powers[NUM_FORCE_POWERS]; + + if (powerLen >= 128) + { //This should not happen. If it does, this is obviously a bogus string. + //They can have this string. Because I said so. + strcpy(powerBuf, "7-1-032330000000001333"); + maintainsValidity = qfalse; + } + else + { + strcpy(powerBuf, powerOut); //copy it as the original + } + + //first of all, print the max rank into the string as the rank + strcpy(powerOut, va("%i-", maxRank)); + + while (i < 128 && powerBuf[i] && powerBuf[i] != '-') + { + i++; + } + i++; + while (i < 128 && powerBuf[i] && powerBuf[i] != '-') + { + readBuf[c] = powerBuf[i]; + c++; + i++; + } + readBuf[c] = 0; + i++; + //at this point, readBuf contains the intended side + final_Side = atoi(readBuf); + + if (final_Side != FORCE_LIGHTSIDE && + final_Side != FORCE_DARKSIDE) + { //Not a valid side. You will be dark. Because I said so. (this is something that should never actually happen unless you purposely feed in an invalid config) + final_Side = FORCE_DARKSIDE; + maintainsValidity = qfalse; + } + + if (teamForce) + { //If we are under force-aligned teams, make sure we're on the right side. + if (final_Side != teamForce) + { + final_Side = teamForce; + //maintainsValidity = qfalse; + //Not doing this, for now. Let them join the team with their filtered powers. + } + } + + //Now we have established a valid rank, and a valid side. + //Read the force powers in, and cut them down based on the various rules supplied. + c = 0; + while (i < 128 && powerBuf[i] && powerBuf[i] != '\n' && c < NUM_FORCE_POWERS) + { + readBuf[0] = powerBuf[i]; + readBuf[1] = 0; + final_Powers[c] = atoi(readBuf); + c++; + i++; + } + + //final_Powers now contains all the stuff from the string + //Set the maximum allowed points used based on the max rank level, and count the points actually used. + allowedPoints = forceMasteryPoints[maxRank]; + + i = 0; + while (i < NUM_FORCE_POWERS) + { //if this power doesn't match the side we're on, then 0 it now. + if (final_Powers[i] && + forcePowerDarkLight[i] && + forcePowerDarkLight[i] != final_Side) + { + final_Powers[i] = 0; + //This is only likely to happen with g_forceBasedTeams. Let it slide. + } + + if ( final_Powers[i] && + (fpDisabled & (1 << i)) ) + { //if this power is disabled on the server via said server option, then we don't get it. + final_Powers[i] = 0; + } + + i++; + } + + if (gametype < GT_TEAM) + { //don't bother with team powers then + final_Powers[FP_TEAM_HEAL] = 0; + final_Powers[FP_TEAM_FORCE] = 0; + } + + usedPoints = 0; + i = 0; + while (i < NUM_FORCE_POWERS) + { + countDown = 0; + + countDown = final_Powers[i]; + + while (countDown > 0) + { + usedPoints += bgForcePowerCost[i][countDown]; //[fp index][fp level] + //if this is jump, or we have a free saber and it's offense or defense, take the level back down on level 1 + if ( countDown == 1 && + ((i == FP_LEVITATION) || + (i == FP_SABER_OFFENSE && freeSaber) || + (i == FP_SABER_DEFENSE && freeSaber)) ) + { + usedPoints -= bgForcePowerCost[i][countDown]; + } + countDown--; + } + + i++; + } + + if (usedPoints > allowedPoints) + { //Time to do the fancy stuff. (meaning, slowly cut parts off while taking a guess at what is most or least important in the config) + int attemptedCycles = 0; + int powerCycle = 2; + int minPow = 0; + + if (freeSaber) + { + minPow = 1; + } + + maintainsValidity = qfalse; + + while (usedPoints > allowedPoints) + { + c = 0; + + while (c < NUM_FORCE_POWERS && usedPoints > allowedPoints) + { + if (final_Powers[c] && final_Powers[c] < powerCycle) + { //kill in order of lowest powers, because the higher powers are probably more important + if (c == FP_SABER_OFFENSE && + (final_Powers[FP_SABER_DEFENSE] > minPow || final_Powers[FP_SABERTHROW] > 0)) + { //if we're on saber attack, only suck it down if we have no def or throw either + int whichOne = FP_SABERTHROW; //first try throw + + if (!final_Powers[whichOne]) + { + whichOne = FP_SABER_DEFENSE; //if no throw, drain defense + } + + while (final_Powers[whichOne] > 0 && usedPoints > allowedPoints) + { + if ( final_Powers[whichOne] > 1 || + ( (whichOne != FP_SABER_OFFENSE || !freeSaber) && + (whichOne != FP_SABER_DEFENSE || !freeSaber) ) ) + { //don't take attack or defend down on level 1 still, if it's free + usedPoints -= bgForcePowerCost[whichOne][final_Powers[whichOne]]; + final_Powers[whichOne]--; + } + else + { + break; + } + } + } + else + { + while (final_Powers[c] > 0 && usedPoints > allowedPoints) + { + if ( final_Powers[c] > 1 || + ((c != FP_LEVITATION) && + (c != FP_SABER_OFFENSE || !freeSaber) && + (c != FP_SABER_DEFENSE || !freeSaber)) ) + { + usedPoints -= bgForcePowerCost[c][final_Powers[c]]; + final_Powers[c]--; + } + else + { + break; + } + } + } + } + + c++; + } + + powerCycle++; + attemptedCycles++; + + if (attemptedCycles > NUM_FORCE_POWERS) + { //I think this should be impossible. But just in case. + break; + } + } + + if (usedPoints > allowedPoints) + { //Still? Fine then.. we will kill all of your powers, except the freebies. + i = 0; + + while (i < NUM_FORCE_POWERS) + { + final_Powers[i] = 0; + if (i == FP_LEVITATION || + (i == FP_SABER_OFFENSE && freeSaber) || + (i == FP_SABER_DEFENSE && freeSaber)) + { + final_Powers[i] = 1; + } + i++; + } + usedPoints = 0; + } + } + + if (freeSaber) + { + if (final_Powers[FP_SABER_OFFENSE] < 1) + { + final_Powers[FP_SABER_OFFENSE] = 1; + } + if (final_Powers[FP_SABER_DEFENSE] < 1) + { + final_Powers[FP_SABER_DEFENSE] = 1; + } + } + if (final_Powers[FP_LEVITATION] < 1) + { + final_Powers[FP_LEVITATION] = 1; + } + + i = 0; + while (i < NUM_FORCE_POWERS) + { + if (final_Powers[i] > FORCE_LEVEL_3) + { + final_Powers[i] = FORCE_LEVEL_3; + } + i++; + } + + if (fpDisabled) + { //If we specifically have attack or def disabled, force them up to level 3. It's the way + //things work for the case of all powers disabled. + //If jump is disabled, down-cap it to level 1. Otherwise don't do a thing. + if (fpDisabled & (1 << FP_LEVITATION)) + { + final_Powers[FP_LEVITATION] = 1; + } + if (fpDisabled & (1 << FP_SABER_OFFENSE)) + { + final_Powers[FP_SABER_OFFENSE] = 3; + } + if (fpDisabled & (1 << FP_SABER_DEFENSE)) + { + final_Powers[FP_SABER_DEFENSE] = 3; + } + } + + if (final_Powers[FP_SABER_OFFENSE] < 1) + { + final_Powers[FP_SABER_DEFENSE] = 0; + final_Powers[FP_SABERTHROW] = 0; + } + + //We finally have all the force powers legalized and stored locally. + //Put them all into the string and return the result. We already have + //the rank there, so print the side and the powers now. + Q_strcat(powerOut, 128, va("%i-", final_Side)); + + i = strlen(powerOut); + c = 0; + while (c < NUM_FORCE_POWERS) + { + strcpy(readBuf, va("%i", final_Powers[c])); + powerOut[i] = readBuf[0]; + c++; + i++; + } + powerOut[i] = 0; + + return maintainsValidity; +} + +#ifdef __LCC__ +// given a boltmatrix, return in vec a normalised vector for the axis requested in flags +void BG_GiveMeVectorFromMatrix(mdxaBone_t *boltMatrix, int flags, vec3_t vec) +{ + switch (flags) + { + case ORIGIN: + vec[0] = boltMatrix->matrix[0][3]; + vec[1] = boltMatrix->matrix[1][3]; + vec[2] = boltMatrix->matrix[2][3]; + break; + case POSITIVE_Y: + vec[0] = boltMatrix->matrix[0][1]; + vec[1] = boltMatrix->matrix[1][1]; + vec[2] = boltMatrix->matrix[2][1]; + break; + case POSITIVE_X: + vec[0] = boltMatrix->matrix[0][0]; + vec[1] = boltMatrix->matrix[1][0]; + vec[2] = boltMatrix->matrix[2][0]; + break; + case POSITIVE_Z: + vec[0] = boltMatrix->matrix[0][2]; + vec[1] = boltMatrix->matrix[1][2]; + vec[2] = boltMatrix->matrix[2][2]; + break; + case NEGATIVE_Y: + vec[0] = -boltMatrix->matrix[0][1]; + vec[1] = -boltMatrix->matrix[1][1]; + vec[2] = -boltMatrix->matrix[2][1]; + break; + case NEGATIVE_X: + vec[0] = -boltMatrix->matrix[0][0]; + vec[1] = -boltMatrix->matrix[1][0]; + vec[2] = -boltMatrix->matrix[2][0]; + break; + case NEGATIVE_Z: + vec[0] = -boltMatrix->matrix[0][2]; + vec[1] = -boltMatrix->matrix[1][2]; + vec[2] = -boltMatrix->matrix[2][2]; + break; + } +} +#endif + +/*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, // classname + NULL, // pickup_sound + { NULL, // world_model[0] + NULL, // world_model[1] + 0, 0} , // world_model[2],[3] + NULL, // view_model +/* icon */ NULL, // icon +/* pickup */ //NULL, // pickup_name + 0, // quantity + 0, // giType (IT_*) + 0, // giTag +/* precache */ "", // precaches +/* sounds */ "", // sounds + "" // description + }, // leave index 0 alone + + // + // Pickups + // + +/*QUAKED item_shield_sm_instant (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Instant shield pickup, restores 25 +*/ + { + "item_shield_sm_instant", + "sound/player/pickupshield.wav", + { "models/map_objects/mp/psd_sm.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/small_shield", +/* pickup */// "Shield Small", + 25, + IT_ARMOR, + 1, //special for shield - max on pickup is maxhealth*tag, thus small shield goes up to 100 shield +/* precache */ "", +/* sounds */ "" + "" // description + }, + +/*QUAKED item_shield_lrg_instant (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Instant shield pickup, restores 100 +*/ + { + "item_shield_lrg_instant", + "sound/player/pickupshield.wav", + { "models/map_objects/mp/psd.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/large_shield", +/* pickup */// "Shield Large", + 100, + IT_ARMOR, + 2, //special for shield - max on pickup is maxhealth*tag, thus large shield goes up to 200 shield +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_medpak_instant (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Instant medpack pickup, heals 25 +*/ + { + "item_medpak_instant", + "sound/player/pickuphealth.wav", + { "models/map_objects/mp/medpac.md3", + 0, 0, 0 }, +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_medkit", +/* pickup */// "Medpack", + 25, + IT_HEALTH, + 0, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + + // + // ITEMS + // + +/*QUAKED item_seeker (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +30 seconds of seeker drone +*/ + { + "item_seeker", + "sound/weapons/w_pkup.wav", + { "models/items/remote.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_seeker", +/* pickup */// "Seeker Drone", + 120, + IT_HOLDABLE, + HI_SEEKER, +/* precache */ "", +/* sounds */ "", + "@MENUS_AN_ATTACK_DRONE_SIMILAR" // description + }, + +/*QUAKED item_shield (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Portable shield +*/ + { + "item_shield", + "sound/weapons/w_pkup.wav", + { "models/map_objects/mp/shield.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_shieldwall", +/* pickup */// "Forcefield", + 120, + IT_HOLDABLE, + HI_SHIELD, +/* precache */ "", +/* sounds */ "sound/weapons/detpack/stick.wav sound/movers/doors/forcefield_on.wav sound/movers/doors/forcefield_off.wav sound/movers/doors/forcefield_lp.wav sound/effects/bumpfield.wav", + "@MENUS_THIS_STATIONARY_ENERGY" // description + }, + +/*QUAKED item_medpac (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Bacta canister pickup, heals 25 on use +*/ + { + "item_medpac", //should be item_bacta + "sound/weapons/w_pkup.wav", + { "models/map_objects/mp/bacta.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_bacta", +/* pickup */// "Bacta Canister", + 25, + IT_HOLDABLE, + HI_MEDPAC, +/* precache */ "", +/* sounds */ "", + "@SP_INGAME_BACTA_DESC" // description + }, + +/*QUAKED item_medpac_big (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Big bacta canister pickup, heals 50 on use +*/ + { + "item_medpac_big", //should be item_bacta + "sound/weapons/w_pkup.wav", + { "models/items/big_bacta.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_big_bacta", +/* pickup */// "Bacta Canister", + 25, + IT_HOLDABLE, + HI_MEDPAC_BIG, +/* precache */ "", +/* sounds */ "", + "@SP_INGAME_BACTA_DESC" // description + }, + +/*QUAKED item_binoculars (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +These will be standard equipment on the player - DO NOT PLACE +*/ + { + "item_binoculars", + "sound/weapons/w_pkup.wav", + { "models/items/binoculars.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_zoom", +/* pickup */// "Binoculars", + 60, + IT_HOLDABLE, + HI_BINOCULARS, +/* precache */ "", +/* sounds */ "", + "@SP_INGAME_LA_GOGGLES_DESC" // description + }, + +/*QUAKED item_sentry_gun (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Sentry gun inventory pickup. +*/ + { + "item_sentry_gun", + "sound/weapons/w_pkup.wav", + { "models/items/psgun.glm", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_sentrygun", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_SENTRY_GUN, +/* precache */ "", +/* sounds */ "", + "@MENUS_THIS_DEADLY_WEAPON_IS" // description + }, + +/*QUAKED item_jetpack (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Do not place. +*/ + { + "item_jetpack", + "sound/weapons/w_pkup.wav", + { "models/items/psgun.glm", //FIXME: no model + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_jetpack", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_JETPACK, +/* precache */ "effects/boba/jet.efx", +/* sounds */ "sound/chars/boba/JETON.wav sound/chars/boba/JETHOVER.wav sound/effects/fire_lp.wav", + "@MENUS_JETPACK_DESC" // description + }, + +/*QUAKED item_healthdisp (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Do not place. For siege classes ONLY. +*/ + { + "item_healthdisp", + "sound/weapons/w_pkup.wav", + { "models/map_objects/mp/bacta.md3", //replace me + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_healthdisp", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_HEALTHDISP, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_ammodisp (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Do not place. For siege classes ONLY. +*/ + { + "item_ammodisp", + "sound/weapons/w_pkup.wav", + { "models/map_objects/mp/bacta.md3", //replace me + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_ammodisp", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_AMMODISP, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_eweb_holdable (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +Do not place. For siege classes ONLY. +*/ + { + "item_eweb_holdable", + "sound/interface/shieldcon_empty", + { "models/map_objects/hoth/eweb_model.glm", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_eweb", +/* pickup */// "Sentry Gun", + 120, + IT_HOLDABLE, + HI_EWEB, +/* precache */ "", +/* sounds */ "", + "@MENUS_EWEB_DESC" // description + }, + +/*QUAKED item_seeker (.3 .3 1) (-8 -8 -0) (8 8 16) suspended +30 seconds of seeker drone +*/ + { + "item_cloak", + "sound/weapons/w_pkup.wav", + { "models/items/psgun.glm", //FIXME: no model + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_cloak", +/* pickup */// "Seeker Drone", + 120, + IT_HOLDABLE, + HI_CLOAK, +/* precache */ "", +/* sounds */ "", + "@MENUS_CLOAK_DESC" // description + }, + +/*QUAKED item_force_enlighten_light (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Adds one rank to all Force powers temporarily. Only light jedi can use. +*/ + { + "item_force_enlighten_light", + "sound/player/enlightenment.wav", + { "models/map_objects/mp/jedi_enlightenment.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/mpi_jlight", +/* pickup */// "Light Force Enlightenment", + 25, + IT_POWERUP, + PW_FORCE_ENLIGHTENED_LIGHT, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_force_enlighten_dark (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Adds one rank to all Force powers temporarily. Only dark jedi can use. +*/ + { + "item_force_enlighten_dark", + "sound/player/enlightenment.wav", + { "models/map_objects/mp/dk_enlightenment.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/mpi_dklight", +/* pickup */// "Dark Force Enlightenment", + 25, + IT_POWERUP, + PW_FORCE_ENLIGHTENED_DARK, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_force_boon (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Unlimited Force Pool for a short time. +*/ + { + "item_force_boon", + "sound/player/boon.wav", + { "models/map_objects/mp/force_boon.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/mpi_fboon", +/* pickup */// "Force Boon", + 25, + IT_POWERUP, + PW_FORCE_BOON, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED item_ysalimari (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +A small lizard carried on the player, which prevents the possessor from using any Force power. However, he is unaffected by any Force power. +*/ + { + "item_ysalimari", + "sound/player/ysalimari.wav", + { "models/map_objects/mp/ysalimari.md3", + 0, 0, 0} , +/* view */ NULL, +/* icon */ "gfx/hud/mpi_ysamari", +/* pickup */// "Ysalamiri", + 25, + IT_POWERUP, + PW_YSALAMIRI, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // + // WEAPONS + // + +/*QUAKED weapon_stun_baton (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "weapon_stun_baton", + "sound/weapons/w_pkup.wav", + { "models/weapons2/stun_baton/baton_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/stun_baton/baton.md3", +/* icon */ "gfx/hud/w_icon_stunbaton", +/* pickup */// "Stun Baton", + 100, + IT_WEAPON, + WP_STUN_BATON, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED weapon_melee (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "weapon_melee", + "sound/weapons/w_pkup.wav", + { "models/weapons2/stun_baton/baton_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/stun_baton/baton.md3", +/* icon */ "gfx/hud/w_icon_melee", +/* pickup */// "Stun Baton", + 100, + IT_WEAPON, + WP_MELEE, +/* precache */ "", +/* sounds */ "", + "@MENUS_MELEE_DESC" // description + }, + +/*QUAKED weapon_saber (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "weapon_saber", + "sound/weapons/w_pkup.wav", + { "models/weapons2/saber/saber_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/saber/saber_w.md3", +/* icon */ "gfx/hud/w_icon_lightsaber", +/* pickup */// "Lightsaber", + 100, + IT_WEAPON, + WP_SABER, +/* precache */ "", +/* sounds */ "", + "@MENUS_AN_ELEGANT_WEAPON_FOR" // description + }, + +/*QUAKED weapon_bryar_pistol (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + //"weapon_bryar_pistol", + "weapon_blaster_pistol", + "sound/weapons/w_pkup.wav", + { "models/weapons2/blaster_pistol/blaster_pistol_w.glm",//"models/weapons2/briar_pistol/briar_pistol_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/blaster_pistol/blaster_pistol.md3",//"models/weapons2/briar_pistol/briar_pistol.md3", +/* icon */ "gfx/hud/w_icon_blaster_pistol",//"gfx/hud/w_icon_rifle", +/* pickup */// "Bryar Pistol", + 100, + IT_WEAPON, + WP_BRYAR_PISTOL, +/* precache */ "", +/* sounds */ "", + "@MENUS_BLASTER_PISTOL_DESC" // description + }, + +/*QUAKED weapon_concussion_rifle (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_concussion_rifle", + "sound/weapons/w_pkup.wav", + { "models/weapons2/concussion/c_rifle_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/concussion/c_rifle.md3", +/* icon */ "gfx/hud/w_icon_c_rifle",//"gfx/hud/w_icon_rifle", +/* pickup */// "Concussion Rifle", + 50, + IT_WEAPON, + WP_CONCUSSION, +/* precache */ "", +/* sounds */ "", + "@MENUS_CONC_RIFLE_DESC" // description + }, + +/*QUAKED weapon_bryar_pistol_old (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "weapon_bryar_pistol", + "sound/weapons/w_pkup.wav", + { "models/weapons2/briar_pistol/briar_pistol_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/briar_pistol/briar_pistol.md3", +/* icon */ "gfx/hud/w_icon_briar",//"gfx/hud/w_icon_rifle", +/* pickup */// "Bryar Pistol", + 100, + IT_WEAPON, + WP_BRYAR_OLD, +/* precache */ "", +/* sounds */ "", + "@SP_INGAME_BLASTER_PISTOL" // description + }, + +/*QUAKED weapon_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_blaster", + "sound/weapons/w_pkup.wav", + { "models/weapons2/blaster_r/blaster_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/blaster_r/blaster.md3", +/* icon */ "gfx/hud/w_icon_blaster", +/* pickup */// "E11 Blaster Rifle", + 100, + IT_WEAPON, + WP_BLASTER, +/* precache */ "", +/* sounds */ "", + "@MENUS_THE_PRIMARY_WEAPON_OF" // description + }, + +/*QUAKED weapon_disruptor (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_disruptor", + "sound/weapons/w_pkup.wav", + { "models/weapons2/disruptor/disruptor_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/disruptor/disruptor.md3", +/* icon */ "gfx/hud/w_icon_disruptor", +/* pickup */// "Tenloss Disruptor Rifle", + 100, + IT_WEAPON, + WP_DISRUPTOR, +/* precache */ "", +/* sounds */ "", + "@MENUS_THIS_NEFARIOUS_WEAPON" // description + }, + +/*QUAKED weapon_bowcaster (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_bowcaster", + "sound/weapons/w_pkup.wav", + { "models/weapons2/bowcaster/bowcaster_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/bowcaster/bowcaster.md3", +/* icon */ "gfx/hud/w_icon_bowcaster", +/* pickup */// "Wookiee Bowcaster", + 100, + IT_WEAPON, + WP_BOWCASTER, +/* precache */ "", +/* sounds */ "", + "@MENUS_THIS_ARCHAIC_LOOKING" // description + }, + +/*QUAKED weapon_repeater (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_repeater", + "sound/weapons/w_pkup.wav", + { "models/weapons2/heavy_repeater/heavy_repeater_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/heavy_repeater/heavy_repeater.md3", +/* icon */ "gfx/hud/w_icon_repeater", +/* pickup */// "Imperial Heavy Repeater", + 100, + IT_WEAPON, + WP_REPEATER, +/* precache */ "", +/* sounds */ "", + "@MENUS_THIS_DESTRUCTIVE_PROJECTILE" // description + }, + +/*QUAKED weapon_demp2 (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +NOTENOTE This weapon is not yet complete. Don't place it. +*/ + { + "weapon_demp2", + "sound/weapons/w_pkup.wav", + { "models/weapons2/demp2/demp2_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/demp2/demp2.md3", +/* icon */ "gfx/hud/w_icon_demp2", +/* pickup */// "DEMP2", + 100, + IT_WEAPON, + WP_DEMP2, +/* precache */ "", +/* sounds */ "", + "@MENUS_COMMONLY_REFERRED_TO" // description + }, + +/*QUAKED weapon_flechette (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_flechette", + "sound/weapons/w_pkup.wav", + { "models/weapons2/golan_arms/golan_arms_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/golan_arms/golan_arms.md3", +/* icon */ "gfx/hud/w_icon_flechette", +/* pickup */// "Golan Arms Flechette", + 100, + IT_WEAPON, + WP_FLECHETTE, +/* precache */ "", +/* sounds */ "", + "@MENUS_WIDELY_USED_BY_THE_CORPORATE" // description + }, + +/*QUAKED weapon_rocket_launcher (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_rocket_launcher", + "sound/weapons/w_pkup.wav", + { "models/weapons2/merr_sonn/merr_sonn_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/merr_sonn/merr_sonn.md3", +/* icon */ "gfx/hud/w_icon_merrsonn", +/* pickup */// "Merr-Sonn Missile System", + 3, + IT_WEAPON, + WP_ROCKET_LAUNCHER, +/* precache */ "", +/* sounds */ "", + "@MENUS_THE_PLX_2M_IS_AN_EXTREMELY" // description + }, + +/*QUAKED ammo_thermal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_thermal", + "sound/weapons/w_pkup.wav", + { "models/weapons2/thermal/thermal_pu.md3", + "models/weapons2/thermal/thermal_w.glm", 0, 0}, +/* view */ "models/weapons2/thermal/thermal.md3", +/* icon */ "gfx/hud/w_icon_thermal", +/* pickup */// "Thermal Detonators", + 4, + IT_AMMO, + AMMO_THERMAL, +/* precache */ "", +/* sounds */ "", + "@MENUS_THE_THERMAL_DETONATOR" // description + }, + +/*QUAKED ammo_tripmine (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_tripmine", + "sound/weapons/w_pkup.wav", + { "models/weapons2/laser_trap/laser_trap_pu.md3", + "models/weapons2/laser_trap/laser_trap_w.glm", 0, 0}, +/* view */ "models/weapons2/laser_trap/laser_trap.md3", +/* icon */ "gfx/hud/w_icon_tripmine", +/* pickup */// "Trip Mines", + 3, + IT_AMMO, + AMMO_TRIPMINE, +/* precache */ "", +/* sounds */ "", + "@MENUS_TRIP_MINES_CONSIST_OF" // description + }, + +/*QUAKED ammo_detpack (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "ammo_detpack", + "sound/weapons/w_pkup.wav", + { "models/weapons2/detpack/det_pack_pu.md3", "models/weapons2/detpack/det_pack_proj.glm", "models/weapons2/detpack/det_pack_w.glm", 0}, +/* view */ "models/weapons2/detpack/det_pack.md3", +/* icon */ "gfx/hud/w_icon_detpack", +/* pickup */// "Det Packs", + 3, + IT_AMMO, + AMMO_DETPACK, +/* precache */ "", +/* sounds */ "", + "@MENUS_A_DETONATION_PACK_IS" // description + }, + +/*QUAKED weapon_thermal (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_thermal", + "sound/weapons/w_pkup.wav", + { "models/weapons2/thermal/thermal_w.glm", "models/weapons2/thermal/thermal_pu.md3", + 0, 0 }, +/* view */ "models/weapons2/thermal/thermal.md3", +/* icon */ "gfx/hud/w_icon_thermal", +/* pickup */// "Thermal Detonator", + 4, + IT_WEAPON, + WP_THERMAL, +/* precache */ "", +/* sounds */ "", + "@MENUS_THE_THERMAL_DETONATOR" // description + }, + +/*QUAKED weapon_trip_mine (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_trip_mine", + "sound/weapons/w_pkup.wav", + { "models/weapons2/laser_trap/laser_trap_w.glm", "models/weapons2/laser_trap/laser_trap_pu.md3", + 0, 0}, +/* view */ "models/weapons2/laser_trap/laser_trap.md3", +/* icon */ "gfx/hud/w_icon_tripmine", +/* pickup */// "Trip Mine", + 3, + IT_WEAPON, + WP_TRIP_MINE, +/* precache */ "", +/* sounds */ "", + "@MENUS_TRIP_MINES_CONSIST_OF" // description + }, + +/*QUAKED weapon_det_pack (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_det_pack", + "sound/weapons/w_pkup.wav", + { "models/weapons2/detpack/det_pack_proj.glm", "models/weapons2/detpack/det_pack_pu.md3", "models/weapons2/detpack/det_pack_w.glm", 0}, +/* view */ "models/weapons2/detpack/det_pack.md3", +/* icon */ "gfx/hud/w_icon_detpack", +/* pickup */// "Det Pack", + 3, + IT_WEAPON, + WP_DET_PACK, +/* precache */ "", +/* sounds */ "", + "@MENUS_A_DETONATION_PACK_IS" // description + }, + +/*QUAKED weapon_emplaced (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +*/ + { + "weapon_emplaced", + "sound/weapons/w_pkup.wav", + { "models/weapons2/blaster_r/blaster_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/blaster_r/blaster.md3", +/* icon */ "gfx/hud/w_icon_blaster", +/* pickup */// "Emplaced Gun", + 50, + IT_WEAPON, + WP_EMPLACED_GUN, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + +//NOTE: This is to keep things from messing up because the turret weapon type isn't real + { + "weapon_turretwp", + "sound/weapons/w_pkup.wav", + { "models/weapons2/blaster_r/blaster_w.glm", + 0, 0, 0}, +/* view */ "models/weapons2/blaster_r/blaster.md3", +/* icon */ "gfx/hud/w_icon_blaster", +/* pickup */// "Turret Gun", + 50, + IT_WEAPON, + WP_TURRET, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // + // AMMO ITEMS + // + +/*QUAKED ammo_force (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Don't place this +*/ + { + "ammo_force", + "sound/player/pickupenergy.wav", + { "models/items/energy_cell.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/hud/w_icon_blaster", +/* pickup */// "Force??", + 100, + IT_AMMO, + AMMO_FORCE, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_blaster (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Ammo for the Bryar and Blaster pistols. +*/ + { + "ammo_blaster", + "sound/player/pickupenergy.wav", + { "models/items/energy_cell.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/hud/i_icon_battery", +/* pickup */// "Blaster Pack", + 100, + IT_AMMO, + AMMO_BLASTER, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_powercell (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Ammo for Tenloss Disruptor, Wookie Bowcaster, and the Destructive Electro Magnetic Pulse (demp2 ) guns +*/ + { + "ammo_powercell", + "sound/player/pickupenergy.wav", + { "models/items/power_cell.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/ammo_power_cell", +/* pickup */// "Power Cell", + 100, + IT_AMMO, + AMMO_POWERCELL, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_metallic_bolts (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Ammo for Imperial Heavy Repeater and the Golan Arms Flechette +*/ + { + "ammo_metallic_bolts", + "sound/player/pickupenergy.wav", + { "models/items/metallic_bolts.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/ammo_metallic_bolts", +/* pickup */// "Metallic Bolts", + 100, + IT_AMMO, + AMMO_METAL_BOLTS, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_rockets (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +Ammo for Merr-Sonn portable missile launcher +*/ + { + "ammo_rockets", + "sound/player/pickupenergy.wav", + { "models/items/rockets.md3", + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/ammo_rockets", +/* pickup */// "Rockets", + 3, + IT_AMMO, + AMMO_ROCKETS, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*QUAKED ammo_all (.3 .3 1) (-16 -16 -16) (16 16 16) suspended +DO NOT PLACE in a map, this is only for siege classes that have ammo +dispensing ability +*/ + { + "ammo_all", + "sound/player/pickupenergy.wav", + { "models/items/battery.md3", //replace me + 0, 0, 0}, +/* view */ NULL, +/* icon */ "gfx/mp/ammo_rockets", //replace me +/* pickup */// "Rockets", + 0, + IT_AMMO, + -1, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // + // POWERUP ITEMS + // +/*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", + "models/flags/r_flag_ysal.md3", 0, 0 }, +/* view */ NULL, +/* icon */ "gfx/hud/mpi_rflag", +/* pickup */// "Red Flag", + 0, + IT_TEAM, + PW_REDFLAG, +/* precache */ "", +/* sounds */ "", + "" // description + }, + +/*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", + "models/flags/b_flag_ysal.md3", 0, 0 }, +/* view */ NULL, +/* icon */ "gfx/hud/mpi_bflag", +/* pickup */// "Blue Flag", + 0, + IT_TEAM, + PW_BLUEFLAG, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // + // PERSISTANT POWERUP ITEMS + // + + /*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 }, +/* view */ NULL, +/* icon */ "icons/iconf_neutral1", +/* pickup */// "Neutral Flag", + 0, + IT_TEAM, + PW_NEUTRALFLAG, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + { + "item_redcube", + "sound/player/pickupenergy.wav", + { "models/powerups/orb/r_orb.md3", + 0, 0, 0 }, +/* view */ NULL, +/* icon */ "icons/iconh_rorb", +/* pickup */// "Red Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + { + "item_bluecube", + "sound/player/pickupenergy.wav", + { "models/powerups/orb/b_orb.md3", + 0, 0, 0 }, +/* view */ NULL, +/* icon */ "icons/iconh_borb", +/* pickup */// "Blue Cube", + 0, + IT_TEAM, + 0, +/* precache */ "", +/* sounds */ "", + "" // description + }, + + // end of list marker + {NULL} +}; + +int bg_numItems = sizeof(bg_itemlist) / sizeof(bg_itemlist[0]) - 1; + +float vectoyaw( const vec3_t vec ) { + float yaw; + + if (vec[YAW] == 0 && vec[PITCH] == 0) { + yaw = 0; + } else { + if (vec[PITCH]) { + yaw = ( atan2( vec[YAW], vec[PITCH]) * 180 / M_PI ); + } else if (vec[YAW] > 0) { + yaw = 90; + } else { + yaw = 270; + } + if (yaw < 0) { + yaw += 360; + } + } + + return yaw; +} + +qboolean BG_HasYsalamiri(int gametype, playerState_t *ps) +{ + if (gametype == GT_CTY && + (ps->powerups[PW_REDFLAG] || ps->powerups[PW_BLUEFLAG])) + { + return qtrue; + } + + if (ps->powerups[PW_YSALAMIRI]) + { + return qtrue; + } + + return qfalse; +} + +qboolean BG_CanUseFPNow(int gametype, playerState_t *ps, int time, forcePowers_t power) +{ + if (BG_HasYsalamiri(gametype, ps)) + { + return qfalse; + } + + if ( ps->forceRestricted || ps->trueNonJedi ) + { + return qfalse; + } + + if (ps->weapon == WP_EMPLACED_GUN) + { //can't use any of your powers while on an emplaced weapon + return qfalse; + } + + if (ps->m_iVehicleNum) + { //can't use powers while riding a vehicle (this may change, I don't know) + return qfalse; + } + + if (ps->duelInProgress) + { + if (power != FP_SABER_OFFENSE && power != FP_SABER_DEFENSE && /*power != FP_SABERTHROW &&*/ + power != FP_LEVITATION) + { + if (!ps->saberLockFrame || power != FP_PUSH) + { + return qfalse; + } + } + } + + if (ps->saberLockFrame || ps->saberLockTime > time) + { + if (power != FP_PUSH) + { + return qfalse; + } + } + + if (ps->fallingToDeath) + { + return qfalse; + } + + if ((ps->brokenLimbs & (1 << BROKENLIMB_RARM)) || + (ps->brokenLimbs & (1 << BROKENLIMB_LARM))) + { //powers we can't use with a broken arm + switch (power) + { + case FP_PUSH: + case FP_PULL: + case FP_GRIP: + case FP_LIGHTNING: + case FP_DRAIN: + return qfalse; + default: + break; + } + } + + return qtrue; +} + +/* +============== +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].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_FindItemForAmmo + +=============== +*/ +gitem_t *BG_FindItemForAmmo( ammo_t ammo ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++) { + if ( it->giType == IT_AMMO && it->giTag == ammo ) { + return it; + } + } + + Com_Error( ERR_DROP, "Couldn't find item for ammo %i", ammo); + return NULL; +} + +/* +=============== +BG_FindItem + +=============== +*/ +gitem_t *BG_FindItem( const char *classname ) { + gitem_t *it; + + for ( it = bg_itemlist + 1 ; it->classname ; it++ ) { + if ( !Q_stricmp( it->classname, classname) ) + 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; +} + +int BG_ProperForceIndex(int power) +{ + int i = 0; + + while (i < NUM_FORCE_POWERS) + { + if (forcePowerSorted[i] == power) + { + return i; + } + + i++; + } + + return -1; +} + +void BG_CycleForce(playerState_t *ps, int direction) +{ + int i = ps->fd.forcePowerSelected; + int x = i; + int presel = i; + int foundnext = -1; + + if (!ps->fd.forcePowersKnown & (1 << x) || + x >= NUM_FORCE_POWERS || + x == -1) + { //apparently we have no valid force powers + return; + } + + x = BG_ProperForceIndex(x); + presel = x; + + if (direction == 1) + { //get the next power + x++; + } + else + { //get the previous power + x--; + } + + if (x >= NUM_FORCE_POWERS) + { //cycled off the end.. cycle around to the first + x = 0; + } + if (x < 0) + { //cycled off the beginning.. cycle around to the last + x = NUM_FORCE_POWERS-1; + } + + i = forcePowerSorted[x]; //the "sorted" value of this power + + while (x != presel) + { //loop around to the current force power + if (ps->fd.forcePowersKnown & (1 << i) && i != ps->fd.forcePowerSelected) + { //we have the force power + if (i != FP_LEVITATION && + i != FP_SABER_OFFENSE && + i != FP_SABER_DEFENSE && + i != FP_SABERTHROW) + { //it's selectable + foundnext = i; + break; + } + } + + if (direction == 1) + { //next + x++; + } + else + { //previous + x--; + } + + if (x >= NUM_FORCE_POWERS) + { //loop around + x = 0; + } + if (x < 0) + { //loop around + x = NUM_FORCE_POWERS-1; + } + + i = forcePowerSorted[x]; //set to the sorted value again + } + + if (foundnext != -1) + { //found one, select it + ps->fd.forcePowerSelected = foundnext; + } +} + +int BG_GetItemIndexByTag(int tag, int type) +{ //Get the itemlist index from the tag and type + int i = 0; + + while (i < bg_numItems) + { + if (bg_itemlist[i].giTag == tag && + bg_itemlist[i].giType == type) + { + return i; + } + + i++; + } + + return 0; +} + +//yeah.. +qboolean BG_IsItemSelectable(playerState_t *ps, int item) +{ + if (item == HI_HEALTHDISP || item == HI_AMMODISP || + item == HI_JETPACK) + { + return qfalse; + } + return qtrue; +} + +void BG_CycleInven(playerState_t *ps, int direction) +{ + int i; + int dontFreeze = 0; + int original; + + i = bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag; + original = i; + + if (direction == 1) + { //next + i++; + if (i == HI_NUM_HOLDABLE) + { + i = 1; + } + } + else + { //previous + i--; + if (i == 0) + { + i = HI_NUM_HOLDABLE-1; + } + } + + while (i != original) + { //go in a full loop until hitting something, if hit nothing then select nothing + if (ps->stats[STAT_HOLDABLE_ITEMS] & (1 << i)) + { //we have it, select it. + if (BG_IsItemSelectable(ps, i)) + { + ps->stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(i, IT_HOLDABLE); + break; + } + } + + if (direction == 1) + { //next + i++; + } + else + { //previous + i--; + } + + if (i <= 0) + { //wrap around to the last + i = HI_NUM_HOLDABLE-1; + } + else if (i >= HI_NUM_HOLDABLE) + { //wrap around to the first + i = 1; + } + + dontFreeze++; + if (dontFreeze >= 32) + { //yeah, sure, whatever (it's 2 am and I'm paranoid and can't frickin think) + break; + } + } +} + +/* +================ +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. +================ +*/ +qboolean BG_CanItemBeGrabbed( int gametype, const entityState_t *ent, const playerState_t *ps ) { + gitem_t *item; + + if ( ent->modelindex < 1 || ent->modelindex >= bg_numItems ) { + Com_Error( ERR_DROP, "BG_CanItemBeGrabbed: index out of range" ); + } + + item = &bg_itemlist[ent->modelindex]; + + if ( ps ) + { + if ( ps->trueJedi ) + {//force powers and saber only + if ( item->giType != IT_TEAM //not a flag + && item->giType != IT_ARMOR//not shields + && (item->giType != IT_WEAPON || item->giTag != WP_SABER)//not a saber + && (item->giType != IT_HOLDABLE || item->giTag != HI_SEEKER)//not a seeker + && (item->giType != IT_POWERUP || item->giTag == PW_YSALAMIRI) )//not a force pick-up + { + return qfalse; + } + } + else if ( ps->trueNonJedi ) + {//can't pick up force powerups + if ( (item->giType == IT_POWERUP && item->giTag != PW_YSALAMIRI) //if a powerup, can only can pick up ysalamiri + || (item->giType == IT_HOLDABLE && item->giTag == HI_SEEKER)//if holdable, cannot pick up seeker + || (item->giType == IT_WEAPON && item->giTag == WP_SABER ) )//or if it's a saber + { + return qfalse; + } + } + if ( ps->isJediMaster && item && (item->giType == IT_WEAPON || item->giType == IT_AMMO)) + {//jedi master cannot pick up weapons + return qfalse; + } + if ( ps->duelInProgress ) + { //no picking stuff up while in a duel, no matter what the type is + return qfalse; + } + } + else + {//safety return since below code assumes a non-null ps + return qfalse; + } + + switch( item->giType ) { + case IT_WEAPON: + if (ent->generic1 == ps->clientNum && ent->powerups) + { + return qfalse; + } + if (!(ent->eFlags & EF_DROPPEDWEAPON) && (ps->stats[STAT_WEAPONS] & (1 << item->giTag)) && + item->giTag != WP_THERMAL && item->giTag != WP_TRIP_MINE && item->giTag != WP_DET_PACK) + { //weaponstay stuff.. if this isn't dropped, and you already have it, you don't get it. + return qfalse; + } + if (item->giTag == WP_THERMAL || item->giTag == WP_TRIP_MINE || item->giTag == WP_DET_PACK) + { //check to see if full on ammo for this, if so, then.. + int ammoIndex = weaponData[item->giTag].ammoIndex; + if (ps->ammo[ammoIndex] >= ammoData[ammoIndex].max) + { //don't need it + return qfalse; + } + } + return qtrue; // weapons are always picked up + + case IT_AMMO: + if (item->giTag == -1) + { //special case for "all ammo" packs + return qtrue; + } + if ( ps->ammo[item->giTag] >= ammoData[item->giTag].max) { + return qfalse; // can't hold any more + } + return qtrue; + + case IT_ARMOR: + if ( ps->stats[STAT_ARMOR] >= ps->stats[STAT_MAX_HEALTH]/* * item->giTag*/ ) { + 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 + if ((ps->fd.forcePowersActive & (1 << FP_RAGE))) + { + return qfalse; + } + + 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: + if (ps && (ps->powerups[PW_YSALAMIRI])) + { + if (item->giTag != PW_YSALAMIRI) + { + return qfalse; + } + } + return qtrue; // powerups are always picked up + + case IT_TEAM: // team items, such as flags + if( gametype == GT_CTF || gametype == GT_CTY ) { + // 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; + } + } + + return qfalse; + + case IT_HOLDABLE: + if ( ps->stats[STAT_HOLDABLE_ITEMS] & (1 << item->giTag)) + { + return qfalse; + } + 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_NONLINEAR_STOP: + if ( atTime > tr->trTime + tr->trDuration ) + { + atTime = tr->trTime + tr->trDuration; + } + //new slow-down at end + if ( atTime - tr->trTime > tr->trDuration || atTime - tr->trTime <= 0 ) + { + deltaTime = 0; + } + else + {//FIXME: maybe scale this somehow? So that it starts out faster and stops faster? + deltaTime = tr->trDuration*0.001f*((float)cos( DEG2RAD(90.0f - (90.0f*((float)atTime-tr->trTime)/(float)tr->trDuration)) )); + } + 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; + default: +#ifdef QAGAME + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: [GAME SIDE] unknown trType: %i", tr->trType ); +#else + Com_Error( ERR_DROP, "BG_EvaluateTrajectory: [CLIENTGAME SIDE] unknown trType: %i", tr->trType ); +#endif + 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_NONLINEAR_STOP: + if ( atTime - tr->trTime > tr->trDuration || atTime - tr->trTime <= 0 ) + { + VectorClear( result ); + return; + } + deltaTime = tr->trDuration*0.001f*((float)cos( DEG2RAD(90.0f - (90.0f*((float)atTime-tr->trTime)/(float)tr->trDuration)) )); + VectorScale( tr->trDelta, deltaTime, 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; + default: + Com_Error( ERR_DROP, "BG_EvaluateTrajectoryDelta: unknown trType: %i", tr->trTime ); + break; + } +} + +char *eventnames[] = { + "EV_NONE", + + "EV_CLIENTJOIN", + + "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", + + "EV_JUMP_PAD", // boing sound at origin", jump sound on player + + "EV_GHOUL2_MARK", //create a projectile impact mark on something with a client-side g2 instance. + + "EV_GLOBAL_DUEL", + "EV_PRIVATE_DUEL", + + "EV_JUMP", + "EV_ROLL", + "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_VEH_FIRE", + + "EV_NOAMMO", + "EV_CHANGE_WEAPON", + "EV_FIRE_WEAPON", + "EV_ALT_FIRE", + "EV_SABER_ATTACK", + "EV_SABER_HIT", + "EV_SABER_BLOCK", + "EV_SABER_CLASHFLARE", + "EV_SABER_UNHOLSTER", + "EV_BECOME_JEDIMASTER", + "EV_DISRUPTOR_MAIN_SHOT", + "EV_DISRUPTOR_SNIPER_SHOT", + "EV_DISRUPTOR_SNIPER_MISS", + "EV_DISRUPTOR_HIT", + "EV_DISRUPTOR_ZOOMSOUND", + + "EV_PREDEFSOUND", + + "EV_TEAM_POWER", + + "EV_SCREENSHAKE", + + "EV_LOCALTIMER", + + "EV_USE", // +Use key + + "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_ITEMUSEFAIL", + + "EV_ITEM_RESPAWN", + "EV_ITEM_POP", + "EV_PLAYER_TELEPORT_IN", + "EV_PLAYER_TELEPORT_OUT", + + "EV_GRENADE_BOUNCE", // eventParm will be the soundindex + "EV_MISSILE_STICK", + + "EV_PLAY_EFFECT", + "EV_PLAY_EFFECT_ID", //finally gave in and added it.. + "EV_PLAY_PORTAL_EFFECT_ID", + + "EV_PLAYDOORSOUND", + "EV_PLAYDOORLOOPSOUND", + "EV_BMODEL_SOUND", + + "EV_MUTE_SOUND", + "EV_VOICECMD_SOUND", + "EV_GENERAL_SOUND", + "EV_GLOBAL_SOUND", // no attenuation + "EV_GLOBAL_TEAM_SOUND", + "EV_ENTITY_SOUND", + + "EV_PLAY_ROFF", + + "EV_GLASS_SHATTER", + "EV_DEBRIS", + "EV_MISC_MODEL_EXP", + + "EV_CONC_ALT_IMPACT", + + "EV_MISSILE_HIT", + "EV_MISSILE_MISS", + "EV_MISSILE_MISS_METAL", + "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_FORCE_DRAINED", + + "EV_GIB_PLAYER", // gib a previously living player + "EV_SCOREPLUM", // score plum + + "EV_CTFMESSAGE", + + "EV_BODYFADE", + + "EV_SIEGE_ROUNDOVER", + "EV_SIEGE_OBJECTIVECOMPLETE", + + "EV_DESTROY_GHOUL2_INSTANCE", + + "EV_DESTROY_WEAPON_MODEL", + + "EV_GIVE_NEW_RANK", + "EV_SET_FREE_SABER", + "EV_SET_FORCE_DISABLE", + + "EV_WEAPON_CHARGE", + "EV_WEAPON_CHARGE_ALT", + + "EV_SHIELD_HIT", + + "EV_DEBUG_LINE", + "EV_TESTLINE", + "EV_STOPLOOPINGSOUND", + "EV_STARTLOOPINGSOUND", + "EV_TAUNT", +//fixme, added a bunch that aren't here! +}; + +/* +=============== +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 + { + static vmCvar_t showEvents; + static qboolean isRegistered = qfalse; + + if (!isRegistered) + { + trap_Cvar_Register(&showEvents, "showevents", "0", 0); + isRegistered = qtrue; + } + + if ( showEvents.integer != 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 && ps->pm_type != PM_JETPACK && ps->pm_type != PM_FLOAT ) { + 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; + } + } + // 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_EmplacedView + +Shared code for emplaced angle gun constriction +================= +*/ +int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint) +{ + float dif = AngleSubtract(baseAngles[YAW], angles[YAW]); + + if (dif > constraint || + dif < -constraint) + { + float amt; + + if (dif > constraint) + { + amt = (dif-constraint); + dif = constraint; + } + else if (dif < -constraint) + { + amt = (dif+constraint); + dif = -constraint; + } + else + { + amt = 0.0f; + } + + *newYaw = AngleSubtract(angles[YAW], -dif); + + if (amt > 1.0f || amt < -1.0f) + { //significant, force the view + return 2; + } + else + { //just a little out of range + return 1; + } + } + + return 0; +} + +//To see if the client is trying to use one of the included skins not meant for MP. +//I don't much care for hardcoded strings, but this seems the best way to go. +qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName) +{ + if (!Q_stricmp(skinName, "menu")) + { + return qfalse; + } + else if (!Q_stricmp(modelName, "kyle")) + { + if (!Q_stricmp(skinName, "fpls")) + { + return qfalse; + } + else if (!Q_stricmp(skinName, "fpls2")) + { + return qfalse; + } + else if (!Q_stricmp(skinName, "fpls3")) + { + return qfalse; + } + } + return qtrue; +} + +qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors ) +{ + if (!Q_stricmpn(modelName, "jedi_",5)) + { //argh, it's a custom player skin! + if (team == TEAM_RED && colors) + { + colors[0] = 1.0f; + colors[1] = 0.0f; + colors[2] = 0.0f; + } + else if (team == TEAM_BLUE && colors) + { + colors[0] = 0.0f; + colors[1] = 0.0f; + colors[2] = 1.0f; + } + return qtrue; + } + + if (team == TEAM_RED) + { + if ( Q_stricmp( "red", skinName ) != 0 ) + {//not "red" + if ( Q_stricmp( "blue", skinName ) == 0 + || Q_stricmp( "default", skinName ) == 0 + || strchr(skinName, '|')//a multi-skin playerModel + || !BG_IsValidCharacterModel(modelName, skinName) ) + { + Q_strncpyz(skinName, "red", MAX_QPATH); + return qfalse; + } + else + {//need to set it to red + int len = strlen( skinName ); + if ( len < 3 ) + {//too short to be "red" + Q_strcat(skinName, MAX_QPATH, "_red"); + } + else + { + char *start = &skinName[len-3]; + if ( Q_strncmp( "red", start, 3 ) != 0 ) + {//doesn't already end in "red" + if ( len+4 >= MAX_QPATH ) + {//too big to append "_red" + Q_strncpyz(skinName, "red", MAX_QPATH); + return qfalse; + } + else + { + Q_strcat(skinName, MAX_QPATH, "_red"); + } + } + } + //if file does not exist, set to "red" + if ( !BG_FileExists( va( "models/players/%s/model_%s.skin", modelName, skinName ) ) ) + { + Q_strncpyz(skinName, "red", MAX_QPATH); + } + return qfalse; + } + } + + } + else if (team == TEAM_BLUE) + { + if ( Q_stricmp( "blue", skinName ) != 0 ) + { + if ( Q_stricmp( "red", skinName ) == 0 + || Q_stricmp( "default", skinName ) == 0 + || strchr(skinName, '|')//a multi-skin playerModel + || !BG_IsValidCharacterModel(modelName, skinName) ) + { + Q_strncpyz(skinName, "blue", MAX_QPATH); + return qfalse; + } + else + {//need to set it to blue + int len = strlen( skinName ); + if ( len < 4 ) + {//too short to be "blue" + Q_strcat(skinName, MAX_QPATH, "_blue"); + } + else + { + char *start = &skinName[len-4]; + if ( Q_strncmp( "blue", start, 4 ) != 0 ) + {//doesn't already end in "blue" + if ( len+5 >= MAX_QPATH ) + {//too big to append "_blue" + Q_strncpyz(skinName, "blue", MAX_QPATH); + return qfalse; + } + else + { + Q_strcat(skinName, MAX_QPATH, "_blue"); + } + } + } + //if file does not exist, set to "blue" + if ( !BG_FileExists( va( "models/players/%s/model_%s.skin", modelName, skinName ) ) ) + { + Q_strncpyz(skinName, "blue", MAX_QPATH); + } + return qfalse; + } + } + } + return qtrue; +} + +/* +======================== +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 ); + } + + s->trickedentindex = ps->fd.forceMindtrickTargetIndex; + s->trickedentindex2 = ps->fd.forceMindtrickTargetIndex2; + s->trickedentindex3 = ps->fd.forceMindtrickTargetIndex3; + s->trickedentindex4 = ps->fd.forceMindtrickTargetIndex4; + + s->forceFrame = ps->saberLockFrame; + + s->emplacedOwner = ps->electrifyTime; + + s->speed = ps->speed; + + s->genericenemyindex = ps->genericEnemyIndex; + + s->activeForcePass = ps->activeForcePass; + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + + s->legsFlip = ps->legsFlip; + s->torsoFlip = ps->torsoFlip; + + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + s->eFlags2 = ps->eFlags2; + + s->saberInFlight = ps->saberInFlight; + s->saberEntityNum = ps->saberEntityNum; + s->saberMove = ps->saberMove; + s->forcePowersActive = ps->fd.forcePowersActive; + + if (ps->duelInProgress) + { + s->bolt1 = 1; + } + else + { + s->bolt1 = 0; + } + + s->otherEntityNum2 = ps->emplacedIndex; + + s->saberHolstered = ps->saberHolstered; + + if (ps->genericEnemyIndex != -1) + { + s->eFlags |= EF_SEEKERDRONE; + } + + 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; + } + } + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + + //NOT INCLUDED IN ENTITYSTATETOPLAYERSTATE: + s->modelindex2 = ps->weaponstate; + s->constantLight = ps->weaponChargeTime; + + VectorCopy(ps->lastHitLoc, s->origin2); + + s->isJediMaster = ps->isJediMaster; + + s->time2 = ps->holocronBits; + + s->fireflag = ps->fd.saberAnimLevel; + + s->heldByClient = ps->heldByClient; + s->ragAttach = ps->ragAttach; + + s->iModelScale = ps->iModelScale; + + s->brokenLimbs = ps->brokenLimbs; + + s->hasLookTarget = ps->hasLookTarget; + s->lookTarget = ps->lookTarget; + + s->customRGBA[0] = ps->customRGBA[0]; + s->customRGBA[1] = ps->customRGBA[1]; + s->customRGBA[2] = ps->customRGBA[2]; + s->customRGBA[3] = ps->customRGBA[3]; + + s->m_iVehicleNum = ps->m_iVehicleNum; +} + +/* +======================== +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 ); + } + + s->trickedentindex = ps->fd.forceMindtrickTargetIndex; + s->trickedentindex2 = ps->fd.forceMindtrickTargetIndex2; + s->trickedentindex3 = ps->fd.forceMindtrickTargetIndex3; + s->trickedentindex4 = ps->fd.forceMindtrickTargetIndex4; + + s->forceFrame = ps->saberLockFrame; + + s->emplacedOwner = ps->electrifyTime; + + s->speed = ps->speed; + + s->genericenemyindex = ps->genericEnemyIndex; + + s->activeForcePass = ps->activeForcePass; + + s->angles2[YAW] = ps->movementDir; + s->legsAnim = ps->legsAnim; + s->torsoAnim = ps->torsoAnim; + + s->legsFlip = ps->legsFlip; + s->torsoFlip = ps->torsoFlip; + + s->clientNum = ps->clientNum; // ET_PLAYER looks here instead of at number + // so corpses can also reference the proper config + s->eFlags = ps->eFlags; + s->eFlags2 = ps->eFlags2; + + s->saberInFlight = ps->saberInFlight; + s->saberEntityNum = ps->saberEntityNum; + s->saberMove = ps->saberMove; + s->forcePowersActive = ps->fd.forcePowersActive; + + if (ps->duelInProgress) + { + s->bolt1 = 1; + } + else + { + s->bolt1 = 0; + } + + s->otherEntityNum2 = ps->emplacedIndex; + + s->saberHolstered = ps->saberHolstered; + + if (ps->genericEnemyIndex != -1) + { + s->eFlags |= EF_SEEKERDRONE; + } + + 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; + } + } + + s->loopSound = ps->loopSound; + s->generic1 = ps->generic1; + + //NOT INCLUDED IN ENTITYSTATETOPLAYERSTATE: + s->modelindex2 = ps->weaponstate; + s->constantLight = ps->weaponChargeTime; + + VectorCopy(ps->lastHitLoc, s->origin2); + + s->isJediMaster = ps->isJediMaster; + + s->time2 = ps->holocronBits; + + s->fireflag = ps->fd.saberAnimLevel; + + s->heldByClient = ps->heldByClient; + s->ragAttach = ps->ragAttach; + + s->iModelScale = ps->iModelScale; + + s->brokenLimbs = ps->brokenLimbs; + + s->hasLookTarget = ps->hasLookTarget; + s->lookTarget = ps->lookTarget; + + s->customRGBA[0] = ps->customRGBA[0]; + s->customRGBA[1] = ps->customRGBA[1]; + s->customRGBA[2] = ps->customRGBA[2]; + s->customRGBA[3] = ps->customRGBA[3]; + + s->m_iVehicleNum = ps->m_iVehicleNum; +} + +/* +============================================================================= + +PLAYER ANGLES + +============================================================================= +*/ + +//perform the appropriate model precache routine +#ifdef QAGAME //game +extern int trap_G2API_InitGhoul2Model(void **ghoul2Ptr, const char *fileName, int modelIndex, qhandle_t customSkin, + qhandle_t customShader, int modelFlags, int lodBias); //exists on game/cgame/ui, only used on game +extern void trap_G2API_CleanGhoul2Models(void **ghoul2Ptr); //exists on game/cgame/ui, only used on game +#else //cgame/ui +extern qhandle_t trap_R_RegisterModel( const char *name ); //exists on cgame/ui +#endif +//game/cgame/ui +extern qhandle_t trap_R_RegisterSkin( const char *name ); //exists on game/cgame/ui + +int BG_ModelCache(const char *modelName, const char *skinName) +{ +#ifdef QAGAME + void *g2 = NULL; + + if (skinName && skinName[0]) + { + trap_R_RegisterSkin(skinName); + } + + //I could hook up a precache ghoul2 function, but oh well, this works + trap_G2API_InitGhoul2Model(&g2, modelName, 0, 0, 0, 0, 0); + if (g2) + { //now get rid of it + trap_G2API_CleanGhoul2Models(&g2); + } + return 0; +#else + if (skinName && skinName[0]) + { + trap_R_RegisterSkin(skinName); + } + return trap_R_RegisterModel(modelName); +#endif +} + +#ifdef _XBOX // Hacky BG_Alloc replacement + +// This file claims to be stateless. Yeah, right. Regardless, I'm not setting +// aside 5.5 MB of static buffers for this crap. Let's use Z_Malloc. Of course, +// we still need to deal with the fact that any code using BG_Malloc is almost +// certainly leaking memory like a sieve. + +// Dave addendum - TAG_BG_ALLOC is entirely freed when the level starts. +void *BG_Alloc ( int size ) +{ + return Z_Malloc(size, TAG_BG_ALLOC, qfalse, 4); +} + +void *BG_AllocUnaligned ( int size ) +{ + // Ignore the unaligned hint, this function isn't called anyway + return Z_Malloc(size, TAG_BG_ALLOC, qfalse, 4); +} + +// Because the interface to BG_TempAlloc/BG_TempFree is brain-dead, we need +// to remember our last few temporary allocations performed. +#define MAX_TEMP_ALLOCS 3 +static void *tempAllocPointers[MAX_TEMP_ALLOCS] = { 0 }; +static int tempAllocSizes[MAX_TEMP_ALLOCS] = { 0 }; + +void *BG_TempAlloc( int size ) +{ + int i; + + // Do we have a free spot? + for (i = 0; i < MAX_TEMP_ALLOCS; ++i) + if (!tempAllocPointers[i]) + break; + + if (i == MAX_TEMP_ALLOCS) + { + assert(!"No space for TempAlloc -> Increase MAX_TEMP_ALLOCS"); + return NULL; + } + + tempAllocPointers[i] = Z_Malloc(size, TAG_TEMP_WORKSPACE, qfalse, 4); + tempAllocSizes[i] = size; + + return tempAllocPointers[i]; +} + +void BG_TempFree( int size ) +{ + int i; + + // Find the allocation + for (i = MAX_TEMP_ALLOCS - 1; i >= 0; --i) + if (tempAllocPointers[i] && (tempAllocSizes[i] == size)) + break; + + if (i < 0) + { + assert(!"BG_TempFree doesn't match a call to BG_TempAlloc"); + return; + } + + Z_Free(tempAllocPointers[i]); + tempAllocPointers[i] = 0; + tempAllocSizes[i] = 0; + + return; +} + +char *BG_StringAlloc ( const char *source ) +{ + char *dest; + + dest = (char *) BG_Alloc ( strlen ( source ) + 1 ); + strcpy ( dest, source ); + return dest; +} + +qboolean BG_OutOfMemory ( void ) +{ + // Never called + return qfalse; +} + +#else // _XBOX + +#ifdef QAGAME +#define MAX_POOL_SIZE 3000000 //1024000 +#elif defined CGAME //don't need as much for cgame stuff. 2mb will be fine. +#define MAX_POOL_SIZE 2048000 +#else //And for the ui the only thing we'll be using this for anyway is allocating anim data for g2 menu models +#define MAX_POOL_SIZE 512000 +#endif + +//I am using this for all the stuff like NPC client structures on server/client and +//non-humanoid animations as well until/if I can get dynamic memory working properly +//with casted datatypes, which is why it is so large. + + +static char bg_pool[MAX_POOL_SIZE]; +static int bg_poolSize = 0; +static int bg_poolTail = MAX_POOL_SIZE; + +void *BG_Alloc ( int size ) +{ + bg_poolSize = ((bg_poolSize + 0x00000003) & 0xfffffffc); + + if (bg_poolSize + size > bg_poolTail) + { + Com_Error( ERR_DROP, "BG_Alloc: buffer exceeded tail (%d > %d)", bg_poolSize + size, bg_poolTail); + return 0; + } + + bg_poolSize += size; + + return &bg_pool[bg_poolSize-size]; +} + +void *BG_AllocUnaligned ( int size ) +{ + if (bg_poolSize + size > bg_poolTail) + { + Com_Error( ERR_DROP, "BG_AllocUnaligned: buffer exceeded tail (%d > %d)", bg_poolSize + size, bg_poolTail); + return 0; + } + + bg_poolSize += size; + + return &bg_pool[bg_poolSize-size]; +} + +void *BG_TempAlloc( int size ) +{ + size = ((size + 0x00000003) & 0xfffffffc); + + if (bg_poolTail - size < bg_poolSize) + { + Com_Error( ERR_DROP, "BG_TempAlloc: buffer exceeded head (%d > %d)", bg_poolTail - size, bg_poolSize); + return 0; + } + + bg_poolTail -= size; + + return &bg_pool[bg_poolTail]; +} + +void BG_TempFree( int size ) +{ + size = ((size + 0x00000003) & 0xfffffffc); + + if (bg_poolTail+size > MAX_POOL_SIZE) + { + Com_Error( ERR_DROP, "BG_TempFree: tail greater than size (%d > %d)", bg_poolTail+size, MAX_POOL_SIZE ); + } + + bg_poolTail += size; +} + +char *BG_StringAlloc ( const char *source ) +{ + char *dest; + + dest = BG_Alloc ( strlen ( source ) + 1 ); + strcpy ( dest, source ); + return dest; +} + +qboolean BG_OutOfMemory ( void ) +{ + return bg_poolSize >= MAX_POOL_SIZE; +} + +#endif // _XBOX && QAGAME + +//#include "../namespace_end.h" diff --git a/code/game/bg_panimate.c b/code/game/bg_panimate.c new file mode 100644 index 0000000..5ad13cc --- /dev/null +++ b/code/game/bg_panimate.c @@ -0,0 +1,3037 @@ +// BG_PAnimate.c + +#include "q_shared.h" +#include "bg_public.h" +#include "bg_strap.h" +#include "bg_local.h" +#include "anims.h" +#include "../cgame/animtable.h" +#ifdef QAGAME +#include "g_local.h" +#endif + +#ifdef CGAME +#include "../namespace_begin.h" +extern sfxHandle_t trap_S_RegisterSound( const char *sample); +extern int trap_FX_RegisterEffect( const char *file); +#include "../namespace_end.h" +#endif + +extern saberInfo_t *BG_MySaber( int clientNum, int saberNum ); +/* +============================================================================== +BEGIN: Animation utility functions (sequence checking) +============================================================================== +*/ +//Called regardless of pm validity: + +// VVFIXME - Most of these functions are totally stateless and stupid. Don't +// need multiple copies of this, but it's much easier (and less likely to +// break in the future) if I keep separate namespace versions now. +#include "../namespace_begin.h" + +qboolean BG_SaberStanceAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_STAND1://not really a saberstance anim, actually... "saber off" stance + case BOTH_STAND2://single-saber, medium style + case BOTH_SABERFAST_STANCE://single-saber, fast style + case BOTH_SABERSLOW_STANCE://single-saber, strong style + case BOTH_SABERSTAFF_STANCE://saber staff style + case BOTH_SABERDUAL_STANCE://dual saber style + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_CrouchAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_SIT1: //# Normal chair sit. + case BOTH_SIT2: //# Lotus position. + case BOTH_SIT3: //# Sitting in tired position: elbows on knees + case BOTH_CROUCH1: //# Transition from standing to crouch + case BOTH_CROUCH1IDLE: //# Crouching idle + case BOTH_CROUCH1WALK: //# Walking while crouched + case BOTH_CROUCH1WALKBACK: //# Walking while crouched + case BOTH_CROUCH2TOSTAND1: //# going from crouch2 to stand1 + case BOTH_CROUCH3: //# Desann crouching down to Kyle (cin 9) + case BOTH_KNEES1: //# Tavion on her knees + case BOTH_CROUCHATTACKBACK1://FIXME: not if in middle of anim? + case BOTH_ROLL_STAB: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InSpecialJump( int anim ) +{ + switch ( (anim) ) + { + case BOTH_WALL_RUN_RIGHT: + case BOTH_WALL_RUN_RIGHT_STOP: + case BOTH_WALL_RUN_RIGHT_FLIP: + case BOTH_WALL_RUN_LEFT: + case BOTH_WALL_RUN_LEFT_STOP: + case BOTH_WALL_RUN_LEFT_FLIP: + case BOTH_WALL_FLIP_RIGHT: + case BOTH_WALL_FLIP_LEFT: + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + case BOTH_WALL_FLIP_BACK1: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_FORCELEAP2_T__B_: + case BOTH_JUMPFLIPSLASHDOWN1://# + case BOTH_JUMPFLIPSTABDOWN://# + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + + case BOTH_FORCELONGLEAP_START: + case BOTH_FORCELONGLEAP_ATTACK: + case BOTH_FORCEWALLRUNFLIP_START: + case BOTH_FORCEWALLRUNFLIP_END: + case BOTH_FORCEWALLRUNFLIP_ALT: + case BOTH_FLIP_ATTACK7: + case BOTH_FLIP_HOLD7: + case BOTH_FLIP_LAND: + case BOTH_A7_SOULCAL: + return qtrue; + } + if ( BG_InReboundJump( anim ) ) + { + return qtrue; + } + if ( BG_InReboundHold( anim ) ) + { + return qtrue; + } + if ( BG_InReboundRelease( anim ) ) + { + return qtrue; + } + if ( BG_InBackFlip( anim ) ) + { + return qtrue; + } + return qfalse; +} + +qboolean BG_InSaberStandAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_SABERFAST_STANCE: + case BOTH_STAND2: + case BOTH_SABERSLOW_STANCE: + case BOTH_SABERDUAL_STANCE: + case BOTH_SABERSTAFF_STANCE: + return qtrue; + default: + return qfalse; + } +} + +qboolean BG_InReboundJump( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEWALLREBOUND_FORWARD: + case BOTH_FORCEWALLREBOUND_LEFT: + case BOTH_FORCEWALLREBOUND_BACK: + case BOTH_FORCEWALLREBOUND_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InReboundHold( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEWALLHOLD_FORWARD: + case BOTH_FORCEWALLHOLD_LEFT: + case BOTH_FORCEWALLHOLD_BACK: + case BOTH_FORCEWALLHOLD_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InReboundRelease( int anim ) +{ + switch ( anim ) + { + case BOTH_FORCEWALLRELEASE_FORWARD: + case BOTH_FORCEWALLRELEASE_LEFT: + case BOTH_FORCEWALLRELEASE_BACK: + case BOTH_FORCEWALLRELEASE_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InBackFlip( int anim ) +{ + switch ( anim ) + { + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_DirectFlippingAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_FLIP_F: //# Flip forward + case BOTH_FLIP_B: //# Flip backwards + case BOTH_FLIP_L: //# Flip left + case BOTH_FLIP_R: //# Flip right + return qtrue; + break; + } + + return qfalse; +} + +qboolean BG_SaberInAttackPure( int move ) +{ + if ( move >= LS_A_TL2BR && move <= LS_A_T2B ) + { + return qtrue; + } + return qfalse; +} +qboolean BG_SaberInAttack( int move ) +{ + if ( move >= LS_A_TL2BR && move <= LS_A_T2B ) + { + return qtrue; + } + switch ( move ) + { + case LS_A_BACK: + case LS_A_BACK_CR: + case LS_A_BACKSTAB: + case LS_ROLL_STAB: + case LS_A_LUNGE: + case LS_A_JUMP_T__B_: + case LS_A_FLIP_STAB: + case LS_A_FLIP_SLASH: + case LS_JUMPATTACK_DUAL: + case LS_JUMPATTACK_ARIAL_LEFT: + case LS_JUMPATTACK_ARIAL_RIGHT: + case LS_JUMPATTACK_CART_LEFT: + case LS_JUMPATTACK_CART_RIGHT: + case LS_JUMPATTACK_STAFF_LEFT: + case LS_JUMPATTACK_STAFF_RIGHT: + case LS_BUTTERFLY_LEFT: + case LS_BUTTERFLY_RIGHT: + case LS_A_BACKFLIP_ATK: + case LS_SPINATTACK_DUAL: + case LS_SPINATTACK: + case LS_LEAP_ATTACK: + case LS_SWOOP_ATTACK_RIGHT: + case LS_SWOOP_ATTACK_LEFT: + case LS_TAUNTAUN_ATTACK_RIGHT: + case LS_TAUNTAUN_ATTACK_LEFT: + case LS_KICK_F: + case LS_KICK_B: + case LS_KICK_R: + case LS_KICK_L: + case LS_KICK_S: + case LS_KICK_BF: + case LS_KICK_RL: + case LS_KICK_F_AIR: + case LS_KICK_B_AIR: + case LS_KICK_R_AIR: + case LS_KICK_L_AIR: + case LS_STABDOWN: + case LS_STABDOWN_STAFF: + case LS_STABDOWN_DUAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_UPSIDE_DOWN_ATTACK: + case LS_PULL_ATTACK_STAB: + case LS_PULL_ATTACK_SWING: + case LS_SPINATTACK_ALORA: + case LS_DUAL_FB: + case LS_DUAL_LR: + case LS_HILT_BASH: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_SaberInKata( int saberMove ) +{ + switch ( saberMove ) + { + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + return qtrue; + } + return qfalse; +} + +qboolean BG_InKataAnim(int anim) +{ + switch (anim) + { + case BOTH_A6_SABERPROTECT: + case BOTH_A7_SOULCAL: + case BOTH_A1_SPECIAL: + case BOTH_A2_SPECIAL: + case BOTH_A3_SPECIAL: + return qtrue; + } + return qfalse; +} + +qboolean BG_SaberInSpecial( int move ) +{ + switch( move ) + { + case LS_A_BACK: + case LS_A_BACK_CR: + case LS_A_BACKSTAB: + case LS_ROLL_STAB: + case LS_A_LUNGE: + case LS_A_JUMP_T__B_: + case LS_A_FLIP_STAB: + case LS_A_FLIP_SLASH: + case LS_JUMPATTACK_DUAL: + case LS_JUMPATTACK_ARIAL_LEFT: + case LS_JUMPATTACK_ARIAL_RIGHT: + case LS_JUMPATTACK_CART_LEFT: + case LS_JUMPATTACK_CART_RIGHT: + case LS_JUMPATTACK_STAFF_LEFT: + case LS_JUMPATTACK_STAFF_RIGHT: + case LS_BUTTERFLY_LEFT: + case LS_BUTTERFLY_RIGHT: + case LS_A_BACKFLIP_ATK: + case LS_SPINATTACK_DUAL: + case LS_SPINATTACK: + case LS_LEAP_ATTACK: + case LS_SWOOP_ATTACK_RIGHT: + case LS_SWOOP_ATTACK_LEFT: + case LS_TAUNTAUN_ATTACK_RIGHT: + case LS_TAUNTAUN_ATTACK_LEFT: + case LS_KICK_F: + case LS_KICK_B: + case LS_KICK_R: + case LS_KICK_L: + case LS_KICK_S: + case LS_KICK_BF: + case LS_KICK_RL: + case LS_KICK_F_AIR: + case LS_KICK_B_AIR: + case LS_KICK_R_AIR: + case LS_KICK_L_AIR: + case LS_STABDOWN: + case LS_STABDOWN_STAFF: + case LS_STABDOWN_DUAL: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_UPSIDE_DOWN_ATTACK: + case LS_PULL_ATTACK_STAB: + case LS_PULL_ATTACK_SWING: + case LS_SPINATTACK_ALORA: + case LS_DUAL_FB: + case LS_DUAL_LR: + case LS_HILT_BASH: + return qtrue; + } + return qfalse; +} + +qboolean BG_KickMove( int move ) +{ + switch( move ) + { + case LS_KICK_F: + case LS_KICK_B: + case LS_KICK_R: + case LS_KICK_L: + case LS_KICK_S: + case LS_KICK_BF: + case LS_KICK_RL: + case LS_KICK_F_AIR: + case LS_KICK_B_AIR: + case LS_KICK_R_AIR: + case LS_KICK_L_AIR: + case LS_HILT_BASH: + return qtrue; + } + return qfalse; +} + +qboolean BG_SaberInIdle( int move ) +{ + switch ( move ) + { + case LS_NONE: + case LS_READY: + case LS_DRAW: + case LS_PUTAWAY: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InExtraDefenseSaberMove( int move ) +{ + switch ( move ) + { + case LS_SPINATTACK_DUAL: + case LS_SPINATTACK: + case LS_DUAL_SPIN_PROTECT: + case LS_STAFF_SOULCAL: + case LS_A1_SPECIAL: + case LS_A2_SPECIAL: + case LS_A3_SPECIAL: + case LS_JUMPATTACK_DUAL: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_FlippingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_FLIP_F: //# Flip forward + case BOTH_FLIP_B: //# Flip backwards + case BOTH_FLIP_L: //# Flip left + case BOTH_FLIP_R: //# Flip right + case BOTH_WALL_RUN_RIGHT_FLIP: + case BOTH_WALL_RUN_LEFT_FLIP: + case BOTH_WALL_FLIP_RIGHT: + case BOTH_WALL_FLIP_LEFT: + case BOTH_FLIP_BACK1: + case BOTH_FLIP_BACK2: + case BOTH_FLIP_BACK3: + case BOTH_WALL_FLIP_BACK1: + //Not really flips, but... + case BOTH_WALL_RUN_RIGHT: + case BOTH_WALL_RUN_LEFT: + case BOTH_WALL_RUN_RIGHT_STOP: + case BOTH_WALL_RUN_LEFT_STOP: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + // + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + case BOTH_JUMPFLIPSLASHDOWN1: + case BOTH_JUMPFLIPSTABDOWN: + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + //JKA + case BOTH_FORCEWALLRUNFLIP_END: + case BOTH_FORCEWALLRUNFLIP_ALT: + case BOTH_FLIP_ATTACK7: + case BOTH_A7_SOULCAL: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_SpinningSaberAnim( int anim ) +{ + switch ( anim ) + { + //level 1 - FIXME: level 1 will have *no* spins + case BOTH_T1_BR_BL: + case BOTH_T1__R__L: + case BOTH_T1__R_BL: + case BOTH_T1_TR_BL: + case BOTH_T1_BR_TL: + case BOTH_T1_BR__L: + case BOTH_T1_TL_BR: + case BOTH_T1__L_BR: + case BOTH_T1__L__R: + case BOTH_T1_BL_BR: + case BOTH_T1_BL__R: + case BOTH_T1_BL_TR: + //level 2 + case BOTH_T2_BR__L: + case BOTH_T2_BR_BL: + case BOTH_T2__R_BL: + case BOTH_T2__L_BR: + case BOTH_T2_BL_BR: + case BOTH_T2_BL__R: + //level 3 + case BOTH_T3_BR__L: + case BOTH_T3_BR_BL: + case BOTH_T3__R_BL: + case BOTH_T3__L_BR: + case BOTH_T3_BL_BR: + case BOTH_T3_BL__R: + //level 4 + case BOTH_T4_BR__L: + case BOTH_T4_BR_BL: + case BOTH_T4__R_BL: + case BOTH_T4__L_BR: + case BOTH_T4_BL_BR: + case BOTH_T4_BL__R: + //level 5 + case BOTH_T5_BR_BL: + case BOTH_T5__R__L: + case BOTH_T5__R_BL: + case BOTH_T5_TR_BL: + case BOTH_T5_BR_TL: + case BOTH_T5_BR__L: + case BOTH_T5_TL_BR: + case BOTH_T5__L_BR: + case BOTH_T5__L__R: + case BOTH_T5_BL_BR: + case BOTH_T5_BL__R: + case BOTH_T5_BL_TR: + //level 6 + case BOTH_T6_BR_TL: + case BOTH_T6__R_TL: + case BOTH_T6__R__L: + case BOTH_T6__R_BL: + case BOTH_T6_TR_TL: + case BOTH_T6_TR__L: + case BOTH_T6_TR_BL: + case BOTH_T6_T__TL: + case BOTH_T6_T__BL: + case BOTH_T6_TL_BR: + case BOTH_T6__L_BR: + case BOTH_T6__L__R: + case BOTH_T6_TL__R: + case BOTH_T6_TL_TR: + case BOTH_T6__L_TR: + case BOTH_T6__L_T_: + case BOTH_T6_BL_T_: + case BOTH_T6_BR__L: + case BOTH_T6_BR_BL: + case BOTH_T6_BL_BR: + case BOTH_T6_BL__R: + case BOTH_T6_BL_TR: + //level 7 + case BOTH_T7_BR_TL: + case BOTH_T7_BR__L: + case BOTH_T7_BR_BL: + case BOTH_T7__R__L: + case BOTH_T7__R_BL: + case BOTH_T7_TR__L: + case BOTH_T7_T___R: + case BOTH_T7_TL_BR: + case BOTH_T7__L_BR: + case BOTH_T7__L__R: + case BOTH_T7_BL_BR: + case BOTH_T7_BL__R: + case BOTH_T7_BL_TR: + case BOTH_T7_TL_TR: + case BOTH_T7_T__BR: + case BOTH_T7__L_TR: + case BOTH_V7_BL_S7: + //special + //case BOTH_A2_STABBACK1: + case BOTH_ATTACK_BACK: + case BOTH_CROUCHATTACKBACK1: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_JUMPFLIPSLASHDOWN1: + case BOTH_JUMPFLIPSTABDOWN: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_SaberInSpecialAttack( int anim ) +{ + switch ( anim ) + { + case BOTH_A2_STABBACK1: + case BOTH_ATTACK_BACK: + case BOTH_CROUCHATTACKBACK1: + case BOTH_ROLL_STAB: + case BOTH_BUTTERFLY_LEFT: + case BOTH_BUTTERFLY_RIGHT: + case BOTH_BUTTERFLY_FL1: + case BOTH_BUTTERFLY_FR1: + case BOTH_FJSS_TR_BL: + case BOTH_FJSS_TL_BR: + case BOTH_LUNGE2_B__T_: + case BOTH_FORCELEAP2_T__B_: + case BOTH_JUMPFLIPSLASHDOWN1://# + case BOTH_JUMPFLIPSTABDOWN://# + case BOTH_JUMPATTACK6: + case BOTH_JUMPATTACK7: + case BOTH_SPINATTACK6: + case BOTH_SPINATTACK7: + case BOTH_FORCELONGLEAP_ATTACK: + case BOTH_VS_ATR_S: + case BOTH_VS_ATL_S: + case BOTH_VT_ATR_S: + case BOTH_VT_ATL_S: + case BOTH_A7_KICK_F: + case BOTH_A7_KICK_B: + case BOTH_A7_KICK_R: + case BOTH_A7_KICK_L: + case BOTH_A7_KICK_S: + case BOTH_A7_KICK_BF: + case BOTH_A7_KICK_RL: + case BOTH_A7_KICK_F_AIR: + case BOTH_A7_KICK_B_AIR: + case BOTH_A7_KICK_R_AIR: + case BOTH_A7_KICK_L_AIR: + case BOTH_STABDOWN: + case BOTH_STABDOWN_STAFF: + case BOTH_STABDOWN_DUAL: + case BOTH_A6_SABERPROTECT: + case BOTH_A7_SOULCAL: + case BOTH_A1_SPECIAL: + case BOTH_A2_SPECIAL: + case BOTH_A3_SPECIAL: + case BOTH_FLIP_ATTACK7: + case BOTH_PULL_IMPALE_STAB: + case BOTH_PULL_IMPALE_SWING: + case BOTH_ALORA_SPIN_SLASH: + case BOTH_A6_FB: + case BOTH_A6_LR: + case BOTH_A7_HILT: + return qtrue; + } + return qfalse; +} + +qboolean BG_KickingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_A7_KICK_F: + case BOTH_A7_KICK_B: + case BOTH_A7_KICK_R: + case BOTH_A7_KICK_L: + case BOTH_A7_KICK_S: + case BOTH_A7_KICK_BF: + case BOTH_A7_KICK_RL: + case BOTH_A7_KICK_F_AIR: + case BOTH_A7_KICK_B_AIR: + case BOTH_A7_KICK_R_AIR: + case BOTH_A7_KICK_L_AIR: + case BOTH_A7_HILT: + //NOT kicks, but do kick traces anyway + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + return qtrue; + break; + } + return qfalse; +} + +int BG_InGrappleMove(int anim) +{ + switch (anim) + { + case BOTH_KYLE_GRAB: + case BOTH_KYLE_MISS: + return 1; //grabbing at someone + case BOTH_KYLE_PA_1: + case BOTH_KYLE_PA_2: + return 2; //beating the shit out of someone + case BOTH_PLAYER_PA_1: + case BOTH_PLAYER_PA_2: + case BOTH_PLAYER_PA_FLY: + return 3; //getting the shit beaten out of you + break; + } + + return 0; +} + +int BG_BrokenParryForAttack( int move ) +{ + //Our attack was knocked away by a knockaway parry + //FIXME: need actual anims for this + //FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center + switch ( saberMoveData[move].startQuad ) + { + case Q_B: + return LS_V1_B_; + break; + case Q_BR: + return LS_V1_BR; + break; + case Q_R: + return LS_V1__R; + break; + case Q_TR: + return LS_V1_TR; + break; + case Q_T: + return LS_V1_T_; + break; + case Q_TL: + return LS_V1_TL; + break; + case Q_L: + return LS_V1__L; + break; + case Q_BL: + return LS_V1_BL; + break; + } + return LS_NONE; +} + +int BG_BrokenParryForParry( int move ) +{ + //FIXME: need actual anims for this + //FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center + switch ( move ) + { + case LS_PARRY_UP: + //Hmm... since we don't know what dir the hit came from, randomly pick knock down or knock back + if ( Q_irand( 0, 1 ) ) + { + return LS_H1_B_; + } + else + { + return LS_H1_T_; + } + break; + case LS_PARRY_UR: + return LS_H1_TR; + break; + case LS_PARRY_UL: + return LS_H1_TL; + break; + case LS_PARRY_LR: + return LS_H1_BR; + break; + case LS_PARRY_LL: + return LS_H1_BL; + break; + case LS_READY: + return LS_H1_B_;//??? + break; + } + return LS_NONE; +} + +int BG_KnockawayForParry( int move ) +{ + //FIXME: need actual anims for this + //FIXME: need to know which side of the saber was hit! For now, we presume the saber gets knocked away from the center + switch ( move ) + { + case BLOCKED_TOP://LS_PARRY_UP: + return LS_K1_T_;//push up + break; + case BLOCKED_UPPER_RIGHT://LS_PARRY_UR: + default://case LS_READY: + return LS_K1_TR;//push up, slightly to right + break; + case BLOCKED_UPPER_LEFT://LS_PARRY_UL: + return LS_K1_TL;//push up and to left + break; + case BLOCKED_LOWER_RIGHT://LS_PARRY_LR: + return LS_K1_BR;//push down and to left + break; + case BLOCKED_LOWER_LEFT://LS_PARRY_LL: + return LS_K1_BL;//push down and to right + break; + } + //return LS_NONE; +} + +qboolean BG_InRoll( playerState_t *ps, int anim ) +{ + switch ( (anim) ) + { + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + if ( ps->legsTimer > 0 ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean BG_InSpecialDeathAnim( int anim ) +{ + switch( anim ) + { + case BOTH_DEATH_ROLL: //# Death anim from a roll + case BOTH_DEATH_FLIP: //# Death anim from a flip + case BOTH_DEATH_SPIN_90_R: //# Death anim when facing 90 degrees right + case BOTH_DEATH_SPIN_90_L: //# Death anim when facing 90 degrees left + case BOTH_DEATH_SPIN_180: //# Death anim when facing backwards + case BOTH_DEATH_LYING_UP: //# Death anim when lying on back + case BOTH_DEATH_LYING_DN: //# Death anim when lying on front + case BOTH_DEATH_FALLING_DN: //# Death anim when falling on face + case BOTH_DEATH_FALLING_UP: //# Death anim when falling on back + case BOTH_DEATH_CROUCHED: //# Death anim when crouched + return qtrue; + break; + default: + return qfalse; + break; + } +} + +qboolean BG_InDeathAnim ( int anim ) +{//Purposely does not cover stumbledeath and falldeath... + switch( anim ) + { + case BOTH_DEATH1: //# First Death anim + case BOTH_DEATH2: //# Second Death anim + case BOTH_DEATH3: //# Third Death anim + case BOTH_DEATH4: //# Fourth Death anim + case BOTH_DEATH5: //# Fifth Death anim + case BOTH_DEATH6: //# Sixth Death anim + case BOTH_DEATH7: //# Seventh Death anim + case BOTH_DEATH8: //# + case BOTH_DEATH9: //# + case BOTH_DEATH10: //# + case BOTH_DEATH11: //# + case BOTH_DEATH12: //# + case BOTH_DEATH13: //# + case BOTH_DEATH14: //# + case BOTH_DEATH14_UNGRIP: //# Desann's end death (cin #35) + case BOTH_DEATH14_SITUP: //# Tavion sitting up after having been thrown (cin #23) + case BOTH_DEATH15: //# + case BOTH_DEATH16: //# + case BOTH_DEATH17: //# + case BOTH_DEATH18: //# + case BOTH_DEATH19: //# + case BOTH_DEATH20: //# + case BOTH_DEATH21: //# + case BOTH_DEATH22: //# + case BOTH_DEATH23: //# + case BOTH_DEATH24: //# + case BOTH_DEATH25: //# + + case BOTH_DEATHFORWARD1: //# First Death in which they get thrown forward + case BOTH_DEATHFORWARD2: //# Second Death in which they get thrown forward + case BOTH_DEATHFORWARD3: //# Tavion's falling in cin# 23 + case BOTH_DEATHBACKWARD1: //# First Death in which they get thrown backward + case BOTH_DEATHBACKWARD2: //# Second Death in which they get thrown backward + + case BOTH_DEATH1IDLE: //# Idle while close to death + case BOTH_LYINGDEATH1: //# Death to play when killed lying down + case BOTH_STUMBLEDEATH1: //# Stumble forward and fall face first death + case BOTH_FALLDEATH1: //# Fall forward off a high cliff and splat death - start + case BOTH_FALLDEATH1INAIR: //# Fall forward off a high cliff and splat death - loop + case BOTH_FALLDEATH1LAND: //# Fall forward off a high cliff and splat death - hit bottom + //# #sep case BOTH_ DEAD POSES # Should be last frame of corresponding previous anims + case BOTH_DEAD1: //# First Death finished pose + case BOTH_DEAD2: //# Second Death finished pose + case BOTH_DEAD3: //# Third Death finished pose + case BOTH_DEAD4: //# Fourth Death finished pose + case BOTH_DEAD5: //# Fifth Death finished pose + case BOTH_DEAD6: //# Sixth Death finished pose + case BOTH_DEAD7: //# Seventh Death finished pose + case BOTH_DEAD8: //# + case BOTH_DEAD9: //# + case BOTH_DEAD10: //# + case BOTH_DEAD11: //# + case BOTH_DEAD12: //# + case BOTH_DEAD13: //# + case BOTH_DEAD14: //# + case BOTH_DEAD15: //# + case BOTH_DEAD16: //# + case BOTH_DEAD17: //# + case BOTH_DEAD18: //# + case BOTH_DEAD19: //# + case BOTH_DEAD20: //# + case BOTH_DEAD21: //# + case BOTH_DEAD22: //# + case BOTH_DEAD23: //# + case BOTH_DEAD24: //# + case BOTH_DEAD25: //# + case BOTH_DEADFORWARD1: //# First thrown forward death finished pose + case BOTH_DEADFORWARD2: //# Second thrown forward death finished pose + case BOTH_DEADBACKWARD1: //# First thrown backward death finished pose + case BOTH_DEADBACKWARD2: //# Second thrown backward death finished pose + case BOTH_LYINGDEAD1: //# Killed lying down death finished pose + case BOTH_STUMBLEDEAD1: //# Stumble forward death finished pose + case BOTH_FALLDEAD1LAND: //# Fall forward and splat death finished pose + //# #sep case BOTH_ DEAD TWITCH/FLOP # React to being shot from death poses + case BOTH_DEADFLOP1: //# React to being shot from First Death finished pose + case BOTH_DEADFLOP2: //# React to being shot from Second Death finished pose + case BOTH_DISMEMBER_HEAD1: //# + case BOTH_DISMEMBER_TORSO1: //# + case BOTH_DISMEMBER_LLEG: //# + case BOTH_DISMEMBER_RLEG: //# + case BOTH_DISMEMBER_RARM: //# + case BOTH_DISMEMBER_LARM: //# + return qtrue; + break; + default: + return BG_InSpecialDeathAnim( anim ); + break; + } +} + +qboolean BG_InKnockDownOnly( int anim ) +{ + switch ( anim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + return qtrue; + } + return qfalse; +} + +qboolean BG_InSaberLockOld( int anim ) +{ + switch ( anim ) + { + case BOTH_BF2LOCK: + case BOTH_BF1LOCK: + case BOTH_CWCIRCLELOCK: + case BOTH_CCWCIRCLELOCK: + return qtrue; + } + return qfalse; +} + +qboolean BG_InSaberLock( int anim ) +{ + switch ( anim ) + { + case BOTH_LK_S_DL_S_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_DL_T_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_ST_S_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_S_ST_T_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_S_S_S_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_S_S_T_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_DL_DL_S_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_DL_T_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_ST_S_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_DL_ST_T_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_DL_S_S_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_DL_S_T_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_ST_DL_S_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_DL_T_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_ST_S_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_ST_T_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_S_S_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_ST_S_T_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_S_S_S_L_2: + case BOTH_LK_S_S_T_L_2: + case BOTH_LK_DL_DL_S_L_2: + case BOTH_LK_DL_DL_T_L_2: + case BOTH_LK_ST_ST_S_L_2: + case BOTH_LK_ST_ST_T_L_2: + return qtrue; + break; + default: + return BG_InSaberLockOld( anim ); + break; + } + //return qfalse; +} + +//Called only where pm is valid (not all require pm, but some do): +qboolean PM_InCartwheel( int anim ) +{ + switch ( anim ) + { + case BOTH_ARIAL_LEFT: + case BOTH_ARIAL_RIGHT: + case BOTH_ARIAL_F1: + case BOTH_CARTWHEEL_LEFT: + case BOTH_CARTWHEEL_RIGHT: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InKnockDownOnGround( playerState_t *ps ) +{ + switch ( ps->legsAnim ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + case BOTH_RELEASED: + //if ( PM_AnimLength( g_entities[ps->clientNum].client->clientInfo.animFileIndex, (animNumber_t)ps->legsAnim ) - ps->legsAnimTimer > 300 ) + {//at end of fall down anim + return qtrue; + } + break; + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_GETUP_CROUCH_F1: + case BOTH_GETUP_CROUCH_B1: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + if ( BG_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->legsTimer < 500 ) + {//at beginning of getup anim + return qtrue; + } + break; + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + if ( BG_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->legsTimer < 500 ) + {//at beginning of getup anim + return qtrue; + } + break; + case BOTH_LK_DL_ST_T_SB_1_L: + if ( ps->legsTimer < 1000 ) + { + return qtrue; + } + break; + case BOTH_PLAYER_PA_3_FLY: + if ( ps->legsTimer < 300 ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean BG_StabDownAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_STABDOWN: + case BOTH_STABDOWN_STAFF: + case BOTH_STABDOWN_DUAL: + return qtrue; + } + return qfalse; +} + +int PM_SaberBounceForAttack( int move ) +{ + switch ( saberMoveData[move].startQuad ) + { + case Q_B: + case Q_BR: + return LS_B1_BR; + break; + case Q_R: + return LS_B1__R; + break; + case Q_TR: + return LS_B1_TR; + break; + case Q_T: + return LS_B1_T_; + break; + case Q_TL: + return LS_B1_TL; + break; + case Q_L: + return LS_B1__L; + break; + case Q_BL: + return LS_B1_BL; + break; + } + return LS_NONE; +} + +int PM_SaberDeflectionForQuad( int quad ) +{ + switch ( quad ) + { + case Q_B: + return LS_D1_B_; + break; + case Q_BR: + return LS_D1_BR; + break; + case Q_R: + return LS_D1__R; + break; + case Q_TR: + return LS_D1_TR; + break; + case Q_T: + return LS_D1_T_; + break; + case Q_TL: + return LS_D1_TL; + break; + case Q_L: + return LS_D1__L; + break; + case Q_BL: + return LS_D1_BL; + break; + } + return LS_NONE; +} + +qboolean PM_SaberInDeflect( int move ) +{ + if ( move >= LS_D1_BR && move <= LS_D1_B_ ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInParry( int move ) +{ + if ( move >= LS_PARRY_UP && move <= LS_PARRY_LL ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInKnockaway( int move ) +{ + if ( move >= LS_K1_T_ && move <= LS_K1_BL ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInReflect( int move ) +{ + if ( move >= LS_REFLECT_UP && move <= LS_REFLECT_LL ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInStart( int move ) +{ + if ( move >= LS_S_TL2BR && move <= LS_S_T2B ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInReturn( int move ) +{ + if ( move >= LS_R_TL2BR && move <= LS_R_T2B ) + { + return qtrue; + } + return qfalse; +} + +qboolean BG_SaberInReturn( int move ) +{ + return PM_SaberInReturn( move ); +} + +qboolean PM_InSaberAnim( int anim ) +{ + if ( (anim) >= BOTH_A1_T__B_ && (anim) <= BOTH_H1_S1_BR ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_InKnockDown( playerState_t *ps ) +{ + switch ( (ps->legsAnim) ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + return qtrue; + break; + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + if ( ps->legsTimer ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean PM_PainAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_PAIN1: //# First take pain anim + case BOTH_PAIN2: //# Second take pain anim + case BOTH_PAIN3: //# Third take pain anim + case BOTH_PAIN4: //# Fourth take pain anim + case BOTH_PAIN5: //# Fifth take pain anim - from behind + case BOTH_PAIN6: //# Sixth take pain anim - from behind + case BOTH_PAIN7: //# Seventh take pain anim - from behind + case BOTH_PAIN8: //# Eigth take pain anim - from behind + case BOTH_PAIN9: //# + case BOTH_PAIN10: //# + case BOTH_PAIN11: //# + case BOTH_PAIN12: //# + case BOTH_PAIN13: //# + case BOTH_PAIN14: //# + case BOTH_PAIN15: //# + case BOTH_PAIN16: //# + case BOTH_PAIN17: //# + case BOTH_PAIN18: //# + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_JumpingAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_JUMP1: //# Jump - wind-up and leave ground + case BOTH_INAIR1: //# In air loop (from jump) + case BOTH_LAND1: //# Landing (from in air loop) + case BOTH_LAND2: //# Landing Hard (from a great height) + case BOTH_JUMPBACK1: //# Jump backwards - wind-up and leave ground + case BOTH_INAIRBACK1: //# In air loop (from jump back) + case BOTH_LANDBACK1: //# Landing backwards(from in air loop) + case BOTH_JUMPLEFT1: //# Jump left - wind-up and leave ground + case BOTH_INAIRLEFT1: //# In air loop (from jump left) + case BOTH_LANDLEFT1: //# Landing left(from in air loop) + case BOTH_JUMPRIGHT1: //# Jump right - wind-up and leave ground + case BOTH_INAIRRIGHT1: //# In air loop (from jump right) + case BOTH_LANDRIGHT1: //# Landing right(from in air loop) + case BOTH_FORCEJUMP1: //# Jump - wind-up and leave ground + case BOTH_FORCEINAIR1: //# In air loop (from jump) + case BOTH_FORCELAND1: //# Landing (from in air loop) + case BOTH_FORCEJUMPBACK1: //# Jump backwards - wind-up and leave ground + case BOTH_FORCEINAIRBACK1: //# In air loop (from jump back) + case BOTH_FORCELANDBACK1: //# Landing backwards(from in air loop) + case BOTH_FORCEJUMPLEFT1: //# Jump left - wind-up and leave ground + case BOTH_FORCEINAIRLEFT1: //# In air loop (from jump left) + case BOTH_FORCELANDLEFT1: //# Landing left(from in air loop) + case BOTH_FORCEJUMPRIGHT1: //# Jump right - wind-up and leave ground + case BOTH_FORCEINAIRRIGHT1: //# In air loop (from jump right) + case BOTH_FORCELANDRIGHT1: //# Landing right(from in air loop) + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_LandingAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_LAND1: //# Landing (from in air loop) + case BOTH_LAND2: //# Landing Hard (from a great height) + case BOTH_LANDBACK1: //# Landing backwards(from in air loop) + case BOTH_LANDLEFT1: //# Landing left(from in air loop) + case BOTH_LANDRIGHT1: //# Landing right(from in air loop) + case BOTH_FORCELAND1: //# Landing (from in air loop) + case BOTH_FORCELANDBACK1: //# Landing backwards(from in air loop) + case BOTH_FORCELANDLEFT1: //# Landing left(from in air loop) + case BOTH_FORCELANDRIGHT1: //# Landing right(from in air loop) + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SpinningAnim( int anim ) +{ + /* + switch ( anim ) + { + //FIXME: list any other spinning anims + default: + break; + } + */ + return BG_SpinningSaberAnim( anim ); +} + +qboolean PM_InOnGroundAnim ( int anim ) +{ + switch( anim ) + { + case BOTH_DEAD1: + case BOTH_DEAD2: + case BOTH_DEAD3: + case BOTH_DEAD4: + case BOTH_DEAD5: + case BOTH_DEADFORWARD1: + case BOTH_DEADBACKWARD1: + case BOTH_DEADFORWARD2: + case BOTH_DEADBACKWARD2: + case BOTH_LYINGDEATH1: + case BOTH_LYINGDEAD1: + case BOTH_SLEEP1: //# laying on back-rknee up-rhand on torso + case BOTH_KNOCKDOWN1: //# + case BOTH_KNOCKDOWN2: //# + case BOTH_KNOCKDOWN3: //# + case BOTH_KNOCKDOWN4: //# + case BOTH_KNOCKDOWN5: //# + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_GETUP_CROUCH_F1: + case BOTH_GETUP_CROUCH_B1: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_FORCE_GETUP_B6: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + return qtrue; + break; + } + + return qfalse; +} + +qboolean PM_InRollComplete( playerState_t *ps, int anim ) +{ + switch ( (anim) ) + { + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + if ( ps->legsTimer < 1 ) + { + return qtrue; + } + break; + } + return qfalse; +} + +qboolean PM_CanRollFromSoulCal( playerState_t *ps ) +{ + if ( ps->legsAnim == BOTH_A7_SOULCAL + && ps->legsTimer < 700 + && ps->legsTimer > 250 ) + { + return qtrue; + } + return qfalse; +} + +qboolean BG_SuperBreakLoseAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_LK_S_DL_S_SB_1_L: //super break I lost + case BOTH_LK_S_DL_T_SB_1_L: //super break I lost + case BOTH_LK_S_ST_S_SB_1_L: //super break I lost + case BOTH_LK_S_ST_T_SB_1_L: //super break I lost + case BOTH_LK_S_S_S_SB_1_L: //super break I lost + case BOTH_LK_S_S_T_SB_1_L: //super break I lost + case BOTH_LK_DL_DL_S_SB_1_L: //super break I lost + case BOTH_LK_DL_DL_T_SB_1_L: //super break I lost + case BOTH_LK_DL_ST_S_SB_1_L: //super break I lost + case BOTH_LK_DL_ST_T_SB_1_L: //super break I lost + case BOTH_LK_DL_S_S_SB_1_L: //super break I lost + case BOTH_LK_DL_S_T_SB_1_L: //super break I lost + case BOTH_LK_ST_DL_S_SB_1_L: //super break I lost + case BOTH_LK_ST_DL_T_SB_1_L: //super break I lost + case BOTH_LK_ST_ST_S_SB_1_L: //super break I lost + case BOTH_LK_ST_ST_T_SB_1_L: //super break I lost + case BOTH_LK_ST_S_S_SB_1_L: //super break I lost + case BOTH_LK_ST_S_T_SB_1_L: //super break I lost + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_SuperBreakWinAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_LK_S_DL_S_SB_1_W: //super break I won + case BOTH_LK_S_DL_T_SB_1_W: //super break I won + case BOTH_LK_S_ST_S_SB_1_W: //super break I won + case BOTH_LK_S_ST_T_SB_1_W: //super break I won + case BOTH_LK_S_S_S_SB_1_W: //super break I won + case BOTH_LK_S_S_T_SB_1_W: //super break I won + case BOTH_LK_DL_DL_S_SB_1_W: //super break I won + case BOTH_LK_DL_DL_T_SB_1_W: //super break I won + case BOTH_LK_DL_ST_S_SB_1_W: //super break I won + case BOTH_LK_DL_ST_T_SB_1_W: //super break I won + case BOTH_LK_DL_S_S_SB_1_W: //super break I won + case BOTH_LK_DL_S_T_SB_1_W: //super break I won + case BOTH_LK_ST_DL_S_SB_1_W: //super break I won + case BOTH_LK_ST_DL_T_SB_1_W: //super break I won + case BOTH_LK_ST_ST_S_SB_1_W: //super break I won + case BOTH_LK_ST_ST_T_SB_1_W: //super break I won + case BOTH_LK_ST_S_S_SB_1_W: //super break I won + case BOTH_LK_ST_S_T_SB_1_W: //super break I won + return qtrue; + break; + } + return qfalse; +} + + +qboolean BG_SaberLockBreakAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_BF1BREAK: + case BOTH_BF2BREAK: + case BOTH_CWCIRCLEBREAK: + case BOTH_CCWCIRCLEBREAK: + case BOTH_LK_S_DL_S_B_1_L: //normal break I lost + case BOTH_LK_S_DL_S_B_1_W: //normal break I won + case BOTH_LK_S_DL_T_B_1_L: //normal break I lost + case BOTH_LK_S_DL_T_B_1_W: //normal break I won + case BOTH_LK_S_ST_S_B_1_L: //normal break I lost + case BOTH_LK_S_ST_S_B_1_W: //normal break I won + case BOTH_LK_S_ST_T_B_1_L: //normal break I lost + case BOTH_LK_S_ST_T_B_1_W: //normal break I won + case BOTH_LK_S_S_S_B_1_L: //normal break I lost + case BOTH_LK_S_S_S_B_1_W: //normal break I won + case BOTH_LK_S_S_T_B_1_L: //normal break I lost + case BOTH_LK_S_S_T_B_1_W: //normal break I won + case BOTH_LK_DL_DL_S_B_1_L: //normal break I lost + case BOTH_LK_DL_DL_S_B_1_W: //normal break I won + case BOTH_LK_DL_DL_T_B_1_L: //normal break I lost + case BOTH_LK_DL_DL_T_B_1_W: //normal break I won + case BOTH_LK_DL_ST_S_B_1_L: //normal break I lost + case BOTH_LK_DL_ST_S_B_1_W: //normal break I won + case BOTH_LK_DL_ST_T_B_1_L: //normal break I lost + case BOTH_LK_DL_ST_T_B_1_W: //normal break I won + case BOTH_LK_DL_S_S_B_1_L: //normal break I lost + case BOTH_LK_DL_S_S_B_1_W: //normal break I won + case BOTH_LK_DL_S_T_B_1_L: //normal break I lost + case BOTH_LK_DL_S_T_B_1_W: //normal break I won + case BOTH_LK_ST_DL_S_B_1_L: //normal break I lost + case BOTH_LK_ST_DL_S_B_1_W: //normal break I won + case BOTH_LK_ST_DL_T_B_1_L: //normal break I lost + case BOTH_LK_ST_DL_T_B_1_W: //normal break I won + case BOTH_LK_ST_ST_S_B_1_L: //normal break I lost + case BOTH_LK_ST_ST_S_B_1_W: //normal break I won + case BOTH_LK_ST_ST_T_B_1_L: //normal break I lost + case BOTH_LK_ST_ST_T_B_1_W: //normal break I won + case BOTH_LK_ST_S_S_B_1_L: //normal break I lost + case BOTH_LK_ST_S_S_B_1_W: //normal break I won + case BOTH_LK_ST_S_T_B_1_L: //normal break I lost + case BOTH_LK_ST_S_T_B_1_W: //normal break I won + return qtrue; + break; + } + return (BG_SuperBreakLoseAnim(anim)||BG_SuperBreakWinAnim(anim)); +} + + +qboolean BG_FullBodyTauntAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_GESTURE1: + case BOTH_DUAL_TAUNT: + case BOTH_STAFF_TAUNT: + case BOTH_BOW: + case BOTH_MEDITATE: + case BOTH_SHOWOFF_FAST: + case BOTH_SHOWOFF_MEDIUM: + case BOTH_SHOWOFF_STRONG: + case BOTH_SHOWOFF_DUAL: + case BOTH_SHOWOFF_STAFF: + case BOTH_VICTORY_FAST: + case BOTH_VICTORY_MEDIUM: + case BOTH_VICTORY_STRONG: + case BOTH_VICTORY_DUAL: + case BOTH_VICTORY_STAFF: + return qtrue; + break; + } + return qfalse; +} + + +/* +============= +BG_AnimLength + +Get the "length" of an anim given the local anim index (which skeleton) +and anim number. Obviously does not take things like the length of the +anim while force speeding (as an example) and whatnot into account. +============= +*/ +int BG_AnimLength( int index, animNumber_t anim ) +{ + if (anim >= MAX_ANIMATIONS) + { + return -1; + } + + return bgAllAnims[index].anims[anim].numFrames * fabs((float)(bgAllAnims[index].anims[anim].frameLerp)); +} + +//just use whatever pm->animations is +int PM_AnimLength( int index, animNumber_t anim ) +{ + if (anim >= MAX_ANIMATIONS || !pm->animations) + { + return -1; + } + if ( anim < 0 ) + { + Com_Error(ERR_DROP,"ERROR: anim %d < 0\n", anim ); + } + return pm->animations[anim].numFrames * fabs((float)(pm->animations[anim].frameLerp)); +} + +void PM_DebugLegsAnim(int anim) +{ + int oldAnim = (pm->ps->legsAnim); + int newAnim = (anim); + + if (oldAnim < MAX_TOTALANIMATIONS && oldAnim >= BOTH_DEATH1 && + newAnim < MAX_TOTALANIMATIONS && newAnim >= BOTH_DEATH1) + { + Com_Printf("OLD: %s\n", animTable[oldAnim]); + Com_Printf("NEW: %s\n", animTable[newAnim]); + } +} + +qboolean PM_SaberInTransition( int move ) +{ + if ( move >= LS_T1_BR__R && move <= LS_T1_BL__L ) + { + return qtrue; + } + return qfalse; +} + +qboolean BG_SaberInTransitionAny( int move ) +{ + if ( PM_SaberInStart( move ) ) + { + return qtrue; + } + else if ( PM_SaberInTransition( move ) ) + { + return qtrue; + } + else if ( PM_SaberInReturn( move ) ) + { + return qtrue; + } + return qfalse; +} + +/* +============================================================================== +END: Animation utility functions (sequence checking) +============================================================================== +*/ + +void BG_FlipPart(playerState_t *ps, int part) +{ + if (part == SETANIM_TORSO) + { + if (ps->torsoFlip) + { + ps->torsoFlip = qfalse; + } + else + { + ps->torsoFlip = qtrue; + } + } + else if (part == SETANIM_LEGS) + { + if (ps->legsFlip) + { + ps->legsFlip = qfalse; + } + else + { + ps->legsFlip = qtrue; + } + } +} + +#ifdef Q3_VM +char BGPAFtext[60000]; +#endif +qboolean BGPAFtextLoaded = qfalse; +animation_t bgHumanoidAnimations[MAX_TOTALANIMATIONS]; //humanoid animations are the only ones that are statically allocated. + +//#define CONVENIENT_ANIMATION_FILE_DEBUG_THING + +#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING +void SpewDebugStuffToFile() +{ + fileHandle_t f; + int i = 0; + + trap_FS_FOpenFile("file_of_debug_stuff_MP.txt", &f, FS_WRITE); + + if (!f) + { + return; + } + + BGPAFtext[0] = 0; + + while (i < MAX_ANIMATIONS) + { + strcat(BGPAFtext, va("%i %i\n", i, bgHumanoidAnimations[i].frameLerp)); + i++; + } + + trap_FS_Write(BGPAFtext, strlen(BGPAFtext), f); + trap_FS_FCloseFile(f); +} +#endif + +bgLoadedAnim_t bgAllAnims[MAX_ANIM_FILES]; +int bgNumAllAnims = 2; //start off at 2, because 0 will always be assigned to humanoid, and 1 will always be rockettrooper + +//ALWAYS call on game/cgame init +void BG_InitAnimsets(void) +{ + memset(&bgAllAnims, 0, sizeof(bgAllAnims)); + BGPAFtextLoaded = qfalse; // VVFIXME - The PC doesn't seem to need this, but why? +} + +//ALWAYS call on game/cgame shutdown +void BG_ClearAnimsets(void) +{ + /* + int i = 1; + + while (i < bgNumAllAnims) + { + if (bgAllAnims[i].anims) + { + strap_TrueFree((void **)&bgAllAnims[i].anims); + } + i++; + } + */ +} + +animation_t *BG_AnimsetAlloc(void) +{ + assert (bgNumAllAnims < MAX_ANIM_FILES); + bgAllAnims[bgNumAllAnims].anims = (animation_t *) BG_Alloc(sizeof(animation_t)*MAX_TOTALANIMATIONS); + + return bgAllAnims[bgNumAllAnims].anims; +} + +void BG_AnimsetFree(animation_t *animset) +{ + /* + if (!animset) + { + return; + } + + strap_TrueFree((void **)&animset); + +#ifdef _DEBUG + if (animset) + { + assert(!"Failed to free anim set"); + } +#endif + */ +} + +#ifndef QAGAME //none of this is actually needed serverside. Could just be moved to cgame code but it's here since it + //used to tie in a lot with the anim loading stuff. +stringID_table_t animEventTypeTable[MAX_ANIM_EVENTS+1] = +{ + ENUM2STRING(AEV_SOUND), //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay + ENUM2STRING(AEV_FOOTSTEP), //# animID AEV_FOOTSTEP framenum footstepType + ENUM2STRING(AEV_EFFECT), //# animID AEV_EFFECT framenum effectpath boltName + ENUM2STRING(AEV_FIRE), //# animID AEV_FIRE framenum altfire chancetofire + ENUM2STRING(AEV_MOVE), //# animID AEV_MOVE framenum forwardpush rightpush uppush + ENUM2STRING(AEV_SOUNDCHAN), //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay + ENUM2STRING(AEV_SABER_SWING), //# animID AEV_SABER_SWING framenum CHANNEL randomlow randomhi chancetoplay + ENUM2STRING(AEV_SABER_SPIN), //# animID AEV_SABER_SPIN framenum CHANNEL chancetoplay + //must be terminated + NULL,-1 +}; + +stringID_table_t footstepTypeTable[NUM_FOOTSTEP_TYPES+1] = +{ + ENUM2STRING(FOOTSTEP_R), + ENUM2STRING(FOOTSTEP_L), + ENUM2STRING(FOOTSTEP_HEAVY_R), + ENUM2STRING(FOOTSTEP_HEAVY_L), + //must be terminated + NULL,-1 +}; + +int CheckAnimFrameForEventType( animevent_t *animEvents, int keyFrame, animEventType_t eventType ) +{ + int i; + + for ( i = 0; i < MAX_ANIM_EVENTS; i++ ) + { + if ( animEvents[i].keyFrame == keyFrame ) + {//there is an animevent on this frame already + if ( animEvents[i].eventType == eventType ) + {//and it is of the same type + return i; + } + } + } + //nope + return -1; +} + +void ParseAnimationEvtBlock(const char *aeb_filename, animevent_t *animEvents, animation_t *animations, int *i,const char **text_p) +{ + const char *token; + int num, n, animNum, keyFrame, lowestVal, highestVal, curAnimEvent, lastAnimEvent = 0; + animEventType_t eventType; + char stringData[MAX_QPATH]; + + // get past starting bracket + while(1) + { + token = COM_Parse( text_p ); + if ( !Q_stricmp( token, "{" ) ) + { + break; + } + } + + //NOTE: instead of a blind increment, increase the index + // this way if we have an event on an anim that already + // has an event of that type, it stomps it + + // read information for each frame + while ( 1 ) + { + if ( lastAnimEvent >= MAX_ANIM_EVENTS ) + { + Com_Error( ERR_DROP, "ParseAnimationEvtBlock: number events in animEvent file %s > MAX_ANIM_EVENTS(%i)", aeb_filename, MAX_ANIM_EVENTS ); + return; + } + // Get base frame of sequence + token = COM_Parse( text_p ); + if ( !token || !token[0]) + { + break; + } + + if ( !Q_stricmp( token, "}" ) ) // At end of block + { + break; + } + + //Compare to same table as animations used + // so we don't have to use actual numbers for animation first frames, + // just need offsets. + //This way when animation numbers change, this table won't have to be updated, + // at least not much. + animNum = GetIDForString(animTable, token); + if(animNum == -1) + {//Unrecognized ANIM ENUM name, or we're skipping this line, keep going till you get a good one + Com_Printf(S_COLOR_YELLOW"WARNING: Unknown token %s in animEvent file %s\n", token, aeb_filename ); + while (token[0]) + { + token = COM_ParseExt( text_p, qfalse ); //returns empty string when next token is EOL + } + continue; + } + + if ( animations[animNum].numFrames == 0 ) + {//we don't use this anim + Com_Printf(S_COLOR_YELLOW"WARNING: %s animevents.cfg: anim %s not used by this model\n", aeb_filename, token); + //skip this entry + SkipRestOfLine( text_p ); + continue; + } + + token = COM_Parse( text_p ); + eventType = (animEventType_t)GetIDForString(animEventTypeTable, token); + if ( eventType == AEV_NONE || eventType == -1 ) + {//Unrecognized ANIM EVENT TYOE, or we're skipping this line, keep going till you get a good one + //Com_Printf(S_COLOR_YELLOW"WARNING: Unknown token %s in animEvent file %s\n", token, aeb_filename ); + continue; + } + + //set our start frame + keyFrame = animations[animNum].firstFrame; + // Get offset to frame within sequence + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + keyFrame += atoi( token ); + + //see if this frame already has an event of this type on it, if so, overwrite it + curAnimEvent = CheckAnimFrameForEventType( animEvents, keyFrame, eventType ); + if ( curAnimEvent == -1 ) + {//this anim frame doesn't already have an event of this type on it + curAnimEvent = lastAnimEvent; + } + + //now that we know which event index we're going to plug the data into, start doing it + animEvents[curAnimEvent].eventType = eventType; + animEvents[curAnimEvent].keyFrame = keyFrame; + + //now read out the proper data based on the type + switch ( animEvents[curAnimEvent].eventType ) + { + case AEV_SOUNDCHAN: //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + if ( stricmp( token, "CHAN_VOICE_ATTEN" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE_ATTEN; + } + else if ( stricmp( token, "CHAN_VOICE_GLOBAL" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE_GLOBAL; + } + else if ( stricmp( token, "CHAN_ANNOUNCER" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_ANNOUNCER; + } + else if ( stricmp( token, "CHAN_BODY" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_BODY; + } + else if ( stricmp( token, "CHAN_WEAPON" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_WEAPON; + } + else if ( stricmp( token, "CHAN_VOICE" ) == 0 ) + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_VOICE; + } + else + { + animEvents[curAnimEvent].eventData[AED_SOUNDCHANNEL] = CHAN_AUTO; + } + //fall through to normal sound + case AEV_SOUND: //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay + //get soundstring + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + strcpy(stringData, token); + //get lowest value + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + lowestVal = atoi( token ); + //get highest value + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + highestVal = atoi( token ); + //Now precache all the sounds + //NOTE: If we can be assured sequential handles, we can store sound indices + // instead of strings, unfortunately, if these sounds were previously + // registered, we cannot be guaranteed sequential indices. Thus an array + if(lowestVal && highestVal) + { + //assert(highestVal - lowestVal < MAX_RANDOM_ANIM_SOUNDS); + if ((highestVal-lowestVal) >= MAX_RANDOM_ANIM_SOUNDS) + { + highestVal = lowestVal + (MAX_RANDOM_ANIM_SOUNDS-1); + } + for ( n = lowestVal, num = AED_SOUNDINDEX_START; n <= highestVal && num <= AED_SOUNDINDEX_END; n++, num++ ) + { + if (stringData[0] == '*') + { //FIXME? Would be nice to make custom sounds work with animEvents. + animEvents[curAnimEvent].eventData[num] = 0; + } + else + { + animEvents[curAnimEvent].eventData[num] = trap_S_RegisterSound( va( stringData, n ) ); + } + } + animEvents[curAnimEvent].eventData[AED_SOUND_NUMRANDOMSNDS] = num - 1; + } + else + { + if (stringData[0] == '*') + { //FIXME? Would be nice to make custom sounds work with animEvents. + animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] = 0; + } + else + { + animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] = trap_S_RegisterSound( stringData ); + } +#ifndef FINAL_BUILD + if ( !animEvents[curAnimEvent].eventData[AED_SOUNDINDEX_START] && + stringData[0] != '*') + {//couldn't register it - file not found + Com_Printf( S_COLOR_RED "ParseAnimationSndBlock: sound %s does not exist (animevents.cfg %s)!\n", stringData, aeb_filename ); + } +#endif + animEvents[curAnimEvent].eventData[AED_SOUND_NUMRANDOMSNDS] = 0; + } + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_SOUND_PROBABILITY] = atoi( token ); + + //last part - cheat and check and see if it's a special overridable saber sound we know of... + if ( !Q_stricmpn( "sound/weapons/saber/saberhup", stringData, 28 ) ) + {//a saber swing + animEvents[curAnimEvent].eventType = AEV_SABER_SWING; + animEvents[curAnimEvent].eventData[AED_SABER_SWING_SABERNUM] = 0;//since we don't know which one they meant if we're hacking this, always use first saber + animEvents[curAnimEvent].eventData[AED_SABER_SWING_PROBABILITY] = animEvents[curAnimEvent].eventData[AED_SOUND_PROBABILITY]; + if ( lowestVal < 4 ) + {//fast swing + animEvents[curAnimEvent].eventData[AED_SABER_SWING_TYPE] = 0;//SWING_FAST; + } + else if ( lowestVal < 7 ) + {//medium swing + animEvents[curAnimEvent].eventData[AED_SABER_SWING_TYPE] = 1;//SWING_MEDIUM; + } + else + {//strong swing + animEvents[curAnimEvent].eventData[AED_SABER_SWING_TYPE] = 2;//SWING_STRONG; + } + } + else if ( !Q_stricmpn( "sound/weapons/saber/saberspin", stringData, 29 ) ) + {//a saber spin + animEvents[curAnimEvent].eventType = AEV_SABER_SPIN; + animEvents[curAnimEvent].eventData[AED_SABER_SPIN_SABERNUM] = 0;//since we don't know which one they meant if we're hacking this, always use first saber + animEvents[curAnimEvent].eventData[AED_SABER_SPIN_PROBABILITY] = animEvents[curAnimEvent].eventData[AED_SOUND_PROBABILITY]; + if ( stringData[29] == 'o' ) + {//saberspinoff + animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 0; + } + else if ( stringData[29] == '1' ) + {//saberspin1 + animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 2; + } + else if ( stringData[29] == '2' ) + {//saberspin2 + animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 3; + } + else if ( stringData[29] == '3' ) + {//saberspin3 + animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 4; + } + else if ( stringData[29] == '%' ) + {//saberspin%d + animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 5; + } + else + {//just plain saberspin + animEvents[curAnimEvent].eventData[AED_SABER_SPIN_TYPE] = 1; + } + } + break; + case AEV_FOOTSTEP: //# animID AEV_FOOTSTEP framenum footstepType + //get footstep type + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + animEvents[curAnimEvent].eventData[AED_FOOTSTEP_TYPE] = GetIDForString(footstepTypeTable, token); + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_FOOTSTEP_PROBABILITY] = atoi( token ); + break; + case AEV_EFFECT: //# animID AEV_EFFECT framenum effectpath boltName + //get effect index + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + animEvents[curAnimEvent].eventData[AED_EFFECTINDEX] = trap_FX_RegisterEffect( token ); + //get bolt index + token = COM_Parse( text_p ); + if ( !token ) + { + break; + } + if ( Q_stricmp( "none", token ) != 0 && Q_stricmp( "NULL", token ) != 0 ) + {//actually are specifying a bolt to use + if (!animEvents[curAnimEvent].stringData) + { //eh, whatever. no dynamic stuff, so this will do. + animEvents[curAnimEvent].stringData = (char *) BG_Alloc(2048); + } + strcpy(animEvents[curAnimEvent].stringData, token); + } + //NOTE: this string will later be used to add a bolt and store the index, as below: + //animEvent->eventData[AED_BOLTINDEX] = gi.G2API_AddBolt( ¢->gent->ghoul2[cent->gent->playerModel], animEvent->stringData ); + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_EFFECT_PROBABILITY] = atoi( token ); + break; + case AEV_FIRE: //# animID AEV_FIRE framenum altfire chancetofire + //get altfire + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_FIRE_ALT] = atoi( token ); + //get probability + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_FIRE_PROBABILITY] = atoi( token ); + break; + case AEV_MOVE: //# animID AEV_MOVE framenum forwardpush rightpush uppush + //get forward push + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_MOVE_FWD] = atoi( token ); + //get right push + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_MOVE_RT] = atoi( token ); + //get upwards push + token = COM_Parse( text_p ); + if ( !token ) + {//WARNING! BAD TABLE! + break; + } + animEvents[curAnimEvent].eventData[AED_MOVE_UP] = atoi( token ); + break; + default: //unknown? + SkipRestOfLine( text_p ); + continue; + break; + } + + if ( curAnimEvent == lastAnimEvent ) + { + lastAnimEvent++; + } + } +} + +/* +====================== +BG_ParseAnimationEvtFile + +Read a configuration file containing animation events +models/players/kyle/animevents.cfg, etc + +This file's presence is not required + +====================== +*/ +bgLoadedEvents_t bgAllEvents[MAX_ANIM_FILES]; +int bgNumAnimEvents = 1; +static int bg_animParseIncluding = 0; +int BG_ParseAnimationEvtFile( const char *as_filename, int animFileIndex, int eventFileIndex ) +{ + const char *text_p; + int len; + const char *token; + char text[80000]; + char sfilename[MAX_QPATH]; + fileHandle_t f; + int i, j, upper_i, lower_i; + int usedIndex = -1; + animevent_t *legsAnimEvents; + animevent_t *torsoAnimEvents; + animation_t *animations; + int forcedIndex; + + assert(animFileIndex < MAX_ANIM_FILES); + assert(eventFileIndex < MAX_ANIM_FILES); + + if (eventFileIndex == -1) + { + forcedIndex = 0; + } + else + { + forcedIndex = eventFileIndex; + } + + if (bg_animParseIncluding <= 0) + { //if we should be parsing an included file, skip this part + if ( bgAllEvents[forcedIndex].eventsParsed ) + {//already cached this one + return forcedIndex; + } + } + + legsAnimEvents = bgAllEvents[forcedIndex].legsAnimEvents; + torsoAnimEvents = bgAllEvents[forcedIndex].torsoAnimEvents; + animations = bgAllAnims[animFileIndex].anims; + + if (bg_animParseIncluding <= 0) + { //if we should be parsing an included file, skip this part + //Go through and see if this filename is already in the table. + i = 0; + while (i < bgNumAnimEvents && forcedIndex != 0) + { + if (!Q_stricmp(as_filename, bgAllEvents[i].filename)) + { //looks like we have it already. + return i; + } + i++; + } + } + + // Load and parse animevents.cfg file + Com_sprintf( sfilename, sizeof( sfilename ), "%sanimevents.cfg", as_filename ); + + if (bg_animParseIncluding <= 0) + { //should already be done if we're including + //initialize anim event array + for( i = 0; i < MAX_ANIM_EVENTS; i++ ) + { + //Type of event + torsoAnimEvents[i].eventType = AEV_NONE; + legsAnimEvents[i].eventType = AEV_NONE; + //Frame to play event on + torsoAnimEvents[i].keyFrame = -1; + legsAnimEvents[i].keyFrame = -1; + //we allow storage of one string, temporarily (in case we have to look up an index later, then make sure to set stringData to NULL so we only do the look-up once) + torsoAnimEvents[i].stringData = NULL; + legsAnimEvents[i].stringData = NULL; + //Unique IDs, can be soundIndex of sound file to play OR effect index or footstep type, etc. + for ( j = 0; j < AED_ARRAY_SIZE; j++ ) + { + torsoAnimEvents[i].eventData[j] = -1; + legsAnimEvents[i].eventData[j] = -1; + } + } + } + + // load the file + len = trap_FS_FOpenFile( sfilename, &f, FS_READ ); + if ( len <= 0 ) + {//no file + goto fin; + } + if ( len >= sizeof( text ) - 1 ) + { + trap_FS_FCloseFile(f); +#ifndef FINAL_BUILD + Com_Error(ERR_DROP, "File %s too long\n", sfilename ); +#else + Com_Printf( "File %s too long\n", sfilename ); +#endif + goto fin; + } + + trap_FS_Read( text, len, f ); + text[len] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + upper_i =0; + lower_i =0; + + // read information for batches of sounds (UPPER or LOWER) + while ( 1 ) + { + // Get base frame of sequence + token = COM_Parse( &text_p ); + if ( !token || !token[0] ) + { + break; + } + + if ( !Q_stricmp(token,"include") ) // grab from another animevents.cfg + {//NOTE: you REALLY should NOT do this after the main block of UPPERSOUNDS and LOWERSOUNDS + const char *include_filename = COM_Parse( &text_p ); + if ( include_filename != NULL ) + { + char fullIPath[MAX_QPATH]; + strcpy(fullIPath, va("models/players/%s/", include_filename)); + bg_animParseIncluding++; + BG_ParseAnimationEvtFile( fullIPath, animFileIndex, forcedIndex ); + bg_animParseIncluding--; + } + } + + if ( !Q_stricmp(token,"UPPEREVENTS") ) // A batch of upper sounds + { + ParseAnimationEvtBlock( as_filename, torsoAnimEvents, animations, &upper_i, &text_p ); + } + + else if ( !Q_stricmp(token,"LOWEREVENTS") ) // A batch of lower sounds + { + ParseAnimationEvtBlock( as_filename, legsAnimEvents, animations, &lower_i, &text_p ); + } + } + + usedIndex = forcedIndex; +fin: + //Mark this anim set so that we know we tried to load he sounds, don't care if the load failed + if (bg_animParseIncluding <= 0) + { //if we should be parsing an included file, skip this part + bgAllEvents[forcedIndex].eventsParsed = qtrue; + strcpy(bgAllEvents[forcedIndex].filename, as_filename); + if (forcedIndex) + { + bgNumAnimEvents++; + } + } + + return usedIndex; +} +#endif + +/* +====================== +BG_ParseAnimationFile + +Read a configuration file containing animation coutns and rates +models/players/visor/animation.cfg, etc + +====================== +*/ +int BG_ParseAnimationFile(const char *filename, animation_t *animset, qboolean isHumanoid) +{ + char *text_p; + int len; + int i; + char *token; + float fps; + int skip; + int usedIndex = -1; + int nextIndex = bgNumAllAnims; + qboolean dynAlloc = qfalse; + qboolean wasLoaded = qfalse; +#ifndef Q3_VM + char BGPAFtext[60000]; +#endif + + fileHandle_t f; + int animNum; + + if (!isHumanoid) + { + i = 0; + while (i < bgNumAllAnims) + { //see if it's been loaded already + if (!Q_stricmp(bgAllAnims[i].filename, filename)) + { + animset = bgAllAnims[i].anims; + return i; //alright, we already have it. + } + i++; + } + + //Looks like it has not yet been loaded. Allocate space for the anim set if we need to, and continue along. + if (!animset) + { + if (strstr(filename, "players/_humanoid/")) + { //then use the static humanoid set. + animset = bgHumanoidAnimations; + nextIndex = 0; + } + else if (strstr(filename, "players/rockettrooper/")) + { //rockettrooper always index 1 + nextIndex = 1; + animset = BG_AnimsetAlloc(); + dynAlloc = qtrue; //so we know to free this memory in case we have to return early. Don't want any leaks. + + if (!animset) + { + assert(!"Anim set alloc failed!"); + return -1; + } + } + else + { + animset = BG_AnimsetAlloc(); + dynAlloc = qtrue; //so we know to free this memory in case we have to return early. Don't want any leaks. + + if (!animset) + { + assert(!"Anim set alloc failed!"); + return -1; + } + } + } + } +#ifdef _DEBUG + else + { + assert(animset); + } +#endif + + // load the file + if (!BGPAFtextLoaded || !isHumanoid) + { //rww - We are always using the same animation config now. So only load it once. + len = trap_FS_FOpenFile( filename, &f, FS_READ ); + if ( (len <= 0) || (len >= sizeof( BGPAFtext ) - 1) ) + { + if (dynAlloc) + { + BG_AnimsetFree(animset); + } + if (len > 0) + { + Com_Error(ERR_DROP, "%s exceeds the allowed game-side animation buffer!", filename); + } + return -1; + } + + trap_FS_Read( BGPAFtext, len, f ); + BGPAFtext[len] = 0; + trap_FS_FCloseFile( f ); + } + else + { + if (dynAlloc) + { + assert(!"Should not have allocated dynamically for humanoid"); + BG_AnimsetFree(animset); + } + return 0; //humanoid index + } + + // parse the text + text_p = BGPAFtext; + skip = 0; // quiet the compiler warning + + //FIXME: have some way of playing anims backwards... negative numFrames? + + //initialize anim array so that from 0 to MAX_ANIMATIONS, set default values of 0 1 0 100 + for(i = 0; i < MAX_ANIMATIONS; i++) + { + animset[i].firstFrame = 0; + animset[i].numFrames = 0; + animset[i].loopFrames = -1; + animset[i].frameLerp = 100; + } + + // read information for each frame + while(1) + { + token = COM_Parse( (const char **)(&text_p) ); + + if ( !token || !token[0]) + { + break; + } + + animNum = GetIDForString(animTable, token); + if(animNum == -1) + { +//#ifndef FINAL_BUILD +#ifdef _DEBUG + Com_Printf(S_COLOR_RED"WARNING: Unknown token %s in %s\n", token, filename); + while (token[0]) + { + token = COM_ParseExt( (const char **) &text_p, qfalse ); //returns empty string when next token is EOL + } +#endif + continue; + } + + token = COM_Parse( (const char **)(&text_p) ); + if ( !token ) + { + break; + } + animset[animNum].firstFrame = atoi( token ); + + token = COM_Parse( (const char **)(&text_p) ); + if ( !token ) + { + break; + } + animset[animNum].numFrames = atoi( token ); + + token = COM_Parse( (const char **)(&text_p) ); + if ( !token ) + { + break; + } + animset[animNum].loopFrames = atoi( token ); + + token = COM_Parse( (const char **)(&text_p) ); + if ( !token ) + { + break; + } + fps = atof( token ); + if ( fps == 0 ) + { + fps = 1;//Don't allow divide by zero error + } + if ( fps < 0 ) + {//backwards + animset[animNum].frameLerp = floor(1000.0f / fps); + } + else + { + animset[animNum].frameLerp = ceil(1000.0f / fps); + } + } +/* +#ifdef _DEBUG + //Check the array, and print the ones that have nothing in them. + for(i = 0; i < MAX_ANIMATIONS; i++) + { + if (animTable[i].name != NULL) // This animation reference exists. + { + if (animset[i].firstFrame <= 0 && animset[i].numFrames <=0) + { // This is an empty animation reference. + Com_Printf("***ANIMTABLE reference #%d (%s) is empty!\n", i, animTable[i].name); + } + } + } +#endif // _DEBUG +*/ +#ifdef CONVENIENT_ANIMATION_FILE_DEBUG_THING + SpewDebugStuffToFile(); +#endif + + wasLoaded = BGPAFtextLoaded; + + if (isHumanoid) + { + bgAllAnims[0].anims = animset; + strcpy(bgAllAnims[0].filename, filename); + BGPAFtextLoaded = qtrue; + + usedIndex = 0; + } + else + { + bgAllAnims[nextIndex].anims = animset; + strcpy(bgAllAnims[nextIndex].filename, filename); + + usedIndex = bgNumAllAnims; + + if (nextIndex > 1) + { //don't bother increasing the number if this ended up as a humanoid/rockettrooper load. + bgNumAllAnims++; + } + else + { + BGPAFtextLoaded = qtrue; + usedIndex = nextIndex; + } + } + + /* + if (!wasLoaded && BGPAFtextLoaded) + { //just loaded humanoid skel - we always want the rockettrooper to be after it, in slot 1 +#ifdef _DEBUG + assert(BG_ParseAnimationFile("models/players/rockettrooper/animation.cfg", NULL, qfalse) == 1); +#else + BG_ParseAnimationFile("models/players/rockettrooper/animation.cfg", NULL, qfalse); +#endif + } + */ + + return usedIndex; +} + +/* +=================== +LEGS Animations +Base animation for overall body +=================== +*/ +static void BG_StartLegsAnim( playerState_t *ps, int anim ) +{ + if ( ps->pm_type >= PM_DEAD ) + { + assert(!BG_InDeathAnim(anim)); + //please let me know if this assert fires on you (ideally before you close/ignore it) -rww + + //vehicles are allowed to do this.. IF it's a vehicle death anim + if (ps->clientNum < MAX_CLIENTS || anim != BOTH_VT_DEATH1) + { + return; + } + } + if ( ps->legsTimer > 0 ) + { + return; // a high priority animation is running + } + + if (ps->legsAnim == anim) + { + BG_FlipPart(ps, SETANIM_LEGS); + } +#ifdef QAGAME + else if (g_entities[ps->clientNum].s.legsAnim == anim) + { //toggled anim to one anim then back to the one we were at previously in + //one frame, indicating that anim should be restarted. + BG_FlipPart(ps, SETANIM_LEGS); + } +#endif + ps->legsAnim = anim; + + /* + if ( pm->debugLevel ) { + Com_Printf("%d: StartLegsAnim %d, on client#%d\n", pm->cmd.serverTime, anim, pm->ps->clientNum); + } + */ +} + +void PM_ContinueLegsAnim( int anim ) { + if ( ( pm->ps->legsAnim ) == anim ) { + return; + } + if ( pm->ps->legsTimer > 0 ) { + return; // a high priority animation is running + } + + BG_StartLegsAnim( pm->ps, anim ); +} + +void PM_ForceLegsAnim( int anim) { + if (BG_InSpecialJump(pm->ps->legsAnim) && + pm->ps->legsTimer > 0 && + !BG_InSpecialJump(anim)) + { + return; + } + + if (BG_InRoll(pm->ps, pm->ps->legsAnim) && + pm->ps->legsTimer > 0 && + !BG_InRoll(pm->ps, anim)) + { + return; + } + + pm->ps->legsTimer = 0; + BG_StartLegsAnim( pm->ps, anim ); +} + + + +/* +=================== +TORSO Animations +Override animations for upper body +=================== +*/ +void BG_StartTorsoAnim( playerState_t *ps, int anim ) +{ + if ( ps->pm_type >= PM_DEAD ) + { + assert(!BG_InDeathAnim(anim)); + //please let me know if this assert fires on you (ideally before you close/ignore it) -rww + return; + } + + if (ps->torsoAnim == anim) + { + BG_FlipPart(ps, SETANIM_TORSO); + } +#ifdef QAGAME + else if (g_entities[ps->clientNum].s.torsoAnim == anim) + { //toggled anim to one anim then back to the one we were at previously in + //one frame, indicating that anim should be restarted. + BG_FlipPart(ps, SETANIM_TORSO); + } +#endif + ps->torsoAnim = anim; +} + +void PM_StartTorsoAnim( int anim ) +{ + BG_StartTorsoAnim(pm->ps, anim); +} + + +/* +------------------------- +PM_SetLegsAnimTimer +------------------------- +*/ +void BG_SetLegsAnimTimer(playerState_t *ps, int time) +{ + ps->legsTimer = time; + + if (ps->legsTimer < 0 && time != -1 ) + {//Cap timer to 0 if was counting down, but let it be -1 if that was intentional. NOTENOTE Yeah this seems dumb, but it mirrors SP. + ps->legsTimer = 0; + } +} + +void PM_SetLegsAnimTimer(int time) +{ + BG_SetLegsAnimTimer(pm->ps, time); +} + +/* +------------------------- +PM_SetTorsoAnimTimer +------------------------- +*/ +void BG_SetTorsoAnimTimer(playerState_t *ps, int time ) +{ + ps->torsoTimer = time; + + if (ps->torsoTimer < 0 && time != -1 ) + {//Cap timer to 0 if was counting down, but let it be -1 if that was intentional. NOTENOTE Yeah this seems dumb, but it mirrors SP. + ps->torsoTimer = 0; + } +} + +void PM_SetTorsoAnimTimer(int time ) +{ + BG_SetTorsoAnimTimer(pm->ps, time); +} + +void BG_SaberStartTransAnim( int clientNum, int saberAnimLevel, int weapon, int anim, float *animSpeed, int broken ) +{ + if ( anim >= BOTH_A1_T__B_ && anim <= BOTH_ROLL_STAB ) + { + if ( weapon == WP_SABER ) + { + saberInfo_t *saber = BG_MySaber( clientNum, 0 ); + if ( saber + && saber->animSpeedScale != 1.0f ) + { + *animSpeed *= saber->animSpeedScale; + } + saber = BG_MySaber( clientNum, 1 ); + if ( saber + && saber->animSpeedScale != 1.0f ) + { + *animSpeed *= saber->animSpeedScale; + } + } + } + + if ( ( (anim) >= BOTH_T1_BR__R && + (anim) <= BOTH_T1_BL_TL ) || + ( (anim) >= BOTH_T2_BR__R && + (anim) <= BOTH_T2_BL_TL ) || + ( (anim) >= BOTH_T3_BR__R && + (anim) <= BOTH_T3_BL_TL ) ) + { + if ( saberAnimLevel == FORCE_LEVEL_1 ) + { + *animSpeed *= 1.5f; + } + else if ( saberAnimLevel == FORCE_LEVEL_3 ) + { + *animSpeed *= 0.75f; + } + + if (broken & (1< -1); + assert(animations[anim].firstFrame > 0 || animations[anim].numFrames > 0); + + //NOTE: Setting blendTime here breaks actual blending.. + blendTime = 0; + + BG_SaberStartTransAnim(ps->clientNum, ps->fd.saberAnimLevel, ps->weapon, anim, &editAnimSpeed, ps->brokenLimbs); + + // Set torso anim + if (setAnimParts & SETANIM_TORSO) + { + // Don't reset if it's already running the anim + if( !(setAnimFlags & SETANIM_FLAG_RESTART) && (ps->torsoAnim) == anim ) + { + goto setAnimLegs; + } + // or if a more important anim is running + if( !(setAnimFlags & SETANIM_FLAG_OVERRIDE) && ((ps->torsoTimer > 0)||(ps->torsoTimer == -1)) ) + { + goto setAnimLegs; + } + + BG_StartTorsoAnim(ps, anim); + + if (setAnimFlags & SETANIM_FLAG_HOLD) + { + if (setAnimFlags & SETANIM_FLAG_HOLDLESS) + { // Make sure to only wait in full 1/20 sec server frame intervals. + int dur; + int speedDif; + + dur = (animations[anim].numFrames-1) * fabs((float)(animations[anim].frameLerp)); + speedDif = dur - (dur * editAnimSpeed); + dur += speedDif; + if (dur > 1) + { + ps->torsoTimer = dur-1; + } + else + { + ps->torsoTimer = fabs((float)(animations[anim].frameLerp)); + } + } + else + { + ps->torsoTimer = ((animations[anim].numFrames ) * fabs((float)(animations[anim].frameLerp))); + } + + if (ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + ps->torsoTimer /= 1.7; + } + } + } + +setAnimLegs: + // Set legs anim + if (setAnimParts & SETANIM_LEGS) + { + // Don't reset if it's already running the anim + if( !(setAnimFlags & SETANIM_FLAG_RESTART) && (ps->legsAnim) == anim ) + { + goto setAnimDone; + } + // or if a more important anim is running + if( !(setAnimFlags & SETANIM_FLAG_OVERRIDE) && ((ps->legsTimer > 0)||(ps->legsTimer == -1)) ) + { + goto setAnimDone; + } + + BG_StartLegsAnim(ps, anim); + + if (setAnimFlags & SETANIM_FLAG_HOLD) + { + if (setAnimFlags & SETANIM_FLAG_HOLDLESS) + { // Make sure to only wait in full 1/20 sec server frame intervals. + int dur; + int speedDif; + + dur = (animations[anim].numFrames-1) * fabs((float)(animations[anim].frameLerp)); + speedDif = dur - (dur * editAnimSpeed); + dur += speedDif; + if (dur > 1) + { + ps->legsTimer = dur-1; + } + else + { + ps->legsTimer = fabs((float)(animations[anim].frameLerp)); + } + } + else + { + ps->legsTimer = ((animations[anim].numFrames ) * fabs((float)(animations[anim].frameLerp))); + } + + if (PM_RunningAnim(anim) || + PM_WalkingAnim(anim)) //these guys are ok, they don't actually reference pm + { + if (ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + ps->legsTimer /= 1.3; + } + else if (ps->fd.forcePowersActive & (1 << FP_SPEED)) + { + ps->legsTimer /= 1.7; + } + } + } + } + +setAnimDone: + return; +} + +void PM_SetAnimFinal(int setAnimParts,int anim,int setAnimFlags, + int blendTime) // default blendTime=350 +{ + BG_SetAnimFinal(pm->ps, pm->animations, setAnimParts, anim, setAnimFlags, blendTime); +} + + +qboolean BG_HasAnimation(int animIndex, int animation) +{ + animation_t *animations; + + //must be a valid anim number + if ( animation < 0 || animation >= MAX_ANIMATIONS ) + { + return qfalse; + } + + //Must have a file index entry + if( animIndex < 0 || animIndex > bgNumAllAnims ) + return qfalse; + + animations = bgAllAnims[animIndex].anims; + + //No frames, no anim + if ( animations[animation].numFrames == 0 ) + return qfalse; + + //Has the sequence + return qtrue; +} + +int BG_PickAnim( int animIndex, int minAnim, int maxAnim ) +{ + int anim; + int count = 0; + + do + { + anim = Q_irand(minAnim, maxAnim); + count++; + } + while ( !BG_HasAnimation( animIndex, anim ) && count < 1000 ); + + if (count == 1000) + { //guess we just don't have a death anim then. + return -1; + } + + return anim; +} + +//I want to be able to use this on a playerstate even when we are not the focus +//of a pmove too so I have ported it to true BGishness. +//Please do not reference pm in this function or any functions that it calls, +//or I will cry. -rww +void BG_SetAnim(playerState_t *ps, animation_t *animations, int setAnimParts,int anim,int setAnimFlags, int blendTime) +{ + if (!animations) + { + animations = bgAllAnims[0].anims; + } + + if (animations[anim].firstFrame == 0 && animations[anim].numFrames == 0) + { + if (anim == BOTH_RUNBACK1 || + anim == BOTH_WALKBACK1 || + anim == BOTH_RUN1) + { //hack for droids + anim = BOTH_WALK2; + } + + if (animations[anim].firstFrame == 0 && animations[anim].numFrames == 0) + { //still? Just return then I guess. + return; + } + } + + /* + if (BG_InSpecialJump(anim)) + { + setAnimFlags |= SETANIM_FLAG_RESTART; + } + */ + //Don't know why I put this here originally but it's messing stuff up now and it isn't needed. + +// if (BG_InRoll(ps, ps->legsAnim)) +// { //never interrupt a roll +// return; +// } + + if (setAnimFlags&SETANIM_FLAG_OVERRIDE) + { + if (setAnimParts & SETANIM_TORSO) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || (ps->torsoAnim) != anim ) + { + BG_SetTorsoAnimTimer(ps, 0); + } + } + if (setAnimParts & SETANIM_LEGS) + { + if( (setAnimFlags & SETANIM_FLAG_RESTART) || (ps->legsAnim) != anim ) + { + BG_SetLegsAnimTimer(ps, 0); + } + } + } + + BG_SetAnimFinal(ps, animations, setAnimParts, anim, setAnimFlags, blendTime); +} + +void PM_SetAnim(int setAnimParts,int anim,int setAnimFlags, int blendTime) +{ + BG_SetAnim(pm->ps, pm->animations, setAnimParts, anim, setAnimFlags, blendTime); +} + +#include "../namespace_end.h" // End of animation utilities diff --git a/code/game/bg_pmove.c b/code/game/bg_pmove.c new file mode 100644 index 0000000..85619e9 --- /dev/null +++ b/code/game/bg_pmove.c @@ -0,0 +1,11214 @@ +// 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" +#include "bg_strap.h" +#include "../ghoul2/G2.h" + +#ifdef QAGAME +#include "g_local.h" //ahahahahhahahaha@$!$! +#endif + +#define MAX_WEAPON_CHARGE_TIME 5000 + +#ifdef QAGAME +extern void G_CheapWeaponFire(int entNum, int ev); +extern qboolean TryGrapple(gentity_t *ent); //g_cmds.c +extern void trap_FX_PlayEffect( const char *file, vec3_t org, vec3_t fwd, int vol, int rad ); +#endif + +#include "../namespace_begin.h" +extern qboolean BG_FullBodyTauntAnim( int anim ); +extern float PM_WalkableGroundDistance(void); +extern qboolean PM_GroundSlideOkay( float zNormal ); +extern saberInfo_t *BG_MySaber( int clientNum, int saberNum ); + +pmove_t *pm; +pml_t pml; + +bgEntity_t *pm_entSelf = NULL; +bgEntity_t *pm_entVeh = NULL; + +qboolean gPMDoSlowFall = qfalse; + +qboolean pm_cancelOutZoom = qfalse; + +// movement parameters +float pm_stopspeed = 100.0f; +float pm_duckScale = 0.50f; +float pm_swimScale = 0.50f; +float pm_wadeScale = 0.70f; + +float pm_vehicleaccelerate = 36.0f; +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; + +float forceSpeedLevels[4] = +{ + 1, //rank 0? + 1.25, + 1.5, + 1.75 +}; + +int forcePowerNeeded[NUM_FORCE_POWER_LEVELS][NUM_FORCE_POWERS] = +{ + { //nothing should be usable at rank 0.. + 999,//FP_HEAL,//instant + 999,//FP_LEVITATION,//hold/duration + 999,//FP_SPEED,//duration + 999,//FP_PUSH,//hold/duration + 999,//FP_PULL,//hold/duration + 999,//FP_TELEPATHY,//instant + 999,//FP_GRIP,//hold/duration + 999,//FP_LIGHTNING,//hold/duration + 999,//FP_RAGE,//duration + 999,//FP_PROTECT,//duration + 999,//FP_ABSORB,//duration + 999,//FP_TEAM_HEAL,//instant + 999,//FP_TEAM_FORCE,//instant + 999,//FP_DRAIN,//hold/duration + 999,//FP_SEE,//duration + 999,//FP_SABER_OFFENSE, + 999,//FP_SABER_DEFENSE, + 999//FP_SABERTHROW, + //NUM_FORCE_POWERS + }, + { + 65,//FP_HEAL,//instant //was 25, but that was way too little + 10,//FP_LEVITATION,//hold/duration + 50,//FP_SPEED,//duration + 20,//FP_PUSH,//hold/duration + 20,//FP_PULL,//hold/duration + 20,//FP_TELEPATHY,//instant + 30,//FP_GRIP,//hold/duration + 1,//FP_LIGHTNING,//hold/duration + 50,//FP_RAGE,//duration + 50,//FP_PROTECT,//duration + 50,//FP_ABSORB,//duration + 50,//FP_TEAM_HEAL,//instant + 50,//FP_TEAM_FORCE,//instant + 20,//FP_DRAIN,//hold/duration + 20,//FP_SEE,//duration + 0,//FP_SABER_OFFENSE, + 2,//FP_SABER_DEFENSE, + 20//FP_SABERTHROW, + //NUM_FORCE_POWERS + }, + { + 60,//FP_HEAL,//instant + 10,//FP_LEVITATION,//hold/duration + 50,//FP_SPEED,//duration + 20,//FP_PUSH,//hold/duration + 20,//FP_PULL,//hold/duration + 20,//FP_TELEPATHY,//instant + 30,//FP_GRIP,//hold/duration + 1,//FP_LIGHTNING,//hold/duration + 50,//FP_RAGE,//duration + 25,//FP_PROTECT,//duration + 25,//FP_ABSORB,//duration + 33,//FP_TEAM_HEAL,//instant + 33,//FP_TEAM_FORCE,//instant + 20,//FP_DRAIN,//hold/duration + 20,//FP_SEE,//duration + 0,//FP_SABER_OFFENSE, + 1,//FP_SABER_DEFENSE, + 20//FP_SABERTHROW, + //NUM_FORCE_POWERS + }, + { + 50,//FP_HEAL,//instant //You get 5 points of health.. for 50 force points! + 10,//FP_LEVITATION,//hold/duration + 50,//FP_SPEED,//duration + 20,//FP_PUSH,//hold/duration + 20,//FP_PULL,//hold/duration + 20,//FP_TELEPATHY,//instant + 60,//FP_GRIP,//hold/duration + 1,//FP_LIGHTNING,//hold/duration + 50,//FP_RAGE,//duration + 10,//FP_PROTECT,//duration + 10,//FP_ABSORB,//duration + 25,//FP_TEAM_HEAL,//instant + 25,//FP_TEAM_FORCE,//instant + 20,//FP_DRAIN,//hold/duration + 20,//FP_SEE,//duration + 0,//FP_SABER_OFFENSE, + 0,//FP_SABER_DEFENSE, + 20//FP_SABERTHROW, + //NUM_FORCE_POWERS + } +}; + +float forceJumpHeight[NUM_FORCE_POWER_LEVELS] = +{ + 32,//normal jump (+stepheight+crouchdiff = 66) + 96,//(+stepheight+crouchdiff = 130) + 192,//(+stepheight+crouchdiff = 226) + 384//(+stepheight+crouchdiff = 418) +}; + +float forceJumpStrength[NUM_FORCE_POWER_LEVELS] = +{ + JUMP_VELOCITY,//normal jump + 420, + 590, + 840 +}; + +//rww - Get a pointer to the bgEntity by the index +bgEntity_t *PM_BGEntForNum( int num ) +{ + bgEntity_t *ent; + + if (!pm) + { + assert(!"You cannot call PM_BGEntForNum outside of pm functions!"); + return NULL; + } + + if (!pm->baseEnt) + { + assert(!"Base entity address not set"); + return NULL; + } + + if (!pm->entSize) + { + assert(!"sizeof(ent) is 0, impossible (not set?)"); + return NULL; + } + + assert(num >= 0 && num < MAX_GENTITIES); + + ent = (bgEntity_t *)((byte *)pm->baseEnt + pm->entSize*(num)); + + return ent; +} + +qboolean BG_SabersOff( playerState_t *ps ) +{ + if ( !ps->saberHolstered ) + { + return qfalse; + } + if ( ps->fd.saberAnimLevelBase == SS_DUAL + || ps->fd.saberAnimLevelBase == SS_STAFF ) + { + if ( ps->saberHolstered < 2 ) + { + return qfalse; + } + } + return qtrue; +} + +qboolean BG_KnockDownable(playerState_t *ps) +{ + if (!ps) + { //just for safety + return qfalse; + } + + if (ps->m_iVehicleNum) + { //riding a vehicle, don't knock me down + return qfalse; + } + + if (ps->emplacedIndex) + { //using emplaced gun or eweb, can't be knocked down + return qfalse; + } + + //ok, I guess? + return qtrue; +} + +//I should probably just do a global inline sometime. +#ifndef __LCC__ +#define PM_INLINE ID_INLINE +#else +#define PM_INLINE //none +#endif + +//hacky assumption check, assume any client non-humanoid is a rocket trooper +qboolean PM_INLINE PM_IsRocketTrooper(void) +{ + /* + if (pm->ps->clientNum < MAX_CLIENTS && + pm->gametype == GT_SIEGE && + pm->nonHumanoid) + { + return qtrue; + } + */ + + return qfalse; +} + +int PM_GetSaberStance(void) +{ + int anim = BOTH_STAND2; + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + + if (!pm->ps->saberEntityNum) + { //lost it + return BOTH_STAND1; + } + + if ( BG_SabersOff( pm->ps ) ) + { + return BOTH_STAND1; + } + + if ( saber1 + && saber1->readyAnim != -1 ) + { + return saber1->readyAnim; + } + + if ( saber2 + && saber2->readyAnim != -1 ) + { + return saber2->readyAnim; + } + + if ( saber1 + && saber2 + && !pm->ps->saberHolstered ) + {//dual sabers, both on + return BOTH_SABERDUAL_STANCE; + } + + switch ( pm->ps->fd.saberAnimLevel ) + { + case SS_DUAL: + anim = BOTH_SABERDUAL_STANCE; + break; + case SS_STAFF: + anim = BOTH_SABERSTAFF_STANCE; + break; + case SS_FAST: + case SS_TAVION: + anim = BOTH_SABERFAST_STANCE; + break; + case SS_STRONG: + anim = BOTH_SABERSLOW_STANCE; + break; + case SS_NONE: + case SS_MEDIUM: + case SS_DESANN: + default: + anim = BOTH_STAND2; + break; + } + return anim; +} + +qboolean PM_DoSlowFall(void) +{ + if ( ( (pm->ps->legsAnim) == BOTH_WALL_RUN_RIGHT || (pm->ps->legsAnim) == BOTH_WALL_RUN_LEFT ) && pm->ps->legsTimer > 500 ) + { + return qtrue; + } + + return qfalse; +} + +//begin vehicle functions crudely ported from sp -rww +/* +==================================================================== +void pitch_roll_for_slope (edict_t *forwhom, vec3_t *slope, vec3_t storeAngles ) + +MG + +This will adjust the pitch and roll of a monster to match +a given slope - if a non-'0 0 0' slope is passed, it will +use that value, otherwise it will use the ground underneath +the monster. If it doesn't find a surface, it does nothinh\g +and returns. +==================================================================== +*/ + +void PM_pitch_roll_for_slope( bgEntity_t *forwhom, vec3_t pass_slope, vec3_t storeAngles ) +{ + vec3_t slope; + vec3_t nvf, ovf, ovr, startspot, endspot, new_angles = { 0, 0, 0 }; + float pitch, mod, dot; + + //if we don't have a slope, get one + if( !pass_slope || VectorCompare( vec3_origin, pass_slope ) ) + { + trace_t trace; + + VectorCopy( pm->ps->origin, startspot ); + startspot[2] += pm->mins[2] + 4; + VectorCopy( startspot, endspot ); + endspot[2] -= 300; + pm->trace( &trace, pm->ps->origin, vec3_origin, vec3_origin, endspot, forwhom->s.number, MASK_SOLID ); +// if(trace_fraction>0.05&&forwhom.movetype==MOVETYPE_STEP) +// forwhom.flags(-)FL_ONGROUND; + + if ( trace.fraction >= 1.0 ) + return; + + if( !( &trace.plane ) ) + return; + + if ( VectorCompare( vec3_origin, trace.plane.normal ) ) + return; + + VectorCopy( trace.plane.normal, slope ); + } + else + { + VectorCopy( pass_slope, slope ); + } + + if ( forwhom->s.NPC_class == CLASS_VEHICLE ) + {//special code for vehicles + Vehicle_t *pVeh = forwhom->m_pVehicle; + vec3_t tempAngles; + + tempAngles[PITCH] = tempAngles[ROLL] = 0; + tempAngles[YAW] = pVeh->m_vOrientation[YAW]; + AngleVectors( tempAngles, ovf, ovr, NULL ); + } + else + { + AngleVectors( pm->ps->viewangles, ovf, ovr, NULL ); + } + + vectoangles( slope, new_angles ); + pitch = new_angles[PITCH] + 90; + new_angles[ROLL] = new_angles[PITCH] = 0; + + AngleVectors( new_angles, nvf, NULL, NULL ); + + mod = DotProduct( nvf, ovr ); + + if ( mod<0 ) + mod = -1; + else + mod = 1; + + dot = DotProduct( nvf, ovf ); + + if ( storeAngles ) + { + storeAngles[PITCH] = dot * pitch; + storeAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + } + else //if ( forwhom->client ) + { + float oldmins2; + + pm->ps->viewangles[PITCH] = dot * pitch; + pm->ps->viewangles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + oldmins2 = pm->mins[2]; + pm->mins[2] = -24 + 12 * fabs(pm->ps->viewangles[PITCH])/180.0f; + //FIXME: if it gets bigger, move up + if ( oldmins2 > pm->mins[2] ) + {//our mins is now lower, need to move up + //FIXME: trace? + pm->ps->origin[2] += (oldmins2 - pm->mins[2]); + //forwhom->currentOrigin[2] = forwhom->client->ps.origin[2]; + //gi.linkentity( forwhom ); + } + } + /* + else + { + forwhom->currentAngles[PITCH] = dot * pitch; + forwhom->currentAngles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); + } + */ +} + +#define FLY_NONE 0 +#define FLY_NORMAL 1 +#define FLY_VEHICLE 2 +#define FLY_HOVER 3 +static int pm_flying = FLY_NONE; + +void PM_SetSpecialMoveValues (void) +{ + bgEntity_t *pEnt; + + if (pm->ps->clientNum < MAX_CLIENTS) + { //we know that real players aren't vehs + pm_flying = FLY_NONE; + return; + } + + //default until we decide otherwise + pm_flying = FLY_NONE; + + pEnt = pm_entSelf; + + if ( pEnt ) + { + if ( (pm->ps->eFlags2&EF2_FLYING) )// pm->gent->client->moveType == MT_FLYSWIM ) + { + pm_flying = FLY_NORMAL; + } + else if ( pEnt->s.NPC_class == CLASS_VEHICLE ) + { + if ( pEnt->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + { + pm_flying = FLY_VEHICLE; + } + else if ( pEnt->m_pVehicle->m_pVehicleInfo->hoverHeight > 0 ) + { + pm_flying = FLY_HOVER; + } + } + } +} + +static void PM_SetVehicleAngles( vec3_t normal ) +{ + bgEntity_t *pEnt = pm_entSelf; + Vehicle_t *pVeh; + vec3_t vAngles; + float vehicleBankingSpeed; + float pitchBias; + int i; + + if ( !pEnt || pEnt->s.NPC_class != CLASS_VEHICLE ) + { + return; + } + + pVeh = pEnt->m_pVehicle; + + //float curVehicleBankingSpeed; + vehicleBankingSpeed = (pVeh->m_pVehicleInfo->bankingSpeed*32.0f)*pml.frametime;//0.25f + + if ( vehicleBankingSpeed <= 0 + || ( pVeh->m_pVehicleInfo->pitchLimit == 0 && pVeh->m_pVehicleInfo->rollLimit == 0 ) ) + {//don't bother, this vehicle doesn't bank + return; + } + //FIXME: do 3 traces to define a plane and use that... smoothes it out some, too... + //pitch_roll_for_slope( pm->gent, normal, vAngles ); + //FIXME: maybe have some pitch control in water and/or air? + + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + pitchBias = 0.0f; + } + else + { + //FIXME: gravity does not matter in SPACE!!! + //center of gravity affects pitch in air/water (FIXME: what about roll?) + pitchBias = 90.0f*pVeh->m_pVehicleInfo->centerOfGravity[0];//if centerOfGravity is all the way back (-1.0f), vehicle pitches up 90 degrees when in air + } + + VectorClear( vAngles ); + if ( pm->waterlevel > 0 ) + {//in water + //view pitch has some influence when in water + //FIXME: take center of gravity into account? + vAngles[PITCH] += (pm->ps->viewangles[PITCH]-vAngles[PITCH])*0.75f + (pitchBias*0.5); + } + else if ( normal ) + {//have a valid surface below me + PM_pitch_roll_for_slope( pEnt, normal, vAngles ); + if ( (pml.groundTrace.contents&(CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) ) + {//on water + //view pitch has some influence when on a fluid surface + //FIXME: take center of gravity into account + vAngles[PITCH] += (pm->ps->viewangles[PITCH]-vAngles[PITCH])*0.5f + (pitchBias*0.5f); + } + } + else + {//in air, let pitch match view...? + //FIXME: take center of gravity into account + vAngles[PITCH] = pm->ps->viewangles[PITCH]*0.5f + pitchBias; + //don't bank so fast when in the air + vehicleBankingSpeed *= (0.125f*pml.frametime); + } + //NOTE: if angles are flat and we're moving through air (not on ground), + // then pitch/bank? + if ( pVeh->m_pVehicleInfo->rollLimit > 0 ) + { + //roll when banking + vec3_t velocity; + float speed; + VectorCopy( pm->ps->velocity, velocity ); + velocity[2] = 0.0f; + speed = VectorNormalize( velocity ); + if ( speed > 32.0f || speed < -32.0f ) + { + vec3_t rt, tempVAngles; + float side; + float dp; + + // Magic number fun! Speed is used for banking, so modulate the speed by a sine wave + //FIXME: this banks too early + speed *= sin( (150 + pml.frametime) * 0.003 ); + + // Clamp to prevent harsh rolling + if ( speed > 60 ) + speed = 60; + + VectorCopy( pVeh->m_vOrientation, tempVAngles ); + tempVAngles[ROLL] = 0; + AngleVectors( tempVAngles, NULL, rt, NULL ); + dp = DotProduct( velocity, rt ); + side = speed * dp; + vAngles[ROLL] -= side; + } + } + + //cap + if ( pVeh->m_pVehicleInfo->pitchLimit != -1 ) + { + if ( vAngles[PITCH] > pVeh->m_pVehicleInfo->pitchLimit ) + { + vAngles[PITCH] = pVeh->m_pVehicleInfo->pitchLimit; + } + else if ( vAngles[PITCH] < -pVeh->m_pVehicleInfo->pitchLimit ) + { + vAngles[PITCH] = -pVeh->m_pVehicleInfo->pitchLimit; + } + } + + if ( vAngles[ROLL] > pVeh->m_pVehicleInfo->rollLimit ) + { + vAngles[ROLL] = pVeh->m_pVehicleInfo->rollLimit; + } + else if ( vAngles[ROLL] < -pVeh->m_pVehicleInfo->rollLimit ) + { + vAngles[ROLL] = -pVeh->m_pVehicleInfo->rollLimit; + } + + //do it + for ( i = 0; i < 3; i++ ) + { + if ( i == YAW ) + {//yawing done elsewhere + continue; + } + //bank faster the higher the difference is + /* + else if ( i == PITCH ) + { + curVehicleBankingSpeed = vehicleBankingSpeed*fabs(AngleNormalize180(AngleSubtract( vAngles[PITCH], pVeh->m_vOrientation[PITCH] )))/(g_vehicleInfo[pm->ps->vehicleIndex].pitchLimit/2.0f); + } + else if ( i == ROLL ) + { + curVehicleBankingSpeed = vehicleBankingSpeed*fabs(AngleNormalize180(AngleSubtract( vAngles[ROLL], pVeh->m_vOrientation[ROLL] )))/(g_vehicleInfo[pm->ps->vehicleIndex].rollLimit/2.0f); + } + + if ( curVehicleBankingSpeed ) + */ + { + if ( pVeh->m_vOrientation[i] >= vAngles[i] + vehicleBankingSpeed ) + { + pVeh->m_vOrientation[i] -= vehicleBankingSpeed; + } + else if ( pVeh->m_vOrientation[i] <= vAngles[i] - vehicleBankingSpeed ) + { + pVeh->m_vOrientation[i] += vehicleBankingSpeed; + } + else + { + pVeh->m_vOrientation[i] = vAngles[i]; + } + } + } +} + +#ifndef QAGAME +extern vmCvar_t cg_paused; +#endif + +void BG_ExternThisSoICanRecompileInDebug( Vehicle_t *pVeh, playerState_t *riderPS ) +{ +/* + float pitchSubtract, pitchDelta, yawDelta; + //Com_Printf( S_COLOR_RED"PITCH: %4.2f, YAW: %4.2f, ROLL: %4.2f\n", riderPS->viewangles[0],riderPS->viewangles[1],riderPS->viewangles[2]); + yawDelta = AngleSubtract(riderPS->viewangles[YAW],pVeh->m_vPrevRiderViewAngles[YAW]); +#ifndef QAGAME + if ( !cg_paused.integer ) + { + //Com_Printf( "%d - yawDelta %4.2f\n", pm->cmd.serverTime, yawDelta ); + } +#endif + yawDelta *= (4.0f*pVeh->m_fTimeModifier); + pVeh->m_vOrientation[ROLL] -= yawDelta; + + pitchDelta = AngleSubtract(riderPS->viewangles[PITCH],pVeh->m_vPrevRiderViewAngles[PITCH]); + pitchDelta *= (2.0f*pVeh->m_fTimeModifier); + pitchSubtract = pitchDelta * (fabs(pVeh->m_vOrientation[ROLL])/90.0f); + pVeh->m_vOrientation[PITCH] += pitchDelta-pitchSubtract; + if ( pVeh->m_vOrientation[ROLL] > 0 ) + { + pVeh->m_vOrientation[YAW] += pitchSubtract; + } + else + { + pVeh->m_vOrientation[YAW] -= pitchSubtract; + } + pVeh->m_vOrientation[PITCH] = AngleNormalize180( pVeh->m_vOrientation[PITCH] ); + pVeh->m_vOrientation[YAW] = AngleNormalize360( pVeh->m_vOrientation[YAW] ); + pVeh->m_vOrientation[ROLL] = AngleNormalize180( pVeh->m_vOrientation[ROLL] ); + + VectorCopy( riderPS->viewangles, pVeh->m_vPrevRiderViewAngles ); +*/ +} + +void BG_VehicleTurnRateForSpeed( Vehicle_t *pVeh, float speed, float *mPitchOverride, float *mYawOverride ) +{ + if ( pVeh && pVeh->m_pVehicleInfo ) + { + float speedFrac = 1.0f; + if ( pVeh->m_pVehicleInfo->speedDependantTurning ) + { + if ( pVeh->m_LandTrace.fraction >= 1.0f + || pVeh->m_LandTrace.plane.normal[2] < MIN_LANDING_SLOPE ) + { + speedFrac = (speed/(pVeh->m_pVehicleInfo->speedMax*0.75f)); + if ( speedFrac < 0.25f ) + { + speedFrac = 0.25f; + } + else if ( speedFrac > 1.0f ) + { + speedFrac = 1.0f; + } + } + } + if ( pVeh->m_pVehicleInfo->mousePitch ) + { + *mPitchOverride = pVeh->m_pVehicleInfo->mousePitch*speedFrac; + } + if ( pVeh->m_pVehicleInfo->mouseYaw ) + { + *mYawOverride = pVeh->m_pVehicleInfo->mouseYaw*speedFrac; + } + } +} + +#include "../namespace_end.h" + +// Following couple things don't belong in the DLL namespace! +#ifdef QAGAME +typedef struct gentity_s gentity_t; +gentity_t *G_PlayEffectID(const int fxID, vec3_t org, vec3_t ang); +#endif + +#include "../namespace_begin.h" + +static void PM_GroundTraceMissed( void ); +void PM_HoverTrace( void ) +{ + Vehicle_t *pVeh; + float hoverHeight; + vec3_t point, vAng, fxAxis[3]; + trace_t *trace; + float relativeWaterLevel; + + bgEntity_t *pEnt = pm_entSelf; + if ( !pEnt || pEnt->s.NPC_class != CLASS_VEHICLE ) + { + return; + } + + pVeh = pEnt->m_pVehicle; + hoverHeight = pVeh->m_pVehicleInfo->hoverHeight; + trace = &pml.groundTrace; + + pml.groundPlane = qfalse; + + //relativeWaterLevel = (pm->ps->waterheight - (pm->ps->origin[2]+pm->mins[2])); + relativeWaterLevel = pm->waterlevel; //I.. guess this works + if ( pm->waterlevel && relativeWaterLevel >= 0 ) + {//in water + if ( pVeh->m_pVehicleInfo->bouyancy <= 0.0f ) + {//sink like a rock + } + else + {//rise up + float floatHeight = (pVeh->m_pVehicleInfo->bouyancy * ((pm->maxs[2]-pm->mins[2])*0.5f)) - (hoverHeight*0.5f);//1.0f should make you float half-in, half-out of water + if ( relativeWaterLevel > floatHeight ) + {//too low, should rise up + pm->ps->velocity[2] += (relativeWaterLevel - floatHeight) * pVeh->m_fTimeModifier; + } + } + //if ( pm->ps->waterheight < pm->ps->origin[2]+pm->maxs[2] ) + if (pm->waterlevel <= 1) + {//part of us is sticking out of water + if ( fabs(pm->ps->velocity[0]) + fabs(pm->ps->velocity[1]) > 100 ) + {//moving at a decent speed + if ( Q_irand( pml.frametime, 100 ) >= 50 ) + {//splash + vec3_t wakeOrg; + + vAng[PITCH] = vAng[ROLL] = 0; + vAng[YAW] = pVeh->m_vOrientation[YAW]; + AngleVectors( vAng, fxAxis[2], fxAxis[1], fxAxis[0] ); + VectorCopy( pm->ps->origin, wakeOrg ); + //wakeOrg[2] = pm->ps->waterheight; + if (pm->waterlevel >= 2) + { + wakeOrg[2] = pm->ps->origin[2]+16; + } + else + { + wakeOrg[2] = pm->ps->origin[2]; + } +#ifdef QAGAME //yeah, this is kind of crappy and makes no use of prediction whatsoever + if ( pVeh->m_pVehicleInfo->iWakeFX ) + { + //G_PlayEffectID( pVeh->m_pVehicleInfo->iWakeFX, wakeOrg, fxAxis[0] ); + //tempent use bad! + G_AddEvent((gentity_t *)pEnt, EV_PLAY_EFFECT_ID, pVeh->m_pVehicleInfo->iWakeFX); + } +#endif + } + } + } + } + else + { + int traceContents; + float minNormal = (float)MIN_WALK_NORMAL; + minNormal = pVeh->m_pVehicleInfo->maxSlope; + + point[0] = pm->ps->origin[0]; + point[1] = pm->ps->origin[1]; + point[2] = pm->ps->origin[2] - hoverHeight; + + //FIXME: check for water, too? If over water, go slower and make wave effect + // If *in* water, go really slow and use bouyancy stat to determine how far below surface to float + + //NOTE: if bouyancy is 2.0f or higher, you float over water like it's solid ground. + // if it's 1.0f, you sink halfway into water. If it's 0, you sink... + traceContents = pm->tracemask; + if ( pVeh->m_pVehicleInfo->bouyancy >= 2.0f ) + {//sit on water + traceContents |= (CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA); + } + pm->trace( trace, pm->ps->origin, pm->mins, pm->maxs, point, pm->ps->clientNum, traceContents ); + if (trace->plane.normal[0] > 0.5f || trace->plane.normal[0] < -0.5f || + trace->plane.normal[1] > 0.5f || trace->plane.normal[1] < -0.5f) + { //steep slanted hill, don't go up it. + float d = fabs(trace->plane.normal[0]); + float e = fabs(trace->plane.normal[1]); + if (e > d) + { + d = e; + } + pm->ps->velocity[2] = -300.0f*d; + } + else if ( trace->plane.normal[2] >= minNormal ) + {//not a steep slope, so push us up + if ( trace->fraction < 1.0f ) + {//push up off ground + float hoverForce = pVeh->m_pVehicleInfo->hoverStrength; + if ( trace->fraction > 0.5f ) + { + pm->ps->velocity[2] += (1.0f-trace->fraction)*hoverForce*pVeh->m_fTimeModifier; + } + else + { + pm->ps->velocity[2] += (0.5f-(trace->fraction*trace->fraction))*hoverForce*2.0f*pVeh->m_fTimeModifier; + } + if ( (trace->contents&(CONTENTS_WATER|CONTENTS_SLIME|CONTENTS_LAVA)) ) + {//hovering on water, make a spash if moving + if ( fabs(pm->ps->velocity[0]) + fabs(pm->ps->velocity[1]) > 100 ) + {//moving at a decent speed + if ( Q_irand( pml.frametime, 100 ) >= 50 ) + {//splash + vAng[PITCH] = vAng[ROLL] = 0; + vAng[YAW] = pVeh->m_vOrientation[YAW]; + AngleVectors( vAng, fxAxis[2], fxAxis[1], fxAxis[0] ); +#ifdef QAGAME + if ( pVeh->m_pVehicleInfo->iWakeFX ) + { + G_PlayEffectID( pVeh->m_pVehicleInfo->iWakeFX, trace->endpos, fxAxis[0] ); + } +#endif + } + } + } + pml.groundPlane = qtrue; + } + } + } + if ( pml.groundPlane ) + { + PM_SetVehicleAngles( pml.groundTrace.plane.normal ); + // We're on the ground. + pVeh->m_ulFlags &= ~VEH_FLYING; + + pVeh->m_vAngularVelocity = 0.0f; + } + else + { + PM_SetVehicleAngles( NULL ); + // We're flying in the air. + pVeh->m_ulFlags |= VEH_FLYING; + //groundTrace + + if (pVeh->m_vAngularVelocity==0.0f) + { + pVeh->m_vAngularVelocity = pVeh->m_vOrientation[YAW] - pVeh->m_vPrevOrientation[YAW]; + if (pVeh->m_vAngularVelocity<-15.0f) + { + pVeh->m_vAngularVelocity = -15.0f; + } + if (pVeh->m_vAngularVelocity> 15.0f) + { + pVeh->m_vAngularVelocity = 15.0f; + } + } + //pVeh->m_vAngularVelocity *= 0.95f; // Angular Velocity Decays Over Time + if (pVeh->m_vAngularVelocity > 0.0f) + { + pVeh->m_vAngularVelocity -= pml.frametime; + if (pVeh->m_vAngularVelocity < 0.0f) + { + pVeh->m_vAngularVelocity = 0.0f; + } + } + else if (pVeh->m_vAngularVelocity < 0.0f) + { + pVeh->m_vAngularVelocity += pml.frametime; + if (pVeh->m_vAngularVelocity > 0.0f) + { + pVeh->m_vAngularVelocity = 0.0f; + } + } + } + PM_GroundTraceMissed(); +} +//end vehicle functions crudely ported from sp -rww + +/* +=============== +PM_AddEvent + +=============== +*/ +void PM_AddEvent( int newEvent ) { + BG_AddPredictableEventToPlayerstate( newEvent, 0, pm->ps ); +} + +void PM_AddEventWithParm( int newEvent, int parm ) +{ + BG_AddPredictableEventToPlayerstate( newEvent, parm, 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_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; + float oldInZ; + int i; + + if ( (pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//no sliding! + VectorCopy( in, out ); + return; + } + oldInZ = in[2]; + + 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; + } + if ( pm->stepSlideFix ) + { + if ( pm->ps->clientNum < MAX_CLIENTS//normal player + && pm->ps->groundEntityNum != ENTITYNUM_NONE//on the ground + && normal[2] < MIN_WALK_NORMAL )//sliding against a steep slope + {//if walking on the ground, don't slide up slopes that are too steep to walk on + out[2] = oldInZ; + } + } +} + + +/* +================== +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; + bgEntity_t *pEnt = NULL; + + 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 + if (pm->ps->pm_type == PM_SPECTATOR) + { + vel[2] = 0; + } + // FIXME: still have z friction underwater? + return; + } + + drop = 0; + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + pEnt = pm_entSelf; + } + + // apply ground friction, even if on ladder + if (pm_flying != FLY_VEHICLE && + pEnt && + pEnt->s.NPC_class == CLASS_VEHICLE && + pEnt->m_pVehicle && + pEnt->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL && + pEnt->m_pVehicle->m_pVehicleInfo->type != VH_WALKER && + pEnt->m_pVehicle->m_pVehicleInfo->friction ) + { + float friction = pEnt->m_pVehicle->m_pVehicleInfo->friction; + if ( !(pm->ps->pm_flags & PMF_TIME_KNOCKBACK) /*&& !(pm->ps->pm_flags & PMF_TIME_NOFRICTION)*/ ) + { + control = speed < pm_stopspeed ? pm_stopspeed : speed; + drop += control*friction*pml.frametime; + /* + if ( Flying == FLY_HOVER ) + { + if ( pm->cmd.rightmove ) + {//if turning, increase friction + control *= 2.0f; + } + if ( pm->ps->groundEntityNum < ENTITYNUM_NONE ) + {//on the ground + drop += control*friction*pml.frametime; + } + else if ( pml.groundPlane ) + {//on a slope + drop += control*friction*2.0f*pml.frametime; + } + else + {//in air + drop += control*2.0f*friction*pml.frametime; + } + } + */ + } + } + else if ( pm_flying != FLY_NORMAL && pm_flying != FLY_VEHICLE ) + { + // 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; + } + } + } + } + + if ( pm_flying == FLY_VEHICLE ) + { + 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; + } + // If on a client then there is no friction + else if ( pm->ps->groundEntityNum < MAX_CLIENTS ) + { + drop = 0; + } + + if ( pm->ps->pm_type == PM_SPECTATOR || pm->ps->pm_type == PM_FLOAT ) + { + if (pm->ps->pm_type == PM_FLOAT) + { //almost no friction while floating + drop += speed*0.1*pml.frametime; + } + else + { + 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 (pm->gametype != GT_SIEGE + || pm->ps->m_iVehicleNum + || pm->ps->clientNum >= MAX_CLIENTS + || pm->ps->pm_type != PM_NORMAL) + { //standard method, allows "bunnyhopping" and whatnot + int i; + float addspeed, accelspeed, currentspeed; + + currentspeed = DotProduct (pm->ps->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0 && pm->ps->clientNum < MAX_CLIENTS) { + return; + } + + if (addspeed < 0) + { + accelspeed = (-accel)*pml.frametime*wishspeed; + if (accelspeed < addspeed) { + accelspeed = addspeed; + } + } + else + { + accelspeed = accel*pml.frametime*wishspeed; + if (accelspeed > addspeed) { + accelspeed = addspeed; + } + } + + for (i=0 ; i<3 ; i++) { + pm->ps->velocity[i] += accelspeed*wishdir[i]; + } + } + else + { //use the proper way for siege + 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 ); + } +} + + + +/* +============ +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; + int umove = 0; //cmd->upmove; + //don't factor upmove into scaling speed + + max = abs( cmd->forwardmove ); + if ( abs( cmd->rightmove ) > max ) { + max = abs( cmd->rightmove ); + } + if ( abs( umove ) > max ) { + max = abs( umove ); + } + if ( !max ) { + return 0; + } + + total = sqrt( (float)(cmd->forwardmove * cmd->forwardmove + + cmd->rightmove * cmd->rightmove + umove * umove) ); + 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; + } + } +} + +#define METROID_JUMP 1 + +qboolean PM_ForceJumpingUp(void) +{ + if ( !(pm->ps->fd.forcePowersActive&(1<ps->fd.forceJumpCharge ) + {//already jumped and let go + return qfalse; + } + + if ( BG_InSpecialJump( pm->ps->legsAnim ) ) + { + return qfalse; + } + + if (BG_SaberInSpecial(pm->ps->saberMove)) + { + return qfalse; + } + + if (BG_SaberInSpecialAttack(pm->ps->legsAnim)) + { + return qfalse; + } + + if (BG_HasYsalamiri(pm->gametype, pm->ps)) + { + return qfalse; + } + + if (!BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION)) + { + return qfalse; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE && //in air + (pm->ps->pm_flags & PMF_JUMP_HELD) && //jumped + pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 && //force-jump capable + pm->ps->velocity[2] > 0 )//going up + { + return qtrue; + } + return qfalse; +} + +static void PM_JumpForDir( void ) +{ + int anim = BOTH_JUMP1; + if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_JUMP1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_JUMPBACK1; + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_JUMPRIGHT1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_JUMPLEFT1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + anim = BOTH_JUMP1; + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + if(!BG_InDeathAnim(pm->ps->legsAnim)) + { + PM_SetAnim(SETANIM_LEGS,anim,SETANIM_FLAG_OVERRIDE, 100); + } +} + +void PM_SetPMViewAngle(playerState_t *ps, vec3_t angle, usercmd_t *ucmd) +{ + int i; + + for (i=0 ; i<3 ; i++) + { // set the delta angle + int cmdAngle; + + cmdAngle = ANGLE2SHORT(angle[i]); + ps->delta_angles[i] = cmdAngle - ucmd->angles[i]; + } + VectorCopy (angle, ps->viewangles); +} + +qboolean PM_AdjustAngleForWallRun( playerState_t *ps, usercmd_t *ucmd, qboolean doMove ) +{ + if (( (ps->legsAnim) == BOTH_WALL_RUN_RIGHT || (ps->legsAnim) == BOTH_WALL_RUN_LEFT ) && ps->legsTimer > 500 ) + {//wall-running and not at end of anim + //stick to wall, if there is one + vec3_t fwd, rt, traceTo, mins, maxs, fwdAngles; + trace_t trace; + float dist, yawAdjust; + + VectorSet(mins, -15, -15, 0); + VectorSet(maxs, 15, 15, 24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, rt, NULL ); + if ( (ps->legsAnim) == BOTH_WALL_RUN_RIGHT ) + { + dist = 128; + yawAdjust = -90; + } + else + { + dist = -128; + yawAdjust = 90; + } + VectorMA( ps->origin, dist, rt, traceTo ); + + pm->trace( &trace, ps->origin, mins, maxs, traceTo, ps->clientNum, MASK_PLAYERSOLID ); + + if ( trace.fraction < 1.0f + && (trace.plane.normal[2] >= 0.0f && trace.plane.normal[2] <= 0.4f) )//&& ent->client->ps.groundEntityNum == ENTITYNUM_NONE ) + { + trace_t trace2; + vec3_t traceTo2; + vec3_t wallRunFwd, wallRunAngles; + + VectorClear( wallRunAngles ); + wallRunAngles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; + AngleVectors( wallRunAngles, wallRunFwd, NULL, NULL ); + + VectorMA( pm->ps->origin, 32, wallRunFwd, traceTo2 ); + pm->trace( &trace2, pm->ps->origin, mins, maxs, traceTo2, pm->ps->clientNum, MASK_PLAYERSOLID ); + if ( trace2.fraction < 1.0f && DotProduct( trace2.plane.normal, wallRunFwd ) <= -0.999f ) + {//wall we can't run on in front of us + trace.fraction = 1.0f;//just a way to get it to kick us off the wall below + } + } + + if ( trace.fraction < 1.0f + && (trace.plane.normal[2] >= 0.0f&&trace.plane.normal[2] <= 0.4f/*MAX_WALL_RUN_Z_NORMAL*/) ) + {//still a wall there + if ( (ps->legsAnim) == BOTH_WALL_RUN_RIGHT ) + { + ucmd->rightmove = 127; + } + else + { + ucmd->rightmove = -127; + } + if ( ucmd->upmove < 0 ) + { + ucmd->upmove = 0; + } + //make me face perpendicular to the wall + ps->viewangles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; + + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); + + ucmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - ps->delta_angles[YAW]; + if ( doMove ) + { + //push me forward + float zVel = ps->velocity[2]; + if ( ps->legsTimer > 500 ) + {//not at end of anim yet + float speed = 175; + if ( ucmd->forwardmove < 0 ) + {//slower + speed = 100; + } + else if ( ucmd->forwardmove > 0 ) + { + speed = 250;//running speed + } + VectorScale( fwd, speed, ps->velocity ); + } + ps->velocity[2] = zVel;//preserve z velocity + //pull me toward the wall, too + VectorMA( ps->velocity, dist, rt, ps->velocity ); + } + ucmd->forwardmove = 0; + return qtrue; + } + else if ( doMove ) + {//stop it + if ( (ps->legsAnim) == BOTH_WALL_RUN_RIGHT ) + { + PM_SetAnim(SETANIM_BOTH, BOTH_WALL_RUN_RIGHT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + else if ( (ps->legsAnim) == BOTH_WALL_RUN_LEFT ) + { + PM_SetAnim(SETANIM_BOTH, BOTH_WALL_RUN_LEFT_STOP, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + } + } + + return qfalse; +} + +qboolean PM_AdjustAnglesForWallRunUpFlipAlt( usercmd_t *ucmd ) +{ +// ucmd->angles[PITCH] = ANGLE2SHORT( pm->ps->viewangles[PITCH] ) - pm->ps->delta_angles[PITCH]; +// ucmd->angles[YAW] = ANGLE2SHORT( pm->ps->viewangles[YAW] ) - pm->ps->delta_angles[YAW]; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, ucmd); + return qtrue; +} + +qboolean PM_AdjustAngleForWallRunUp( playerState_t *ps, usercmd_t *ucmd, qboolean doMove ) +{ + if ( ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START ) + {//wall-running up + //stick to wall, if there is one + vec3_t fwd, traceTo, mins, maxs, fwdAngles; + trace_t trace; + float dist = 128; + + VectorSet(mins, -15,-15,0); + VectorSet(maxs, 15,15,24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + VectorMA( ps->origin, dist, fwd, traceTo ); + pm->trace( &trace, ps->origin, mins, maxs, traceTo, ps->clientNum, MASK_PLAYERSOLID ); + if ( trace.fraction > 0.5f ) + {//hmm, some room, see if there's a floor right here + trace_t trace2; + vec3_t top, bottom; + + VectorCopy( trace.endpos, top ); + top[2] += (pm->mins[2]*-1) + 4.0f; + VectorCopy( top, bottom ); + bottom[2] -= 64.0f; + pm->trace( &trace2, top, pm->mins, pm->maxs, bottom, ps->clientNum, MASK_PLAYERSOLID ); + if ( !trace2.allsolid + && !trace2.startsolid + && trace2.fraction < 1.0f + && trace2.plane.normal[2] > 0.7f )//slope we can stand on + {//cool, do the alt-flip and land on whetever it is we just scaled up + VectorScale( fwd, 100, pm->ps->velocity ); + pm->ps->velocity[2] += 400; + PM_SetAnim(SETANIM_BOTH, BOTH_FORCEWALLRUNFLIP_ALT, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + pm->ps->pm_flags |= PMF_JUMP_HELD; + //ent->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + //ent->client->ps.forcePowersActive |= (1<upmove = 0; + return qfalse; + } + } + + if ( //ucmd->upmove <= 0 && + ps->legsTimer > 0 && + ucmd->forwardmove > 0 && + trace.fraction < 1.0f && + (trace.plane.normal[2] >= 0.0f&&trace.plane.normal[2]<=0.4f/*MAX_WALL_RUN_Z_NORMAL*/) ) + {//still a vertical wall there + //make sure there's not a ceiling above us! + trace_t trace2; + VectorCopy( ps->origin, traceTo ); + traceTo[2] += 64; + pm->trace( &trace2, ps->origin, mins, maxs, traceTo, ps->clientNum, MASK_PLAYERSOLID ); + if ( trace2.fraction < 1.0f ) + {//will hit a ceiling, so force jump-off right now + //NOTE: hits any entity or clip brush in the way, too, not just architecture! + } + else + {//all clear, keep going + //FIXME: don't pull around 90 turns + //FIXME: simulate stepping up steps here, somehow? + ucmd->forwardmove = 127; + if ( ucmd->upmove < 0 ) + { + ucmd->upmove = 0; + } + //make me face the wall + ps->viewangles[YAW] = vectoyaw( trace.plane.normal )+180; + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); + /* + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + */ + ucmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - ps->delta_angles[YAW]; + //if ( ent->s.number || !player_locked ) + if (1) //aslkfhsakf + { + if ( doMove ) + { + //pull me toward the wall + VectorScale( trace.plane.normal, -dist*trace.fraction, ps->velocity ); + //push me up + if ( ps->legsTimer > 200 ) + {//not at end of anim yet + float speed = 300; + /* + if ( ucmd->forwardmove < 0 ) + {//slower + speed = 100; + } + else if ( ucmd->forwardmove > 0 ) + { + speed = 250;//running speed + } + */ + ps->velocity[2] = speed;//preserve z velocity + } + } + } + ucmd->forwardmove = 0; + return qtrue; + } + } + //failed! + if ( doMove ) + {//stop it + VectorScale( fwd, -300.0f, ps->velocity ); + ps->velocity[2] += 200; + //NPC_SetAnim( ent, SETANIM_BOTH, BOTH_FORCEWALLRUNFLIP_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + //why?!?#?#@!%$R@$KR#F:Hdl;asfm + PM_SetAnim(SETANIM_BOTH, BOTH_FORCEWALLRUNFLIP_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + ps->pm_flags |= PMF_JUMP_HELD; + //ent->client->ps.pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + + //FIXME do I need this in mp? + //ent->client->ps.forcePowersActive |= (1<upmove = 0; + //return qtrue; + } + } + return qfalse; +} + +#define JUMP_OFF_WALL_SPEED 200.0f +//nice... +static float BG_ForceWallJumpStrength( void ) +{ + return (forceJumpStrength[FORCE_LEVEL_3]/2.5f); +} + +qboolean PM_AdjustAngleForWallJump( playerState_t *ps, usercmd_t *ucmd, qboolean doMove ) +{ + if ( ( ( BG_InReboundJump( ps->legsAnim ) || BG_InReboundHold( ps->legsAnim ) ) + && ( BG_InReboundJump( ps->torsoAnim ) || BG_InReboundHold( ps->torsoAnim ) ) ) + || (pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//hugging wall, getting ready to jump off + //stick to wall, if there is one + vec3_t checkDir, traceTo, mins, maxs, fwdAngles; + trace_t trace; + float dist = 128.0f, yawAdjust; + + VectorSet(mins, pm->mins[0],pm->mins[1],0); + VectorSet(maxs, pm->maxs[0],pm->maxs[1],24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + switch ( ps->legsAnim ) + { + case BOTH_FORCEWALLREBOUND_RIGHT: + case BOTH_FORCEWALLHOLD_RIGHT: + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + yawAdjust = -90; + break; + case BOTH_FORCEWALLREBOUND_LEFT: + case BOTH_FORCEWALLHOLD_LEFT: + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + VectorScale( checkDir, -1, checkDir ); + yawAdjust = 90; + break; + case BOTH_FORCEWALLREBOUND_FORWARD: + case BOTH_FORCEWALLHOLD_FORWARD: + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + yawAdjust = 180; + break; + case BOTH_FORCEWALLREBOUND_BACK: + case BOTH_FORCEWALLHOLD_BACK: + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + VectorScale( checkDir, -1, checkDir ); + yawAdjust = 0; + break; + default: + //WTF??? + pm->ps->pm_flags &= ~PMF_STUCK_TO_WALL; + return qfalse; + break; + } + if ( pm->debugMelee ) + {//uber-skillz + if ( ucmd->upmove > 0 ) + {//hold on until you let go manually + if ( BG_InReboundHold( ps->legsAnim ) ) + {//keep holding + if ( ps->legsTimer < 150 ) + { + ps->legsTimer = 150; + } + } + else + {//if got to hold part of anim, play hold anim + if ( ps->legsTimer <= 300 ) + { + ps->saberHolstered = 2; + PM_SetAnim( SETANIM_BOTH, BOTH_FORCEWALLRELEASE_FORWARD+(ps->legsAnim-BOTH_FORCEWALLHOLD_FORWARD), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + ps->legsTimer = ps->torsoTimer = 150; + } + } + } + } + VectorMA( ps->origin, dist, checkDir, traceTo ); + pm->trace( &trace, ps->origin, mins, maxs, traceTo, ps->clientNum, MASK_PLAYERSOLID ); + if ( //ucmd->upmove <= 0 && + ps->legsTimer > 100 && + trace.fraction < 1.0f && + fabs(trace.plane.normal[2]) <= 0.2f/*MAX_WALL_GRAB_SLOPE*/ ) + {//still a vertical wall there + //FIXME: don't pull around 90 turns + /* + if ( ent->s.number || !player_locked ) + { + ucmd->forwardmove = 127; + } + */ + if ( ucmd->upmove < 0 ) + { + ucmd->upmove = 0; + } + //align me to the wall + ps->viewangles[YAW] = vectoyaw( trace.plane.normal )+yawAdjust; + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); + /* + if ( ent->client->ps.viewEntity <= 0 || ent->client->ps.viewEntity >= ENTITYNUM_WORLD ) + {//don't clamp angles when looking through a viewEntity + SetClientViewAngle( ent, ent->client->ps.viewangles ); + } + */ + ucmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - ps->delta_angles[YAW]; + //if ( ent->s.number || !player_locked ) + if (1) + { + if ( doMove ) + { + //pull me toward the wall + VectorScale( trace.plane.normal, -128.0f, ps->velocity ); + } + } + ucmd->upmove = 0; + ps->pm_flags |= PMF_STUCK_TO_WALL; + return qtrue; + } + else if ( doMove + && (ps->pm_flags&PMF_STUCK_TO_WALL)) + {//jump off + //push off of it! + ps->pm_flags &= ~PMF_STUCK_TO_WALL; + ps->velocity[0] = ps->velocity[1] = 0; + VectorScale( checkDir, -JUMP_OFF_WALL_SPEED, ps->velocity ); + ps->velocity[2] = BG_ForceWallJumpStrength(); + ps->pm_flags |= PMF_JUMP_HELD;//PMF_JUMPING|PMF_JUMP_HELD; + //G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + ps->fd.forceJumpSound = 1; //this is a stupid thing, i should fix it. + //ent->client->ps.forcePowersActive |= (1<origin[2] < ps->fd.forceJumpZStart) + { + ps->fd.forceJumpZStart = ps->origin[2]; + } + //FIXME do I need this? + + BG_ForcePowerDrain( ps, FP_LEVITATION, 10 ); + //no control for half a second + ps->pm_flags |= PMF_TIME_KNOCKBACK; + ps->pm_time = 500; + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + ucmd->upmove = 127; + + if ( BG_InReboundHold( ps->legsAnim ) ) + {//if was in hold pose, release now + PM_SetAnim( SETANIM_BOTH, BOTH_FORCEWALLRELEASE_FORWARD+(ps->legsAnim-BOTH_FORCEWALLHOLD_FORWARD), SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + } + else + { + //PM_JumpForDir(); + PM_SetAnim(SETANIM_LEGS,BOTH_FORCEJUMP1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART, 0); + } + + //return qtrue; + } + } + ps->pm_flags &= ~PMF_STUCK_TO_WALL; + return qfalse; +} + +//Set the height for when a force jump was started. If it's 0, nuge it up (slight hack to prevent holding jump over slopes) +void PM_SetForceJumpZStart(float value) +{ + pm->ps->fd.forceJumpZStart = value; + if (!pm->ps->fd.forceJumpZStart) + { + pm->ps->fd.forceJumpZStart -= 0.1; + } +} + +float forceJumpHeightMax[NUM_FORCE_POWER_LEVELS] = +{ + 66,//normal jump (32+stepheight(18)+crouchdiff(24) = 74) + 130,//(96+stepheight(18)+crouchdiff(24) = 138) + 226,//(192+stepheight(18)+crouchdiff(24) = 234) + 418//(384+stepheight(18)+crouchdiff(24) = 426) +}; + +void PM_GrabWallForJump( int anim ) +{//NOTE!!! assumes an appropriate anim is being passed in!!! + PM_SetAnim( SETANIM_BOTH, anim, SETANIM_FLAG_RESTART|SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + PM_AddEvent( EV_JUMP );//make sound for grab + pm->ps->pm_flags |= PMF_STUCK_TO_WALL; +} + +/* +============= +PM_CheckJump +============= +*/ +static qboolean PM_CheckJump( void ) +{ + qboolean allowFlips = qtrue; + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *pEnt = pm_entSelf; + + if (pEnt->s.eType == ET_NPC && + pEnt->s.NPC_class == CLASS_VEHICLE) + { //no! + return qfalse; + } + } + + if (pm->ps->forceHandExtend == HANDEXTEND_KNOCKDOWN || + pm->ps->forceHandExtend == HANDEXTEND_PRETHROWN || + pm->ps->forceHandExtend == HANDEXTEND_POSTTHROWN) + { + return qfalse; + } + + if (pm->ps->pm_type == PM_JETPACK) + { //there's no actual jumping while we jetpack + return qfalse; + } + + //Don't allow jump until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return qfalse; + } + + if ( PM_InKnockDown( pm->ps ) || BG_InRoll( pm->ps, pm->ps->legsAnim ) ) + {//in knockdown + return qfalse; + } + + if ( pm->ps->weapon == WP_SABER ) + { + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber1 + && (saber1->saberFlags&SFL_NO_FLIPS) ) + { + allowFlips = qfalse; + } + if ( saber2 + && (saber2->saberFlags&SFL_NO_FLIPS) ) + { + allowFlips = qfalse; + } + } + + if (pm->ps->groundEntityNum != ENTITYNUM_NONE || pm->ps->origin[2] < pm->ps->fd.forceJumpZStart) + { + pm->ps->fd.forcePowersActive &= ~(1<ps->fd.forcePowersActive & (1 << FP_LEVITATION)) + { //Force jump is already active.. continue draining power appropriately until we land. + if (pm->ps->fd.forcePowerDebounce[FP_LEVITATION] < pm->cmd.serverTime) + { + if ( pm->gametype == GT_DUEL + || pm->gametype == GT_POWERDUEL ) + {//jump takes less power + BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 1 ); + } + else + { + BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 5 ); + } + if (pm->ps->fd.forcePowerLevel[FP_LEVITATION] >= FORCE_LEVEL_2) + { + pm->ps->fd.forcePowerDebounce[FP_LEVITATION] = pm->cmd.serverTime + 300; + } + else + { + pm->ps->fd.forcePowerDebounce[FP_LEVITATION] = pm->cmd.serverTime + 200; + } + } + } + + if (pm->ps->forceJumpFlip) + { //Forced jump anim + int anim = BOTH_FORCEINAIR1; + int parts = SETANIM_BOTH; + if ( allowFlips ) + { + if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_FLIP_F; + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_FLIP_B; + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_FLIP_R; + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_FLIP_L; + } + } + else + { + if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_FORCEINAIR1; + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_FORCEINAIRBACK1; + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_FORCEINAIRRIGHT1; + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_FORCEINAIRLEFT1; + } + } + if ( pm->ps->weaponTime ) + {//FIXME: really only care if we're in a saber attack anim... + parts = SETANIM_LEGS; + } + + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 ); + pm->ps->forceJumpFlip = qfalse; + return qtrue; + } +#if METROID_JUMP + if ( pm->waterlevel < 2 ) + { + if ( pm->ps->gravity > 0 ) + {//can't do this in zero-G + if ( PM_ForceJumpingUp() ) + {//holding jump in air + float curHeight = pm->ps->origin[2] - pm->ps->fd.forceJumpZStart; + //check for max force jump level and cap off & cut z vel + if ( ( curHeight<=forceJumpHeight[0] ||//still below minimum jump height + (pm->ps->fd.forcePower&&pm->cmd.upmove>=10) ) &&////still have force power available and still trying to jump up + curHeight < forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]] && + pm->ps->fd.forceJumpZStart)//still below maximum jump height + {//can still go up + if ( curHeight > forceJumpHeight[0] ) + {//passed normal jump height *2? + if ( !(pm->ps->fd.forcePowersActive&(1<ps->fd.forcePowersActive |= (1<ps->fd.forceJumpSound = 1; + //play flip + if ((pm->cmd.forwardmove || pm->cmd.rightmove) && //pushing in a dir + (pm->ps->legsAnim) != BOTH_FLIP_F &&//not already flipping + (pm->ps->legsAnim) != BOTH_FLIP_B && + (pm->ps->legsAnim) != BOTH_FLIP_R && + (pm->ps->legsAnim) != BOTH_FLIP_L + && allowFlips ) + { + int anim = BOTH_FORCEINAIR1; + int parts = SETANIM_BOTH; + + if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_FLIP_F; + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_FLIP_B; + } + else if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_FLIP_R; + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_FLIP_L; + } + if ( pm->ps->weaponTime ) + { + parts = SETANIM_LEGS; + } + + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 ); + } + else if ( pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + { + vec3_t facingFwd, facingRight, facingAngles; + int anim = -1; + float dotR, dotF; + + VectorSet(facingAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( facingAngles, facingFwd, facingRight, NULL ); + dotR = DotProduct( facingRight, pm->ps->velocity ); + dotF = DotProduct( facingFwd, pm->ps->velocity ); + + if ( fabs(dotR) > fabs(dotF) * 1.5 ) + { + if ( dotR > 150 ) + { + anim = BOTH_FORCEJUMPRIGHT1; + } + else if ( dotR < -150 ) + { + anim = BOTH_FORCEJUMPLEFT1; + } + } + else + { + if ( dotF > 150 ) + { + anim = BOTH_FORCEJUMP1; + } + else if ( dotF < -150 ) + { + anim = BOTH_FORCEJUMPBACK1; + } + } + if ( anim != -1 ) + { + int parts = SETANIM_BOTH; + if ( pm->ps->weaponTime ) + {//FIXME: really only care if we're in a saber attack anim... + parts = SETANIM_LEGS; + } + + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 ); + } + } + } + else + { //jump is already active (the anim has started) + if ( pm->ps->legsTimer < 1 ) + {//not in the middle of a legsAnim + int anim = (pm->ps->legsAnim); + int newAnim = -1; + switch ( anim ) + { + case BOTH_FORCEJUMP1: + newAnim = BOTH_FORCELAND1;//BOTH_FORCEINAIR1; + break; + case BOTH_FORCEJUMPBACK1: + newAnim = BOTH_FORCELANDBACK1;//BOTH_FORCEINAIRBACK1; + break; + case BOTH_FORCEJUMPLEFT1: + newAnim = BOTH_FORCELANDLEFT1;//BOTH_FORCEINAIRLEFT1; + break; + case BOTH_FORCEJUMPRIGHT1: + newAnim = BOTH_FORCELANDRIGHT1;//BOTH_FORCEINAIRRIGHT1; + break; + } + if ( newAnim != -1 ) + { + int parts = SETANIM_BOTH; + if ( pm->ps->weaponTime ) + { + parts = SETANIM_LEGS; + } + + PM_SetAnim( parts, newAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150 ); + } + } + } + } + + //need to scale this down, start with height velocity (based on max force jump height) and scale down to regular jump vel + pm->ps->velocity[2] = (forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]]-curHeight)/forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]]*forceJumpStrength[pm->ps->fd.forcePowerLevel[FP_LEVITATION]];//JUMP_VELOCITY; + pm->ps->velocity[2] /= 10; + pm->ps->velocity[2] += JUMP_VELOCITY; + pm->ps->pm_flags |= PMF_JUMP_HELD; + } + else if ( curHeight > forceJumpHeight[0] && curHeight < forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]] - forceJumpHeight[0] ) + {//still have some headroom, don't totally stop it + if ( pm->ps->velocity[2] > JUMP_VELOCITY ) + { + pm->ps->velocity[2] = JUMP_VELOCITY; + } + } + else + { + //pm->ps->velocity[2] = 0; + //rww - changed for the sake of balance in multiplayer + + if ( pm->ps->velocity[2] > JUMP_VELOCITY ) + { + pm->ps->velocity[2] = JUMP_VELOCITY; + } + } + pm->cmd.upmove = 0; + return qfalse; + } + } + } + +#endif + + //Not jumping + if ( pm->cmd.upmove < 10 && pm->ps->groundEntityNum != ENTITYNUM_NONE) { + 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; + } + + if ( pm->ps->gravity <= 0 ) + {//in low grav, you push in the dir you're facing as long as there is something behind you to shove off of + vec3_t forward, back; + trace_t trace; + + AngleVectors( pm->ps->viewangles, forward, NULL, NULL ); + VectorMA( pm->ps->origin, -8, forward, back ); + pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, back, pm->ps->clientNum, pm->tracemask ); + + if ( trace.fraction <= 1.0f ) + { + VectorMA( pm->ps->velocity, JUMP_VELOCITY*2, forward, pm->ps->velocity ); + PM_SetAnim(SETANIM_LEGS,BOTH_FORCEJUMP1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_RESTART, 150); + }//else no surf close enough to push off of + pm->cmd.upmove = 0; + } + else if ( pm->cmd.upmove > 0 && pm->waterlevel < 2 && + pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_0 && + !(pm->ps->pm_flags&PMF_JUMP_HELD) && + (pm->ps->weapon == WP_SABER || pm->ps->weapon == WP_MELEE) && + !PM_IsRocketTrooper() && + !BG_HasYsalamiri(pm->gametype, pm->ps) && + BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION) ) + { + qboolean allowWallRuns = qtrue; + qboolean allowWallFlips = qtrue; + qboolean allowFlips = qtrue; + qboolean allowWallGrabs = qtrue; + if ( pm->ps->weapon == WP_SABER ) + { + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber1 + && (saber1->saberFlags&SFL_NO_WALL_RUNS) ) + { + allowWallRuns = qfalse; + } + if ( saber2 + && (saber2->saberFlags&SFL_NO_WALL_RUNS) ) + { + allowWallRuns = qfalse; + } + if ( saber1 + && (saber1->saberFlags&SFL_NO_WALL_FLIPS) ) + { + allowWallFlips = qfalse; + } + if ( saber2 + && (saber2->saberFlags&SFL_NO_WALL_FLIPS) ) + { + allowWallFlips = qfalse; + } + if ( saber1 + && (saber1->saberFlags&SFL_NO_FLIPS) ) + { + allowFlips = qfalse; + } + if ( saber2 + && (saber2->saberFlags&SFL_NO_FLIPS) ) + { + allowFlips = qfalse; + } + if ( saber1 + && (saber1->saberFlags&SFL_NO_WALL_GRAB) ) + { + allowWallGrabs = qfalse; + } + if ( saber2 + && (saber2->saberFlags&SFL_NO_WALL_GRAB) ) + { + allowWallGrabs = qfalse; + } + } + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//on the ground + //check for left-wall and right-wall special jumps + int anim = -1; + float vertPush = 0; + if ( pm->cmd.rightmove > 0 && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + {//strafing right + if ( pm->cmd.forwardmove > 0 ) + {//wall-run + if ( allowWallRuns ) + { + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.0f; + anim = BOTH_WALL_RUN_RIGHT; + } + } + else if ( pm->cmd.forwardmove == 0 ) + {//wall-flip + if ( allowWallFlips ) + { + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + anim = BOTH_WALL_FLIP_RIGHT; + } + } + } + else if ( pm->cmd.rightmove < 0 && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 ) + {//strafing left + if ( pm->cmd.forwardmove > 0 ) + {//wall-run + if ( allowWallRuns ) + { + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.0f; + anim = BOTH_WALL_RUN_LEFT; + } + } + else if ( pm->cmd.forwardmove == 0 ) + {//wall-flip + if ( allowWallFlips ) + { + vertPush = forceJumpStrength[FORCE_LEVEL_2]/2.25f; + anim = BOTH_WALL_FLIP_LEFT; + } + } + } + else if ( pm->cmd.forwardmove < 0 && !(pm->cmd.buttons&BUTTON_ATTACK) ) + {//backflip + if ( allowFlips ) + { + vertPush = JUMP_VELOCITY; + anim = BOTH_FLIP_BACK1;//BG_PickAnim( BOTH_FLIP_BACK1, BOTH_FLIP_BACK3 ); + } + } + + vertPush += 128; //give them an extra shove + + if ( anim != -1 ) + { + vec3_t fwd, right, traceto, mins, maxs, fwdAngles; + vec3_t idealNormal={0}, wallNormal={0}; + trace_t trace; + qboolean doTrace = qfalse; + int contents = MASK_SOLID;//MASK_PLAYERSOLID; + + VectorSet(mins, pm->mins[0],pm->mins[1],0); + VectorSet(maxs, pm->maxs[0],pm->maxs[1],24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + memset(&trace, 0, sizeof(trace)); //to shut the compiler up + + AngleVectors( fwdAngles, fwd, right, NULL ); + + //trace-check for a wall, if necc. + switch ( anim ) + { + case BOTH_WALL_FLIP_LEFT: + //NOTE: purposely falls through to next case! + case BOTH_WALL_RUN_LEFT: + doTrace = qtrue; + VectorMA( pm->ps->origin, -16, right, traceto ); + break; + + case BOTH_WALL_FLIP_RIGHT: + //NOTE: purposely falls through to next case! + case BOTH_WALL_RUN_RIGHT: + doTrace = qtrue; + VectorMA( pm->ps->origin, 16, right, traceto ); + break; + + case BOTH_WALL_FLIP_BACK1: + doTrace = qtrue; + VectorMA( pm->ps->origin, 16, fwd, traceto ); + break; + } + + if ( doTrace ) + { + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents ); + VectorCopy( trace.plane.normal, wallNormal ); + VectorNormalize( wallNormal ); + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + } + + if ( !doTrace || (trace.fraction < 1.0f && (trace.entityNum < MAX_CLIENTS || DotProduct(wallNormal,idealNormal) > 0.7)) ) + {//there is a wall there.. or hit a client + if ( (anim != BOTH_WALL_RUN_LEFT + && anim != BOTH_WALL_RUN_RIGHT + && anim != BOTH_FORCEWALLRUNFLIP_START) + || (wallNormal[2] >= 0.0f&&wallNormal[2]<=0.4f/*MAX_WALL_RUN_Z_NORMAL*/) ) + {//wall-runs can only run on perfectly flat walls, sorry. + int parts; + //move me to side + if ( anim == BOTH_WALL_FLIP_LEFT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, 150, right, pm->ps->velocity ); + } + else if ( anim == BOTH_WALL_FLIP_RIGHT ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -150, right, pm->ps->velocity ); + } + else if ( anim == BOTH_FLIP_BACK1 + || anim == BOTH_FLIP_BACK2 + || anim == BOTH_FLIP_BACK3 + || anim == BOTH_WALL_FLIP_BACK1 ) + { + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity ); + } + + /* + if ( doTrace && anim != BOTH_WALL_RUN_LEFT && anim != BOTH_WALL_RUN_RIGHT ) + { + if (trace.entityNum < MAX_CLIENTS) + { + pm->ps->forceKickFlip = trace.entityNum+1; //let the server know that this person gets kicked by this client + } + } + */ + + //up + if ( vertPush ) + { + pm->ps->velocity[2] = vertPush; + pm->ps->fd.forcePowersActive |= (1 << FP_LEVITATION); + } + //animate me + parts = SETANIM_LEGS; + if ( anim == BOTH_BUTTERFLY_LEFT ) + { + parts = SETANIM_BOTH; + pm->cmd.buttons&=~BUTTON_ATTACK; + pm->ps->saberMove = LS_NONE; + } + else if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + if ( anim == BOTH_BUTTERFLY_LEFT ) + { + pm->ps->weaponTime = pm->ps->torsoTimer; + } + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->cmd.upmove = 0; + pm->ps->fd.forceJumpSound = 1; + } + } + } + } + else + {//in the air + int legsAnim = pm->ps->legsAnim; + + if ( legsAnim == BOTH_WALL_RUN_LEFT || legsAnim == BOTH_WALL_RUN_RIGHT ) + {//running on a wall + vec3_t right, traceto, mins, maxs, fwdAngles; + trace_t trace; + int anim = -1; + + VectorSet(mins, pm->mins[0], pm->mins[0], 0); + VectorSet(maxs, pm->maxs[0], pm->maxs[0], 24); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, NULL, right, NULL ); + + if ( legsAnim == BOTH_WALL_RUN_LEFT ) + { + if ( pm->ps->legsTimer > 400 ) + {//not at the end of the anim + float animLen = PM_AnimLength( 0, (animNumber_t)BOTH_WALL_RUN_LEFT ); + if ( pm->ps->legsTimer < animLen - 400 ) + {//not at start of anim + VectorMA( pm->ps->origin, -16, right, traceto ); + anim = BOTH_WALL_RUN_LEFT_FLIP; + } + } + } + else if ( legsAnim == BOTH_WALL_RUN_RIGHT ) + { + if ( pm->ps->legsTimer > 400 ) + {//not at the end of the anim + float animLen = PM_AnimLength( 0, (animNumber_t)BOTH_WALL_RUN_RIGHT ); + if ( pm->ps->legsTimer < animLen - 400 ) + {//not at start of anim + VectorMA( pm->ps->origin, 16, right, traceto ); + anim = BOTH_WALL_RUN_RIGHT_FLIP; + } + } + } + if ( anim != -1 ) + { + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.fraction < 1.0f ) + {//flip off wall + int parts = 0; + + if ( anim == BOTH_WALL_RUN_LEFT_FLIP ) + { + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + VectorMA( pm->ps->velocity, 150, right, pm->ps->velocity ); + } + else if ( anim == BOTH_WALL_RUN_RIGHT_FLIP ) + { + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + VectorMA( pm->ps->velocity, -150, right, pm->ps->velocity ); + } + parts = SETANIM_LEGS; + if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + pm->cmd.upmove = 0; + } + } + if ( pm->cmd.upmove != 0 ) + {//jump failed, so don't try to do normal jump code, just return + return qfalse; + } + } + //NEW JKA + else if ( pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START ) + { + vec3_t fwd, traceto, mins, maxs, fwdAngles; + trace_t trace; + int anim = -1; + float animLen; + + VectorSet(mins, pm->mins[0], pm->mins[0], 0.0f); + VectorSet(maxs, pm->maxs[0], pm->maxs[0], 24.0f); + //hmm, did you mean [1] and [1]? + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0.0f); + AngleVectors( fwdAngles, fwd, NULL, NULL ); + + assert(pm_entSelf); //null pm_entSelf would be a Bad Thing + animLen = BG_AnimLength( pm_entSelf->localAnimIndex, BOTH_FORCEWALLRUNFLIP_START ); + if ( pm->ps->legsTimer < animLen - 400 ) + {//not at start of anim + VectorMA( pm->ps->origin, 16, fwd, traceto ); + anim = BOTH_FORCEWALLRUNFLIP_END; + } + if ( anim != -1 ) + { + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID|CONTENTS_BODY ); + if ( trace.fraction < 1.0f ) + {//flip off wall + int parts = SETANIM_LEGS; + + pm->ps->velocity[0] *= 0.5f; + pm->ps->velocity[1] *= 0.5f; + VectorMA( pm->ps->velocity, -300, fwd, pm->ps->velocity ); + pm->ps->velocity[2] += 200; + if ( !pm->ps->weaponTime ) + {//not attacking, set anim on both + parts = SETANIM_BOTH; + } + PM_SetAnim( parts, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + //FIXME: do damage to traceEnt, like above? + //pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + //ha ha, so silly with your silly jumpy fally flags. + pm->cmd.upmove = 0; + PM_AddEvent( EV_JUMP ); + } + } + if ( pm->cmd.upmove != 0 ) + {//jump failed, so don't try to do normal jump code, just return + return qfalse; + } + } + /* + else if ( pm->cmd.forwardmove > 0 //pushing forward + && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 + && pm->ps->velocity[2] > 200 + && PM_GroundDistance() <= 80 //unfortunately we do not have a happy ground timer like SP (this would use up more bandwidth if we wanted prediction workign right), so we'll just use the actual ground distance. + && !BG_InSpecialJump(pm->ps->legsAnim)) + {//run up wall, flip backwards + vec3_t fwd, traceto, mins, maxs, fwdAngles; + trace_t trace; + vec3_t idealNormal; + + VectorSet(mins, pm->mins[0],pm->mins[1],pm->mins[2]); + VectorSet(maxs, pm->maxs[0],pm->maxs[1],pm->maxs[2]); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + VectorMA( pm->ps->origin, 32, fwd, traceto ); + + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, MASK_PLAYERSOLID );//FIXME: clip brushes too? + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + + if ( trace.fraction < 1.0f ) + {//there is a wall there + int parts = SETANIM_LEGS; + + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity ); + pm->ps->velocity[2] += 128; + + if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + PM_SetAnim( parts, BOTH_WALL_FLIP_BACK1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + + pm->ps->legsTimer -= 600; //I force this anim to play to the end to prevent landing on your head and suddenly flipping over. + //It is a bit too long at the end though, so I'll just shorten it. + + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + pm->cmd.upmove = 0; + pm->ps->fd.forceJumpSound = 1; + BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 5 ); + + if (trace.entityNum < MAX_CLIENTS) + { + pm->ps->forceKickFlip = trace.entityNum+1; //let the server know that this person gets kicked by this client + } + } + } + */ + else if ( pm->cmd.forwardmove > 0 //pushing forward + && pm->ps->fd.forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 + && PM_WalkableGroundDistance() <= 80 //unfortunately we do not have a happy ground timer like SP (this would use up more bandwidth if we wanted prediction workign right), so we'll just use the actual ground distance. + && (pm->ps->legsAnim == BOTH_JUMP1 || pm->ps->legsAnim == BOTH_INAIR1 ) )//not in a flip or spin or anything + {//run up wall, flip backwards + if ( allowWallRuns ) + { + //FIXME: have to be moving... make sure it's opposite the wall... or at least forward? + int wallWalkAnim = BOTH_WALL_FLIP_BACK1; + int parts = SETANIM_LEGS; + int contents = MASK_SOLID;//MASK_PLAYERSOLID;//CONTENTS_SOLID; + //qboolean kick = qtrue; + if ( pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_2 ) + { + wallWalkAnim = BOTH_FORCEWALLRUNFLIP_START; + parts = SETANIM_BOTH; + //kick = qfalse; + } + else + { + if ( !pm->ps->weaponTime ) + { + parts = SETANIM_BOTH; + } + } + //if ( PM_HasAnimation( pm->gent, wallWalkAnim ) ) + if (1) //sure, we have it! Because I SAID SO. + { + vec3_t fwd, traceto, mins, maxs, fwdAngles; + trace_t trace; + vec3_t idealNormal; + bgEntity_t *traceEnt; + + VectorSet(mins, pm->mins[0], pm->mins[1], 0.0f); + VectorSet(maxs, pm->maxs[0], pm->maxs[1], 24.0f); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0.0f); + + AngleVectors( fwdAngles, fwd, NULL, NULL ); + VectorMA( pm->ps->origin, 32, fwd, traceto ); + + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, contents );//FIXME: clip brushes too? + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + traceEnt = PM_BGEntForNum(trace.entityNum); + + if ( trace.fraction < 1.0f + &&((trace.entityNums.solid!=SOLID_BMODEL)||DotProduct(trace.plane.normal,idealNormal)>0.7) ) + {//there is a wall there + pm->ps->velocity[0] = pm->ps->velocity[1] = 0; + if ( wallWalkAnim == BOTH_FORCEWALLRUNFLIP_START ) + { + pm->ps->velocity[2] = forceJumpStrength[FORCE_LEVEL_3]/2.0f; + } + else + { + VectorMA( pm->ps->velocity, -150, fwd, pm->ps->velocity ); + pm->ps->velocity[2] += 150.0f; + } + //animate me + PM_SetAnim( parts, wallWalkAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + // pm->ps->pm_flags |= PMF_JUMPING|PMF_SLOW_MO_FALL; + //again with the flags! + //G_SoundOnEnt( pm->gent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + //yucky! + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + pm->cmd.upmove = 0; + pm->ps->fd.forceJumpSound = 1; + BG_ForcePowerDrain( pm->ps, FP_LEVITATION, 5 ); + + //kick if jumping off an ent + /* + if ( kick && traceEnt && (traceEnt->s.eType == ET_PLAYER || traceEnt->s.eType == ET_NPC) ) + { //kick that thang! + pm->ps->forceKickFlip = traceEnt->s.number+1; + } + */ + pm->cmd.rightmove = pm->cmd.forwardmove= 0; + } + } + } + } + else if ( (!BG_InSpecialJump( legsAnim )//not in a special jump anim + ||BG_InReboundJump( legsAnim )//we're already in a rebound + ||BG_InBackFlip( legsAnim ) )//a backflip (needed so you can jump off a wall behind you) + //&& pm->ps->velocity[2] <= 0 + && pm->ps->velocity[2] > -1200 //not falling down very fast + && !(pm->ps->pm_flags&PMF_JUMP_HELD)//have to have released jump since last press + && (pm->cmd.forwardmove||pm->cmd.rightmove)//pushing in a direction + //&& pm->ps->forceRageRecoveryTime < pm->cmd.serverTime //not in a force Rage recovery period + && pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_2//level 3 jump or better + //&& WP_ForcePowerAvailable( pm->gent, FP_LEVITATION, 10 )//have enough force power to do another one + && BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION) + && (pm->ps->origin[2]-pm->ps->fd.forceJumpZStart) < (forceJumpHeightMax[FORCE_LEVEL_3]-(BG_ForceWallJumpStrength()/2.0f)) //can fit at least one more wall jump in (yes, using "magic numbers"... for now) + //&& (pm->ps->legsAnim == BOTH_JUMP1 || pm->ps->legsAnim == BOTH_INAIR1 ) )//not in a flip or spin or anything + ) + {//see if we're pushing at a wall and jump off it if so + if ( allowWallGrabs ) + { + //FIXME: make sure we have enough force power + //FIXME: check to see if we can go any higher + //FIXME: limit to a certain number of these in a row? + //FIXME: maybe don't require a ucmd direction, just check all 4? + //FIXME: should stick to the wall for a second, then push off... + vec3_t checkDir, traceto, mins, maxs, fwdAngles; + trace_t trace; + vec3_t idealNormal; + int anim = -1; + + VectorSet(mins, pm->mins[0], pm->mins[1], 0.0f); + VectorSet(maxs, pm->maxs[0], pm->maxs[1], 24.0f); + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0.0f); + + if ( pm->cmd.rightmove ) + { + if ( pm->cmd.rightmove > 0 ) + { + anim = BOTH_FORCEWALLREBOUND_RIGHT; + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + } + else if ( pm->cmd.rightmove < 0 ) + { + anim = BOTH_FORCEWALLREBOUND_LEFT; + AngleVectors( fwdAngles, NULL, checkDir, NULL ); + VectorScale( checkDir, -1, checkDir ); + } + } + else if ( pm->cmd.forwardmove > 0 ) + { + anim = BOTH_FORCEWALLREBOUND_FORWARD; + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + } + else if ( pm->cmd.forwardmove < 0 ) + { + anim = BOTH_FORCEWALLREBOUND_BACK; + AngleVectors( fwdAngles, checkDir, NULL, NULL ); + VectorScale( checkDir, -1, checkDir ); + } + if ( anim != -1 ) + {//trace in the dir we're pushing in and see if there's a vertical wall there + bgEntity_t *traceEnt; + + VectorMA( pm->ps->origin, 8, checkDir, traceto ); + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID );//FIXME: clip brushes too? + VectorSubtract( pm->ps->origin, traceto, idealNormal ); + VectorNormalize( idealNormal ); + traceEnt = PM_BGEntForNum(trace.entityNum); + if ( trace.fraction < 1.0f + &&fabs(trace.plane.normal[2]) <= 0.2f/*MAX_WALL_GRAB_SLOPE*/ + &&((trace.entityNums.solid!=SOLID_BMODEL)||DotProduct(trace.plane.normal,idealNormal)>0.7) ) + {//there is a wall there + float dot = DotProduct( pm->ps->velocity, trace.plane.normal ); + if ( dot < 1.0f ) + {//can't be heading *away* from the wall! + //grab it! + PM_GrabWallForJump( anim ); + } + } + } + } + } + else + { + //FIXME: if in a butterfly, kick people away? + } + //END NEW JKA + } + } + + /* + if ( pm->cmd.upmove > 0 + && (pm->ps->weapon == WP_SABER || pm->ps->weapon == WP_MELEE) + && !PM_IsRocketTrooper() + && (pm->ps->weaponTime > 0||pm->cmd.buttons&BUTTON_ATTACK) ) + {//okay, we just jumped and we're in an attack + if ( !BG_InRoll( pm->ps, pm->ps->legsAnim ) + && !PM_InKnockDown( pm->ps ) + && !BG_InDeathAnim(pm->ps->legsAnim) + && !BG_FlippingAnim( pm->ps->legsAnim ) + && !PM_SpinningAnim( pm->ps->legsAnim ) + && !BG_SaberInSpecialAttack( pm->ps->torsoAnim ) + && ( BG_SaberInAttack( pm->ps->saberMove ) ) ) + {//not in an anim we shouldn't interrupt + //see if it's not too late to start a special jump-attack + float animLength = PM_AnimLength( 0, (animNumber_t)pm->ps->torsoAnim ); + if ( animLength - pm->ps->torsoTimer < 500 ) + {//just started the saberMove + //check for special-case jump attacks + if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_2 ) + {//using medium attacks + if (PM_GroundDistance() < 32 && + !BG_InSpecialJump(pm->ps->legsAnim)) + { //FLIP AND DOWNWARD ATTACK + //trace_t tr; + + //if (PM_SomeoneInFront(&tr)) + { + PM_SetSaberMove(PM_SaberFlipOverAttackMove()); + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + VectorClear(pml.groundTrace.plane.normal); + + pm->ps->weaponTime = pm->ps->torsoTimer; + } + } + } + else if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_3 ) + {//using strong attacks + if ( pm->cmd.forwardmove > 0 && //going forward + (pm->cmd.buttons & BUTTON_ATTACK) && //must be holding attack still + PM_GroundDistance() < 32 && + !BG_InSpecialJump(pm->ps->legsAnim)) + {//strong attack: jump-hack + PM_SetSaberMove( PM_SaberJumpAttackMove() ); + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + VectorClear(pml.groundTrace.plane.normal); + + pm->ps->weaponTime = pm->ps->torsoTimer; + } + } + } + } + } + */ + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + return qfalse; + } + if ( pm->cmd.upmove > 0 ) + {//no special jumps + pm->ps->velocity[2] = JUMP_VELOCITY; + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + pm->ps->pm_flags |= PMF_JUMP_HELD; + } + + //Jumping + pml.groundPlane = qfalse; + pml.walking = qfalse; + pm->ps->pm_flags |= PMF_JUMP_HELD; + pm->ps->groundEntityNum = ENTITYNUM_NONE; + PM_SetForceJumpZStart(pm->ps->origin[2]); + + PM_AddEvent( EV_JUMP ); + + //Set the animations + if ( pm->ps->gravity > 0 && !BG_InSpecialJump( pm->ps->legsAnim ) ) + { + PM_JumpForDir(); + } + + 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 ); +} + +/* +=================== +PM_FlyVehicleMove + +=================== +*/ +static void PM_FlyVehicleMove( void ) +{ + int i; + vec3_t wishvel; + float wishspeed; + vec3_t wishdir; + float scale; + float zVel; + float fmove = 0.0f, smove = 0.0f; + + // We don't use these here because we pre-calculate the movedir in the vehicle update anyways, and if + // you leave this, you get strange motion during boarding (the player can move the vehicle). + //fmove = pm->cmd.forwardmove; + //smove = pm->cmd.rightmove; + + // normal slowdown + if ( pm->ps->gravity && pm->ps->velocity[2] < 0 && pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//falling + zVel = pm->ps->velocity[2]; + PM_Friction (); + pm->ps->velocity[2] = zVel; + } + else + { + PM_Friction (); + if ( pm->ps->velocity[2] < 0 && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + pm->ps->velocity[2] = 0; // ignore slope movement + } + } + + scale = PM_CmdScale( &pm->cmd ); + + // Get The WishVel And WishSpeed + //------------------------------- + if ( pm->ps->clientNum >= MAX_CLIENTS ) + {//NPC + + // If The UCmds Were Set, But Never Converted Into A MoveDir, Then Make The WishDir From UCmds + //-------------------------------------------------------------------------------------------- + if ((fmove!=0.0f || smove!=0.0f) && VectorCompare(pm->ps->moveDir, vec3_origin)) + { + //gi.Printf("Generating MoveDir\n"); + for ( i = 0 ; i < 3 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + // Otherwise, Use The Move Dir + //----------------------------- + else + { + wishspeed = pm->ps->speed; + VectorScale( pm->ps->moveDir, pm->ps->speed, wishvel ); + VectorCopy( pm->ps->moveDir, wishdir ); + } + } + else + { + 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; + } + + // Handle negative speed. + if ( wishspeed < 0 ) + { + wishspeed = wishspeed * -1.0f; + VectorScale( wishvel, -1.0f, wishvel ); + VectorScale( wishdir, -1.0f, wishdir ); + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize( wishdir ); + + PM_Accelerate( wishdir, wishspeed, 100 ); + + PM_StepSlideMove( 1 ); +} + +/* +=================== +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 ); + + if ( pm->ps->pm_type == PM_SPECTATOR && pm->cmd.buttons & BUTTON_ALT_ATTACK) { + //turbo boost + scale *= 10; + } + + // + // user intentions + // + if ( !scale ) { + wishvel[0] = 0; + wishvel[1] = 0; + wishvel[2] = pm->ps->speed * (pm->cmd.upmove/127.0f); + } 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; + float accelerate; + usercmd_t cmd; + Vehicle_t *pVeh = NULL; + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *pEnt = pm_entSelf; + + if ( pEnt && pEnt->s.NPC_class == CLASS_VEHICLE ) + { + pVeh = pEnt->m_pVehicle; + } + } + + if (pm->ps->pm_type != PM_SPECTATOR) + { +#if METROID_JUMP + PM_CheckJump(); +#else + if (pm->ps->fd.forceJumpZStart && + pm->ps->forceJumpFlip) + { + PM_CheckJump(); + } +#endif + } + 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); + + if ( pVeh && pVeh->m_pVehicleInfo->hoverHeight > 0 ) + {//in a hovering vehicle, have air control + if ( 1 ) + { + wishspeed = pm->ps->speed; + VectorScale( pm->ps->moveDir, pm->ps->speed, wishvel ); + VectorCopy( pm->ps->moveDir, wishdir ); + scale = 1.0f; + } +#if 0 + else + { + float controlMod = 1.0f; + if ( pml.groundPlane ) + {//on a slope of some kind, shouldn't have much control and should slide a lot + controlMod = pml.groundTrace.plane.normal[2]; + } + + vec3_t vfwd, vrt; + vec3_t vAngles; + + VectorCopy( pVeh->m_vOrientation, vAngles ); + vAngles[ROLL] = 0;//since we're a hovercraft, we really don't want to stafe up into the air if we're banking + AngleVectors( vAngles, vfwd, vrt, NULL ); + + float speed = pm->ps->speed; + float strafeSpeed = 0; + + if ( fmove < 0 ) + {//going backwards + if ( speed < 0 ) + {//speed is negative, but since our command is reverse, make speed positive + speed = fabs( speed ); + /* + if ( pml.groundPlane ) + {//on a slope, still have some control + speed = fabs( speed ); + } + else + {//can't reverse in air + speed = 0; + } + */ + } + else if ( speed > 0 ) + {//trying to move back but speed is still positive, so keep moving forward (we'll slow down eventually) + speed = 0; + } + } + + if ( pm->ps->clientNum < MAX_CLIENTS ) + {//do normal adding to wishvel + VectorScale( vfwd, speed*controlMod*(fmove/127.0f), wishvel ); + //just add strafing + if ( pVeh->m_pVehicleInfo->strafePerc ) + {//we can strafe + if ( smove ) + {//trying to strafe + float minSpeed = pVeh->m_pVehicleInfo->speedMax * 0.5f * pVeh->m_pVehicleInfo->strafePerc; + strafeSpeed = fabs(DotProduct( pm->ps->velocity, vfwd ))*pVeh->m_pVehicleInfo->strafePerc; + if ( strafeSpeed < minSpeed ) + { + strafeSpeed = minSpeed; + } + strafeSpeed *= controlMod*((float)(smove))/127.0f; + if ( strafeSpeed < 0 ) + {//pm_accelerate does not understand negative numbers + strafeSpeed *= -1; + VectorScale( vrt, -1, vrt ); + } + //now just add it to actual velocity + PM_Accelerate( vrt, strafeSpeed, pVeh->m_pVehicleInfo->traction ); + } + } + } + else + { + if ( pVeh->m_pVehicleInfo->strafePerc ) + {//we can strafe + if ( pm->ps->clientNum ) + {//alternate control scheme: can strafe + if ( smove ) + { + /* + if ( fmove > 0 ) + {//actively accelerating + strafeSpeed = pm->ps->speed; + } + else + {//not stepping on accelerator, only strafe based on magnitude of current forward velocity + strafeSpeed = fabs(DotProduct( pm->ps->velocity, vfwd )); + } + */ + strafeSpeed = ((float)(smove))/127.0f; + } + } + } + //strafing takes away from forward speed + VectorScale( vfwd, (fmove/127.0f)*(1.0f-pVeh->m_pVehicleInfo->strafePerc), wishvel ); + if ( strafeSpeed ) + { + VectorMA( wishvel, strafeSpeed*pVeh->m_pVehicleInfo->strafePerc, vrt, wishvel ); + } + VectorNormalize( wishvel ); + VectorScale( wishvel, speed*controlMod, wishvel ); + } + } +#endif + } + else if ( gPMDoSlowFall ) + {//no air-control + VectorClear( wishvel ); + } + else if (pm->ps->pm_type == PM_JETPACK) + { //reduced air control while not jetting + for ( i = 0 ; i < 2 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + wishvel[2] = 0; + + if (pm->cmd.upmove <= 0) + { + VectorScale(wishvel, 0.8f, wishvel); + } + else + { //if we are jetting then we have more control than usual + VectorScale(wishvel, 2.0f, wishvel); + } + } + else + { + 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; + + accelerate = pm_airaccelerate; + if ( pVeh && pVeh->m_pVehicleInfo->type == VH_SPEEDER ) + {//speeders have more control in air + //in mid-air + accelerate = pVeh->m_pVehicleInfo->traction; + if ( pml.groundPlane ) + {//on a slope of some kind, shouldn't have much control and should slide a lot + accelerate *= 0.5f; + } + } + // not on ground, so little effect on velocity + PM_Accelerate (wishdir, wishspeed, accelerate); + + // 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 ) + { + if ( !(pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//don't slide when stuck to a wall + if ( PM_GroundSlideOkay( pml.groundTrace.plane.normal[2] ) ) + { + PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, + pm->ps->velocity, OVERCLIP ); + } + } + } + + if ( (pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//no grav when stuck to wall + PM_StepSlideMove( qfalse ); + } + else + { + PM_StepSlideMove( qtrue ); + } +} + +/* +=================== +PM_WalkMove + +=================== +*/ +static void PM_WalkMove( void ) { + int i; + vec3_t wishvel; + float fmove, smove; + vec3_t wishdir; + float wishspeed = 0.0f; + float scale; + usercmd_t cmd; + float accelerate; + float vel; + qboolean npcMovement = qfalse; + + if ( pm->waterlevel > 2 && DotProduct( pml.forward, pml.groundTrace.plane.normal ) > 0 ) { + // begin swimming + PM_WaterMove(); + return; + } + + + if (pm->ps->pm_type != PM_SPECTATOR) + { + 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); + + // Get The WishVel And WishSpeed + //------------------------------- + if ( pm->ps->clientNum >= MAX_CLIENTS && !VectorCompare( pm->ps->moveDir, vec3_origin ) ) + {//NPC + bgEntity_t *pEnt = pm_entSelf; + + if (pEnt && pEnt->s.NPC_class == CLASS_VEHICLE) + { + // If The UCmds Were Set, But Never Converted Into A MoveDir, Then Make The WishDir From UCmds + //-------------------------------------------------------------------------------------------- + if ((fmove!=0.0f || smove!=0.0f) && VectorCompare(pm->ps->moveDir, vec3_origin)) + { + //gi.Printf("Generating MoveDir\n"); + for ( i = 0 ; i < 3 ; i++ ) + { + wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; + } + + VectorCopy( wishvel, wishdir ); + wishspeed = VectorNormalize(wishdir); + wishspeed *= scale; + } + // Otherwise, Use The Move Dir + //----------------------------- + else + { + //wishspeed = pm->ps->speed; + VectorScale( pm->ps->moveDir, pm->ps->speed, wishvel ); + VectorCopy (wishvel, wishdir); + wishspeed = VectorNormalize(wishdir); + } + + npcMovement = qtrue; + } + } + + if (!npcMovement) + { + 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 + + 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; + } + } + else if ( (pm->ps->pm_flags & PMF_ROLLING) && !BG_InRoll(pm->ps, pm->ps->legsAnim) && + !PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { + 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 ( pm_flying == FLY_HOVER ) + { + accelerate = pm_vehicleaccelerate; + } + else 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); + /* + if (pm->ps->clientNum >= MAX_CLIENTS) + { +#ifdef QAGAME + Com_Printf("^1S: %f, %f\n", wishspeed, pm->ps->speed); +#else + Com_Printf("^2C: %f, %f\n", wishspeed, pm->ps->speed); +#endif + } + */ + + //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; + } + + 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 ); + if (pm->cmd.buttons & BUTTON_ATTACK) { //turbo boost + scale *= 10; + } + if (pm->cmd.buttons & BUTTON_ALT_ATTACK) { //turbo boost + scale *= 10; + } + + 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; + } + return ( pml.groundTrace.surfaceFlags & MATERIAL_MASK ); +} + +extern qboolean PM_CanRollFromSoulCal( playerState_t *ps ); +static int PM_TryRoll( void ) +{ + trace_t trace; + int anim = -1; + vec3_t fwd, right, traceto, mins, maxs, fwdAngles; + + if ( BG_SaberInAttack( pm->ps->saberMove ) || BG_SaberInSpecialAttack( pm->ps->torsoAnim ) + || BG_SpinningSaberAnim( pm->ps->legsAnim ) + || PM_SaberInStart( pm->ps->saberMove ) ) + {//attacking or spinning (or, if player, starting an attack) + if ( PM_CanRollFromSoulCal( pm->ps ) ) + {//hehe + } + else + { + return 0; + } + } + + if ((pm->ps->weapon != WP_SABER && pm->ps->weapon != WP_MELEE) || + PM_IsRocketTrooper() || + BG_HasYsalamiri(pm->gametype, pm->ps) || + !BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_LEVITATION)) + { //Not using saber, or can't use jump + return 0; + } + + if ( pm->ps->weapon == WP_SABER ) + { + saberInfo_t *saber = BG_MySaber( pm->ps->clientNum, 0 ); + if ( saber + && (saber->saberFlags&SFL_NO_ROLLS) ) + { + return 0; + } + saber = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber + && (saber->saberFlags&SFL_NO_ROLLS) ) + { + return 0; + } + } + + VectorSet(mins, pm->mins[0],pm->mins[1],pm->mins[2]+STEPSIZE); + VectorSet(maxs, pm->maxs[0],pm->maxs[1],pm->ps->crouchheight); + + VectorSet(fwdAngles, 0, pm->ps->viewangles[YAW], 0); + + AngleVectors( fwdAngles, fwd, right, NULL ); + + if ( pm->cmd.forwardmove ) + { //check forward/backward rolls + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + anim = BOTH_ROLL_B; + VectorMA( pm->ps->origin, -64, fwd, traceto ); + } + else + { + anim = BOTH_ROLL_F; + VectorMA( pm->ps->origin, 64, fwd, traceto ); + } + } + else if ( pm->cmd.rightmove > 0 ) + { //right + anim = BOTH_ROLL_R; + VectorMA( pm->ps->origin, 64, right, traceto ); + } + else if ( pm->cmd.rightmove < 0 ) + { //left + anim = BOTH_ROLL_L; + VectorMA( pm->ps->origin, -64, right, traceto ); + } + + if ( anim != -1 ) + { //We want to roll. Perform a trace to see if we can, and if so, send us into one. + pm->trace( &trace, pm->ps->origin, mins, maxs, traceto, pm->ps->clientNum, CONTENTS_SOLID ); + if ( trace.fraction >= 1.0f ) + { + pm->ps->saberMove = LS_NONE; + return anim; + } + } + return 0; +} + +#ifdef QAGAME +static void PM_CrashLandEffect( void ) +{ + float delta; + if ( pm->waterlevel ) + { + return; + } + delta = fabs(pml.previous_velocity[2])/10;//VectorLength( pml.previous_velocity );? + if ( delta >= 30 ) + { + vec3_t bottom; + int effectID = -1; + int material = (pml.groundTrace.surfaceFlags&MATERIAL_MASK); + VectorSet( bottom, pm->ps->origin[0],pm->ps->origin[1],pm->ps->origin[2]+pm->mins[2]+1 ); + switch ( material ) + { + case MATERIAL_MUD: + effectID = EFFECT_LANDING_MUD; + break; + case MATERIAL_SAND: + effectID = EFFECT_LANDING_SAND; + break; + case MATERIAL_DIRT: + effectID = EFFECT_LANDING_DIRT; + break; + case MATERIAL_SNOW: + effectID = EFFECT_LANDING_SNOW; + break; + case MATERIAL_GRAVEL: + effectID = EFFECT_LANDING_GRAVEL; + break; + } + + if ( effectID != -1 ) + { + G_PlayEffect( effectID, bottom, pml.groundTrace.plane.normal ); + } + } +} +#endif +/* +================= +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; + qboolean didRoll = qfalse; + + // 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 ) { + pm->ps->inAirAnim = qfalse; + return; + } + t = (-b - sqrt( den ) ) / ( 2 * a ); + + delta = vel + t * acc; + delta = delta*delta * 0.0001; + +#ifdef QAGAME + PM_CrashLandEffect(); +#endif + // ducking while falling doubles damage + if ( pm->ps->pm_flags & PMF_DUCKED ) { + delta *= 2; + } + + if (pm->ps->legsAnim == BOTH_A7_KICK_F_AIR || + pm->ps->legsAnim == BOTH_A7_KICK_B_AIR || + pm->ps->legsAnim == BOTH_A7_KICK_R_AIR || + pm->ps->legsAnim == BOTH_A7_KICK_L_AIR) + { + int landAnim = -1; + switch ( pm->ps->legsAnim ) + { + case BOTH_A7_KICK_F_AIR: + landAnim = BOTH_FORCELAND1; + break; + case BOTH_A7_KICK_B_AIR: + landAnim = BOTH_FORCELANDBACK1; + break; + case BOTH_A7_KICK_R_AIR: + landAnim = BOTH_FORCELANDRIGHT1; + break; + case BOTH_A7_KICK_L_AIR: + landAnim = BOTH_FORCELANDLEFT1; + break; + } + if ( landAnim != -1 ) + { + if ( pm->ps->torsoAnim == pm->ps->legsAnim ) + { + PM_SetAnim(SETANIM_BOTH, landAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + else + { + PM_SetAnim(SETANIM_LEGS, landAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + } + } + else if (pm->ps->legsAnim == BOTH_FORCEJUMPLEFT1 || + pm->ps->legsAnim == BOTH_FORCEJUMPRIGHT1 || + pm->ps->legsAnim == BOTH_FORCEJUMPBACK1 || + pm->ps->legsAnim == BOTH_FORCEJUMP1) + { + int fjAnim; + switch (pm->ps->legsAnim) + { + case BOTH_FORCEJUMPLEFT1: + fjAnim = BOTH_LANDLEFT1; + break; + case BOTH_FORCEJUMPRIGHT1: + fjAnim = BOTH_LANDRIGHT1; + break; + case BOTH_FORCEJUMPBACK1: + fjAnim = BOTH_LANDBACK1; + break; + default: + fjAnim = BOTH_LAND1; + break; + } + PM_SetAnim(SETANIM_BOTH, fjAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + // decide which landing animation to use + else if (!BG_InRoll(pm->ps, pm->ps->legsAnim) && pm->ps->inAirAnim && !pm->ps->m_iVehicleNum) + { //only play a land animation if we transitioned into an in-air animation while off the ground + if (!BG_SaberInSpecial(pm->ps->saberMove)) + { + if ( pm->ps->pm_flags & PMF_BACKWARDS_JUMP ) { + PM_ForceLegsAnim( BOTH_LANDBACK1 ); + } else { + PM_ForceLegsAnim( BOTH_LAND1 ); + } + } + } + + if (pm->ps->weapon != WP_SABER && pm->ps->weapon != WP_MELEE && !PM_IsRocketTrooper()) + { //saber handles its own anims + //This will push us back into our weaponready stance from the land anim. + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + PM_StartTorsoAnim( TORSO_WEAPONREADY4 ); + } + else + { + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + else + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + } + + if (!BG_InSpecialJump(pm->ps->legsAnim) || + pm->ps->legsTimer < 1 || + (pm->ps->legsAnim) == BOTH_WALL_RUN_LEFT || + (pm->ps->legsAnim) == BOTH_WALL_RUN_RIGHT) + { //Only set the timer if we're in an anim that can be interrupted (this would not be, say, a flip) + if (!BG_InRoll(pm->ps, pm->ps->legsAnim) && pm->ps->inAirAnim) + { + if (!BG_SaberInSpecial(pm->ps->saberMove) || pm->ps->weapon != WP_SABER) + { + if (pm->ps->legsAnim != BOTH_FORCELAND1 && + pm->ps->legsAnim != BOTH_FORCELANDBACK1 && + pm->ps->legsAnim != BOTH_FORCELANDRIGHT1 && + pm->ps->legsAnim != BOTH_FORCELANDLEFT1) + { //don't override if we have started a force land + pm->ps->legsTimer = TIMER_LAND; + } + } + } + } + + pm->ps->inAirAnim = qfalse; + + if (pm->ps->m_iVehicleNum) + { //don't do fall stuff while on a vehicle + return; + } + + // 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; + } + + if ( pm->ps->pm_flags & PMF_DUCKED ) + { + if( delta >= 2 && !PM_InOnGroundAnim( pm->ps->legsAnim ) && !PM_InKnockDown( pm->ps ) && !BG_InRoll(pm->ps, pm->ps->legsAnim) && + pm->ps->forceHandExtend == HANDEXTEND_NONE ) + {//roll! + int anim = PM_TryRoll(); + + if (PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { + anim = 0; + pm->ps->legsTimer = 0; + pm->ps->legsAnim = 0; + PM_SetAnim(SETANIM_BOTH,BOTH_LAND1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150); + pm->ps->legsTimer = TIMER_LAND; + } + + if ( anim ) + {//absorb some impact + pm->ps->legsTimer = 0; + delta /= 3; // /= 2 just cancels out the above delta *= 2 when landing while crouched, the roll itself should absorb a little damage + pm->ps->legsAnim = 0; + if (pm->ps->torsoAnim == BOTH_A7_SOULCAL) + { //get out of it on torso + pm->ps->torsoTimer = 0; + } + PM_SetAnim(SETANIM_BOTH,anim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150); + didRoll = qtrue; + } + } + } + + // 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 > 7) + { + int delta_send = (int)delta; + + if (delta_send > 600) + { //will never need to know any value above this + delta_send = 600; + } + + if (pm->ps->fd.forceJumpZStart) + { + if ((int)pm->ps->origin[2] >= (int)pm->ps->fd.forceJumpZStart) + { //was force jumping, landed on higher or same level as when force jump was started + if (delta_send > 8) + { + delta_send = 8; + } + } + else + { + if (delta_send > 8) + { + int dif = ((int)pm->ps->fd.forceJumpZStart - (int)pm->ps->origin[2]); + int dmgLess = (forceJumpHeight[pm->ps->fd.forcePowerLevel[FP_LEVITATION]] - dif); + + if (dmgLess < 0) + { + dmgLess = 0; + } + + delta_send -= (dmgLess*0.3); + + if (delta_send < 8) + { + delta_send = 8; + } + + //Com_Printf("Damage sub: %i\n", (int)((dmgLess*0.1))); + } + } + } + + if (didRoll) + { //Add the appropriate event.. + PM_AddEventWithParm( EV_ROLL, delta_send ); + } + else + { + PM_AddEventWithParm( EV_FALL, delta_send ); + } + } + else + { + if (didRoll) + { + PM_AddEventWithParm( EV_ROLL, 0 ); + } + else + { + PM_AddEventWithParm( EV_FOOTSTEP, PM_FootstepForSurface() ); + } + } + } + + // make sure velocity resets so we don't bounce back up again in case we miss the clear elsewhere + pm->ps->velocity[2] = 0; + + // start footstep cycle over + pm->ps->bobCycle = 0; +} + +/* +============= +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; + + //rww - don't want to do this when handextend_choke, because you can be standing on the ground + //while still holding your throat. + if ( pm->ps->pm_type == PM_FLOAT ) + { + //we're assuming this is because you're being choked + int parts = SETANIM_LEGS; + + //rww - also don't use SETANIM_FLAG_HOLD, it will cause the legs to float around a bit before going into + //a proper anim even when on the ground. + PM_SetAnim(parts, BOTH_CHOKE3, SETANIM_FLAG_OVERRIDE, 100); + } + else if ( pm->ps->pm_type == PM_JETPACK ) + {//jetpacking + //rww - also don't use SETANIM_FLAG_HOLD, it will cause the legs to float around a bit before going into + //a proper anim even when on the ground. + //PM_SetAnim(SETANIM_LEGS,BOTH_FORCEJUMP1,SETANIM_FLAG_OVERRIDE, 100); + } + //If the anim is choke3, act like we just went into the air because we aren't in a float + else if ( pm->ps->groundEntityNum != ENTITYNUM_NONE || (pm->ps->legsAnim) == BOTH_CHOKE3 ) + { + // 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 || pm->ps->pm_type == PM_FLOAT ) { + if ( pm->ps->velocity[2] <= 0 && !(pm->ps->pm_flags&PMF_JUMP_HELD)) + { + //PM_SetAnim(SETANIM_LEGS,BOTH_INAIR1,SETANIM_FLAG_OVERRIDE, 100); + PM_SetAnim(SETANIM_LEGS,BOTH_INAIR1,0, 100); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else if ( pm->cmd.forwardmove >= 0 ) + { + PM_SetAnim(SETANIM_LEGS,BOTH_JUMP1,SETANIM_FLAG_OVERRIDE, 100); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } + else + { + PM_SetAnim(SETANIM_LEGS,BOTH_JUMPBACK1,SETANIM_FLAG_OVERRIDE, 100); + pm->ps->pm_flags |= PMF_BACKWARDS_JUMP; + } + + pm->ps->inAirAnim = qtrue; + } + } + else if (!pm->ps->inAirAnim) + { + // 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 || pm->ps->pm_type == PM_FLOAT ) + { + pm->ps->inAirAnim = qtrue; + } + } + + if (PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { //Client won't catch an animation restart because it only checks frame against incoming frame, so if you roll when you land after rolling + //off of something it won't replay the roll anim unless we switch it off in the air. This fixes that. + PM_SetAnim(SETANIM_BOTH,BOTH_INAIR1,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150); + pm->ps->inAirAnim = qtrue; + } + + pm->ps->groundEntityNum = ENTITYNUM_NONE; + pml.groundPlane = qfalse; + pml.walking = qfalse; +} + + +/* +============= +PM_GroundTrace +============= +*/ +static void PM_GroundTrace( void ) { + vec3_t point; + trace_t trace; + float minNormal = (float)MIN_WALK_NORMAL; + + if ( pm->ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *pEnt = pm_entSelf; + + if (pEnt && pEnt->s.NPC_class == CLASS_VEHICLE) + { + minNormal = pEnt->m_pVehicle->m_pVehicleInfo->maxSlope; + } + } + + 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 (pm->ps->pm_type == PM_FLOAT || pm->ps->pm_type == PM_JETPACK) + { + PM_GroundTraceMissed(); + pml.groundPlane = qfalse; + pml.walking = qfalse; + 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( BOTH_JUMP1 ); + pm->ps->pm_flags &= ~PMF_BACKWARDS_JUMP; + } else { + PM_ForceLegsAnim( BOTH_JUMPBACK1 ); + 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] < minNormal ) { + if ( pm->debugLevel ) { + Com_Printf("%i:steep\n", c_pmove); + } + 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(); + +#ifdef QAGAME + if (pm->ps->clientNum < MAX_CLIENTS && + !pm->ps->m_iVehicleNum && + trace.entityNum < ENTITYNUM_WORLD && + trace.entityNum >= MAX_CLIENTS && + !pm->ps->zoomMode && + pm_entSelf) + { //check if we landed on a vehicle + gentity_t *trEnt = &g_entities[trace.entityNum]; + if (trEnt->inuse && trEnt->client && trEnt->s.eType == ET_NPC && trEnt->s.NPC_class == CLASS_VEHICLE && + !trEnt->client->ps.m_iVehicleNum && + trEnt->m_pVehicle && + trEnt->m_pVehicle->m_pVehicleInfo->type != VH_WALKER && + trEnt->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) + { //it's a vehicle alright, let's board it.. if it's not an atst or ship + if (!BG_SaberInSpecial(pm->ps->saberMove) && + pm->ps->forceHandExtend == HANDEXTEND_NONE && + pm->ps->weaponTime <= 0) + { + gentity_t *servEnt = (gentity_t *)pm_entSelf; + if (g_gametype.integer < GT_TEAM || + !trEnt->alliedTeam || + (trEnt->alliedTeam == servEnt->client->sess.sessionTeam)) + { //not belonging to a team, or client is on same team + trEnt->m_pVehicle->m_pVehicleInfo->Board(trEnt->m_pVehicle, pm_entSelf); + } + } + } + } +#endif + + // 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; + pm->ps->lastOnGround = pm->cmd.serverTime; + + PM_AddTouchEnt( trace.entityNum ); +} + + +/* +============= +PM_SetWaterLevel +============= +*/ +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; + } + } + } + +} + +qboolean PM_CheckDualForwardJumpDuck( void ) +{ + qboolean resized = qfalse; + if ( pm->ps->legsAnim == BOTH_JUMPATTACK6 ) + { + //dynamically reduce bounding box to let character sail over heads of enemies + if ( ( pm->ps->legsTimer >= 1450 + && PM_AnimLength( 0, BOTH_JUMPATTACK6 ) - pm->ps->legsTimer >= 400 ) + ||(pm->ps->legsTimer >= 400 + && PM_AnimLength( 0, BOTH_JUMPATTACK6 ) - pm->ps->legsTimer >= 1100 ) ) + {//in a part of the anim that we're pretty much sideways in, raise up the mins + pm->mins[2] = 0; + pm->ps->pm_flags |= PMF_FIX_MINS; + resized = qtrue; + } + } + return resized; +} + +void PM_CheckFixMins( void ) +{ + if ( (pm->ps->pm_flags&PMF_FIX_MINS) )// pm->mins[2] > DEFAULT_MINS_2 ) + {//drop the mins back down + //do a trace to make sure it's okay + trace_t trace; + vec3_t end, curMins, curMaxs; + + VectorSet( end, pm->ps->origin[0], pm->ps->origin[1], pm->ps->origin[2]+MINS_Z ); + VectorSet( curMins, pm->mins[0], pm->mins[1], 0 ); + VectorSet( curMaxs, pm->maxs[0], pm->maxs[1], pm->ps->standheight ); + + pm->trace( &trace, pm->ps->origin, curMins, curMaxs, end, pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid && !trace.startsolid ) + {//should never start in solid + if ( trace.fraction >= 1.0f ) + {//all clear + //drop the bottom of my bbox back down + pm->mins[2] = MINS_Z; + pm->ps->pm_flags &= ~PMF_FIX_MINS; + } + else + {//move me up so the bottom of my bbox will be where the trace ended, at least + //need to trace up, too + float updist = ((1.0f-trace.fraction) * -MINS_Z); + end[2] = pm->ps->origin[2]+updist; + pm->trace( &trace, pm->ps->origin, curMins, curMaxs, end, pm->ps->clientNum, pm->tracemask ); + if ( !trace.allsolid && !trace.startsolid ) + {//should never start in solid + if ( trace.fraction >= 1.0f ) + {//all clear + //move me up + pm->ps->origin[2] += updist; + //drop the bottom of my bbox back down + pm->mins[2] = MINS_Z; + pm->ps->pm_flags &= ~PMF_FIX_MINS; + } + else + {//crap, no room to expand, so just crouch us + if ( pm->ps->legsAnim != BOTH_JUMPATTACK6 + || pm->ps->legsTimer <= 200 ) + {//at the end of the anim, and we can't leave ourselves like this + //so drop the maxs, put the mins back and move us up + pm->maxs[2] += MINS_Z; + pm->ps->origin[2] -= MINS_Z; + pm->mins[2] = MINS_Z; + //this way we'll be in a crouch when we're done + if ( pm->ps->legsAnim == BOTH_JUMPATTACK6 ) + { + pm->ps->legsTimer = pm->ps->torsoTimer = 0; + } + pm->ps->pm_flags |= PMF_DUCKED; + //FIXME: do we need to set a crouch anim here? + pm->ps->pm_flags &= ~PMF_FIX_MINS; + } + } + }//crap, stuck + } + }//crap, stuck! + } +} + +/* +============== +PM_CheckDuck + +Sets mins, maxs, and pm->ps->viewheight +============== +*/ +static void PM_CheckDuck (void) +{ + trace_t trace; + + if ( pm->ps->m_iVehicleNum > 0 && pm->ps->m_iVehicleNum < ENTITYNUM_NONE ) + {//riding a vehicle or are a vehicle + //no ducking or rolling when on a vehicle + //right? not even on ones that you just ride on top of? + pm->ps->pm_flags &= ~PMF_DUCKED; + pm->ps->pm_flags &= ~PMF_ROLLING; + //NOTE: we don't clear the pm->cmd.upmove here because + //the vehicle code may need it later... but, for riders, + //it should have already been copied over to the vehicle, right? + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + return; + } + if (pm_entVeh && pm_entVeh->m_pVehicle && + (pm_entVeh->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER || + pm_entVeh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)) + { + trace_t solidTr; + + pm->mins[0] = -16; + pm->mins[1] = -16; + pm->mins[2] = MINS_Z; + + pm->maxs[0] = 16; + pm->maxs[1] = 16; + pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + + pm->trace (&solidTr, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->m_iVehicleNum, pm->tracemask); + if (solidTr.startsolid || solidTr.allsolid || solidTr.fraction != 1.0f) + { //whoops, can't fit here. Down to 0! + VectorClear(pm->mins); + VectorClear(pm->maxs); +#ifdef QAGAME + { + gentity_t *me = &g_entities[pm->ps->clientNum]; + if (me->inuse && me->client) + { //yeah, this is a really terrible hack. + me->client->solidHack = level.time + 200; + } + } +#endif + } + } + } + else + { + if (pm->ps->clientNum < MAX_CLIENTS) + { + pm->mins[0] = -15; + pm->mins[1] = -15; + + pm->maxs[0] = 15; + pm->maxs[1] = 15; + } + + if ( PM_CheckDualForwardJumpDuck() ) + {//special anim resizing us + } + else + { + PM_CheckFixMins(); + + if ( !pm->mins[2] ) + { + pm->mins[2] = MINS_Z; + } + } + + if (pm->ps->pm_type == PM_DEAD && pm->ps->clientNum < MAX_CLIENTS) + { + pm->maxs[2] = -8; + pm->ps->viewheight = DEAD_VIEWHEIGHT; + return; + } + + if (BG_InRoll(pm->ps, pm->ps->legsAnim) && !BG_KickingAnim(pm->ps->legsAnim)) + { + pm->maxs[2] = pm->ps->crouchheight; //CROUCH_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + pm->ps->pm_flags &= ~PMF_DUCKED; + pm->ps->pm_flags |= PMF_ROLLING; + return; + } + else if (pm->ps->pm_flags & PMF_ROLLING) + { + // try to stand up + pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2; + 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_ROLLING; + } + else if (pm->cmd.upmove < 0 || + pm->ps->forceHandExtend == HANDEXTEND_KNOCKDOWN || + pm->ps->forceHandExtend == HANDEXTEND_PRETHROWN || + pm->ps->forceHandExtend == HANDEXTEND_POSTTHROWN) + { // 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] = pm->ps->standheight;//DEFAULT_MAXS_2; + 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] = pm->ps->crouchheight;//CROUCH_MAXS_2; + pm->ps->viewheight = CROUCH_VIEWHEIGHT; + } + else if (pm->ps->pm_flags & PMF_ROLLING) + { + pm->maxs[2] = pm->ps->crouchheight;//CROUCH_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + } + else + { + pm->maxs[2] = pm->ps->standheight;//DEFAULT_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + } +} + + + +//=================================================================== + + + +/* +============== +PM_Use + +Generates a use event +============== +*/ +#define USE_DELAY 2000 + +void PM_Use( void ) +{ + if ( pm->ps->useTime > 0 ) + pm->ps->useTime -= 100;//pm->cmd.msec; + + if ( pm->ps->useTime > 0 ) { + return; + } + + if ( ! (pm->cmd.buttons & BUTTON_USE ) ) + { + pm->useEvent = 0; + pm->ps->useTime = 0; + return; + } + + pm->useEvent = EV_USE; + pm->ps->useTime = USE_DELAY; +} + +qboolean PM_WalkingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_WALK1: //# Normal walk + case BOTH_WALK2: //# Normal walk with saber + case BOTH_WALK_STAFF: //# Normal walk with staff + case BOTH_WALK_DUAL: //# Normal walk with staff + case BOTH_WALK5: //# Tavion taunting Kyle (cin 22) + case BOTH_WALK6: //# Slow walk for Luke (cin 12) + case BOTH_WALK7: //# Fast walk + case BOTH_WALKBACK1: //# Walk1 backwards + case BOTH_WALKBACK2: //# Walk2 backwards + case BOTH_WALKBACK_STAFF: //# Walk backwards with staff + case BOTH_WALKBACK_DUAL: //# Walk backwards with dual + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_RunningAnim( int anim ) +{ + switch ( (anim) ) + { + case BOTH_RUN1: + case BOTH_RUN2: + case BOTH_RUN_STAFF: + case BOTH_RUN_DUAL: + case BOTH_RUNBACK1: + case BOTH_RUNBACK2: + case BOTH_RUNBACK_STAFF: + case BOTH_RUNBACK_DUAL: + case BOTH_RUN1START: //# Start into full run1 + case BOTH_RUN1STOP: //# Stop from full run1 + case BOTH_RUNSTRAFE_LEFT1: //# Sidestep left: should loop + case BOTH_RUNSTRAFE_RIGHT1: //# Sidestep right: should loop + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_SwimmingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_SWIM_IDLE1: //# Swimming Idle 1 + case BOTH_SWIMFORWARD: //# Swim forward loop + case BOTH_SWIMBACKWARD: //# Swim backward loop + return qtrue; + break; + } + return qfalse; +} + +qboolean PM_RollingAnim( int anim ) +{ + switch ( anim ) + { + case BOTH_ROLL_F: //# Roll forward + case BOTH_ROLL_B: //# Roll backward + case BOTH_ROLL_L: //# Roll left + case BOTH_ROLL_R: //# Roll right + return qtrue; + break; + } + return qfalse; +} + +void PM_AnglesForSlope( const float yaw, const vec3_t slope, vec3_t angles ) +{ + vec3_t nvf, ovf, ovr, new_angles; + float pitch, mod, dot; + + VectorSet( angles, 0, yaw, 0 ); + AngleVectors( angles, ovf, ovr, NULL ); + + vectoangles( slope, new_angles ); + pitch = new_angles[PITCH] + 90; + new_angles[ROLL] = new_angles[PITCH] = 0; + + AngleVectors( new_angles, nvf, NULL, NULL ); + + mod = DotProduct( nvf, ovr ); + + if ( mod < 0 ) + mod = -1; + else + mod = 1; + + dot = DotProduct( nvf, ovf ); + + angles[YAW] = 0; + angles[PITCH] = dot * pitch; + angles[ROLL] = ((1-Q_fabs(dot)) * pitch * mod); +} + +void PM_FootSlopeTrace( float *pDiff, float *pInterval ) +{ + vec3_t footLOrg, footROrg, footLBot, footRBot; + vec3_t footLPoint, footRPoint; + vec3_t footMins, footMaxs; + vec3_t footLSlope, footRSlope; + + trace_t trace; + float diff, interval; + + mdxaBone_t boltMatrix; + vec3_t G2Angles; + + VectorSet(G2Angles, 0, pm->ps->viewangles[YAW], 0); + + interval = 4;//? + + strap_G2API_GetBoltMatrix( pm->ghoul2, 0, pm->g2Bolts_LFoot, + &boltMatrix, G2Angles, pm->ps->origin, pm->cmd.serverTime, + NULL, pm->modelScale ); + footLPoint[0] = boltMatrix.matrix[0][3]; + footLPoint[1] = boltMatrix.matrix[1][3]; + footLPoint[2] = boltMatrix.matrix[2][3]; + + strap_G2API_GetBoltMatrix( pm->ghoul2, 0, pm->g2Bolts_RFoot, + &boltMatrix, G2Angles, pm->ps->origin, pm->cmd.serverTime, + NULL, pm->modelScale ); + footRPoint[0] = boltMatrix.matrix[0][3]; + footRPoint[1] = boltMatrix.matrix[1][3]; + footRPoint[2] = boltMatrix.matrix[2][3]; + + //get these on the cgame and store it, save ourselves a ghoul2 construct skel call + VectorCopy( footLPoint, footLOrg ); + VectorCopy( footRPoint, footROrg ); + + //step 2: adjust foot tag z height to bottom of bbox+1 + footLOrg[2] = pm->ps->origin[2] + pm->mins[2] + 1; + footROrg[2] = pm->ps->origin[2] + pm->mins[2] + 1; + VectorSet( footLBot, footLOrg[0], footLOrg[1], footLOrg[2] - interval*10 ); + VectorSet( footRBot, footROrg[0], footROrg[1], footROrg[2] - interval*10 ); + + //step 3: trace down from each, find difference + VectorSet( footMins, -3, -3, 0 ); + VectorSet( footMaxs, 3, 3, 1 ); + + pm->trace( &trace, footLOrg, footMins, footMaxs, footLBot, pm->ps->clientNum, pm->tracemask ); + VectorCopy( trace.endpos, footLBot ); + VectorCopy( trace.plane.normal, footLSlope ); + + pm->trace( &trace, footROrg, footMins, footMaxs, footRBot, pm->ps->clientNum, pm->tracemask ); + VectorCopy( trace.endpos, footRBot ); + VectorCopy( trace.plane.normal, footRSlope ); + + diff = footLBot[2] - footRBot[2]; + + if ( pDiff != NULL ) + { + *pDiff = diff; + } + if ( pInterval != NULL ) + { + *pInterval = interval; + } +} + +qboolean BG_InSlopeAnim( int anim ) +{ + switch ( anim ) + { + case LEGS_LEFTUP1: //# On a slope with left foot 4 higher than right + case LEGS_LEFTUP2: //# On a slope with left foot 8 higher than right + case LEGS_LEFTUP3: //# On a slope with left foot 12 higher than right + case LEGS_LEFTUP4: //# On a slope with left foot 16 higher than right + case LEGS_LEFTUP5: //# On a slope with left foot 20 higher than right + case LEGS_RIGHTUP1: //# On a slope with RIGHT foot 4 higher than left + case LEGS_RIGHTUP2: //# On a slope with RIGHT foot 8 higher than left + case LEGS_RIGHTUP3: //# On a slope with RIGHT foot 12 higher than left + case LEGS_RIGHTUP4: //# On a slope with RIGHT foot 16 higher than left + case LEGS_RIGHTUP5: //# On a slope with RIGHT foot 20 higher than left + case LEGS_S1_LUP1: + case LEGS_S1_LUP2: + case LEGS_S1_LUP3: + case LEGS_S1_LUP4: + case LEGS_S1_LUP5: + case LEGS_S1_RUP1: + case LEGS_S1_RUP2: + case LEGS_S1_RUP3: + case LEGS_S1_RUP4: + case LEGS_S1_RUP5: + case LEGS_S3_LUP1: + case LEGS_S3_LUP2: + case LEGS_S3_LUP3: + case LEGS_S3_LUP4: + case LEGS_S3_LUP5: + case LEGS_S3_RUP1: + case LEGS_S3_RUP2: + case LEGS_S3_RUP3: + case LEGS_S3_RUP4: + case LEGS_S3_RUP5: + case LEGS_S4_LUP1: + case LEGS_S4_LUP2: + case LEGS_S4_LUP3: + case LEGS_S4_LUP4: + case LEGS_S4_LUP5: + case LEGS_S4_RUP1: + case LEGS_S4_RUP2: + case LEGS_S4_RUP3: + case LEGS_S4_RUP4: + case LEGS_S4_RUP5: + case LEGS_S5_LUP1: + case LEGS_S5_LUP2: + case LEGS_S5_LUP3: + case LEGS_S5_LUP4: + case LEGS_S5_LUP5: + case LEGS_S5_RUP1: + case LEGS_S5_RUP2: + case LEGS_S5_RUP3: + case LEGS_S5_RUP4: + case LEGS_S5_RUP5: + return qtrue; + break; + } + return qfalse; +} + +#define SLOPE_RECALC_INT 100 + +qboolean PM_AdjustStandAnimForSlope( void ) +{ + float diff; + float interval; + int destAnim; + int legsAnim; + #define SLOPERECALCVAR pm->ps->slopeRecalcTime //this is purely convenience + + if (!pm->ghoul2) + { //probably just changed models and not quite in sync yet + return qfalse; + } + + if ( pm->g2Bolts_LFoot == -1 || pm->g2Bolts_RFoot == -1 ) + {//need these bolts! + return qfalse; + } + + //step 1: find the 2 foot tags + PM_FootSlopeTrace( &diff, &interval ); + + //step 4: based on difference, choose one of the left/right slope-match intervals + if ( diff >= interval*5 ) + { + destAnim = LEGS_LEFTUP5; + } + else if ( diff >= interval*4 ) + { + destAnim = LEGS_LEFTUP4; + } + else if ( diff >= interval*3 ) + { + destAnim = LEGS_LEFTUP3; + } + else if ( diff >= interval*2 ) + { + destAnim = LEGS_LEFTUP2; + } + else if ( diff >= interval ) + { + destAnim = LEGS_LEFTUP1; + } + else if ( diff <= interval*-5 ) + { + destAnim = LEGS_RIGHTUP5; + } + else if ( diff <= interval*-4 ) + { + destAnim = LEGS_RIGHTUP4; + } + else if ( diff <= interval*-3 ) + { + destAnim = LEGS_RIGHTUP3; + } + else if ( diff <= interval*-2 ) + { + destAnim = LEGS_RIGHTUP2; + } + else if ( diff <= interval*-1 ) + { + destAnim = LEGS_RIGHTUP1; + } + else + { + return qfalse; + } + + legsAnim = pm->ps->legsAnim; + //adjust for current legs anim + switch ( legsAnim ) + { + case BOTH_STAND1: + + case LEGS_S1_LUP1: + case LEGS_S1_LUP2: + case LEGS_S1_LUP3: + case LEGS_S1_LUP4: + case LEGS_S1_LUP5: + case LEGS_S1_RUP1: + case LEGS_S1_RUP2: + case LEGS_S1_RUP3: + case LEGS_S1_RUP4: + case LEGS_S1_RUP5: + destAnim = LEGS_S1_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND2: + case BOTH_SABERFAST_STANCE: + case BOTH_SABERSLOW_STANCE: + case BOTH_CROUCH1IDLE: + case BOTH_CROUCH1: + case LEGS_LEFTUP1: //# On a slope with left foot 4 higher than right + case LEGS_LEFTUP2: //# On a slope with left foot 8 higher than right + case LEGS_LEFTUP3: //# On a slope with left foot 12 higher than right + case LEGS_LEFTUP4: //# On a slope with left foot 16 higher than right + case LEGS_LEFTUP5: //# On a slope with left foot 20 higher than right + case LEGS_RIGHTUP1: //# On a slope with RIGHT foot 4 higher than left + case LEGS_RIGHTUP2: //# On a slope with RIGHT foot 8 higher than left + case LEGS_RIGHTUP3: //# On a slope with RIGHT foot 12 higher than left + case LEGS_RIGHTUP4: //# On a slope with RIGHT foot 16 higher than left + case LEGS_RIGHTUP5: //# On a slope with RIGHT foot 20 higher than left + //fine + break; + case BOTH_STAND3: + case LEGS_S3_LUP1: + case LEGS_S3_LUP2: + case LEGS_S3_LUP3: + case LEGS_S3_LUP4: + case LEGS_S3_LUP5: + case LEGS_S3_RUP1: + case LEGS_S3_RUP2: + case LEGS_S3_RUP3: + case LEGS_S3_RUP4: + case LEGS_S3_RUP5: + destAnim = LEGS_S3_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND4: + case LEGS_S4_LUP1: + case LEGS_S4_LUP2: + case LEGS_S4_LUP3: + case LEGS_S4_LUP4: + case LEGS_S4_LUP5: + case LEGS_S4_RUP1: + case LEGS_S4_RUP2: + case LEGS_S4_RUP3: + case LEGS_S4_RUP4: + case LEGS_S4_RUP5: + destAnim = LEGS_S4_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND5: + case LEGS_S5_LUP1: + case LEGS_S5_LUP2: + case LEGS_S5_LUP3: + case LEGS_S5_LUP4: + case LEGS_S5_LUP5: + case LEGS_S5_RUP1: + case LEGS_S5_RUP2: + case LEGS_S5_RUP3: + case LEGS_S5_RUP4: + case LEGS_S5_RUP5: + destAnim = LEGS_S5_LUP1 + (destAnim-LEGS_LEFTUP1); + break; + case BOTH_STAND6: + default: + return qfalse; + break; + } + + //step 5: based on the chosen interval and the current legsAnim, pick the correct anim + //step 6: increment/decrement to the dest anim, not instant + if ( (legsAnim >= LEGS_LEFTUP1 && legsAnim <= LEGS_LEFTUP5) + || (legsAnim >= LEGS_S1_LUP1 && legsAnim <= LEGS_S1_LUP5) + || (legsAnim >= LEGS_S3_LUP1 && legsAnim <= LEGS_S3_LUP5) + || (legsAnim >= LEGS_S4_LUP1 && legsAnim <= LEGS_S4_LUP5) + || (legsAnim >= LEGS_S5_LUP1 && legsAnim <= LEGS_S5_LUP5) ) + {//already in left-side up + if ( destAnim > legsAnim && SLOPERECALCVAR < pm->cmd.serverTime ) + { + legsAnim++; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim < legsAnim && SLOPERECALCVAR < pm->cmd.serverTime ) + { + legsAnim--; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else //if (SLOPERECALCVAR < pm->cmd.serverTime) + { + legsAnim = destAnim; + } + + destAnim = legsAnim; + } + else if ( (legsAnim >= LEGS_RIGHTUP1 && legsAnim <= LEGS_RIGHTUP5) + || (legsAnim >= LEGS_S1_RUP1 && legsAnim <= LEGS_S1_RUP5) + || (legsAnim >= LEGS_S3_RUP1 && legsAnim <= LEGS_S3_RUP5) + || (legsAnim >= LEGS_S4_RUP1 && legsAnim <= LEGS_S4_RUP5) + || (legsAnim >= LEGS_S5_RUP1 && legsAnim <= LEGS_S5_RUP5) ) + {//already in right-side up + if ( destAnim > legsAnim && SLOPERECALCVAR < pm->cmd.serverTime ) + { + legsAnim++; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim < legsAnim && SLOPERECALCVAR < pm->cmd.serverTime ) + { + legsAnim--; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else //if (SLOPERECALCVAR < pm->cmd.serverTime) + { + legsAnim = destAnim; + } + + destAnim = legsAnim; + } + else + {//in a stand of some sort? + switch ( legsAnim ) + { + case BOTH_STAND1: + case TORSO_WEAPONREADY1: + case TORSO_WEAPONREADY2: + case TORSO_WEAPONREADY3: + case TORSO_WEAPONREADY10: + + if ( destAnim >= LEGS_S1_LUP1 && destAnim <= LEGS_S1_LUP5 ) + {//going into left side up + destAnim = LEGS_S1_LUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S1_RUP1 && destAnim <= LEGS_S1_RUP5 ) + {//going into right side up + destAnim = LEGS_S1_RUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND2: + case BOTH_SABERFAST_STANCE: + case BOTH_SABERSLOW_STANCE: + case BOTH_CROUCH1IDLE: + if ( destAnim >= LEGS_LEFTUP1 && destAnim <= LEGS_LEFTUP5 ) + {//going into left side up + destAnim = LEGS_LEFTUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_RIGHTUP1 && destAnim <= LEGS_RIGHTUP5 ) + {//going into right side up + destAnim = LEGS_RIGHTUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND3: + if ( destAnim >= LEGS_S3_LUP1 && destAnim <= LEGS_S3_LUP5 ) + {//going into left side up + destAnim = LEGS_S3_LUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S3_RUP1 && destAnim <= LEGS_S3_RUP5 ) + {//going into right side up + destAnim = LEGS_S3_RUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND4: + if ( destAnim >= LEGS_S4_LUP1 && destAnim <= LEGS_S4_LUP5 ) + {//going into left side up + destAnim = LEGS_S4_LUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S4_RUP1 && destAnim <= LEGS_S4_RUP5 ) + {//going into right side up + destAnim = LEGS_S4_RUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND5: + if ( destAnim >= LEGS_S5_LUP1 && destAnim <= LEGS_S5_LUP5 ) + {//going into left side up + destAnim = LEGS_S5_LUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else if ( destAnim >= LEGS_S5_RUP1 && destAnim <= LEGS_S5_RUP5 ) + {//going into right side up + destAnim = LEGS_S5_RUP1; + SLOPERECALCVAR = pm->cmd.serverTime + SLOPE_RECALC_INT; + } + else + {//will never get here + return qfalse; + } + break; + case BOTH_STAND6: + default: + return qfalse; + break; + } + } + //step 7: set the anim + //PM_SetAnim( SETANIM_LEGS, destAnim, SETANIM_FLAG_NORMAL, 100 ); + PM_ContinueLegsAnim(destAnim); + + return qtrue; +} + +extern int WeaponReadyLegsAnim[WP_NUM_WEAPONS]; + +//rww - slowly back out of slope leg anims, to prevent skipping between slope anims and general jittering +int PM_LegsSlopeBackTransition(int desiredAnim) +{ + int anim = pm->ps->legsAnim; + int resultingAnim = desiredAnim; + + switch ( anim ) + { + case LEGS_LEFTUP2: //# On a slope with left foot 8 higher than right + case LEGS_LEFTUP3: //# On a slope with left foot 12 higher than right + case LEGS_LEFTUP4: //# On a slope with left foot 16 higher than right + case LEGS_LEFTUP5: //# On a slope with left foot 20 higher than right + case LEGS_RIGHTUP2: //# On a slope with RIGHT foot 8 higher than left + case LEGS_RIGHTUP3: //# On a slope with RIGHT foot 12 higher than left + case LEGS_RIGHTUP4: //# On a slope with RIGHT foot 16 higher than left + case LEGS_RIGHTUP5: //# On a slope with RIGHT foot 20 higher than left + case LEGS_S1_LUP2: + case LEGS_S1_LUP3: + case LEGS_S1_LUP4: + case LEGS_S1_LUP5: + case LEGS_S1_RUP2: + case LEGS_S1_RUP3: + case LEGS_S1_RUP4: + case LEGS_S1_RUP5: + case LEGS_S3_LUP2: + case LEGS_S3_LUP3: + case LEGS_S3_LUP4: + case LEGS_S3_LUP5: + case LEGS_S3_RUP2: + case LEGS_S3_RUP3: + case LEGS_S3_RUP4: + case LEGS_S3_RUP5: + case LEGS_S4_LUP2: + case LEGS_S4_LUP3: + case LEGS_S4_LUP4: + case LEGS_S4_LUP5: + case LEGS_S4_RUP2: + case LEGS_S4_RUP3: + case LEGS_S4_RUP4: + case LEGS_S4_RUP5: + case LEGS_S5_LUP2: + case LEGS_S5_LUP3: + case LEGS_S5_LUP4: + case LEGS_S5_LUP5: + case LEGS_S5_RUP2: + case LEGS_S5_RUP3: + case LEGS_S5_RUP4: + case LEGS_S5_RUP5: + if (pm->ps->slopeRecalcTime < pm->cmd.serverTime) + { + resultingAnim = anim-1; + pm->ps->slopeRecalcTime = pm->cmd.serverTime + 8;//SLOPE_RECALC_INT; + } + else + { + resultingAnim = anim; + } + VectorClear(pm->ps->velocity); + break; + } + + return resultingAnim; +} + +/* +=============== +PM_Footsteps +=============== +*/ +static void PM_Footsteps( void ) { + float bobmove; + int old; + qboolean footstep; + int setAnimFlags = 0; + + if ( (PM_InSaberAnim( (pm->ps->legsAnim) ) && !BG_SpinningSaberAnim( (pm->ps->legsAnim) )) + || (pm->ps->legsAnim) == BOTH_STAND1 + || (pm->ps->legsAnim) == BOTH_STAND1TO2 + || (pm->ps->legsAnim) == BOTH_STAND2TO1 + || (pm->ps->legsAnim) == BOTH_STAND2 + || (pm->ps->legsAnim) == BOTH_SABERFAST_STANCE + || (pm->ps->legsAnim) == BOTH_SABERSLOW_STANCE + || (pm->ps->legsAnim) == BOTH_BUTTON_HOLD + || (pm->ps->legsAnim) == BOTH_BUTTON_RELEASE + || PM_LandingAnim( (pm->ps->legsAnim) ) + || PM_PainAnim( (pm->ps->legsAnim) )) + {//legs are in a saber anim, and not spinning, be sure to override it + setAnimFlags |= SETANIM_FLAG_OVERRIDE; + } + + // + // 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->saberMove == LS_SPINATTACK) + { + PM_ContinueLegsAnim( pm->ps->torsoAnim ); + } + else if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { + + // airborne leaves position in cycle intact, but doesn't advance + if ( pm->waterlevel > 1 ) + { + if (pm->xyspeed > 60) + { + PM_ContinueLegsAnim( BOTH_SWIMFORWARD ); + } + else + { + PM_ContinueLegsAnim( BOTH_SWIM_IDLE1 ); + } + } + return; + } + // if not trying to move + else if ( !pm->cmd.forwardmove && !pm->cmd.rightmove ) { + if ( pm->xyspeed < 5 ) { + pm->ps->bobCycle = 0; // start at beginning of cycle again + if ( pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_RANCOR ) + { + if ( (pm->ps->eFlags2&EF2_USE_ALT_ANIM) ) + {//holding someone + PM_ContinueLegsAnim( BOTH_STAND4 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND4,SETANIM_FLAG_NORMAL); + } + else if ( (pm->ps->eFlags2&EF2_ALERTED) ) + {//have an enemy or have had one since we spawned + PM_ContinueLegsAnim( BOTH_STAND2 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND2,SETANIM_FLAG_NORMAL); + } + else + {//just stand there + PM_ContinueLegsAnim( BOTH_STAND1 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else if ( pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_WAMPA ) + { + if ( (pm->ps->eFlags2&EF2_USE_ALT_ANIM) ) + {//holding a victim + PM_ContinueLegsAnim( BOTH_STAND2 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND2,SETANIM_FLAG_NORMAL); + } + else + {//not holding a victim + PM_ContinueLegsAnim( BOTH_STAND1 ); + //PM_SetAnim(pm,SETANIM_LEGS,BOTH_STAND1,SETANIM_FLAG_NORMAL); + } + } + else if ( (pm->ps->pm_flags & PMF_DUCKED) || (pm->ps->pm_flags & PMF_ROLLING) ) { + if ((pm->ps->legsAnim) != BOTH_CROUCH1IDLE) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1IDLE, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1IDLE ); + } + } else { + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + ///???? continue legs anim on a torso anim...??!!! + //yeah.. the anim has a valid pose for the legs, it uses it (you can't move while using disruptor) + PM_ContinueLegsAnim( TORSO_WEAPONREADY4 ); + } + else + { + if (pm->ps->weapon == WP_SABER && BG_SabersOff( pm->ps ) ) + { + if (!PM_AdjustStandAnimForSlope()) + { + //PM_ContinueLegsAnim( BOTH_STAND1 ); + PM_ContinueLegsAnim(PM_LegsSlopeBackTransition(BOTH_STAND1)); + } + } + else + { + if (pm->ps->weapon != WP_SABER || !PM_AdjustStandAnimForSlope()) + { + if (pm->ps->weapon == WP_SABER) + { + PM_ContinueLegsAnim(PM_LegsSlopeBackTransition(PM_GetSaberStance())); + } + else + { + PM_ContinueLegsAnim(PM_LegsSlopeBackTransition(WeaponReadyLegsAnim[pm->ps->weapon])); + } + } + } + } + } + } + return; + } + + + footstep = qfalse; + + if (pm->ps->saberMove == LS_SPINATTACK) + { + bobmove = 0.2f; + PM_ContinueLegsAnim( pm->ps->torsoAnim ); + } + else if ( pm->ps->pm_flags & PMF_DUCKED ) + { + int rolled = 0; + + bobmove = 0.5; // ducked characters bob much faster + + if ( ( (PM_RunningAnim( pm->ps->legsAnim )&&VectorLengthSquared(pm->ps->velocity)>=40000/*200*200*/) || PM_CanRollFromSoulCal( pm->ps ) ) && + !BG_InRoll(pm->ps, pm->ps->legsAnim) ) + {//roll! + rolled = PM_TryRoll(); + } + if ( !rolled ) + { //if the roll failed or didn't attempt, do standard crouching anim stuff. + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) { + if ((pm->ps->legsAnim) != BOTH_CROUCH1WALKBACK) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALKBACK, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1WALKBACK ); + } + } + else { + if ((pm->ps->legsAnim) != BOTH_CROUCH1WALK) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALK, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1WALK ); + } + } + } + else + { //otherwise send us into the roll + pm->ps->legsTimer = 0; + pm->ps->legsAnim = 0; + PM_SetAnim(SETANIM_BOTH,rolled,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 150); + PM_AddEventWithParm( EV_ROLL, 0 ); + pm->maxs[2] = pm->ps->crouchheight;//CROUCH_MAXS_2; + pm->ps->viewheight = DEFAULT_VIEWHEIGHT; + pm->ps->pm_flags &= ~PMF_DUCKED; + pm->ps->pm_flags |= PMF_ROLLING; + } + } + else if ((pm->ps->pm_flags & PMF_ROLLING) && !BG_InRoll(pm->ps, pm->ps->legsAnim) && + !PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { + bobmove = 0.5; // ducked characters bob much faster + + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + if ((pm->ps->legsAnim) != BOTH_CROUCH1WALKBACK) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALKBACK, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1WALKBACK ); + } + } + else + { + if ((pm->ps->legsAnim) != BOTH_CROUCH1WALK) + { + PM_SetAnim(SETANIM_LEGS, BOTH_CROUCH1WALK, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim( BOTH_CROUCH1WALK ); + } + } + } + else + { + int desiredAnim = -1; + + if ((pm->ps->legsAnim == BOTH_FORCELAND1 || + pm->ps->legsAnim == BOTH_FORCELANDBACK1 || + pm->ps->legsAnim == BOTH_FORCELANDRIGHT1 || + pm->ps->legsAnim == BOTH_FORCELANDLEFT1) && + pm->ps->legsTimer > 0) + { //let it finish first + bobmove = 0.2f; + } + else if ( !( pm->cmd.buttons & BUTTON_WALKING ) ) + {//running + bobmove = 0.4f; // faster speeds bob faster + if ( pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_WAMPA ) + { + if ( (pm->ps->eFlags2&EF2_USE_ALT_ANIM) ) + {//full on run, on all fours + desiredAnim = BOTH_RUN1; + } + else + {//regular, upright run + desiredAnim = BOTH_RUN2; + } + } + else if ( pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_RANCOR ) + {//no run anims + if ( (pm->ps->pm_flags&PMF_BACKWARDS_RUN) ) + { + desiredAnim = BOTH_WALKBACK1; + } + else + { + desiredAnim = BOTH_WALK1; + } + } + else if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + switch (pm->ps->fd.saberAnimLevel) + { + case SS_STAFF: + if ( pm->ps->saberHolstered > 1 ) + {//saber off + desiredAnim = BOTH_RUNBACK1; + } + else + { + //desiredAnim = BOTH_RUNBACK_STAFF; + //hmm.. stuff runback anim is pretty messed up for some reason. + desiredAnim = BOTH_RUNBACK2; + } + break; + case SS_DUAL: + if ( pm->ps->saberHolstered > 1 ) + {//sabers off + desiredAnim = BOTH_RUNBACK1; + } + else + { + //desiredAnim = BOTH_RUNBACK_DUAL; + //and so is the dual + desiredAnim = BOTH_RUNBACK2; + } + break; + default: + if ( pm->ps->saberHolstered ) + {//saber off + desiredAnim = BOTH_RUNBACK1; + } + else + { + desiredAnim = BOTH_RUNBACK2; + } + break; + } + } + else + { + switch (pm->ps->fd.saberAnimLevel) + { + case SS_STAFF: + if ( pm->ps->saberHolstered > 1 ) + {//blades off + desiredAnim = BOTH_RUN1; + } + else if ( pm->ps->saberHolstered == 1 ) + {//1 blade on + desiredAnim = BOTH_RUN2; + } + else + { + if (pm->ps->fd.forcePowersActive & (1<ps->saberHolstered > 1 ) + {//blades off + desiredAnim = BOTH_RUN1; + } + else if ( pm->ps->saberHolstered == 1 ) + {//1 saber on + desiredAnim = BOTH_RUN2; + } + else + { + desiredAnim = BOTH_RUN_DUAL; + } + break; + default: + if ( pm->ps->saberHolstered ) + {//saber off + desiredAnim = BOTH_RUN1; + } + else + { + desiredAnim = BOTH_RUN2; + } + break; + } + } + footstep = qtrue; + } + else + { + bobmove = 0.2f; // walking bobs slow + if ( pm->ps->pm_flags & PMF_BACKWARDS_RUN ) + { + switch (pm->ps->fd.saberAnimLevel) + { + case SS_STAFF: + if ( pm->ps->saberHolstered > 1 ) + { + desiredAnim = BOTH_WALKBACK1; + } + else if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALKBACK2; + } + else + { + desiredAnim = BOTH_WALKBACK_STAFF; + } + break; + case SS_DUAL: + if ( pm->ps->saberHolstered > 1 ) + { + desiredAnim = BOTH_WALKBACK1; + } + else if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALKBACK2; + } + else + { + desiredAnim = BOTH_WALKBACK_DUAL; + } + break; + default: + if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALKBACK1; + } + else + { + desiredAnim = BOTH_WALKBACK2; + } + break; + } + } + else + { + if ( pm->ps->weapon == WP_MELEE ) + { + desiredAnim = BOTH_WALK1; + } + else if ( BG_SabersOff( pm->ps ) ) + { + desiredAnim = BOTH_WALK1; + } + else + { + switch (pm->ps->fd.saberAnimLevel) + { + case SS_STAFF: + if ( pm->ps->saberHolstered > 1 ) + { + desiredAnim = BOTH_WALK1; + } + else if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALK2; + } + else + { + desiredAnim = BOTH_WALK_STAFF; + } + break; + case SS_DUAL: + if ( pm->ps->saberHolstered > 1 ) + { + desiredAnim = BOTH_WALK1; + } + else if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALK2; + } + else + { + desiredAnim = BOTH_WALK_DUAL; + } + break; + default: + if ( pm->ps->saberHolstered ) + { + desiredAnim = BOTH_WALK1; + } + else + { + desiredAnim = BOTH_WALK2; + } + break; + } + } + } + } + + if (desiredAnim != -1) + { + int ires = PM_LegsSlopeBackTransition(desiredAnim); + + if ((pm->ps->legsAnim) != desiredAnim && ires == desiredAnim) + { + PM_SetAnim(SETANIM_LEGS, desiredAnim, setAnimFlags, 100); + } + else + { + PM_ContinueLegsAnim(ires); + } + } + } + + // 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 ) + { + pm->ps->footstepTime = pm->cmd.serverTime + 300; + 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? +#ifdef QAGAME + qboolean impact_splash = qfalse; +#endif + // + // if just entered a water volume, play a sound + // + if (!pml.previous_waterlevel && pm->waterlevel) { +#ifdef QAGAME + if ( VectorLengthSquared( pm->ps->velocity ) > 40000 ) + { + impact_splash = qtrue; + } +#endif + PM_AddEvent( EV_WATER_TOUCH ); + } + + // + // if just completely exited a water volume, play a sound + // + if (pml.previous_waterlevel && !pm->waterlevel) { +#ifdef QAGAME + if ( VectorLengthSquared( pm->ps->velocity ) > 40000 ) + { + impact_splash = qtrue; + } +#endif + PM_AddEvent( EV_WATER_LEAVE ); + } + +#ifdef QAGAME + if ( impact_splash ) + { + //play the splash effect + trace_t tr; + vec3_t start, end; + + + VectorCopy( pm->ps->origin, start ); + VectorCopy( pm->ps->origin, end ); + + // FIXME: set start and end better + start[2] += 10; + end[2] -= 40; + + pm->trace( &tr, start, vec3_origin, vec3_origin, end, pm->ps->clientNum, MASK_WATER ); + + if ( tr.fraction < 1.0f ) + { + if ( (tr.contents&CONTENTS_LAVA) ) + { + G_PlayEffect( EFFECT_LAVA_SPLASH, tr.endpos, tr.plane.normal ); + } + else if ( (tr.contents&CONTENTS_SLIME) ) + { + G_PlayEffect( EFFECT_ACID_SPLASH, tr.endpos, tr.plane.normal ); + } + else //must be water + { + G_PlayEffect( EFFECT_WATER_SPLASH, tr.endpos, tr.plane.normal ); + } + } + } +#endif + + // + // 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 ); + } +} + +void BG_ClearRocketLock( playerState_t *ps ) +{ + if ( ps ) + { + ps->rocketLockIndex = ENTITYNUM_NONE; + ps->rocketLastValidTime = 0; + ps->rocketLockTime = -1; + ps->rocketTargetTime = 0; + } +} + +/* +=============== +PM_BeginWeaponChange +=============== +*/ +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; + } + + // turn of any kind of zooming when weapon switching. + if (pm->ps->zoomMode) + { + pm->ps->zoomMode = 0; + pm->ps->zoomTime = pm->ps->commandTime; + } + + PM_AddEventWithParm( EV_CHANGE_WEAPON, weapon ); + pm->ps->weaponstate = WEAPON_DROPPING; + pm->ps->weaponTime += 200; + //PM_StartTorsoAnim( TORSO_DROPWEAP1 ); + PM_SetAnim(SETANIM_TORSO, TORSO_DROPWEAP1, SETANIM_FLAG_OVERRIDE, 0); + + BG_ClearRocketLock( pm->ps ); +} + + +/* +=============== +PM_FinishWeaponChange +=============== +*/ +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; + } + + if (weapon == WP_SABER) + { + PM_SetSaberMove(LS_DRAW); + } + else + { + //PM_StartTorsoAnim( TORSO_RAISEWEAP1); + PM_SetAnim(SETANIM_TORSO, TORSO_RAISEWEAP1, SETANIM_FLAG_OVERRIDE, 0); + } + pm->ps->weapon = weapon; + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime += 250; +} + +#ifdef QAGAME +extern void WP_GetVehicleCamPos( gentity_t *ent, gentity_t *pilot, vec3_t camPos ); +#else +extern void CG_GetVehicleCamPos( vec3_t camPos ); +#endif +#define MAX_XHAIR_DIST_ACCURACY 20000.0f +int BG_VehTraceFromCamPos( trace_t *camTrace, bgEntity_t *bgEnt, const vec3_t entOrg, const vec3_t shotStart, const vec3_t end, vec3_t newEnd, vec3_t shotDir, float bestDist ) +{ + //NOTE: this MUST stay up to date with the method used in CG_ScanForCrosshairEntity (where it checks the doExtraVehTraceFromViewPos bool) + vec3_t viewDir2End, extraEnd, camPos; + float minAutoAimDist; + +#ifdef QAGAME + WP_GetVehicleCamPos( (gentity_t *)bgEnt, (gentity_t *)bgEnt->m_pVehicle->m_pPilot, camPos ); +#else + CG_GetVehicleCamPos( camPos ); +#endif + + minAutoAimDist = Distance( entOrg, camPos ) + (bgEnt->m_pVehicle->m_pVehicleInfo->length/2.0f) + 200.0f; + + VectorCopy( end, newEnd ); + VectorSubtract( end, camPos, viewDir2End ); + VectorNormalize( viewDir2End ); + VectorMA( camPos, MAX_XHAIR_DIST_ACCURACY, viewDir2End, extraEnd ); + +#ifdef QAGAME + trap_Trace( camTrace, camPos, vec3_origin, vec3_origin, extraEnd, + bgEnt->s.number, CONTENTS_SOLID|CONTENTS_BODY ); +#else + pm->trace( camTrace, camPos, vec3_origin, vec3_origin, extraEnd, + bgEnt->s.number, CONTENTS_SOLID|CONTENTS_BODY ); +#endif + + if ( !camTrace->allsolid + && !camTrace->startsolid + && camTrace->fraction < 1.0f + && (camTrace->fraction*MAX_XHAIR_DIST_ACCURACY) > minAutoAimDist + && ((camTrace->fraction*MAX_XHAIR_DIST_ACCURACY)-Distance( entOrg, camPos )) < bestDist ) + {//this trace hit *something* that's closer than the thing the main trace hit, so use this result instead + VectorCopy( camTrace->endpos, newEnd ); + VectorSubtract( newEnd, shotStart, shotDir ); + VectorNormalize( shotDir ); + return (camTrace->entityNum+1); + } + return 0; +} + +void PM_RocketLock( float lockDist, qboolean vehicleLock ) +{ + // Not really a charge weapon, but we still want to delay fire until the button comes up so that we can + // implement our alt-fire locking stuff + vec3_t ang; + trace_t tr; + + vec3_t muzzleOffPoint, muzzlePoint, forward, right, up; + + if ( vehicleLock ) + { + AngleVectors( pm->ps->viewangles, forward, right, up ); + VectorCopy( pm->ps->origin, muzzlePoint ); + VectorMA( muzzlePoint, lockDist, forward, ang ); + } + else + { + AngleVectors( pm->ps->viewangles, forward, right, up ); + + AngleVectors(pm->ps->viewangles, ang, NULL, NULL); + + VectorCopy( pm->ps->origin, muzzlePoint ); + VectorCopy(WP_MuzzlePoint[WP_ROCKET_LAUNCHER], muzzleOffPoint); + + VectorMA(muzzlePoint, muzzleOffPoint[0], forward, muzzlePoint); + VectorMA(muzzlePoint, muzzleOffPoint[1], right, muzzlePoint); + muzzlePoint[2] += pm->ps->viewheight + muzzleOffPoint[2]; + ang[0] = muzzlePoint[0] + ang[0]*lockDist; + ang[1] = muzzlePoint[1] + ang[1]*lockDist; + ang[2] = muzzlePoint[2] + ang[2]*lockDist; + } + + + pm->trace(&tr, muzzlePoint, NULL, NULL, ang, pm->ps->clientNum, MASK_PLAYERSOLID); + + if ( vehicleLock ) + {//vehicles also do a trace from the camera point if the main one misses + if ( tr.fraction >= 1.0f ) + { + trace_t camTrace; + vec3_t newEnd, shotDir; + if ( BG_VehTraceFromCamPos( &camTrace, PM_BGEntForNum(pm->ps->clientNum), pm->ps->origin, muzzlePoint, tr.endpos, newEnd, shotDir, (tr.fraction*lockDist) ) ) + { + memcpy( &tr, &camTrace, sizeof(tr) ); + } + } + } + + if (tr.fraction != 1 && tr.entityNum < ENTITYNUM_NONE && tr.entityNum != pm->ps->clientNum) + { + bgEntity_t *bgEnt = PM_BGEntForNum(tr.entityNum); + if ( bgEnt && (bgEnt->s.powerups&PW_CLOAKED) ) + { + pm->ps->rocketLockIndex = ENTITYNUM_NONE; + pm->ps->rocketLockTime = 0; + } + else if (bgEnt && (bgEnt->s.eType == ET_PLAYER || bgEnt->s.eType == ET_NPC)) + { + if (pm->ps->rocketLockIndex == ENTITYNUM_NONE) + { + pm->ps->rocketLockIndex = tr.entityNum; + pm->ps->rocketLockTime = pm->cmd.serverTime; + } + else if (pm->ps->rocketLockIndex != tr.entityNum && pm->ps->rocketTargetTime < pm->cmd.serverTime) + { + pm->ps->rocketLockIndex = tr.entityNum; + pm->ps->rocketLockTime = pm->cmd.serverTime; + } + else if (pm->ps->rocketLockIndex == tr.entityNum) + { + if (pm->ps->rocketLockTime == -1) + { + pm->ps->rocketLockTime = pm->ps->rocketLastValidTime; + } + } + + if (pm->ps->rocketLockIndex == tr.entityNum) + { + pm->ps->rocketTargetTime = pm->cmd.serverTime + 500; + } + } + else if (!vehicleLock) + { + if (pm->ps->rocketTargetTime < pm->cmd.serverTime) + { + pm->ps->rocketLockIndex = ENTITYNUM_NONE; + pm->ps->rocketLockTime = 0; + } + } + } + else if (pm->ps->rocketTargetTime < pm->cmd.serverTime) + { + pm->ps->rocketLockIndex = ENTITYNUM_NONE; + pm->ps->rocketLockTime = 0; + } + else + { + if (pm->ps->rocketLockTime != -1) + { + pm->ps->rocketLastValidTime = pm->ps->rocketLockTime; + } + pm->ps->rocketLockTime = -1; + } +} + +//--------------------------------------- +static qboolean PM_DoChargedWeapons( qboolean vehicleRocketLock, bgEntity_t *veh ) +//--------------------------------------- +{ + qboolean charging = qfalse, + altFire = qfalse; + + if ( vehicleRocketLock ) + { + if ( (pm->cmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + {//actually charging + if ( veh + && veh->m_pVehicle ) + {//just make sure we have this veh info + if ( ( (pm->cmd.buttons&BUTTON_ATTACK) + &&g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].fHoming + &&pm->ps->ammo[0]>=g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].iAmmoPerShot ) + || + ( (pm->cmd.buttons&BUTTON_ALT_ATTACK) + &&g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].fHoming + &&pm->ps->ammo[1]>=g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].iAmmoPerShot ) ) + {//pressing the appropriate fire button for the lock-on/charging weapon + PM_RocketLock(16384, qtrue); + charging = qtrue; + } + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + altFire = qtrue; + } + } + } + //else, let go and should fire now + } + else + { + // If you want your weapon to be a charging weapon, just set this bit up + switch( pm->ps->weapon ) + { + //------------------ + case WP_BRYAR_PISTOL: + + // alt-fire charges the weapon + //if ( pm->gametype == GT_SIEGE ) + if (1) + { + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + charging = qtrue; + altFire = qtrue; + } + } + break; + + case WP_CONCUSSION: + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + altFire = qtrue; + } + break; + + case WP_BRYAR_OLD: + + // alt-fire charges the weapon + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + charging = qtrue; + altFire = qtrue; + } + break; + + //------------------ + case WP_BOWCASTER: + + // primary fire charges the weapon + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + charging = qtrue; + } + break; + + //------------------ + case WP_ROCKET_LAUNCHER: + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) + && pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] >= weaponData[pm->ps->weapon].altEnergyPerShot ) + { + PM_RocketLock(2048,qfalse); + charging = qtrue; + altFire = qtrue; + } + break; + + //------------------ + case WP_THERMAL: + + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + altFire = qtrue; // override default of not being an alt-fire + charging = qtrue; + } + else if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + charging = qtrue; + } + break; + + case WP_DEMP2: + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + altFire = qtrue; // override default of not being an alt-fire + charging = qtrue; + } + break; + + case WP_DISRUPTOR: + if ((pm->cmd.buttons & BUTTON_ATTACK) && + pm->ps->zoomMode == 1 && + pm->ps->zoomLocked) + { + if (!pm->cmd.forwardmove && + !pm->cmd.rightmove && + pm->cmd.upmove <= 0) + { + charging = qtrue; + altFire = qtrue; + } + else + { + charging = qfalse; + altFire = qfalse; + } + } + + if (pm->ps->zoomMode != 1 && + pm->ps->weaponstate == WEAPON_CHARGING_ALT) + { + pm->ps->weaponstate = WEAPON_READY; + charging = qfalse; + altFire = qfalse; + } + + } // end switch + } + + // set up the appropriate weapon state based on the button that's down. + // Note that we ALWAYS return if charging is set ( meaning the buttons are still down ) + if ( charging ) + { + if ( altFire ) + { + if ( pm->ps->weaponstate != WEAPON_CHARGING_ALT ) + { + // charge isn't started, so do it now + pm->ps->weaponstate = WEAPON_CHARGING_ALT; + pm->ps->weaponChargeTime = pm->cmd.serverTime; + pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].altChargeSubTime; + +#ifdef _DEBUG + Com_Printf("Starting charge\n"); +#endif + assert(pm->ps->weapon > WP_NONE); + BG_AddPredictableEventToPlayerstate(EV_WEAPON_CHARGE_ALT, pm->ps->weapon, pm->ps); + } + + if ( vehicleRocketLock ) + {//check vehicle ammo + if ( veh && pm->ps->ammo[1] < g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].iAmmoPerShot ) + { + pm->ps->weaponstate = WEAPON_CHARGING_ALT; + goto rest; + } + } + else if (pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < (weaponData[pm->ps->weapon].altChargeSub+weaponData[pm->ps->weapon].altEnergyPerShot)) + { + pm->ps->weaponstate = WEAPON_CHARGING_ALT; + + goto rest; + } + else if ((pm->cmd.serverTime - pm->ps->weaponChargeTime) < weaponData[pm->ps->weapon].altMaxCharge) + { + if (pm->ps->weaponChargeSubtractTime < pm->cmd.serverTime) + { + pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= weaponData[pm->ps->weapon].altChargeSub; + pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].altChargeSubTime; + } + } + } + else + { + if ( pm->ps->weaponstate != WEAPON_CHARGING ) + { + // charge isn't started, so do it now + pm->ps->weaponstate = WEAPON_CHARGING; + pm->ps->weaponChargeTime = pm->cmd.serverTime; + pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].chargeSubTime; + +#ifdef _DEBUG + Com_Printf("Starting charge\n"); +#endif + BG_AddPredictableEventToPlayerstate(EV_WEAPON_CHARGE, pm->ps->weapon, pm->ps); + } + + if ( vehicleRocketLock ) + { + if ( veh && pm->ps->ammo[0] < g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].iAmmoPerShot ) + {//check vehicle ammo + pm->ps->weaponstate = WEAPON_CHARGING; + goto rest; + } + } + else if (pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < (weaponData[pm->ps->weapon].chargeSub+weaponData[pm->ps->weapon].energyPerShot)) + { + pm->ps->weaponstate = WEAPON_CHARGING; + + goto rest; + } + else if ((pm->cmd.serverTime - pm->ps->weaponChargeTime) < weaponData[pm->ps->weapon].maxCharge) + { + if (pm->ps->weaponChargeSubtractTime < pm->cmd.serverTime) + { + pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= weaponData[pm->ps->weapon].chargeSub; + pm->ps->weaponChargeSubtractTime = pm->cmd.serverTime + weaponData[pm->ps->weapon].chargeSubTime; + } + } + } + + return qtrue; // short-circuit rest of weapon code + } +rest: + // Only charging weapons should be able to set these states...so.... + // let's see which fire mode we need to set up now that the buttons are up + if ( pm->ps->weaponstate == WEAPON_CHARGING ) + { + // weapon has a charge, so let us do an attack +#ifdef _DEBUG + Com_Printf("Firing. Charge time=%d\n", pm->cmd.serverTime - pm->ps->weaponChargeTime); +#endif + + // dumb, but since we shoot a charged weapon on button-up, we need to repress this button for now + pm->cmd.buttons |= BUTTON_ATTACK; + pm->ps->eFlags |= EF_FIRING; + } + else if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT ) + { + // weapon has a charge, so let us do an alt-attack +#ifdef _DEBUG + Com_Printf("Firing. Charge time=%d\n", pm->cmd.serverTime - pm->ps->weaponChargeTime); +#endif + + // dumb, but since we shoot a charged weapon on button-up, we need to repress this button for now + pm->cmd.buttons |= BUTTON_ALT_ATTACK; + pm->ps->eFlags |= (EF_FIRING|EF_ALT_FIRING); + } + + return qfalse; // continue with the rest of the weapon code +} + + +#define BOWCASTER_CHARGE_UNIT 200.0f // bowcaster charging gives us one more unit every 200ms--if you change this, you'll have to do the same in g_weapon +#define BRYAR_CHARGE_UNIT 200.0f // bryar charging gives us one more unit every 200ms--if you change this, you'll have to do the same in g_weapon + +int PM_ItemUsable(playerState_t *ps, int forcedUse) +{ + vec3_t fwd, fwdorg, dest, pos; + vec3_t yawonly; + vec3_t mins, maxs; + vec3_t trtest; + trace_t tr; + + if (ps->m_iVehicleNum) + { + return 0; + } + + if (ps->pm_flags & PMF_USE_ITEM_HELD) + { //force to let go first + return 0; + } + + if (ps->duelInProgress) + { //not allowed to use holdables while in a private duel. + return 0; + } + + if (!forcedUse) + { + forcedUse = bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag; + } + + if (!BG_IsItemSelectable(ps, forcedUse)) + { + return 0; + } + + switch (forcedUse) + { + case HI_MEDPAC: + case HI_MEDPAC_BIG: + if (ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH]) + { + return 0; + } + if (ps->stats[STAT_HEALTH] <= 0 || + (ps->eFlags & EF_DEAD)) + { + return 0; + } + + return 1; + case HI_SEEKER: + if (ps->eFlags & EF_SEEKERDRONE) + { + PM_AddEventWithParm(EV_ITEMUSEFAIL, SEEKER_ALREADYDEPLOYED); + return 0; + } + + return 1; + case HI_SENTRY_GUN: + if (ps->fd.sentryDeployed) + { + PM_AddEventWithParm(EV_ITEMUSEFAIL, SENTRY_ALREADYPLACED); + return 0; + } + + yawonly[ROLL] = 0; + yawonly[PITCH] = 0; + yawonly[YAW] = ps->viewangles[YAW]; + + VectorSet( mins, -8, -8, 0 ); + VectorSet( maxs, 8, 8, 24 ); + + AngleVectors(yawonly, fwd, NULL, NULL); + + fwdorg[0] = ps->origin[0] + fwd[0]*64; + fwdorg[1] = ps->origin[1] + fwd[1]*64; + fwdorg[2] = ps->origin[2] + fwd[2]*64; + + trtest[0] = fwdorg[0] + fwd[0]*16; + trtest[1] = fwdorg[1] + fwd[1]*16; + trtest[2] = fwdorg[2] + fwd[2]*16; + + pm->trace(&tr, ps->origin, mins, maxs, trtest, ps->clientNum, MASK_PLAYERSOLID); + + if ((tr.fraction != 1 && tr.entityNum != ps->clientNum) || tr.startsolid || tr.allsolid) + { + PM_AddEventWithParm(EV_ITEMUSEFAIL, SENTRY_NOROOM); + return 0; + } + + return 1; + case HI_SHIELD: + mins[0] = -8; + mins[1] = -8; + mins[2] = 0; + + maxs[0] = 8; + maxs[1] = 8; + maxs[2] = 8; + + AngleVectors (ps->viewangles, fwd, NULL, NULL); + fwd[2] = 0; + VectorMA(ps->origin, 64, fwd, dest); + pm->trace(&tr, ps->origin, mins, maxs, dest, ps->clientNum, MASK_SHOT ); + if (tr.fraction > 0.9 && !tr.startsolid && !tr.allsolid) + { + VectorCopy(tr.endpos, pos); + VectorSet( dest, pos[0], pos[1], pos[2] - 4096 ); + pm->trace( &tr, pos, mins, maxs, dest, ps->clientNum, MASK_SOLID ); + if ( !tr.startsolid && !tr.allsolid ) + { + return 1; + } + } + PM_AddEventWithParm(EV_ITEMUSEFAIL, SHIELD_NOROOM); + return 0; + case HI_JETPACK: //check for stuff here? + return 1; + case HI_HEALTHDISP: + return 1; + case HI_AMMODISP: + return 1; + case HI_EWEB: + return 1; + case HI_CLOAK: //check for stuff here? + return 1; + default: + return 1; + } +} + +//cheesy vehicle weapon hackery +qboolean PM_CanSetWeaponAnims(void) +{ + if (pm->ps->m_iVehicleNum) + { + return qfalse; + } + + return qtrue; +} + +//perform player anim overrides while on vehicle. +extern int PM_irand_timesync(int val1, int val2); +void PM_VehicleWeaponAnimate(void) +{ + bgEntity_t *veh = pm_entVeh; + Vehicle_t *pVeh; + int iFlags = 0, iBlend = 0, Anim = -1; + + if (!veh || + !veh->m_pVehicle || + !veh->m_pVehicle->m_pPilot || + !veh->m_pVehicle->m_pPilot->playerState || + pm->ps->clientNum != veh->m_pVehicle->m_pPilot->playerState->clientNum) + { //make sure the vehicle exists, and its pilot is this player + return; + } + + pVeh = veh->m_pVehicle; + + if (pVeh->m_pVehicleInfo->type == VH_WALKER || + pVeh->m_pVehicleInfo->type == VH_FIGHTER) + { //slightly hacky I guess, but whatever. + return; + } +backAgain: + // If they're firing, play the right fire animation. + if ( pm->cmd.buttons & ( BUTTON_ATTACK | BUTTON_ALT_ATTACK ) ) + { + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + iBlend = 200; + + switch ( pm->ps->weapon ) + { + case WP_SABER: + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { //don't do anything.. I guess. + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + goto backAgain; + } + // If we're already in an attack animation, leave (let it continue). + if (pm->ps->torsoTimer <= 0) + { //we'll be starting a new attack + PM_AddEvent(EV_SABER_ATTACK); + } + + //just set it to something so we have a proper trail. This is a stupid + //hack (much like the rest of this function) + pm->ps->saberMove = LS_R_TL2BR; + + if ( pm->ps->torsoTimer > 0 && (pm->ps->torsoAnim == BOTH_VS_ATR_S || + pm->ps->torsoAnim == BOTH_VS_ATL_S) ) + { + /* + //FIXME: no need to even call the PM_SetAnim at all in this case + Anim = (animNumber_t)pm->ps->torsoAnim; + iFlags = SETANIM_FLAG_NORMAL; + break; + */ + return; + } + + // Start the attack. + if ( pm->cmd.rightmove > 0 ) //right side attack + { + Anim = BOTH_VS_ATR_S; + } + else if ( pm->cmd.rightmove < 0 ) //left-side attack + { + Anim = BOTH_VS_ATL_S; + } + else //random + { + //FIXME: alternate back and forth or auto-aim? + //if ( !Q_irand( 0, 1 ) ) + if (!PM_irand_timesync(0, 1)) + { + Anim = BOTH_VS_ATR_S; + } + else + { + Anim = BOTH_VS_ATL_S; + } + } + + if (pm->ps->torsoTimer <= 0) + { //restart the anim if we are already in it (and finished) + iFlags |= SETANIM_FLAG_RESTART; + } + break; + + case WP_BLASTER: + // Override the shoot anim. + if ( pm->ps->torsoAnim == BOTH_ATTACK3 ) + { + if ( pm->cmd.rightmove > 0 ) //right side attack + { + Anim = BOTH_VS_ATR_G; + } + else if ( pm->cmd.rightmove < 0 ) //left side + { + Anim = BOTH_VS_ATL_G; + } + else //frontal + { + Anim = BOTH_VS_ATF_G; + } + } + break; + + default: + Anim = BOTH_VS_IDLE; + break; + } + } + else if (veh->playerState && veh->playerState->speed < 0 && + pVeh->m_pVehicleInfo->type == VH_ANIMAL) + { //tauntaun is going backwards + Anim = BOTH_VT_WALK_REV; + iBlend = 600; + } + else if (veh->playerState && veh->playerState->speed < 0 && + pVeh->m_pVehicleInfo->type == VH_SPEEDER) + { //speeder is going backwards + Anim = BOTH_VS_REV; + iBlend = 600; + } + // They're not firing so play the Idle for the weapon. + else + { + iFlags = SETANIM_FLAG_NORMAL; + + switch ( pm->ps->weapon ) + { + case WP_SABER: + if ( BG_SabersOff( pm->ps ) ) + { //saber holstered, normal idle + Anim = BOTH_VS_IDLE; + } + // In the Air. + //else if ( pVeh->m_ulFlags & VEH_FLYING ) + else if (0) + { + iBlend = 800; + Anim = BOTH_VS_AIR_G; + iFlags = SETANIM_FLAG_OVERRIDE; + } + // Crashing. + //else if ( pVeh->m_ulFlags & VEH_CRASHING ) + else if (0) + { + pVeh->m_ulFlags &= ~VEH_CRASHING; // Remove the flag, we are doing the animation. + iBlend = 800; + Anim = BOTH_VS_LAND_SR; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + } + else + { + Anim = BOTH_VS_IDLE_SR; + } + break; + + case WP_BLASTER: + // In the Air. + //if ( pVeh->m_ulFlags & VEH_FLYING ) + if (0) + { + iBlend = 800; + Anim = BOTH_VS_AIR_G; + iFlags = SETANIM_FLAG_OVERRIDE; + } + // Crashing. + //else if ( pVeh->m_ulFlags & VEH_CRASHING ) + else if (0) + { + pVeh->m_ulFlags &= ~VEH_CRASHING; // Remove the flag, we are doing the animation. + iBlend = 800; + Anim = BOTH_VS_LAND_G; + iFlags = SETANIM_FLAG_OVERRIDE | SETANIM_FLAG_HOLD; + } + else + { + Anim = BOTH_VS_IDLE_G; + } + break; + + default: + Anim = BOTH_VS_IDLE; + break; + } + } + + if (Anim != -1) + { //override it + if (pVeh->m_pVehicleInfo->type == VH_ANIMAL) + { //agh.. remap anims for the tauntaun + switch (Anim) + { + case BOTH_VS_IDLE: + if (veh->playerState && veh->playerState->speed > 0) + { + if (veh->playerState->speed > pVeh->m_pVehicleInfo->speedMax) + { //turbo + Anim = BOTH_VT_TURBO; + } + else + { + Anim = BOTH_VT_RUN_FWD; + } + } + else + { + Anim = BOTH_VT_IDLE; + } + break; + case BOTH_VS_ATR_S: + Anim = BOTH_VT_ATR_S; + break; + case BOTH_VS_ATL_S: + Anim = BOTH_VT_ATL_S; + break; + case BOTH_VS_ATR_G: + Anim = BOTH_VT_ATR_G; + break; + case BOTH_VS_ATL_G: + Anim = BOTH_VT_ATL_G; + break; + case BOTH_VS_ATF_G: + Anim = BOTH_VT_ATF_G; + break; + case BOTH_VS_IDLE_SL: + Anim = BOTH_VT_IDLE_S; + break; + case BOTH_VS_IDLE_SR: + Anim = BOTH_VT_IDLE_S; + break; + case BOTH_VS_IDLE_G: + Anim = BOTH_VT_IDLE_G; + break; + + //should not happen for tauntaun: + case BOTH_VS_AIR_G: + case BOTH_VS_LAND_SL: + case BOTH_VS_LAND_SR: + case BOTH_VS_LAND_G: + return; + default: + break; + } + } + + PM_SetAnim(SETANIM_BOTH, Anim, iFlags, iBlend); + } +} + +/* +============== +PM_Weapon + +Generates weapon events and modifes the weapon counter +============== +*/ +extern int PM_KickMoveForConditions(void); +static void PM_Weapon( void ) +{ + int addTime; + int amount; + int killAfterItem = 0; + bgEntity_t *veh = NULL; + qboolean vehicleRocketLock = qfalse; + +#ifdef QAGAME + if (pm->ps->clientNum >= MAX_CLIENTS && + pm->ps->weapon == WP_NONE && + pm->cmd.weapon == WP_NONE && + pm_entSelf) + { //npc with no weapon + gentity_t *gent = (gentity_t *)pm_entSelf; + if (gent->inuse && gent->client && + !gent->localAnimIndex) + { //humanoid + pm->ps->torsoAnim = pm->ps->legsAnim; + pm->ps->torsoTimer = pm->ps->legsTimer; + return; + } + } +#endif + + if (!pm->ps->emplacedIndex && + pm->ps->weapon == WP_EMPLACED_GUN) + { //oh no! + int i = 0; + int weap = -1; + + while (i < WP_NUM_WEAPONS) + { + if ((pm->ps->stats[STAT_WEAPONS] & (1 << i)) && i != WP_NONE) + { //this one's good + weap = i; + break; + } + i++; + } + + if (weap != -1) + { + pm->cmd.weapon = weap; + pm->ps->weapon = weap; + return; + } + } + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + { //riding a vehicle + veh = pm_entVeh; + if ( veh && + (veh->m_pVehicle && veh->m_pVehicle->m_pVehicleInfo->type == VH_WALKER || veh->m_pVehicle && veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) ) + {//riding a walker/fighter + //keep saber off, do no weapon stuff at all! + pm->ps->saberHolstered = 2; +#ifdef QAGAME + pm->cmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); +#else + if ( g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].fHoming + || g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].fHoming ) + {//our vehicle uses a rocket launcher, so do the normal checks + vehicleRocketLock = qtrue; + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + else + { + pm->cmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); + } +#endif + } + } + + if (pm->ps->weapon != WP_DISRUPTOR //not using disruptor + && pm->ps->weapon != WP_ROCKET_LAUNCHER//not using rocket launcher + && pm->ps->weapon != WP_THERMAL//not using thermals + && !pm->ps->m_iVehicleNum )//not a vehicle or in a vehicle + { //check for exceeding max charge time if not using disruptor or rocket launcher or thermals + if ( pm->ps->weaponstate == WEAPON_CHARGING_ALT ) + { + int timeDif = (pm->cmd.serverTime - pm->ps->weaponChargeTime); + + if (timeDif > MAX_WEAPON_CHARGE_TIME) + { + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + } + } + + if ( pm->ps->weaponstate == WEAPON_CHARGING ) + { + int timeDif = (pm->cmd.serverTime - pm->ps->weaponChargeTime); + + if (timeDif > MAX_WEAPON_CHARGE_TIME) + { + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + } + } + + if (pm->ps->forceHandExtend == HANDEXTEND_WEAPONREADY && + PM_CanSetWeaponAnims()) + { //reset into weapon stance + if (pm->ps->weapon != WP_SABER && pm->ps->weapon != WP_MELEE && !PM_IsRocketTrooper()) + { //saber handles its own anims + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + //PM_StartTorsoAnim( TORSO_WEAPONREADY4 ); + PM_StartTorsoAnim( TORSO_RAISEWEAP1); + } + else + { + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + else + { + //PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + PM_StartTorsoAnim( TORSO_RAISEWEAP1); + } + } + } + + //we now go into a weapon raise anim after every force hand extend. + //this is so that my holster-view-weapon-when-hand-extend stuff works. + pm->ps->weaponstate = WEAPON_RAISING; + pm->ps->weaponTime += 250; + + pm->ps->forceHandExtend = HANDEXTEND_NONE; + } + else if (pm->ps->forceHandExtend != HANDEXTEND_NONE) + { //nothing else should be allowed to happen during this time, including weapon fire + int desiredAnim = 0; + qboolean seperateOnTorso = qfalse; + qboolean playFullBody = qfalse; + int desiredOnTorso = 0; + + switch(pm->ps->forceHandExtend) + { + case HANDEXTEND_FORCEPUSH: + desiredAnim = BOTH_FORCEPUSH; + break; + case HANDEXTEND_FORCEPULL: + desiredAnim = BOTH_FORCEPULL; + break; + case HANDEXTEND_FORCE_HOLD: + if ( (pm->ps->fd.forcePowersActive&(1<ps->fd.forcePowersActive&(1<ps->weapon == WP_MELEE + && pm->ps->activeForcePass > FORCE_LEVEL_2 ) + {//2-handed lightning + desiredAnim = BOTH_FORCE_2HANDEDLIGHTNING_HOLD; + } + else + { + desiredAnim = BOTH_FORCELIGHTNING_HOLD; + } + } + else if ( (pm->ps->fd.forcePowersActive&(1<ps->forceDodgeAnim; + break; + case HANDEXTEND_KNOCKDOWN: + if (pm->ps->forceDodgeAnim) + { + if (pm->ps->forceDodgeAnim > 4) + { //this means that we want to play a sepereate anim on the torso + int originalDAnim = pm->ps->forceDodgeAnim-8; //-8 is the original legs anim + if (originalDAnim == 2) + { + desiredAnim = BOTH_FORCE_GETUP_B1; + } + else if (originalDAnim == 3) + { + desiredAnim = BOTH_FORCE_GETUP_B3; + } + else + { + desiredAnim = BOTH_GETUP1; + } + + //now specify the torso anim + seperateOnTorso = qtrue; + desiredOnTorso = BOTH_FORCEPUSH; + } + else if (pm->ps->forceDodgeAnim == 2) + { + desiredAnim = BOTH_FORCE_GETUP_B1; + } + else if (pm->ps->forceDodgeAnim == 3) + { + desiredAnim = BOTH_FORCE_GETUP_B3; + } + else + { + desiredAnim = BOTH_GETUP1; + } + } + else + { + desiredAnim = BOTH_KNOCKDOWN1; + } + break; + case HANDEXTEND_DUELCHALLENGE: + desiredAnim = BOTH_ENGAGETAUNT; + break; + case HANDEXTEND_TAUNT: + desiredAnim = pm->ps->forceDodgeAnim; + if ( desiredAnim != BOTH_ENGAGETAUNT + && VectorCompare( pm->ps->velocity, vec3_origin ) + && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { + playFullBody = qtrue; + } + break; + case HANDEXTEND_PRETHROW: + desiredAnim = BOTH_A3_TL_BR; + playFullBody = qtrue; + break; + case HANDEXTEND_POSTTHROW: + desiredAnim = BOTH_D3_TL___; + playFullBody = qtrue; + break; + case HANDEXTEND_PRETHROWN: + desiredAnim = BOTH_KNEES1; + playFullBody = qtrue; + break; + case HANDEXTEND_POSTTHROWN: + if (pm->ps->forceDodgeAnim) + { + desiredAnim = BOTH_FORCE_GETUP_F2; + } + else + { + desiredAnim = BOTH_KNOCKDOWN5; + } + playFullBody = qtrue; + break; + case HANDEXTEND_DRAGGING: + desiredAnim = BOTH_B1_BL___; + break; + case HANDEXTEND_JEDITAUNT: + desiredAnim = BOTH_GESTURE1; + //playFullBody = qtrue; + break; + //Hmm... maybe use these, too? + //BOTH_FORCEHEAL_QUICK //quick heal (SP level 2 & 3) + //BOTH_MINDTRICK1 // wave (maybe for mind trick 2 & 3 - whole area, and for force seeing) + //BOTH_MINDTRICK2 // tap (maybe for mind trick 1 - one person) + //BOTH_FORCEGRIP_START //start grip + //BOTH_FORCEGRIP_HOLD //hold grip + //BOTH_FORCEGRIP_RELEASE //release grip + //BOTH_FORCELIGHTNING //quick lightning burst (level 1) + //BOTH_FORCELIGHTNING_START //start lightning + //BOTH_FORCELIGHTNING_HOLD //hold lightning + //BOTH_FORCELIGHTNING_RELEASE //release lightning + default: + desiredAnim = BOTH_FORCEPUSH; + break; + } + + if (!seperateOnTorso) + { //of seperateOnTorso, handle it after setting the legs + PM_SetAnim(SETANIM_TORSO, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->torsoTimer = 1; + } + + if (playFullBody) + { //sorry if all these exceptions are getting confusing. This one just means play on both legs and torso. + PM_SetAnim(SETANIM_BOTH, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->legsTimer = pm->ps->torsoTimer = 1; + } + else if (pm->ps->forceHandExtend == HANDEXTEND_DODGE || pm->ps->forceHandExtend == HANDEXTEND_KNOCKDOWN || + (pm->ps->forceHandExtend == HANDEXTEND_CHOKE && pm->ps->groundEntityNum == ENTITYNUM_NONE) ) + { //special case, play dodge anim on whole body, choke anim too if off ground + if (seperateOnTorso) + { + PM_SetAnim(SETANIM_LEGS, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->legsTimer = 1; + + PM_SetAnim(SETANIM_TORSO, desiredOnTorso, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->torsoTimer = 1; + } + else + { + PM_SetAnim(SETANIM_LEGS, desiredAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->legsTimer = 1; + } + } + + return; + } + + if (BG_InSpecialJump(pm->ps->legsAnim) || + BG_InRoll(pm->ps, pm->ps->legsAnim) || + PM_InRollComplete(pm->ps, pm->ps->legsAnim)) + { + /* + if (pm->cmd.weapon != WP_MELEE && + pm->ps->weapon != WP_MELEE && + (pm->ps->stats[STAT_WEAPONS] & (1<cmd.weapon = WP_SABER; + pm->ps->weapon = WP_SABER; + } + */ + if (pm->ps->weaponTime < pm->ps->legsTimer) + { + pm->ps->weaponTime = pm->ps->legsTimer; + } + } + + if (pm->ps->duelInProgress) + { + pm->cmd.weapon = WP_SABER; + pm->ps->weapon = WP_SABER; + + if (pm->ps->duelTime >= pm->cmd.serverTime) + { + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + } + } + + if (pm->ps->weapon == WP_SABER && pm->ps->saberMove != LS_READY && pm->ps->saberMove != LS_NONE) + { + pm->cmd.weapon = WP_SABER; //don't allow switching out mid-attack + } + + if (pm->ps->weapon == WP_SABER) + { + //rww - we still need the item stuff, so we won't return immediately + PM_WeaponLightsaber(); + killAfterItem = 1; + } + else if (pm->ps->weapon != WP_EMPLACED_GUN) + { + pm->ps->saberHolstered = 0; + } + + if (PM_CanSetWeaponAnims()) + { + if (pm->ps->weapon == WP_THERMAL || + pm->ps->weapon == WP_TRIP_MINE || + pm->ps->weapon == WP_DET_PACK) + { + if (pm->ps->weapon == WP_THERMAL) + { + if ((pm->ps->torsoAnim) == WeaponAttackAnim[pm->ps->weapon] && + (pm->ps->weaponTime-200) <= 0) + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + else + { + if ((pm->ps->torsoAnim) == WeaponAttackAnim[pm->ps->weapon] && + (pm->ps->weaponTime-700) <= 0) + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + } + } + + // don't allow attack until all buttons are up + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + + // ignore if spectator + if ( pm->ps->clientNum < MAX_CLIENTS && 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 ) ) { + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + && pm->ps->m_iVehicleNum) + {//riding a vehicle, can't use holdable items, this button operates as the weapon link/unlink toggle + return; + } + + if (!pm->ps->stats[STAT_HOLDABLE_ITEM]) + { + return; + } + + if (!PM_ItemUsable(pm->ps, 0)) + { + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + return; + } + else + { + if (pm->ps->stats[STAT_HOLDABLE_ITEMS] & (1 << bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag)) + { + if (bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_BINOCULARS && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_JETPACK && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_HEALTHDISP && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_AMMODISP && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_CLOAK && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_EWEB) + { //never use up the binoculars or jetpack or dispensers or cloak or ... + pm->ps->stats[STAT_HOLDABLE_ITEMS] -= (1 << bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag); + } + } + else + { + return; //this should not happen... + } + + pm->ps->pm_flags |= PMF_USE_ITEM_HELD; + PM_AddEvent( EV_USE_ITEM0 + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag ); + + if (bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_BINOCULARS && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_JETPACK && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_HEALTHDISP && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_AMMODISP && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_CLOAK && + bg_itemlist[pm->ps->stats[STAT_HOLDABLE_ITEM]].giTag != HI_EWEB) + { + pm->ps->stats[STAT_HOLDABLE_ITEM] = 0; + BG_CycleInven(pm->ps, 1); + } + } + return; + } + } else { + pm->ps->pm_flags &= ~PMF_USE_ITEM_HELD; + } + + /* + if (pm->ps->weapon == WP_SABER || pm->ps->weapon == WP_MELEE) + { //we can't toggle zoom while using saber (for obvious reasons) so make sure it's always off + pm->ps->zoomMode = 0; + pm->ps->zoomFov = 0; + pm->ps->zoomLocked = qfalse; + pm->ps->zoomLockTime = 0; + } + */ + + if (killAfterItem) + { + return; + } + + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + } + + if (pm->ps->isJediMaster && pm->ps->emplacedIndex) + { + pm->ps->emplacedIndex = 0; + pm->ps->saberHolstered = 0; + } + + if (pm->ps->duelInProgress && pm->ps->emplacedIndex) + { + pm->ps->emplacedIndex = 0; + pm->ps->saberHolstered = 0; + } + + if (pm->ps->weapon == WP_EMPLACED_GUN && pm->ps->emplacedIndex) + { + pm->cmd.weapon = WP_EMPLACED_GUN; //No switch for you! + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + + if (pm->ps->isJediMaster || pm->ps->duelInProgress || pm->ps->trueJedi) + { + pm->cmd.weapon = WP_SABER; + pm->ps->weapon = WP_SABER; + + if (pm->ps->isJediMaster || pm->ps->trueJedi) + { + pm->ps->stats[STAT_WEAPONS] = (1 << WP_SABER); + } + } + + amount = weaponData[pm->ps->weapon].energyPerShot; + + // take an ammo away if not infinite + if ( pm->ps->weapon != WP_NONE && + pm->ps->weapon == pm->cmd.weapon && + (pm->ps->weaponTime <= 0 || pm->ps->weaponstate != WEAPON_FIRING) ) + { + if ( pm->ps->clientNum < MAX_CLIENTS && pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) + { + // enough energy to fire this weapon? + if (pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < weaponData[pm->ps->weapon].energyPerShot && + pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < weaponData[pm->ps->weapon].altEnergyPerShot) + { //the weapon is out of ammo essentially because it cannot fire primary or secondary, so do the switch + //regardless of if the player is attacking or not + PM_AddEventWithParm( EV_NOAMMO, WP_NUM_WEAPONS+pm->ps->weapon ); + + if (pm->ps->weaponTime < 500) + { + pm->ps->weaponTime += 500; + } + return; + } + + if (pm->ps->weapon == WP_DET_PACK && !pm->ps->hasDetPackPlanted && pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] < 1) + { + PM_AddEventWithParm( EV_NOAMMO, WP_NUM_WEAPONS+pm->ps->weapon ); + + if (pm->ps->weaponTime < 500) + { + pm->ps->weaponTime += 500; + } + return; + } + } + } + + // 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; + } + + if (pm->ps->weapon == WP_DISRUPTOR && + pm->ps->zoomMode == 1) + { + if (pm_cancelOutZoom) + { + pm->ps->zoomMode = 0; + pm->ps->zoomFov = 0; + pm->ps->zoomLocked = qfalse; + pm->ps->zoomLockTime = 0; + PM_AddEvent( EV_DISRUPTOR_ZOOMSOUND ); + return; + } + + if (pm->cmd.forwardmove || + pm->cmd.rightmove || + pm->cmd.upmove > 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; + if (PM_CanSetWeaponAnims()) + { + if ( pm->ps->weapon == WP_SABER ) + { + PM_StartTorsoAnim( PM_GetSaberStance() ); + } + else if (pm->ps->weapon == WP_MELEE || PM_IsRocketTrooper()) + { + PM_StartTorsoAnim( pm->ps->legsAnim ); + } + else + { + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + PM_StartTorsoAnim( TORSO_WEAPONREADY4 ); + } + else + { + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + else + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + } + } + return; + } + + if (PM_CanSetWeaponAnims() && + !PM_IsRocketTrooper() && + pm->ps->weaponstate == WEAPON_READY && pm->ps->weaponTime <= 0 && + (pm->ps->weapon >= WP_BRYAR_PISTOL || pm->ps->weapon == WP_STUN_BATON) && + pm->ps->torsoTimer <= 0 && + (pm->ps->torsoAnim) != WeaponReadyAnim[pm->ps->weapon] && + pm->ps->torsoAnim != TORSO_WEAPONIDLE3 && + pm->ps->weapon != WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + else if (PM_CanSetWeaponAnims() && + pm->ps->weapon == WP_MELEE) + { + if (pm->ps->weaponTime <= 0 && + pm->ps->forceHandExtend == HANDEXTEND_NONE) + { + int desTAnim = pm->ps->legsAnim; + + if (desTAnim == BOTH_STAND1 || + desTAnim == BOTH_STAND2) + { //remap the standard standing anims for melee stance + desTAnim = BOTH_STAND6; + } + + if (!(pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK))) + { //don't do this while holding attack + if (pm->ps->torsoAnim != desTAnim) + { + PM_StartTorsoAnim( desTAnim ); + } + } + } + } + else if (PM_CanSetWeaponAnims() && PM_IsRocketTrooper()) + { + int desTAnim = pm->ps->legsAnim; + + if (!(pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK))) + { //don't do this while holding attack + if (pm->ps->torsoAnim != desTAnim) + { + PM_StartTorsoAnim( desTAnim ); + } + } + } + + if (((pm->ps->torsoAnim) == TORSO_WEAPONREADY4 || + (pm->ps->torsoAnim) == BOTH_ATTACK4) && + (pm->ps->weapon != WP_DISRUPTOR || pm->ps->zoomMode != 1)) + { + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + PM_StartTorsoAnim( BOTH_GUNSIT1 ); + } + else if (PM_CanSetWeaponAnims()) + { + PM_StartTorsoAnim( WeaponReadyAnim[pm->ps->weapon] ); + } + } + else if (((pm->ps->torsoAnim) != TORSO_WEAPONREADY4 && + (pm->ps->torsoAnim) != BOTH_ATTACK4) && + PM_CanSetWeaponAnims() && + (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1)) + { + PM_StartTorsoAnim( TORSO_WEAPONREADY4 ); + } + + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE) + {//we are a vehicle + veh = pm_entSelf; + } + if ( veh + && veh->m_pVehicle ) + { + if ( g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID].fHoming + || g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID].fHoming ) + {//don't clear the rocket locking ever? + vehicleRocketLock = qtrue; + } + } + + if ( !vehicleRocketLock ) + { + if (pm->ps->weapon != WP_ROCKET_LAUNCHER) + { + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + {//riding a vehicle, the vehicle will tell me my rocketlock stuff... + } + else + { + pm->ps->rocketLockIndex = ENTITYNUM_NONE; + pm->ps->rocketLockTime = 0; + pm->ps->rocketTargetTime = 0; + } + } + } + + if ( PM_DoChargedWeapons(vehicleRocketLock, veh)) + { + // In some cases the charged weapon code may want us to short circuit the rest of the firing code + return; + } + + // check for fire + if ( ! (pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK))) + { + pm->ps->weaponTime = 0; + pm->ps->weaponstate = WEAPON_READY; + return; + } + + if (pm->ps->weapon == WP_EMPLACED_GUN) + { + addTime = weaponData[pm->ps->weapon].fireTime; + pm->ps->weaponTime += addTime; + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) + { + PM_AddEvent( EV_ALT_FIRE ); + } + else + { + PM_AddEvent( EV_FIRE_WEAPON ); + } + return; + } + else if (pm->ps->m_iVehicleNum + && pm_entSelf->s.NPC_class==CLASS_VEHICLE) + { //a vehicle NPC that has a pilot + pm->ps->weaponstate = WEAPON_FIRING; + pm->ps->weaponTime += 100; +#ifdef QAGAME //hack, only do it game-side. vehicle weapons don't really need predicting I suppose. + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) + { + G_CheapWeaponFire(pm->ps->clientNum, EV_ALT_FIRE); + } + else + { + G_CheapWeaponFire(pm->ps->clientNum, EV_FIRE_WEAPON); + } +#endif + /* + addTime = weaponData[WP_EMPLACED_GUN].fireTime; + pm->ps->weaponTime += addTime; + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) + { + PM_AddEvent( EV_ALT_FIRE ); + } + else + { + PM_AddEvent( EV_FIRE_WEAPON ); + } + */ + return; + } + + if (pm->ps->weapon == WP_DISRUPTOR && + (pm->cmd.buttons & BUTTON_ALT_ATTACK) && + !pm->ps->zoomLocked) + { + return; + } + + if (pm->ps->weapon == WP_DISRUPTOR && + (pm->cmd.buttons & BUTTON_ALT_ATTACK) && + pm->ps->zoomMode == 2) + { //can't use disruptor secondary while zoomed binoculars + return; + } + + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { + PM_StartTorsoAnim( BOTH_ATTACK4 ); + } + else if (pm->ps->weapon == WP_MELEE) + { //special anims for standard melee attacks + //Alternate between punches and use the anim length as weapon time. + if (!pm->ps->m_iVehicleNum) + { //if riding a vehicle don't do this stuff at all + if (pm->debugMelee && + (pm->cmd.buttons & BUTTON_ATTACK) && + (pm->cmd.buttons & BUTTON_ALT_ATTACK)) + { //ok, grapple time +#if 0 //eh, I want to try turning the saber off, but can't do that reliably for prediction.. + qboolean icandoit = qtrue; + if (pm->ps->weaponTime > 0) + { //weapon busy + icandoit = qfalse; + } + if (pm->ps->forceHandExtend != HANDEXTEND_NONE) + { //force power or knockdown or something + icandoit = qfalse; + } + if (pm->ps->weapon != WP_SABER && pm->ps->weapon != WP_MELEE) + { + icandoit = qfalse; + } + + if (icandoit) + { + //G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_GRAB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + PM_SetAnim(SETANIM_BOTH, BOTH_KYLE_GRAB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (pm->ps->torsoAnim == BOTH_KYLE_GRAB) + { //providing the anim set succeeded.. + pm->ps->torsoTimer += 500; //make the hand stick out a little longer than it normally would + if (pm->ps->legsAnim == pm->ps->torsoAnim) + { + pm->ps->legsTimer = pm->ps->torsoTimer; + } + pm->ps->weaponTime = pm->ps->torsoTimer; + return; + } + } +#else + #ifdef QAGAME + if (pm_entSelf) + { + if (TryGrapple((gentity_t *)pm_entSelf)) + { + return; + } + } + #else + return; + #endif +#endif + } + else if (pm->debugMelee && + (pm->cmd.buttons & BUTTON_ALT_ATTACK)) + { //kicks + if (!BG_KickingAnim(pm->ps->torsoAnim) && + !BG_KickingAnim(pm->ps->legsAnim)) + { + int kickMove = PM_KickMoveForConditions(); + if (kickMove == LS_HILT_BASH) + { //yeah.. no hilt to bash with! + kickMove = LS_KICK_F; + } + + if (kickMove != -1) + { + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//if in air, convert kick to an in-air kick + float gDist = PM_GroundDistance(); + //let's only allow air kicks if a certain distance from the ground + //it's silly to be able to do them right as you land. + //also looks wrong to transition from a non-complete flip anim... + if ((!BG_FlippingAnim( pm->ps->legsAnim ) || pm->ps->legsTimer <= 0) && + gDist > 64.0f && //strict minimum + gDist > (-pm->ps->velocity[2])-64.0f //make sure we are high to ground relative to downward velocity as well + ) + { + switch ( kickMove ) + { + case LS_KICK_F: + kickMove = LS_KICK_F_AIR; + break; + case LS_KICK_B: + kickMove = LS_KICK_B_AIR; + break; + case LS_KICK_R: + kickMove = LS_KICK_R_AIR; + break; + case LS_KICK_L: + kickMove = LS_KICK_L_AIR; + break; + default: //oh well, can't do any other kick move while in-air + kickMove = -1; + break; + } + } + else + { //off ground, but too close to ground + kickMove = -1; + } + } + } + + if (kickMove != -1) + { + int kickAnim = saberMoveData[kickMove].animToUse; + + if (kickAnim != -1) + { + PM_SetAnim(SETANIM_BOTH, kickAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + if (pm->ps->legsAnim == kickAnim) + { + pm->ps->weaponTime = pm->ps->legsTimer; + return; + } + } + } + } + + //if got here then no move to do so put torso into leg idle or whatever + if (pm->ps->torsoAnim != pm->ps->legsAnim) + { + PM_SetAnim(SETANIM_BOTH, pm->ps->legsAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + } + pm->ps->weaponTime = 0; + return; + } + else + { //just punch + int desTAnim = BOTH_MELEE1; + if (pm->ps->torsoAnim == BOTH_MELEE1) + { + desTAnim = BOTH_MELEE2; + } + PM_StartTorsoAnim( desTAnim ); + + if (pm->ps->torsoAnim == desTAnim) + { + pm->ps->weaponTime = pm->ps->torsoTimer; + } + } + } + } + else + { + PM_StartTorsoAnim( WeaponAttackAnim[pm->ps->weapon] ); + } + + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + amount = weaponData[pm->ps->weapon].altEnergyPerShot; + } + else + { + amount = weaponData[pm->ps->weapon].energyPerShot; + } + + pm->ps->weaponstate = WEAPON_FIRING; + + // take an ammo away if not infinite + if ( pm->ps->clientNum < MAX_CLIENTS && pm->ps->ammo[ weaponData[pm->ps->weapon].ammoIndex ] != -1 ) + { + // enough energy to fire this weapon? + if ((pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] - amount) >= 0) + { + pm->ps->ammo[weaponData[pm->ps->weapon].ammoIndex] -= amount; + } + else // Not enough energy + { + // Switch weapons + if (pm->ps->weapon != WP_DET_PACK || !pm->ps->hasDetPackPlanted) + { + PM_AddEventWithParm( EV_NOAMMO, WP_NUM_WEAPONS+pm->ps->weapon ); + if (pm->ps->weaponTime < 500) + { + pm->ps->weaponTime += 500; + } + } + return; + } + } + + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) { + //if ( pm->ps->weapon == WP_BRYAR_PISTOL && pm->gametype != GT_SIEGE ) + if (0) + { //kind of a hack for now + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = weaponData[pm->ps->weapon].fireTime; + } + else if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode != 1) + { + PM_AddEvent( EV_FIRE_WEAPON ); + addTime = weaponData[pm->ps->weapon].fireTime; + } + else + { + if (pm->ps->weapon != WP_MELEE || + !pm->ps->m_iVehicleNum) + { //do not fire melee events at all when on vehicle + PM_AddEvent( EV_ALT_FIRE ); + } + addTime = weaponData[pm->ps->weapon].altFireTime; + } + } + else { + if (pm->ps->weapon != WP_MELEE || + !pm->ps->m_iVehicleNum) + { //do not fire melee events at all when on vehicle + PM_AddEvent( EV_FIRE_WEAPON ); + } + addTime = weaponData[pm->ps->weapon].fireTime; + if ( pm->gametype == GT_SIEGE && pm->ps->weapon == WP_DET_PACK ) + { // were far too spammy before? So says Rick. + addTime *= 2; + } + } + + /* + if ( pm->ps->powerups[PW_HASTE] ) { + addTime /= 1.3; + } + */ + + if (pm->ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + addTime *= 0.75; + } + else if (pm->ps->fd.forceRageRecoveryTime > pm->cmd.serverTime) + { + addTime *= 1.5; + } + + pm->ps->weaponTime += addTime; +} + +/* +================ +PM_Animate +================ +*/ + +static void PM_Animate( void ) { + if ( pm->cmd.buttons & BUTTON_GESTURE ) { + if (pm->ps->m_iVehicleNum) + { //eh, fine, clear it + if (pm->ps->forceHandExtendTime < pm->cmd.serverTime) + { + pm->ps->forceHandExtend = HANDEXTEND_NONE; + } + } + + if ( pm->ps->torsoTimer < 1 && pm->ps->forceHandExtend == HANDEXTEND_NONE && + pm->ps->legsTimer < 1 && pm->ps->weaponTime < 1 && pm->ps->saberLockTime < pm->cmd.serverTime) { + + pm->ps->forceHandExtend = HANDEXTEND_TAUNT; + + //FIXME: random taunt anims? + pm->ps->forceDodgeAnim = BOTH_ENGAGETAUNT; + + pm->ps->forceHandExtendTime = pm->cmd.serverTime + 1000; + + //pm->ps->weaponTime = 100; + + PM_AddEvent( EV_TAUNT ); + } +#if 0 +// Here's an interesting bit. The bots in TA used buttons to do additional gestures. +// I ripped them out because I didn't want too many buttons given the fact that I was already adding some for JK2. +// We can always add some back in if we want though. + } 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; + } + } +} + +// Following function is stateless (at the moment). And hoisting it out +// of the namespace here is easier than fixing all the places it's used, +// which includes files that are also compiled in SP. We do need to make +// sure we only get one copy in the linker, though. + +#include "../namespace_end.h" + +#if !defined(_XBOX) || defined(QAGAME) +extern vmCvar_t bg_fighterAltControl; +qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ) +{ + if ( bg_fighterAltControl.integer + && ps->clientNum < MAX_CLIENTS //real client + && ps->m_iVehicleNum//in a vehicle + && pVeh //valid vehicle data pointer + && pVeh->m_pVehicleInfo//valid vehicle info + && pVeh->m_pVehicleInfo->type == VH_FIGHTER )//fighter + //FIXME: specify per vehicle instead of assuming true for all fighters + //FIXME: map/server setting? + {//can roll and pitch without limitation! + return qtrue; + } + return qfalse; +} +#else +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); +#endif + +#include "../namespace_begin.h" + +/* +================ +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]; +#ifdef VEH_CONTROL_SCHEME_4 + if ( pm_entVeh + && pm_entVeh->m_pVehicle + && pm_entVeh->m_pVehicle->m_pVehicleInfo + && pm_entVeh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER + && (cmd->serverTime-pm_entVeh->playerState->hyperSpaceTime) >= HYPERSPACE_TIME ) + {//in a vehicle and not hyperspacing + if ( i == PITCH ) + { + int pitchClamp = ANGLE2SHORT(AngleNormalize180(pm_entVeh->m_pVehicle->m_vPrevRiderViewAngles[PITCH]+10.0f)); + // don't let the player look up or down more than 22.5 degrees + if ( temp > pitchClamp ) + { + ps->delta_angles[i] = pitchClamp - cmd->angles[i]; + temp = pitchClamp; + } + else if ( temp < -pitchClamp ) + { + ps->delta_angles[i] = -pitchClamp - cmd->angles[i]; + temp = -pitchClamp; + } + } + if ( i == YAW ) + { + int yawClamp = ANGLE2SHORT(AngleNormalize180(pm_entVeh->m_pVehicle->m_vPrevRiderViewAngles[YAW]+10.0f)); + // don't let the player look left or right more than 22.5 degrees + if ( temp > yawClamp ) + { + ps->delta_angles[i] = yawClamp - cmd->angles[i]; + temp = yawClamp; + } + else if ( temp < -yawClamp ) + { + ps->delta_angles[i] = -yawClamp - cmd->angles[i]; + temp = -yawClamp; + } + } + } +#else //VEH_CONTROL_SCHEME_4 + if ( pm_entVeh && BG_UnrestrainedPitchRoll( ps, pm_entVeh->m_pVehicle ) ) + {//in a fighter + /* + if ( i == ROLL ) + {//get roll from vehicle + ps->viewangles[ROLL] = pm_entVeh->playerState->viewangles[ROLL];//->m_pVehicle->m_vOrientation[ROLL]; + continue; + + } + */ + } +#endif // VEH_CONTROL_SCHEME_4 + else + { + 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); + } +} + +/* +void PM_UpdateViewAngles( playerState_t *ps, const usercmd_t *cmd ) { + short temp; + int i; + float rootPitch = 0, pitchMin=-90, pitchMax=90, yawMin=0, yawMax=0, lockedYawValue = 0; //just to shut up warnings + qboolean lockedYaw = qfalse, clamped = qfalse; + bgEntity_t *vehEnt = NULL; + + 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 + } + + // If we're a vehicle, or we're riding a vehicle...? + if ( ps->m_iVehicleNum ) + { + if ( ps->clientNum < MAX_CLIENTS ) + { //player riding vehicle + vehEnt = PM_BGEntForNum(ps->m_iVehicleNum); + } + else + { //vehicle with player pilot + vehEnt = PM_BGEntForNum(ps->clientNum); + } + if ( vehEnt ) + {//there is a vehicle + Vehicle_t *pVeh = vehEnt->m_pVehicle; + if ( pVeh && pVeh->m_pVehicleInfo ) + { + // There is a vehicle... + if ( pVeh->m_pVehicleInfo->type != VH_ANIMAL ) + {//animals just turn normally, no clamping + if ( pVeh->m_pVehicleInfo->type == VH_FIGHTER ) + { + rootPitch = pVeh->m_vOrientation[PITCH];//gent->owner->client->ps.vehicleAngles[PITCH];//??? what if goes over 90 when add the min/max? + if ( pVeh->m_pVehicleInfo->pitchLimit == -1 ) + { + pitchMax = 180; + } + else + { + pitchMax = pVeh->m_pVehicleInfo->pitchLimit; + } + pitchMin = -pitchMax; + } + else + { + lockedYawValue = 0;//gent->owner->client->ps.vehicleAngles[YAW]; + lockedYaw = qtrue; + yawMax = pVeh->m_pVehicleInfo->lookYaw; + yawMin = -yawMax; + rootPitch = 0;//gent->owner->client->ps.vehicleAngles[PITCH];//??? what if goes over 90 when add the min/max? + pitchMax = pVeh->m_pVehicleInfo->lookPitch; + pitchMin = -pitchMax; + } + } + } + } + } + if ( 1 ) + { + const short pitchClampMin = ANGLE2SHORT(rootPitch+pitchMin); + const short pitchClampMax = ANGLE2SHORT(rootPitch+pitchMax); + const short yawClampMin = ANGLE2SHORT(lockedYawValue+yawMin); + const short yawClampMax = ANGLE2SHORT(lockedYawValue+yawMax); + for (i=0 ; i<3 ; i++) + { + temp = cmd->angles[i] + ps->delta_angles[i]; + if ( i == PITCH ) + { + //FIXME get this limit from the NPCs stats? + // don't let the player look up or down more than 90 degrees + if ( temp > pitchClampMax ) + { + ps->delta_angles[i] = (pitchClampMax - cmd->angles[i]) & 0xffff; //& clamp to short + temp = pitchClampMax; + clamped = qtrue; + } + else if ( temp < pitchClampMin ) + { + ps->delta_angles[i] = (pitchClampMin - cmd->angles[i]) & 0xffff; //& clamp to short + temp = pitchClampMin; + clamped = qtrue; + } + } + if ( i == YAW && lockedYaw ) + { + //FIXME get this limit from the NPCs stats? + // don't let the player look up or down more than 90 degrees + if ( temp > yawClampMax ) + { + ps->delta_angles[i] = (yawClampMax - cmd->angles[i]) & 0xffff; //& clamp to short + temp = yawClampMax; + clamped = qtrue; + } + else if ( temp < yawClampMin ) + { + ps->delta_angles[i] = (yawClampMin - cmd->angles[i]) & 0xffff; //& clamp to short + temp = yawClampMin; + clamped = qtrue; + } + ps->viewangles[i] = SHORT2ANGLE(temp); + } + else + { + ps->viewangles[i] = SHORT2ANGLE(temp); + } + } + } + else + { + // 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); + } + } + +} +*/ + +//------------------------------------------- +void PM_AdjustAttackStates( pmove_t *pm ) +//------------------------------------------- +{ + int amount; + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + { //riding a vehicle + bgEntity_t *veh = pm_entVeh; + if ( veh && + (veh->m_pVehicle && (veh->m_pVehicle->m_pVehicleInfo->type == VH_WALKER || veh->m_pVehicle && veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER)) ) + {//riding a walker/fighter + //not firing, ever + pm->ps->eFlags &= ~(EF_FIRING|EF_ALT_FIRING); + return; + } + } + // get ammo usage + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].altEnergyPerShot; + } + else + { + amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - weaponData[pm->ps->weapon].energyPerShot; + } + + // disruptor alt-fire should toggle the zoom mode, but only bother doing this for the player? + if ( pm->ps->weapon == WP_DISRUPTOR && pm->ps->weaponstate == WEAPON_READY ) + { + if ( !(pm->ps->eFlags & EF_ALT_FIRING) && (pm->cmd.buttons & BUTTON_ALT_ATTACK) /*&& + pm->cmd.upmove <= 0 && !pm->cmd.forwardmove && !pm->cmd.rightmove*/) + { + // We just pressed the alt-fire key + if ( !pm->ps->zoomMode && pm->ps->pm_type != PM_DEAD ) + { + // not already zooming, so do it now + pm->ps->zoomMode = 1; + pm->ps->zoomLocked = qfalse; + pm->ps->zoomFov = 80.0f;//cg_fov.value; + pm->ps->zoomLockTime = pm->cmd.serverTime + 50; + PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND); + } + else if (pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime) + { //check for == 1 so we can't turn binoculars off with disruptor alt fire + // already zooming, so must be wanting to turn it off + pm->ps->zoomMode = 0; + pm->ps->zoomTime = pm->ps->commandTime; + pm->ps->zoomLocked = qfalse; + PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND); + pm->ps->weaponTime = 1000; + } + } + else if ( !(pm->cmd.buttons & BUTTON_ALT_ATTACK ) && pm->ps->zoomLockTime < pm->cmd.serverTime) + { + // Not pressing zoom any more + if ( pm->ps->zoomMode ) + { + if (pm->ps->zoomMode == 1 && !pm->ps->zoomLocked) + { //approximate what level the client should be zoomed at based on how long zoom was held + pm->ps->zoomFov = ((pm->cmd.serverTime+50) - pm->ps->zoomLockTime) * 0.035f; + if (pm->ps->zoomFov > 50) + { + pm->ps->zoomFov = 50; + } + if (pm->ps->zoomFov < 1) + { + pm->ps->zoomFov = 1; + } + } + // were zooming in, so now lock the zoom + pm->ps->zoomLocked = qtrue; + } + } + //This seemed like a good idea, but apparently it confuses people. So disabled for now. + /* + else if (!(pm->ps->eFlags & EF_ALT_FIRING) && (pm->cmd.buttons & BUTTON_ALT_ATTACK) && + (pm->cmd.upmove > 0 || pm->cmd.forwardmove || pm->cmd.rightmove)) + { //if you try to zoom while moving, just convert it into a primary attack + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + pm->cmd.buttons |= BUTTON_ATTACK; + } + */ + + /* + if (pm->cmd.upmove > 0 || pm->cmd.forwardmove || pm->cmd.rightmove) + { + if (pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime) + { //check for == 1 so we can't turn binoculars off with disruptor alt fire + pm->ps->zoomMode = 0; + pm->ps->zoomTime = pm->ps->commandTime; + pm->ps->zoomLocked = qfalse; + PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND); + } + } + */ + + if ( pm->cmd.buttons & BUTTON_ATTACK ) + { + // If we are zoomed, we should switch the ammo usage to the alt-fire, otherwise, we'll + // just use whatever ammo was selected from above + if ( pm->ps->zoomMode ) + { + amount = pm->ps->ammo[weaponData[ pm->ps->weapon ].ammoIndex] - + weaponData[pm->ps->weapon].altEnergyPerShot; + } + } + else + { + // alt-fire button pressing doesn't use any ammo + amount = 0; + } + } + /* + else if (pm->ps->weapon == WP_DISRUPTOR) //still perform certain checks, even if the weapon is not ready + { + if (pm->cmd.upmove > 0 || pm->cmd.forwardmove || pm->cmd.rightmove) + { + if (pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime) + { //check for == 1 so we can't turn binoculars off with disruptor alt fire + pm->ps->zoomMode = 0; + pm->ps->zoomTime = pm->ps->commandTime; + pm->ps->zoomLocked = qfalse; + PM_AddEvent(EV_DISRUPTOR_ZOOMSOUND); + } + } + } + */ + + // set the firing flag for continuous beam weapons, saber will fire even if out of ammo + if ( !(pm->ps->pm_flags & PMF_RESPAWNED) && + pm->ps->pm_type != PM_INTERMISSION && + ( pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) && + ( amount >= 0 || pm->ps->weapon == WP_SABER )) + { + if ( pm->cmd.buttons & BUTTON_ALT_ATTACK ) + { + pm->ps->eFlags |= EF_ALT_FIRING; + } + else + { + pm->ps->eFlags &= ~EF_ALT_FIRING; + } + + // This flag should always get set, even when alt-firing + pm->ps->eFlags |= EF_FIRING; + } + else + { + // Clear 'em out + pm->ps->eFlags &= ~(EF_FIRING|EF_ALT_FIRING); + } + + // disruptor should convert a main fire to an alt-fire if the gun is currently zoomed + if ( pm->ps->weapon == WP_DISRUPTOR) + { + if ( pm->cmd.buttons & BUTTON_ATTACK && pm->ps->zoomMode == 1 && pm->ps->zoomLocked) + { + // converting the main fire to an alt-fire + pm->cmd.buttons |= BUTTON_ALT_ATTACK; + pm->ps->eFlags |= EF_ALT_FIRING; + } + else if ( pm->cmd.buttons & BUTTON_ALT_ATTACK && pm->ps->zoomMode == 1 && pm->ps->zoomLocked) + { + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + pm->ps->eFlags &= ~EF_ALT_FIRING; + } + } +} + +void BG_CmdForRoll( playerState_t *ps, int anim, usercmd_t *pCmd ) +{ + switch ( (anim) ) + { + case BOTH_ROLL_F: + pCmd->forwardmove = 127; + pCmd->rightmove = 0; + break; + case BOTH_ROLL_B: + pCmd->forwardmove = -127; + pCmd->rightmove = 0; + break; + case BOTH_ROLL_R: + pCmd->forwardmove = 0; + pCmd->rightmove = 127; + break; + case BOTH_ROLL_L: + pCmd->forwardmove = 0; + pCmd->rightmove = -127; + break; + case BOTH_GETUP_BROLL_R: + pCmd->forwardmove = 0; + pCmd->rightmove = 48; + //NOTE: speed is 400 + break; + + case BOTH_GETUP_FROLL_R: + if ( ps->legsTimer <= 250 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = 0; + pCmd->rightmove = 48; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_BROLL_L: + pCmd->forwardmove = 0; + pCmd->rightmove = -48; + //NOTE: speed is 400 + break; + + case BOTH_GETUP_FROLL_L: + if ( ps->legsTimer <= 250 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = 0; + pCmd->rightmove = -48; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_BROLL_B: + if ( ps->torsoTimer <= 250 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else if ( PM_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->torsoTimer < 350 ) + {//beginning of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + //FIXME: ramp down over length of anim + pCmd->forwardmove = -64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_FROLL_B: + if ( ps->torsoTimer <= 100 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else if ( PM_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->torsoTimer < 200 ) + {//beginning of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + //FIXME: ramp down over length of anim + pCmd->forwardmove = -64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_BROLL_F: + if ( ps->torsoTimer <= 550 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else if ( PM_AnimLength( 0, (animNumber_t)ps->legsAnim ) - ps->torsoTimer < 150 ) + {//beginning of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + pCmd->forwardmove = 64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + + case BOTH_GETUP_FROLL_F: + if ( ps->torsoTimer <= 100 ) + {//end of anim + pCmd->forwardmove = pCmd->rightmove = 0; + } + else + { + //FIXME: ramp down over length of anim + pCmd->forwardmove = 64; + pCmd->rightmove = 0; + //NOTE: speed is 400 + } + break; + } + pCmd->upmove = 0; +} + +qboolean PM_SaberInTransition( int move ); + +void BG_AdjustClientSpeed(playerState_t *ps, usercmd_t *cmd, int svTime) +{ + saberInfo_t *saber; + + if (ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *bgEnt = pm_entSelf; + + if (bgEnt && bgEnt->s.NPC_class == CLASS_VEHICLE) + { //vehicles manage their own speed + return; + } + } + + //For prediction, always reset speed back to the last known server base speed + //If we didn't do this, under lag we'd eventually dwindle speed down to 0 even though + //that would not be the correct predicted value. + ps->speed = ps->basespeed; + + if (ps->forceHandExtend == HANDEXTEND_DODGE) + { + ps->speed = 0; + } + + if (ps->forceHandExtend == HANDEXTEND_KNOCKDOWN || + ps->forceHandExtend == HANDEXTEND_PRETHROWN || + ps->forceHandExtend == HANDEXTEND_POSTTHROWN) + { + ps->speed = 0; + } + + + if ( cmd->forwardmove < 0 && !(cmd->buttons&BUTTON_WALKING) && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//running backwards is slower than running forwards (like SP) + ps->speed *= 0.75; + } + + if (ps->fd.forcePowersActive & (1 << FP_GRIP)) + { + ps->speed *= 0.4; + } + + if (ps->fd.forcePowersActive & (1 << FP_SPEED)) + { + ps->speed *= 1.7; + } + else if (ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + ps->speed *= 1.3; + } + else if (ps->fd.forceRageRecoveryTime > svTime) + { + ps->speed *= 0.75; + } + + if (pm->ps->weapon == WP_DISRUPTOR && + pm->ps->zoomMode == 1 && pm->ps->zoomLockTime < pm->cmd.serverTime) + { + ps->speed *= 0.5f; + } + + if (ps->fd.forceGripCripple) + { + if (ps->fd.forcePowersActive & (1 << FP_RAGE)) + { + ps->speed *= 0.9; + } + else if (ps->fd.forcePowersActive & (1 << FP_SPEED)) + { //force speed will help us escape + ps->speed *= 0.8; + } + else + { + ps->speed *= 0.2; + } + } + + if ( BG_SaberInAttack( ps->saberMove ) && cmd->forwardmove < 0 ) + {//if running backwards while attacking, don't run as fast. + switch( ps->fd.saberAnimLevel ) + { + case FORCE_LEVEL_1: + ps->speed *= 0.75f; + break; + case FORCE_LEVEL_2: + case SS_DUAL: + case SS_STAFF: + ps->speed *= 0.60f; + break; + case FORCE_LEVEL_3: + ps->speed *= 0.45f; + break; + default: + break; + } + } + else if ( BG_SpinningSaberAnim( ps->legsAnim ) ) + { + if (ps->fd.saberAnimLevel == FORCE_LEVEL_3) + { + ps->speed *= 0.3f; + } + else + { + ps->speed *= 0.5f; + } + } + else if ( ps->weapon == WP_SABER && BG_SaberInAttack( ps->saberMove ) ) + {//if attacking with saber while running, drop your speed + switch( ps->fd.saberAnimLevel ) + { + case FORCE_LEVEL_2: + case SS_DUAL: + case SS_STAFF: + ps->speed *= 0.85f; + break; + case FORCE_LEVEL_3: + ps->speed *= 0.55f; + break; + default: + break; + } + } + else if (ps->weapon == WP_SABER && ps->fd.saberAnimLevel == FORCE_LEVEL_3 && + PM_SaberInTransition(ps->saberMove)) + { //Now, we want to even slow down in transitions for level 3 (since it has chains and stuff now) + if (cmd->forwardmove < 0) + { + ps->speed *= 0.4f; + } + else + { + ps->speed *= 0.6f; + } + } + + if ( BG_InRoll( ps, ps->legsAnim ) && ps->speed > 50 ) + { //can't roll unless you're able to move normally + if ((ps->legsAnim) == BOTH_ROLL_B) + { //backwards roll is pretty fast, should also be slower + if (ps->legsTimer > 800) + { + ps->speed = ps->legsTimer/2.5; + } + else + { + ps->speed = ps->legsTimer/6.0;//450; + } + } + else + { + if (ps->legsTimer > 800) + { + ps->speed = ps->legsTimer/1.5;//450; + } + else + { + ps->speed = ps->legsTimer/5.0;//450; + } + } + if (ps->speed > 600) + { + ps->speed = 600; + } + //Automatically slow down as the roll ends. + } + + saber = BG_MySaber( ps->clientNum, 0 ); + if ( saber + && saber->moveSpeedScale != 1.0f ) + { + ps->speed *= saber->moveSpeedScale; + } + saber = BG_MySaber( ps->clientNum, 1 ); + if ( saber + && saber->moveSpeedScale != 1.0f ) + { + ps->speed *= saber->moveSpeedScale; + } +} + +qboolean BG_InRollAnim( entityState_t *cent ) +{ + switch ( (cent->legsAnim) ) + { + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + return qtrue; + } + return qfalse; +} + +qboolean BG_InKnockDown( int anim ) +{ + switch ( (anim) ) + { + case BOTH_KNOCKDOWN1: + case BOTH_KNOCKDOWN2: + case BOTH_KNOCKDOWN3: + case BOTH_KNOCKDOWN4: + case BOTH_KNOCKDOWN5: + return qtrue; + break; + case BOTH_GETUP1: + case BOTH_GETUP2: + case BOTH_GETUP3: + case BOTH_GETUP4: + case BOTH_GETUP5: + case BOTH_FORCE_GETUP_F1: + case BOTH_FORCE_GETUP_F2: + case BOTH_FORCE_GETUP_B1: + case BOTH_FORCE_GETUP_B2: + case BOTH_FORCE_GETUP_B3: + case BOTH_FORCE_GETUP_B4: + case BOTH_FORCE_GETUP_B5: + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + return qtrue; + break; + } + return qfalse; +} + +qboolean BG_InRollES( entityState_t *ps, int anim ) +{ + switch ( (anim) ) + { + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + return qtrue; + break; + } + return qfalse; +} + +void BG_IK_MoveArm(void *ghoul2, int lHandBolt, int time, entityState_t *ent, int basePose, vec3_t desiredPos, qboolean *ikInProgress, + vec3_t origin, vec3_t angles, vec3_t scale, int blendTime, qboolean forceHalt) +{ + mdxaBone_t lHandMatrix; + vec3_t lHand; + vec3_t torg; + float distToDest; + + if (!ghoul2) + { + return; + } + + assert(bgHumanoidAnimations[basePose].firstFrame > 0); + + if (!*ikInProgress && !forceHalt) + { + int baseposeAnim = basePose; + sharedSetBoneIKStateParams_t ikP; + + //restrict the shoulder joint + //VectorSet(ikP.pcjMins,-50.0f,-80.0f,-15.0f); + //VectorSet(ikP.pcjMaxs,15.0f,40.0f,15.0f); + + //for now, leaving it unrestricted, but restricting elbow joint. + //This lets us break the arm however we want in order to fling people + //in throws, and doesn't look bad. + VectorSet(ikP.pcjMins,0,0,0); + VectorSet(ikP.pcjMaxs,0,0,0); + + //give the info on our entity. + ikP.blendTime = blendTime; + VectorCopy(origin, ikP.origin); + VectorCopy(angles, ikP.angles); + ikP.angles[PITCH] = 0; + ikP.pcjOverrides = 0; + ikP.radius = 10.0f; + VectorCopy(scale, ikP.scale); + + //base pose frames for the limb + ikP.startFrame = bgHumanoidAnimations[baseposeAnim].firstFrame + bgHumanoidAnimations[baseposeAnim].numFrames; + ikP.endFrame = bgHumanoidAnimations[baseposeAnim].firstFrame + bgHumanoidAnimations[baseposeAnim].numFrames; + + ikP.forceAnimOnBone = qfalse; //let it use existing anim if it's the same as this one. + + //we want to call with a null bone name first. This will init all of the + //ik system stuff on the g2 instance, because we need ragdoll effectors + //in order for our pcj's to know how to angle properly. + if (!strap_G2API_SetBoneIKState(ghoul2, time, NULL, IKS_DYNAMIC, &ikP)) + { + assert(!"Failed to init IK system for g2 instance!"); + } + + //Now, create our IK bone state. + if (strap_G2API_SetBoneIKState(ghoul2, time, "lhumerus", IKS_DYNAMIC, &ikP)) + { + //restrict the elbow joint + VectorSet(ikP.pcjMins,-90.0f,-20.0f,-20.0f); + VectorSet(ikP.pcjMaxs,30.0f,20.0f,-20.0f); + + if (strap_G2API_SetBoneIKState(ghoul2, time, "lradius", IKS_DYNAMIC, &ikP)) + { //everything went alright. + *ikInProgress = qtrue; + } + } + } + + if (*ikInProgress && !forceHalt) + { //actively update our ik state. + sharedIKMoveParams_t ikM; + sharedRagDollUpdateParams_t tuParms; + vec3_t tAngles; + + //set the argument struct up + VectorCopy(desiredPos, ikM.desiredOrigin); //we want the bone to move here.. if possible + + VectorCopy(angles, tAngles); + tAngles[PITCH] = tAngles[ROLL] = 0; + + strap_G2API_GetBoltMatrix(ghoul2, 0, lHandBolt, &lHandMatrix, tAngles, origin, time, 0, scale); + //Get the point position from the matrix. + lHand[0] = lHandMatrix.matrix[0][3]; + lHand[1] = lHandMatrix.matrix[1][3]; + lHand[2] = lHandMatrix.matrix[2][3]; + + VectorSubtract(lHand, desiredPos, torg); + distToDest = VectorLength(torg); + + //closer we are, more we want to keep updated. + //if we're far away we don't want to be too fast or we'll start twitching all over. + if (distToDest < 2) + { //however if we're this close we want very precise movement + ikM.movementSpeed = 0.4f; + } + else if (distToDest < 16) + { + ikM.movementSpeed = 0.9f;//8.0f; + } + else if (distToDest < 32) + { + ikM.movementSpeed = 0.8f;//4.0f; + } + else if (distToDest < 64) + { + ikM.movementSpeed = 0.7f;//2.0f; + } + else + { + ikM.movementSpeed = 0.6f; + } + VectorCopy(origin, ikM.origin); //our position in the world. + + ikM.boneName[0] = 0; + if (strap_G2API_IKMove(ghoul2, time, &ikM)) + { + //now do the standard model animate stuff with ragdoll update params. + VectorCopy(angles, tuParms.angles); + tuParms.angles[PITCH] = 0; + + VectorCopy(origin, tuParms.position); + VectorCopy(scale, tuParms.scale); + + tuParms.me = ent->number; + VectorClear(tuParms.velocity); + + strap_G2API_AnimateG2Models(ghoul2, time, &tuParms); + } + else + { + *ikInProgress = qfalse; + } + } + else if (*ikInProgress) + { //kill it + float cFrame, animSpeed; + int sFrame, eFrame, flags; + + strap_G2API_SetBoneIKState(ghoul2, time, "lhumerus", IKS_NONE, NULL); + strap_G2API_SetBoneIKState(ghoul2, time, "lradius", IKS_NONE, NULL); + + //then reset the angles/anims on these PCJs + strap_G2API_SetBoneAngles(ghoul2, 0, "lhumerus", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "lradius", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, time); + + //Get the anim/frames that the pelvis is on exactly, and match the left arm back up with them again. + strap_G2API_GetBoneAnim(ghoul2, "pelvis", (const int)time, &cFrame, &sFrame, &eFrame, &flags, &animSpeed, 0, 0); + strap_G2API_SetBoneAnim(ghoul2, 0, "lhumerus", sFrame, eFrame, flags, animSpeed, time, sFrame, 300); + strap_G2API_SetBoneAnim(ghoul2, 0, "lradius", sFrame, eFrame, flags, animSpeed, time, sFrame, 300); + + //And finally, get rid of all the ik state effector data by calling with null bone name (similar to how we init it). + strap_G2API_SetBoneIKState(ghoul2, time, NULL, IKS_NONE, NULL); + + *ikInProgress = qfalse; + } +} + +//Adjust the head/neck desired angles +void BG_UpdateLookAngles( int lookingDebounceTime, vec3_t lastHeadAngles, int time, vec3_t lookAngles, float lookSpeed, float minPitch, float maxPitch, float minYaw, float maxYaw, float minRoll, float maxRoll ) +{ + static const float fFrameInter = 0.1f; + static vec3_t oldLookAngles; + static vec3_t lookAnglesDiff; + static int ang; + + if ( lookingDebounceTime > time ) + { + //clamp so don't get "Exorcist" effect + if ( lookAngles[PITCH] > maxPitch ) + { + lookAngles[PITCH] = maxPitch; + } + else if ( lookAngles[PITCH] < minPitch ) + { + lookAngles[PITCH] = minPitch; + } + if ( lookAngles[YAW] > maxYaw ) + { + lookAngles[YAW] = maxYaw; + } + else if ( lookAngles[YAW] < minYaw ) + { + lookAngles[YAW] = minYaw; + } + if ( lookAngles[ROLL] > maxRoll ) + { + lookAngles[ROLL] = maxRoll; + } + else if ( lookAngles[ROLL] < minRoll ) + { + lookAngles[ROLL] = minRoll; + } + + //slowly lerp to this new value + //Remember last headAngles + VectorCopy( lastHeadAngles, oldLookAngles ); + VectorSubtract( lookAngles, oldLookAngles, lookAnglesDiff ); + + for ( ang = 0; ang < 3; ang++ ) + { + lookAnglesDiff[ang] = AngleNormalize180( lookAnglesDiff[ang] ); + } + + if( VectorLengthSquared( lookAnglesDiff ) ) + { + lookAngles[PITCH] = AngleNormalize180( oldLookAngles[PITCH]+(lookAnglesDiff[PITCH]*fFrameInter*lookSpeed) ); + lookAngles[YAW] = AngleNormalize180( oldLookAngles[YAW]+(lookAnglesDiff[YAW]*fFrameInter*lookSpeed) ); + lookAngles[ROLL] = AngleNormalize180( oldLookAngles[ROLL]+(lookAnglesDiff[ROLL]*fFrameInter*lookSpeed) ); + } + } + //Remember current lookAngles next time + VectorCopy( lookAngles, lastHeadAngles ); +} + +//for setting visual look (headturn) angles +static void BG_G2ClientNeckAngles( void *ghoul2, int time, const vec3_t lookAngles, vec3_t headAngles, vec3_t neckAngles, vec3_t thoracicAngles, vec3_t headClampMinAngles, vec3_t headClampMaxAngles ) +{ + vec3_t lA; + VectorCopy( lookAngles, lA ); + //clamp the headangles (which should now be relative to the cervical (neck) angles + if ( lA[PITCH] < headClampMinAngles[PITCH] ) + { + lA[PITCH] = headClampMinAngles[PITCH]; + } + else if ( lA[PITCH] > headClampMaxAngles[PITCH] ) + { + lA[PITCH] = headClampMaxAngles[PITCH]; + } + + if ( lA[YAW] < headClampMinAngles[YAW] ) + { + lA[YAW] = headClampMinAngles[YAW]; + } + else if ( lA[YAW] > headClampMaxAngles[YAW] ) + { + lA[YAW] = headClampMaxAngles[YAW]; + } + + if ( lA[ROLL] < headClampMinAngles[ROLL] ) + { + lA[ROLL] = headClampMinAngles[ROLL]; + } + else if ( lA[ROLL] > headClampMaxAngles[ROLL] ) + { + lA[ROLL] = headClampMaxAngles[ROLL]; + } + + //split it up between the neck and cranium + if ( thoracicAngles[PITCH] ) + {//already been set above, blend them + thoracicAngles[PITCH] = (thoracicAngles[PITCH] + (lA[PITCH] * 0.4)) * 0.5f; + } + else + { + thoracicAngles[PITCH] = lA[PITCH] * 0.4; + } + if ( thoracicAngles[YAW] ) + {//already been set above, blend them + thoracicAngles[YAW] = (thoracicAngles[YAW] + (lA[YAW] * 0.1)) * 0.5f; + } + else + { + thoracicAngles[YAW] = lA[YAW] * 0.1; + } + if ( thoracicAngles[ROLL] ) + {//already been set above, blend them + thoracicAngles[ROLL] = (thoracicAngles[ROLL] + (lA[ROLL] * 0.1)) * 0.5f; + } + else + { + thoracicAngles[ROLL] = lA[ROLL] * 0.1; + } + + neckAngles[PITCH] = lA[PITCH] * 0.2f; + neckAngles[YAW] = lA[YAW] * 0.3f; + neckAngles[ROLL] = lA[ROLL] * 0.3f; + + headAngles[PITCH] = lA[PITCH] * 0.4; + headAngles[YAW] = lA[YAW] * 0.6; + headAngles[ROLL] = lA[ROLL] * 0.6; + + /* //non-applicable SP code + if ( G_RidingVehicle( cent->gent ) )// && type == VH_SPEEDER ? + {//aim torso forward too + headAngles[YAW] = neckAngles[YAW] = thoracicAngles[YAW] = 0; + } + */ + + strap_G2API_SetBoneAngles(ghoul2, 0, "cranium", headAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cervical", neckAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", thoracicAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); +} + +//rww - Finally decided to convert all this stuff to BG form. +static void BG_G2ClientSpineAngles( void *ghoul2, int motionBolt, vec3_t cent_lerpOrigin, vec3_t cent_lerpAngles, entityState_t *cent, + int time, vec3_t viewAngles, int ciLegs, int ciTorso, const vec3_t angles, vec3_t thoracicAngles, + vec3_t ulAngles, vec3_t llAngles, vec3_t modelScale, float *tPitchAngle, float *tYawAngle, int *corrTime ) +{ + qboolean doCorr = qfalse; + + //*tPitchAngle = viewAngles[PITCH]; + viewAngles[YAW] = AngleDelta( cent_lerpAngles[YAW], angles[YAW] ); + //*tYawAngle = viewAngles[YAW]; + +#if 1 + if ( !BG_FlippingAnim( cent->legsAnim ) && + !BG_SpinningSaberAnim( cent->legsAnim ) && + !BG_SpinningSaberAnim( cent->torsoAnim ) && + !BG_InSpecialJump( cent->legsAnim ) && + !BG_InSpecialJump( cent->torsoAnim ) && + !BG_InDeathAnim(cent->legsAnim) && + !BG_InDeathAnim(cent->torsoAnim) && + !BG_InRollES(cent, cent->legsAnim) && + !BG_InRollAnim(cent) && + !BG_SaberInSpecial(cent->saberMove) && + !BG_SaberInSpecialAttack(cent->torsoAnim) && + !BG_SaberInSpecialAttack(cent->legsAnim) && + + !BG_InKnockDown(cent->torsoAnim) && + !BG_InKnockDown(cent->legsAnim) && + !BG_InKnockDown(ciTorso) && + !BG_InKnockDown(ciLegs) && + + !BG_FlippingAnim( ciLegs ) && + !BG_SpinningSaberAnim( ciLegs ) && + !BG_SpinningSaberAnim( ciTorso ) && + !BG_InSpecialJump( ciLegs ) && + !BG_InSpecialJump( ciTorso ) && + !BG_InDeathAnim(ciLegs) && + !BG_InDeathAnim(ciTorso) && + !BG_SaberInSpecialAttack(ciTorso) && + !BG_SaberInSpecialAttack(ciLegs) && + + !(cent->eFlags & EF_DEAD) && + (cent->legsAnim) != (cent->torsoAnim) && + (ciLegs) != (ciTorso) && + !cent->m_iVehicleNum) + { + doCorr = qtrue; + } +#else + if ( ((!BG_FlippingAnim( cent->legsAnim ) + && !BG_SpinningSaberAnim( cent->legsAnim ) + && !BG_SpinningSaberAnim( cent->torsoAnim ) + && (cent->legsAnim) != (cent->torsoAnim)) //NOTE: presumes your legs & torso are on the same frame, though they *should* be because PM_SetAnimFinal tries to keep them in synch + || + (!BG_FlippingAnim( ciLegs ) + && !BG_SpinningSaberAnim( ciLegs ) + && !BG_SpinningSaberAnim( ciTorso ) + && (ciLegs) != (ciTorso))) + || + ciLegs != cent->legsAnim + || + ciTorso != cent->torsoAnim) + { + doCorr = qtrue; + *corrTime = time + 1000; //continue correcting for a second after to smooth things out. SP doesn't need this for whatever reason but I can't find a way around it. + } + else if (*corrTime >= time) + { + if (!BG_FlippingAnim( cent->legsAnim ) + && !BG_SpinningSaberAnim( cent->legsAnim ) + && !BG_SpinningSaberAnim( cent->torsoAnim ) + && !BG_FlippingAnim( ciLegs ) + && !BG_SpinningSaberAnim( ciLegs ) + && !BG_SpinningSaberAnim( ciTorso )) + { + doCorr = qtrue; + } + } +#endif + + if (doCorr) + {//FIXME: no need to do this if legs and torso on are same frame + //adjust for motion offset + mdxaBone_t boltMatrix; + vec3_t motionFwd, motionAngles; + vec3_t motionRt, tempAng; + int ang; + + strap_G2API_GetBoltMatrix_NoRecNoRot( ghoul2, 0, motionBolt, &boltMatrix, vec3_origin, cent_lerpOrigin, time, 0, modelScale); + //BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_Y, motionFwd ); + motionFwd[0] = -boltMatrix.matrix[0][1]; + motionFwd[1] = -boltMatrix.matrix[1][1]; + motionFwd[2] = -boltMatrix.matrix[2][1]; + + vectoangles( motionFwd, motionAngles ); + + //BG_GiveMeVectorFromMatrix( &boltMatrix, NEGATIVE_X, motionRt ); + motionRt[0] = -boltMatrix.matrix[0][0]; + motionRt[1] = -boltMatrix.matrix[1][0]; + motionRt[2] = -boltMatrix.matrix[2][0]; + + vectoangles( motionRt, tempAng ); + motionAngles[ROLL] = -tempAng[PITCH]; + + for ( ang = 0; ang < 3; ang++ ) + { + viewAngles[ang] = AngleNormalize180( viewAngles[ang] - AngleNormalize180( motionAngles[ang] ) ); + } + } + + //distribute the angles differently up the spine + //NOTE: each of these distributions must add up to 1.0f + thoracicAngles[PITCH] = viewAngles[PITCH]*0.20f; + llAngles[PITCH] = viewAngles[PITCH]*0.40f; + ulAngles[PITCH] = viewAngles[PITCH]*0.40f; + + thoracicAngles[YAW] = viewAngles[YAW]*0.20f; + ulAngles[YAW] = viewAngles[YAW]*0.35f; + llAngles[YAW] = viewAngles[YAW]*0.45f; + + thoracicAngles[ROLL] = viewAngles[ROLL]*0.20f; + ulAngles[ROLL] = viewAngles[ROLL]*0.35f; + llAngles[ROLL] = viewAngles[ROLL]*0.45f; +} + +/* +================== +CG_SwingAngles +================== +*/ +static float BG_SwingAngles( float destination, float swingTolerance, float clampTolerance, + float speed, float *angle, qboolean *swinging, int frametime ) { + 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 0; + } + + // 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 = frametime * scale * speed; + if ( move >= swing ) { + move = swing; + *swinging = qfalse; + } + *angle = AngleMod( *angle + move ); + } else if ( swing < 0 ) { + move = 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) ); + } + + return swing; +} + +//#define BONE_BASED_LEG_ANGLES + +//I apologize for this function +qboolean BG_InRoll2( entityState_t *es ) +{ + switch ( (es->legsAnim) ) + { + case BOTH_GETUP_BROLL_B: + case BOTH_GETUP_BROLL_F: + case BOTH_GETUP_BROLL_L: + case BOTH_GETUP_BROLL_R: + case BOTH_GETUP_FROLL_B: + case BOTH_GETUP_FROLL_F: + case BOTH_GETUP_FROLL_L: + case BOTH_GETUP_FROLL_R: + case BOTH_ROLL_F: + case BOTH_ROLL_B: + case BOTH_ROLL_R: + case BOTH_ROLL_L: + return qtrue; + break; + } + return qfalse; +} + + +extern qboolean BG_SaberLockBreakAnim( int anim ); //bg_panimate.c +void BG_G2PlayerAngles(void *ghoul2, int motionBolt, entityState_t *cent, int time, vec3_t cent_lerpOrigin, + vec3_t cent_lerpAngles, vec3_t legs[3], vec3_t legsAngles, qboolean *tYawing, + qboolean *tPitching, qboolean *lYawing, float *tYawAngle, float *tPitchAngle, + float *lYawAngle, int frametime, vec3_t turAngles, vec3_t modelScale, int ciLegs, + int ciTorso, int *corrTime, vec3_t lookAngles, vec3_t lastHeadAngles, int lookTime, + entityState_t *emplaced, int *crazySmoothFactor) +{ + int adddir = 0; + static int dir; + static int i; + static int movementOffsets[8] = { 0, 22, 45, -22, 0, 22, -45, -22 }; + float degrees_negative = 0; + float degrees_positive = 0; + static float dif; + static float dest; + static float speed; //, speed_dif, speed_desired; + static const float lookSpeed = 1.5f; +#ifdef BONE_BASED_LEG_ANGLES + static float legBoneYaw; +#endif + static vec3_t eyeAngles; + static vec3_t neckAngles; + static vec3_t velocity; + static vec3_t torsoAngles, headAngles; + static vec3_t velPos, velAng; + static vec3_t ulAngles, llAngles, viewAngles, angles, thoracicAngles = {0,0,0}; + static vec3_t headClampMinAngles = {-25,-55,-10}, headClampMaxAngles = {50,50,10}; + + if ( cent->m_iVehicleNum || cent->forceFrame || BG_SaberLockBreakAnim(cent->legsAnim) || BG_SaberLockBreakAnim(cent->torsoAnim) ) + { //a vehicle or riding a vehicle - in either case we don't need to be in here + vec3_t forcedAngles; + + VectorClear(forcedAngles); + forcedAngles[YAW] = cent_lerpAngles[YAW]; + forcedAngles[ROLL] = cent_lerpAngles[ROLL]; + AnglesToAxis( forcedAngles, legs ); + VectorCopy(forcedAngles, legsAngles); + + if (cent->number < MAX_CLIENTS) + { + strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + } + return; + } + + if ((time+2000) < *corrTime) + { + *corrTime = 0; + } + + VectorCopy( cent_lerpAngles, headAngles ); + headAngles[YAW] = AngleMod( headAngles[YAW] ); + VectorClear( legsAngles ); + VectorClear( torsoAngles ); + // --------- yaw ------------- + + // allow yaw to drift a bit + if ((( cent->legsAnim ) != BOTH_STAND1) || + ( cent->torsoAnim ) != WeaponReadyAnim[cent->weapon] ) + { + // if not standing still, always point all in the same direction + //cent->pe.torso.yawing = qtrue; // always center + *tYawing = qtrue; + //cent->pe.torso.pitching = qtrue; // always center + *tPitching = qtrue; + //cent->pe.legs.yawing = qtrue; // always center + *lYawing = qtrue; + } + + // adjust legs for movement dir + if ( cent->eFlags & EF_DEAD ) { + // don't let dead bodies twitch + dir = 0; + } else { + dir = cent->angles2[YAW]; + if ( dir < 0 || dir > 7 ) { + Com_Error( ERR_DROP, "Bad player movement angle (%i)", dir ); + } + } + + torsoAngles[YAW] = headAngles[YAW]; + + //for now, turn torso instantly and let the legs swing to follow + *tYawAngle = torsoAngles[YAW]; + + // --------- pitch ------------- + + VectorCopy( cent->pos.trDelta, velocity ); + + if (BG_InRoll2(cent)) + { //don't affect angles based on vel then + VectorClear(velocity); + } + else if (cent->weapon == WP_SABER && + BG_SaberInSpecial(cent->saberMove)) + { + VectorClear(velocity); + } + + speed = VectorNormalize( velocity ); + + if (!speed) + { + torsoAngles[YAW] = headAngles[YAW]; + } + + // only show a fraction of the pitch angle in the torso + if ( headAngles[PITCH] > 180 ) { + dest = (-360 + headAngles[PITCH]) * 0.75; + } else { + dest = headAngles[PITCH] * 0.75; + } + + if (cent->m_iVehicleNum) + { //swing instantly on vehicles + *tPitchAngle = dest; + } + else + { + BG_SwingAngles( dest, 15, 30, 0.1, tPitchAngle, tPitching, frametime ); + } + torsoAngles[PITCH] = *tPitchAngle; + + // --------- roll ------------- + + if ( speed ) { + vec3_t axis[3]; + float side; + + speed *= 0.05; + + AnglesToAxis( legsAngles, axis ); + side = speed * DotProduct( velocity, axis[1] ); + legsAngles[ROLL] -= side; + + side = speed * DotProduct( velocity, axis[0] ); + legsAngles[PITCH] += side; + } + + //legsAngles[YAW] = headAngles[YAW] + (movementOffsets[ dir ]*speed_dif); + + //rww - crazy velocity-based leg angle calculation + legsAngles[YAW] = headAngles[YAW]; + velPos[0] = cent_lerpOrigin[0] + velocity[0]; + velPos[1] = cent_lerpOrigin[1] + velocity[1]; + velPos[2] = cent_lerpOrigin[2];// + velocity[2]; + + if ( cent->groundEntityNum == ENTITYNUM_NONE || + cent->forceFrame || + (cent->weapon == WP_EMPLACED_GUN && emplaced) ) + { //off the ground, no direction-based leg angles (same if in saberlock) + VectorCopy(cent_lerpOrigin, velPos); + } + + VectorSubtract(cent_lerpOrigin, velPos, velAng); + + if (!VectorCompare(velAng, vec3_origin)) + { + vectoangles(velAng, velAng); + + if (velAng[YAW] <= legsAngles[YAW]) + { + degrees_negative = (legsAngles[YAW] - velAng[YAW]); + degrees_positive = (360 - legsAngles[YAW]) + velAng[YAW]; + } + else + { + degrees_negative = legsAngles[YAW] + (360 - velAng[YAW]); + degrees_positive = (velAng[YAW] - legsAngles[YAW]); + } + + if ( degrees_negative < degrees_positive ) + { + dif = degrees_negative; + adddir = 0; + } + else + { + dif = degrees_positive; + adddir = 1; + } + + if (dif > 90) + { + dif = (180 - dif); + } + + if (dif > 60) + { + dif = 60; + } + + //Slight hack for when playing is running backward + if (dir == 3 || dir == 5) + { + dif = -dif; + } + + if (adddir) + { + legsAngles[YAW] -= dif; + } + else + { + legsAngles[YAW] += dif; + } + } + + if (cent->m_iVehicleNum) + { //swing instantly on vehicles + *lYawAngle = legsAngles[YAW]; + } + else + { + BG_SwingAngles( legsAngles[YAW], /*40*/0, 90, 0.65, lYawAngle, lYawing, frametime ); + } + legsAngles[YAW] = *lYawAngle; + + /* + // pain twitch + CG_AddPainTwitch( cent, torsoAngles ); + */ + + legsAngles[ROLL] = 0; + torsoAngles[ROLL] = 0; + +// VectorCopy(legsAngles, turAngles); + + // pull the angles back out of the hierarchial chain + AnglesSubtract( headAngles, torsoAngles, headAngles ); + AnglesSubtract( torsoAngles, legsAngles, torsoAngles ); + + legsAngles[PITCH] = 0; + + if (cent->heldByClient) + { //keep the base angles clear when doing the IK stuff, it doesn't compensate for it. + //rwwFIXMEFIXME: Store leg angles off and add them to all the fed in angles for G2 functions? + VectorClear(legsAngles); + legsAngles[YAW] = cent_lerpAngles[YAW]; + } + +#ifdef BONE_BASED_LEG_ANGLES + legBoneYaw = legsAngles[YAW]; + VectorClear(legsAngles); + legsAngles[YAW] = cent_lerpAngles[YAW]; +#endif + + VectorCopy(legsAngles, turAngles); + + AnglesToAxis( legsAngles, legs ); + + VectorCopy( cent_lerpAngles, viewAngles ); + viewAngles[YAW] = viewAngles[ROLL] = 0; + viewAngles[PITCH] *= 0.5; + + VectorSet( angles, 0, legsAngles[1], 0 ); + + angles[0] = legsAngles[0]; + if ( angles[0] > 30 ) + { + angles[0] = 30; + } + else if ( angles[0] < -30 ) + { + angles[0] = -30; + } + + if (cent->weapon == WP_EMPLACED_GUN && + emplaced) + { //if using an emplaced gun, then we want to make sure we're angled to "hold" it right + vec3_t facingAngles; + + VectorSubtract(emplaced->pos.trBase, cent_lerpOrigin, facingAngles); + vectoangles(facingAngles, facingAngles); + + if (emplaced->weapon == WP_NONE) + { //e-web + VectorCopy(facingAngles, legsAngles); + AnglesToAxis( legsAngles, legs ); + } + else + { //misc emplaced + float dif = AngleSubtract(cent_lerpAngles[YAW], facingAngles[YAW]); + + /* + if (emplaced->weapon == WP_NONE) + { //offset is a little bit different for the e-web + dif -= 16.0f; + } + */ + + VectorSet(facingAngles, -16.0f, -dif, 0.0f); + + if (cent->legsAnim == BOTH_STRAFE_LEFT1 || cent->legsAnim == BOTH_STRAFE_RIGHT1) + { //try to adjust so it doesn't look wrong + if (crazySmoothFactor) + { //want to smooth a lot during this because it chops around and looks like ass + *crazySmoothFactor = time + 1000; + } + + BG_G2ClientSpineAngles(ghoul2, motionBolt, cent_lerpOrigin, cent_lerpAngles, cent, time, + viewAngles, ciLegs, ciTorso, angles, thoracicAngles, ulAngles, llAngles, modelScale, + tPitchAngle, tYawAngle, corrTime); + strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + + VectorAdd(facingAngles, thoracicAngles, facingAngles); + + if (cent->legsAnim == BOTH_STRAFE_LEFT1) + { //this one needs some further correction + facingAngles[YAW] -= 32.0f; + } + } + else + { + //strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + //strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "cranium", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + } + + VectorScale(facingAngles, 0.6f, facingAngles); + strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + VectorScale(facingAngles, 0.8f, facingAngles); + strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", facingAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + VectorScale(facingAngles, 0.8f, facingAngles); + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", facingAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + + //Now we want the head angled toward where we are facing + VectorSet(facingAngles, 0.0f, dif, 0.0f); + VectorScale(facingAngles, 0.6f, facingAngles); + strap_G2API_SetBoneAngles(ghoul2, 0, "cervical", facingAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + + return; //don't have to bother with the rest then + } + } + + BG_G2ClientSpineAngles(ghoul2, motionBolt, cent_lerpOrigin, cent_lerpAngles, cent, time, + viewAngles, ciLegs, ciTorso, angles, thoracicAngles, ulAngles, llAngles, modelScale, + tPitchAngle, tYawAngle, corrTime); + + VectorCopy(cent_lerpAngles, eyeAngles); + + for ( i = 0; i < 3; i++ ) + { + lookAngles[i] = AngleNormalize180( lookAngles[i] ); + eyeAngles[i] = AngleNormalize180( eyeAngles[i] ); + } + AnglesSubtract( lookAngles, eyeAngles, lookAngles ); + + BG_UpdateLookAngles(lookTime, lastHeadAngles, time, lookAngles, lookSpeed, -50.0f, 50.0f, -70.0f, 70.0f, -30.0f, 30.0f); + + BG_G2ClientNeckAngles(ghoul2, time, lookAngles, headAngles, neckAngles, thoracicAngles, headClampMinAngles, headClampMaxAngles); + +#ifdef BONE_BASED_LEG_ANGLES + { + vec3_t bLAngles; + VectorClear(bLAngles); + bLAngles[ROLL] = AngleNormalize180((legBoneYaw - cent_lerpAngles[YAW])); + strap_G2API_SetBoneAngles(ghoul2, 0, "model_root", bLAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + + if (!llAngles[YAW]) + { + llAngles[YAW] -= bLAngles[ROLL]; + } + } +#endif + strap_G2API_SetBoneAngles(ghoul2, 0, "lower_lumbar", llAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "upper_lumbar", ulAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", thoracicAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); + //strap_G2API_SetBoneAngles(ghoul2, 0, "cervical", vec3_origin, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); +} + +void BG_G2ATSTAngles(void *ghoul2, int time, vec3_t cent_lerpAngles ) +{// up right fwd + strap_G2API_SetBoneAngles(ghoul2, 0, "thoracic", cent_lerpAngles, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, 0, 0, time); +} + +static qboolean PM_AdjustAnglesForDualJumpAttack( playerState_t *ps, usercmd_t *ucmd ) +{ + //ucmd->angles[PITCH] = ANGLE2SHORT( ps->viewangles[PITCH] ) - ps->delta_angles[PITCH]; + //ucmd->angles[YAW] = ANGLE2SHORT( ps->viewangles[YAW] ) - ps->delta_angles[YAW]; + return qtrue; +} + +#ifdef __LCC__ +static void PM_CmdForSaberMoves(usercmd_t *ucmd) +#else +static ID_INLINE void PM_CmdForSaberMoves(usercmd_t *ucmd) +#endif +{ + //DUAL FORWARD+JUMP+ATTACK + if ( ( pm->ps->legsAnim == BOTH_JUMPATTACK6 + && pm->ps->saberMove == LS_JUMPATTACK_DUAL ) + || ( pm->ps->legsAnim == BOTH_BUTTERFLY_FL1 + && pm->ps->saberMove == LS_JUMPATTACK_STAFF_LEFT ) + || ( pm->ps->legsAnim == BOTH_BUTTERFLY_FR1 + && pm->ps->saberMove == LS_JUMPATTACK_STAFF_RIGHT ) + || ( pm->ps->legsAnim == BOTH_BUTTERFLY_RIGHT + && pm->ps->saberMove == LS_BUTTERFLY_RIGHT ) + || ( pm->ps->legsAnim == BOTH_BUTTERFLY_LEFT + && pm->ps->saberMove == LS_BUTTERFLY_LEFT ) ) + { + int aLen = PM_AnimLength(0, BOTH_JUMPATTACK6); + + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + + if ( pm->ps->legsAnim == BOTH_JUMPATTACK6 ) + { //dual stance attack + if ( pm->ps->legsTimer >= 100 //not at end + && (aLen - pm->ps->legsTimer) >= 250 ) //not in beginning + { //middle of anim + //push forward + ucmd->forwardmove = 127; + } + + if ( (pm->ps->legsTimer >= 900 //not at end + && aLen - pm->ps->legsTimer >= 950 ) //not in beginning + || ( pm->ps->legsTimer >= 1600 + && aLen - pm->ps->legsTimer >= 400 ) ) //not in beginning + { //one of the two jumps + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + { //still on ground? + if ( pm->ps->groundEntityNum >= MAX_CLIENTS ) + { + //jump! + pm->ps->velocity[2] = 250;//400; + pm->ps->fd.forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + //pm->ps->pm_flags |= PMF_JUMPING; + //FIXME: NPCs yell? + PM_AddEvent(EV_JUMP); + //G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + } + else + { //FIXME: if this is the second jump, maybe we should just stop the anim? + } + } + } + else + { //saberstaff attacks + int aLen = PM_AnimLength(0, (animNumber_t)pm->ps->legsAnim); + float lenMin = 1700.0f; + float lenMax = 1800.0f; + + if (pm->ps->legsAnim == BOTH_BUTTERFLY_LEFT) + { + lenMin = 1200.0f; + lenMax = 1400.0f; + } + + //FIXME: don't slide off people/obstacles? + if ( pm->ps->legsAnim == BOTH_BUTTERFLY_RIGHT + || pm->ps->legsAnim == BOTH_BUTTERFLY_LEFT ) + { + if ( pm->ps->legsTimer > 450 ) + { + switch ( pm->ps->legsAnim ) + { + case BOTH_BUTTERFLY_LEFT: + ucmd->rightmove = -127; + break; + case BOTH_BUTTERFLY_RIGHT: + ucmd->rightmove = 127; + break; + default: + break; + } + } + } + else + { + if ( pm->ps->legsTimer >= 100 //not at end + && aLen - pm->ps->legsTimer >= 250 )//not in beginning + {//middle of anim + //push forward + ucmd->forwardmove = 127; + } + } + + if ( pm->ps->legsTimer >= lenMin && pm->ps->legsTimer < lenMax ) + {//one of the two jumps + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + //jump! + if (pm->ps->legsAnim == BOTH_BUTTERFLY_LEFT) + { + pm->ps->velocity[2] = 350; + } + else + { + pm->ps->velocity[2] = 250; + } + pm->ps->fd.forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + //pm->ps->pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; + //FIXME: NPCs yell? + PM_AddEvent(EV_JUMP); + //G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + } + else + {//FIXME: if this is the second jump, maybe we should just stop the anim? + } + } + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//can only turn when your feet hit the ground + if (PM_AdjustAnglesForDualJumpAttack(pm->ps, ucmd)) + { + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, ucmd); + } + } + //rwwFIXMEFIXME: Bother with bbox resizing like sp? + } + //STAFF BACK+JUMP+ATTACK + else if (pm->ps->saberMove == LS_A_BACKFLIP_ATK && + pm->ps->legsAnim == BOTH_JUMPATTACK7) + { + int aLen = PM_AnimLength(0, BOTH_JUMPATTACK7); + + if ( pm->ps->legsTimer > 800 //not at end + && aLen - pm->ps->legsTimer >= 400 )//not in beginning + {//middle of anim + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + vec3_t yawAngles, backDir; + + //push backwards some? + VectorSet( yawAngles, 0, pm->ps->viewangles[YAW]+180, 0 ); + AngleVectors( yawAngles, backDir, 0, 0 ); + VectorScale( backDir, 100, pm->ps->velocity ); + + //jump! + pm->ps->velocity[2] = 300; + pm->ps->fd.forceJumpZStart = pm->ps->origin[2]; //so we don't take damage if we land at same height + //pm->ps->pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; + + //FIXME: NPCs yell? + PM_AddEvent(EV_JUMP); + //G_SoundOnEnt( ent, CHAN_BODY, "sound/weapons/force/jump.wav" ); + ucmd->upmove = 0; //clear any actual jump command + } + } + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + } + //STAFF/DUAL SPIN ATTACK + else if (pm->ps->saberMove == LS_SPINATTACK || + pm->ps->saberMove == LS_SPINATTACK_DUAL) + { + ucmd->forwardmove = ucmd->rightmove = ucmd->upmove = 0; + //lock their viewangles during these attacks. + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, ucmd); + } +} + +//constrain him based on the angles of his vehicle and the caps +void PM_VehicleViewAngles(playerState_t *ps, bgEntity_t *veh, usercmd_t *ucmd) +{ + Vehicle_t *pVeh = veh->m_pVehicle; + qboolean setAngles = qtrue; + vec3_t clampMin; + vec3_t clampMax; + int i; + + if ( veh->m_pVehicle->m_pPilot + && veh->m_pVehicle->m_pPilot->s.number == ps->clientNum ) + {//set the pilot's viewangles to the vehicle's viewangles +#ifdef VEH_CONTROL_SCHEME_4 + if ( 1 ) +#else //VEH_CONTROL_SCHEME_4 + if ( !BG_UnrestrainedPitchRoll( ps, veh->m_pVehicle ) ) +#endif //VEH_CONTROL_SCHEME_4 + {//only if not if doing special free-roll/pitch control + setAngles = qtrue; + clampMin[PITCH] = -pVeh->m_pVehicleInfo->lookPitch; + clampMax[PITCH] = pVeh->m_pVehicleInfo->lookPitch; + clampMin[YAW] = clampMax[YAW] = 0; + clampMin[ROLL] = clampMax[ROLL] = -1; + } + } + else + { + //NOTE: passengers can look around freely, UNLESS they're controlling a turret! + for ( i = 0; i < MAX_VEHICLE_TURRETS; i++ ) + { + if ( veh->m_pVehicle->m_pVehicleInfo->turret[i].passengerNum == ps->generic1 ) + {//this turret is my station + setAngles = qtrue; + clampMin[PITCH] = veh->m_pVehicle->m_pVehicleInfo->turret[i].pitchClampUp; + clampMax[PITCH] = veh->m_pVehicle->m_pVehicleInfo->turret[i].pitchClampDown; + clampMin[YAW] = veh->m_pVehicle->m_pVehicleInfo->turret[i].yawClampRight; + clampMax[YAW] = veh->m_pVehicle->m_pVehicleInfo->turret[i].yawClampLeft; + clampMin[ROLL] = clampMax[ROLL] = 0; + break; + } + } + } + if ( setAngles ) + { + for ( i = 0; i < 3; i++ ) + {//clamp viewangles + if ( clampMin[i] == -1 || clampMax[i] == -1 ) + {//no clamp + } + else if ( !clampMin[i] && !clampMax[i] ) + {//no allowance + //ps->viewangles[i] = veh->playerState->viewangles[i]; + } + else + {//allowance + if (ps->viewangles[i] > clampMax[i]) + { + ps->viewangles[i] = clampMax[i]; + } + else if (ps->viewangles[i] < clampMin[i]) + { + ps->viewangles[i] = clampMin[i]; + } + } + } + + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); + } +} + +/* +//constrain him based on the angles of his vehicle and the caps +void PM_VehicleViewAngles(playerState_t *ps, bgEntity_t *veh, usercmd_t *ucmd) +{ + Vehicle_t *pVeh = veh->m_pVehicle; + + //now set the viewangles to the vehicle's directly + ps->viewangles[YAW] = veh->playerState->viewangles[YAW]; + + //constrain the viewangles pitch based on the vehicle properties + if ( !pVeh->m_pVehicleInfo->lookPitch ) + {//not allowed to look up & down! ....??? + ps->viewangles[PITCH] = veh->playerState->viewangles[PITCH]; + } + else + {//clamp + if (ps->viewangles[PITCH] > pVeh->m_pVehicleInfo->lookPitch) + { + ps->viewangles[PITCH] = pVeh->m_pVehicleInfo->lookPitch; + } + else if (ps->viewangles[PITCH] < -pVeh->m_pVehicleInfo->lookPitch) + { + ps->viewangles[PITCH] = -pVeh->m_pVehicleInfo->lookPitch; + } + } + + PM_SetPMViewAngle(ps, ps->viewangles, ucmd); +} +*/ +//see if a weapon is ok to use on a vehicle +qboolean PM_WeaponOkOnVehicle( int weapon ) +{ + //FIXME: check g_vehicleInfo for our vehicle? + switch ( weapon ) + { + //case WP_NONE: + case WP_MELEE: + case WP_SABER: + case WP_BLASTER: + //case WP_THERMAL: + return qtrue; + break; + } + return qfalse; +} + +//do we have a weapon that's ok for using on the vehicle? +int PM_GetOkWeaponForVehicle(void) +{ + int i = 0; + + while (i < WP_NUM_WEAPONS) + { + if ((pm->ps->stats[STAT_WEAPONS] & (1 << i)) && + PM_WeaponOkOnVehicle(i)) + { //this one's good + return i; + } + + i++; + } + + //oh dear! + //assert(!"No valid veh weaps"); + return -1; +} + +//force the vehicle to turn and travel to its forced destination point +void PM_VehForcedTurning(bgEntity_t *veh) +{ + bgEntity_t *dst = PM_BGEntForNum(veh->playerState->vehTurnaroundIndex); + float pitchD, yawD; + vec3_t dir; + + if (!veh || !veh->m_pVehicle) + { + return; + } + + if (!dst) + { //can't find dest ent? + return; + } + + pm->cmd.upmove = veh->m_pVehicle->m_ucmd.upmove = 127; + pm->cmd.forwardmove = veh->m_pVehicle->m_ucmd.forwardmove = 0; + pm->cmd.rightmove = veh->m_pVehicle->m_ucmd.rightmove = 0; + + VectorSubtract(dst->s.origin, veh->playerState->origin, dir); + vectoangles(dir, dir); + + yawD = AngleSubtract(pm->ps->viewangles[YAW], dir[YAW]); + pitchD = AngleSubtract(pm->ps->viewangles[PITCH], dir[PITCH]); + + yawD *= 0.6f*pml.frametime; + pitchD *= 0.6f*pml.frametime; + +#ifdef VEH_CONTROL_SCHEME_4 + veh->playerState->viewangles[YAW] = AngleSubtract(veh->playerState->viewangles[YAW], yawD); + veh->playerState->viewangles[PITCH] = AngleSubtract(veh->playerState->viewangles[PITCH], pitchD); + pm->ps->viewangles[YAW] = veh->playerState->viewangles[YAW]; + pm->ps->viewangles[PITCH] = 0; + + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + PM_SetPMViewAngle(veh->playerState, veh->playerState->viewangles, &pm->cmd); + VectorClear( veh->m_pVehicle->m_vPrevRiderViewAngles ); + veh->m_pVehicle->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(pm->ps->viewangles[YAW]); + +#else //VEH_CONTROL_SCHEME_4 + + pm->ps->viewangles[YAW] = AngleSubtract(pm->ps->viewangles[YAW], yawD); + pm->ps->viewangles[PITCH] = AngleSubtract(pm->ps->viewangles[PITCH], pitchD); + + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); +#endif //VEH_CONTROL_SCHEME_4 +} + +#ifdef VEH_CONTROL_SCHEME_4 +void PM_VehFaceHyperspacePoint(bgEntity_t *veh) +{ + + if (!veh || !veh->m_pVehicle) + { + return; + } + else + { + float timeFrac = ((float)(pm->cmd.serverTime-veh->playerState->hyperSpaceTime))/HYPERSPACE_TIME; + float turnRate, aDelta; + int i, matchedAxes = 0; + + pm->cmd.upmove = veh->m_pVehicle->m_ucmd.upmove = 127; + pm->cmd.forwardmove = veh->m_pVehicle->m_ucmd.forwardmove = 0; + pm->cmd.rightmove = veh->m_pVehicle->m_ucmd.rightmove = 0; + + turnRate = (90.0f*pml.frametime); + for ( i = 0; i < 3; i++ ) + { + aDelta = AngleSubtract(veh->playerState->hyperSpaceAngles[i], veh->m_pVehicle->m_vOrientation[i]); + if ( fabs( aDelta ) < turnRate ) + {//all is good + veh->playerState->viewangles[i] = veh->playerState->hyperSpaceAngles[i]; + matchedAxes++; + } + else + { + aDelta = AngleSubtract(veh->playerState->hyperSpaceAngles[i], veh->playerState->viewangles[i]); + if ( fabs( aDelta ) < turnRate ) + { + veh->playerState->viewangles[i] = veh->playerState->hyperSpaceAngles[i]; + } + else if ( aDelta > 0 ) + { + if ( i == YAW ) + { + veh->playerState->viewangles[i] = AngleNormalize360( veh->playerState->viewangles[i]+turnRate ); + } + else + { + veh->playerState->viewangles[i] = AngleNormalize180( veh->playerState->viewangles[i]+turnRate ); + } + } + else + { + if ( i == YAW ) + { + veh->playerState->viewangles[i] = AngleNormalize360( veh->playerState->viewangles[i]-turnRate ); + } + else + { + veh->playerState->viewangles[i] = AngleNormalize180( veh->playerState->viewangles[i]-turnRate ); + } + } + } + } + + pm->ps->viewangles[YAW] = veh->playerState->viewangles[YAW]; + pm->ps->viewangles[PITCH] = 0.0f; + + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + PM_SetPMViewAngle(veh->playerState, veh->playerState->viewangles, &pm->cmd); + VectorClear( veh->m_pVehicle->m_vPrevRiderViewAngles ); + veh->m_pVehicle->m_vPrevRiderViewAngles[YAW] = AngleNormalize180(pm->ps->viewangles[YAW]); + + if ( timeFrac < HYPERSPACE_TELEPORT_FRAC ) + {//haven't gone through yet + if ( matchedAxes < 3 ) + {//not facing the right dir yet + //keep hyperspace time up to date + veh->playerState->hyperSpaceTime += pml.msec; + } + else if ( !(veh->playerState->eFlags2&EF2_HYPERSPACE)) + {//flag us as ready to hyperspace! + veh->playerState->eFlags2 |= EF2_HYPERSPACE; + } + } + } +} + +#else //VEH_CONTROL_SCHEME_4 + +void PM_VehFaceHyperspacePoint(bgEntity_t *veh) +{ + + if (!veh || !veh->m_pVehicle) + { + return; + } + else + { + float timeFrac = ((float)(pm->cmd.serverTime-veh->playerState->hyperSpaceTime))/HYPERSPACE_TIME; + float turnRate, aDelta; + int i, matchedAxes = 0; + + pm->cmd.upmove = veh->m_pVehicle->m_ucmd.upmove = 127; + pm->cmd.forwardmove = veh->m_pVehicle->m_ucmd.forwardmove = 0; + pm->cmd.rightmove = veh->m_pVehicle->m_ucmd.rightmove = 0; + + turnRate = (90.0f*pml.frametime); + for ( i = 0; i < 3; i++ ) + { + aDelta = AngleSubtract(veh->playerState->hyperSpaceAngles[i], veh->m_pVehicle->m_vOrientation[i]); + if ( fabs( aDelta ) < turnRate ) + {//all is good + pm->ps->viewangles[i] = veh->playerState->hyperSpaceAngles[i]; + matchedAxes++; + } + else + { + aDelta = AngleSubtract(veh->playerState->hyperSpaceAngles[i], pm->ps->viewangles[i]); + if ( fabs( aDelta ) < turnRate ) + { + pm->ps->viewangles[i] = veh->playerState->hyperSpaceAngles[i]; + } + else if ( aDelta > 0 ) + { + if ( i == YAW ) + { + pm->ps->viewangles[i] = AngleNormalize360( pm->ps->viewangles[i]+turnRate ); + } + else + { + pm->ps->viewangles[i] = AngleNormalize180( pm->ps->viewangles[i]+turnRate ); + } + } + else + { + if ( i == YAW ) + { + pm->ps->viewangles[i] = AngleNormalize360( pm->ps->viewangles[i]-turnRate ); + } + else + { + pm->ps->viewangles[i] = AngleNormalize180( pm->ps->viewangles[i]-turnRate ); + } + } + } + } + + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + + if ( timeFrac < HYPERSPACE_TELEPORT_FRAC ) + {//haven't gone through yet + if ( matchedAxes < 3 ) + {//not facing the right dir yet + //keep hyperspace time up to date + veh->playerState->hyperSpaceTime += pml.msec; + } + else if ( !(veh->playerState->eFlags2&EF2_HYPERSPACE)) + {//flag us as ready to hyperspace! + veh->playerState->eFlags2 |= EF2_HYPERSPACE; + } + } + } +} + +#endif //VEH_CONTROL_SCHEME_4 + +void BG_VehicleAdjustBBoxForOrientation( Vehicle_t *veh, vec3_t origin, vec3_t mins, vec3_t maxs, + int clientNum, int tracemask, + void (*localTrace)(trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentMask)) +{ + if ( !veh + || !veh->m_pVehicleInfo->length + || !veh->m_pVehicleInfo->width + || !veh->m_pVehicleInfo->height ) + //|| veh->m_LandTrace.fraction < 1.0f ) + { + return; + } + else if ( veh->m_pVehicleInfo->type != VH_FIGHTER + //&& veh->m_pVehicleInfo->type != VH_SPEEDER + && veh->m_pVehicleInfo->type != VH_FLIER ) + {//only those types of vehicles have dynamic bboxes, the rest just use a static bbox + VectorSet( maxs, veh->m_pVehicleInfo->width/2.0f, veh->m_pVehicleInfo->width/2.0f, veh->m_pVehicleInfo->height+DEFAULT_MINS_2 ); + VectorSet( mins, veh->m_pVehicleInfo->width/-2.0f, veh->m_pVehicleInfo->width/-2.0f, DEFAULT_MINS_2 ); + return; + } + else + { + vec3_t axis[3], point[8]; + vec3_t newMins, newMaxs; + int curAxis = 0, i; + trace_t trace; + + AnglesToAxis( veh->m_vOrientation, axis ); + VectorMA( origin, veh->m_pVehicleInfo->length/2.0f, axis[0], point[0] ); + VectorMA( origin, -veh->m_pVehicleInfo->length/2.0f, axis[0], point[1] ); + //extrapolate each side up and down + VectorMA( point[0], veh->m_pVehicleInfo->height/2.0f, axis[2], point[0] ); + VectorMA( point[0], -veh->m_pVehicleInfo->height, axis[2], point[2] ); + VectorMA( point[1], veh->m_pVehicleInfo->height/2.0f, axis[2], point[1] ); + VectorMA( point[1], -veh->m_pVehicleInfo->height, axis[2], point[3] ); + + VectorMA( origin, veh->m_pVehicleInfo->width/2.0f, axis[1], point[4] ); + VectorMA( origin, -veh->m_pVehicleInfo->width/2.0f, axis[1], point[5] ); + //extrapolate each side up and down + VectorMA( point[4], veh->m_pVehicleInfo->height/2.0f, axis[2], point[4] ); + VectorMA( point[4], -veh->m_pVehicleInfo->height, axis[2], point[6] ); + VectorMA( point[5], veh->m_pVehicleInfo->height/2.0f, axis[2], point[5] ); + VectorMA( point[5], -veh->m_pVehicleInfo->height, axis[2], point[7] ); + /* + VectorMA( origin, veh->m_pVehicleInfo->height/2.0f, axis[2], point[4] ); + VectorMA( origin, -veh->m_pVehicleInfo->height/2.0f, axis[2], point[5] ); + */ + //Now inflate a bbox around these points + VectorCopy( origin, newMins ); + VectorCopy( origin, newMaxs ); + for ( curAxis = 0; curAxis < 3; curAxis++ ) + { + for ( i = 0; i < 8; i++ ) + { + if ( point[i][curAxis] > newMaxs[curAxis] ) + { + newMaxs[curAxis] = point[i][curAxis]; + } + else if ( point[i][curAxis] < newMins[curAxis] ) + { + newMins[curAxis] = point[i][curAxis]; + } + } + } + VectorSubtract( newMins, origin, newMins ); + VectorSubtract( newMaxs, origin, newMaxs ); + //now see if that's a valid way to be + if (localTrace) + { + localTrace( &trace, origin, newMins, newMaxs, origin, clientNum, tracemask ); + } + else + { //don't care about solid stuff then + trace.startsolid = trace.allsolid = 0; + } + if ( !trace.startsolid && !trace.allsolid ) + {//let's use it! + VectorCopy( newMins, mins ); + VectorCopy( newMaxs, maxs ); + } + //else: just use the last one, I guess...? + //FIXME: make it as close as possible? Or actually prevent the change in m_vOrientation? Or push away from anything we hit? + } +} +/* +================ +PmoveSingle + +================ +*/ +extern void trap_SnapVector( float *v ); +extern int BG_EmplacedView(vec3_t baseAngles, vec3_t angles, float *newYaw, float constraint); +extern qboolean BG_FighterUpdate(Vehicle_t *pVeh, const usercmd_t *pUcmd, vec3_t trMins, vec3_t trMaxs, float gravity, + void (*traceFunc)( trace_t *results, const vec3_t start, const vec3_t lmins, const vec3_t lmaxs, const vec3_t end, int passEntityNum, int contentMask )); //FighterNPC.c + +#define JETPACK_HOVER_HEIGHT 64 + +//#define _TESTING_VEH_PREDICTION + +void PM_MoveForKata(usercmd_t *ucmd) +{ + if ( pm->ps->legsAnim == BOTH_A7_SOULCAL + && pm->ps->saberMove == LS_STAFF_SOULCAL ) + {//forward spinning staff attack + ucmd->upmove = 0; + + if ( PM_CanRollFromSoulCal( pm->ps ) ) + { + ucmd->upmove = -127; + ucmd->rightmove = 0; + if (ucmd->forwardmove < 0) + { + ucmd->forwardmove = 0; + } + } + else + { + ucmd->rightmove = 0; + //FIXME: don't slide off people/obstacles? + if ( pm->ps->legsTimer >= 2750 ) + {//not at end + //push forward + ucmd->forwardmove = 64; + } + else + { + ucmd->forwardmove = 0; + } + } + if ( pm->ps->legsTimer >= 2650 + && pm->ps->legsTimer < 2850 ) + {//the jump + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//still on ground? + //jump! + pm->ps->velocity[2] = 250; + pm->ps->fd.forceJumpZStart = pm->ps->origin[2];//so we don't take damage if we land at same height + // pm->ps->pm_flags |= PMF_JUMPING;//|PMF_SLOW_MO_FALL; + //FIXME: NPCs yell? + PM_AddEvent(EV_JUMP); + } + } + } + else if (pm->ps->legsAnim == BOTH_A2_SPECIAL) + { //medium kata + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + if (pm->ps->legsTimer < 2700 && pm->ps->legsTimer > 2300) + { + pm->cmd.forwardmove = 127; + } + else if (pm->ps->legsTimer < 900 && pm->ps->legsTimer > 500) + { + pm->cmd.forwardmove = 127; + } + else + { + pm->cmd.forwardmove = 0; + } + } + else if (pm->ps->legsAnim == BOTH_A3_SPECIAL) + { //strong kata + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + if (pm->ps->legsTimer < 1700 && pm->ps->legsTimer > 1000) + { + pm->cmd.forwardmove = 127; + } + else + { + pm->cmd.forwardmove = 0; + } + } + else + { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } +} + +void PmoveSingle (pmove_t *pmove) { + qboolean stiffenedUp = qfalse; + float gDist = 0; + qboolean noAnimate = qfalse; + int savedGravity = 0; + + pm = pmove; + + if (pm->ps->emplacedIndex) + { + if (pm->cmd.buttons & BUTTON_ALT_ATTACK) + { //hackerrific. + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + pm->cmd.buttons |= BUTTON_ATTACK; + } + } + + //set up these "global" bg ents + pm_entSelf = PM_BGEntForNum(pm->ps->clientNum); + if (pm->ps->m_iVehicleNum) + { + if (pm->ps->clientNum < MAX_CLIENTS) + { //player riding vehicle + pm_entVeh = PM_BGEntForNum(pm->ps->m_iVehicleNum); + } + else + { //vehicle with player pilot + pm_entVeh = PM_BGEntForNum(pm->ps->m_iVehicleNum-1); + } + } + else + { //no vehicle ent + pm_entVeh = NULL; + } + + gPMDoSlowFall = PM_DoSlowFall(); + + // 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; + + if (PM_IsRocketTrooper()) + { //kind of nasty, don't let them crouch or anything if nonhumanoid (probably a rockettrooper) + if (pm->cmd.upmove < 0) + { + pm->cmd.upmove = 0; + } + } + + if (pm->ps->pm_type == PM_FLOAT) + { //You get no control over where you go in grip movement + stiffenedUp = qtrue; + } + else if (pm->ps->eFlags & EF_DISINTEGRATION) + { + stiffenedUp = qtrue; + } + else if ( BG_SaberLockBreakAnim( pm->ps->legsAnim ) + || BG_SaberLockBreakAnim( pm->ps->torsoAnim ) + || pm->ps->saberLockTime >= pm->cmd.serverTime ) + {//can't move or turn + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + else if ( pm->ps->saberMove == LS_A_BACK || pm->ps->saberMove == LS_A_BACK_CR || + pm->ps->saberMove == LS_A_BACKSTAB || pm->ps->saberMove == LS_A_FLIP_STAB || + pm->ps->saberMove == LS_A_FLIP_SLASH || pm->ps->saberMove == LS_A_JUMP_T__B_ || + pm->ps->saberMove == LS_DUAL_LR || pm->ps->saberMove == LS_DUAL_FB) + { + if (pm->ps->legsAnim == BOTH_JUMPFLIPSTABDOWN || + pm->ps->legsAnim == BOTH_JUMPFLIPSLASHDOWN1) + { //flipover medium stance attack + if (pm->ps->legsTimer < 1600 && pm->ps->legsTimer > 900) + { + pm->ps->viewangles[YAW] += pml.frametime*240.0f; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + } + stiffenedUp = qtrue; + } + else if ((pm->ps->legsAnim) == (BOTH_A2_STABBACK1) || + (pm->ps->legsAnim) == (BOTH_ATTACK_BACK) || + (pm->ps->legsAnim) == (BOTH_CROUCHATTACKBACK1) || + (pm->ps->legsAnim) == (BOTH_FORCELEAP2_T__B_) || + (pm->ps->legsAnim) == (BOTH_JUMPFLIPSTABDOWN) || + (pm->ps->legsAnim) == (BOTH_JUMPFLIPSLASHDOWN1)) + { + stiffenedUp = qtrue; + } + else if (pm->ps->legsAnim == BOTH_ROLL_STAB) + { + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + else if (pm->ps->heldByClient) + { + stiffenedUp = qtrue; + } + else if (BG_KickMove(pm->ps->saberMove) || BG_KickingAnim(pm->ps->legsAnim)) + { + stiffenedUp = qtrue; + } + else if (BG_InGrappleMove(pm->ps->torsoAnim)) + { + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + else if ( pm->ps->saberMove == LS_STABDOWN_DUAL || + pm->ps->saberMove == LS_STABDOWN_STAFF || + pm->ps->saberMove == LS_STABDOWN) + {//FIXME: need to only move forward until we bump into our target...? + if (pm->ps->legsTimer < 800) + { //freeze movement near end of anim + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + else + { //force forward til then + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 64; + } + } + else if (pm->ps->saberMove == LS_PULL_ATTACK_STAB || + pm->ps->saberMove == LS_PULL_ATTACK_SWING) + { + stiffenedUp = qtrue; + } + else if (BG_SaberInKata(pm->ps->saberMove) || + BG_InKataAnim(pm->ps->torsoAnim) || + BG_InKataAnim(pm->ps->legsAnim)) + { + PM_MoveForKata(&pm->cmd); + } + else if ( BG_FullBodyTauntAnim( pm->ps->legsAnim ) + && BG_FullBodyTauntAnim( pm->ps->torsoAnim ) ) + { + if ( (pm->cmd.buttons&BUTTON_ATTACK) + || (pm->cmd.buttons&BUTTON_ALT_ATTACK) + || (pm->cmd.buttons&BUTTON_FORCEPOWER) + || (pm->cmd.buttons&BUTTON_FORCEGRIP) + || (pm->cmd.buttons&BUTTON_FORCE_LIGHTNING) + || (pm->cmd.buttons&BUTTON_FORCE_DRAIN) + || pm->cmd.upmove ) + {//stop the anim + if ( pm->ps->legsAnim == BOTH_MEDITATE + && pm->ps->torsoAnim == BOTH_MEDITATE ) + { + PM_SetAnim( SETANIM_BOTH, BOTH_MEDITATE_END, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + } + else + { + pm->ps->legsTimer = pm->ps->torsoTimer = 0; + } + if ( pm->ps->forceHandExtend == HANDEXTEND_TAUNT ) + { + pm->ps->forceHandExtend = 0; + } + } + else + { + if ( pm->ps->legsAnim == BOTH_MEDITATE ) + { + if ( pm->ps->legsTimer < 100 ) + { + pm->ps->legsTimer = 100; + } + } + if ( pm->ps->torsoAnim == BOTH_MEDITATE ) + { + if ( pm->ps->torsoTimer < 100 ) + { + pm->ps->legsTimer = 100; + } + pm->ps->forceHandExtend = HANDEXTEND_TAUNT; + pm->ps->forceHandExtendTime = pm->cmd.serverTime + 100; + } + if ( pm->ps->legsTimer > 0 || pm->ps->torsoTimer > 0 ) + { + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 0; + pm->cmd.buttons = 0; + } + } + } + else if ( pm->ps->legsAnim == BOTH_MEDITATE_END + && pm->ps->legsTimer > 0 ) + { + stiffenedUp = qtrue; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 0; + pm->cmd.buttons = 0; + } + else if (pm->ps->legsAnim == BOTH_FORCELAND1 || + pm->ps->legsAnim == BOTH_FORCELANDBACK1 || + pm->ps->legsAnim == BOTH_FORCELANDRIGHT1 || + pm->ps->legsAnim == BOTH_FORCELANDLEFT1) + { //can't move while in a force land + stiffenedUp = qtrue; + } + + if ( pm->ps->saberMove == LS_A_LUNGE ) + {//can't move during lunge + pm->cmd.rightmove = pm->cmd.upmove = 0; + if ( pm->ps->legsTimer > 500 ) + { + pm->cmd.forwardmove = 127; + } + else + { + pm->cmd.forwardmove = 0; + } + } + + if ( pm->ps->saberMove == LS_A_JUMP_T__B_ ) + {//can't move during leap + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//hit the ground + pm->cmd.forwardmove = 0; + } + pm->cmd.rightmove = pm->cmd.upmove = 0; + } + +#if 0 + if ((pm->ps->legsAnim) == BOTH_KISSER1LOOP || + (pm->ps->legsAnim) == BOTH_KISSEE1LOOP) + { + stiffenedUp = qtrue; + } +#endif + + if (pm->ps->emplacedIndex) + { + if (pm->cmd.forwardmove < 0 || PM_GroundDistance() > 32.0f) + { + pm->ps->emplacedIndex = 0; + pm->ps->saberHolstered = 0; + } + else + { + stiffenedUp = qtrue; + } + } + + /* + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->weaponstate == WEAPON_CHARGING_ALT) + { //not allowed to move while charging the disruptor + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + if (pm->cmd.upmove > 0) + { + pm->cmd.upmove = 0; + } + } + */ + + if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->weaponstate == WEAPON_CHARGING_ALT) + { //not allowed to move while charging the disruptor + if (pm->cmd.forwardmove || + pm->cmd.rightmove || + pm->cmd.upmove > 0) + { //get out + pm->ps->weaponstate = WEAPON_READY; + pm->ps->weaponTime = 1000; + PM_AddEventWithParm(EV_WEAPON_CHARGE, WP_DISRUPTOR); //cut the weapon charge sound + pm->cmd.upmove = 0; + } + } + else if (pm->ps->weapon == WP_DISRUPTOR && pm->ps->zoomMode == 1) + { //can't jump + if (pm->cmd.upmove > 0) + { + pm->cmd.upmove = 0; + } + } + + if (stiffenedUp) + { + pm->cmd.forwardmove = 0; + pm->cmd.rightmove = 0; + pm->cmd.upmove = 0; + } + + if (pm->ps->fd.forceGripCripple) + { //don't let attack or alt attack if being gripped I guess + pm->cmd.buttons &= ~BUTTON_ATTACK; + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + } + + if ( BG_InRoll( pm->ps, pm->ps->legsAnim ) ) + { //can't roll unless you're able to move normally + BG_CmdForRoll( pm->ps, pm->ps->legsAnim, &pm->cmd ); + } + + PM_CmdForSaberMoves(&pm->cmd); + + BG_AdjustClientSpeed(pm->ps, &pm->cmd, pm->cmd.serverTime); + + 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; + } + + pm_cancelOutZoom = qfalse; + if (pm->ps->weapon == WP_DISRUPTOR && + pm->ps->zoomMode == 1) + { + if ((pm->cmd.buttons & BUTTON_ALT_ATTACK) && + !(pm->cmd.buttons & BUTTON_ATTACK) && + pm->ps->zoomLocked) + { + pm_cancelOutZoom = qtrue; + } + } + // In certain situations, we may want to control which attack buttons are pressed and what kind of functionality + // is attached to them + PM_AdjustAttackStates( pm ); + + // 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; + } + + // 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; + } + + /* + if (pm->ps->clientNum >= MAX_CLIENTS) + { +#ifdef QAGAME + Com_Printf( "^1 SERVER N%i msec %d\n", pm->ps->clientNum, pml.msec ); +#else + Com_Printf( "^2 CLIENT N%i msec %d\n", pm->ps->clientNum, pml.msec ); +#endif + } + */ + + 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; + + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE) + { //we are a vehicle + bgEntity_t *veh = pm_entSelf; + assert( veh && veh->m_pVehicle); + if ( veh && veh->m_pVehicle ) + { + veh->m_pVehicle->m_fTimeModifier = pml.frametime*60.0f; + } + } + else if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + { + bgEntity_t *veh = pm_entVeh; + + if (veh && veh->playerState && + (pm->cmd.serverTime-veh->playerState->hyperSpaceTime) < HYPERSPACE_TIME) + { //going into hyperspace, turn to face the right angles + PM_VehFaceHyperspacePoint(veh); + } + else if (veh && veh->playerState && + veh->playerState->vehTurnaroundIndex && + veh->playerState->vehTurnaroundTime > pm->cmd.serverTime) + { //riding this vehicle, turn my view too + PM_VehForcedTurning(veh); + } + } + + if ( pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_ALT && + pm->ps->legsTimer > 0 ) + { + vec3_t vFwd, fwdAng; + VectorSet(fwdAng, 0.0f, pm->ps->viewangles[YAW], 0.0f); + + AngleVectors( fwdAng, vFwd, NULL, NULL ); + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + float savZ = pm->ps->velocity[2]; + VectorScale( vFwd, 100, pm->ps->velocity ); + pm->ps->velocity[2] = savZ; + } + pm->cmd.forwardmove = pm->cmd.rightmove = pm->cmd.upmove = 0; + PM_AdjustAnglesForWallRunUpFlipAlt( &pm->cmd ); + } + +// PM_AdjustAngleForWallRun(pm->ps, &pm->cmd, qtrue); +// PM_AdjustAnglesForStabDown( pm->ps, &pm->cmd ); + PM_AdjustAngleForWallJump( pm->ps, &pm->cmd, qtrue ); + PM_AdjustAngleForWallRunUp( pm->ps, &pm->cmd, qtrue ); + PM_AdjustAngleForWallRun( pm->ps, &pm->cmd, qtrue ); + + if (pm->ps->saberMove == LS_A_JUMP_T__B_ || pm->ps->saberMove == LS_A_LUNGE || + pm->ps->saberMove == LS_A_BACK_CR || pm->ps->saberMove == LS_A_BACK || + pm->ps->saberMove == LS_A_BACKSTAB) + { + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } + +#if 0 + if ((pm->ps->legsAnim) == BOTH_KISSER1LOOP || + (pm->ps->legsAnim) == BOTH_KISSEE1LOOP) + { + pm->ps->viewangles[PITCH] = 0; + PM_SetPMViewAngle(pm->ps, pm->ps->viewangles, &pm->cmd); + } +#endif + + PM_SetSpecialMoveValues(); + + // update the viewangles + PM_UpdateViewAngles( pm->ps, &pm->cmd ); + + AngleVectors (pm->ps->viewangles, pml.forward, pml.right, pml.up); + + if ( pm->cmd.upmove < 10 && !(pm->ps->pm_flags & PMF_STUCK_TO_WALL)) { + // 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->fd.saberAnimLevel == SS_STAFF && + (pm->cmd.buttons & BUTTON_ALT_ATTACK) && + pm->cmd.upmove > 0) + { //this is how you do kick-for-condition + pm->cmd.upmove = 0; + pm->ps->pm_flags |= PMF_JUMP_HELD; + } + */ + + if (pm->ps->saberLockTime >= pm->cmd.serverTime) + { + pm->cmd.upmove = 0; + pm->cmd.forwardmove = 0;//50; + pm->cmd.rightmove = 0;//*= 0.1; + } + + if ( pm->ps->pm_type == PM_SPECTATOR ) { + PM_CheckDuck (); + if (!pm->noSpecMove) + { + PM_FlyMove (); + } + PM_DropTimers (); + return; + } + + if ( pm->ps->pm_type == PM_NOCLIP ) { + if (pm->ps->clientNum < MAX_CLIENTS) + { + 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 (); + + if (pm->ps->pm_type == PM_JETPACK) + { + gDist = PM_GroundDistance(); + savedGravity = pm->ps->gravity; + + if (gDist < JETPACK_HOVER_HEIGHT+64) + { + pm->ps->gravity *= 0.1f; + } + else + { + pm->ps->gravity *= 0.25f; + } + } + else if (gPMDoSlowFall) + { + savedGravity = pm->ps->gravity; + pm->ps->gravity *= 0.5; + } + + //if we're in jetpack mode then see if we should be jetting around + if (pm->ps->pm_type == PM_JETPACK) + { + if (pm->cmd.rightmove > 0) + { + PM_ContinueLegsAnim(BOTH_INAIRRIGHT1); + } + else if (pm->cmd.rightmove < 0) + { + PM_ContinueLegsAnim(BOTH_INAIRLEFT1); + } + else if (pm->cmd.forwardmove > 0) + { + PM_ContinueLegsAnim(BOTH_INAIR1); + } + else if (pm->cmd.forwardmove < 0) + { + PM_ContinueLegsAnim(BOTH_INAIRBACK1); + } + else + { + PM_ContinueLegsAnim(BOTH_INAIR1); + } + + if (pm->ps->weapon == WP_SABER && + BG_SpinningSaberAnim( pm->ps->legsAnim )) + { //make him stir around since he shouldn't have any real control when spinning + pm->ps->velocity[0] += Q_irand(-100, 100); + pm->ps->velocity[1] += Q_irand(-100, 100); + } + + if (pm->cmd.upmove > 0 && pm->ps->velocity[2] < 256) + { //cap upward velocity off at 256. Seems reasonable. + float addIn = 12.0f; + +/* + //Add based on our distance to the ground if we're already travelling upward + if (pm->ps->velocity[2] > 0) + { + while (gDist > 64) + { //subtract 1 for every 64 units off the ground we get + addIn--; + + gDist -= 64; + + if (addIn <= 0) + { //break out if we're not even going to add anything + break; + } + } + } +*/ + if (pm->ps->velocity[2] > 0) + { + addIn = 12.0f - (gDist / 64.0f); + } + + if (addIn > 0.0f) + { + pm->ps->velocity[2] += addIn; + } + + pm->ps->eFlags |= EF_JETPACK_FLAMING; //going up + } + else + { + pm->ps->eFlags &= ~EF_JETPACK_FLAMING; //idling + + if (pm->ps->velocity[2] < 256) + { + if (pm->ps->velocity[2] < -100) + { + pm->ps->velocity[2] = -100; + } + if (gDist < JETPACK_HOVER_HEIGHT) + { //make sure we're always hovering off the ground somewhat while jetpack is active + pm->ps->velocity[2] += 2; + } + } + } + } + + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && pm_entSelf->m_pVehicle) + { //Now update our mins/maxs to match our m_vOrientation based on our length, width & height + BG_VehicleAdjustBBoxForOrientation( pm_entSelf->m_pVehicle, pm->ps->origin, pm->mins, pm->maxs, pm->ps->clientNum, pm->tracemask, pm->trace ); + } + + // set groundentity + PM_GroundTrace(); + if ( pm_flying == FLY_HOVER ) + {//never stick to the ground + PM_HoverTrace(); + } + + if ( pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//on ground + pm->ps->fd.forceJumpZStart = 0; + } + + if ( pm->ps->pm_type == PM_DEAD ) { + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE && + pm_entSelf->m_pVehicle->m_pVehicleInfo->type != VH_ANIMAL) + {//vehicles don't use deadmove + } + else + { + PM_DeadMove (); + } + } + + PM_DropTimers(); + +#ifdef _TESTING_VEH_PREDICTION +#ifndef QAGAME + { + vec3_t blah; + VectorMA(pm->ps->origin, 128.0f, pm->ps->moveDir, blah); + CG_TestLine(pm->ps->origin, blah, 1, 0x0000ff, 1); + + VectorMA(pm->ps->origin, 1.0f, pm->ps->velocity, blah); + CG_TestLine(pm->ps->origin, blah, 1, 0xff0000, 1); + } +#endif +#endif + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + { //a player riding a vehicle + bgEntity_t *veh = pm_entVeh; + + if ( veh && veh->m_pVehicle && + (veh->m_pVehicle->m_pVehicleInfo->type == VH_WALKER || veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) ) + {//*sigh*, until we get forced weapon-switching working? + pm->cmd.buttons &= ~(BUTTON_ATTACK|BUTTON_ALT_ATTACK); + pm->ps->eFlags &= ~(EF_FIRING|EF_ALT_FIRING); + //pm->cmd.weapon = pm->ps->weapon; + } + } + + if (!pm->ps->m_iVehicleNum && + pm_entSelf->s.NPC_class!=CLASS_VEHICLE&& + pm_entSelf->s.NPC_class!=CLASS_RANCOR&& + pm->ps->groundEntityNum < ENTITYNUM_WORLD && + pm->ps->groundEntityNum >= MAX_CLIENTS) + { //I am a player client, not riding on a vehicle, and potentially standing on an NPC + bgEntity_t *pEnt = PM_BGEntForNum(pm->ps->groundEntityNum); + + if (pEnt && pEnt->s.eType == ET_NPC && + pEnt->s.NPC_class != CLASS_VEHICLE) //don't bounce on vehicles + { //this is actually an NPC, let's try to bounce of its head to make sure we can't just stand around on top of it. + if (pm->ps->velocity[2] < 270) + { //try forcing velocity up and also force him to jump + pm->ps->velocity[2] = 270; //seems reasonable + pm->cmd.upmove = 127; + } + } +#ifdef QAGAME + else if ( !pm->ps->zoomMode && + pm_entSelf //I exist + && pEnt->m_pVehicle )//ent has a vehicle + { + gentity_t *gEnt = (gentity_t*)pEnt; + if ( gEnt->client + && !gEnt->client->ps.m_iVehicleNum //vehicle is empty + && (gEnt->spawnflags&2) )//SUSPENDED + {//it's a vehicle, see if we should get in it + //if land on an empty, suspended vehicle, get in it + pEnt->m_pVehicle->m_pVehicleInfo->Board( pEnt->m_pVehicle, (bgEntity_t *)pm_entSelf ); + } + } +#endif + } + + if (pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE) + { //we are a vehicle + bgEntity_t *veh = pm_entSelf; + + assert(veh && veh->playerState && veh->m_pVehicle && veh->s.number >= MAX_CLIENTS); + + if (veh->m_pVehicle->m_pVehicleInfo->type != VH_FIGHTER) + { //kind of hacky, don't want to do this for flying vehicles + veh->m_pVehicle->m_vOrientation[PITCH] = pm->ps->viewangles[PITCH]; + } + + if (!pm->ps->m_iVehicleNum) + { //no one is driving, just update and get out +#ifdef QAGAME + veh->m_pVehicle->m_pVehicleInfo->Update(veh->m_pVehicle, &pm->cmd); + veh->m_pVehicle->m_pVehicleInfo->Animate(veh->m_pVehicle); +#endif + } + else + { + bgEntity_t *self = pm_entVeh; +#ifdef QAGAME + int i = 0; +#endif + + assert(self && self->playerState && self->s.number < MAX_CLIENTS); + + if (pm->ps->pm_type == PM_DEAD && + (veh->m_pVehicle->m_ulFlags & VEH_CRASHING)) + { + veh->m_pVehicle->m_ulFlags &= ~VEH_CRASHING; + } + + if (self->playerState->m_iVehicleNum) + { //only do it if they still have a vehicle (didn't get ejected this update or something) + PM_VehicleViewAngles(self->playerState, veh, &veh->m_pVehicle->m_ucmd); + } + +#ifdef QAGAME + veh->m_pVehicle->m_pVehicleInfo->Update(veh->m_pVehicle, &veh->m_pVehicle->m_ucmd); + veh->m_pVehicle->m_pVehicleInfo->Animate(veh->m_pVehicle); + + veh->m_pVehicle->m_pVehicleInfo->UpdateRider(veh->m_pVehicle, self, &veh->m_pVehicle->m_ucmd); + //update the passengers + while (i < veh->m_pVehicle->m_iNumPassengers) + { + if (veh->m_pVehicle->m_ppPassengers[i]) + { + gentity_t *thePassenger = (gentity_t *)veh->m_pVehicle->m_ppPassengers[i]; //yes, this is, in fact, ass. + if (thePassenger->inuse && thePassenger->client) + { + veh->m_pVehicle->m_pVehicleInfo->UpdateRider(veh->m_pVehicle, veh->m_pVehicle->m_ppPassengers[i], &thePassenger->client->pers.cmd); + } + } + i++; + } +#else + if (!veh->playerState->vehBoarding )//|| veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { + if (veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER) + { //client must explicitly call this for prediction + BG_FighterUpdate(veh->m_pVehicle, &veh->m_pVehicle->m_ucmd, pm->mins, pm->maxs, self->playerState->gravity, pm->trace); + } + + if (veh->m_pVehicle->m_iBoarding == 0) + { + vec3_t vRollAng; + + //make sure we are set as its pilot cgame side + veh->m_pVehicle->m_pPilot = self; + + // Keep track of the old orientation. + VectorCopy( veh->m_pVehicle->m_vOrientation, veh->m_pVehicle->m_vPrevOrientation ); + + veh->m_pVehicle->m_pVehicleInfo->ProcessOrientCommands(veh->m_pVehicle); + PM_SetPMViewAngle(veh->playerState, veh->m_pVehicle->m_vOrientation, &veh->m_pVehicle->m_ucmd); + veh->m_pVehicle->m_pVehicleInfo->ProcessMoveCommands(veh->m_pVehicle); + + vRollAng[YAW] = self->playerState->viewangles[YAW]; + vRollAng[PITCH] = self->playerState->viewangles[PITCH]; + vRollAng[ROLL] = veh->m_pVehicle->m_vOrientation[ROLL]; + PM_SetPMViewAngle(self->playerState, vRollAng, &pm->cmd); + + // Setup the move direction. + if ( veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + { + AngleVectors( veh->m_pVehicle->m_vOrientation, veh->playerState->moveDir, NULL, NULL ); + } + else + { + vec3_t vVehAngles; + + VectorSet(vVehAngles, 0, veh->m_pVehicle->m_vOrientation[YAW], 0); + AngleVectors( vVehAngles, veh->playerState->moveDir, NULL, NULL ); + } + } + } + /* + else + { + veh->playerState->speed = 0.0f; + PM_SetPMViewAngle(self->playerState, veh->playerState->viewangles, &veh->m_pVehicle->m_ucmd); + } + */ + else if (veh->playerState) + { + veh->playerState->speed = 0.0f; + if (veh->m_pVehicle) + { + PM_SetPMViewAngle(self->playerState, veh->m_pVehicle->m_vOrientation, &pm->cmd); + PM_SetPMViewAngle(veh->playerState, veh->m_pVehicle->m_vOrientation, &pm->cmd); + } + } +#endif + } + noAnimate = qtrue; + } + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + &&pm->ps->m_iVehicleNum) + {//don't even run physics on a player if he's on a vehicle - he goes where the vehicle goes + } + else + { //don't even run physics on a player if he's on a vehicle - he goes where the vehicle goes + if (pm->ps->pm_type == PM_FLOAT + ||pm_flying == FLY_NORMAL) + { + PM_FlyMove (); + } + else if ( pm_flying == FLY_VEHICLE ) + { + PM_FlyVehicleMove(); + } + 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(); + } + } + } + + if (!noAnimate) + { + PM_Animate(); + } + + // set groundentity, watertype, and waterlevel + PM_GroundTrace(); + if ( pm_flying == FLY_HOVER ) + {//never stick to the ground + PM_HoverTrace(); + } + PM_SetWaterLevel(); + if (pm->cmd.forcesel != -1 && (pm->ps->fd.forcePowersKnown & (1 << pm->cmd.forcesel))) + { + pm->ps->fd.forcePowerSelected = pm->cmd.forcesel; + } + if (pm->cmd.invensel != -1 && (pm->ps->stats[STAT_HOLDABLE_ITEMS] & (1 << pm->cmd.invensel))) + { + pm->ps->stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(pm->cmd.invensel, IT_HOLDABLE); + } + + if (pm->ps->m_iVehicleNum + /*&&pm_entSelf->s.NPC_class!=CLASS_VEHICLE*/ + && pm->ps->clientNum < MAX_CLIENTS) + {//a client riding a vehicle + if ( (pm->ps->eFlags&EF_NODRAW) ) + {//inside the vehicle, do nothing + } + else if (!PM_WeaponOkOnVehicle(pm->cmd.weapon) || !PM_WeaponOkOnVehicle(pm->ps->weapon)) + { //this weapon is not legal for the vehicle, force to our current one + if (!PM_WeaponOkOnVehicle(pm->ps->weapon)) + { //uh-oh! + int weap = PM_GetOkWeaponForVehicle(); + + if (weap != -1) + { + pm->cmd.weapon = weap; + pm->ps->weapon = weap; + } + } + else + { + pm->cmd.weapon = pm->ps->weapon; + } + } + } + + if (!pm->ps->m_iVehicleNum //not a vehicle and not riding one + || pm_entSelf->s.NPC_class==CLASS_VEHICLE //you are a vehicle NPC + || (!(pm->ps->eFlags&EF_NODRAW)&&PM_WeaponOkOnVehicle(pm->cmd.weapon)) )//you're not inside the vehicle and the weapon you're holding can be used when riding this vehicle + { //only run weapons if a valid weapon is selected + // weapons + PM_Weapon(); + } + + PM_Use(); + + if (!pm->ps->m_iVehicleNum && + (pm->ps->clientNum < MAX_CLIENTS || + !pm_entSelf || + pm_entSelf->s.NPC_class != CLASS_VEHICLE)) + { //don't do this if we're on a vehicle, or we are one + // 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 ); + + if (pm->ps->pm_type == PM_JETPACK || gPMDoSlowFall ) + { + pm->ps->gravity = savedGravity; + } + + if (//pm->ps->m_iVehicleNum && + pm->ps->clientNum >= MAX_CLIENTS && + pm_entSelf && + pm_entSelf->s.NPC_class == CLASS_VEHICLE) + { //a vehicle with passengers + bgEntity_t *veh; + + veh = pm_entSelf; + + assert(veh->m_pVehicle); + + //this could be kind of "inefficient" because it's called after every passenger pmove too. + //Maybe instead of AttachRiders we should have each rider call attach for himself? + if (veh->m_pVehicle && veh->ghoul2) + { + veh->m_pVehicle->m_pVehicleInfo->AttachRiders( veh->m_pVehicle ); + } + } + + if (pm_entSelf->s.NPC_class!=CLASS_VEHICLE + && pm->ps->m_iVehicleNum) + { //riding a vehicle, see if we should do some anim overrides + PM_VehicleWeaponAnimate(); + } +} + + +/* +================ +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; + } + + if (pmove->ps->fallingToDeath) + { + pmove->cmd.forwardmove = 0; + pmove->cmd.rightmove = 0; + pmove->cmd.upmove = 0; + pmove->cmd.buttons = 0; + } + + 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; + } + } +} + +#include "../namespace_end.h" diff --git a/code/game/bg_public.h b/code/game/bg_public.h new file mode 100644 index 0000000..56e2227 --- /dev/null +++ b/code/game/bg_public.h @@ -0,0 +1,1686 @@ +// 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 + +#ifndef __BG_PUBLIC_H__ +#define __BG_PUBLIC_H__ + +#include "bg_weapons.h" +#include "anims.h" +#ifdef JKA +#include "bg_vehicles.h" +#endif + +//these two defs are shared now because we do clientside ent parsing +#define MAX_SPAWN_VARS 64 +#define MAX_SPAWN_VARS_CHARS 4096 + + +#define GAME_VERSION "basejka-1" + +#define STEPSIZE 18 + +#define DEFAULT_GRAVITY 800 +#define GIB_HEALTH -40 +#define ARMOR_PROTECTION 0.50 // Shields only stop 50% of armor-piercing dmg +#define ARMOR_REDUCTION_FACTOR 0.50 // Certain damage doesn't take off armor as efficiently + +#define JUMP_VELOCITY 225//270 + +#define MAX_ITEMS 256 + +#define RANK_TIED_FLAG 0x4000 + +#define ITEM_RADIUS 15 // item sizes are needed for client side pickup detection + +#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 DEFAULT_MINS_2 -24 +#define DEFAULT_MAXS_2 40 +#define CROUCH_MAXS_2 16 +#define STANDARD_VIEWHEIGHT_OFFSET -4 + +#define MINS_Z -24 +#define DEFAULT_VIEWHEIGHT (DEFAULT_MAXS_2+STANDARD_VIEWHEIGHT_OFFSET)//26 +#define CROUCH_VIEWHEIGHT (CROUCH_MAXS_2+STANDARD_VIEWHEIGHT_OFFSET)//12 +#define DEAD_VIEWHEIGHT -16 + +#define MAX_CLIENT_SCORE_SEND 20 + +// +// 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 + +#define CS_CLIENT_JEDIMASTER 28 // current jedi master +#define CS_CLIENT_DUELWINNER 29 // current duel round winner - needed for printing at top of scoreboard +#define CS_CLIENT_DUELISTS 30 // client numbers for both current duelists. Needed for a number of client-side things. +#define CS_CLIENT_DUELHEALTHS 31 // nmckenzie: DUEL_HEALTH. Hopefully adding this cs is safe and good? +#define CS_GLOBAL_AMBIENT_SET 32 + +#define CS_AMBIENT_SET 37 + +#define CS_SIEGE_STATE (CS_AMBIENT_SET+MAX_AMBIENT_SETS) +#define CS_SIEGE_OBJECTIVES (CS_SIEGE_STATE+1) +#define CS_SIEGE_TIMEOVERRIDE (CS_SIEGE_OBJECTIVES+1) +#define CS_SIEGE_WINTEAM (CS_SIEGE_TIMEOVERRIDE+1) +#define CS_SIEGE_ICONS (CS_SIEGE_WINTEAM+1) + +#define CS_MODELS (CS_SIEGE_ICONS+1) +#define CS_SKYBOXORG (CS_MODELS+MAX_MODELS) //rww - skybox info +#define CS_SOUNDS (CS_SKYBOXORG+1) +#define CS_ICONS (CS_SOUNDS+MAX_SOUNDS) +#define CS_PLAYERS (CS_ICONS+MAX_ICONS) +/* +Ghoul2 Insert Start +*/ +#define CS_G2BONES (CS_PLAYERS+MAX_CLIENTS) +//rww - used to be CS_CHARSKINS, but I have eliminated the need for that. +/* +Ghoul2 Insert End +*/ +#define CS_LOCATIONS (CS_G2BONES+MAX_G2BONES) +#define CS_PARTICLES (CS_LOCATIONS+MAX_LOCATIONS) +#define CS_EFFECTS (CS_PARTICLES+MAX_LOCATIONS) +#define CS_LIGHT_STYLES (CS_EFFECTS + MAX_FX) + +//rwwRMG - added: +#define CS_TERRAINS (CS_LIGHT_STYLES + (MAX_LIGHT_STYLES*3)) +#define CS_BSP_MODELS (CS_TERRAINS + MAX_TERRAINS) + +#define CS_MAX (CS_BSP_MODELS + MAX_SUB_BSP) + +#if (CS_MAX) > MAX_CONFIGSTRINGS +#error overflow: (CS_MAX) > MAX_CONFIGSTRINGS +#endif + +typedef enum { + G2_MODELPART_HEAD = 10, + G2_MODELPART_WAIST, + G2_MODELPART_LARM, + G2_MODELPART_RARM, + G2_MODELPART_RHAND, + G2_MODELPART_LLEG, + G2_MODELPART_RLEG +} g2ModelParts_t; + +#define G2_MODEL_PART 50 + +#define BG_NUM_TOGGLEABLE_SURFACES 31 + +#define MAX_CUSTOM_SIEGE_SOUNDS 30 + +//#include "../namespace_begin.h" +extern const char *bg_customSiegeSoundNames[MAX_CUSTOM_SIEGE_SOUNDS]; + +extern const char *bgToggleableSurfaces[BG_NUM_TOGGLEABLE_SURFACES]; +extern const int bgToggleableSurfaceDebris[BG_NUM_TOGGLEABLE_SURFACES]; +//#include "../namespace_end.h" + +typedef enum { + HANDEXTEND_NONE = 0, + HANDEXTEND_FORCEPUSH, + HANDEXTEND_FORCEPULL, + HANDEXTEND_FORCE_HOLD, + HANDEXTEND_SABERPULL, + HANDEXTEND_CHOKE, //use handextend priorities to choke someone being gripped + HANDEXTEND_WEAPONREADY, + HANDEXTEND_DODGE, + HANDEXTEND_KNOCKDOWN, + HANDEXTEND_DUELCHALLENGE, + HANDEXTEND_TAUNT, + + HANDEXTEND_PRETHROW, + HANDEXTEND_POSTTHROW, + HANDEXTEND_PRETHROWN, + HANDEXTEND_POSTTHROWN, + + HANDEXTEND_DRAGGING, + + HANDEXTEND_JEDITAUNT, +} forceHandAnims_t; + +typedef enum +{ + BROKENLIMB_NONE = 0, + BROKENLIMB_LARM, + BROKENLIMB_RARM, + NUM_BROKENLIMBS +} brokenLimb_t; + +//for supplier class items +#define TOSS_DEBOUNCE_TIME 5000 + +typedef enum { + GT_FFA, // free for all + GT_HOLOCRON, // holocron ffa + GT_JEDIMASTER, // jedi master + GT_DUEL, // one on one tournament + GT_POWERDUEL, + GT_SINGLE_PLAYER, // single player ffa + + //-- team games go after this -- + + GT_TEAM, // team deathmatch + GT_SIEGE, // siege + GT_CTF, // capture the flag + GT_CTY, + GT_MAX_GAME_TYPE +}; +typedef int gametype_t; + +typedef enum { GENDER_MALE, GENDER_FEMALE, GENDER_NEUTER } gender_t; + +extern vec3_t WP_MuzzlePoint[WP_NUM_WEAPONS]; + +//#include "../namespace_begin.h" +extern int forcePowerSorted[NUM_FORCE_POWERS]; +//#include "../namespace_end.h" + +typedef enum +{ + SABERLOCK_TOP, + SABERLOCK_SIDE, + SABERLOCK_LOCK, + SABERLOCK_BREAK, + SABERLOCK_SUPERBREAK, + SABERLOCK_WIN, + SABERLOCK_LOSE +}; + +typedef enum +{ + DIR_RIGHT, + DIR_LEFT, + DIR_FRONT, + DIR_BACK +}; + +/* +=================================================================================== + +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. +=================================================================================== +*/ + + +#pragma pack(push, 1) +typedef struct animation_s { + unsigned short firstFrame; + unsigned short numFrames; + short frameLerp; // msec between frames + //initialLerp is abs(frameLerp) + signed char loopFrames; // 0 to numFrames +} animation_t; +#pragma pack(pop) + +//#include "../namespace_begin.h" +extern qboolean BGPAFtextLoaded; +extern animation_t bgHumanoidAnimations[MAX_TOTALANIMATIONS]; +//#include "../namespace_end.h" + +#define MAX_ANIM_FILES 16 +#define MAX_ANIM_EVENTS 300 + +typedef enum +{ + FOOTSTEP_R, + FOOTSTEP_L, + FOOTSTEP_HEAVY_R, + FOOTSTEP_HEAVY_L, + NUM_FOOTSTEP_TYPES +} footstepType_t; + +extern stringID_table_t animEventTypeTable[MAX_ANIM_EVENTS+1]; +extern stringID_table_t footstepTypeTable[NUM_FOOTSTEP_TYPES+1]; + +//size of Anim eventData array... +#define MAX_RANDOM_ANIM_SOUNDS 4 +#define AED_ARRAY_SIZE (MAX_RANDOM_ANIM_SOUNDS+3) +//indices for AEV_SOUND data +#define AED_SOUNDINDEX_START 0 +#define AED_SOUNDINDEX_END (MAX_RANDOM_ANIM_SOUNDS-1) +#define AED_SOUND_NUMRANDOMSNDS (MAX_RANDOM_ANIM_SOUNDS) +#define AED_SOUND_PROBABILITY (MAX_RANDOM_ANIM_SOUNDS+1) +//indices for AEV_SOUNDCHAN data +#define AED_SOUNDCHANNEL (MAX_RANDOM_ANIM_SOUNDS+2) +//indices for AEV_FOOTSTEP data +#define AED_FOOTSTEP_TYPE 0 +#define AED_FOOTSTEP_PROBABILITY 1 +//indices for AEV_EFFECT data +#define AED_EFFECTINDEX 0 +#define AED_BOLTINDEX 1 +#define AED_EFFECT_PROBABILITY 2 +#define AED_MODELINDEX 3 +//indices for AEV_FIRE data +#define AED_FIRE_ALT 0 +#define AED_FIRE_PROBABILITY 1 +//indices for AEV_MOVE data +#define AED_MOVE_FWD 0 +#define AED_MOVE_RT 1 +#define AED_MOVE_UP 2 +//indices for AEV_SABER_SWING data +#define AED_SABER_SWING_SABERNUM 0 +#define AED_SABER_SWING_TYPE 1 +#define AED_SABER_SWING_PROBABILITY 2 +//indices for AEV_SABER_SPIN data +#define AED_SABER_SPIN_SABERNUM 0 +#define AED_SABER_SPIN_TYPE 1 //0 = saberspinoff, 1 = saberspin, 2-4 = saberspin1-saberspin3 +#define AED_SABER_SPIN_PROBABILITY 2 + +typedef enum +{//NOTENOTE: Be sure to update animEventTypeTable and ParseAnimationEvtBlock(...) if you change this enum list! + AEV_NONE, + AEV_SOUND, //# animID AEV_SOUND framenum soundpath randomlow randomhi chancetoplay + AEV_FOOTSTEP, //# animID AEV_FOOTSTEP framenum footstepType chancetoplay + AEV_EFFECT, //# animID AEV_EFFECT framenum effectpath boltName chancetoplay + AEV_FIRE, //# animID AEV_FIRE framenum altfire chancetofire + AEV_MOVE, //# animID AEV_MOVE framenum forwardpush rightpush uppush + AEV_SOUNDCHAN, //# animID AEV_SOUNDCHAN framenum CHANNEL soundpath randomlow randomhi chancetoplay + AEV_SABER_SWING, //# animID AEV_SABER_SWING framenum CHANNEL randomlow randomhi chancetoplay + AEV_SABER_SPIN, //# animID AEV_SABER_SPIN framenum CHANNEL chancetoplay + AEV_NUM_AEV +} animEventType_t; + +typedef struct animevent_s +{ + animEventType_t eventType; + unsigned short keyFrame; //Frame to play event on + signed short eventData[AED_ARRAY_SIZE]; //Unique IDs, can be soundIndex of sound file to play OR effect index or footstep type, etc. + char *stringData; //we allow storage of one string, temporarily (in case we have to look up an index later, then make sure to set stringData to NULL so we only do the look-up once) +} animevent_t; + +typedef struct +{ + char filename[MAX_QPATH]; + animation_t *anims; +// animsounds_t torsoAnimSnds[MAX_ANIM_SOUNDS]; +// animsounds_t legsAnimSnds[MAX_ANIM_SOUNDS]; +// qboolean soundsCached; +} bgLoadedAnim_t; + +typedef struct +{ + char filename[MAX_QPATH]; + animevent_t torsoAnimEvents[MAX_ANIM_EVENTS]; + animevent_t legsAnimEvents[MAX_ANIM_EVENTS]; + qboolean eventsParsed; +} bgLoadedEvents_t; + +//#include "../namespace_begin.h" + +extern bgLoadedAnim_t bgAllAnims[MAX_ANIM_FILES]; + +//In SP this is shared in with the anim stuff, and humanoid anim sets can be loaded +//multiple times just for the sake of sounds being different. We probably wouldn't +//care normally but since we're working in VMs we have to do everything possible to +//cut memory cost. +//On the bright side this also means we're cutting a rather large size out of +//required game-side memory. +#ifndef QAGAME +extern bgLoadedEvents_t bgAllEvents[MAX_ANIM_FILES]; +extern int bgNumAnimEvents; +#endif + +//#include "../namespace_end.h" + +typedef enum { + PM_NORMAL, // can accelerate and turn + PM_JETPACK, // special jetpack movement + PM_FLOAT, // float with no gravity in general direction of velocity (intended for gripping) + 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, + WEAPON_CHARGING, + WEAPON_CHARGING_ALT, + WEAPON_IDLE, //lowered // NOTENOTE Added with saber +} weaponstate_t; + + +typedef enum { + FORCE_MASTERY_UNINITIATED, + FORCE_MASTERY_INITIATE, + FORCE_MASTERY_PADAWAN, + FORCE_MASTERY_JEDI, + FORCE_MASTERY_JEDI_GUARDIAN, + FORCE_MASTERY_JEDI_ADEPT, + FORCE_MASTERY_JEDI_KNIGHT, + FORCE_MASTERY_JEDI_MASTER, + NUM_FORCE_MASTERY_LEVELS +}; + +//#include "../namespace_begin.h" +extern char *forceMasteryLevels[NUM_FORCE_MASTERY_LEVELS]; +extern int forceMasteryPoints[NUM_FORCE_MASTERY_LEVELS]; + +extern int bgForcePowerCost[NUM_FORCE_POWERS][NUM_FORCE_POWER_LEVELS]; +//#include "../namespace_end.h" + +// pmove->pm_flags +#define PMF_DUCKED 1 +#define PMF_JUMP_HELD 2 +#define PMF_ROLLING 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_FIX_MINS 128 // mins have been brought up, keep tracing down to fix them +#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_UPDATE_ANIM 2048 // The server updated the animation, the pmove should set the ghoul2 anim to match. +#define PMF_FOLLOW 4096 // spectate following another player +#define PMF_SCOREBOARD 8192 // spectate as a scoreboard +#define PMF_STUCK_TO_WALL 16384 // grabbing a wall + +#define PMF_ALL_TIMES (PMF_TIME_WATERJUMP|PMF_TIME_LAND|PMF_TIME_KNOCKBACK) + +#define MAXTOUCH 32 + +typedef struct bgEntity_s +{ + entityState_t s; + playerState_t *playerState; +#ifdef JKA + Vehicle_t *m_pVehicle; //vehicle data +#endif + void *ghoul2; //g2 instance + int localAnimIndex; //index locally (game/cgame) to anim data for this skel + vec3_t modelScale; //needed for g2 collision + + //Data type(s) must directly correspond to the head of the gentity and centity structures +} bgEntity_t; + +typedef struct { + // state (in / out) + playerState_t *ps; + + //rww - shared ghoul2 stuff (not actually the same data, but hey) + void *ghoul2; + int g2Bolts_LFoot; + int g2Bolts_RFoot; + vec3_t modelScale; + + //hacky bool so we know if we're dealing with a nonhumanoid (which is probably a rockettrooper) + qboolean nonHumanoid; + + // 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]; + + int useEvent; + + vec3_t mins, maxs; // bounding box size + + int watertype; + int waterlevel; + + int gametype; + + int debugMelee; + int stepSlideFix; + int noSpecMove; + + animation_t *animations; + + 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 ); + + int checkDuelLoss; + + //rww - bg entitystate access method + bgEntity_t *baseEnt; //base address of the entity array (g_entities or cg_entities) + int entSize; //size of the struct (gentity_t or centity_t) so things can be dynamic +} pmove_t; + +//#include "../namespace_begin.h" + +extern pmove_t *pm; + +#define SETANIM_TORSO 1 +#define SETANIM_LEGS 2 +#define SETANIM_BOTH SETANIM_TORSO|SETANIM_LEGS//3 + +#define SETANIM_FLAG_NORMAL 0//Only set if timer is 0 +#define SETANIM_FLAG_OVERRIDE 1//Override previous +#define SETANIM_FLAG_HOLD 2//Set the new timer +#define SETANIM_FLAG_RESTART 4//Allow restarting the anim if playing the same one (weapon fires) +#define SETANIM_FLAG_HOLDLESS 8//Set the new timer + + +// 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); + +//#include "../namespace_end.h" + +//=================================================================================== + + +// player_state->stats[] indexes +// NOTE: may not have more than 16 +typedef enum { + STAT_HEALTH, + STAT_HOLDABLE_ITEM, + STAT_HOLDABLE_ITEMS, + STAT_PERSISTANT_POWERUP, + //MAKE SURE STAT_WEAPONS REMAINS 4!!!! + //There is a hardcoded reference in msg.cpp to send it in 32 bits -rww + STAT_WEAPONS = 4, // 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 +} 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 +} persEnum_t; + + +// entityState_t->eFlags +#define EF_G2ANIMATING (1<<0) //perform g2 bone anims based on torsoAnim and legsAnim, works for ET_GENERAL -rww +#define EF_DEAD (1<<1) // don't draw a foe marker over players with EF_DEAD +//#define EF_BOUNCE_SHRAPNEL (1<<2) // special shrapnel flag +//do not use eflags for server-only things, it wastes bandwidth -rww +#define EF_RADAROBJECT (1<<2) // display on team radar + +#define EF_TELEPORT_BIT (1<<3) // toggled every time the origin abruptly changes + +#define EF_SHADER_ANIM (1<<4) // Animating shader (by s.frame) + +#define EF_PLAYER_EVENT (1<<5) +//#define EF_BOUNCE (1<<5) // for missiles +//#define EF_BOUNCE_HALF (1<<6) // for missiles +//these aren't even referenced in bg or client code and do not need to be eFlags, so I +//am using these flags for rag stuff -rww + +#define EF_RAG (1<<6) //ragdoll him even if he's alive + + +#define EF_PERMANENT (1<<7) // rww - I am claiming this. (for permanent entities) + +#define EF_NODRAW (1<<8) // may have an event, but no model (unspawned items) +#define EF_FIRING (1<<9) // for lightning gun +#define EF_ALT_FIRING (1<<10) // for alt-fires, mostly for lightning guns though +#define EF_JETPACK_ACTIVE (1<<11) //jetpack is activated + +#define EF_NOT_USED_1 (1<<12) // not used + +#define EF_TALK (1<<13) // draw a talk balloon +#define EF_CONNECTION (1<<14) // draw a connection trouble sprite +#define EF_NOT_USED_6 (1<<15) // not used + +#define EF_NOT_USED_2 (1<<16) // not used +#define EF_NOT_USED_3 (1<<17) // not used +#define EF_NOT_USED_4 (1<<18) // not used + +#define EF_BODYPUSH (1<<19) //rww - claiming this for fullbody push effect + +#define EF_DOUBLE_AMMO (1<<20) // Hacky way to get around ammo max +#define EF_SEEKERDRONE (1<<21) // show seeker drone floating around head +#define EF_MISSILE_STICK (1<<22) // missiles that stick to the wall. +#define EF_ITEMPLACEHOLDER (1<<23) // item effect +#define EF_SOUNDTRACKER (1<<24) // sound position needs to be updated in relation to another entity +#define EF_DROPPEDWEAPON (1<<25) // it's a dropped weapon +#define EF_DISINTEGRATION (1<<26) // being disintegrated by the disruptor +#define EF_INVULNERABLE (1<<27) // just spawned in or whatever, so is protected + +#define EF_CLIENTSMOOTH (1<<28) // standard lerporigin smooth override on client + +#define EF_JETPACK (1<<29) //rww - wearing a jetpack +#define EF_JETPACK_FLAMING (1<<30) //rww - jetpack fire effect + +#define EF_NOT_USED_5 (1<<31) // not used + +//These new EF2_??? flags were added for NPCs, they really should not be used often. +//NOTE: we only allow 10 of these! +#define EF2_HELD_BY_MONSTER (1<<0) // Being held by something, like a Rancor or a Wampa +#define EF2_USE_ALT_ANIM (1<<1) // For certain special runs/stands for creatures like the Rancor and Wampa whose runs/stands are conditional +#define EF2_ALERTED (1<<2) // For certain special anims, for Rancor: means you've had an enemy, so use the more alert stand +#define EF2_GENERIC_NPC_FLAG (1<<3) // So far, used for Rancor... +#define EF2_FLYING (1<<4) // Flying FIXME: only used on NPCs doesn't *really* have to be passed over, does it? +#define EF2_HYPERSPACE (1<<5) // Used to both start the hyperspace effect on the predicted client and to let the vehicle know it can now jump into hyperspace (after turning to face the proper angle) +#define EF2_BRACKET_ENTITY (1<<6) // Draw as bracketed +#define EF2_SHIP_DEATH (1<<7) // "died in ship" mode +#define EF2_NOT_USED_1 (1<<8) // not used + + +typedef enum { + EFFECT_NONE = 0, + EFFECT_SMOKE, + EFFECT_EXPLOSION, + EFFECT_EXPLOSION_PAS, + EFFECT_SPARK_EXPLOSION, + EFFECT_EXPLOSION_TRIPMINE, + EFFECT_EXPLOSION_DETPACK, + EFFECT_EXPLOSION_FLECHETTE, + EFFECT_STUNHIT, + EFFECT_EXPLOSION_DEMP2ALT, + EFFECT_EXPLOSION_TURRET, + EFFECT_SPARKS, + EFFECT_WATER_SPLASH, + EFFECT_ACID_SPLASH, + EFFECT_LAVA_SPLASH, + EFFECT_LANDING_MUD, + EFFECT_LANDING_SAND, + EFFECT_LANDING_DIRT, + EFFECT_LANDING_SNOW, + EFFECT_LANDING_GRAVEL, + EFFECT_MAX +} effectTypes_t; + +// NOTE: may not have more than 16 +typedef enum { + PW_NONE, + + PW_QUAD, + PW_BATTLESUIT, + PW_PULL, + //PW_INVIS, //rww - removed + //PW_REGEN, //rww - removed + //PW_FLIGHT, //rww - removed + + PW_REDFLAG, + PW_BLUEFLAG, + PW_NEUTRALFLAG, + + PW_SHIELDHIT, + + //PW_SCOUT, //rww - removed + //PW_GUARD, //rww - removed + //PW_DOUBLER, //rww - removed + //PW_AMMOREGEN, //rww - removed + PW_SPEEDBURST, + PW_DISINT_4, + PW_SPEED, + PW_CLOAKED, + PW_FORCE_ENLIGHTENED_LIGHT, + PW_FORCE_ENLIGHTENED_DARK, + PW_FORCE_BOON, + PW_YSALAMIRI, + + PW_NUM_POWERUPS + +}; +typedef int powerup_t; + +typedef enum { + HI_NONE, + + HI_SEEKER, + HI_SHIELD, + HI_MEDPAC, + HI_MEDPAC_BIG, + HI_BINOCULARS, + HI_SENTRY_GUN, + HI_JETPACK, + + HI_HEALTHDISP, + HI_AMMODISP, + HI_EWEB, + HI_CLOAK, + + HI_NUM_HOLDABLE +}; +typedef int holdable_t; + + +typedef enum { + CTFMESSAGE_FRAGGED_FLAG_CARRIER, + CTFMESSAGE_FLAG_RETURNED, + CTFMESSAGE_PLAYER_RETURNED_FLAG, + CTFMESSAGE_PLAYER_CAPTURED_FLAG, + CTFMESSAGE_PLAYER_GOT_FLAG +} ctfMsg_t; + +// reward sounds (stored in ps->persistant[PERS_PLAYEREVENTS]) +#define PLAYEREVENT_DENIEDREWARD 0x0001 +#define PLAYEREVENT_GAUNTLETREWARD 0x0002 + +// 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 +{ + PDSOUND_NONE, + PDSOUND_PROTECTHIT, + PDSOUND_PROTECT, + PDSOUND_ABSORBHIT, + PDSOUND_ABSORB, + PDSOUND_FORCEJUMP, + PDSOUND_FORCEGRIP +} pdSounds_t; + +typedef enum { + EV_NONE, + + EV_CLIENTJOIN, + + 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, + + EV_JUMP_PAD, // boing sound at origin, jump sound on player + + EV_GHOUL2_MARK, //create a projectile impact mark on something with a client-side g2 instance. + + EV_GLOBAL_DUEL, + EV_PRIVATE_DUEL, + + EV_JUMP, + EV_ROLL, + 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_VEH_FIRE, + + EV_NOAMMO, + EV_CHANGE_WEAPON, + EV_FIRE_WEAPON, + EV_ALT_FIRE, + EV_SABER_ATTACK, + EV_SABER_HIT, + EV_SABER_BLOCK, + EV_SABER_CLASHFLARE, + EV_SABER_UNHOLSTER, + EV_BECOME_JEDIMASTER, + EV_DISRUPTOR_MAIN_SHOT, + EV_DISRUPTOR_SNIPER_SHOT, + EV_DISRUPTOR_SNIPER_MISS, + EV_DISRUPTOR_HIT, + EV_DISRUPTOR_ZOOMSOUND, + + EV_PREDEFSOUND, + + EV_TEAM_POWER, + + EV_SCREENSHAKE, + + EV_LOCALTIMER, + + EV_USE, // +Use key + + 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_ITEMUSEFAIL, + + EV_ITEM_RESPAWN, + EV_ITEM_POP, + EV_PLAYER_TELEPORT_IN, + EV_PLAYER_TELEPORT_OUT, + + EV_GRENADE_BOUNCE, // eventParm will be the soundindex + EV_MISSILE_STICK, // eventParm will be the soundindex + + EV_PLAY_EFFECT, + EV_PLAY_EFFECT_ID, + EV_PLAY_PORTAL_EFFECT_ID, + + EV_PLAYDOORSOUND, + EV_PLAYDOORLOOPSOUND, + EV_BMODEL_SOUND, + + EV_MUTE_SOUND, + EV_VOICECMD_SOUND, + EV_GENERAL_SOUND, + EV_GLOBAL_SOUND, // no attenuation + EV_GLOBAL_TEAM_SOUND, + EV_ENTITY_SOUND, + + EV_PLAY_ROFF, + + EV_GLASS_SHATTER, + EV_DEBRIS, + EV_MISC_MODEL_EXP, + + EV_CONC_ALT_IMPACT, + + EV_MISSILE_HIT, + EV_MISSILE_MISS, + EV_MISSILE_MISS_METAL, + 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_FORCE_DRAINED, + + EV_GIB_PLAYER, // gib a previously living player + EV_SCOREPLUM, // score plum + + EV_CTFMESSAGE, + + EV_BODYFADE, + + EV_SIEGE_ROUNDOVER, + EV_SIEGE_OBJECTIVECOMPLETE, + + EV_DESTROY_GHOUL2_INSTANCE, + + EV_DESTROY_WEAPON_MODEL, + + EV_GIVE_NEW_RANK, + EV_SET_FREE_SABER, + EV_SET_FORCE_DISABLE, + + EV_WEAPON_CHARGE, + EV_WEAPON_CHARGE_ALT, + + EV_SHIELD_HIT, + + EV_DEBUG_LINE, + EV_TESTLINE, + EV_STOPLOOPINGSOUND, + EV_STARTLOOPINGSOUND, + EV_TAUNT, + + //rww - Begin NPC sound events + EV_ANGER1, //Say when acquire an enemy when didn't have one before + EV_ANGER2, + EV_ANGER3, + + EV_VICTORY1, //Say when killed an enemy + EV_VICTORY2, + EV_VICTORY3, + + EV_CONFUSE1, //Say when confused + EV_CONFUSE2, + EV_CONFUSE3, + + EV_PUSHED1, //Say when pushed + EV_PUSHED2, + EV_PUSHED3, + + EV_CHOKE1, //Say when choking + EV_CHOKE2, + EV_CHOKE3, + + EV_FFWARN, //ffire founds + EV_FFTURN, + //extra sounds for ST + EV_CHASE1, + EV_CHASE2, + EV_CHASE3, + EV_COVER1, + EV_COVER2, + EV_COVER3, + EV_COVER4, + EV_COVER5, + EV_DETECTED1, + EV_DETECTED2, + EV_DETECTED3, + EV_DETECTED4, + EV_DETECTED5, + EV_LOST1, + EV_OUTFLANK1, + EV_OUTFLANK2, + EV_ESCAPING1, + EV_ESCAPING2, + EV_ESCAPING3, + EV_GIVEUP1, + EV_GIVEUP2, + EV_GIVEUP3, + EV_GIVEUP4, + EV_LOOK1, + EV_LOOK2, + EV_SIGHT1, + EV_SIGHT2, + EV_SIGHT3, + EV_SOUND1, + EV_SOUND2, + EV_SOUND3, + EV_SUSPICIOUS1, + EV_SUSPICIOUS2, + EV_SUSPICIOUS3, + EV_SUSPICIOUS4, + EV_SUSPICIOUS5, + //extra sounds for Jedi + EV_COMBAT1, + EV_COMBAT2, + EV_COMBAT3, + EV_JDETECTED1, + EV_JDETECTED2, + EV_JDETECTED3, + EV_TAUNT1, + EV_TAUNT2, + EV_TAUNT3, + EV_JCHASE1, + EV_JCHASE2, + EV_JCHASE3, + EV_JLOST1, + EV_JLOST2, + EV_JLOST3, + EV_DEFLECT1, + EV_DEFLECT2, + EV_DEFLECT3, + EV_GLOAT1, + EV_GLOAT2, + EV_GLOAT3, + EV_PUSHFAIL, + + EV_SIEGESPEC, + +} entity_event_t; // There is a maximum of 256 events (8 bits transmission, 2 high bits for uniqueness) + + +typedef enum { + GTS_RED_CAPTURE, + GTS_BLUE_CAPTURE, + GTS_RED_RETURN, + GTS_BLUE_RETURN, + GTS_RED_TAKEN, + GTS_BLUE_TAKEN, + GTS_REDTEAM_SCORED, + GTS_BLUETEAM_SCORED, + GTS_REDTEAM_TOOK_LEAD, + GTS_BLUETEAM_TOOK_LEAD, + GTS_TEAMS_ARE_TIED +} global_team_sound_t; + + + +typedef enum { + TEAM_FREE, + TEAM_RED, + TEAM_BLUE, + TEAM_SPECTATOR, + + TEAM_NUM_TEAMS +}; +typedef int team_t; + +typedef enum { + DUELTEAM_FREE, + DUELTEAM_LONE, + DUELTEAM_DOUBLE, + + DUELTEAM_SINGLE, // for regular duel matches (not power duel) +} duelTeam_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; + +// means of death +typedef enum { + MOD_UNKNOWN, + MOD_STUN_BATON, + MOD_MELEE, + MOD_SABER, + MOD_BRYAR_PISTOL, + MOD_BRYAR_PISTOL_ALT, + MOD_BLASTER, + MOD_TURBLAST, + MOD_DISRUPTOR, + MOD_DISRUPTOR_SPLASH, + MOD_DISRUPTOR_SNIPER, + MOD_BOWCASTER, + MOD_REPEATER, + MOD_REPEATER_ALT, + MOD_REPEATER_ALT_SPLASH, + MOD_DEMP2, + MOD_DEMP2_ALT, + MOD_FLECHETTE, + MOD_FLECHETTE_ALT_SPLASH, + MOD_ROCKET, + MOD_ROCKET_SPLASH, + MOD_ROCKET_HOMING, + MOD_ROCKET_HOMING_SPLASH, + MOD_THERMAL, + MOD_THERMAL_SPLASH, + MOD_TRIP_MINE_SPLASH, + MOD_TIMED_MINE_SPLASH, + MOD_DET_PACK_SPLASH, + MOD_VEHICLE, + MOD_CONC, + MOD_CONC_ALT, + MOD_FORCE_DARK, + MOD_SENTRY, + MOD_WATER, + MOD_SLIME, + MOD_LAVA, + MOD_CRUSH, + MOD_TELEFRAG, + MOD_FALLING, + MOD_SUICIDE, + MOD_TARGET_LASER, + MOD_TRIGGER_HURT, + MOD_TEAM_CHANGE, + //AURELIO: when/if you put this back in, remember to make a case for it in all the other places where + //mod's are checked. Also, it probably isn't the most elegant solution for what you want - just add + //a frag back to the player after you call the player_die (and keep a local of his pre-death score to + //make sure he actually lost points, there may be cases where you don't lose points on changing teams + //or suiciding, and so you would actually be giving him a point) -Rich + // I put it back in for now, if it becomes a problem we'll work around it later (it shouldn't though)... + MOD_MAX +} 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 +}; +typedef int 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 *view_model; + 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 + char *description; +} gitem_t; + +// included in both the game dll and the client +//#include "../namespace_begin.h" + +extern gitem_t bg_itemlist[]; +extern int bg_numItems; + +float vectoyaw( const vec3_t vec ); + +gitem_t *BG_FindItem( const char *classname ); +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 ); + +//#include "../namespace_end.h" + + +#define SABER_BLOCK_DUR 150 // number of milliseconds a block animation should take. + + + +// g_dmflags->integer flags +#define DF_NO_FALLING 8 +#define DF_FIXED_FOV 16 +#define DF_NO_FOOTSTEPS 32 + +//rwwRMG - added in CONTENTS_TERRAIN +// content masks +#define MASK_ALL (-1) +#define MASK_SOLID (CONTENTS_SOLID|CONTENTS_TERRAIN) +#define MASK_PLAYERSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_BODY|CONTENTS_TERRAIN) +#define MASK_NPCSOLID (CONTENTS_SOLID|CONTENTS_MONSTERCLIP|CONTENTS_BODY|CONTENTS_TERRAIN) +#define MASK_DEADSOLID (CONTENTS_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_TERRAIN) +#define MASK_WATER (CONTENTS_WATER|CONTENTS_LAVA|CONTENTS_SLIME) +#define MASK_OPAQUE (CONTENTS_SOLID|CONTENTS_SLIME|CONTENTS_LAVA|CONTENTS_TERRAIN) +#define MASK_SHOT (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_TERRAIN) + + +// ET_FX States (stored in modelindex2) + +#define FX_STATE_OFF 0 +#define FX_STATE_ONE_SHOT 1 +#define FX_STATE_ONE_SHOT_LIMIT 10 +#define FX_STATE_CONTINUOUS 20 + +// +// entityState_t->eType +// +typedef enum { + ET_GENERAL, + ET_PLAYER, + ET_ITEM, + ET_MISSILE, + ET_SPECIAL, // rww - force fields + ET_HOLOCRON, // rww - holocron icon displays + ET_MOVER, + ET_BEAM, + ET_PORTAL, + ET_SPEAKER, + ET_PUSH_TRIGGER, + ET_TELEPORT_TRIGGER, + ET_INVISIBLE, + ET_NPC, // ghoul2 player-like entity + ET_TEAM, + ET_BODY, + ET_TERRAIN, + ET_FX, + + ET_EVENTS // any of the EV_* events can be added freestanding + // by setting eType to ET_EVENTS + eventNum + // this avoids having to set eFlags and eventNum +} entityType_t; + + + + +// +// fields are needed for spawning from the entity string +// +//I moved these from g_spawn.c because the entity parsing stuff is semi-shared now -rww +#undef _GAME_SIDE + +#ifdef QAGAME +#define _GAME_SIDE +#elif defined CGAME +#define _GAME_SIDE +#endif + +#ifdef _GAME_SIDE +typedef enum { + F_INT, + F_FLOAT, + F_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + F_GSTRING, // string on disk, pointer in memory, TAG_GAME + F_VECTOR, + F_ANGLEHACK, + F_ENTITY, // index on disk, pointer in memory + F_ITEM, // index on disk, pointer in memory + F_CLIENT, // index on disk, pointer in memory + F_PARM1, // Special case for parms + F_PARM2, // Special case for parms + F_PARM3, // Special case for parms + F_PARM4, // Special case for parms + F_PARM5, // Special case for parms + F_PARM6, // Special case for parms + F_PARM7, // Special case for parms + F_PARM8, // Special case for parms + F_PARM9, // Special case for parms + F_PARM10, // Special case for parms + F_PARM11, // Special case for parms + F_PARM12, // Special case for parms + F_PARM13, // Special case for parms + F_PARM14, // Special case for parms + F_PARM15, // Special case for parms + F_PARM16, // Special case for parms + F_IGNORE +} fieldtype_t; + + + + +typedef struct +{ + char *name; + int ofs; + fieldtype_t type; + int flags; +} BG_field_t; + + + +#endif + + + + +// Okay, here lies the much-dreaded Pat-created FSM movement chart... Heretic II strikes again! +// Why am I inflicting this on you? Well, it's better than hardcoded states. +// Ideally this will be replaced with an external file or more sophisticated move-picker +// once the game gets out of prototype stage. + +// rww - Moved all this to bg_public so that we can access the saberMoveData stuff on the cgame +// which is currently used for determining if a saber trail should be rendered in a given frame +#ifdef LS_NONE +#undef LS_NONE +#endif + +typedef enum { + //totally invalid + LS_INVALID = -1, + // Invalid, or saber not armed + LS_NONE = 0, + + // General movements with saber + LS_READY, + LS_DRAW, + LS_PUTAWAY, + + // Attacks + LS_A_TL2BR,//4 + LS_A_L2R, + LS_A_BL2TR, + LS_A_BR2TL, + LS_A_R2L, + LS_A_TR2BL, + LS_A_T2B, + LS_A_BACKSTAB, + LS_A_BACK, + LS_A_BACK_CR, + LS_ROLL_STAB, + LS_A_LUNGE, + LS_A_JUMP_T__B_, + LS_A_FLIP_STAB, + LS_A_FLIP_SLASH, + LS_JUMPATTACK_DUAL, + LS_JUMPATTACK_ARIAL_LEFT, + LS_JUMPATTACK_ARIAL_RIGHT, + LS_JUMPATTACK_CART_LEFT, + LS_JUMPATTACK_CART_RIGHT, + LS_JUMPATTACK_STAFF_LEFT, + LS_JUMPATTACK_STAFF_RIGHT, + LS_BUTTERFLY_LEFT, + LS_BUTTERFLY_RIGHT, + LS_A_BACKFLIP_ATK, + LS_SPINATTACK_DUAL, + LS_SPINATTACK, + LS_LEAP_ATTACK, + LS_SWOOP_ATTACK_RIGHT, + LS_SWOOP_ATTACK_LEFT, + LS_TAUNTAUN_ATTACK_RIGHT, + LS_TAUNTAUN_ATTACK_LEFT, + LS_KICK_F, + LS_KICK_B, + LS_KICK_R, + LS_KICK_L, + LS_KICK_S, + LS_KICK_BF, + LS_KICK_RL, + LS_KICK_F_AIR, + LS_KICK_B_AIR, + LS_KICK_R_AIR, + LS_KICK_L_AIR, + LS_STABDOWN, + LS_STABDOWN_STAFF, + LS_STABDOWN_DUAL, + LS_DUAL_SPIN_PROTECT, + LS_STAFF_SOULCAL, + LS_A1_SPECIAL, + LS_A2_SPECIAL, + LS_A3_SPECIAL, + LS_UPSIDE_DOWN_ATTACK, + LS_PULL_ATTACK_STAB, + LS_PULL_ATTACK_SWING, + LS_SPINATTACK_ALORA, + LS_DUAL_FB, + LS_DUAL_LR, + LS_HILT_BASH, + + //starts + LS_S_TL2BR,//26 + LS_S_L2R, + LS_S_BL2TR,//# Start of attack chaining to SLASH LR2UL + LS_S_BR2TL,//# Start of attack chaining to SLASH LR2UL + LS_S_R2L, + LS_S_TR2BL, + LS_S_T2B, + + //returns + LS_R_TL2BR,//33 + LS_R_L2R, + LS_R_BL2TR, + LS_R_BR2TL, + LS_R_R2L, + LS_R_TR2BL, + LS_R_T2B, + + //transitions + LS_T1_BR__R,//40 + LS_T1_BR_TR, + LS_T1_BR_T_, + LS_T1_BR_TL, + LS_T1_BR__L, + LS_T1_BR_BL, + LS_T1__R_BR,//46 + LS_T1__R_TR, + LS_T1__R_T_, + LS_T1__R_TL, + LS_T1__R__L, + LS_T1__R_BL, + LS_T1_TR_BR,//52 + LS_T1_TR__R, + LS_T1_TR_T_, + LS_T1_TR_TL, + LS_T1_TR__L, + LS_T1_TR_BL, + LS_T1_T__BR,//58 + LS_T1_T___R, + LS_T1_T__TR, + LS_T1_T__TL, + LS_T1_T___L, + LS_T1_T__BL, + LS_T1_TL_BR,//64 + LS_T1_TL__R, + LS_T1_TL_TR, + LS_T1_TL_T_, + LS_T1_TL__L, + LS_T1_TL_BL, + LS_T1__L_BR,//70 + LS_T1__L__R, + LS_T1__L_TR, + LS_T1__L_T_, + LS_T1__L_TL, + LS_T1__L_BL, + LS_T1_BL_BR,//76 + LS_T1_BL__R, + LS_T1_BL_TR, + LS_T1_BL_T_, + LS_T1_BL_TL, + LS_T1_BL__L, + + //Bounces + LS_B1_BR, + LS_B1__R, + LS_B1_TR, + LS_B1_T_, + LS_B1_TL, + LS_B1__L, + LS_B1_BL, + + //Deflected attacks + LS_D1_BR, + LS_D1__R, + LS_D1_TR, + LS_D1_T_, + LS_D1_TL, + LS_D1__L, + LS_D1_BL, + LS_D1_B_, + + //Reflected attacks + LS_V1_BR, + LS_V1__R, + LS_V1_TR, + LS_V1_T_, + LS_V1_TL, + LS_V1__L, + LS_V1_BL, + LS_V1_B_, + + // Broken parries + LS_H1_T_,// + LS_H1_TR, + LS_H1_TL, + LS_H1_BR, + LS_H1_B_, + LS_H1_BL, + + // Knockaways + LS_K1_T_,// + LS_K1_TR, + LS_K1_TL, + LS_K1_BR, + LS_K1_BL, + + // Parries + LS_PARRY_UP,// + LS_PARRY_UR, + LS_PARRY_UL, + LS_PARRY_LR, + LS_PARRY_LL, + + // Projectile Reflections + LS_REFLECT_UP,// + LS_REFLECT_UR, + LS_REFLECT_UL, + LS_REFLECT_LR, + LS_REFLECT_LL, + + LS_MOVE_MAX// +}; +typedef int saberMoveName_t; + +typedef enum { + Q_BR, + Q_R, + Q_TR, + Q_T, + Q_TL, + Q_L, + Q_BL, + Q_B, + Q_NUM_QUADS +} saberQuadrant_t; + +typedef struct +{ + char *name; + int animToUse; + int startQuad; + int endQuad; + unsigned animSetFlags; + int blendTime; + int blocking; + saberMoveName_t chain_idle; // What move to call if the attack button is not pressed at the end of this anim + saberMoveName_t chain_attack; // What move to call if the attack button (and nothing else) is pressed + qboolean trailLength; +} saberMoveData_t; + +//#include "../namespace_begin.h" + +extern saberMoveData_t saberMoveData[LS_MOVE_MAX]; + +bgEntity_t *PM_BGEntForNum( int num ); +qboolean BG_KnockDownable(playerState_t *ps); +qboolean BG_LegalizedForcePowers(char *powerOut, int maxRank, qboolean freeSaber, int teamForce, int gametype, int fpDisabled); + +//#include "../namespace_end.h" + +#ifdef __LCC__ //can't inline it then, it is declared over in bg_misc in this case +void BG_GiveMeVectorFromMatrix(mdxaBone_t *boltMatrix, int flags, vec3_t vec); +#else +// given a boltmatrix, return in vec a normalised vector for the axis requested in flags +static ID_INLINE void BG_GiveMeVectorFromMatrix(mdxaBone_t *boltMatrix, int flags, vec3_t vec) +{ + switch (flags) + { + case ORIGIN: + vec[0] = boltMatrix->matrix[0][3]; + vec[1] = boltMatrix->matrix[1][3]; + vec[2] = boltMatrix->matrix[2][3]; + break; + case POSITIVE_Y: + vec[0] = boltMatrix->matrix[0][1]; + vec[1] = boltMatrix->matrix[1][1]; + vec[2] = boltMatrix->matrix[2][1]; + break; + case POSITIVE_X: + vec[0] = boltMatrix->matrix[0][0]; + vec[1] = boltMatrix->matrix[1][0]; + vec[2] = boltMatrix->matrix[2][0]; + break; + case POSITIVE_Z: + vec[0] = boltMatrix->matrix[0][2]; + vec[1] = boltMatrix->matrix[1][2]; + vec[2] = boltMatrix->matrix[2][2]; + break; + case NEGATIVE_Y: + vec[0] = -boltMatrix->matrix[0][1]; + vec[1] = -boltMatrix->matrix[1][1]; + vec[2] = -boltMatrix->matrix[2][1]; + break; + case NEGATIVE_X: + vec[0] = -boltMatrix->matrix[0][0]; + vec[1] = -boltMatrix->matrix[1][0]; + vec[2] = -boltMatrix->matrix[2][0]; + break; + case NEGATIVE_Z: + vec[0] = -boltMatrix->matrix[0][2]; + vec[1] = -boltMatrix->matrix[1][2]; + vec[2] = -boltMatrix->matrix[2][2]; + break; + } +} +#endif + +//#include "../namespace_begin.h" + +void BG_IK_MoveArm(void *ghoul2, int lHandBolt, int time, entityState_t *ent, int basePose, vec3_t desiredPos, qboolean *ikInProgress, + vec3_t origin, vec3_t angles, vec3_t scale, int blendTime, qboolean forceHalt); + +void BG_G2PlayerAngles(void *ghoul2, int motionBolt, entityState_t *cent, int time, vec3_t cent_lerpOrigin, + vec3_t cent_lerpAngles, vec3_t legs[3], vec3_t legsAngles, qboolean *tYawing, + qboolean *tPitching, qboolean *lYawing, float *tYawAngle, float *tPitchAngle, + float *lYawAngle, int frametime, vec3_t turAngles, vec3_t modelScale, int ciLegs, + int ciTorso, int *corrTime, vec3_t lookAngles, vec3_t lastHeadAngles, int lookTime, + entityState_t *emplaced, int *crazySmoothFactor); +void BG_G2ATSTAngles(void *ghoul2, int time, vec3_t cent_lerpAngles ); + +//BG anim utility functions: + +int BG_AnimLength( int index, animNumber_t anim ); + +qboolean BG_InSpecialJump( int anim ); +qboolean BG_InSaberStandAnim( int anim ); +qboolean BG_InReboundJump( int anim ); +qboolean BG_InReboundHold( int anim ); +qboolean BG_InReboundRelease( int anim ); +qboolean BG_InBackFlip( int anim ); +qboolean BG_DirectFlippingAnim( int anim ); +qboolean BG_SaberInAttack( int move ); +qboolean BG_SaberInSpecial( int move ); +qboolean BG_KickMove( int move ); +qboolean BG_SaberInIdle( int move ); +qboolean BG_FlippingAnim( int anim ); +qboolean BG_SpinningSaberAnim( int anim ); +qboolean BG_SaberInSpecialAttack( int anim ); +qboolean BG_SaberInKata( int saberMove ); +qboolean BG_InKataAnim(int anim); +qboolean BG_KickingAnim( int anim ); +int BG_InGrappleMove(int anim); +int BG_BrokenParryForAttack( int move ); +int BG_BrokenParryForParry( int move ); +int BG_KnockawayForParry( int move ); +qboolean BG_InRoll( playerState_t *ps, int anim ); +qboolean BG_InDeathAnim( int anim ); +qboolean BG_InSaberLockOld( int anim ); +qboolean BG_InSaberLock( int anim ); + +void BG_SaberStartTransAnim( int clientNum, int saberAnimLevel, int weapon, int anim, float *animSpeed, int broken ); + +void BG_ForcePowerDrain( playerState_t *ps, forcePowers_t forcePower, int overrideAmt ); + +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 ); + +void BG_InitAnimsets(void); +void BG_ClearAnimsets(void); +int BG_ParseAnimationFile(const char *filename, animation_t *animSet, qboolean isHumanoid); +#ifndef QAGAME +int BG_ParseAnimationEvtFile( const char *as_filename, int animFileIndex, int eventFileIndex ); +#endif + +qboolean BG_HasAnimation(int animIndex, int animation); +int BG_PickAnim( int animIndex, int minAnim, int maxAnim ); + +int BG_GetItemIndexByTag(int tag, int type); + +qboolean BG_IsItemSelectable(playerState_t *ps, int item); + +qboolean BG_HasYsalamiri(int gametype, playerState_t *ps); +qboolean BG_CanUseFPNow(int gametype, playerState_t *ps, int time, forcePowers_t power); + +void *BG_Alloc ( int size ); +void *BG_AllocUnaligned ( int size ); +void *BG_TempAlloc( int size ); +void BG_TempFree( int size ); +char *BG_StringAlloc ( const char *source ); +qboolean BG_OutOfMemory ( void ); + +void BG_BLADE_ActivateTrail ( bladeInfo_t *blade, float duration ); +void BG_BLADE_DeactivateTrail ( bladeInfo_t *blade, float duration ); +void BG_SI_Activate( saberInfo_t *saber ); +void BG_SI_Deactivate( saberInfo_t *saber ); +void BG_SI_BladeActivate( saberInfo_t *saber, int iBlade, qboolean bActive ); +qboolean BG_SI_Active(saberInfo_t *saber); +void BG_SI_SetLength( saberInfo_t *saber, float length ); +void BG_SI_SetDesiredLength(saberInfo_t *saber, float len, int bladeNum); +void BG_SI_SetLengthGradual( saberInfo_t *saber, int time ); +float BG_SI_Length(saberInfo_t *saber); +float BG_SI_LengthMax(saberInfo_t *saber); +void BG_SI_ActivateTrail ( saberInfo_t *saber, float duration ); +void BG_SI_DeactivateTrail ( saberInfo_t *saber, float duration ); +extern void BG_AttachToRancor( void *ghoul2,float rancYaw,vec3_t rancOrigin,int time,qhandle_t *modelList,vec3_t modelScale,qboolean inMouth,vec3_t out_origin,vec3_t out_angles,vec3_t out_axis[3] ); + +extern int WeaponReadyAnim[WP_NUM_WEAPONS]; +extern int WeaponAttackAnim[WP_NUM_WEAPONS]; + +extern int forcePowerDarkLight[NUM_FORCE_POWERS]; + +//#include "../namespace_end.h" + +#define ARENAS_PER_TIER 4 +#define MAX_ARENAS 1024 +#define MAX_ARENAS_TEXT 8192 + +#define MAX_BOTS 1024 +#define MAX_BOTS_TEXT 8192 + +#define HYPERSPACE_TIME 4000 //For hyperspace triggers +#define HYPERSPACE_TELEPORT_FRAC 0.75f +#define HYPERSPACE_SPEED 10000.0f//was 30000 +#define HYPERSPACE_TURN_RATE 45.0f + +#endif //__BG_PUBLIC_H__ diff --git a/code/game/bg_saber.c b/code/game/bg_saber.c new file mode 100644 index 0000000..87428ab --- /dev/null +++ b/code/game/bg_saber.c @@ -0,0 +1,4140 @@ +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" +#include "w_saber.h" + +#include "../namespace_begin.h" +extern qboolean BG_SabersOff( playerState_t *ps ); +saberInfo_t *BG_MySaber( int clientNum, int saberNum ); + +int PM_irand_timesync(int val1, int val2) +{ + int i; + + i = (val1-1) + (Q_random( &pm->cmd.serverTime )*(val2 - val1)) + 1; + if (i < val1) + { + i = val1; + } + if (i > val2) + { + i = val2; + } + + return i; +} + +void BG_ForcePowerDrain( playerState_t *ps, forcePowers_t forcePower, int overrideAmt ) +{ + //take away the power + int drain = overrideAmt; + + /* + if (ps->powerups[PW_FORCE_BOON]) + { + return; + } + */ + //No longer grant infinite force with boon. + + if ( !drain ) + { + drain = forcePowerNeeded[ps->fd.forcePowerLevel[forcePower]][forcePower]; + } + if ( !drain ) + { + return; + } + + if (forcePower == FP_LEVITATION) + { //special case + int jumpDrain = 0; + + if (ps->velocity[2] > 250) + { + jumpDrain = 20; + } + else if (ps->velocity[2] > 200) + { + jumpDrain = 16; + } + else if (ps->velocity[2] > 150) + { + jumpDrain = 12; + } + else if (ps->velocity[2] > 100) + { + jumpDrain = 8; + } + else if (ps->velocity[2] > 50) + { + jumpDrain = 6; + } + else if (ps->velocity[2] > 0) + { + jumpDrain = 4; + } + + if (jumpDrain) + { + if (ps->fd.forcePowerLevel[FP_LEVITATION]) + { //don't divide by 0! + jumpDrain /= ps->fd.forcePowerLevel[FP_LEVITATION]; + } + } + + ps->fd.forcePower -= jumpDrain; + if ( ps->fd.forcePower < 0 ) + { + ps->fd.forcePower = 0; + } + + return; + } + + ps->fd.forcePower -= drain; + if ( ps->fd.forcePower < 0 ) + { + ps->fd.forcePower = 0; + } +} + +qboolean BG_EnoughForcePowerForMove( int cost ) +{ + if ( pm->ps->fd.forcePower < cost ) + { + PM_AddEvent( EV_NOAMMO ); + return qfalse; + } + + return qtrue; +} + +// Silly, but I'm replacing these macros so they are shorter! +#define AFLAG_IDLE (SETANIM_FLAG_NORMAL) +#define AFLAG_ACTIVE (SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD | SETANIM_FLAG_HOLDLESS) +#define AFLAG_WAIT (SETANIM_FLAG_HOLD | SETANIM_FLAG_HOLDLESS) +#define AFLAG_FINISH (SETANIM_FLAG_HOLD) + +//FIXME: add the alternate anims for each style? +saberMoveData_t saberMoveData[LS_MOVE_MAX] = {// NB:randomized + // name anim(do all styles?)startQ endQ setanimflag blend, blocking chain_idle chain_attack trailLen + {"None", BOTH_STAND1, Q_R, Q_R, AFLAG_IDLE, 350, BLK_NO, LS_NONE, LS_NONE, 0 }, // LS_NONE = 0, + + // General movements with saber + {"Ready", BOTH_STAND2, Q_R, Q_R, AFLAG_IDLE, 350, BLK_WIDE, LS_READY, LS_S_R2L, 0 }, // LS_READY, + {"Draw", BOTH_STAND1TO2, Q_R, Q_R, AFLAG_FINISH, 350, BLK_NO, LS_READY, LS_S_R2L, 0 }, // LS_DRAW, + {"Putaway", BOTH_STAND2TO1, Q_R, Q_R, AFLAG_FINISH, 350, BLK_NO, LS_READY, LS_S_R2L, 0 }, // LS_PUTAWAY, + + // Attacks + //UL2LR + {"TL2BR Att", BOTH_A1_TL_BR, Q_TL, Q_BR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_TL2BR, LS_R_TL2BR, 200 }, // LS_A_TL2BR + //SLASH LEFT + {"L2R Att", BOTH_A1__L__R, Q_L, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_L2R, LS_R_L2R, 200 }, // LS_A_L2R + //LL2UR + {"BL2TR Att", BOTH_A1_BL_TR, Q_BL, Q_TR, AFLAG_ACTIVE, 50, BLK_TIGHT, LS_R_BL2TR, LS_R_BL2TR, 200 }, // LS_A_BL2TR + //LR2UL + {"BR2TL Att", BOTH_A1_BR_TL, Q_BR, Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_BR2TL, LS_R_BR2TL, 200 }, // LS_A_BR2TL + //SLASH RIGHT + {"R2L Att", BOTH_A1__R__L, Q_R, Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_R2L, LS_R_R2L, 200 },// LS_A_R2L + //UR2LL + {"TR2BL Att", BOTH_A1_TR_BL, Q_TR, Q_BL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_TR2BL, LS_R_TR2BL, 200 }, // LS_A_TR2BL + //SLASH DOWN + {"T2B Att", BOTH_A1_T__B_, Q_T, Q_B, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_R_T2B, LS_R_T2B, 200 }, // LS_A_T2B + //special attacks + {"Back Stab", BOTH_A2_STABBACK1, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_BACKSTAB + {"Back Att", BOTH_ATTACK_BACK, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_BACK + {"CR Back Att", BOTH_CROUCHATTACKBACK1,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_BACK_CR + {"RollStab", BOTH_ROLL_STAB, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_ROLL_STAB + {"Lunge Att", BOTH_LUNGE2_B__T_, Q_B, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_LUNGE + {"Jump Att", BOTH_FORCELEAP2_T__B_,Q_T, Q_B, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_A_JUMP_T__B_ + {"Flip Stab", BOTH_JUMPFLIPSTABDOWN,Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_T___R, 200 }, // LS_A_FLIP_STAB + {"Flip Slash", BOTH_JUMPFLIPSLASHDOWN1,Q_L,Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__R_T_, 200 }, // LS_A_FLIP_SLASH + {"DualJump Atk",BOTH_JUMPATTACK6, Q_R, Q_BL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_BL_TR, 200 }, // LS_JUMPATTACK_DUAL + + {"DualJumpAtkL_A",BOTH_ARIAL_LEFT, Q_R, Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_A_TL2BR, 200 }, // LS_JUMPATTACK_ARIAL_LEFT + {"DualJumpAtkR_A",BOTH_ARIAL_RIGHT, Q_R, Q_TR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_A_TR2BL, 200 }, // LS_JUMPATTACK_ARIAL_RIGHT + + {"DualJumpAtkL_A",BOTH_CARTWHEEL_LEFT, Q_R,Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_TL_BR, 200 }, // LS_JUMPATTACK_CART_LEFT + {"DualJumpAtkR_A",BOTH_CARTWHEEL_RIGHT, Q_R,Q_TR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_TR_BL, 200 }, // LS_JUMPATTACK_CART_RIGHT + + {"DualJumpAtkLStaff", BOTH_BUTTERFLY_FL1,Q_R,Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__L__R, 200 }, // LS_JUMPATTACK_STAFF_LEFT + {"DualJumpAtkRStaff", BOTH_BUTTERFLY_FR1,Q_R,Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__R__L, 200 }, // LS_JUMPATTACK_STAFF_RIGHT + + {"ButterflyLeft", BOTH_BUTTERFLY_LEFT,Q_R,Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__L__R, 200 }, // LS_BUTTERFLY_LEFT + {"ButterflyRight", BOTH_BUTTERFLY_RIGHT,Q_R,Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1__R__L, 200 }, // LS_BUTTERFLY_RIGHT + + {"BkFlip Atk", BOTH_JUMPATTACK7, Q_B, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_T1_T___R, 200 }, // LS_A_BACKFLIP_ATK + {"DualSpinAtk", BOTH_SPINATTACK6, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SPINATTACK_DUAL + {"StfSpinAtk", BOTH_SPINATTACK7, Q_L, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SPINATTACK + {"LngLeapAtk", BOTH_FORCELONGLEAP_ATTACK,Q_R,Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_LEAP_ATTACK + {"SwoopAtkR", BOTH_VS_ATR_S, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SWOOP_ATTACK_RIGHT + {"SwoopAtkL", BOTH_VS_ATL_S, Q_L, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SWOOP_ATTACK_LEFT + {"TauntaunAtkR",BOTH_VT_ATR_S, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_TAUNTAUN_ATTACK_RIGHT + {"TauntaunAtkL",BOTH_VT_ATL_S, Q_L, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_TAUNTAUN_ATTACK_LEFT + {"StfKickFwd", BOTH_A7_KICK_F, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_F + {"StfKickBack", BOTH_A7_KICK_B, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_B + {"StfKickRight",BOTH_A7_KICK_R, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_R + {"StfKickLeft", BOTH_A7_KICK_L, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_L + {"StfKickSpin", BOTH_A7_KICK_S, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_S_R2L, 200 }, // LS_KICK_S + {"StfKickBkFwd",BOTH_A7_KICK_BF, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_S_R2L, 200 }, // LS_KICK_BF + {"StfKickSplit",BOTH_A7_KICK_RL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_S_R2L, 200 }, // LS_KICK_RL + {"StfKickFwdAir",BOTH_A7_KICK_F_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_F_AIR + {"StfKickBackAir",BOTH_A7_KICK_B_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_B_AIR + {"StfKickRightAir",BOTH_A7_KICK_R_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_R_AIR + {"StfKickLeftAir",BOTH_A7_KICK_L_AIR,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_KICK_L_AIR + {"StabDown", BOTH_STABDOWN, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_STABDOWN + {"StabDownStf", BOTH_STABDOWN_STAFF,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_STABDOWN_STAFF + {"StabDownDual",BOTH_STABDOWN_DUAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_S_R2L, 200 }, // LS_STABDOWN_DUAL + {"dualspinprot",BOTH_A6_SABERPROTECT,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 500 }, // LS_DUAL_SPIN_PROTECT + {"StfSoulCal", BOTH_A7_SOULCAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 500 }, // LS_STAFF_SOULCAL + {"specialfast", BOTH_A1_SPECIAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 2000}, // LS_A1_SPECIAL + {"specialmed", BOTH_A2_SPECIAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 2000}, // LS_A2_SPECIAL + {"specialstr", BOTH_A3_SPECIAL, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 2000}, // LS_A3_SPECIAL + {"upsidedwnatk",BOTH_FLIP_ATTACK7, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200}, // LS_UPSIDE_DOWN_ATTACK + {"pullatkstab", BOTH_PULL_IMPALE_STAB,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200}, // LS_PULL_ATTACK_STAB + {"pullatkswing",BOTH_PULL_IMPALE_SWING,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200}, // LS_PULL_ATTACK_SWING + {"AloraSpinAtk",BOTH_ALORA_SPIN_SLASH,Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_SPINATTACK_ALORA + {"Dual FB Atk", BOTH_A6_FB, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_DUAL_FB + {"Dual LR Atk", BOTH_A6_LR, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_DUAL_LR + {"StfHiltBash", BOTH_A7_HILT, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_HILT_BASH + + //starts + {"TL2BR St", BOTH_S1_S1_TL, Q_R, Q_TL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_TL2BR, LS_A_TL2BR, 200 }, // LS_S_TL2BR + {"L2R St", BOTH_S1_S1__L, Q_R, Q_L, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_L2R, LS_A_L2R, 200 }, // LS_S_L2R + {"BL2TR St", BOTH_S1_S1_BL, Q_R, Q_BL, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_BL2TR, LS_A_BL2TR, 200 }, // LS_S_BL2TR + {"BR2TL St", BOTH_S1_S1_BR, Q_R, Q_BR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_BR2TL, LS_A_BR2TL, 200 }, // LS_S_BR2TL + {"R2L St", BOTH_S1_S1__R, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_R2L, LS_A_R2L, 200 }, // LS_S_R2L + {"TR2BL St", BOTH_S1_S1_TR, Q_R, Q_TR, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_TR2BL, LS_A_TR2BL, 200 }, // LS_S_TR2BL + {"T2B St", BOTH_S1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_TIGHT, LS_A_T2B, LS_A_T2B, 200 }, // LS_S_T2B + + //returns + {"TL2BR Ret", BOTH_R1_BR_S1, Q_BR, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_TL2BR + {"L2R Ret", BOTH_R1__R_S1, Q_R, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_L2R + {"BL2TR Ret", BOTH_R1_TR_S1, Q_TR, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_BL2TR + {"BR2TL Ret", BOTH_R1_TL_S1, Q_TL, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_BR2TL + {"R2L Ret", BOTH_R1__L_S1, Q_L, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_R2L + {"TR2BL Ret", BOTH_R1_BL_S1, Q_BL, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_TR2BL + {"T2B Ret", BOTH_R1_B__S1, Q_B, Q_R, AFLAG_FINISH, 100, BLK_TIGHT, LS_READY, LS_READY, 200 }, // LS_R_T2B + + //Transitions + {"BR2R Trans", BOTH_T1_BR__R, Q_BR, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc bottom right to right + {"BR2TR Trans", BOTH_T1_BR_TR, Q_BR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc bottom right to top right (use: BOTH_T1_TR_BR) + {"BR2T Trans", BOTH_T1_BR_T_, Q_BR, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc bottom right to top (use: BOTH_T1_T__BR) + {"BR2TL Trans", BOTH_T1_BR_TL, Q_BR, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast weak spin bottom right to top left + {"BR2L Trans", BOTH_T1_BR__L, Q_BR, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast weak spin bottom right to left + {"BR2BL Trans", BOTH_T1_BR_BL, Q_BR, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast weak spin bottom right to bottom left + {"R2BR Trans", BOTH_T1__R_BR, Q_R, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast arc right to bottom right (use: BOTH_T1_BR__R) + {"R2TR Trans", BOTH_T1__R_TR, Q_R, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc right to top right + {"R2T Trans", BOTH_T1__R_T_, Q_R, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast ar right to top (use: BOTH_T1_T___R) + {"R2TL Trans", BOTH_T1__R_TL, Q_R, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc right to top left + {"R2L Trans", BOTH_T1__R__L, Q_R, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast weak spin right to left + {"R2BL Trans", BOTH_T1__R_BL, Q_R, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast weak spin right to bottom left + {"TR2BR Trans", BOTH_T1_TR_BR, Q_TR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast arc top right to bottom right + {"TR2R Trans", BOTH_T1_TR__R, Q_TR, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc top right to right (use: BOTH_T1__R_TR) + {"TR2T Trans", BOTH_T1_TR_T_, Q_TR, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc top right to top (use: BOTH_T1_T__TR) + {"TR2TL Trans", BOTH_T1_TR_TL, Q_TR, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc top right to top left + {"TR2L Trans", BOTH_T1_TR__L, Q_TR, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc top right to left + {"TR2BL Trans", BOTH_T1_TR_BL, Q_TR, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast weak spin top right to bottom left + {"T2BR Trans", BOTH_T1_T__BR, Q_T, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast arc top to bottom right + {"T2R Trans", BOTH_T1_T___R, Q_T, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc top to right + {"T2TR Trans", BOTH_T1_T__TR, Q_T, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc top to top right + {"T2TL Trans", BOTH_T1_T__TL, Q_T, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc top to top left + {"T2L Trans", BOTH_T1_T___L, Q_T, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc top to left + {"T2BL Trans", BOTH_T1_T__BL, Q_T, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast arc top to bottom left + {"TL2BR Trans", BOTH_T1_TL_BR, Q_TL, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast weak spin top left to bottom right + {"TL2R Trans", BOTH_T1_TL__R, Q_TL, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast arc top left to right (use: BOTH_T1__R_TL) + {"TL2TR Trans", BOTH_T1_TL_TR, Q_TL, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc top left to top right (use: BOTH_T1_TR_TL) + {"TL2T Trans", BOTH_T1_TL_T_, Q_TL, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc top left to top (use: BOTH_T1_T__TL) + {"TL2L Trans", BOTH_T1_TL__L, Q_TL, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc top left to left (use: BOTH_T1__L_TL) + {"TL2BL Trans", BOTH_T1_TL_BL, Q_TL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast arc top left to bottom left + {"L2BR Trans", BOTH_T1__L_BR, Q_L, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast weak spin left to bottom right + {"L2R Trans", BOTH_T1__L__R, Q_L, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast weak spin left to right + {"L2TR Trans", BOTH_T1__L_TR, Q_L, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast arc left to top right (use: BOTH_T1_TR__L) + {"L2T Trans", BOTH_T1__L_T_, Q_L, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc left to top (use: BOTH_T1_T___L) + {"L2TL Trans", BOTH_T1__L_TL, Q_L, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc left to top left + {"L2BL Trans", BOTH_T1__L_BL, Q_L, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_A_BL2TR, 150 }, //# Fast arc left to bottom left (use: BOTH_T1_BL__L) + {"BL2BR Trans", BOTH_T1_BL_BR, Q_BL, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_A_BR2TL, 150 }, //# Fast weak spin bottom left to bottom right + {"BL2R Trans", BOTH_T1_BL__R, Q_BL, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_A_R2L, 150 }, //# Fast weak spin bottom left to right + {"BL2TR Trans", BOTH_T1_BL_TR, Q_BL, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_TR2BL, 150 }, //# Fast weak spin bottom left to top right + {"BL2T Trans", BOTH_T1_BL_T_, Q_BL, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_A_T2B, 150 }, //# Fast arc bottom left to top (use: BOTH_T1_T__BL) + {"BL2TL Trans", BOTH_T1_BL_TL, Q_BL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_A_TL2BR, 150 }, //# Fast arc bottom left to top left (use: BOTH_T1_TL_BL) + {"BL2L Trans", BOTH_T1_BL__L, Q_BL, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_A_L2R, 150 }, //# Fast arc bottom left to left + + //Bounces + {"Bounce BR", BOTH_B1_BR___, Q_BR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_T1_BR_TR, 150 }, + {"Bounce R", BOTH_B1__R___, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_T1__R__L, 150 }, + {"Bounce TR", BOTH_B1_TR___, Q_TR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_TR_TL, 150 }, + {"Bounce T", BOTH_B1_T____, Q_T, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_T__BL, 150 }, + {"Bounce TL", BOTH_B1_TL___, Q_TL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_T1_TL_TR, 150 }, + {"Bounce L", BOTH_B1__L___, Q_L, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_T1__L__R, 150 }, + {"Bounce BL", BOTH_B1_BL___, Q_BL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_T1_BL_TR, 150 }, + + //Deflected attacks (like bounces, but slide off enemy saber, not straight back) + {"Deflect BR", BOTH_D1_BR___, Q_BR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TL2BR, LS_T1_BR_TR, 150 }, + {"Deflect R", BOTH_D1__R___, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_R_L2R, LS_T1__R__L, 150 }, + {"Deflect TR", BOTH_D1_TR___, Q_TR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_TR_TL, 150 }, + {"Deflect T", BOTH_B1_T____, Q_T, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_T__BL, 150 }, + {"Deflect TL", BOTH_D1_TL___, Q_TL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BR2TL, LS_T1_TL_TR, 150 }, + {"Deflect L", BOTH_D1__L___, Q_L, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_R_R2L, LS_T1__L__R, 150 }, + {"Deflect BL", BOTH_D1_BL___, Q_BL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_R_TR2BL, LS_T1_BL_TR, 150 }, + {"Deflect B", BOTH_D1_B____, Q_B, Q_B, AFLAG_ACTIVE, 100, BLK_NO, LS_R_BL2TR, LS_T1_T__BL, 150 }, + + //Reflected attacks + {"Reflected BR",BOTH_V1_BR_S1, Q_BR, Q_BR, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_BR + {"Reflected R", BOTH_V1__R_S1, Q_R, Q_R, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1__R + {"Reflected TR",BOTH_V1_TR_S1, Q_TR, Q_TR, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_TR + {"Reflected T", BOTH_V1_T__S1, Q_T, Q_T, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_T_ + {"Reflected TL",BOTH_V1_TL_S1, Q_TL, Q_TL, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_TL + {"Reflected L", BOTH_V1__L_S1, Q_L, Q_L, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1__L + {"Reflected BL",BOTH_V1_BL_S1, Q_BL, Q_BL, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_BL + {"Reflected B", BOTH_V1_B__S1, Q_B, Q_B, AFLAG_ACTIVE, 100, BLK_NO, LS_READY, LS_READY, 150 },// LS_V1_B_ + + // Broken parries + {"BParry Top", BOTH_H1_S1_T_, Q_T, Q_B, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_UP, + {"BParry UR", BOTH_H1_S1_TR, Q_TR, Q_BL, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_UR, + {"BParry UL", BOTH_H1_S1_TL, Q_TL, Q_BR, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_UL, + {"BParry LR", BOTH_H1_S1_BL, Q_BL, Q_TR, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_LR, + {"BParry Bot", BOTH_H1_S1_B_, Q_B, Q_T, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_LL + {"BParry LL", BOTH_H1_S1_BR, Q_BR, Q_TL, AFLAG_ACTIVE, 50, BLK_NO, LS_READY, LS_READY, 150 }, // LS_PARRY_LL + + // Knockaways + {"Knock Top", BOTH_K1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_T1_T__BR, 150 }, // LS_PARRY_UP, + {"Knock UR", BOTH_K1_S1_TR, Q_R, Q_TR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_T1_TR__R, 150 }, // LS_PARRY_UR, + {"Knock UL", BOTH_K1_S1_TL, Q_R, Q_TL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BR2TL, LS_T1_TL__L, 150 }, // LS_PARRY_UL, + {"Knock LR", BOTH_K1_S1_BL, Q_R, Q_BL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TL2BR, LS_T1_BL_TL, 150 }, // LS_PARRY_LR, + {"Knock LL", BOTH_K1_S1_BR, Q_R, Q_BR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TR2BL, LS_T1_BR_TR, 150 }, // LS_PARRY_LL + + // Parry + {"Parry Top", BOTH_P1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_T2B, 150 }, // LS_PARRY_UP, + {"Parry UR", BOTH_P1_S1_TR, Q_R, Q_TL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_TR2BL, 150 }, // LS_PARRY_UR, + {"Parry UL", BOTH_P1_S1_TL, Q_R, Q_TR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BR2TL, LS_A_TL2BR, 150 }, // LS_PARRY_UL, + {"Parry LR", BOTH_P1_S1_BL, Q_R, Q_BR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TL2BR, LS_A_BR2TL, 150 }, // LS_PARRY_LR, + {"Parry LL", BOTH_P1_S1_BR, Q_R, Q_BL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TR2BL, LS_A_BL2TR, 150 }, // LS_PARRY_LL + + // Reflecting a missile + {"Reflect Top", BOTH_P1_S1_T_, Q_R, Q_T, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_T2B, 300 }, // LS_PARRY_UP, + {"Reflect UR", BOTH_P1_S1_TL, Q_R, Q_TR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BR2TL, LS_A_TL2BR, 300 }, // LS_PARRY_UR, + {"Reflect UL", BOTH_P1_S1_TR, Q_R, Q_TL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_BL2TR, LS_A_TR2BL, 300 }, // LS_PARRY_UL, + {"Reflect LR", BOTH_P1_S1_BR, Q_R, Q_BL, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TR2BL, LS_A_BL2TR, 300 }, // LS_PARRY_LR + {"Reflect LL", BOTH_P1_S1_BL, Q_R, Q_BR, AFLAG_ACTIVE, 50, BLK_WIDE, LS_R_TL2BR, LS_A_BR2TL, 300 }, // LS_PARRY_LL, +}; + + +int transitionMove[Q_NUM_QUADS][Q_NUM_QUADS] = +{ + LS_NONE, //Can't transition to same pos! + LS_T1_BR__R,//40 + LS_T1_BR_TR, + LS_T1_BR_T_, + LS_T1_BR_TL, + LS_T1_BR__L, + LS_T1_BR_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1__R_BR,//46 + LS_NONE, //Can't transition to same pos! + LS_T1__R_TR, + LS_T1__R_T_, + LS_T1__R_TL, + LS_T1__R__L, + LS_T1__R_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_TR_BR,//52 + LS_T1_TR__R, + LS_NONE, //Can't transition to same pos! + LS_T1_TR_T_, + LS_T1_TR_TL, + LS_T1_TR__L, + LS_T1_TR_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_T__BR,//58 + LS_T1_T___R, + LS_T1_T__TR, + LS_NONE, //Can't transition to same pos! + LS_T1_T__TL, + LS_T1_T___L, + LS_T1_T__BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_TL_BR,//64 + LS_T1_TL__R, + LS_T1_TL_TR, + LS_T1_TL_T_, + LS_NONE, //Can't transition to same pos! + LS_T1_TL__L, + LS_T1_TL_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1__L_BR,//70 + LS_T1__L__R, + LS_T1__L_TR, + LS_T1__L_T_, + LS_T1__L_TL, + LS_NONE, //Can't transition to same pos! + LS_T1__L_BL, + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_BL_BR,//76 + LS_T1_BL__R, + LS_T1_BL_TR, + LS_T1_BL_T_, + LS_T1_BL_TL, + LS_T1_BL__L, + LS_NONE, //Can't transition to same pos! + LS_NONE, //No transitions to bottom, and no anims start there, so shouldn't need any + LS_T1_BL_BR,//NOTE: there are no transitions from bottom, so re-use the bottom right transitions + LS_T1_BR__R, + LS_T1_BR_TR, + LS_T1_BR_T_, + LS_T1_BR_TL, + LS_T1_BR__L, + LS_T1_BR_BL, + LS_NONE //No transitions to bottom, and no anims start there, so shouldn't need any +}; + +saberMoveName_t PM_AttackMoveForQuad( int quad ) +{ + switch ( quad ) + { + case Q_B: + case Q_BR: + return LS_A_BR2TL; + break; + case Q_R: + return LS_A_R2L; + break; + case Q_TR: + return LS_A_TR2BL; + break; + case Q_T: + return LS_A_T2B; + break; + case Q_TL: + return LS_A_TL2BR; + break; + case Q_L: + return LS_A_L2R; + break; + case Q_BL: + return LS_A_BL2TR; + break; + } + return LS_NONE; +} + +qboolean PM_SaberKataDone(int curmove, int newmove); + +int PM_SaberAnimTransitionAnim( int curmove, int newmove ) +{ + int retmove = newmove; + if ( curmove == LS_READY ) + {//just standing there + switch ( newmove ) + { + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + //transition is the start + retmove = LS_S_TL2BR + (newmove-LS_A_TL2BR); + break; + } + } + else + { + switch ( newmove ) + { + //transitioning to ready pose + case LS_READY: + switch ( curmove ) + { + //transitioning from an attack + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + //transition is the return + retmove = LS_R_TL2BR + (newmove-LS_A_TL2BR); + break; + } + break; + //transitioning to an attack + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + if ( newmove == curmove ) + { + //going into an attack + if ( PM_SaberKataDone( curmove, newmove ) ) + {//done with this kata, must return to ready before attack again + retmove = LS_R_TL2BR + (newmove-LS_A_TL2BR); + } + else + {//okay to chain to another attack + retmove = transitionMove[saberMoveData[curmove].endQuad][saberMoveData[newmove].startQuad]; + } + } + else if ( saberMoveData[curmove].endQuad == saberMoveData[newmove].startQuad ) + {//new move starts from same quadrant + retmove = newmove; + } + else + { + switch ( curmove ) + { + //transitioning from an attack + case LS_A_TL2BR: + case LS_A_L2R: + case LS_A_BL2TR: + case LS_A_BR2TL: + case LS_A_R2L: + case LS_A_TR2BL: + case LS_A_T2B: + case LS_D1_BR: + case LS_D1__R: + case LS_D1_TR: + case LS_D1_T_: + case LS_D1_TL: + case LS_D1__L: + case LS_D1_BL: + case LS_D1_B_: + retmove = transitionMove[saberMoveData[curmove].endQuad][saberMoveData[newmove].startQuad]; + break; + //transitioning from a return + case LS_R_TL2BR: + case LS_R_L2R: + case LS_R_BL2TR: + case LS_R_BR2TL: + case LS_R_R2L: + case LS_R_TR2BL: + case LS_R_T2B: + //transitioning from a bounce + /* + case LS_BOUNCE_UL2LL: + case LS_BOUNCE_LL2UL: + case LS_BOUNCE_L2LL: + case LS_BOUNCE_L2UL: + case LS_BOUNCE_UR2LR: + case LS_BOUNCE_LR2UR: + case LS_BOUNCE_R2LR: + case LS_BOUNCE_R2UR: + case LS_BOUNCE_TOP: + case LS_OVER_UR2UL: + case LS_OVER_UL2UR: + case LS_BOUNCE_UR: + case LS_BOUNCE_UL: + case LS_BOUNCE_LR: + case LS_BOUNCE_LL: + */ + //transitioning from a parry/reflection/knockaway/broken parry + case LS_PARRY_UP: + case LS_PARRY_UR: + case LS_PARRY_UL: + case LS_PARRY_LR: + case LS_PARRY_LL: + case LS_REFLECT_UP: + case LS_REFLECT_UR: + case LS_REFLECT_UL: + case LS_REFLECT_LR: + case LS_REFLECT_LL: + case LS_K1_T_: + case LS_K1_TR: + case LS_K1_TL: + case LS_K1_BR: + case LS_K1_BL: + case LS_V1_BR: + case LS_V1__R: + case LS_V1_TR: + case LS_V1_T_: + case LS_V1_TL: + case LS_V1__L: + case LS_V1_BL: + case LS_V1_B_: + case LS_H1_T_: + case LS_H1_TR: + case LS_H1_TL: + case LS_H1_BR: + case LS_H1_BL: + retmove = transitionMove[saberMoveData[curmove].endQuad][saberMoveData[newmove].startQuad]; + break; + //NB: transitioning from transitions is fine + } + } + break; + //transitioning to any other anim is not supported + } + } + + if ( retmove == LS_NONE ) + { + return newmove; + } + + return retmove; +} + +extern qboolean BG_InKnockDown( int anim ); +saberMoveName_t PM_CheckStabDown( void ) +{ + vec3_t faceFwd, facingAngles; + vec3_t fwd; + bgEntity_t *ent = NULL; + trace_t tr; + //yeah, vm's may complain, but.. who cares! + vec3_t trmins = {-15, -15, -15}; + vec3_t trmaxs = {15, 15, 15}; + + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber1 + && (saber1->saberFlags&SFL_NO_STABDOWN) ) + { + return LS_NONE; + } + if ( saber2 + && (saber2->saberFlags&SFL_NO_STABDOWN) ) + { + return LS_NONE; + } + + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//sorry must be on ground! + return LS_NONE; + } + if ( pm->ps->clientNum < MAX_CLIENTS ) + {//player + pm->ps->velocity[2] = 0; + pm->cmd.upmove = 0; + } + + VectorSet(facingAngles, 0, pm->ps->viewangles[YAW], 0); + AngleVectors( facingAngles, faceFwd, NULL, NULL ); + + //FIXME: need to only move forward until we bump into our target...? + VectorMA(pm->ps->origin, 164.0f, faceFwd, fwd); + + pm->trace(&tr, pm->ps->origin, trmins, trmaxs, fwd, pm->ps->clientNum, MASK_PLAYERSOLID); + + if (tr.entityNum < ENTITYNUM_WORLD) + { + ent = PM_BGEntForNum(tr.entityNum); + } + + if ( ent && + (ent->s.eType == ET_PLAYER || ent->s.eType == ET_NPC) && + BG_InKnockDown( ent->s.legsAnim ) ) + {//guy is on the ground below me, do a top-down attack + if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + return LS_STABDOWN_DUAL; + } + else if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + return LS_STABDOWN_STAFF; + } + else + { + return LS_STABDOWN; + } + } + return LS_NONE; +} + +int PM_SaberMoveQuadrantForMovement( usercmd_t *ucmd ) +{ + if ( ucmd->rightmove > 0 ) + {//moving right + if ( ucmd->forwardmove > 0 ) + {//forward right = TL2BR slash + return Q_TL; + } + else if ( ucmd->forwardmove < 0 ) + {//backward right = BL2TR uppercut + return Q_BL; + } + else + {//just right is a left slice + return Q_L; + } + } + else if ( ucmd->rightmove < 0 ) + {//moving left + if ( ucmd->forwardmove > 0 ) + {//forward left = TR2BL slash + return Q_TR; + } + else if ( ucmd->forwardmove < 0 ) + {//backward left = BR2TL uppercut + return Q_BR; + } + else + {//just left is a right slice + return Q_R; + } + } + else + {//not moving left or right + if ( ucmd->forwardmove > 0 ) + {//forward= T2B slash + return Q_T; + } + else if ( ucmd->forwardmove < 0 ) + {//backward= T2B slash //or B2T uppercut? + return Q_T; + } + else + {//Not moving at all + return Q_R; + } + } +} + +//=================================================================== +qboolean PM_SaberInBounce( int move ) +{ + if ( move >= LS_B1_BR && move <= LS_B1_BL ) + { + return qtrue; + } + if ( move >= LS_D1_BR && move <= LS_D1_BL ) + { + return qtrue; + } + return qfalse; +} + +qboolean PM_SaberInTransition( int move ); + +int saberMoveTransitionAngle[Q_NUM_QUADS][Q_NUM_QUADS] = +{ + 0,//Q_BR,Q_BR, + 45,//Q_BR,Q_R, + 90,//Q_BR,Q_TR, + 135,//Q_BR,Q_T, + 180,//Q_BR,Q_TL, + 215,//Q_BR,Q_L, + 270,//Q_BR,Q_BL, + 45,//Q_BR,Q_B, + 45,//Q_R,Q_BR, + 0,//Q_R,Q_R, + 45,//Q_R,Q_TR, + 90,//Q_R,Q_T, + 135,//Q_R,Q_TL, + 180,//Q_R,Q_L, + 215,//Q_R,Q_BL, + 90,//Q_R,Q_B, + 90,//Q_TR,Q_BR, + 45,//Q_TR,Q_R, + 0,//Q_TR,Q_TR, + 45,//Q_TR,Q_T, + 90,//Q_TR,Q_TL, + 135,//Q_TR,Q_L, + 180,//Q_TR,Q_BL, + 135,//Q_TR,Q_B, + 135,//Q_T,Q_BR, + 90,//Q_T,Q_R, + 45,//Q_T,Q_TR, + 0,//Q_T,Q_T, + 45,//Q_T,Q_TL, + 90,//Q_T,Q_L, + 135,//Q_T,Q_BL, + 180,//Q_T,Q_B, + 180,//Q_TL,Q_BR, + 135,//Q_TL,Q_R, + 90,//Q_TL,Q_TR, + 45,//Q_TL,Q_T, + 0,//Q_TL,Q_TL, + 45,//Q_TL,Q_L, + 90,//Q_TL,Q_BL, + 135,//Q_TL,Q_B, + 215,//Q_L,Q_BR, + 180,//Q_L,Q_R, + 135,//Q_L,Q_TR, + 90,//Q_L,Q_T, + 45,//Q_L,Q_TL, + 0,//Q_L,Q_L, + 45,//Q_L,Q_BL, + 90,//Q_L,Q_B, + 270,//Q_BL,Q_BR, + 215,//Q_BL,Q_R, + 180,//Q_BL,Q_TR, + 135,//Q_BL,Q_T, + 90,//Q_BL,Q_TL, + 45,//Q_BL,Q_L, + 0,//Q_BL,Q_BL, + 45,//Q_BL,Q_B, + 45,//Q_B,Q_BR, + 90,//Q_B,Q_R, + 135,//Q_B,Q_TR, + 180,//Q_B,Q_T, + 135,//Q_B,Q_TL, + 90,//Q_B,Q_L, + 45,//Q_B,Q_BL, + 0//Q_B,Q_B, +}; + +int PM_SaberAttackChainAngle( int move1, int move2 ) +{ + if ( move1 == -1 || move2 == -1 ) + { + return -1; + } + return saberMoveTransitionAngle[saberMoveData[move1].endQuad][saberMoveData[move2].startQuad]; +} + +qboolean PM_SaberKataDone(int curmove, int newmove) +{ + if (pm->ps->m_iVehicleNum) + { //never continue kata on vehicle + if (pm->ps->saberAttackChainCount > 0) + { + return qtrue; + } + } + + if ( pm->ps->fd.saberAnimLevel == SS_DESANN || pm->ps->fd.saberAnimLevel == SS_TAVION ) + {//desann and tavion can link up as many attacks as they want + return qfalse; + } + + if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + //TEMP: for now, let staff attacks infinitely chain + return qfalse; + } + else if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + //TEMP: for now, let staff attacks infinitely chain + return qfalse; + } + else if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_3 ) + { + if ( curmove == LS_NONE || newmove == LS_NONE ) + { + if ( pm->ps->fd.saberAnimLevel >= FORCE_LEVEL_3 && pm->ps->saberAttackChainCount > PM_irand_timesync( 0, 1 ) ) + { + return qtrue; + } + } + else if ( pm->ps->saberAttackChainCount > PM_irand_timesync( 2, 3 ) ) + { + return qtrue; + } + else if ( pm->ps->saberAttackChainCount > 0 ) + { + int chainAngle = PM_SaberAttackChainAngle( curmove, newmove ); + if ( chainAngle < 135 || chainAngle > 215 ) + {//if trying to chain to a move that doesn't continue the momentum + return qtrue; + } + else if ( chainAngle == 180 ) + {//continues the momentum perfectly, allow it to chain 66% of the time + if ( pm->ps->saberAttackChainCount > 1 ) + { + return qtrue; + } + } + else + {//would continue the movement somewhat, 50% chance of continuing + if ( pm->ps->saberAttackChainCount > 2 ) + { + return qtrue; + } + } + } + } + else + {//Perhaps have chainAngle influence fast and medium chains as well? For now, just do level 3. + if (newmove == LS_A_TL2BR || + newmove == LS_A_L2R || + newmove == LS_A_BL2TR || + newmove == LS_A_BR2TL || + newmove == LS_A_R2L || + newmove == LS_A_TR2BL ) + { //lower chaining tolerance for spinning saber anims + int chainTolerance; + + if (pm->ps->fd.saberAnimLevel == FORCE_LEVEL_1) + { + chainTolerance = 5; + } + else + { + chainTolerance = 3; + } + + if (pm->ps->saberAttackChainCount >= chainTolerance && PM_irand_timesync(1, pm->ps->saberAttackChainCount) > chainTolerance) + { + return qtrue; + } + } + if ( pm->ps->fd.saberAnimLevel == FORCE_LEVEL_2 && pm->ps->saberAttackChainCount > PM_irand_timesync( 2, 5 ) ) + { + return qtrue; + } + } + return qfalse; +} + +void PM_SetAnimFrame( playerState_t *gent, int frame, qboolean torso, qboolean legs ) +{ + gent->saberLockFrame = frame; +} + +int PM_SaberLockWinAnim( qboolean victory, qboolean superBreak ) +{ + int winAnim = -1; + switch ( pm->ps->torsoAnim ) + { +/* + default: +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR-PM_SaberLockBreak: %s not in saberlock anim, anim = (%d)%s\n", pm->gent->NPC_type, pm->ps->torsoAnim, animTable[pm->ps->torsoAnim].name ); +#endif +*/ + case BOTH_BF2LOCK: + if ( superBreak ) + { + winAnim = BOTH_LK_S_S_T_SB_1_W; + } + else if ( !victory ) + { + winAnim = BOTH_BF1BREAK; + } + else + { + pm->ps->saberMove = LS_A_T2B; + winAnim = BOTH_A3_T__B_; + } + break; + case BOTH_BF1LOCK: + if ( superBreak ) + { + winAnim = BOTH_LK_S_S_T_SB_1_W; + } + else if ( !victory ) + { + winAnim = BOTH_KNOCKDOWN4; + } + else + { + pm->ps->saberMove = LS_K1_T_; + winAnim = BOTH_K1_S1_T_; + } + break; + case BOTH_CWCIRCLELOCK: + if ( superBreak ) + { + winAnim = BOTH_LK_S_S_S_SB_1_W; + } + else if ( !victory ) + { + pm->ps->saberMove = LS_V1_BL;//pm->ps->saberBounceMove = + pm->ps->saberBlocked = BLOCKED_PARRY_BROKEN; + winAnim = BOTH_V1_BL_S1; + } + else + { + winAnim = BOTH_CWCIRCLEBREAK; + } + break; + case BOTH_CCWCIRCLELOCK: + if ( superBreak ) + { + winAnim = BOTH_LK_S_S_S_SB_1_W; + } + else if ( !victory ) + { + pm->ps->saberMove = LS_V1_BR;//pm->ps->saberBounceMove = + pm->ps->saberBlocked = BLOCKED_PARRY_BROKEN; + winAnim = BOTH_V1_BR_S1; + } + else + { + winAnim = BOTH_CCWCIRCLEBREAK; + } + break; + default: + //must be using new system: + break; + } + if ( winAnim != -1 ) + { + PM_SetAnim( SETANIM_BOTH, winAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + pm->ps->weaponTime = pm->ps->torsoTimer; + pm->ps->saberBlocked = BLOCKED_NONE; + pm->ps->weaponstate = WEAPON_FIRING; + /* + if ( superBreak + && winAnim != BOTH_LK_ST_DL_T_SB_1_W ) + {//going to attack with saber, do a saber trail + pm->ps->SaberActivateTrail( 200 ); + } + */ + } + return winAnim; +} + +// Need to avoid nesting namespaces! +#include "../namespace_end.h" +#ifdef QAGAME //including game headers on cgame is FORBIDDEN ^_^ + +#include "g_local.h" +extern void NPC_SetAnim(gentity_t *ent, int setAnimParts, int anim, int setAnimFlags); +extern gentity_t g_entities[]; + +#elif defined CGAME + +#include "..\cgame\cg_local.h" //ahahahahhahahaha@$!$! + +#endif +#include "../namespace_begin.h" + +int PM_SaberLockLoseAnim( playerState_t *genemy, qboolean victory, qboolean superBreak ) +{ + int loseAnim = -1; + switch ( genemy->torsoAnim ) + { +/* + default: +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR-PM_SaberLockBreak: %s not in saberlock anim, anim = (%d)%s\n", genemy->NPC_type, genemy->client->ps.torsoAnim, animTable[genemy->client->ps.torsoAnim].name ); +#endif +*/ + case BOTH_BF2LOCK: + if ( superBreak ) + { + loseAnim = BOTH_LK_S_S_T_SB_1_L; + } + else if ( !victory ) + { + loseAnim = BOTH_BF1BREAK; + } + else + { + if ( !victory ) + {//no-one won + genemy->saberMove = LS_K1_T_; + loseAnim = BOTH_K1_S1_T_; + } + else + {//FIXME: this anim needs to transition back to ready when done + loseAnim = BOTH_BF1BREAK; + } + } + break; + case BOTH_BF1LOCK: + if ( superBreak ) + { + loseAnim = BOTH_LK_S_S_T_SB_1_L; + } + else if ( !victory ) + { + loseAnim = BOTH_KNOCKDOWN4; + } + else + { + if ( !victory ) + {//no-one won + genemy->saberMove = LS_A_T2B; + loseAnim = BOTH_A3_T__B_; + } + else + { + loseAnim = BOTH_KNOCKDOWN4; + } + } + break; + case BOTH_CWCIRCLELOCK: + if ( superBreak ) + { + loseAnim = BOTH_LK_S_S_S_SB_1_L; + } + else if ( !victory ) + { + genemy->saberMove = LS_V1_BL;//genemy->saberBounceMove = + genemy->saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BL_S1; + } + else + { + if ( !victory ) + {//no-one won + loseAnim = BOTH_CCWCIRCLEBREAK; + } + else + { + genemy->saberMove = LS_V1_BL;//genemy->saberBounceMove = + genemy->saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BL_S1; + /* + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_H1_BR; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_H1_S1_BL; + */ + } + } + break; + case BOTH_CCWCIRCLELOCK: + if ( superBreak ) + { + loseAnim = BOTH_LK_S_S_S_SB_1_L; + } + else if ( !victory ) + { + genemy->saberMove = LS_V1_BR;//genemy->saberBounceMove = + genemy->saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BR_S1; + } + else + { + if ( !victory ) + {//no-one won + loseAnim = BOTH_CWCIRCLEBREAK; + } + else + { + genemy->saberMove = LS_V1_BR;//genemy->saberBounceMove = + genemy->saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_V1_BR_S1; + /* + genemy->client->ps.saberMove = genemy->client->ps.saberBounceMove = LS_H1_BL; + genemy->client->ps.saberBlocked = BLOCKED_PARRY_BROKEN; + loseAnim = BOTH_H1_S1_BR; + */ + } + } + break; + } + if ( loseAnim != -1 ) + { +#ifdef QAGAME + NPC_SetAnim( &g_entities[genemy->clientNum], SETANIM_BOTH, loseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + genemy->weaponTime = genemy->torsoTimer;// + 250; +#endif + genemy->saberBlocked = BLOCKED_NONE; + genemy->weaponstate = WEAPON_READY; + } + return loseAnim; +} + +int PM_SaberLockResultAnim( playerState_t *duelist, qboolean superBreak, qboolean won ) +{ + int baseAnim = duelist->torsoAnim; + switch ( baseAnim ) + { + case BOTH_LK_S_S_S_L_2: //lock if I'm using single vs. a single and other intitiated + baseAnim = BOTH_LK_S_S_S_L_1; + break; + case BOTH_LK_S_S_T_L_2: //lock if I'm using single vs. a single and other initiated + baseAnim = BOTH_LK_S_S_T_L_1; + break; + case BOTH_LK_DL_DL_S_L_2: //lock if I'm using dual vs. dual and other initiated + baseAnim = BOTH_LK_DL_DL_S_L_1; + break; + case BOTH_LK_DL_DL_T_L_2: //lock if I'm using dual vs. dual and other initiated + baseAnim = BOTH_LK_DL_DL_T_L_1; + break; + case BOTH_LK_ST_ST_S_L_2: //lock if I'm using staff vs. a staff and other initiated + baseAnim = BOTH_LK_ST_ST_S_L_1; + break; + case BOTH_LK_ST_ST_T_L_2: //lock if I'm using staff vs. a staff and other initiated + baseAnim = BOTH_LK_ST_ST_T_L_1; + break; + } + //what kind of break? + if ( !superBreak ) + { + baseAnim -= 2; + } + else if ( superBreak ) + { + baseAnim += 1; + } + else + {//WTF? Not a valid result + return -1; + } + //win or lose? + if ( won ) + { + baseAnim += 1; + } + + //play the anim and hold it +#ifdef QAGAME + //server-side: set it on the other guy, too + if ( duelist->clientNum == pm->ps->clientNum ) + {//me + PM_SetAnim( SETANIM_BOTH, baseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); + } + else + {//other guy + NPC_SetAnim( &g_entities[duelist->clientNum], SETANIM_BOTH, baseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } +#else + PM_SetAnim( SETANIM_BOTH, baseAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0 ); +#endif + + if ( superBreak + && !won ) + {//if you lose a superbreak, you're defenseless + /* + //Taken care of in SetSaberBoxSize() + //make saberent not block + gentity_t *saberent = &g_entities[duelist->client->ps.saberEntityNum]; + if ( saberent ) + { + VectorClear(saberent->mins); + VectorClear(saberent->maxs); + G_SetOrigin(saberent, duelist->currentOrigin); + } + */ +#ifdef QAGAME + if ( 1 ) +#else + if ( duelist->clientNum == pm->ps->clientNum ) +#endif + { + //set sabermove to none + duelist->saberMove = LS_NONE; + //Hold the anim a little longer than it is + duelist->torsoTimer += 250; + } + } + +#ifdef QAGAME + if ( 1 ) +#else + if ( duelist->clientNum == pm->ps->clientNum ) +#endif + { + //no attacking during this anim + duelist->weaponTime = duelist->torsoTimer; + duelist->saberBlocked = BLOCKED_NONE; + /* + if ( superBreak + && won + && baseAnim != BOTH_LK_ST_DL_T_SB_1_W ) + {//going to attack with saber, do a saber trail + duelist->client->ps.SaberActivateTrail( 200 ); + } + */ + } + return baseAnim; +} + +void PM_SaberLockBreak( playerState_t *genemy, qboolean victory, int strength ) +{ + int winAnim = BOTH_STAND1, loseAnim = BOTH_STAND1; + //qboolean punishLoser = qfalse; + qboolean noKnockdown = qfalse; + qboolean singleVsSingle = qtrue; + qboolean superBreak = (strength+pm->ps->saberLockHits > Q_irand(2,4)); + + winAnim = PM_SaberLockWinAnim( victory, superBreak ); + if ( winAnim != -1 ) + {//a single vs. single break + loseAnim = PM_SaberLockLoseAnim( genemy, victory, superBreak ); + } + else + {//must be a saberlock that's not between single and single... + singleVsSingle = qfalse; + winAnim = PM_SaberLockResultAnim( pm->ps, superBreak, qtrue ); + pm->ps->weaponstate = WEAPON_FIRING; + loseAnim = PM_SaberLockResultAnim( genemy, superBreak, qfalse ); + genemy->weaponstate = WEAPON_READY; + } + + if ( victory ) + { //someone lost the lock, so punish them by knocking them down + if ( pm->ps->saberLockHits && !superBreak ) + {//there was some over-power in the win, but not enough to superbreak + vec3_t oppDir; + + int strength = 8; + + VectorSubtract(genemy->origin, pm->ps->origin, oppDir); + VectorNormalize(oppDir); + + if (noKnockdown) + { + if (!genemy->saberEntityNum) + { //if he has already lost his saber then just knock him down + noKnockdown = qfalse; + } + } + + if (!noKnockdown && BG_KnockDownable(genemy)) + { + genemy->forceHandExtend = HANDEXTEND_KNOCKDOWN; + genemy->forceHandExtendTime = pm->cmd.serverTime + 1100; + genemy->forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim + + genemy->otherKiller = pm->ps->clientNum; + genemy->otherKillerTime = pm->cmd.serverTime + 5000; + genemy->otherKillerDebounceTime = pm->cmd.serverTime + 100; + + genemy->velocity[0] = oppDir[0]*(strength*40); + genemy->velocity[1] = oppDir[1]*(strength*40); + genemy->velocity[2] = 100; + } + + pm->checkDuelLoss = genemy->clientNum+1; + + pm->ps->saberEventFlags |= SEF_LOCK_WON; + } + } + else + { //If no one lost, then shove each player away from the other + vec3_t oppDir; + + int strength = 4; + + VectorSubtract(genemy->origin, pm->ps->origin, oppDir); + VectorNormalize(oppDir); + genemy->velocity[0] = oppDir[0]*(strength*40); + genemy->velocity[1] = oppDir[1]*(strength*40); + genemy->velocity[2] = 150; + + VectorSubtract(pm->ps->origin, genemy->origin, oppDir); + VectorNormalize(oppDir); + pm->ps->velocity[0] = oppDir[0]*(strength*40); + pm->ps->velocity[1] = oppDir[1]*(strength*40); + pm->ps->velocity[2] = 150; + + genemy->forceHandExtend = HANDEXTEND_WEAPONREADY; + } + + pm->ps->weaponTime = 0; + genemy->weaponTime = 0; + + pm->ps->saberLockTime = genemy->saberLockTime = 0; + pm->ps->saberLockFrame = genemy->saberLockFrame = 0; + pm->ps->saberLockEnemy = genemy->saberLockEnemy = 0; + + pm->ps->forceHandExtend = HANDEXTEND_WEAPONREADY; + + PM_AddEvent( EV_JUMP ); + if ( !victory ) + {//no-one won + BG_AddPredictableEventToPlayerstate(EV_JUMP, 0, genemy); + } + else + { + if ( PM_irand_timesync( 0, 1 ) ) + { + BG_AddPredictableEventToPlayerstate(EV_JUMP, PM_irand_timesync( 0, 75 ), genemy); + } + } +} + +qboolean BG_CheckIncrementLockAnim( int anim, int winOrLose ) +{ + qboolean increment = qfalse;//??? + //RULE: if you are the first style in the lock anim, you advance from LOSING position to WINNING position + // if you are the second style in the lock anim, you advance from WINNING position to LOSING position + switch ( anim ) + { + //increment to win: + case BOTH_LK_DL_DL_S_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_DL_S_L_2: //lock if I'm using dual vs. dual and other initiated + case BOTH_LK_DL_DL_T_L_1: //lock if I'm using dual vs. dual and I initiated + case BOTH_LK_DL_DL_T_L_2: //lock if I'm using dual vs. dual and other initiated + case BOTH_LK_DL_S_S_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_DL_S_T_L_1: //lock if I'm using dual vs. a single + case BOTH_LK_DL_ST_S_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_DL_ST_T_L_1: //lock if I'm using dual vs. a staff + case BOTH_LK_S_S_S_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_S_S_T_L_2: //lock if I'm using single vs. a single and other initiated + case BOTH_LK_ST_S_S_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_ST_S_T_L_1: //lock if I'm using staff vs. a single + case BOTH_LK_ST_ST_T_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_ST_T_L_2: //lock if I'm using staff vs. a staff and other initiated + if ( winOrLose == SABERLOCK_WIN ) + { + increment = qtrue; + } + else + { + increment = qfalse; + } + break; + + //decrement to win: + case BOTH_LK_S_DL_S_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_DL_T_L_1: //lock if I'm using single vs. a dual + case BOTH_LK_S_S_S_L_2: //lock if I'm using single vs. a single and other intitiated + case BOTH_LK_S_S_T_L_1: //lock if I'm using single vs. a single and I initiated + case BOTH_LK_S_ST_S_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_S_ST_T_L_1: //lock if I'm using single vs. a staff + case BOTH_LK_ST_DL_S_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_DL_T_L_1: //lock if I'm using staff vs. dual + case BOTH_LK_ST_ST_S_L_1: //lock if I'm using staff vs. a staff and I initiated + case BOTH_LK_ST_ST_S_L_2: //lock if I'm using staff vs. a staff and other initiated + if ( winOrLose == SABERLOCK_WIN ) + { + increment = qfalse; + } + else + { + increment = qtrue; + } + break; + default: + break; + } + return increment; +} + +extern qboolean ValidAnimFileIndex ( int index ); +void PM_SaberLocked( void ) +{ + int remaining = 0; + playerState_t *genemy; + bgEntity_t *eGenemy = PM_BGEntForNum(pm->ps->saberLockEnemy); + + if (!eGenemy) + { + return; + } + + genemy = eGenemy->playerState; + + if ( !genemy ) + { + return; + } + /*if ( ( (pm->ps->torsoAnim) == BOTH_BF2LOCK || + (pm->ps->torsoAnim) == BOTH_BF1LOCK || + (pm->ps->torsoAnim) == BOTH_CWCIRCLELOCK || + (pm->ps->torsoAnim) == BOTH_CCWCIRCLELOCK ) + && ( (genemy->torsoAnim) == BOTH_BF2LOCK || + (genemy->torsoAnim) == BOTH_BF1LOCK || + (genemy->torsoAnim) == BOTH_CWCIRCLELOCK || + (genemy->torsoAnim) == BOTH_CCWCIRCLELOCK ) + ) + */ //yeah.. + if (pm->ps->saberLockFrame && + genemy->saberLockFrame && + BG_InSaberLock(pm->ps->torsoAnim) && + BG_InSaberLock(genemy->torsoAnim)) + { + float dist = 0; + + pm->ps->torsoTimer = 0; + pm->ps->weaponTime = 0; + genemy->torsoTimer = 0; + genemy->weaponTime = 0; + + dist = DistanceSquared(pm->ps->origin,genemy->origin); + if ( dist < 64 || dist > 6400 ) + {//between 8 and 80 from each other + PM_SaberLockBreak( genemy, qfalse, 0 ); + return; + } + /* + //NOTE: time-out is handled around where PM_SaberLocked is called + if ( pm->ps->saberLockTime <= pm->cmd.serverTime + 500 ) + {//lock just ended + PM_SaberLockBreak( genemy, qfalse, 0 ); + return; + } + */ + if ( pm->ps->saberLockAdvance ) + {//holding attack + animation_t *anim; + float currentFrame; + int curFrame; + int strength = 1; + + pm->ps->saberLockAdvance = qfalse; + + anim = &pm->animations[pm->ps->torsoAnim]; + + currentFrame = pm->ps->saberLockFrame; + + strength = pm->ps->fd.forcePowerLevel[FP_SABER_OFFENSE]+1; + + //advance/decrement my frame number + if ( BG_InSaberLockOld( pm->ps->torsoAnim ) ) + { //old locks + if ( (pm->ps->torsoAnim) == BOTH_CCWCIRCLELOCK || + (pm->ps->torsoAnim) == BOTH_BF2LOCK ) + { + curFrame = floor( currentFrame )-strength; + //drop my frame one + if ( curFrame <= anim->firstFrame ) + {//I won! Break out + PM_SaberLockBreak( genemy, qtrue, strength ); + return; + } + else + { + PM_SetAnimFrame( pm->ps, curFrame, qtrue, qtrue ); + remaining = curFrame-anim->firstFrame; + } + } + else + { + curFrame = ceil( currentFrame )+strength; + //advance my frame one + if ( curFrame >= anim->firstFrame+anim->numFrames ) + {//I won! Break out + PM_SaberLockBreak( genemy, qtrue, strength ); + return; + } + else + { + PM_SetAnimFrame( pm->ps, curFrame, qtrue, qtrue ); + remaining = anim->firstFrame+anim->numFrames-curFrame; + } + } + } + else + { //new locks + if ( BG_CheckIncrementLockAnim( pm->ps->torsoAnim, SABERLOCK_WIN ) ) + { + curFrame = ceil( currentFrame )+strength; + //advance my frame one + if ( curFrame >= anim->firstFrame+anim->numFrames ) + {//I won! Break out + PM_SaberLockBreak( genemy, qtrue, strength ); + return; + } + else + { + PM_SetAnimFrame( pm->ps, curFrame, qtrue, qtrue ); + remaining = anim->firstFrame+anim->numFrames-curFrame; + } + } + else + { + curFrame = floor( currentFrame )-strength; + //drop my frame one + if ( curFrame <= anim->firstFrame ) + {//I won! Break out + PM_SaberLockBreak( genemy, qtrue, strength ); + return; + } + else + { + PM_SetAnimFrame( pm->ps, curFrame, qtrue, qtrue ); + remaining = curFrame-anim->firstFrame; + } + } + } + if ( !PM_irand_timesync( 0, 2 ) ) + { + PM_AddEvent( EV_JUMP ); + } + //advance/decrement enemy frame number + anim = &pm->animations[(genemy->torsoAnim)]; + + if ( BG_InSaberLockOld( genemy->torsoAnim ) ) + { + if ( (genemy->torsoAnim) == BOTH_CWCIRCLELOCK || + (genemy->torsoAnim) == BOTH_BF1LOCK ) + { + if ( !PM_irand_timesync( 0, 2 ) ) + { + BG_AddPredictableEventToPlayerstate(EV_PAIN, floor((float)80/100*100.0f), genemy); + } + PM_SetAnimFrame( genemy, anim->firstFrame+remaining, qtrue, qtrue ); + } + else + { + PM_SetAnimFrame( genemy, anim->firstFrame+anim->numFrames-remaining, qtrue, qtrue ); + } + } + else + {//new locks + if ( BG_CheckIncrementLockAnim( genemy->torsoAnim, SABERLOCK_LOSE ) ) + { + if ( !PM_irand_timesync( 0, 2 ) ) + { + BG_AddPredictableEventToPlayerstate(EV_PAIN, floor((float)80/100*100.0f), genemy); + } + PM_SetAnimFrame( genemy, anim->firstFrame+anim->numFrames-remaining, qtrue, qtrue ); + } + else + { + PM_SetAnimFrame( genemy, anim->firstFrame+remaining, qtrue, qtrue ); + } + } + } + } + else + {//something broke us out of it + PM_SaberLockBreak( genemy, qfalse, 0 ); + } +} + +qboolean PM_SaberInBrokenParry( int move ) +{ + if ( move >= LS_V1_BR && move <= LS_V1_B_ ) + { + return qtrue; + } + if ( move >= LS_H1_T_ && move <= LS_H1_BL ) + { + return qtrue; + } + return qfalse; +} + + +int PM_BrokenParryForParry( int move ) +{ + switch ( move ) + { + case LS_PARRY_UP: + return LS_H1_T_; + break; + case LS_PARRY_UR: + return LS_H1_TR; + break; + case LS_PARRY_UL: + return LS_H1_TL; + break; + case LS_PARRY_LR: + return LS_H1_BL; + break; + case LS_PARRY_LL: + return LS_H1_BR; + break; + case LS_READY: + return LS_H1_B_; + break; + } + return LS_NONE; +} + +#define BACK_STAB_DISTANCE 128 + +qboolean PM_CanBackstab(void) +{ + trace_t tr; + vec3_t flatAng; + vec3_t fwd, back; + vec3_t trmins = {-15, -15, -8}; + vec3_t trmaxs = {15, 15, 8}; + + VectorCopy(pm->ps->viewangles, flatAng); + flatAng[PITCH] = 0; + + AngleVectors(flatAng, fwd, 0, 0); + + back[0] = pm->ps->origin[0] - fwd[0]*BACK_STAB_DISTANCE; + back[1] = pm->ps->origin[1] - fwd[1]*BACK_STAB_DISTANCE; + back[2] = pm->ps->origin[2] - fwd[2]*BACK_STAB_DISTANCE; + + pm->trace(&tr, pm->ps->origin, trmins, trmaxs, back, pm->ps->clientNum, MASK_PLAYERSOLID); + + if (tr.fraction != 1.0 && tr.entityNum >= 0 && tr.entityNum < ENTITYNUM_NONE) + { + bgEntity_t *bgEnt = PM_BGEntForNum(tr.entityNum); + + if (bgEnt && (bgEnt->s.eType == ET_PLAYER || bgEnt->s.eType == ET_NPC)) + { + return qtrue; + } + } + + return qfalse; +} + +saberMoveName_t PM_SaberFlipOverAttackMove(void) +{ + vec3_t fwdAngles, jumpFwd; +// float zDiff = 0; +// playerState_t *psData; +// bgEntity_t *bgEnt; + + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + //see if we have an overridden (or cancelled) lunge move + if ( saber1 + && saber1->jumpAtkFwdMove != LS_INVALID ) + { + if ( saber1->jumpAtkFwdMove != LS_NONE ) + { + return (saberMoveName_t)saber1->jumpAtkFwdMove; + } + } + if ( saber2 + && saber2->jumpAtkFwdMove != LS_INVALID ) + { + if ( saber2->jumpAtkFwdMove != LS_NONE ) + { + return (saberMoveName_t)saber2->jumpAtkFwdMove; + } + } + //no overrides, cancelled? + if ( saber1 + && saber1->jumpAtkFwdMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + if ( saber2 + && saber2->jumpAtkFwdMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + //just do it + VectorCopy( pm->ps->viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + AngleVectors( fwdAngles, jumpFwd, NULL, NULL ); + VectorScale( jumpFwd, 150, pm->ps->velocity );//was 50 + pm->ps->velocity[2] = 400; + + /* + bgEnt = PM_BGEntForNum(tr->entityNum); + + if (!bgEnt) + { + return LS_A_FLIP_STAB; + } + + psData = bgEnt->playerState; + + //go higher for enemies higher than you, lower for those lower than you + if (psData) + { + zDiff = psData->origin[2] - pm->ps->origin[2]; + } + else + { + zDiff = 0; + } + pm->ps->velocity[2] += (zDiff)*1.5f; + + //clamp to decent-looking values + if ( zDiff <= 0 && pm->ps->velocity[2] < 200 ) + {//if we're on same level, don't let me jump so low, I clip into the ground + pm->ps->velocity[2] = 200; + } + else if ( pm->ps->velocity[2] < 100 ) + { + pm->ps->velocity[2] = 100; + } + else if ( pm->ps->velocity[2] > 400 ) + { + pm->ps->velocity[2] = 400; + } + */ + + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + + PM_AddEvent( EV_JUMP ); + pm->ps->fd.forceJumpSound = 1; + pm->cmd.upmove = 0; + + /* + if ( PM_irand_timesync( 0, 1 ) ) + { + return LS_A_FLIP_STAB; + } + else + */ + { + return LS_A_FLIP_SLASH; + } +} + +int PM_SaberBackflipAttackMove( void ) +{ + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + //see if we have an overridden (or cancelled) lunge move + if ( saber1 + && saber1->jumpAtkBackMove != LS_INVALID ) + { + if ( saber1->jumpAtkBackMove != LS_NONE ) + { + return (saberMoveName_t)saber1->jumpAtkBackMove; + } + } + if ( saber2 + && saber2->jumpAtkBackMove != LS_INVALID ) + { + if ( saber2->jumpAtkBackMove != LS_NONE ) + { + return (saberMoveName_t)saber2->jumpAtkBackMove; + } + } + //no overrides, cancelled? + if ( saber1 + && saber1->jumpAtkBackMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + if ( saber2 + && saber2->jumpAtkBackMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + //just do it + pm->cmd.upmove = 127; + pm->ps->velocity[2] = 500; + return LS_A_BACKFLIP_ATK; +} + +int PM_SaberDualJumpAttackMove( void ) +{ + //FIXME: to make this move easier to execute, should be allowed to do it + // after you've already started your jump... but jump is delayed in + // this anim, so how do we undo the jump? + pm->cmd.upmove = 0;//no jump just yet + return LS_JUMPATTACK_DUAL; +} + +#define FLIPHACK_DISTANCE 200 + +qboolean PM_SomeoneInFront(trace_t *tr) +{ //Also a very simplified version of the sp counterpart + vec3_t flatAng; + vec3_t fwd, back; + vec3_t trmins = {-15, -15, -8}; + vec3_t trmaxs = {15, 15, 8}; + + VectorCopy(pm->ps->viewangles, flatAng); + flatAng[PITCH] = 0; + + AngleVectors(flatAng, fwd, 0, 0); + + back[0] = pm->ps->origin[0] + fwd[0]*FLIPHACK_DISTANCE; + back[1] = pm->ps->origin[1] + fwd[1]*FLIPHACK_DISTANCE; + back[2] = pm->ps->origin[2] + fwd[2]*FLIPHACK_DISTANCE; + + pm->trace(tr, pm->ps->origin, trmins, trmaxs, back, pm->ps->clientNum, MASK_PLAYERSOLID); + + if (tr->fraction != 1.0 && tr->entityNum >= 0 && tr->entityNum < ENTITYNUM_NONE) + { + bgEntity_t *bgEnt = PM_BGEntForNum(tr->entityNum); + + if (bgEnt && (bgEnt->s.eType == ET_PLAYER || bgEnt->s.eType == ET_NPC)) + { + return qtrue; + } + } + + return qfalse; +} + +saberMoveName_t PM_SaberLungeAttackMove( qboolean noSpecials ) +{ + vec3_t fwdAngles, jumpFwd; + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + //see if we have an overridden (or cancelled) lunge move + if ( saber1 + && saber1->lungeAtkMove != LS_INVALID ) + { + if ( saber1->lungeAtkMove != LS_NONE ) + { + return (saberMoveName_t)saber1->lungeAtkMove; + } + } + if ( saber2 + && saber2->lungeAtkMove != LS_INVALID ) + { + if ( saber2->lungeAtkMove != LS_NONE ) + { + return (saberMoveName_t)saber2->lungeAtkMove; + } + } + //no overrides, cancelled? + if ( saber1 + && saber1->lungeAtkMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + if ( saber2 + && saber2->lungeAtkMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + //just do it + if (pm->ps->fd.saberAnimLevel == SS_FAST) + { + VectorCopy( pm->ps->viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + //do the lunge + AngleVectors( fwdAngles, jumpFwd, NULL, NULL ); + VectorScale( jumpFwd, 150, pm->ps->velocity ); + PM_AddEvent( EV_JUMP ); + + return LS_A_LUNGE; + } + else if ( !noSpecials && pm->ps->fd.saberAnimLevel == SS_STAFF) + { + return LS_SPINATTACK; + } + else if ( !noSpecials ) + { + return LS_SPINATTACK_DUAL; + } + return LS_A_T2B; +} + +saberMoveName_t PM_SaberJumpAttackMove2( void ) +{ + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + //see if we have an overridden (or cancelled) lunge move + if ( saber1 + && saber1->jumpAtkFwdMove != LS_INVALID ) + { + if ( saber1->jumpAtkFwdMove != LS_NONE ) + { + return (saberMoveName_t)saber1->jumpAtkFwdMove; + } + } + if ( saber2 + && saber2->jumpAtkFwdMove != LS_INVALID ) + { + if ( saber2->jumpAtkFwdMove != LS_NONE ) + { + return (saberMoveName_t)saber2->jumpAtkFwdMove; + } + } + //no overrides, cancelled? + if ( saber1 + && saber1->jumpAtkFwdMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + if ( saber2 + && saber2->jumpAtkFwdMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + //just do it + if (pm->ps->fd.saberAnimLevel == SS_DUAL) + { + return PM_SaberDualJumpAttackMove(); + } + else + { + //rwwFIXMEFIXME I don't like randomness for this sort of thing, gives people reason to + //complain combat is unpredictable. Maybe do something more clever to determine + //if we should do a left or right? + /* + if (PM_irand_timesync(0, 1)) + { + newmove = LS_JUMPATTACK_STAFF_LEFT; + } + else + */ + { + return LS_JUMPATTACK_STAFF_RIGHT; + } + } + return LS_A_T2B; +} + +saberMoveName_t PM_SaberJumpAttackMove( void ) +{ + vec3_t fwdAngles, jumpFwd; + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + //see if we have an overridden (or cancelled) lunge move + if ( saber1 + && saber1->jumpAtkFwdMove != LS_INVALID ) + { + if ( saber1->jumpAtkFwdMove != LS_NONE ) + { + return (saberMoveName_t)saber1->jumpAtkFwdMove; + } + } + if ( saber2 + && saber2->jumpAtkFwdMove != LS_INVALID ) + { + if ( saber2->jumpAtkFwdMove != LS_NONE ) + { + return (saberMoveName_t)saber2->jumpAtkFwdMove; + } + } + //no overrides, cancelled? + if ( saber1 + && saber1->jumpAtkFwdMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + if ( saber2 + && saber2->jumpAtkFwdMove == LS_NONE ) + { + return LS_A_T2B;//LS_NONE; + } + //just do it + VectorCopy( pm->ps->viewangles, fwdAngles ); + fwdAngles[PITCH] = fwdAngles[ROLL] = 0; + AngleVectors( fwdAngles, jumpFwd, NULL, NULL ); + VectorScale( jumpFwd, 300, pm->ps->velocity ); + pm->ps->velocity[2] = 280; + PM_SetForceJumpZStart(pm->ps->origin[2]);//so we don't take damage if we land at same height + + PM_AddEvent( EV_JUMP ); + pm->ps->fd.forceJumpSound = 1; + pm->cmd.upmove = 0; + + return LS_A_JUMP_T__B_; +} + +float PM_GroundDistance(void) +{ + trace_t tr; + vec3_t down; + + VectorCopy(pm->ps->origin, down); + + down[2] -= 4096; + + pm->trace(&tr, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, MASK_SOLID); + + VectorSubtract(pm->ps->origin, tr.endpos, down); + + return VectorLength(down); +} + +float PM_WalkableGroundDistance(void) +{ + trace_t tr; + vec3_t down; + + VectorCopy(pm->ps->origin, down); + + down[2] -= 4096; + + pm->trace(&tr, pm->ps->origin, pm->mins, pm->maxs, down, pm->ps->clientNum, MASK_SOLID); + + if ( tr.plane.normal[2] < MIN_WALK_NORMAL ) + {//can't stand on this plane + return 4096; + } + + VectorSubtract(pm->ps->origin, tr.endpos, down); + + return VectorLength(down); +} + +qboolean BG_SaberInTransitionAny( int move ); +static qboolean PM_CanDoDualDoubleAttacks(void) +{ + if ( pm->ps->weapon == WP_SABER ) + { + saberInfo_t *saber = BG_MySaber( pm->ps->clientNum, 0 ); + if ( saber + && (saber->saberFlags&SFL_NO_MIRROR_ATTACKS) ) + { + return qfalse; + } + saber = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber + && (saber->saberFlags&SFL_NO_MIRROR_ATTACKS) ) + { + return qfalse; + } + } + if (BG_SaberInSpecialAttack(pm->ps->torsoAnim) || + BG_SaberInSpecialAttack(pm->ps->legsAnim)) + { + return qfalse; + } + return qtrue; +} + +static qboolean PM_CheckEnemyPresence( int dir, float radius ) +{ //anyone in this dir? + vec3_t angles; + vec3_t checkDir; + vec3_t tTo; + vec3_t tMins, tMaxs; + trace_t tr; + const float tSize = 12.0f; + //sp uses a bbox ent list check, but.. that's not so easy/fast to + //do in predicted code. So I'll just do a single box trace in the proper direction, + //and take whatever is first hit. + + VectorSet(tMins, -tSize, -tSize, -tSize); + VectorSet(tMaxs, tSize, tSize, tSize); + + VectorCopy(pm->ps->viewangles, angles); + angles[PITCH] = 0.0f; + + switch( dir ) + { + case DIR_RIGHT: + AngleVectors( angles, NULL, checkDir, NULL ); + break; + case DIR_LEFT: + AngleVectors( angles, NULL, checkDir, NULL ); + VectorScale( checkDir, -1, checkDir ); + break; + case DIR_FRONT: + AngleVectors( angles, checkDir, NULL, NULL ); + break; + case DIR_BACK: + AngleVectors( angles, checkDir, NULL, NULL ); + VectorScale( checkDir, -1, checkDir ); + break; + } + + VectorMA(pm->ps->origin, radius, checkDir, tTo); + pm->trace(&tr, pm->ps->origin, tMins, tMaxs, tTo, pm->ps->clientNum, MASK_PLAYERSOLID); + + if (tr.fraction != 1.0f && tr.entityNum < ENTITYNUM_WORLD) + { //let's see who we hit + bgEntity_t *bgEnt = PM_BGEntForNum(tr.entityNum); + + if (bgEnt && + (bgEnt->s.eType == ET_PLAYER || bgEnt->s.eType == ET_NPC)) + { //this guy can be considered an "enemy"... if he is on the same team, oh well. can't bg-check that (without a whole lot of hassle). + return qtrue; + } + } + + //no one in the trace + return qfalse; +} + +#define SABER_ALT_ATTACK_POWER 50//75? +#define SABER_ALT_ATTACK_POWER_LR 10//30? +#define SABER_ALT_ATTACK_POWER_FB 25//30/50? + +extern qboolean PM_SaberInReturn( int move ); //bg_panimate.c +saberMoveName_t PM_CheckPullAttack( void ) +{ +#if 0 //disabling these for MP, they aren't useful + if (!(pm->cmd.buttons & BUTTON_ATTACK)) + { + return LS_NONE; + } + + if ( (pm->ps->saberMove == LS_READY||PM_SaberInReturn(pm->ps->saberMove)||PM_SaberInReflect(pm->ps->saberMove))//ready + //&& (pm->ps->clientNum < MAX_CLIENTS||PM_ControlledByPlayer())//PLAYER ONLY + && pm->ps->fd.saberAnimLevel >= SS_FAST//single saber styles - FIXME: Tavion? + && pm->ps->fd.saberAnimLevel <= SS_STRONG//single saber styles - FIXME: Tavion? + //&& G_TryingPullAttack( pm->gent, &pm->cmd, qfalse ) + //&& pm->ps->fd.forcePowerLevel[FP_PULL] + //rwwFIXMEFIXME: rick has the damn msg.cpp file checked out exclusively so I can't update the bloody psf to send this for prediction + && pm->ps->powerups[PW_DISINT_4] > pm->cmd.serverTime + && !(pm->ps->fd.forcePowersActive & (1<ps->powerups[PW_PULL] > pm->cmd.serverTime + //&& pm->cmd.forwardmove<0//pulling back + && (pm->cmd.buttons&BUTTON_ATTACK)//attacking + && BG_EnoughForcePowerForMove( SABER_ALT_ATTACK_POWER_FB ) )//pm->ps->forcePower >= SABER_ALT_ATTACK_POWER_FB//have enough power + {//FIXME: some NPC logic to do this? + qboolean doMove = qtrue; +// if ( g_saberNewControlScheme->integer +// || g_crosshairEntNum < ENTITYNUM_WORLD )//in old control scheme, there has to be someone there + { + saberMoveName_t pullAttackMove = LS_NONE; + if ( pm->ps->fd.saberAnimLevel == SS_FAST ) + { + pullAttackMove = LS_PULL_ATTACK_STAB; + } + else + { + pullAttackMove = LS_PULL_ATTACK_SWING; + } + + /* + if ( g_crosshairEntNum < ENTITYNUM_WORLD + && pm->gent && pm->gent->client ) + { + gentity_t *targEnt = &g_entities[g_crosshairEntNum]; + if ( targEnt->client + && targEnt->health > 0 + //FIXME: check other things like in knockdown, saberlock, uninterruptable anims, etc. + && !PM_InOnGroundAnim( &targEnt->client->ps ) + && !PM_LockedAnim( targEnt->client->ps.legsAnim ) + && !PM_SuperBreakLoseAnim( targEnt->client->ps.legsAnim ) + && !PM_SuperBreakWinAnim( targEnt->client->ps.legsAnim ) + && targEnt->client->ps.saberLockTime <= 0 + && WP_ForceThrowable( targEnt, targEnt, pm->gent, qtrue, 1.0f, 0.0f, NULL ) ) + { + if ( !g_saberNewControlScheme->integer ) + {//in old control scheme, make sure they're close or far enough away for the move we'll be doing + float targDist = Distance( targEnt->currentOrigin, pm->ps->origin ); + if ( pullAttackMove == LS_PULL_ATTACK_STAB ) + {//must be closer than 512 + if ( targDist > 384.0f ) + { + return LS_NONE; + } + } + else//if ( pullAttackMove == LS_PULL_ATTACK_SWING ) + {//must be farther than 256 + if ( targDist > 512.0f ) + { + return LS_NONE; + } + if ( targDist < 192.0f ) + { + return LS_NONE; + } + } + } + + vec3_t targAngles = {0,targEnt->client->ps.viewangles[YAW],0}; + if ( InFront( pm->ps->origin, targEnt->currentOrigin, targAngles ) ) + { + NPC_SetAnim( targEnt, SETANIM_BOTH, BOTH_PULLED_INAIR_F, SETANIM_FLAG_OVERRIDE, SETANIM_FLAG_HOLD ); + } + else + { + NPC_SetAnim( targEnt, SETANIM_BOTH, BOTH_PULLED_INAIR_B, SETANIM_FLAG_OVERRIDE, SETANIM_FLAG_HOLD ); + } + //hold the anim until I'm with done pull anim + targEnt->client->ps.legsAnimTimer = targEnt->client->ps.torsoAnimTimer = PM_AnimLength( pm->gent->client->clientInfo.animFileIndex, (animNumber_t)saberMoveData[pullAttackMove].animToUse ); + //set pullAttackTime + pm->gent->client->ps.pullAttackTime = targEnt->client->ps.pullAttackTime = level.time+targEnt->client->ps.legsAnimTimer; + //make us know about each other + pm->gent->client->ps.pullAttackEntNum = g_crosshairEntNum; + targEnt->client->ps.pullAttackEntNum = pm->ps->clientNum; + //do effect and sound on me + pm->ps->powerups[PW_FORCE_PUSH] = level.time + 1000; + if ( pm->gent ) + { + G_Sound( pm->gent, G_SoundIndex( "sound/weapons/force/pull.wav" ) ); + } + doMove = qtrue; + } + } + */ + if ( doMove ) + { + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB ); + return pullAttackMove; + } + } + } +#endif + return LS_NONE; +} + +qboolean PM_InSecondaryStyle( void ) +{ + if ( pm->ps->fd.saberAnimLevelBase == SS_STAFF + || pm->ps->fd.saberAnimLevelBase == SS_DUAL ) + { + if ( pm->ps->fd.saberAnimLevel != pm->ps->fd.saberAnimLevelBase ) + { + return qtrue; + } + } + return qfalse; +} + +saberMoveName_t PM_SaberAttackForMovement(saberMoveName_t curmove) +{ + saberMoveName_t newmove = LS_NONE; + qboolean noSpecials = PM_InSecondaryStyle(); + qboolean allowCartwheels = qtrue; + saberMoveName_t overrideJumpRightAttackMove = LS_INVALID; + saberMoveName_t overrideJumpLeftAttackMove = LS_INVALID; + + if ( pm->ps->weapon == WP_SABER ) + { + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + + if ( saber1 + && saber1->jumpAtkRightMove != LS_INVALID ) + { + if ( saber1->jumpAtkRightMove != LS_NONE ) + {//actually overriding + overrideJumpRightAttackMove = (saberMoveName_t)saber1->jumpAtkRightMove; + } + else if ( saber2 + && saber2->jumpAtkRightMove > LS_NONE ) + {//would be cancelling it, but check the second saber, too + overrideJumpRightAttackMove = (saberMoveName_t)saber2->jumpAtkRightMove; + } + else + {//nope, just cancel it + overrideJumpRightAttackMove = LS_NONE; + } + } + else if ( saber2 + && saber2->jumpAtkRightMove != LS_INVALID ) + {//first saber not overridden, check second + overrideJumpRightAttackMove = (saberMoveName_t)saber2->jumpAtkRightMove; + } + + if ( saber1 + && saber1->jumpAtkLeftMove != LS_INVALID ) + { + if ( saber1->jumpAtkLeftMove != LS_NONE ) + {//actually overriding + overrideJumpLeftAttackMove = (saberMoveName_t)saber1->jumpAtkLeftMove; + } + else if ( saber2 + && saber2->jumpAtkLeftMove > LS_NONE ) + {//would be cancelling it, but check the second saber, too + overrideJumpLeftAttackMove = (saberMoveName_t)saber2->jumpAtkLeftMove; + } + else + {//nope, just cancel it + overrideJumpLeftAttackMove = LS_NONE; + } + } + else if ( saber2 + && saber2->jumpAtkLeftMove != LS_INVALID ) + {//first saber not overridden, check second + overrideJumpLeftAttackMove = (saberMoveName_t)saber1->jumpAtkLeftMove; + } + + if ( saber1 + && (saber1->saberFlags&SFL_NO_CARTWHEELS) ) + { + allowCartwheels = qfalse; + } + if ( saber2 + && (saber2->saberFlags&SFL_NO_CARTWHEELS) ) + { + allowCartwheels = qfalse; + } + } + + if ( pm->cmd.rightmove > 0 ) + {//moving right + if ( !noSpecials + && overrideJumpRightAttackMove != LS_NONE + && pm->ps->velocity[2] > 20.0f //pm->ps->groundEntityNum != ENTITYNUM_NONE//on ground + && (pm->cmd.buttons&BUTTON_ATTACK)//hitting attack + && PM_GroundDistance() < 70.0f //not too high above ground + && ( pm->cmd.upmove > 0 || (pm->ps->pm_flags & PMF_JUMP_HELD) )//focus-holding player + && BG_EnoughForcePowerForMove( SABER_ALT_ATTACK_POWER_LR ) )//have enough power + {//cartwheel right + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_LR); + if ( overrideJumpRightAttackMove != LS_INVALID ) + {//overridden with another move + return overrideJumpRightAttackMove; + } + else + { + vec3_t right, fwdAngles; + + VectorSet(fwdAngles, 0.0f, pm->ps->viewangles[YAW], 0.0f); + + AngleVectors( fwdAngles, NULL, right, NULL ); + pm->ps->velocity[0] = pm->ps->velocity[1] = 0.0f; + VectorMA( pm->ps->velocity, 190.0f, right, pm->ps->velocity ); + if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + newmove = LS_BUTTERFLY_RIGHT; + pm->ps->velocity[2] = 350.0f; + } + else if ( allowCartwheels ) + { + //PM_SetJumped( JUMP_VELOCITY, qtrue ); + PM_AddEvent( EV_JUMP ); + pm->ps->velocity[2] = 300.0f; + + //if ( !Q_irand( 0, 1 ) ) + //if (PM_GroundDistance() >= 25.0f) + if (1) + { + newmove = LS_JUMPATTACK_ARIAL_RIGHT; + } + else + { + newmove = LS_JUMPATTACK_CART_RIGHT; + } + } + } + } + else if ( pm->cmd.forwardmove > 0 ) + {//forward right = TL2BR slash + newmove = LS_A_TL2BR; + } + else if ( pm->cmd.forwardmove < 0 ) + {//backward right = BL2TR uppercut + newmove = LS_A_BL2TR; + } + else + {//just right is a left slice + newmove = LS_A_L2R; + } + } + else if ( pm->cmd.rightmove < 0 ) + {//moving left + if ( !noSpecials + && overrideJumpLeftAttackMove != LS_NONE + && pm->ps->velocity[2] > 20.0f //pm->ps->groundEntityNum != ENTITYNUM_NONE//on ground + && (pm->cmd.buttons&BUTTON_ATTACK)//hitting attack + && PM_GroundDistance() < 70.0f //not too high above ground + && ( pm->cmd.upmove > 0 || (pm->ps->pm_flags & PMF_JUMP_HELD) )//focus-holding player + && BG_EnoughForcePowerForMove( SABER_ALT_ATTACK_POWER_LR ) )//have enough power + {//cartwheel left + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_LR); + + if ( overrideJumpLeftAttackMove != LS_INVALID ) + {//overridden with another move + return overrideJumpLeftAttackMove; + } + else + { + vec3_t right, fwdAngles; + + VectorSet(fwdAngles, 0.0f, pm->ps->viewangles[YAW], 0.0f); + AngleVectors( fwdAngles, NULL, right, NULL ); + pm->ps->velocity[0] = pm->ps->velocity[1] = 0.0f; + VectorMA( pm->ps->velocity, -190.0f, right, pm->ps->velocity ); + if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + newmove = LS_BUTTERFLY_LEFT; + pm->ps->velocity[2] = 250.0f; + } + else if ( allowCartwheels ) + { + //PM_SetJumped( JUMP_VELOCITY, qtrue ); + PM_AddEvent( EV_JUMP ); + pm->ps->velocity[2] = 350.0f; + + //if ( !Q_irand( 0, 1 ) ) + //if (PM_GroundDistance() >= 25.0f) + if (1) + { + newmove = LS_JUMPATTACK_ARIAL_LEFT; + } + else + { + newmove = LS_JUMPATTACK_CART_LEFT; + } + } + } + } + else if ( pm->cmd.forwardmove > 0 ) + {//forward left = TR2BL slash + newmove = LS_A_TR2BL; + } + else if ( pm->cmd.forwardmove < 0 ) + {//backward left = BR2TL uppercut + newmove = LS_A_BR2TL; + } + else + {//just left is a right slice + newmove = LS_A_R2L; + } + } + else + {//not moving left or right + if ( pm->cmd.forwardmove > 0 ) + {//forward= T2B slash + if (!noSpecials&& + (pm->ps->fd.saberAnimLevel == SS_DUAL || pm->ps->fd.saberAnimLevel == SS_STAFF) && + pm->ps->fd.forceRageRecoveryTime < pm->cmd.serverTime && + //pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 && + (pm->ps->groundEntityNum != ENTITYNUM_NONE || PM_GroundDistance() <= 40) && + pm->ps->velocity[2] >= 0 && + (pm->cmd.upmove > 0 || pm->ps->pm_flags & PMF_JUMP_HELD) && + !BG_SaberInTransitionAny(pm->ps->saberMove) && + !BG_SaberInAttack(pm->ps->saberMove) && + pm->ps->weaponTime <= 0 && + pm->ps->forceHandExtend == HANDEXTEND_NONE && + (pm->cmd.buttons & BUTTON_ATTACK)&& + BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB) ) + { //DUAL/STAFF JUMP ATTACK + newmove = PM_SaberJumpAttackMove2(); + if ( newmove != LS_A_T2B + && newmove != LS_NONE ) + { + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + } + else if (!noSpecials&& + pm->ps->fd.saberAnimLevel == SS_MEDIUM && + pm->ps->velocity[2] > 100 && + PM_GroundDistance() < 32 && + !BG_InSpecialJump(pm->ps->legsAnim) && + !BG_SaberInSpecialAttack(pm->ps->torsoAnim)&& + BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB)) + { //FLIP AND DOWNWARD ATTACK + //trace_t tr; + + //if (PM_SomeoneInFront(&tr)) + { + newmove = PM_SaberFlipOverAttackMove(); + if ( newmove != LS_A_T2B + && newmove != LS_NONE ) + { + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + } + } + else if (!noSpecials&& + pm->ps->fd.saberAnimLevel == SS_STRONG && + pm->ps->velocity[2] > 100 && + PM_GroundDistance() < 32 && + !BG_InSpecialJump(pm->ps->legsAnim) && + !BG_SaberInSpecialAttack(pm->ps->torsoAnim)&& + BG_EnoughForcePowerForMove( SABER_ALT_ATTACK_POWER_FB )) + { //DFA + //trace_t tr; + + //if (PM_SomeoneInFront(&tr)) + { + newmove = PM_SaberJumpAttackMove(); + if ( newmove != LS_A_T2B + && newmove != LS_NONE ) + { + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + } + } + else if ((pm->ps->fd.saberAnimLevel == SS_FAST || pm->ps->fd.saberAnimLevel == SS_DUAL || pm->ps->fd.saberAnimLevel == SS_STAFF) && + pm->ps->groundEntityNum != ENTITYNUM_NONE && + (pm->ps->pm_flags & PMF_DUCKED) && + pm->ps->weaponTime <= 0 && + !BG_SaberInSpecialAttack(pm->ps->torsoAnim)&& + BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB)) + { //LUNGE (weak) + newmove = PM_SaberLungeAttackMove( noSpecials ); + if ( newmove != LS_A_T2B + && newmove != LS_NONE ) + { + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + } + else if ( !noSpecials ) + { + saberMoveName_t stabDownMove = PM_CheckStabDown(); + if (stabDownMove != LS_NONE + && BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB) ) + { + newmove = stabDownMove; + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + else + { + newmove = LS_A_T2B; + } + } + } + else if ( pm->cmd.forwardmove < 0 ) + {//backward= T2B slash//B2T uppercut? + if (!noSpecials&& + pm->ps->fd.saberAnimLevel == SS_STAFF && + pm->ps->fd.forceRageRecoveryTime < pm->cmd.serverTime && + pm->ps->fd.forcePowerLevel[FP_LEVITATION] > FORCE_LEVEL_1 && + (pm->ps->groundEntityNum != ENTITYNUM_NONE || PM_GroundDistance() <= 40) && + pm->ps->velocity[2] >= 0 && + (pm->cmd.upmove > 0 || pm->ps->pm_flags & PMF_JUMP_HELD) && + !BG_SaberInTransitionAny(pm->ps->saberMove) && + !BG_SaberInAttack(pm->ps->saberMove) && + pm->ps->weaponTime <= 0 && + pm->ps->forceHandExtend == HANDEXTEND_NONE && + (pm->cmd.buttons & BUTTON_ATTACK)) + { //BACKFLIP ATTACK + newmove = PM_SaberBackflipAttackMove(); + } + else if (PM_CanBackstab() && !BG_SaberInSpecialAttack(pm->ps->torsoAnim)) + { //BACKSTAB (attack varies by level) + if (pm->ps->fd.saberAnimLevel >= FORCE_LEVEL_2 && pm->ps->fd.saberAnimLevel != SS_STAFF) + {//medium and higher attacks + if ( (pm->ps->pm_flags&PMF_DUCKED) || pm->cmd.upmove < 0 ) + { + newmove = LS_A_BACK_CR; + } + else + { + newmove = LS_A_BACK; + } + } + else + { //weak attack + newmove = LS_A_BACKSTAB; + } + } + else + { + newmove = LS_A_T2B; + } + } + else if ( PM_SaberInBounce( curmove ) ) + {//bounces should go to their default attack if you don't specify a direction but are attacking + newmove = saberMoveData[curmove].chain_attack; + + if ( PM_SaberKataDone(curmove, newmove) ) + { + newmove = saberMoveData[curmove].chain_idle; + } + else + { + newmove = saberMoveData[curmove].chain_attack; + } + } + else if ( curmove == LS_READY ) + {//Not moving at all, shouldn't have gotten here...? + //for now, just pick a random attack + //newmove = Q_irand( LS_A_TL2BR, LS_A_T2B ); + //rww - If we don't seed with a "common" value, the client and server will get mismatched + //prediction values. Under laggy conditions this will cause the appearance of rapid swing + //sequence changes. + + newmove = LS_A_T2B; //decided we don't like random attacks when idle, use an overhead instead. + } + } + + if (pm->ps->fd.saberAnimLevel == SS_DUAL) + { + if ( ( newmove == LS_A_R2L || newmove == LS_S_R2L + || newmove == LS_A_L2R || newmove == LS_S_L2R ) + && PM_CanDoDualDoubleAttacks() + && PM_CheckEnemyPresence( DIR_RIGHT, 100.0f ) + && PM_CheckEnemyPresence( DIR_LEFT, 100.0f ) ) + {//enemy both on left and right + newmove = LS_DUAL_LR; + //probably already moved, but... + pm->cmd.rightmove = 0; + } + else if ( (newmove == LS_A_T2B || newmove == LS_S_T2B + || newmove == LS_A_BACK || newmove == LS_A_BACK_CR ) + && PM_CanDoDualDoubleAttacks() + && PM_CheckEnemyPresence( DIR_FRONT, 100.0f ) + && PM_CheckEnemyPresence( DIR_BACK, 100.0f ) ) + {//enemy both in front and back + newmove = LS_DUAL_FB; + //probably already moved, but... + pm->cmd.forwardmove = 0; + } + } + + return newmove; +} + +int PM_KickMoveForConditions(void) +{ + int kickMove = -1; + + //FIXME: only if FP_SABER_OFFENSE >= 3 + if ( pm->cmd.rightmove ) + {//kick to side + if ( pm->cmd.rightmove > 0 ) + {//kick right + kickMove = LS_KICK_R; + } + else + {//kick left + kickMove = LS_KICK_L; + } + pm->cmd.rightmove = 0; + } + else if ( pm->cmd.forwardmove ) + {//kick front/back + if ( pm->cmd.forwardmove > 0 ) + {//kick fwd + /* + if (pm->ps->groundEntityNum != ENTITYNUM_NONE && + PM_CheckEnemyPresence( DIR_FRONT, 64.0f )) + { + kickMove = LS_HILT_BASH; + } + else + */ + { + kickMove = LS_KICK_F; + } + } + else + {//kick back + kickMove = LS_KICK_B; + } + pm->cmd.forwardmove = 0; + } + else + { + //if (pm->cmd.buttons & BUTTON_ATTACK) + //if (pm->ps->pm_flags & PMF_JUMP_HELD) + if (0) + { //ok, let's try some fancy kicks + //qboolean is actually of type int anyway, but just for safeness. + int front = (int)PM_CheckEnemyPresence( DIR_FRONT, 100.0f ); + int back = (int)PM_CheckEnemyPresence( DIR_BACK, 100.0f ); + int right = (int)PM_CheckEnemyPresence( DIR_RIGHT, 100.0f ); + int left = (int)PM_CheckEnemyPresence( DIR_LEFT, 100.0f ); + int numEnemy = front+back+right+left; + + if (numEnemy >= 3 || + ((!right || !left) && numEnemy >= 2)) + { //> 2 enemies near, or, >= 2 enemies near and they are not to the right and left. + kickMove = LS_KICK_S; + } + else if (right && left) + { //enemies on both sides + kickMove = LS_KICK_RL; + } + else + { //oh well, just do a forward kick + kickMove = LS_KICK_F; + } + + pm->cmd.upmove = 0; + } + } + + return kickMove; +} + +qboolean BG_InSlopeAnim( int anim ); +qboolean PM_RunningAnim( int anim ); + +qboolean PM_SaberMoveOkayForKata( void ) +{ + if ( pm->ps->saberMove == LS_READY + || PM_SaberInStart( pm->ps->saberMove ) ) + { + return qtrue; + } + else + { + return qfalse; + } +} + +qboolean PM_CanDoKata( void ) +{ + if ( PM_InSecondaryStyle() ) + { + return qfalse; + } + + if ( !pm->ps->saberInFlight//not throwing saber + && PM_SaberMoveOkayForKata() + && !BG_SaberInKata(pm->ps->saberMove) + && !BG_InKataAnim(pm->ps->legsAnim) + && !BG_InKataAnim(pm->ps->torsoAnim) + /* + && pm->ps->saberAnimLevel >= SS_FAST//fast, med or strong style + && pm->ps->saberAnimLevel <= SS_STRONG//FIXME: Tavion, too? + */ + && pm->ps->groundEntityNum != ENTITYNUM_NONE//not in the air + && (pm->cmd.buttons&BUTTON_ATTACK)//pressing attack + && (pm->cmd.buttons&BUTTON_ALT_ATTACK)//pressing alt attack + && !pm->cmd.forwardmove//not moving f/b + && !pm->cmd.rightmove//not moving r/l + && pm->cmd.upmove <= 0//not jumping...? + && BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER) )// have enough power + {//FIXME: check rage, etc... + saberInfo_t *saber = BG_MySaber( pm->ps->clientNum, 0 ); + if ( saber + && saber->kataMove == LS_NONE ) + {//kata move has been overridden in a way that should stop you from doing it at all + return qfalse; + } + saber = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber + && saber->kataMove == LS_NONE ) + {//kata move has been overridden in a way that should stop you from doing it at all + return qfalse; + } + return qtrue; + } + return qfalse; +} + +qboolean PM_CheckAltKickAttack( void ) +{ + if ( pm->ps->weapon == WP_SABER ) + { + saberInfo_t *saber = BG_MySaber( pm->ps->clientNum, 0 ); + if ( saber + && (saber->saberFlags&SFL_NO_KICKS) ) + { + return qfalse; + } + saber = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber + && (saber->saberFlags&SFL_NO_KICKS) ) + { + return qfalse; + } + } + if ( (pm->cmd.buttons&BUTTON_ALT_ATTACK) + //&& (!(pm->ps->pm_flags&PMF_ALT_ATTACK_HELD)||PM_SaberInReturn(pm->ps->saberMove)) + && (!BG_FlippingAnim(pm->ps->legsAnim)||pm->ps->legsTimer<=250) + && (pm->ps->fd.saberAnimLevel == SS_STAFF/*||!pm->ps->saber[0].throwable*/) && !pm->ps->saberHolstered ) + { + return qtrue; + } + return qfalse; +} + +int bg_parryDebounce[NUM_FORCE_POWER_LEVELS] = +{ + 500,//if don't even have defense, can't use defense! + 300, + 150, + 50 +}; + +qboolean PM_SaberPowerCheck(void) +{ + if (pm->ps->saberInFlight) + { //so we don't keep doing stupid force out thing while guiding saber. + if (pm->ps->fd.forcePower > forcePowerNeeded[pm->ps->fd.forcePowerLevel[FP_SABERTHROW]][FP_SABERTHROW]) + { + return qtrue; + } + } + else + { + return BG_EnoughForcePowerForMove(forcePowerNeeded[pm->ps->fd.forcePowerLevel[FP_SABERTHROW]][FP_SABERTHROW]); + } + + return qfalse; +} + +qboolean PM_CanDoRollStab( void ) +{ + if ( pm->ps->weapon == WP_SABER ) + { + saberInfo_t *saber = BG_MySaber( pm->ps->clientNum, 0 ); + if ( saber + && (saber->saberFlags&SFL_NO_ROLL_STAB) ) + { + return qfalse; + } + saber = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber + && (saber->saberFlags&SFL_NO_ROLL_STAB) ) + { + return qfalse; + } + } + return qtrue; +} +/* +================= +PM_WeaponLightsaber + +Consults a chart to choose what to do with the lightsaber. +While this is a little different than the Quake 3 code, there is no clean way of using the Q3 code for this kind of thing. +================= +*/ +// Ultimate goal is to set the sabermove to the proper next location +// Note that if the resultant animation is NONE, then the animation is essentially "idle", and is set in WP_TorsoAnim +qboolean PM_WalkingAnim( int anim ); +qboolean PM_SwimmingAnim( int anim ); +int PM_SaberBounceForAttack( int move ); +qboolean BG_SuperBreakLoseAnim( int anim ); +qboolean BG_SuperBreakWinAnim( int anim ); +void PM_WeaponLightsaber(void) +{ + int addTime,amount; + qboolean delayed_fire = qfalse; + int anim=-1, curmove, newmove=LS_NONE; + + qboolean checkOnlyWeap = qfalse; + + if ( PM_InKnockDown( pm->ps ) || BG_InRoll( pm->ps, pm->ps->legsAnim )) + {//in knockdown + // make weapon function + if ( pm->ps->weaponTime > 0 ) { + pm->ps->weaponTime -= pml.msec; + if ( pm->ps->weaponTime <= 0 ) + { + pm->ps->weaponTime = 0; + } + } + if ( pm->ps->legsAnim == BOTH_ROLL_F + && pm->ps->legsTimer <= 250 ) + { + if ( (pm->cmd.buttons&BUTTON_ATTACK) ) + { + if ( BG_EnoughForcePowerForMove(SABER_ALT_ATTACK_POWER_FB) && !pm->ps->saberInFlight ) + { + if ( PM_CanDoRollStab() ) + { + //make sure the saber is on for this move! + if ( pm->ps->saberHolstered == 2 ) + {//all the way off + pm->ps->saberHolstered = 0; + PM_AddEvent(EV_SABER_UNHOLSTER); + } + PM_SetSaberMove( LS_ROLL_STAB ); + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER_FB); + } + } + } + } + return; + } + + if ( pm->ps->saberLockTime > pm->cmd.serverTime ) + { + pm->ps->saberMove = LS_NONE; + PM_SaberLocked(); + return; + } + else + { + if ( /*( (pm->ps->torsoAnim) == BOTH_BF2LOCK || + (pm->ps->torsoAnim) == BOTH_BF1LOCK || + (pm->ps->torsoAnim) == BOTH_CWCIRCLELOCK || + (pm->ps->torsoAnim) == BOTH_CCWCIRCLELOCK ||*/ + pm->ps->saberLockFrame + ) + { + if (pm->ps->saberLockEnemy < ENTITYNUM_NONE && + pm->ps->saberLockEnemy >= 0) + { + bgEntity_t *bgEnt; + playerState_t *en; + + bgEnt = PM_BGEntForNum(pm->ps->saberLockEnemy); + + if (bgEnt) + { + en = bgEnt->playerState; + + if (en) + { + PM_SaberLockBreak(en, qfalse, 0); + return; + } + } + } + + if (/* ( (pm->ps->torsoAnim) == BOTH_BF2LOCK || + (pm->ps->torsoAnim) == BOTH_BF1LOCK || + (pm->ps->torsoAnim) == BOTH_CWCIRCLELOCK || + (pm->ps->torsoAnim) == BOTH_CCWCIRCLELOCK ||*/ + pm->ps->saberLockFrame + ) + { + pm->ps->torsoTimer = 0; + PM_SetAnim(SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_OVERRIDE, 100); + pm->ps->saberLockFrame = 0; + } + } + } + + if ( BG_KickingAnim( pm->ps->legsAnim ) || + BG_KickingAnim( pm->ps->torsoAnim )) + { + if ( pm->ps->legsTimer > 0 ) + {//you're kicking, no interruptions + return; + } + //done? be immeditately ready to do an attack + pm->ps->saberMove = LS_READY; + pm->ps->weaponTime = 0; + } + + if ( BG_SuperBreakLoseAnim( pm->ps->torsoAnim ) + || BG_SuperBreakWinAnim( pm->ps->torsoAnim ) ) + { + if ( pm->ps->torsoTimer > 0 ) + {//never interrupt these + return; + } + } + + if (BG_SabersOff( pm->ps )) + { + if (pm->ps->saberMove != LS_READY) + { + PM_SetSaberMove( LS_READY ); + } + + if ((pm->ps->legsAnim) != (pm->ps->torsoAnim) && !BG_InSlopeAnim(pm->ps->legsAnim) && + pm->ps->torsoTimer <= 0) + { + PM_SetAnim(SETANIM_TORSO,(pm->ps->legsAnim),SETANIM_FLAG_OVERRIDE, 100); + } + else if (BG_InSlopeAnim(pm->ps->legsAnim) && pm->ps->torsoTimer <= 0) + { + PM_SetAnim(SETANIM_TORSO,PM_GetSaberStance(),SETANIM_FLAG_OVERRIDE, 100); + } + + if (pm->ps->weaponTime < 1 && ((pm->cmd.buttons & BUTTON_ALT_ATTACK) || (pm->cmd.buttons & BUTTON_ATTACK))) + { + if (pm->ps->duelTime < pm->cmd.serverTime) + { + if (!pm->ps->m_iVehicleNum) + { //don't let em unholster the saber by attacking while on vehicle + pm->ps->saberHolstered = 0; + PM_AddEvent(EV_SABER_UNHOLSTER); + } + else + { + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + } + } + + if ( pm->ps->weaponTime > 0 ) + { + pm->ps->weaponTime -= pml.msec; + } + + checkOnlyWeap = qtrue; + goto weapChecks; + } + + if (!pm->ps->saberEntityNum && pm->ps->saberInFlight) + { //this means our saber has been knocked away + /* + if (pm->ps->saberMove != LS_READY) + { + PM_SetSaberMove( LS_READY ); + } + + if ((pm->ps->legsAnim) != (pm->ps->torsoAnim) && !BG_InSlopeAnim(pm->ps->legsAnim)) + { + PM_SetAnim(SETANIM_TORSO,(pm->ps->legsAnim),SETANIM_FLAG_OVERRIDE, 100); + } + + if (BG_InSaberStandAnim(pm->ps->torsoAnim) || pm->ps->torsoAnim == BOTH_SABERPULL) + { + PM_SetAnim(SETANIM_TORSO,BOTH_STAND1,SETANIM_FLAG_OVERRIDE, 100); + } + + return; + */ + //Old method, don't want to do this now because we want to finish up reflected attacks and things + //if our saber is pried out of our hands from one. + if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + if ( pm->ps->saberHolstered > 1 ) + { + pm->ps->saberHolstered = 1; + } + } + else + { + pm->cmd.buttons &= ~BUTTON_ATTACK; + } + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + } + + if ( (pm->cmd.buttons & BUTTON_ALT_ATTACK) ) + { //might as well just check for a saber throw right here + if (pm->ps->fd.saberAnimLevel == SS_STAFF) + { //kick instead of doing a throw + //if in a saber attack return anim, can interrupt it with a kick + if ( pm->ps->weaponTime > 0//can't fire yet + && PM_SaberInReturn( pm->ps->saberMove )//in a saber return move - FIXME: what about transitions? + //&& pm->ps->weaponTime <= 250//should be able to fire soon + //&& pm->ps->torsoTimer <= 250//torso almost done + && pm->ps->saberBlocked == BLOCKED_NONE//not interacting with any other saber + && !(pm->cmd.buttons&BUTTON_ATTACK) )//not trying to swing the saber + { + if ( (pm->cmd.forwardmove||pm->cmd.rightmove)//trying to kick in a specific direction + && PM_CheckAltKickAttack() )//trying to do a kick + {//allow them to do the kick now! + int kickMove = PM_KickMoveForConditions(); + if (kickMove != -1) + { + pm->ps->weaponTime = 0; + PM_SetSaberMove( kickMove ); + return; + } + } + } + } + else if ( pm->ps->weaponTime < 1&& + pm->ps->saberCanThrow && + //pm->ps->fd.forcePower >= forcePowerNeeded[pm->ps->fd.forcePowerLevel[FP_SABERTHROW]][FP_SABERTHROW] && + !BG_HasYsalamiri(pm->gametype, pm->ps) && + BG_CanUseFPNow(pm->gametype, pm->ps, pm->cmd.serverTime, FP_SABERTHROW) && + pm->ps->fd.forcePowerLevel[FP_SABERTHROW] > 0 && + PM_SaberPowerCheck() ) + { + trace_t sabTr; + vec3_t fwd, minFwd, sabMins, sabMaxs; + + VectorSet( sabMins, SABERMINS_X, SABERMINS_Y, SABERMINS_Z ); + VectorSet( sabMaxs, SABERMAXS_X, SABERMAXS_Y, SABERMAXS_Z ); + + AngleVectors( pm->ps->viewangles, fwd, NULL, NULL ); + VectorMA( pm->ps->origin, SABER_MIN_THROW_DIST, fwd, minFwd ); + + pm->trace(&sabTr, pm->ps->origin, sabMins, sabMaxs, minFwd, pm->ps->clientNum, MASK_PLAYERSOLID); + + if ( sabTr.allsolid || sabTr.startsolid || sabTr.fraction < 1.0f ) + {//not enough room to throw + } + else + {//throw it + //This will get set to false again once the saber makes it back to its owner game-side + if (!pm->ps->saberInFlight) + { + pm->ps->fd.forcePower -= forcePowerNeeded[pm->ps->fd.forcePowerLevel[FP_SABERTHROW]][FP_SABERTHROW]; + } + + pm->ps->saberInFlight = qtrue; + } + } + } + + if ( pm->ps->saberInFlight && pm->ps->saberEntityNum ) + {//guiding saber + if ( (pm->ps->fd.saberAnimLevel != SS_DUAL //not using 2 sabers + || pm->ps->saberHolstered //left one off - FIXME: saberHolstered 1 should be left one off, 0 should be both on, 2 should be both off + || (!(pm->cmd.buttons&BUTTON_ATTACK)//not trying to start an attack AND... + && (pm->ps->torsoAnim == BOTH_SABERDUAL_STANCE//not already attacking + || pm->ps->torsoAnim == BOTH_SABERPULL//not already attacking + || pm->ps->torsoAnim == BOTH_STAND1//not already attacking + || PM_RunningAnim( pm->ps->torsoAnim ) //not already attacking + || PM_WalkingAnim( pm->ps->torsoAnim ) //not already attacking + || PM_JumpingAnim( pm->ps->torsoAnim )//not already attacking + || PM_SwimmingAnim( pm->ps->torsoAnim ))//not already attacking + ) + ) + ) + { + PM_SetAnim(SETANIM_TORSO, BOTH_SABERPULL, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + pm->ps->torsoTimer = 1; + return; + } + } + + // don't allow attack until all buttons are up + //This is bad. It freezes the attack state and the animations if you hold the button after respawning, and it looks strange. + /* + if ( pm->ps->pm_flags & PMF_RESPAWNED ) { + return; + } + */ + + // check for dead player + if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { + return; + } + + /* + + if (pm->ps->weaponstate == WEAPON_READY || + pm->ps->weaponstate == WEAPON_IDLE) + { + if (pm->ps->saberMove != LS_READY && pm->ps->weaponTime <= 0 && !pm->ps->saberBlocked) + { + PM_SetSaberMove( LS_READY ); + } + } + + if(PM_RunningAnim(pm->ps->torsoAnim)) + { + if ((pm->ps->torsoAnim) != (pm->ps->legsAnim)) + { + PM_SetAnim(SETANIM_TORSO,(pm->ps->legsAnim),SETANIM_FLAG_OVERRIDE, 100); + } + } + */ + + // make weapon function + if ( pm->ps->weaponTime > 0 ) + { + //check for special pull move while busy + saberMoveName_t pullmove = PM_CheckPullAttack(); + if (pullmove != LS_NONE) + { + pm->ps->weaponTime = 0; + pm->ps->torsoTimer = 0; + pm->ps->legsTimer = 0; + pm->ps->forceHandExtend = HANDEXTEND_NONE; + pm->ps->weaponstate = WEAPON_READY; + PM_SetSaberMove(pullmove); + return; + } + + pm->ps->weaponTime -= pml.msec; + + //This was stupid and didn't work right. Looks like things are fine without it. + // if (pm->ps->saberBlocked && pm->ps->torsoAnim != saberMoveData[pm->ps->saberMove].animToUse) + // { //rww - keep him in the blocking pose until he can attack again + // PM_SetAnim(SETANIM_TORSO,saberMoveData[pm->ps->saberMove].animToUse,saberMoveData[pm->ps->saberMove].animSetFlags|SETANIM_FLAG_HOLD, saberMoveData[pm->ps->saberMove].blendTime); + // return; + // } + } + else + { + pm->ps->weaponstate = WEAPON_READY; + } + + // Now we react to a block action by the player's lightsaber. + if ( pm->ps->saberBlocked ) + { + if ( pm->ps->saberBlocked >= BLOCKED_UPPER_RIGHT + && pm->ps->saberBlocked < BLOCKED_UPPER_RIGHT_PROJ) + {//hold the parry for a bit + pm->ps->weaponTime = bg_parryDebounce[pm->ps->fd.forcePowerLevel[FP_SABER_DEFENSE]]+200; + } + switch ( pm->ps->saberBlocked ) + { + case BLOCKED_BOUNCE_MOVE: + { //act as a bounceMove and reset the saberMove instead of using a seperate value for it + pm->ps->torsoTimer = 0; + PM_SetSaberMove( pm->ps->saberMove ); + pm->ps->weaponTime = pm->ps->torsoTimer; + pm->ps->saberBlocked = 0; + } + break; + case BLOCKED_PARRY_BROKEN: + //whatever parry we were is in now broken, play the appropriate knocked-away anim + { + int nextMove; + + if ( PM_SaberInBrokenParry( pm->ps->saberMove ) ) + {//already have one...? + nextMove = pm->ps->saberMove; + } + else + { + nextMove = PM_BrokenParryForParry( pm->ps->saberMove ); + } + if ( nextMove != LS_NONE ) + { + PM_SetSaberMove( nextMove ); + pm->ps->weaponTime = pm->ps->torsoTimer; + } + else + {//Maybe in a knockaway? + } + } + break; + case BLOCKED_ATK_BOUNCE: + // If there is absolutely no blocked move in the chart, don't even mess with the animation. + // OR if we are already in a block or parry. + if (pm->ps->saberMove >= LS_T1_BR__R) + {//an actual bounce? Other bounces before this are actually transitions? + pm->ps->saberBlocked = BLOCKED_NONE; + } + else + { + int bounceMove; + + if ( PM_SaberInBounce( pm->ps->saberMove ) || !BG_SaberInAttack( pm->ps->saberMove ) ) + { + if ( pm->cmd.buttons & BUTTON_ATTACK ) + {//transition to a new attack + int newQuad = PM_SaberMoveQuadrantForMovement( &pm->cmd ); + while ( newQuad == saberMoveData[pm->ps->saberMove].startQuad ) + {//player is still in same attack quad, don't repeat that attack because it looks bad, + //FIXME: try to pick one that might look cool? + //newQuad = Q_irand( Q_BR, Q_BL ); + newQuad = PM_irand_timesync( Q_BR, Q_BL ); + //FIXME: sanity check, just in case? + }//else player is switching up anyway, take the new attack dir + bounceMove = transitionMove[saberMoveData[pm->ps->saberMove].startQuad][newQuad]; + } + else + {//return to ready + if ( saberMoveData[pm->ps->saberMove].startQuad == Q_T ) + { + bounceMove = LS_R_BL2TR; + } + else if ( saberMoveData[pm->ps->saberMove].startQuad < Q_T ) + { + bounceMove = LS_R_TL2BR+saberMoveData[pm->ps->saberMove].startQuad-Q_BR; + } + else// if ( saberMoveData[pm->ps->saberMove].startQuad > Q_T ) + { + bounceMove = LS_R_BR2TL+saberMoveData[pm->ps->saberMove].startQuad-Q_TL; + } + } + } + else + {//start the bounce + bounceMove = PM_SaberBounceForAttack( (saberMoveName_t)pm->ps->saberMove ); + } + + PM_SetSaberMove( bounceMove ); + + pm->ps->weaponTime = pm->ps->torsoTimer;//+saberMoveData[bounceMove].blendTime+SABER_BLOCK_DUR; + + } + break; + case BLOCKED_UPPER_RIGHT: + PM_SetSaberMove( LS_PARRY_UR ); + break; + case BLOCKED_UPPER_RIGHT_PROJ: + PM_SetSaberMove( LS_REFLECT_UR ); + break; + case BLOCKED_UPPER_LEFT: + PM_SetSaberMove( LS_PARRY_UL ); + break; + case BLOCKED_UPPER_LEFT_PROJ: + PM_SetSaberMove( LS_REFLECT_UL ); + break; + case BLOCKED_LOWER_RIGHT: + PM_SetSaberMove( LS_PARRY_LR ); + break; + case BLOCKED_LOWER_RIGHT_PROJ: + PM_SetSaberMove( LS_REFLECT_LR ); + break; + case BLOCKED_LOWER_LEFT: + PM_SetSaberMove( LS_PARRY_LL ); + break; + case BLOCKED_LOWER_LEFT_PROJ: + PM_SetSaberMove( LS_REFLECT_LL); + break; + case BLOCKED_TOP: + PM_SetSaberMove( LS_PARRY_UP ); + break; + case BLOCKED_TOP_PROJ: + PM_SetSaberMove( LS_REFLECT_UP ); + break; + default: + pm->ps->saberBlocked = BLOCKED_NONE; + break; + } + if ( pm->ps->saberBlocked >= BLOCKED_UPPER_RIGHT + && pm->ps->saberBlocked < BLOCKED_UPPER_RIGHT_PROJ) + {//hold the parry for a bit + if ( pm->ps->torsoTimer < pm->ps->weaponTime ) + { + pm->ps->torsoTimer = pm->ps->weaponTime; + } + } + + //what the? I don't know why I was doing this. + /* + if (pm->ps->saberBlocked != BLOCKED_ATK_BOUNCE && pm->ps->saberBlocked != BLOCKED_PARRY_BROKEN && pm->ps->weaponTime < 1) + { + pm->ps->torsoTimer = SABER_BLOCK_DUR; + pm->ps->weaponTime = pm->ps->torsoTimer; + } + */ + + //clear block + pm->ps->saberBlocked = 0; + + // Charging is like a lead-up before attacking again. This is an appropriate use, or we can create a new weaponstate for blocking + pm->ps->weaponstate = WEAPON_READY; + + // Done with block, so stop these active weapon branches. + return; + } + +weapChecks: + if (pm->ps->saberEntityNum) + { //only check if we have our saber with us + // 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->weaponTime <= 0 && pm->ps->torsoTimer <= 0) + { + if ( pm->ps->weapon != pm->cmd.weapon ) { + PM_BeginWeaponChange( pm->cmd.weapon ); + } + } + } + + if ( PM_CanDoKata() ) + { + saberMoveName_t overrideMove = LS_INVALID; + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + //see if we have an overridden (or cancelled) kata move + if ( saber1 && saber1->kataMove != LS_INVALID ) + { + if ( saber1->kataMove != LS_NONE ) + { + overrideMove = (saberMoveName_t)saber1->kataMove; + } + } + if ( overrideMove == LS_INVALID ) + {//not overridden by first saber, check second + if ( saber2 + && saber2->kataMove != LS_INVALID ) + { + if ( saber2->kataMove != LS_NONE ) + { + overrideMove = (saberMoveName_t)saber2->kataMove; + } + } + } + //no overrides, cancelled? + if ( overrideMove == LS_INVALID ) + { + if ( saber2 + && saber2->kataMove == LS_NONE ) + { + overrideMove = LS_NONE; + } + else if ( saber2 + && saber2->kataMove == LS_NONE ) + { + overrideMove = LS_NONE; + } + } + if ( overrideMove == LS_INVALID ) + {//not overridden + //FIXME: make sure to turn on saber(s)! + switch ( pm->ps->fd.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + PM_SetSaberMove( LS_A1_SPECIAL ); + break; + case SS_MEDIUM: + PM_SetSaberMove( LS_A2_SPECIAL ); + break; + case SS_STRONG: + case SS_DESANN: + PM_SetSaberMove( LS_A3_SPECIAL ); + break; + case SS_DUAL: + PM_SetSaberMove( LS_DUAL_SPIN_PROTECT );//PM_CheckDualSpinProtect(); + break; + case SS_STAFF: + PM_SetSaberMove( LS_STAFF_SOULCAL ); + break; + } + pm->ps->weaponstate = WEAPON_FIRING; + //G_DrainPowerForSpecialMove( pm->gent, FP_SABER_OFFENSE, SABER_ALT_ATTACK_POWER );//FP_SPEED, SINGLE_SPECIAL_POWER ); + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER); + } + else if ( overrideMove != LS_NONE ) + { + PM_SetSaberMove( overrideMove ); + pm->ps->weaponstate = WEAPON_FIRING; + BG_ForcePowerDrain(pm->ps, FP_GRIP, SABER_ALT_ATTACK_POWER); + } + if ( overrideMove != LS_NONE ) + {//not cancelled + return; + } + } + + if ( pm->ps->weaponTime > 0 ) + { + return; + } + + // ********************************************************* + // WEAPON_DROPPING + // ********************************************************* + + // change weapon if time + if ( pm->ps->weaponstate == WEAPON_DROPPING ) { + PM_FinishWeaponChange(); + return; + } + + // ********************************************************* + // WEAPON_RAISING + // ********************************************************* + + if ( pm->ps->weaponstate == WEAPON_RAISING ) + {//Just selected the weapon + pm->ps->weaponstate = WEAPON_IDLE; + if((pm->ps->legsAnim) == BOTH_WALK1 ) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK1,SETANIM_FLAG_NORMAL, 100); + } + else if((pm->ps->legsAnim) == BOTH_RUN1 ) + { + PM_SetAnim(SETANIM_TORSO,BOTH_RUN1,SETANIM_FLAG_NORMAL, 100); + } + else if((pm->ps->legsAnim) == BOTH_RUN2 ) + { + PM_SetAnim(SETANIM_TORSO,BOTH_RUN2,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_RUN_STAFF) + { + PM_SetAnim(SETANIM_TORSO,BOTH_RUN_STAFF,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_RUN_DUAL) + { + PM_SetAnim(SETANIM_TORSO,BOTH_RUN_DUAL,SETANIM_FLAG_NORMAL, 100); + } + else if((pm->ps->legsAnim) == BOTH_WALK1 ) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK1,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_WALK2) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK2,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_WALK_STAFF) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK_STAFF,SETANIM_FLAG_NORMAL, 100); + } + else if( pm->ps->legsAnim == BOTH_WALK_DUAL) + { + PM_SetAnim(SETANIM_TORSO,BOTH_WALK_DUAL,SETANIM_FLAG_NORMAL, 100); + } + else + { + PM_SetAnim(SETANIM_TORSO,PM_GetSaberStance(),SETANIM_FLAG_NORMAL, 100); + } + + if (pm->ps->weaponstate == WEAPON_RAISING) + { + return; + } + + } + + if (checkOnlyWeap) + { + return; + } + + // ********************************************************* + // Check for WEAPON ATTACK + // ********************************************************* + if (pm->ps->fd.saberAnimLevel == SS_STAFF && + (pm->cmd.buttons & BUTTON_ALT_ATTACK)) + { //ok, try a kick I guess. + int kickMove = -1; + + if ( !BG_KickingAnim(pm->ps->torsoAnim) && + !BG_KickingAnim(pm->ps->legsAnim) && + !BG_InRoll(pm->ps, pm->ps->legsAnim) && +// !BG_KickMove( pm->ps->saberMove )//not already in a kick + pm->ps->saberMove == LS_READY + && !(pm->ps->pm_flags&PMF_DUCKED)//not ducked + && (pm->cmd.upmove >= 0 ) //not trying to duck + )//&& pm->ps->groundEntityNum != ENTITYNUM_NONE) + {//player kicks + kickMove = PM_KickMoveForConditions(); + } + + if (kickMove != -1) + { + if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) + {//if in air, convert kick to an in-air kick + float gDist = PM_GroundDistance(); + //let's only allow air kicks if a certain distance from the ground + //it's silly to be able to do them right as you land. + //also looks wrong to transition from a non-complete flip anim... + if ((!BG_FlippingAnim( pm->ps->legsAnim ) || pm->ps->legsTimer <= 0) && + gDist > 64.0f && //strict minimum + gDist > (-pm->ps->velocity[2])-64.0f //make sure we are high to ground relative to downward velocity as well + ) + { + switch ( kickMove ) + { + case LS_KICK_F: + kickMove = LS_KICK_F_AIR; + break; + case LS_KICK_B: + kickMove = LS_KICK_B_AIR; + break; + case LS_KICK_R: + kickMove = LS_KICK_R_AIR; + break; + case LS_KICK_L: + kickMove = LS_KICK_L_AIR; + break; + default: //oh well, can't do any other kick move while in-air + kickMove = -1; + break; + } + } + else + {//leave it as a normal kick unless we're too high up + if ( gDist > 128.0f || pm->ps->velocity[2] >= 0 ) + { //off ground, but too close to ground + kickMove = -1; + } + } + } + + if (kickMove != -1) + { + PM_SetSaberMove( kickMove ); + return; + } + } + } + + //this is never a valid regular saber attack button + pm->cmd.buttons &= ~BUTTON_ALT_ATTACK; + + if(!delayed_fire) + { + // Start with the current move, and cross index it with the current control states. + if ( pm->ps->saberMove > LS_NONE && pm->ps->saberMove < LS_MOVE_MAX ) + { + curmove = pm->ps->saberMove; + } + else + { + curmove = LS_READY; + } + + if ( curmove == LS_A_JUMP_T__B_ || pm->ps->torsoAnim == BOTH_FORCELEAP2_T__B_ ) + {//must transition back to ready from this anim + newmove = LS_R_T2B; + } + // check for fire + else if ( !(pm->cmd.buttons & (BUTTON_ATTACK|BUTTON_ALT_ATTACK)) ) + {//not attacking + pm->ps->weaponTime = 0; + + if ( pm->ps->weaponTime > 0 ) + {//Still firing + pm->ps->weaponstate = WEAPON_FIRING; + } + else if ( pm->ps->weaponstate != WEAPON_READY ) + { + pm->ps->weaponstate = WEAPON_IDLE; + } + //Check for finishing an anim if necc. + if ( curmove >= LS_S_TL2BR && curmove <= LS_S_T2B ) + {//started a swing, must continue from here + newmove = LS_A_TL2BR + (curmove-LS_S_TL2BR); + } + else if ( curmove >= LS_A_TL2BR && curmove <= LS_A_T2B ) + {//finished an attack, must continue from here + newmove = LS_R_TL2BR + (curmove-LS_A_TL2BR); + } + else if ( PM_SaberInTransition( curmove ) ) + {//in a transition, must play sequential attack + newmove = saberMoveData[curmove].chain_attack; + } + else if ( PM_SaberInBounce( curmove ) ) + {//in a bounce + newmove = saberMoveData[curmove].chain_idle;//oops, not attacking, so don't chain + } + else + {//FIXME: what about returning from a parry? + //PM_SetSaberMove( LS_READY ); + //if ( pm->ps->saberBlockingTime > pm->cmd.serverTime ) + { + PM_SetSaberMove( LS_READY ); + } + return; + } + } + + // *************************************************** + // Pressing attack, so we must look up the proper attack move. + + if ( pm->ps->weaponTime > 0 ) + { // Last attack is not yet complete. + pm->ps->weaponstate = WEAPON_FIRING; + return; + } + else + { + int both = qfalse; + if ( pm->ps->torsoAnim == BOTH_FORCELONGLEAP_ATTACK + || pm->ps->torsoAnim == BOTH_FORCELONGLEAP_LAND ) + {//can't attack in these anims + return; + } + else if ( pm->ps->torsoAnim == BOTH_FORCELONGLEAP_START ) + {//only 1 attack you can do from this anim + if ( pm->ps->torsoTimer >= 200 ) + {//hit it early enough to do the attack + PM_SetSaberMove( LS_LEAP_ATTACK ); + } + return; + } + if ( curmove >= LS_PARRY_UP && curmove <= LS_REFLECT_LL ) + {//from a parry or reflection, can go directly into an attack + switch ( saberMoveData[curmove].endQuad ) + { + case Q_T: + newmove = LS_A_T2B; + break; + case Q_TR: + newmove = LS_A_TR2BL; + break; + case Q_TL: + newmove = LS_A_TL2BR; + break; + case Q_BR: + newmove = LS_A_BR2TL; + break; + case Q_BL: + newmove = LS_A_BL2TR; + break; + //shouldn't be a parry that ends at L, R or B + } + } + + if ( newmove != LS_NONE ) + {//have a valid, final LS_ move picked, so skip findingt he transition move and just get the anim + anim = saberMoveData[newmove].animToUse; + } + + //FIXME: diagonal dirs use the figure-eight attacks from ready pose? + if ( anim == -1 ) + { + //FIXME: take FP_SABER_OFFENSE into account here somehow? + if ( PM_SaberInTransition( curmove ) ) + {//in a transition, must play sequential attack + newmove = saberMoveData[curmove].chain_attack; + } + else if ( curmove >= LS_S_TL2BR && curmove <= LS_S_T2B ) + {//started a swing, must continue from here + newmove = LS_A_TL2BR + (curmove-LS_S_TL2BR); + } + else if ( PM_SaberInBrokenParry( curmove ) ) + {//broken parries must always return to ready + newmove = LS_READY; + } + else//if ( pm->cmd.buttons&BUTTON_ATTACK && !(pm->ps->pm_flags&PMF_ATTACK_HELD) )//only do this if just pressed attack button? + {//get attack move from movement command + /* + if ( PM_SaberKataDone() ) + {//we came from a bounce and cannot chain to another attack because our kata is done + newmove = saberMoveData[curmove].chain_idle; + } + else */ + newmove = PM_SaberAttackForMovement( curmove ); + if ( (PM_SaberInBounce( curmove )||PM_SaberInBrokenParry( curmove )) + && saberMoveData[newmove].startQuad == saberMoveData[curmove].endQuad ) + {//this attack would be a repeat of the last (which was blocked), so don't actually use it, use the default chain attack for this bounce + newmove = saberMoveData[curmove].chain_attack; + } + + if ( PM_SaberKataDone( curmove, newmove ) ) + {//cannot chain this time + newmove = saberMoveData[curmove].chain_idle; + } + } + /* + if ( newmove == LS_NONE ) + {//FIXME: should we allow this? Are there some anims that you should never be able to chain into an attack? + //only curmove that might get in here is LS_NONE, LS_DRAW, LS_PUTAWAY and the LS_R_ returns... all of which are in Q_R + newmove = PM_AttackMoveForQuad( saberMoveData[curmove].endQuad ); + } + */ + if ( newmove != LS_NONE ) + { + //Now get the proper transition move + newmove = PM_SaberAnimTransitionAnim( curmove, newmove ); + anim = saberMoveData[newmove].animToUse; + } + } + + if (anim == -1) + {//not side-stepping, pick neutral anim + // Add randomness for prototype? + newmove = saberMoveData[curmove].chain_attack; + + anim= saberMoveData[newmove].animToUse; + + if ( !pm->cmd.forwardmove && !pm->cmd.rightmove && pm->cmd.upmove >= 0 && pm->ps->groundEntityNum != ENTITYNUM_NONE ) + {//not moving at all, so set the anim on entire body + both = qtrue; + } + + } + + if ( anim == -1) + { + switch ( pm->ps->legsAnim ) + { + case BOTH_WALK1: + case BOTH_WALK2: + case BOTH_WALK_STAFF: + case BOTH_WALK_DUAL: + case BOTH_WALKBACK1: + case BOTH_WALKBACK2: + case BOTH_WALKBACK_STAFF: + case BOTH_WALKBACK_DUAL: + case BOTH_RUN1: + case BOTH_RUN2: + case BOTH_RUN_STAFF: + case BOTH_RUN_DUAL: + case BOTH_RUNBACK1: + case BOTH_RUNBACK2: + case BOTH_RUNBACK_STAFF: + anim = pm->ps->legsAnim; + break; + default: + anim = PM_GetSaberStance(); + break; + } + +// if (PM_RunningAnim(anim) && !pm->cmd.forwardmove && !pm->cmd.rightmove) +// { //semi-hacky (if not moving on x-y and still playing the running anim, force the player out of it) +// anim = PM_GetSaberStance(); +// } + newmove = LS_READY; + } + + PM_SetSaberMove( newmove ); + + if ( both && pm->ps->torsoAnim == anim ) + { + PM_SetAnim(SETANIM_LEGS,anim,SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 100); + } + + //don't fire again until anim is done + pm->ps->weaponTime = pm->ps->torsoTimer; + } + } + + // ********************************************************* + // WEAPON_FIRING + // ********************************************************* + + pm->ps->weaponstate = WEAPON_FIRING; + + amount = weaponData[pm->ps->weapon].energyPerShot; + + addTime = pm->ps->weaponTime; + + pm->ps->saberAttackSequence = pm->ps->torsoAnim; + if ( !addTime ) + { + addTime = weaponData[pm->ps->weapon].fireTime; + } + pm->ps->weaponTime = addTime; +} + +void PM_SetSaberMove(short newMove) +{ + unsigned int setflags = saberMoveData[newMove].animSetFlags; + int anim = saberMoveData[newMove].animToUse; + int parts = SETANIM_TORSO; + + if ( newMove == LS_READY || newMove == LS_A_FLIP_STAB || newMove == LS_A_FLIP_SLASH ) + {//finished with a kata (or in a special move) reset attack counter + pm->ps->saberAttackChainCount = 0; + } + else if ( BG_SaberInAttack( newMove ) ) + {//continuing with a kata, increment attack counter + pm->ps->saberAttackChainCount++; + } + + if (pm->ps->saberAttackChainCount > 16) + { //for the sake of being able to send the value over the net within a reasonable bit count + pm->ps->saberAttackChainCount = 16; + } + + if ( newMove == LS_DRAW ) + { + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber1 + && saber1->drawAnim != -1 ) + { + anim = saber1->drawAnim; + } + else if ( saber2 + && saber2->drawAnim != -1 ) + { + anim = saber2->drawAnim; + } + else if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + anim = BOTH_S1_S7; + } + else if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + anim = BOTH_S1_S6; + } + } + else if ( newMove == LS_PUTAWAY ) + { + saberInfo_t *saber1 = BG_MySaber( pm->ps->clientNum, 0 ); + saberInfo_t *saber2 = BG_MySaber( pm->ps->clientNum, 1 ); + if ( saber1 + && saber1->putawayAnim != -1 ) + { + anim = saber1->putawayAnim; + } + else if ( saber2 + && saber2->putawayAnim != -1 ) + { + anim = saber2->putawayAnim; + } + else if ( pm->ps->fd.saberAnimLevel == SS_STAFF ) + { + anim = BOTH_S7_S1; + } + else if ( pm->ps->fd.saberAnimLevel == SS_DUAL ) + { + anim = BOTH_S6_S1; + } + } + else if ( pm->ps->fd.saberAnimLevel == SS_STAFF && newMove >= LS_S_TL2BR && newMove < LS_REFLECT_LL ) + {//staff has an entirely new set of anims, besides special attacks + //FIXME: include ready and draw/putaway? + //FIXME: get hand-made bounces and deflections? + if ( newMove >= LS_V1_BR && newMove <= LS_REFLECT_LL ) + {//there aren't 1-7, just 1, 6 and 7, so just set it + anim = BOTH_P7_S7_T_ + (anim-BOTH_P1_S1_T_);//shift it up to the proper set + } + else + {//add the appropriate animLevel + anim += (pm->ps->fd.saberAnimLevel-FORCE_LEVEL_1) * SABER_ANIM_GROUP_SIZE; + } + } + else if ( pm->ps->fd.saberAnimLevel == SS_DUAL && newMove >= LS_S_TL2BR && newMove < LS_REFLECT_LL ) + { //akimbo has an entirely new set of anims, besides special attacks + //FIXME: include ready and draw/putaway? + //FIXME: get hand-made bounces and deflections? + if ( newMove >= LS_V1_BR && newMove <= LS_REFLECT_LL ) + {//there aren't 1-7, just 1, 6 and 7, so just set it + anim = BOTH_P6_S6_T_ + (anim-BOTH_P1_S1_T_);//shift it up to the proper set + } + else + {//add the appropriate animLevel + anim += (pm->ps->fd.saberAnimLevel-FORCE_LEVEL_1) * SABER_ANIM_GROUP_SIZE; + } + } + /* + else if ( newMove == LS_DRAW && pm->ps->SaberStaff() ) + {//hold saber out front as we turn it on + //FIXME: need a real "draw" anim for this (and put-away) + anim = BOTH_SABERSTAFF_STANCE; + } + */ + else if ( pm->ps->fd.saberAnimLevel > FORCE_LEVEL_1 && + !BG_SaberInIdle( newMove ) && !PM_SaberInParry( newMove ) && !PM_SaberInKnockaway( newMove ) && !PM_SaberInBrokenParry( newMove ) && !PM_SaberInReflect( newMove ) && !BG_SaberInSpecial(newMove)) + {//readies, parries and reflections have only 1 level + anim += (pm->ps->fd.saberAnimLevel-FORCE_LEVEL_1) * SABER_ANIM_GROUP_SIZE; + } + + // If the move does the same animation as the last one, we need to force a restart... + if ( saberMoveData[pm->ps->saberMove].animToUse == anim && newMove > LS_PUTAWAY) + { + setflags |= SETANIM_FLAG_RESTART; + } + + //saber torso anims should always be highest priority (4/12/02 - for special anims only) + if (!pm->ps->m_iVehicleNum) + { //if not riding a vehicle + if (BG_SaberInSpecial(newMove)) + { + setflags |= SETANIM_FLAG_OVERRIDE; + } + /* + if ( newMove == LS_A_LUNGE + || newMove == LS_A_JUMP_T__B_ + || newMove == LS_A_BACKSTAB + || newMove == LS_A_BACK + || newMove == LS_A_BACK_CR + || newMove == LS_A_FLIP_STAB + || newMove == LS_A_FLIP_SLASH + || newMove == LS_JUMPATTACK_DUAL + || newMove == LS_A_BACKFLIP_ATK) + { + setflags |= SETANIM_FLAG_OVERRIDE; + } + */ + } + if ( BG_InSaberStandAnim(anim) || anim == BOTH_STAND1 ) + { + anim = (pm->ps->legsAnim); + + if ((anim >= BOTH_STAND1 && anim <= BOTH_STAND4TOATTACK2) || + (anim >= TORSO_DROPWEAP1 && anim <= TORSO_WEAPONIDLE10)) + { //If standing then use the special saber stand anim + anim = PM_GetSaberStance(); + } + + if (pm->ps->pm_flags & PMF_DUCKED) + { //Playing torso walk anims while crouched makes you look like a monkey + anim = PM_GetSaberStance(); + } + + if (anim == BOTH_WALKBACK1 || anim == BOTH_WALKBACK2 || anim == BOTH_WALK1) + { //normal stance when walking backward so saber doesn't look like it's cutting through leg + anim = PM_GetSaberStance(); + } + + if (BG_InSlopeAnim( anim )) + { + anim = PM_GetSaberStance(); + } + + parts = SETANIM_TORSO; + } + + if (!pm->ps->m_iVehicleNum) + { //if not riding a vehicle + if (newMove == LS_JUMPATTACK_ARIAL_RIGHT || + newMove == LS_JUMPATTACK_ARIAL_LEFT) + { //force only on legs + parts = SETANIM_LEGS; + } + else if ( newMove == LS_A_LUNGE + || newMove == LS_A_JUMP_T__B_ + || newMove == LS_A_BACKSTAB + || newMove == LS_A_BACK + || newMove == LS_A_BACK_CR + || newMove == LS_ROLL_STAB + || newMove == LS_A_FLIP_STAB + || newMove == LS_A_FLIP_SLASH + || newMove == LS_JUMPATTACK_DUAL + || newMove == LS_JUMPATTACK_ARIAL_LEFT + || newMove == LS_JUMPATTACK_ARIAL_RIGHT + || newMove == LS_JUMPATTACK_CART_LEFT + || newMove == LS_JUMPATTACK_CART_RIGHT + || newMove == LS_JUMPATTACK_STAFF_LEFT + || newMove == LS_JUMPATTACK_STAFF_RIGHT + || newMove == LS_A_BACKFLIP_ATK + || newMove == LS_STABDOWN + || newMove == LS_STABDOWN_STAFF + || newMove == LS_STABDOWN_DUAL + || newMove == LS_DUAL_SPIN_PROTECT + || newMove == LS_STAFF_SOULCAL + || newMove == LS_A1_SPECIAL + || newMove == LS_A2_SPECIAL + || newMove == LS_A3_SPECIAL + || newMove == LS_UPSIDE_DOWN_ATTACK + || newMove == LS_PULL_ATTACK_STAB + || newMove == LS_PULL_ATTACK_SWING + || BG_KickMove( newMove ) ) + { + parts = SETANIM_BOTH; + } + else if ( BG_SpinningSaberAnim( anim ) ) + {//spins must be played on entire body + parts = SETANIM_BOTH; + } + else if ( (!pm->cmd.forwardmove&&!pm->cmd.rightmove&&!pm->cmd.upmove)) + {//not trying to run, duck or jump + if ( !BG_FlippingAnim( pm->ps->legsAnim ) && + !BG_InRoll( pm->ps, pm->ps->legsAnim ) && + !PM_InKnockDown( pm->ps ) && + !PM_JumpingAnim( pm->ps->legsAnim ) && + !BG_InSpecialJump( pm->ps->legsAnim ) && + anim != PM_GetSaberStance() && + pm->ps->groundEntityNum != ENTITYNUM_NONE && + !(pm->ps->pm_flags & PMF_DUCKED)) + { + parts = SETANIM_BOTH; + } + else if ( !(pm->ps->pm_flags & PMF_DUCKED) + && ( newMove == LS_SPINATTACK_DUAL || newMove == LS_SPINATTACK ) ) + { + parts = SETANIM_BOTH; + } + } + + PM_SetAnim(parts, anim, setflags, saberMoveData[newMove].blendTime); + if (parts != SETANIM_LEGS && + (pm->ps->legsAnim == BOTH_ARIAL_LEFT || + pm->ps->legsAnim == BOTH_ARIAL_RIGHT)) + { + if (pm->ps->legsTimer > pm->ps->torsoTimer) + { + pm->ps->legsTimer = pm->ps->torsoTimer; + } + } + + } + + if ( (pm->ps->torsoAnim) == anim ) + {//successfully changed anims + //special check for *starting* a saber swing + //playing at attack + if ( BG_SaberInAttack( newMove ) || BG_SaberInSpecialAttack( anim ) ) + { + if ( pm->ps->saberMove != newMove ) + {//wasn't playing that attack before + if ( newMove != LS_KICK_F + && newMove != LS_KICK_B + && newMove != LS_KICK_R + && newMove != LS_KICK_L + && newMove != LS_KICK_F_AIR + && newMove != LS_KICK_B_AIR + && newMove != LS_KICK_R_AIR + && newMove != LS_KICK_L_AIR ) + { + PM_AddEvent(EV_SABER_ATTACK); + } + + if (pm->ps->brokenLimbs) + { //randomly make pain sounds with a broken arm because we are suffering. + int iFactor = -1; + + if (pm->ps->brokenLimbs & (1<ps->brokenLimbs & (1<ps); + } + } + } + } + } + + if (BG_SaberInSpecial(newMove) && + pm->ps->weaponTime < pm->ps->torsoTimer) + { //rww 01-02-03 - I think this will solve the issue of special attacks being interruptable, hopefully without side effects + pm->ps->weaponTime = pm->ps->torsoTimer; + } + + pm->ps->saberMove = newMove; + pm->ps->saberBlocking = saberMoveData[newMove].blocking; + + pm->ps->torsoAnim = anim; + + if (pm->ps->weaponTime <= 0) + { + pm->ps->saberBlocked = BLOCKED_NONE; + } + } +} + +saberInfo_t *BG_MySaber( int clientNum, int saberNum ) +{ + //returns a pointer to the requested saberNum +#ifdef QAGAME + gentity_t *ent = &g_entities[clientNum]; + if ( ent->inuse && ent->client ) + { + if ( !ent->client->saber[saberNum].model + || !ent->client->saber[saberNum].model[0] ) + { //don't have saber anymore! + return NULL; + } + return &ent->client->saber[saberNum]; + } +#elif defined CGAME + clientInfo_t *ci = NULL; + if (clientNum < MAX_CLIENTS) + { + ci = &cgs.clientinfo[clientNum]; + } + else + { + centity_t *cent = &cg_entities[clientNum]; + if (cent->npcClient) + { + ci = cent->npcClient; + } + } + if ( ci + && ci->infoValid ) + { + if ( !ci->saber[saberNum].model + || !ci->saber[saberNum].model[0] ) + { //don't have sabers anymore! + return NULL; + } + return &ci->saber[saberNum]; + } +#endif + + return NULL; +} + +#include "../namespace_end.h" diff --git a/code/game/bg_saberLoad.c b/code/game/bg_saberLoad.c new file mode 100644 index 0000000..d3a301a --- /dev/null +++ b/code/game/bg_saberLoad.c @@ -0,0 +1,3011 @@ +//bg_saberLoad.c +#include "q_shared.h" +#include "bg_public.h" +#include "bg_local.h" +#include "w_saber.h" + +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +//Could use strap stuff but I don't particularly care at the moment anyway. +#include "../namespace_begin.h" +extern int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +extern void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +extern void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +extern void trap_FS_FCloseFile( fileHandle_t f ); +extern int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +extern qhandle_t trap_R_RegisterSkin( const char *name ); +#include "../namespace_end.h" + + +#ifdef QAGAME +extern int G_SoundIndex( const char *name ); +#elif defined CGAME +#include "../namespace_begin.h" +sfxHandle_t trap_S_RegisterSound( const char *sample); +qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +int trap_FX_RegisterEffect(const char *file); +#include "../namespace_end.h" +#endif + +#include "../namespace_begin.h" + +int BG_SoundIndex(char *sound) +{ +#ifdef QAGAME + return G_SoundIndex(sound); +#elif defined CGAME + return trap_S_RegisterSound(sound); +#endif +} + +extern stringID_table_t FPTable[]; + +#define MAX_SABER_DATA_SIZE 0x80000 +static char SaberParms[MAX_SABER_DATA_SIZE]; + +stringID_table_t SaberTable[] = +{ + ENUM2STRING(SABER_NONE), + ENUM2STRING(SABER_SINGLE), + ENUM2STRING(SABER_STAFF), + ENUM2STRING(SABER_BROAD), + ENUM2STRING(SABER_PRONG), + ENUM2STRING(SABER_DAGGER), + ENUM2STRING(SABER_ARC), + ENUM2STRING(SABER_SAI), + ENUM2STRING(SABER_CLAW), + ENUM2STRING(SABER_LANCE), + ENUM2STRING(SABER_STAR), + ENUM2STRING(SABER_TRIDENT), + "", -1 +}; + +stringID_table_t SaberMoveTable[] = +{ + ENUM2STRING(LS_NONE), + // Attacks + ENUM2STRING(LS_A_TL2BR), + ENUM2STRING(LS_A_L2R), + ENUM2STRING(LS_A_BL2TR), + ENUM2STRING(LS_A_BR2TL), + ENUM2STRING(LS_A_R2L), + ENUM2STRING(LS_A_TR2BL), + ENUM2STRING(LS_A_T2B), + ENUM2STRING(LS_A_BACKSTAB), + ENUM2STRING(LS_A_BACK), + ENUM2STRING(LS_A_BACK_CR), + ENUM2STRING(LS_ROLL_STAB), + ENUM2STRING(LS_A_LUNGE), + ENUM2STRING(LS_A_JUMP_T__B_), + ENUM2STRING(LS_A_FLIP_STAB), + ENUM2STRING(LS_A_FLIP_SLASH), + ENUM2STRING(LS_JUMPATTACK_DUAL), + ENUM2STRING(LS_JUMPATTACK_ARIAL_LEFT), + ENUM2STRING(LS_JUMPATTACK_ARIAL_RIGHT), + ENUM2STRING(LS_JUMPATTACK_CART_LEFT), + ENUM2STRING(LS_JUMPATTACK_CART_RIGHT), + ENUM2STRING(LS_JUMPATTACK_STAFF_LEFT), + ENUM2STRING(LS_JUMPATTACK_STAFF_RIGHT), + ENUM2STRING(LS_BUTTERFLY_LEFT), + ENUM2STRING(LS_BUTTERFLY_RIGHT), + ENUM2STRING(LS_A_BACKFLIP_ATK), + ENUM2STRING(LS_SPINATTACK_DUAL), + ENUM2STRING(LS_SPINATTACK), + ENUM2STRING(LS_LEAP_ATTACK), + ENUM2STRING(LS_SWOOP_ATTACK_RIGHT), + ENUM2STRING(LS_SWOOP_ATTACK_LEFT), + ENUM2STRING(LS_TAUNTAUN_ATTACK_RIGHT), + ENUM2STRING(LS_TAUNTAUN_ATTACK_LEFT), + ENUM2STRING(LS_KICK_F), + ENUM2STRING(LS_KICK_B), + ENUM2STRING(LS_KICK_R), + ENUM2STRING(LS_KICK_L), + ENUM2STRING(LS_KICK_S), + ENUM2STRING(LS_KICK_BF), + ENUM2STRING(LS_KICK_RL), + ENUM2STRING(LS_KICK_F_AIR), + ENUM2STRING(LS_KICK_B_AIR), + ENUM2STRING(LS_KICK_R_AIR), + ENUM2STRING(LS_KICK_L_AIR), + ENUM2STRING(LS_STABDOWN), + ENUM2STRING(LS_STABDOWN_STAFF), + ENUM2STRING(LS_STABDOWN_DUAL), + ENUM2STRING(LS_DUAL_SPIN_PROTECT), + ENUM2STRING(LS_STAFF_SOULCAL), + ENUM2STRING(LS_A1_SPECIAL), + ENUM2STRING(LS_A2_SPECIAL), + ENUM2STRING(LS_A3_SPECIAL), + ENUM2STRING(LS_UPSIDE_DOWN_ATTACK), + ENUM2STRING(LS_PULL_ATTACK_STAB), + ENUM2STRING(LS_PULL_ATTACK_SWING), + ENUM2STRING(LS_SPINATTACK_ALORA), + ENUM2STRING(LS_DUAL_FB), + ENUM2STRING(LS_DUAL_LR), + ENUM2STRING(LS_HILT_BASH), + "", -1 +}; + +//Also used in npc code +qboolean BG_ParseLiteral( const char **data, const char *string ) +{ + const char *token; + + token = COM_ParseExt( data, qtrue ); + if ( token[0] == 0 ) + { + Com_Printf( "unexpected EOF\n" ); + return qtrue; + } + + if ( Q_stricmp( token, string ) ) + { + Com_Printf( "required string '%s' missing\n", string ); + return qtrue; + } + + return qfalse; +} + +saber_colors_t TranslateSaberColor( const char *name ) +{ + if ( !Q_stricmp( name, "red" ) ) + { + return SABER_RED; + } + if ( !Q_stricmp( name, "orange" ) ) + { + return SABER_ORANGE; + } + if ( !Q_stricmp( name, "yellow" ) ) + { + return SABER_YELLOW; + } + if ( !Q_stricmp( name, "green" ) ) + { + return SABER_GREEN; + } + if ( !Q_stricmp( name, "blue" ) ) + { + return SABER_BLUE; + } + if ( !Q_stricmp( name, "purple" ) ) + { + return SABER_PURPLE; + } + if ( !Q_stricmp( name, "random" ) ) + { + return ((saber_colors_t)(Q_irand( SABER_ORANGE, SABER_PURPLE ))); + } + return SABER_BLUE; +} + +saber_styles_t TranslateSaberStyle( const char *name ) +{ + if ( !Q_stricmp( name, "fast" ) ) + { + return SS_FAST; + } + if ( !Q_stricmp( name, "medium" ) ) + { + return SS_MEDIUM; + } + if ( !Q_stricmp( name, "strong" ) ) + { + return SS_STRONG; + } + if ( !Q_stricmp( name, "desann" ) ) + { + return SS_DESANN; + } + if ( !Q_stricmp( name, "tavion" ) ) + { + return SS_TAVION; + } + if ( !Q_stricmp( name, "dual" ) ) + { + return SS_DUAL; + } + if ( !Q_stricmp( name, "staff" ) ) + { + return SS_STAFF; + } + return SS_NONE; +} + +qboolean WP_SaberBladeUseSecondBladeStyle( saberInfo_t *saber, int bladeNum ) +{ + if ( saber ) + { + if ( saber->bladeStyle2Start > 0 ) + { + if ( bladeNum >= saber->bladeStyle2Start ) + { + return qtrue; + } + } + } + return qfalse; +} + +qboolean WP_SaberBladeDoTransitionDamage( saberInfo_t *saber, int bladeNum ) +{ + if ( !WP_SaberBladeUseSecondBladeStyle( saber, bladeNum ) + && (saber->saberFlags2&SFL2_TRANSITION_DAMAGE) ) + {//use first blade style for this blade + return qtrue; + } + else if ( WP_SaberBladeUseSecondBladeStyle( saber, bladeNum ) + && (saber->saberFlags2&SFL2_TRANSITION_DAMAGE2) ) + {//use second blade style for this blade + return qtrue; + } + return qfalse; +} + +qboolean WP_UseFirstValidSaberStyle( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int *saberAnimLevel ) +{ + qboolean styleInvalid = qfalse; + qboolean saber1Active; + qboolean saber2Active; + qboolean dualSabers = qfalse; + int validStyles = 0, styleNum; + + if ( saber2 && saber2->model && saber2->model[0] ) + { + dualSabers = qtrue; + } + + if ( dualSabers ) + {//dual + if ( saberHolstered > 1 ) + { + saber1Active = saber2Active = qfalse; + } + else if ( saberHolstered > 0 ) + { + saber1Active = qtrue; + saber2Active = qfalse; + } + else + { + saber1Active = saber2Active = qtrue; + } + } + else + { + saber2Active = qfalse; + if ( !saber1 + || !saber1->model + || !saber1->model[0] ) + { + saber1Active = qfalse; + } + else if ( saber1->numBlades > 1 ) + {//staff + if ( saberHolstered > 1 ) + { + saber1Active = qfalse; + } + else + { + saber1Active = qtrue; + } + } + else + {//single + if ( saberHolstered ) + { + saber1Active = qfalse; + } + else + { + saber1Active = qtrue; + } + } + } + + //initially, all styles are valid + for ( styleNum = SS_NONE+1; styleNum < SS_NUM_SABER_STYLES; styleNum++ ) + { + validStyles |= (1<model + && saber1->model[0] + && saber1->stylesForbidden ) + { + if ( (saber1->stylesForbidden&(1<<*saberAnimLevel)) ) + {//not a valid style for first saber! + styleInvalid = qtrue; + validStyles &= ~saber1->stylesForbidden; + } + } + if ( dualSabers ) + {//check second saber, too + if ( saber2Active + && saber2->stylesForbidden ) + { + if ( (saber2->stylesForbidden&(1<<*saberAnimLevel)) ) + {//not a valid style for second saber! + styleInvalid = qtrue; + //only the ones both sabers allow is valid + validStyles &= ~saber2->stylesForbidden; + } + } + } + if ( styleInvalid && validStyles ) + {//using an invalid style and have at least one valid style to use, so switch to it + int styleNum; + for ( styleNum = SS_FAST; styleNum < SS_NUM_SABER_STYLES; styleNum++ ) + { + if ( (validStyles&(1<model && saber2->model[0] ) + { + dualSabers = qtrue; + } + + if ( dualSabers ) + {//dual + if ( saberHolstered > 1 ) + { + saber1Active = saber2Active = qfalse; + } + else if ( saberHolstered > 0 ) + { + saber1Active = qtrue; + saber2Active = qfalse; + } + else + { + saber1Active = saber2Active = qtrue; + } + } + else + { + saber2Active = qfalse; + if ( !saber1 + || !saber1->model + || !saber1->model[0] ) + { + saber1Active = qfalse; + } + else if ( saber1->numBlades > 1 ) + {//staff + if ( saberHolstered > 1 ) + { + saber1Active = qfalse; + } + else + { + saber1Active = qtrue; + } + } + else + {//single + if ( saberHolstered ) + { + saber1Active = qfalse; + } + else + { + saber1Active = qtrue; + } + } + } + + if ( saber1Active + && saber1 + && saber1->model + && saber1->model[0] + && saber1->stylesForbidden ) + { + if ( (saber1->stylesForbidden&(1<model + && saber2->model[0] ) + { + if ( saber2->stylesForbidden ) + {//check second saber, too + if ( (saber2->stylesForbidden&(1<model + && saber1->model[0] + && (saber1->stylesLearned&(1<stylesLearned&(1<bladeStyle2Start > 0 + && saber->numBlades > saber->bladeStyle2Start ) + { + if ( (saber->saberFlags2&SFL2_NO_MANUAL_DEACTIVATE) + && (saber->saberFlags2&SFL2_NO_MANUAL_DEACTIVATE2) ) + {//all blades are always on + return qfalse; + } + } + else + { + if ( (saber->saberFlags2&SFL2_NO_MANUAL_DEACTIVATE) ) + {//all blades are always on + return qfalse; + } + } + //you can turn some off + return qtrue; +} + +void WP_SaberSetDefaults( saberInfo_t *saber ) +{ + int i; + + //Set defaults so that, if it fails, there's at least something there + for ( i = 0; i < MAX_BLADES; i++ ) + { + saber->blade[i].color = SABER_RED; + saber->blade[i].radius = SABER_RADIUS_STANDARD; + saber->blade[i].lengthMax = 32; + } + + strcpy(saber->name, "default"); + strcpy(saber->fullName, "lightsaber"); + strcpy(saber->model, "models/weapons2/saber_reborn/saber_w.glm"); + saber->skin = 0; + saber->soundOn = BG_SoundIndex( "sound/weapons/saber/enemy_saber_on.wav" ); + saber->soundLoop = BG_SoundIndex( "sound/weapons/saber/saberhum3.wav" ); + saber->soundOff = BG_SoundIndex( "sound/weapons/saber/enemy_saber_off.wav" ); + saber->numBlades = 1; + saber->type = SABER_SINGLE; + saber->stylesLearned = 0; + saber->stylesForbidden = 0;//allow all styles + saber->maxChain = 0;//0 = use default behavior + saber->forceRestrictions = 0; + saber->lockBonus = 0; + saber->parryBonus = 0; + saber->breakParryBonus = 0; + saber->breakParryBonus2 = 0; + saber->disarmBonus = 0; + saber->disarmBonus2 = 0; + saber->singleBladeStyle = SS_NONE;//makes it so that you use a different style if you only have the first blade active +// saber->brokenSaber1 = NULL;//if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your right hand +// saber->brokenSaber2 = NULL;//if saber is actually hit by another saber, it can be cut in half/broken and will be replaced with this saber in your left hand +//===NEW======================================================================================== + //done in cgame (client-side code) + saber->saberFlags = 0; //see all the SFL_ flags + saber->saberFlags2 = 0; //see all the SFL2_ flags + + saber->spinSound = 0; //none - if set, plays this sound as it spins when thrown + saber->swingSound[0] = 0; //none - if set, plays one of these 3 sounds when swung during an attack - NOTE: must provide all 3!!! + saber->swingSound[1] = 0; //none - if set, plays one of these 3 sounds when swung during an attack - NOTE: must provide all 3!!! + saber->swingSound[2] = 0; //none - if set, plays one of these 3 sounds when swung during an attack - NOTE: must provide all 3!!! + + //done in game (server-side code) + saber->moveSpeedScale = 1.0f; //1.0 - you move faster/slower when using this saber + saber->animSpeedScale = 1.0f; //1.0 - plays normal attack animations faster/slower + + saber->kataMove = LS_INVALID; //LS_INVALID - if set, player will execute this move when they press both attack buttons at the same time + saber->lungeAtkMove = LS_INVALID; //LS_INVALID - if set, player will execute this move when they crouch+fwd+attack + saber->jumpAtkUpMove = LS_INVALID; //LS_INVALID - if set, player will execute this move when they jump+attack + saber->jumpAtkFwdMove = LS_INVALID; //LS_INVALID - if set, player will execute this move when they jump+fwd+attack + saber->jumpAtkBackMove = LS_INVALID; //LS_INVALID - if set, player will execute this move when they jump+back+attack + saber->jumpAtkRightMove = LS_INVALID; //LS_INVALID - if set, player will execute this move when they jump+rightattack + saber->jumpAtkLeftMove = LS_INVALID; //LS_INVALID - if set, player will execute this move when they jump+left+attack + saber->readyAnim = -1; //-1 - anim to use when standing idle + saber->drawAnim = -1; //-1 - anim to use when drawing weapon + saber->putawayAnim = -1; //-1 - anim to use when putting weapon away + saber->tauntAnim = -1; //-1 - anim to use when hit "taunt" + saber->bowAnim = -1; //-1 - anim to use when hit "bow" + saber->meditateAnim = -1; //-1 - anim to use when hit "meditate" + saber->flourishAnim = -1; //-1 - anim to use when hit "flourish" + saber->gloatAnim = -1; //-1 - anim to use when hit "gloat" + + //***NOTE: you can only have a maximum of 2 "styles" of blades, so this next value, "bladeStyle2Start" is the number of the first blade to use these value on... all blades before this use the normal values above, all blades at and after this number use the secondary values below*** + saber->bladeStyle2Start = 0; //0 - if set, blades from this number and higher use the following values (otherwise, they use the normal values already set) + + //***The following can be different for the extra blades - not setting them individually defaults them to the value for the whole saber (and first blade)*** + + //===PRIMARY BLADES===================== + //done in cgame (client-side code) + saber->trailStyle = 0; //0 - default (0) is normal, 1 is a motion blur and 2 is no trail at all (good for real-sword type mods) + saber->g2MarksShader = 0; //none - if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + saber->g2WeaponMarkShader = 0; //none - if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + //saber->bladeShader = 0; //none - if set, overrides the shader used for the saber blade? + //saber->trailShader = 0; //none - if set, overrides the shader used for the saber trail? + saber->hitSound[0] = 0; //none - if set, plays one of these 3 sounds when saber hits a person - NOTE: must provide all 3!!! + saber->hitSound[1] = 0; //none - if set, plays one of these 3 sounds when saber hits a person - NOTE: must provide all 3!!! + saber->hitSound[2] = 0; //none - if set, plays one of these 3 sounds when saber hits a person - NOTE: must provide all 3!!! + saber->blockSound[0] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits another saber/sword - NOTE: must provide all 3!!! + saber->blockSound[1] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits another saber/sword - NOTE: must provide all 3!!! + saber->blockSound[2] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits another saber/sword - NOTE: must provide all 3!!! + saber->bounceSound[0] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits a wall and bounces off (must set bounceOnWall to 1 to use these sounds) - NOTE: must provide all 3!!! + saber->bounceSound[1] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits a wall and bounces off (must set bounceOnWall to 1 to use these sounds) - NOTE: must provide all 3!!! + saber->bounceSound[2] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits a wall and bounces off (must set bounceOnWall to 1 to use these sounds) - NOTE: must provide all 3!!! + saber->blockEffect = 0; //none - if set, plays this effect when the saber/sword hits another saber/sword (instead of "saber/saber_block.efx") + saber->hitPersonEffect = 0; //none - if set, plays this effect when the saber/sword hits a person (instead of "saber/blood_sparks_mp.efx") + saber->hitOtherEffect = 0; //none - if set, plays this effect when the saber/sword hits something else damagable (instead of "saber/saber_cut.efx") + saber->bladeEffect = 0; //none - if set, plays this effect at the blade tag + + //done in game (server-side code) + saber->knockbackScale = 0; //0 - if non-zero, uses damage done to calculate an appropriate amount of knockback + saber->damageScale = 1.0f; //1 - scale up or down the damage done by the saber + saber->splashRadius = 0.0f; //0 - radius of splashDamage + saber->splashDamage = 0; //0 - amount of splashDamage, 100% at a distance of 0, 0% at a distance = splashRadius + saber->splashKnockback = 0.0f; //0 - amount of splashKnockback, 100% at a distance of 0, 0% at a distance = splashRadius + + //===SECONDARY BLADES=================== + //done in cgame (client-side code) + saber->trailStyle2 = 0; //0 - default (0) is normal, 1 is a motion blur and 2 is no trail at all (good for real-sword type mods) + saber->g2MarksShader2 = 0; //none - if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + saber->g2WeaponMarkShader2 = 0; //none - if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + //saber->bladeShader = 0; //none - if set, overrides the shader used for the saber blade? + //saber->trailShader = 0; //none - if set, overrides the shader used for the saber trail? + saber->hit2Sound[0] = 0; //none - if set, plays one of these 3 sounds when saber hits a person - NOTE: must provide all 3!!! + saber->hit2Sound[1] = 0; //none - if set, plays one of these 3 sounds when saber hits a person - NOTE: must provide all 3!!! + saber->hit2Sound[2] = 0; //none - if set, plays one of these 3 sounds when saber hits a person - NOTE: must provide all 3!!! + saber->block2Sound[0] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits another saber/sword - NOTE: must provide all 3!!! + saber->block2Sound[1] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits another saber/sword - NOTE: must provide all 3!!! + saber->block2Sound[2] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits another saber/sword - NOTE: must provide all 3!!! + saber->bounce2Sound[0] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits a wall and bounces off (must set bounceOnWall to 1 to use these sounds) - NOTE: must provide all 3!!! + saber->bounce2Sound[1] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits a wall and bounces off (must set bounceOnWall to 1 to use these sounds) - NOTE: must provide all 3!!! + saber->bounce2Sound[2] = 0; //none - if set, plays one of these 3 sounds when saber/sword hits a wall and bounces off (must set bounceOnWall to 1 to use these sounds) - NOTE: must provide all 3!!! + saber->blockEffect2 = 0; //none - if set, plays this effect when the saber/sword hits another saber/sword (instead of "saber/saber_block.efx") + saber->hitPersonEffect2 = 0; //none - if set, plays this effect when the saber/sword hits a person (instead of "saber/blood_sparks_mp.efx") + saber->hitOtherEffect2 = 0; //none - if set, plays this effect when the saber/sword hits something else damagable (instead of "saber/saber_cut.efx") + saber->bladeEffect2 = 0; //none - if set, plays this effect at the blade tag + + //done in game (server-side code) + saber->knockbackScale2 = 0; //0 - if non-zero, uses damage done to calculate an appropriate amount of knockback + saber->damageScale2 = 1.0f; //1 - scale up or down the damage done by the saber + saber->splashRadius2 = 0.0f; //0 - radius of splashDamage + saber->splashDamage2 = 0; //0 - amount of splashDamage, 100% at a distance of 0, 0% at a distance = splashRadius + saber->splashKnockback2 = 0.0f; //0 - amount of splashKnockback, 100% at a distance of 0, 0% at a distance = splashRadius +//========================================================================================================================================= +} + +#define DEFAULT_SABER "Kyle" + +qboolean WP_SaberParseParms( const char *SaberName, saberInfo_t *saber ) +{ + const char *token; + const char *value; + const char *p; + char useSaber[1024]; + float f; + int n; + qboolean triedDefault = qfalse; + int saberMove = LS_INVALID; + int anim = -1; + + if ( !saber ) + { + return qfalse; + } + + //Set defaults so that, if it fails, there's at least something there + WP_SaberSetDefaults( saber ); + + if ( !SaberName || !SaberName[0] ) + { + strcpy(useSaber, DEFAULT_SABER); //default + triedDefault = qtrue; + } + else + { + strcpy(useSaber, SaberName); + } + + //try to parse it out + p = SaberParms; + COM_BeginParseSession("saberinfo"); + + // look for the right saber + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + if (!triedDefault) + { //fall back to default and restart, should always be there + p = SaberParms; + COM_BeginParseSession("saberinfo"); + strcpy(useSaber, DEFAULT_SABER); + triedDefault = qtrue; + } + else + { + return qfalse; + } + } + + if ( !Q_stricmp( token, useSaber ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { //even the default saber isn't found? + return qfalse; + } + + //got the name we're using for sure + strcpy(saber->name, useSaber); + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return qfalse; + } + + // parse the saber info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", useSaber ); + return qfalse; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + //saber fullName + if ( !Q_stricmp( token, "name" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + strcpy(saber->fullName, value); + continue; + } + + //saber type + if ( !Q_stricmp( token, "saberType" ) ) + { + int saberType; + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saberType = GetIDForString( SaberTable, value ); + if ( saberType >= SABER_SINGLE && saberType <= NUM_SABERS ) + { + saber->type = (saberType_t)saberType; + } + continue; + } + + //saber hilt + if ( !Q_stricmp( token, "saberModel" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + strcpy(saber->model, value); + continue; + } + + if ( !Q_stricmp( token, "customSkin" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->skin = trap_R_RegisterSkin(value); + continue; + } + + //on sound + if ( !Q_stricmp( token, "soundOn" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->soundOn = BG_SoundIndex( (char *)value ); + continue; + } + + //loop sound + if ( !Q_stricmp( token, "soundLoop" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->soundLoop = BG_SoundIndex( (char *)value ); + continue; + } + + //off sound + if ( !Q_stricmp( token, "soundOff" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->soundOff = BG_SoundIndex( (char *)value ); + continue; + } + + if ( !Q_stricmp( token, "numBlades" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n < 1 || n > MAX_BLADES ) + { + Com_Error(ERR_DROP, "WP_SaberParseParms: saber %s has illegal number of blades (%d) max: %d", useSaber, n, MAX_BLADES ); + continue; + } + saber->numBlades = n; + continue; + } + + // saberColor + if ( !Q_stricmpn( token, "saberColor", 10 ) ) + { + if (strlen(token)==10) + { + n = -1; + } + else if (strlen(token)==11) + { + n = atoi(&token[10])-1; + if (n > 7 || n < 1 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, useSaber ); + continue; + } + } + else + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberColor '%s' in %s\n", token, useSaber ); + continue; + } + + if ( COM_ParseString( &p, &value ) ) //read the color + { + continue; + } + + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same color by default + saber_colors_t color = TranslateSaberColor( value ); + for ( n = 0; n < MAX_BLADES; n++ ) + { + saber->blade[n].color = color; + } + } else + { + saber->blade[n].color = TranslateSaberColor( value ); + } + continue; + } + + //saber length + if ( !Q_stricmpn( token, "saberLength", 11 ) ) + { + if (strlen(token)==11) + { + n = -1; + } + else if (strlen(token)==12) + { + n = atoi(&token[11])-1; + if (n > 7 || n < 1 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, useSaber ); + continue; + } + } + else + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberLength '%s' in %s\n", token, useSaber ); + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 4.0f ) + { + f = 4.0f; + } + + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same length by default + for ( n = 0; n < MAX_BLADES; n++ ) + { + saber->blade[n].lengthMax = f; + } + } + else + { + saber->blade[n].lengthMax = f; + } + continue; + } + + //blade radius + if ( !Q_stricmpn( token, "saberRadius", 11 ) ) + { + if (strlen(token)==11) + { + n = -1; + } + else if (strlen(token)==12) + { + n = atoi(&token[11])-1; + if (n > 7 || n < 1 ) + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, useSaber ); + continue; + } + } + else + { + Com_Printf( S_COLOR_YELLOW"WARNING: bad saberRadius '%s' in %s\n", token, useSaber ); + continue; + } + + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + //cap + if ( f < 0.25f ) + { + f = 0.25f; + } + if (n==-1) + {//NOTE: this fills in the rest of the blades with the same length by default + for ( n = 0; n < MAX_BLADES; n++ ) + { + saber->blade[n].radius = f; + } + } + else + { + saber->blade[n].radius = f; + } + continue; + } + + //locked saber style + if ( !Q_stricmp( token, "saberStyle" ) ) + { + int style, styleNum; + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //OLD WAY: only allowed ONE style + style = TranslateSaberStyle( value ); + //learn only this style + saber->stylesLearned = (1<stylesForbidden = 0; + for ( styleNum = SS_NONE+1; styleNum < SS_NUM_SABER_STYLES; styleNum++ ) + { + if ( styleNum != style ) + { + saber->stylesForbidden |= (1<stylesLearned |= (1<stylesForbidden |= (1<maxChain = n; + continue; + } + + //lockable + if ( !Q_stricmp( token, "lockable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n == 0 ) + { + saber->saberFlags |= SFL_NOT_LOCKABLE; + } + continue; + } + + //throwable + if ( !Q_stricmp( token, "throwable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n == 0 ) + { + saber->saberFlags |= SFL_NOT_THROWABLE; + } + continue; + } + + //disarmable + if ( !Q_stricmp( token, "disarmable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n == 0 ) + { + saber->saberFlags |= SFL_NOT_DISARMABLE; + } + continue; + } + + //active blocking + if ( !Q_stricmp( token, "blocking" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n == 0 ) + { + saber->saberFlags |= SFL_NOT_ACTIVE_BLOCKING; + } + continue; + } + + //twoHanded + if ( !Q_stricmp( token, "twoHanded" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_TWO_HANDED; + } + continue; + } + + //force power restrictions + if ( !Q_stricmp( token, "forceRestrict" ) ) + { + int fp; + + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + fp = GetIDForString( FPTable, value ); + if ( fp >= FP_FIRST && fp < NUM_FORCE_POWERS ) + { + saber->forceRestrictions |= (1<lockBonus = n; + continue; + } + + //parryBonus + if ( !Q_stricmp( token, "parryBonus" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->parryBonus = n; + continue; + } + + //breakParryBonus + if ( !Q_stricmp( token, "breakParryBonus" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->breakParryBonus = n; + continue; + } + + //breakParryBonus2 + if ( !Q_stricmp( token, "breakParryBonus2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->breakParryBonus2 = n; + continue; + } + + //disarmBonus + if ( !Q_stricmp( token, "disarmBonus" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->disarmBonus = n; + continue; + } + + //disarmBonus2 + if ( !Q_stricmp( token, "disarmBonus2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->disarmBonus2 = n; + continue; + } + + //single blade saber style + if ( !Q_stricmp( token, "singleBladeStyle" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->singleBladeStyle = TranslateSaberStyle( value ); + continue; + } + + //single blade throwable + if ( !Q_stricmp( token, "singleBladeThrowable" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_SINGLE_BLADE_THROWABLE; + } + continue; + } + + //broken replacement saber1 (right hand) + if ( !Q_stricmp( token, "brokenSaber1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //saber->brokenSaber1 = G_NewString( value ); + continue; + } + + //broken replacement saber2 (left hand) + if ( !Q_stricmp( token, "brokenSaber2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + //saber->brokenSaber2 = G_NewString( value ); + continue; + } + + //spins and does damage on return from saberthrow + if ( !Q_stricmp( token, "returnDamage" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_RETURN_DAMAGE; + } + continue; + } + + //spin sound (when thrown) + if ( !Q_stricmp( token, "spinSound" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->spinSound = BG_SoundIndex( (char *)value ); + continue; + } + + //swing sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "swingSound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->swingSound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //swing sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "swingSound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->swingSound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //swing sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "swingSound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->swingSound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //you move faster/slower when using this saber + if ( !Q_stricmp( token, "moveSpeedScale" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->moveSpeedScale = f; + continue; + } + + //plays normal attack animations faster/slower + if ( !Q_stricmp( token, "animSpeedScale" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->animSpeedScale = f; + continue; + } + + //if non-zero, the saber will bounce back when it hits solid architecture (good for real-sword type mods) + if ( !Q_stricmp( token, "bounceOnWalls" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_BOUNCE_ON_WALLS; + } + continue; + } + + //if set, saber model is bolted to wrist, not in hand... useful for things like claws & shields, etc. + if ( !Q_stricmp( token, "boltToWrist" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_BOLT_TO_WRIST; + } + continue; + } + + //kata move + if ( !Q_stricmp( token, "kataMove" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saberMove = GetIDForString( SaberMoveTable, value ); + if ( saberMove >= LS_INVALID && saberMove < LS_MOVE_MAX ) + { + saber->kataMove = saberMove; //LS_INVALID - if set, player will execute this move when they press both attack buttons at the same time + } + continue; + } + //lungeAtkMove move + if ( !Q_stricmp( token, "lungeAtkMove" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saberMove = GetIDForString( SaberMoveTable, value ); + if ( saberMove >= LS_INVALID && saberMove < LS_MOVE_MAX ) + { + saber->lungeAtkMove = saberMove; + } + continue; + } + //jumpAtkUpMove move + if ( !Q_stricmp( token, "jumpAtkUpMove" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saberMove = GetIDForString( SaberMoveTable, value ); + if ( saberMove >= LS_INVALID && saberMove < LS_MOVE_MAX ) + { + saber->jumpAtkUpMove = saberMove; + } + continue; + } + //jumpAtkFwdMove move + if ( !Q_stricmp( token, "jumpAtkFwdMove" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saberMove = GetIDForString( SaberMoveTable, value ); + if ( saberMove >= LS_INVALID && saberMove < LS_MOVE_MAX ) + { + saber->jumpAtkFwdMove = saberMove; + } + continue; + } + //jumpAtkBackMove move + if ( !Q_stricmp( token, "jumpAtkBackMove" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saberMove = GetIDForString( SaberMoveTable, value ); + if ( saberMove >= LS_INVALID && saberMove < LS_MOVE_MAX ) + { + saber->jumpAtkBackMove = saberMove; + } + continue; + } + //jumpAtkRightMove move + if ( !Q_stricmp( token, "jumpAtkRightMove" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saberMove = GetIDForString( SaberMoveTable, value ); + if ( saberMove >= LS_INVALID && saberMove < LS_MOVE_MAX ) + { + saber->jumpAtkRightMove = saberMove; + } + continue; + } + //jumpAtkLeftMove move + if ( !Q_stricmp( token, "jumpAtkLeftMove" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saberMove = GetIDForString( SaberMoveTable, value ); + if ( saberMove >= LS_INVALID && saberMove < LS_MOVE_MAX ) + { + saber->jumpAtkLeftMove = saberMove; + } + continue; + } + //readyAnim + if ( !Q_stricmp( token, "readyAnim" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + anim = GetIDForString( animTable, value ); + if ( anim >= 0 && anim < MAX_ANIMATIONS ) + { + saber->readyAnim = anim; + } + continue; + } + //drawAnim + if ( !Q_stricmp( token, "drawAnim" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + anim = GetIDForString( animTable, value ); + if ( anim >= 0 && anim < MAX_ANIMATIONS ) + { + saber->drawAnim = anim; + } + continue; + } + //putawayAnim + if ( !Q_stricmp( token, "putawayAnim" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + anim = GetIDForString( animTable, value ); + if ( anim >= 0 && anim < MAX_ANIMATIONS ) + { + saber->putawayAnim = anim; + } + continue; + } + //tauntAnim + if ( !Q_stricmp( token, "tauntAnim" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + anim = GetIDForString( animTable, value ); + if ( anim >= 0 && anim < MAX_ANIMATIONS ) + { + saber->tauntAnim = anim; + } + continue; + } + //bowAnim + if ( !Q_stricmp( token, "bowAnim" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + anim = GetIDForString( animTable, value ); + if ( anim >= 0 && anim < MAX_ANIMATIONS ) + { + saber->bowAnim = anim; + } + continue; + } + //meditateAnim + if ( !Q_stricmp( token, "meditateAnim" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + anim = GetIDForString( animTable, value ); + if ( anim >= 0 && anim < MAX_ANIMATIONS ) + { + saber->meditateAnim = anim; + } + continue; + } + //flourishAnim + if ( !Q_stricmp( token, "flourishAnim" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + anim = GetIDForString( animTable, value ); + if ( anim >= 0 && anim < MAX_ANIMATIONS ) + { + saber->flourishAnim = anim; + } + continue; + } + //gloatAnim + if ( !Q_stricmp( token, "gloatAnim" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + anim = GetIDForString( animTable, value ); + if ( anim >= 0 && anim < MAX_ANIMATIONS ) + { + saber->gloatAnim = anim; + } + continue; + } + + //if set, cannot do roll-stab move at end of roll + if ( !Q_stricmp( token, "noRollStab" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_ROLL_STAB; + } + continue; + } + + //if set, cannot do pull+attack move (move not available in MP anyway) + if ( !Q_stricmp( token, "noPullAttack" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_PULL_ATTACK; + } + continue; + } + + //if set, cannot do back-stab moves + if ( !Q_stricmp( token, "noBackAttack" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_BACK_ATTACK; + } + continue; + } + + //if set, cannot do stabdown move (when enemy is on ground) + if ( !Q_stricmp( token, "noStabDown" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_STABDOWN; + } + continue; + } + + //if set, cannot side-run or forward-run on walls + if ( !Q_stricmp( token, "noWallRuns" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_WALL_RUNS; + } + continue; + } + + //if set, cannot do backflip off wall or side-flips off walls + if ( !Q_stricmp( token, "noWallFlips" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_WALL_FLIPS; + } + continue; + } + + //if set, cannot grab wall & jump off + if ( !Q_stricmp( token, "noWallGrab" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_WALL_GRAB; + } + continue; + } + + //if set, cannot roll + if ( !Q_stricmp( token, "noRolls" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_ROLLS; + } + continue; + } + + //if set, cannot do flips + if ( !Q_stricmp( token, "noFlips" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_FLIPS; + } + continue; + } + + //if set, cannot do cartwheels + if ( !Q_stricmp( token, "noCartwheels" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_CARTWHEELS; + } + continue; + } + + //if set, cannot do kicks (can't do kicks anyway if using a throwable saber/sword) + if ( !Q_stricmp( token, "noKicks" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_KICKS; + } + continue; + } + + //if set, cannot do the simultaneous attack left/right moves (only available in Dual Lightsaber Combat Style) + if ( !Q_stricmp( token, "noMirrorAttacks" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags |= SFL_NO_MIRROR_ATTACKS; + } + continue; + } + + //stays on in water + if ( !Q_stricmp( token, "onInWater" ) ) + {//ignore in MP + SkipRestOfLine( &p ); + continue; + } + + if ( !Q_stricmp( token, "notInMP" ) ) + {//ignore this + SkipRestOfLine( &p ); + continue; + } + +//===ABOVE THIS, ALL VALUES ARE GLOBAL TO THE SABER======================================================== + //bladeStyle2Start - where to start using the second set of blade data + if ( !Q_stricmp( token, "bladeStyle2Start" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->bladeStyle2Start = n; + continue; + } +//===BLADE-SPECIFIC FIELDS================================================================================= + + //===PRIMARY BLADE==================================== + //stops the saber from drawing marks on the world (good for real-sword type mods) + if ( !Q_stricmp( token, "noWallMarks" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_WALL_MARKS; + } + continue; + } + + //stops the saber from drawing a dynamic light (good for real-sword type mods) + if ( !Q_stricmp( token, "noDlight" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_DLIGHT; + } + continue; + } + + //stops the saber from drawing a blade (good for real-sword type mods) + if ( !Q_stricmp( token, "noBlade" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_BLADE; + } + continue; + } + + //default (0) is normal, 1 is a motion blur and 2 is no trail at all (good for real-sword type mods) + if ( !Q_stricmp( token, "trailStyle" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->trailStyle = n; + continue; + } + + //if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + if ( !Q_stricmp( token, "g2MarksShader" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + SkipRestOfLine( &p ); + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->g2MarksShader = trap_R_RegisterShader( value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + if ( !Q_stricmp( token, "g2WeaponMarkShader" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + SkipRestOfLine( &p ); + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->g2WeaponMarkShader = trap_R_RegisterShader( value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //if non-zero, uses damage done to calculate an appropriate amount of knockback + if ( !Q_stricmp( token, "knockbackScale" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->knockbackScale = f; + continue; + } + + //scale up or down the damage done by the saber + if ( !Q_stricmp( token, "damageScale" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->damageScale = f; + continue; + } + + //if non-zero, the saber never does dismemberment (good for pointed/blunt melee weapons) + if ( !Q_stricmp( token, "noDismemberment" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_DISMEMBERMENT; + } + continue; + } + + //if non-zero, the saber will not do damage or any effects when it is idle (not in an attack anim). (good for real-sword type mods) + if ( !Q_stricmp( token, "noIdleEffect" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_IDLE_EFFECT; + } + continue; + } + + //if set, the blades will always be blocking (good for things like shields that should always block) + if ( !Q_stricmp( token, "alwaysBlock" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_ALWAYS_BLOCK; + } + continue; + } + + //if set, the blades cannot manually be toggled on and off + if ( !Q_stricmp( token, "noManualDeactivate" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_MANUAL_DEACTIVATE; + } + continue; + } + + //if set, the blade does damage in start, transition and return anims (like strong style does) + if ( !Q_stricmp( token, "transitionDamage" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_TRANSITION_DAMAGE; + } + continue; + } + + //splashRadius - radius of splashDamage + if ( !Q_stricmp( token, "splashRadius" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->splashRadius = f; + continue; + } + + //splashDamage - amount of splashDamage, 100% at a distance of 0, 0% at a distance = splashRadius + if ( !Q_stricmp( token, "splashDamage" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->splashDamage = n; + continue; + } + + //splashKnockback - amount of splashKnockback, 100% at a distance of 0, 0% at a distance = splashRadius + if ( !Q_stricmp( token, "splashKnockback" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->splashKnockback = f; + continue; + } + + //hit sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "hitSound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->hitSound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //hit sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "hitSound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->hitSound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //hit sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "hitSound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->hitSound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //block sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "blockSound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->blockSound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //block sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "blockSound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->blockSound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //block sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "blockSound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->blockSound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //bounce sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "bounceSound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->bounceSound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //bounce sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "bounceSound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->bounceSound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //bounce sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "bounceSound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->bounceSound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //block effect - when saber/sword hits another saber/sword + if ( !Q_stricmp( token, "blockEffect" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->blockEffect = trap_FX_RegisterEffect( (char *)value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //hit person effect - when saber/sword hits a person + if ( !Q_stricmp( token, "hitPersonEffect" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->hitPersonEffect = trap_FX_RegisterEffect( (char *)value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //hit other effect - when saber/sword hits sopmething else damagable + if ( !Q_stricmp( token, "hitOtherEffect" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->hitOtherEffect = trap_FX_RegisterEffect( (char *)value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //blade effect + if ( !Q_stricmp( token, "bladeEffect" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->bladeEffect = trap_FX_RegisterEffect( (char *)value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //if non-zero, the saber will not do the big, white clash flare with other sabers + if ( !Q_stricmp( token, "noClashFlare" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_CLASH_FLARE; + } + continue; + } + + //===SECONDARY BLADE==================================== + //stops the saber from drawing marks on the world (good for real-sword type mods) + if ( !Q_stricmp( token, "noWallMarks2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_WALL_MARKS2; + } + continue; + } + + //stops the saber from drawing a dynamic light (good for real-sword type mods) + if ( !Q_stricmp( token, "noDlight2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_DLIGHT2; + } + continue; + } + + //stops the saber from drawing a blade (good for real-sword type mods) + if ( !Q_stricmp( token, "noBlade2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_BLADE2; + } + continue; + } + + //default (0) is normal, 1 is a motion blur and 2 is no trail at all (good for real-sword type mods) + if ( !Q_stricmp( token, "trailStyle2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->trailStyle2 = n; + continue; + } + + //if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + if ( !Q_stricmp( token, "g2MarksShader2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + SkipRestOfLine( &p ); + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->g2MarksShader2 = trap_R_RegisterShader( value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //if set, the game will use this shader for marks on enemies instead of the default "gfx/damage/saberglowmark" + if ( !Q_stricmp( token, "g2WeaponMarkShader2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + SkipRestOfLine( &p ); + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->g2WeaponMarkShader2 = trap_R_RegisterShader( value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //if non-zero, uses damage done to calculate an appropriate amount of knockback + if ( !Q_stricmp( token, "knockbackScale2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->knockbackScale2 = f; + continue; + } + + //scale up or down the damage done by the saber + if ( !Q_stricmp( token, "damageScale2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->damageScale2 = f; + continue; + } + + //if non-zero, the saber never does dismemberment (good for pointed/blunt melee weapons) + if ( !Q_stricmp( token, "noDismemberment2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_DISMEMBERMENT2; + } + continue; + } + + //if non-zero, the saber will not do damage or any effects when it is idle (not in an attack anim). (good for real-sword type mods) + if ( !Q_stricmp( token, "noIdleEffect2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_IDLE_EFFECT2; + } + continue; + } + + //if set, the blades will always be blocking (good for things like shields that should always block) + if ( !Q_stricmp( token, "alwaysBlock2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_ALWAYS_BLOCK2; + } + continue; + } + + //if set, the blades cannot manually be toggled on and off + if ( !Q_stricmp( token, "noManualDeactivate2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_MANUAL_DEACTIVATE2; + } + continue; + } + + //if set, the blade does damage in start, transition and return anims (like strong style does) + if ( !Q_stricmp( token, "transitionDamage2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_TRANSITION_DAMAGE2; + } + continue; + } + + //splashRadius - radius of splashDamage + if ( !Q_stricmp( token, "splashRadius2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->splashRadius2 = f; + continue; + } + + //splashDamage - amount of splashDamage, 100% at a distance of 0, 0% at a distance = splashRadius + if ( !Q_stricmp( token, "splashDamage2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->splashDamage2 = n; + continue; + } + + //splashKnockback - amount of splashKnockback, 100% at a distance of 0, 0% at a distance = splashRadius + if ( !Q_stricmp( token, "splashKnockback2" ) ) + { + if ( COM_ParseFloat( &p, &f ) ) + { + SkipRestOfLine( &p ); + continue; + } + saber->splashKnockback2 = f; + continue; + } + + //hit sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "hit2Sound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->hit2Sound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //hit sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "hit2Sound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->hit2Sound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //hit sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "hit2Sound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->hit2Sound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //block sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "block2Sound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->block2Sound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //block sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "block2Sound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->block2Sound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //block sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "block2Sound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->block2Sound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //bounce sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "bounce2Sound1" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->bounce2Sound[0] = BG_SoundIndex( (char *)value ); + continue; + } + + //bounce sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "bounce2Sound2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->bounce2Sound[1] = BG_SoundIndex( (char *)value ); + continue; + } + + //bounce sound - NOTE: must provide all 3!!! + if ( !Q_stricmp( token, "bounce2Sound3" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + saber->bounce2Sound[2] = BG_SoundIndex( (char *)value ); + continue; + } + + //block effect - when saber/sword hits another saber/sword + if ( !Q_stricmp( token, "blockEffect2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->blockEffect2 = trap_FX_RegisterEffect( (char *)value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //hit person effect - when saber/sword hits a person + if ( !Q_stricmp( token, "hitPersonEffect2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->hitPersonEffect2 = trap_FX_RegisterEffect( (char *)value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //hit other effect - when saber/sword hits sopmething else damagable + if ( !Q_stricmp( token, "hitOtherEffect2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->hitOtherEffect2 = trap_FX_RegisterEffect( (char *)value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //blade effect + if ( !Q_stricmp( token, "bladeEffect2" ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } +#ifdef QAGAME//cgame-only cares about this + SkipRestOfLine(&p); +#elif defined CGAME + saber->bladeEffect2 = trap_FX_RegisterEffect( (char *)value ); +#else + SkipRestOfLine(&p); +#endif + continue; + } + + //if non-zero, the saber will not do the big, white clash flare with other sabers + if ( !Q_stricmp( token, "noClashFlare2" ) ) + { + if ( COM_ParseInt( &p, &n ) ) + { + SkipRestOfLine( &p ); + continue; + } + if ( n ) + { + saber->saberFlags2 |= SFL2_NO_CLASH_FLARE2; + } + continue; + } +//===END BLADE-SPECIFIC FIELDS============================================================================= + + //FIXME: saber sounds (on, off, loop) + +#ifdef _DEBUG + Com_Printf( "WARNING: unknown keyword '%s' while parsing '%s'\n", token, useSaber ); +#endif + SkipRestOfLine( &p ); + } + + //FIXME: precache the saberModel(s)? + + return qtrue; +} + +qboolean WP_SaberParseParm( const char *saberName, const char *parmname, char *saberData ) +{ + const char *token; + const char *value; + const char *p; + + if ( !saberName || !saberName[0] ) + { + return qfalse; + } + + //try to parse it out + p = SaberParms; + COM_BeginParseSession("saberinfo"); + + // look for the right saber + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, saberName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + if ( BG_ParseLiteral( &p, "{" ) ) + { + return qfalse; + } + + // parse the saber info block + while ( 1 ) + { + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing '%s'\n", saberName ); + return qfalse; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + + if ( !Q_stricmp( token, parmname ) ) + { + if ( COM_ParseString( &p, &value ) ) + { + continue; + } + strcpy( saberData, value ); + return qtrue; + } + + SkipRestOfLine( &p ); + continue; + } + + return qfalse; +} + +qboolean WP_SaberValidForPlayerInMP( const char *saberName ) +{ + char allowed [8]={0}; + if ( !WP_SaberParseParm( saberName, "notInMP", allowed ) ) + {//not defined, default is yes + return qtrue; + } + if ( !allowed[0] ) + {//not defined, default is yes + return qtrue; + } + else + {//return value + return ((qboolean)(atoi(allowed)==0)); + } +} + +void WP_RemoveSaber( saberInfo_t *sabers, int saberNum ) +{ + if ( !sabers ) + { + return; + } + //reset everything for this saber just in case + WP_SaberSetDefaults( &sabers[saberNum] ); + + strcpy(sabers[saberNum].name, "none"); + sabers[saberNum].model[0] = 0; + + //ent->client->ps.dualSabers = qfalse; + BG_SI_Deactivate(&sabers[saberNum]); + BG_SI_SetLength(&sabers[saberNum], 0.0f); +// if ( ent->weaponModel[saberNum] > 0 ) +// { +// gi.G2API_RemoveGhoul2Model( ent->ghoul2, ent->weaponModel[saberNum] ); +// ent->weaponModel[saberNum] = -1; +// } +// if ( saberNum == 1 ) +// { +// ent->client->ps.dualSabers = qfalse; +// } +} + +void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ) +{ + if ( !sabers ) + { + return; + } + if ( Q_stricmp( "none", saberName ) == 0 || Q_stricmp( "remove", saberName ) == 0 ) + { + if (saberNum != 0) + { //can't remove saber 0 ever + WP_RemoveSaber( sabers, saberNum ); + } + return; + } + + if ( entNum < MAX_CLIENTS && + !WP_SaberValidForPlayerInMP( saberName ) ) + { + WP_SaberParseParms( "Kyle", &sabers[saberNum] );//get saber info + } + else + { + WP_SaberParseParms( saberName, &sabers[saberNum] );//get saber info + } + if ((sabers[1].saberFlags&SFL_TWO_HANDED)) + {//not allowed to use a 2-handed saber as second saber + WP_RemoveSaber( sabers, 1 ); + return; + } + else if ((sabers[0].saberFlags&SFL_TWO_HANDED) && + sabers[1].model[0]) + { //you can't use a two-handed saber with a second saber, so remove saber 2 + WP_RemoveSaber( sabers, 1 ); + return; + } +} + +void WP_SaberSetColor( saberInfo_t *sabers, int saberNum, int bladeNum, char *colorName ) +{ + if ( !sabers ) + { + return; + } + sabers[saberNum].blade[bladeNum].color = TranslateSaberColor( colorName ); +} + +static char bgSaberParseTBuffer[MAX_SABER_DATA_SIZE]; + +void WP_SaberLoadParms( void ) +{ + int len, totallen, saberExtFNLen, mainBlockLen, fileCnt, i; + //const char *filename = "ext_data/sabers.cfg"; + char *holdChar, *marker; + char saberExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = SaberParms+totallen; + *marker = 0; + + //now load in the extra .sab extensions + fileCnt = trap_FS_GetFileList("ext_data/sabers", ".sab", saberExtensionListBuf, sizeof(saberExtensionListBuf) ); + + holdChar = saberExtensionListBuf; + for ( i = 0; i < fileCnt; i++, holdChar += saberExtFNLen + 1 ) + { + saberExtFNLen = strlen( holdChar ); + + len = trap_FS_FOpenFile(va( "ext_data/sabers/%s", holdChar), &f, FS_READ); + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { + if ( (totallen + len + 1/*for the endline*/) >= MAX_SABER_DATA_SIZE ) { + Com_Error(ERR_DROP, "Saber extensions (*.sab) are too large" ); + } + + trap_FS_Read(bgSaberParseTBuffer, len, f); + bgSaberParseTBuffer[len] = 0; + + len = COM_Compress( bgSaberParseTBuffer ); + + Q_strcat( marker, MAX_SABER_DATA_SIZE-totallen, bgSaberParseTBuffer ); + trap_FS_FCloseFile(f); + + //get around the stupid problem of not having an endline at the bottom + //of a sab file -rww + Q_strcat(marker, MAX_SABER_DATA_SIZE-totallen, "\n"); + len++; + + totallen += len; + marker = SaberParms+totallen; + } + } +} + +/* +rww - +The following were struct functions in SP. Of course +we can't have that in this codebase so I'm having to +externalize them. Which is why this probably seems +structured a bit oddly. But it's to make porting stuff +easier on myself. SI indicates it was under saberinfo, +and BLADE indicates it was under bladeinfo. +*/ + +//--------------------------------------- +void BG_BLADE_ActivateTrail ( bladeInfo_t *blade, float duration ) +{ + blade->trail.inAction = qtrue; + blade->trail.duration = duration; +} + +void BG_BLADE_DeactivateTrail ( bladeInfo_t *blade, float duration ) +{ + blade->trail.inAction = qfalse; + blade->trail.duration = duration; +} +//--------------------------------------- +void BG_SI_Activate( saberInfo_t *saber ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + saber->blade[i].active = qtrue; + } +} + +void BG_SI_Deactivate( saberInfo_t *saber ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + saber->blade[i].active = qfalse; + } +} + +// Description: Activate a specific Blade of this Saber. +// Created: 10/03/02 by Aurelio Reis, Modified: 10/03/02 by Aurelio Reis. +// [in] int iBlade Which Blade to activate. +// [in] bool bActive Whether to activate it (default true), or deactivate it (false). +// [return] void +void BG_SI_BladeActivate( saberInfo_t *saber, int iBlade, qboolean bActive ) +{ + // Validate blade ID/Index. + if ( iBlade < 0 || iBlade >= saber->numBlades ) + return; + + saber->blade[iBlade].active = bActive; +} + +qboolean BG_SI_Active(saberInfo_t *saber) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + if ( saber->blade[i].active ) + { + return qtrue; + } + } + return qfalse; +} + +void BG_SI_SetLength( saberInfo_t *saber, float length ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + saber->blade[i].length = length; + } +} + +//not in sp, added it for my own convenience +void BG_SI_SetDesiredLength(saberInfo_t *saber, float len, int bladeNum ) +{ + int i, startBlade = 0, maxBlades = saber->numBlades; + + if ( bladeNum >= 0 && bladeNum < saber->numBlades) + {//doing this on a specific blade + startBlade = bladeNum; + maxBlades = bladeNum+1; + } + for (i = startBlade; i < maxBlades; i++) + { + saber->blade[i].desiredLength = len; + } +} + +//also not in sp, added it for my own convenience +void BG_SI_SetLengthGradual(saberInfo_t *saber, int time) +{ + int i; + float amt, dLen; + + for (i = 0; i < saber->numBlades; i++) + { + dLen = saber->blade[i].desiredLength; + + if (dLen == -1) + { //assume we want max blade len + dLen = saber->blade[i].lengthMax; + } + + if (saber->blade[i].length == dLen) + { + continue; + } + + if (saber->blade[i].length == saber->blade[i].lengthMax || + saber->blade[i].length == 0) + { + saber->blade[i].extendDebounce = time; + if (saber->blade[i].length == 0) + { + saber->blade[i].length++; + } + else + { + saber->blade[i].length--; + } + } + + amt = (time - saber->blade[i].extendDebounce)*0.01; + + if (amt < 0.2f) + { + amt = 0.2f; + } + + if (saber->blade[i].length < dLen) + { + saber->blade[i].length += amt; + + if (saber->blade[i].length > dLen) + { + saber->blade[i].length = dLen; + } + if (saber->blade[i].length > saber->blade[i].lengthMax) + { + saber->blade[i].length = saber->blade[i].lengthMax; + } + } + else if (saber->blade[i].length > dLen) + { + saber->blade[i].length -= amt; + + if (saber->blade[i].length < dLen) + { + saber->blade[i].length = dLen; + } + if (saber->blade[i].length < 0) + { + saber->blade[i].length = 0; + } + } + } +} + +float BG_SI_Length(saberInfo_t *saber) +{//return largest length + int len1 = 0; + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + if ( saber->blade[i].length > len1 ) + { + len1 = saber->blade[i].length; + } + } + return len1; +} + +float BG_SI_LengthMax(saberInfo_t *saber) +{ + int len1 = 0; + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + if ( saber->blade[i].lengthMax > len1 ) + { + len1 = saber->blade[i].lengthMax; + } + } + return len1; +} + +void BG_SI_ActivateTrail ( saberInfo_t *saber, float duration ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + //saber->blade[i].ActivateTrail( duration ); + BG_BLADE_ActivateTrail(&saber->blade[i], duration); + } +} + +void BG_SI_DeactivateTrail ( saberInfo_t *saber, float duration ) +{ + int i; + + for ( i = 0; i < saber->numBlades; i++ ) + { + //saber->blade[i].DeactivateTrail( duration ); + BG_BLADE_DeactivateTrail(&saber->blade[i], duration); + } +} + +#include "../namespace_end.h" diff --git a/code/game/bg_saga.c b/code/game/bg_saga.c new file mode 100644 index 0000000..02a0553 --- /dev/null +++ b/code/game/bg_saga.c @@ -0,0 +1,1508 @@ +// Copyright (C) 2000-2002 Raven Software, Inc. +// +/***************************************************************************** + * name: bg_saga.c + * + * desc: Siege module, shared for game, cgame, and ui. + * + * $Author: osman $ + * $Revision: 1.9 $ + * + *****************************************************************************/ +#include "../qcommon/q_shared.h" +#include "../game/bg_saga.h" +#include "../game/bg_weapons.h" +#include "../game/bg_public.h" + +#define SIEGECHAR_TAB 9 //perhaps a bit hacky, but I don't think there's any define existing for "tab" + +//Could use strap stuff but I don't particularly care at the moment anyway. +//#include "../namespace_begin.h" + +extern int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +extern void trap_FS_Read( void *buffer, int len, fileHandle_t f ); +extern void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); +extern void trap_FS_FCloseFile( fileHandle_t f ); +extern int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); + +#ifndef QAGAME //cgame, ui +qhandle_t trap_R_RegisterShaderNoMip( const char *name ); +#endif + +char siege_info[MAX_SIEGE_INFO_SIZE]; +int siege_valid = 0; + +siegeTeam_t *team1Theme = NULL; +siegeTeam_t *team2Theme = NULL; + +siegeClass_t bgSiegeClasses[MAX_SIEGE_CLASSES]; +int bgNumSiegeClasses = 0; + +siegeTeam_t bgSiegeTeams[MAX_SIEGE_TEAMS]; +int bgNumSiegeTeams = 0; + +//class flags +stringID_table_t bgSiegeClassFlagNames[] = +{ + ENUM2STRING(CFL_MORESABERDMG), + ENUM2STRING(CFL_STRONGAGAINSTPHYSICAL), + ENUM2STRING(CFL_FASTFORCEREGEN), + ENUM2STRING(CFL_STATVIEWER), + ENUM2STRING(CFL_HEAVYMELEE), + ENUM2STRING(CFL_SINGLE_ROCKET), + ENUM2STRING(CFL_CUSTOMSKEL), + ENUM2STRING(CFL_EXTRA_AMMO), + "", -1 +}; + +//saber stances +stringID_table_t StanceTable[] = +{ + ENUM2STRING(SS_NONE), + ENUM2STRING(SS_FAST), + ENUM2STRING(SS_MEDIUM), + ENUM2STRING(SS_STRONG), + ENUM2STRING(SS_DESANN), + ENUM2STRING(SS_TAVION), + ENUM2STRING(SS_DUAL), + ENUM2STRING(SS_STAFF), + "", 0 +}; + +//Weapon and force power tables are also used in NPC parsing code and some other places. +stringID_table_t WPTable[] = +{ + "NULL",WP_NONE, + ENUM2STRING(WP_NONE), + // Player weapons + ENUM2STRING(WP_STUN_BATON), + ENUM2STRING(WP_MELEE), + ENUM2STRING(WP_SABER), + ENUM2STRING(WP_BRYAR_PISTOL), + "WP_BLASTER_PISTOL", WP_BRYAR_PISTOL, + ENUM2STRING(WP_BLASTER), + ENUM2STRING(WP_DISRUPTOR), + ENUM2STRING(WP_BOWCASTER), + ENUM2STRING(WP_REPEATER), + ENUM2STRING(WP_DEMP2), + ENUM2STRING(WP_FLECHETTE), + ENUM2STRING(WP_ROCKET_LAUNCHER), + ENUM2STRING(WP_THERMAL), + ENUM2STRING(WP_TRIP_MINE), + ENUM2STRING(WP_DET_PACK), + ENUM2STRING(WP_CONCUSSION), + ENUM2STRING(WP_BRYAR_OLD), + ENUM2STRING(WP_EMPLACED_GUN), + ENUM2STRING(WP_TURRET), + "", 0 +}; + +stringID_table_t FPTable[] = +{ + ENUM2STRING(FP_HEAL), + ENUM2STRING(FP_LEVITATION), + ENUM2STRING(FP_SPEED), + ENUM2STRING(FP_PUSH), + ENUM2STRING(FP_PULL), + ENUM2STRING(FP_TELEPATHY), + ENUM2STRING(FP_GRIP), + ENUM2STRING(FP_LIGHTNING), + ENUM2STRING(FP_RAGE), + ENUM2STRING(FP_PROTECT), + ENUM2STRING(FP_ABSORB), + ENUM2STRING(FP_TEAM_HEAL), + ENUM2STRING(FP_TEAM_FORCE), + ENUM2STRING(FP_DRAIN), + ENUM2STRING(FP_SEE), + ENUM2STRING(FP_SABER_OFFENSE), + ENUM2STRING(FP_SABER_DEFENSE), + ENUM2STRING(FP_SABERTHROW), + "", -1 +}; + +stringID_table_t HoldableTable[] = +{ + ENUM2STRING(HI_NONE), + + ENUM2STRING(HI_SEEKER), + ENUM2STRING(HI_SHIELD), + ENUM2STRING(HI_MEDPAC), + ENUM2STRING(HI_MEDPAC_BIG), + ENUM2STRING(HI_BINOCULARS), + ENUM2STRING(HI_SENTRY_GUN), + ENUM2STRING(HI_JETPACK), + ENUM2STRING(HI_HEALTHDISP), + ENUM2STRING(HI_AMMODISP), + ENUM2STRING(HI_EWEB), + ENUM2STRING(HI_CLOAK), + + "", -1 +}; + +stringID_table_t PowerupTable[] = +{ + ENUM2STRING(PW_NONE), + ENUM2STRING(PW_QUAD), + ENUM2STRING(PW_BATTLESUIT), + ENUM2STRING(PW_PULL), + ENUM2STRING(PW_REDFLAG), + ENUM2STRING(PW_BLUEFLAG), + ENUM2STRING(PW_NEUTRALFLAG), + ENUM2STRING(PW_SHIELDHIT), + ENUM2STRING(PW_SPEEDBURST), + ENUM2STRING(PW_DISINT_4), + ENUM2STRING(PW_SPEED), + ENUM2STRING(PW_CLOAKED), + ENUM2STRING(PW_FORCE_ENLIGHTENED_LIGHT), + ENUM2STRING(PW_FORCE_ENLIGHTENED_DARK), + ENUM2STRING(PW_FORCE_BOON), + ENUM2STRING(PW_YSALAMIRI), + + "", -1 +}; + + +//====================================== +//Parsing functions +//====================================== +void BG_SiegeStripTabs(char *buf) +{ + int i = 0; + int i_r = 0; + + while (buf[i]) + { + if (buf[i] != SIEGECHAR_TAB) + { //not a tab, just stick it in + buf[i_r] = buf[i]; + } + else + { //If it's a tab, convert it to a space. + buf[i_r] = ' '; + } + + i_r++; + i++; + } + + buf[i_r] = '\0'; +} + +int BG_SiegeGetValueGroup(char *buf, char *group, char *outbuf) +{ + int i = 0; + int j; + char checkGroup[4096]; + qboolean isGroup; + int parseGroups = 0; + + while (buf[i]) + { + if (buf[i] != ' ' && buf[i] != '{' && buf[i] != '}' && buf[i] != '\n' && buf[i] != '\r' && buf[i] != SIEGECHAR_TAB) + { //we're on a valid character + if (buf[i] == '/' && + buf[i+1] == '/') + { //this is a comment, so skip over it + while (buf[i] && buf[i] != '\n' && buf[i] != '\r' && buf[i] != SIEGECHAR_TAB) + { + i++; + } + } + else + { //parse to the next space/endline/eos and check this value against our group value. + j = 0; + + while (buf[i] != ' ' && buf[i] != '\n' && buf[i] != '\r' && buf[i] != SIEGECHAR_TAB && buf[i] != '{' && buf[i]) + { + if (buf[i] == '/' && buf[i+1] == '/') + { //hit a comment, break out. + break; + } + + checkGroup[j] = buf[i]; + j++; + i++; + } + checkGroup[j] = 0; + + //Make sure this is a group as opposed to a globally defined value. + if (buf[i] == '/' && buf[i+1] == '/') + { //stopped on a comment, so first parse to the end of it. + while (buf[i] && buf[i] != '\n' && buf[i] != '\r') + { + i++; + } + while (buf[i] == '\n' || buf[i] == '\r') + { + i++; + } + } + + if (!buf[i]) + { + Com_Error(ERR_DROP, "Unexpected EOF while looking for group '%s'", group); + } + + isGroup = qfalse; + + while (buf[i] && buf[i] == ' ' || buf[i] == SIEGECHAR_TAB || buf[i] == '\n' || buf[i] == '\r') + { //parse to the next valid character + i++; + } + + if (buf[i] == '{') + { //if the next valid character is an opening bracket, then this is indeed a group + isGroup = qtrue; + } + + //Is this the one we want? + if (isGroup && !Q_stricmp(checkGroup, group)) + { //guess so. Parse until we hit the { indicating the beginning of the group. + while (buf[i] != '{' && buf[i]) + { + i++; + } + + if (buf[i]) + { //We're at the start of the group now, so parse to the closing bracket. + j = 0; + + parseGroups = 0; + + while ((buf[i] != '}' || parseGroups) && buf[i]) + { + if (buf[i] == '{') + { //increment for the opening bracket. + parseGroups++; + } + else if (buf[i] == '}') + { //decrement for the closing bracket + parseGroups--; + } + + if (parseGroups < 0) + { //Syntax error, I guess. + Com_Error(ERR_DROP, "Found a closing bracket without an opening bracket while looking for group '%s'", group); + } + + if ((buf[i] != '{' || parseGroups > 1) && + (buf[i] != '}' || parseGroups > 0)) + { //don't put the start and end brackets for this group into the output buffer + outbuf[j] = buf[i]; + j++; + } + + if (buf[i] == '}' && !parseGroups) + { //Alright, we can break out now. + break; + } + + i++; + } + outbuf[j] = 0; + + //Verify that we ended up on the closing bracket. + if (buf[i] != '}') + { + Com_Error(ERR_DROP, "Group '%s' is missing a closing bracket", group); + } + + //Strip the tabs so we're friendly for value parsing. + BG_SiegeStripTabs(outbuf); + + return 1; //we got it, so return 1. + } + else + { + Com_Error(ERR_DROP, "Error parsing group in file, unexpected EOF before opening bracket while looking for group '%s'", group); + } + } + else if (!isGroup) + { //if it wasn't a group, parse to the end of the line + while (buf[i] && buf[i] != '\n' && buf[i] != '\r') + { + i++; + } + } + else + { //this was a group but we not the one we wanted to find, so parse by it. + parseGroups = 0; + + while (buf[i] && (buf[i] != '}' || parseGroups)) + { + if (buf[i] == '{') + { + parseGroups++; + } + else if (buf[i] == '}') + { + parseGroups--; + } + + if (parseGroups < 0) + { //Syntax error, I guess. + Com_Error(ERR_DROP, "Found a closing bracket without an opening bracket while looking for group '%s'", group); + } + + if (buf[i] == '}' && !parseGroups) + { //Alright, we can break out now. + break; + } + + i++; + } + + if (buf[i] != '}') + { + Com_Error(ERR_DROP, "Found an opening bracket without a matching closing bracket while looking for group '%s'", group); + } + + i++; + } + } + } + else if (buf[i] == '{') + { //we're in a group that isn't the one we want, so parse to the end. + parseGroups = 0; + + while (buf[i] && (buf[i] != '}' || parseGroups)) + { + if (buf[i] == '{') + { + parseGroups++; + } + else if (buf[i] == '}') + { + parseGroups--; + } + + if (parseGroups < 0) + { //Syntax error, I guess. + Com_Error(ERR_DROP, "Found a closing bracket without an opening bracket while looking for group '%s'", group); + } + + if (buf[i] == '}' && !parseGroups) + { //Alright, we can break out now. + break; + } + + i++; + } + + if (buf[i] != '}') + { + Com_Error(ERR_DROP, "Found an opening bracket without a matching closing bracket while looking for group '%s'", group); + } + } + + if (!buf[i]) + { + break; + } + i++; + } + + return 0; //guess we never found it. +} + +int BG_SiegeGetPairedValue(char *buf, char *key, char *outbuf) +{ + int i = 0; + int j; + int k; + char checkKey[4096]; + + while (buf[i]) + { + if (buf[i] != ' ' && buf[i] != '{' && buf[i] != '}' && buf[i] != '\n' && buf[i] != '\r') + { //we're on a valid character + if (buf[i] == '/' && + buf[i+1] == '/') + { //this is a comment, so skip over it + while (buf[i] && buf[i] != '\n' && buf[i] != '\r') + { + i++; + } + } + else + { //parse to the next space/endline/eos and check this value against our key value. + j = 0; + + while (buf[i] != ' ' && buf[i] != '\n' && buf[i] != '\r' && buf[i] != SIEGECHAR_TAB && buf[i]) + { + if (buf[i] == '/' && buf[i+1] == '/') + { //hit a comment, break out. + break; + } + + checkKey[j] = buf[i]; + j++; + i++; + } + checkKey[j] = 0; + + k = i; + + while (buf[k] && (buf[k] == ' ' || buf[k] == '\n' || buf[k] == '\r')) + { + k++; + } + + if (buf[k] == '{') + { //this is not the start of a value but rather of a group. We don't want to look in subgroups so skip over the whole thing. + int openB = 0; + + while (buf[i] && (buf[i] != '}' || openB)) + { + if (buf[i] == '{') + { + openB++; + } + else if (buf[i] == '}') + { + openB--; + } + + if (openB < 0) + { + Com_Error(ERR_DROP, "Unexpected closing bracket (too many) while parsing to end of group '%s'", checkKey); + } + + if (buf[i] == '}' && !openB) + { //this is the end of the group + break; + } + i++; + } + + if (buf[i] == '}') + { + i++; + } + } + else + { + //Is this the one we want? + if (buf[i] != '/' || buf[i+1] != '/') + { //make sure we didn't stop on a comment, if we did then this is considered an error in the file. + if (!Q_stricmp(checkKey, key)) + { //guess so. Parse along to the next valid character, then put that into the output buffer and return 1. + while ((buf[i] == ' ' || buf[i] == '\n' || buf[i] == '\r' || buf[i] == SIEGECHAR_TAB) && buf[i]) + { + i++; + } + + if (buf[i]) + { //We're at the start of the value now. + qboolean parseToQuote = qfalse; + + if (buf[i] == '\"') + { //if the value is in quotes, then stop at the next quote instead of ' ' + i++; + parseToQuote = qtrue; + } + + j = 0; + while ( ((!parseToQuote && buf[i] != ' ' && buf[i] != '\n' && buf[i] != '\r') || (parseToQuote && buf[i] != '\"')) ) + { + if (buf[i] == '/' && + buf[i+1] == '/') + { //hit a comment after the value? This isn't an ideal way to be writing things, but we'll support it anyway. + break; + } + outbuf[j] = buf[i]; + j++; + i++; + + if (!buf[i]) + { + if (parseToQuote) + { + Com_Error(ERR_DROP, "Unexpected EOF while looking for endquote, error finding paired value for '%s'", key); + } + else + { + Com_Error(ERR_DROP, "Unexpected EOF while looking for space or endline, error finding paired value for '%s'", key); + } + } + } + outbuf[j] = 0; + + return 1; //we got it, so return 1. + } + else + { + Com_Error(ERR_DROP, "Error parsing file, unexpected EOF while looking for valud '%s'", key); + } + } + else + { //if that wasn't the desired key, then make sure we parse to the end of the line, so we don't mistake a value for a key + while (buf[i] && buf[i] != '\n') + { + i++; + } + } + } + else + { + Com_Error(ERR_DROP, "Error parsing file, found comment, expected value for '%s'", key); + } + } + } + } + + if (!buf[i]) + { + break; + } + i++; + } + + return 0; //guess we never found it. +} +//====================================== +//End parsing functions +//====================================== + + +//====================================== +//Class loading functions +//====================================== +void BG_SiegeTranslateForcePowers(char *buf, siegeClass_t *siegeClass) +{ + char checkPower[1024]; + char checkLevel[256]; + int l = 0; + int k = 0; + int j = 0; + int i = 0; + int parsedLevel = 0; + qboolean allPowers = qfalse; + qboolean noPowers = qfalse; + + if (!Q_stricmp(buf, "FP_ALL")) + { //this is a special case, just give us all the powers on level 3 + allPowers = qtrue; + } + + if (buf[0] == '0' && !buf[1]) + { //no powers then + noPowers = qtrue; + } + + //First clear out the powers, or in the allPowers case, give us all level 3. + while (i < NUM_FORCE_POWERS) + { + if (allPowers) + { + siegeClass->forcePowerLevels[i] = FORCE_LEVEL_3; + } + else + { + siegeClass->forcePowerLevels[i] = 0; + } + i++; + } + + if (allPowers || noPowers) + { //we're done now then. + return; + } + + i = 0; + while (buf[i]) + { //parse through the list which is seperated by |, and add all the weapons into a bitflag + if (buf[i] != ' ' && buf[i] != '|') + { + j = 0; + + while (buf[i] && buf[i] != ' ' && buf[i] != '|' && buf[i] != ',') + { + checkPower[j] = buf[i]; + j++; + i++; + } + checkPower[j] = 0; + + if (buf[i] == ',') + { //parse the power level + i++; + l = 0; + while (buf[i] && buf[i] != ' ' && buf[i] != '|') + { + checkLevel[l] = buf[i]; + l++; + i++; + } + checkLevel[l] = 0; + parsedLevel = atoi(checkLevel); + + //keep sane limits on the powers + if (parsedLevel < 0) + { + parsedLevel = 0; + } + if (parsedLevel > FORCE_LEVEL_5) + { + parsedLevel = FORCE_LEVEL_5; + } + } + else + { //if it's not there, assume level 3 I guess. + parsedLevel = 3; + } + + if (checkPower[0]) + { //Got the name, compare it against the weapon table strings. + k = 0; + + if (!Q_stricmp(checkPower, "FP_JUMP")) + { //haqery + strcpy(checkPower, "FP_LEVITATION"); + } + + while (FPTable[k].id != -1 && FPTable[k].name[0]) + { + if (!Q_stricmp(checkPower, FPTable[k].name)) + { //found it, add the weapon into the weapons value + siegeClass->forcePowerLevels[k] = parsedLevel; + break; + } + k++; + } + } + } + + if (!buf[i]) + { + break; + } + i++; + } +} + +//Used for the majority of generic val parsing stuff. buf should be the value string, +//table should be the appropriate string/id table. If bitflag is qtrue then the +//values are accumulated into a bitflag. If bitflag is qfalse then the first value +//is returned as a directly corresponding id and no further parsing is done. +int BG_SiegeTranslateGenericTable(char *buf, stringID_table_t *table, qboolean bitflag) +{ + int items = 0; + char checkItem[1024]; + int i = 0; + int j = 0; + int k = 0; + + if (buf[0] == '0' && !buf[1]) + { //special case, no items. + return 0; + } + + while (buf[i]) + { //Using basically the same parsing method as we do for weapons and forcepowers. + if (buf[i] != ' ' && buf[i] != '|') + { + j = 0; + + while (buf[i] && buf[i] != ' ' && buf[i] != '|') + { + checkItem[j] = buf[i]; + j++; + i++; + } + checkItem[j] = 0; + + if (checkItem[0]) + { + k = 0; + + while (table[k].name && table[k].name[0]) + { //go through the list and check the parsed flag name against the hardcoded names + if (!Q_stricmp(checkItem, table[k].name)) + { //Got it, so add the value into our items value. + if (bitflag) + { + items |= (1 << table[k].id); + } + else + { //return the value directly then. + return table[k].id; + } + break; + } + k++; + } + } + } + + if (!buf[i]) + { + break; + } + + i++; + } + return items; +} + +char *classTitles[SPC_MAX] = +{ +"infantry", // SPC_INFANTRY +"vanguard", // SPC_VANGUARD +"support", // SPC_SUPPORT +"jedi_general", // SPC_JEDI +"demolitionist", // SPC_DEMOLITIONIST +"heavy_weapons", // SPC_HEAVY_WEAPONS +}; + + +void BG_SiegeParseClassFile(const char *filename, siegeClassDesc_t *descBuffer) +{ + fileHandle_t f; + int len; + int i; + char classInfo[4096]; + char parseBuf[4096]; + + len = trap_FS_FOpenFile(filename, &f, FS_READ); + + if (!f || len >= 4096) + { + return; + } + + trap_FS_Read(classInfo, len, f); + + trap_FS_FCloseFile(f); + + classInfo[len] = 0; + + //first get the description if we have a buffer for it + if (descBuffer) + { + if (!BG_SiegeGetPairedValue(classInfo, "description", descBuffer->desc)) + { + strcpy(descBuffer->desc, "DESCRIPTION UNAVAILABLE"); + } + + //Hit this assert? Memory has already been trashed. Increase + //SIEGE_CLASS_DESC_LEN. + assert(strlen(descBuffer->desc) < SIEGE_CLASS_DESC_LEN); + } + + BG_SiegeGetValueGroup(classInfo, "ClassInfo", classInfo); + + //Parse name + if (BG_SiegeGetPairedValue(classInfo, "name", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].name, parseBuf); + } + else + { + Com_Error(ERR_DROP, "Siege class without name entry"); + } + + //Parse forced model + if (BG_SiegeGetPairedValue(classInfo, "model", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].forcedModel, parseBuf); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].forcedModel[0] = 0; + } + + //Parse forced skin + if (BG_SiegeGetPairedValue(classInfo, "skin", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].forcedSkin, parseBuf); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].forcedSkin[0] = 0; + } + + //Parse first saber + if (BG_SiegeGetPairedValue(classInfo, "saber1", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].saber1, parseBuf); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].saber1[0] = 0; + } + + //Parse second saber + if (BG_SiegeGetPairedValue(classInfo, "saber2", parseBuf)) + { + strcpy(bgSiegeClasses[bgNumSiegeClasses].saber2, parseBuf); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].saber2[0] = 0; + } + + //Parse forced saber stance + if (BG_SiegeGetPairedValue(classInfo, "saberstyle", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].saberStance = BG_SiegeTranslateGenericTable(parseBuf, StanceTable, qtrue); + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].saberStance = 0; + } + + //Parse forced saber color + if (BG_SiegeGetPairedValue(classInfo, "sabercolor", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].forcedSaberColor = atoi(parseBuf); + bgSiegeClasses[bgNumSiegeClasses].hasForcedSaberColor = qtrue; + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].hasForcedSaberColor = qfalse; + } + + //Parse forced saber2 color + if (BG_SiegeGetPairedValue(classInfo, "saber2color", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].forcedSaber2Color = atoi(parseBuf); + bgSiegeClasses[bgNumSiegeClasses].hasForcedSaber2Color = qtrue; + } + else + { //It's ok if there isn't one, it's optional. + bgSiegeClasses[bgNumSiegeClasses].hasForcedSaber2Color = qfalse; + } + + //Parse weapons + if (BG_SiegeGetPairedValue(classInfo, "weapons", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].weapons = BG_SiegeTranslateGenericTable(parseBuf, WPTable, qtrue); + } + else + { + Com_Error(ERR_DROP, "Siege class without weapons entry"); + } + + if (!(bgSiegeClasses[bgNumSiegeClasses].weapons & (1 << WP_SABER))) + { //make sure it has melee if there's no saber + bgSiegeClasses[bgNumSiegeClasses].weapons |= (1 << WP_MELEE); + + //always give them this too if they are not a saber user + //bgSiegeClasses[bgNumSiegeClasses].weapons |= (1 << WP_BRYAR_PISTOL); + } + + //Parse forcepowers + if (BG_SiegeGetPairedValue(classInfo, "forcepowers", parseBuf)) + { + BG_SiegeTranslateForcePowers(parseBuf, &bgSiegeClasses[bgNumSiegeClasses]); + } + else + { //fine, clear out the powers. + i = 0; + while (i < NUM_FORCE_POWERS) + { + bgSiegeClasses[bgNumSiegeClasses].forcePowerLevels[i] = 0; + i++; + } + } + + //Parse classflags + if (BG_SiegeGetPairedValue(classInfo, "classflags", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].classflags = BG_SiegeTranslateGenericTable(parseBuf, bgSiegeClassFlagNames, qtrue); + } + else + { //fine, we'll 0 it. + bgSiegeClasses[bgNumSiegeClasses].classflags = 0; + } + + //Parse maxhealth + if (BG_SiegeGetPairedValue(classInfo, "maxhealth", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].maxhealth = atoi(parseBuf); + } + else + { //It's alright, just default to 100 then. + bgSiegeClasses[bgNumSiegeClasses].maxhealth = 100; + } + + //Parse starthealth + if (BG_SiegeGetPairedValue(classInfo, "starthealth", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].starthealth = atoi(parseBuf); + } + else + { //It's alright, just default to 100 then. + bgSiegeClasses[bgNumSiegeClasses].starthealth = bgSiegeClasses[bgNumSiegeClasses].maxhealth; + } + + + //Parse startarmor + if (BG_SiegeGetPairedValue(classInfo, "maxarmor", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].maxarmor = atoi(parseBuf); + } + else + { //It's alright, just default to 0 then. + bgSiegeClasses[bgNumSiegeClasses].maxarmor = 0; + } + + //Parse startarmor + if (BG_SiegeGetPairedValue(classInfo, "startarmor", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].startarmor = atoi(parseBuf); + if (!bgSiegeClasses[bgNumSiegeClasses].maxarmor) + { //if they didn't specify a damn max armor then use this. + bgSiegeClasses[bgNumSiegeClasses].maxarmor = bgSiegeClasses[bgNumSiegeClasses].startarmor; + } + } + else + { //default to maxarmor. + bgSiegeClasses[bgNumSiegeClasses].startarmor = bgSiegeClasses[bgNumSiegeClasses].maxarmor; + } + + //Parse speed (this is a multiplier value) + if (BG_SiegeGetPairedValue(classInfo, "speed", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].speed = atof(parseBuf); + } + else + { //It's alright, just default to 1 then. + bgSiegeClasses[bgNumSiegeClasses].speed = 1.0f; + } + + //Parse shader for ui to use + if (BG_SiegeGetPairedValue(classInfo, "uishader", parseBuf)) + { +#ifdef QAGAME + bgSiegeClasses[bgNumSiegeClasses].uiPortraitShader = 0; + memset(bgSiegeClasses[bgNumSiegeClasses].uiPortrait,0,sizeof(bgSiegeClasses[bgNumSiegeClasses].uiPortrait)); +#elif defined CGAME + bgSiegeClasses[bgNumSiegeClasses].uiPortraitShader = 0; + memset(bgSiegeClasses[bgNumSiegeClasses].uiPortrait,0,sizeof(bgSiegeClasses[bgNumSiegeClasses].uiPortrait)); +#else //ui + bgSiegeClasses[bgNumSiegeClasses].uiPortraitShader = trap_R_RegisterShaderNoMip(parseBuf); + memcpy(bgSiegeClasses[bgNumSiegeClasses].uiPortrait,parseBuf,sizeof(bgSiegeClasses[bgNumSiegeClasses].uiPortrait)); +#endif + } + else + { //I guess this is an essential.. we don't want to render bad shaders or anything. + Com_Error(ERR_DROP, "Siege class without uishader entry"); + } + + //Parse shader for ui to use + if (BG_SiegeGetPairedValue(classInfo, "class_shader", parseBuf)) + { +#ifdef QAGAME + bgSiegeClasses[bgNumSiegeClasses].classShader = 0; +#else //cgame, ui + bgSiegeClasses[bgNumSiegeClasses].classShader = trap_R_RegisterShaderNoMip(parseBuf); + assert( bgSiegeClasses[bgNumSiegeClasses].classShader ); + if ( !bgSiegeClasses[bgNumSiegeClasses].classShader ) + { + //Com_Error( ERR_DROP, "ERROR: could not find class_shader %s for class %s\n", parseBuf, bgSiegeClasses[bgNumSiegeClasses].name ); + Com_Printf( "ERROR: could not find class_shader %s for class %s\n", parseBuf, bgSiegeClasses[bgNumSiegeClasses].name ); + } + // A very hacky way to determine class . . . + else +#endif + { + // Find the base player class based on the icon name - very bad, I know. + int titleLength,i,arrayTitleLength; + char *holdBuf; + + titleLength = strlen(parseBuf); + for (i=0;ititleLength) // Too long + { + break; + } + + holdBuf = parseBuf + ( titleLength - arrayTitleLength); + if (!strcmp(holdBuf,classTitles[i])) + { + bgSiegeClasses[bgNumSiegeClasses].playerClass = i; + break; + } + } + + // In case the icon name doesn't match up + if (i>=SPC_MAX) + { + bgSiegeClasses[bgNumSiegeClasses].playerClass = SPC_INFANTRY; + } + } + } + else + { //No entry! Bad bad bad + //Com_Error( ERR_DROP, "ERROR: no class_shader defined for class %s\n", bgSiegeClasses[bgNumSiegeClasses].name ); + Com_Printf( "ERROR: no class_shader defined for class %s\n", bgSiegeClasses[bgNumSiegeClasses].name ); + } + + //Parse holdable items to use + if (BG_SiegeGetPairedValue(classInfo, "holdables", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].invenItems = BG_SiegeTranslateGenericTable(parseBuf, HoldableTable, qtrue); + } + else + { //Just don't start out with any then. + bgSiegeClasses[bgNumSiegeClasses].invenItems = 0; + } + + //Parse powerups to use + if (BG_SiegeGetPairedValue(classInfo, "powerups", parseBuf)) + { + bgSiegeClasses[bgNumSiegeClasses].powerups = BG_SiegeTranslateGenericTable(parseBuf, PowerupTable, qtrue); + } + else + { //Just don't start out with any then. + bgSiegeClasses[bgNumSiegeClasses].powerups = 0; + } + + //A successful read. + bgNumSiegeClasses++; +} + +// Count the number of like base classes +int BG_SiegeCountBaseClass(const int team, const short classIndex) +{ + int count = 0,i; + siegeTeam_t *stm; + + stm = BG_SiegeFindThemeForTeam(team); + if (!stm) + { + return(0); + + } + + for (i=0;inumClasses;i++) + { + + if (stm->classes[i]->playerClass == classIndex) + { + count++; + } + } + return(count); +} + +char *BG_GetUIPortraitFile(const int team, const short classIndex, const short cntIndex) +{ + int count = 0,i; + siegeTeam_t *stm; + + stm = BG_SiegeFindThemeForTeam(team); + if (!stm) + { + return(0); + + } + + // Loop through all the classes for this team + for (i=0;inumClasses;i++) + { + // does it match the base class? + if (stm->classes[i]->playerClass == classIndex) + { + if (count==cntIndex) + { + return(stm->classes[i]->uiPortrait); + } + ++count; + } + } + + return(0); +} + +int BG_GetUIPortrait(const int team, const short classIndex, const short cntIndex) +{ + int count = 0,i; + siegeTeam_t *stm; + + stm = BG_SiegeFindThemeForTeam(team); + if (!stm) + { + return(0); + + } + + // Loop through all the classes for this team + for (i=0;inumClasses;i++) + { + // does it match the base class? + if (stm->classes[i]->playerClass == classIndex) + { + if (count==cntIndex) + { + return(stm->classes[i]->uiPortraitShader); + } + ++count; + } + } + + return(0); +} + +// This is really getting ugly - looking to get the base class (within a class) based on the index passed in +siegeClass_t *BG_GetClassOnBaseClass(const int team, const short classIndex, const short cntIndex) +{ + int count = 0,i; + siegeTeam_t *stm; + + stm = BG_SiegeFindThemeForTeam(team); + if (!stm) + { + return(0); + } + + // Loop through all the classes for this team + for (i=0;inumClasses;i++) + { + // does it match the base class? + if (stm->classes[i]->playerClass == classIndex) + { + if (count==cntIndex) + { + return(stm->classes[i]); + } + ++count; + } + } + + return(0); +} + +void BG_SiegeLoadClasses(siegeClassDesc_t *descBuffer) +{ + int numFiles; + int filelen; + char filelist[4096]; + char filename[MAX_QPATH]; + char* fileptr; + int i; + + bgNumSiegeClasses = 0; + + numFiles = trap_FS_GetFileList("ext_data/Siege/Classes", ".scl", filelist, 4096 ); + fileptr = filelist; + + for (i = 0; i < numFiles; i++, fileptr += filelen+1) + { + filelen = strlen(fileptr); + strcpy(filename, "ext_data/Siege/Classes/"); + strcat(filename, fileptr); + + if (descBuffer) + { + BG_SiegeParseClassFile(filename, &descBuffer[i]); + } + else + { + BG_SiegeParseClassFile(filename, NULL); + } + } +} +//====================================== +//End class loading functions +//====================================== + + +//====================================== +//Team loading functions +//====================================== +siegeClass_t *BG_SiegeFindClassByName(const char *classname) +{ + int i = 0; + + while (i < bgNumSiegeClasses) + { + if (!Q_stricmp(bgSiegeClasses[i].name, classname)) + { //found it + return &bgSiegeClasses[i]; + } + i++; + } + + return NULL; +} + +void BG_SiegeParseTeamFile(const char *filename) +{ + fileHandle_t f; + int len; + char teamInfo[2048]; + char parseBuf[1024]; + char lookString[256]; + int i = 1; + qboolean success = qtrue; + + len = trap_FS_FOpenFile(filename, &f, FS_READ); + + if (!f || len >= 2048) + { + return; + } + + trap_FS_Read(teamInfo, len, f); + + trap_FS_FCloseFile(f); + + teamInfo[len] = 0; + + if (BG_SiegeGetPairedValue(teamInfo, "name", parseBuf)) + { + strcpy(bgSiegeTeams[bgNumSiegeTeams].name, parseBuf); + } + else + { + Com_Error(ERR_DROP, "Siege team with no name definition"); + } + +//I don't entirely like doing things this way but it's the easiest way. +#ifdef CGAME + if (BG_SiegeGetPairedValue(teamInfo, "FriendlyShader", parseBuf)) + { + bgSiegeTeams[bgNumSiegeTeams].friendlyShader = trap_R_RegisterShaderNoMip(parseBuf); + } +#else + bgSiegeTeams[bgNumSiegeTeams].friendlyShader = 0; +#endif + + bgSiegeTeams[bgNumSiegeTeams].numClasses = 0; + + if (BG_SiegeGetValueGroup(teamInfo, "Classes", teamInfo)) + { + while (success && i < MAX_SIEGE_CLASSES) + { //keep checking for group values named class# up to MAX_SIEGE_CLASSES until we can't find one. + strcpy(lookString, va("class%i", i)); + + success = BG_SiegeGetPairedValue(teamInfo, lookString, parseBuf); + + if (!success) + { + break; + } + + bgSiegeTeams[bgNumSiegeTeams].classes[bgSiegeTeams[bgNumSiegeTeams].numClasses] = BG_SiegeFindClassByName(parseBuf); + + if (!bgSiegeTeams[bgNumSiegeTeams].classes[bgSiegeTeams[bgNumSiegeTeams].numClasses]) + { + Com_Error(ERR_DROP, "Invalid class specified: '%s'", parseBuf); + } + + bgSiegeTeams[bgNumSiegeTeams].numClasses++; + + i++; + } + } + + if (!bgSiegeTeams[bgNumSiegeTeams].numClasses) + { + Com_Error(ERR_DROP, "Team defined with no allowable classes\n"); + } + + //If we get here then it was a success, so increment the team number + bgNumSiegeTeams++; +} + +void BG_SiegeLoadTeams(void) +{ + int numFiles; + int filelen; + char filelist[4096]; + char filename[MAX_QPATH]; + char* fileptr; + int i; + + bgNumSiegeTeams = 0; + + numFiles = trap_FS_GetFileList("ext_data/Siege/Teams", ".team", filelist, 4096 ); + fileptr = filelist; + + for (i = 0; i < numFiles; i++, fileptr += filelen+1) + { + filelen = strlen(fileptr); + strcpy(filename, "ext_data/Siege/Teams/"); + strcat(filename, fileptr); + BG_SiegeParseTeamFile(filename); + } +} +//====================================== +//End team loading functions +//====================================== + + +//====================================== +//Misc/utility functions +//====================================== +siegeTeam_t *BG_SiegeFindThemeForTeam(int team) +{ + if (team == SIEGETEAM_TEAM1) + { + return team1Theme; + } + else if (team == SIEGETEAM_TEAM2) + { + return team2Theme; + } + + return NULL; +} + +#ifndef UI_EXPORTS //only for game/cgame +//precache all the sabers for the active classes for the team +extern qboolean WP_SaberParseParms( const char *SaberName, saberInfo_t *saber ); //bg_saberLoad.cpp +extern int BG_ModelCache(const char *modelName, const char *skinName); //bg_misc.c + +void BG_PrecacheSabersForSiegeTeam(int team) +{ + siegeTeam_t *t; + saberInfo_t saber; + char *saberName; + int sNum; + + t = BG_SiegeFindThemeForTeam(team); + + if (t) + { + int i = 0; + + while (i < t->numClasses) + { + sNum = 0; + + while (sNum < MAX_SABERS) + { + switch (sNum) + { + case 0: + saberName = &t->classes[i]->saber1[0]; + break; + case 1: + saberName = &t->classes[i]->saber2[0]; + break; + default: + saberName = NULL; + break; + } + + if (saberName && saberName[0]) + { + WP_SaberParseParms(saberName, &saber); + if (!Q_stricmp(saberName, saber.name)) + { //found the matching saber + if (saber.model[0]) + { + BG_ModelCache(saber.model, NULL); + } + } + } + + sNum++; + } + + i++; + } + } +} +#endif + +qboolean BG_SiegeCheckClassLegality(int team, char *classname) +{ + siegeTeam_t **teamPtr = NULL; + int i = 0; + + if (team == SIEGETEAM_TEAM1) + { + teamPtr = &team1Theme; + } + else if (team == SIEGETEAM_TEAM2) + { + teamPtr = &team2Theme; + } + else + { //spectator? Whatever, you're legal then. + return qtrue; + } + + if (!teamPtr || !(*teamPtr)) + { //Well, guess the class is ok, seeing as there is no team theme to begin with. + return qtrue; + } + + //See if the class is listed on the team + while (i < (*teamPtr)->numClasses) + { + if (!Q_stricmp(classname, (*teamPtr)->classes[i]->name)) + { //found it, so it's alright + return qtrue; + } + i++; + } + + //Didn't find it, so copy the name of the first valid class over it. + strcpy(classname, (*teamPtr)->classes[0]->name); + + return qfalse; +} + +siegeTeam_t *BG_SiegeFindTeamForTheme(char *themeName) +{ + int i = 0; + + while (i < bgNumSiegeTeams) + { + if (bgSiegeTeams[i].name && + !Q_stricmp(bgSiegeTeams[i].name, themeName)) + { //this is what we're looking for + return &bgSiegeTeams[i]; + } + + i++; + } + + return NULL; +} + +void BG_SiegeSetTeamTheme(int team, char *themeName) +{ + siegeTeam_t **teamPtr = NULL; + + if (team == SIEGETEAM_TEAM1) + { + teamPtr = &team1Theme; + } + else + { + teamPtr = &team2Theme; + } + + (*teamPtr) = BG_SiegeFindTeamForTheme(themeName); +} + +int BG_SiegeFindClassIndexByName(const char *classname) +{ + int i = 0; + + while (i < bgNumSiegeClasses) + { + if (!Q_stricmp(bgSiegeClasses[i].name, classname)) + { //found it + return i; + } + i++; + } + + return -1; +} +//====================================== +//End misc/utility functions +//====================================== + +//#include "../namespace_end.h" diff --git a/code/game/bg_saga.h b/code/game/bg_saga.h new file mode 100644 index 0000000..2a42f85 --- /dev/null +++ b/code/game/bg_saga.h @@ -0,0 +1,115 @@ +#define MAX_SIEGE_INFO_SIZE 16384 + +#define SIEGETEAM_TEAM1 1 //e.g. TEAM_RED +#define SIEGETEAM_TEAM2 2 //e.g. TEAM_BLUE + +#define SIEGE_POINTS_OBJECTIVECOMPLETED 20 +#define SIEGE_POINTS_FINALOBJECTIVECOMPLETED 30 +#define SIEGE_POINTS_TEAMWONROUND 10 + +#define SIEGE_ROUND_BEGIN_TIME 5000 //delay 5 secs after players are in game. + +#define MAX_SIEGE_CLASSES 128 //up to 128 classes +#define MAX_SIEGE_CLASSES_PER_TEAM 16 + +#define MAX_SIEGE_TEAMS 16 //up to 16 diffent teams + +#define MAX_EXDATA_ENTS_TO_SEND MAX_CLIENTS //max number of extended data for ents to send + +// The basic siege player classes +typedef enum +{ + SPC_INFANTRY = 0, + SPC_VANGUARD, + SPC_SUPPORT, + SPC_JEDI, + SPC_DEMOLITIONIST, + SPC_HEAVY_WEAPONS, + SPC_MAX +} siegePlayerClassFlags_t; + +typedef enum +{ + CFL_MORESABERDMG = 0, + CFL_STRONGAGAINSTPHYSICAL, + CFL_FASTFORCEREGEN, + CFL_STATVIEWER, + CFL_HEAVYMELEE, + CFL_SINGLE_ROCKET,//has only 1 rocket to use with rocketlauncher + CFL_CUSTOMSKEL, //class uses a custom skeleton, be sure to load on server etc + CFL_EXTRA_AMMO +} siegeClassFlags_t; + + +#ifdef _XBOX +#define SIEGE_CLASS_DESC_LEN 512 +#else +#define SIEGE_CLASS_DESC_LEN 4096 +#endif +typedef struct +{ + char desc[SIEGE_CLASS_DESC_LEN]; +} siegeClassDesc_t; + +typedef struct +{ + char name[512]; + char forcedModel[256]; + char forcedSkin[256]; + char saber1[64]; + char saber2[64]; + int saberStance; + int weapons; + int forcePowerLevels[NUM_FORCE_POWERS]; + int classflags; + int maxhealth; + int starthealth; + int maxarmor; + int startarmor; + float speed; + qboolean hasForcedSaberColor; + int forcedSaberColor; + qboolean hasForcedSaber2Color; + int forcedSaber2Color; + int invenItems; + int powerups; + int uiPortraitShader; + char uiPortrait[256]; + int classShader; + short playerClass; // SPC_INFANTRY . .. +} siegeClass_t; + +typedef struct +{ + char name[512]; + siegeClass_t *classes[MAX_SIEGE_CLASSES_PER_TEAM]; + int numClasses; + int friendlyShader; +} siegeTeam_t; + +//#include "../namespace_begin.h" + +extern siegeClass_t bgSiegeClasses[MAX_SIEGE_CLASSES]; +extern int bgNumSiegeClasses; + +extern siegeTeam_t bgSiegeTeams[MAX_SIEGE_TEAMS]; +extern int bgNumSiegeTeams; + +int BG_SiegeGetValueGroup(char *buf, char *group, char *outbuf); +int BG_SiegeGetPairedValue(char *buf, char *key, char *outbuf); +void BG_SiegeStripTabs(char *buf); + +void BG_SiegeLoadClasses(siegeClassDesc_t *descBuffer); +void BG_SiegeLoadTeams(void); + +siegeTeam_t *BG_SiegeFindThemeForTeam(int team); +void BG_PrecacheSabersForSiegeTeam(int team); +siegeClass_t *BG_SiegeFindClassByName(const char *classname); +qboolean BG_SiegeCheckClassLegality(int team, char *classname); +void BG_SiegeSetTeamTheme(int team, char *themeName); +int BG_SiegeFindClassIndexByName(const char *classname); + +extern char siege_info[MAX_SIEGE_INFO_SIZE]; +extern int siege_valid; + +//#include "../namespace_end.h" diff --git a/code/game/bg_slidemove.c b/code/game/bg_slidemove.c new file mode 100644 index 0000000..08b2de0 --- /dev/null +++ b/code/game/bg_slidemove.c @@ -0,0 +1,1071 @@ +// 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" + +#ifdef QAGAME //yeah, this is kind of bad +#include "g_local.h" +#endif + +/* + +input: origin, velocity, bounds, groundPlane, trace function + +output: origin, velocity, impacts, stairup boolean + +*/ + + +//do vehicle impact stuff +// slight rearrangement by BTO (VV) so that we only have one namespace include +#ifdef QAGAME +extern void G_FlyVehicleSurfaceDestruction(gentity_t *veh, trace_t *trace, int magnitude, qboolean force ); //g_vehicle.c +extern qboolean G_CanBeEnemy(gentity_t *self, gentity_t *enemy); //w_saber.c +#endif + +extern qboolean BG_UnrestrainedPitchRoll( playerState_t *ps, Vehicle_t *pVeh ); + +#include "../namespace_begin.h" + +extern bgEntity_t *pm_entSelf; +extern bgEntity_t *pm_entVeh; + +//vehicle impact stuff continued... +#ifndef QAGAME //kind of hacky +extern void trap_FX_PlayEffectID( int id, vec3_t org, vec3_t fwd, int vol, int rad ); +#endif + +#ifdef QAGAME +extern qboolean FighterIsLanded( Vehicle_t *pVeh, playerState_t *parentPS ); +#endif + +extern void PM_SetPMViewAngle(playerState_t *ps, vec3_t angle, usercmd_t *ucmd); + +#define MAX_IMPACT_TURN_ANGLE 45.0f +void PM_VehicleImpact(bgEntity_t *pEnt, trace_t *trace) +{ + // See if the vehicle has crashed into the ground. + Vehicle_t *pSelfVeh = pEnt->m_pVehicle; + float magnitude = VectorLength( pm->ps->velocity ) * pSelfVeh->m_pVehicleInfo->mass / 50.0f; + qboolean forceSurfDestruction = qfalse; +#ifdef QAGAME + gentity_t *hitEnt = trace!=NULL?&g_entities[trace->entityNum]:NULL; + + if (!hitEnt || + (pSelfVeh && pSelfVeh->m_pPilot && + hitEnt && hitEnt->s.eType == ET_MISSILE && hitEnt->inuse && + hitEnt->r.ownerNum == pSelfVeh->m_pPilot->s.number) + ) + { + return; + } + + if ( pSelfVeh//I have a vehicle struct + && pSelfVeh->m_iRemovedSurfaces )//vehicle has bits removed + {//spiralling to our deaths, explode on any solid impact + if ( hitEnt->s.NPC_class == CLASS_VEHICLE ) + {//hit another vehicle, explode! + //Give credit to whoever got me into this death spiral state + gentity_t *parent = (gentity_t *)pSelfVeh->m_pParentEntity; + gentity_t *killer = NULL; + if (parent->client->ps.otherKiller < ENTITYNUM_WORLD && + parent->client->ps.otherKillerTime > level.time) + { + gentity_t *potentialKiller = &g_entities[parent->client->ps.otherKiller]; + + if (potentialKiller->inuse && potentialKiller->client) + { //he's valid I guess + killer = potentialKiller; + } + } + //FIXME: damage hitEnt, some, too? Our explosion should hurt them some, but... + G_Damage( (gentity_t *)pEnt, killer, killer, NULL, pm->ps->origin, 999999, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT + return; + } + else if ( !VectorCompare( trace->plane.normal, vec3_origin ) + && (trace->entityNum == ENTITYNUM_WORLD || hitEnt->r.bmodel ) ) + {//have a valid hit plane and we hit a solid brush + vec3_t moveDir; + float impactDot; + VectorCopy( pm->ps->velocity, moveDir ); + VectorNormalize( moveDir ); + impactDot = DotProduct( moveDir, trace->plane.normal ); + if ( impactDot <= -0.7f )//hit rather head-on and hard + {// Just DIE now + //Give credit to whoever got me into this death spiral state + gentity_t *parent = (gentity_t *)pSelfVeh->m_pParentEntity; + gentity_t *killer = NULL; + if (parent->client->ps.otherKiller < ENTITYNUM_WORLD && + parent->client->ps.otherKillerTime > level.time) + { + gentity_t *potentialKiller = &g_entities[parent->client->ps.otherKiller]; + + if (potentialKiller->inuse && potentialKiller->client) + { //he's valid I guess + killer = potentialKiller; + } + } + G_Damage( (gentity_t *)pEnt, killer, killer, NULL, pm->ps->origin, 999999, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT + return; + } + } + } + + if ( trace->entityNum < ENTITYNUM_WORLD + && hitEnt->s.eType == ET_MOVER + && hitEnt->s.apos.trType != TR_STATIONARY//rotating + && (hitEnt->spawnflags&16) //IMPACT + && Q_stricmp( "func_rotating", hitEnt->classname ) == 0 ) + {//hit a func_rotating that is supposed to destroy anything it touches! + //guarantee the hit will happen, thereby taking off a piece of the ship + forceSurfDestruction = qtrue; + } + else if ( (fabs(pm->ps->velocity[0])+fabs(pm->ps->velocity[1])) < 100.0f + && pm->ps->velocity[2] > -100.0f ) +#else + if ( (fabs(pm->ps->velocity[0])+fabs(pm->ps->velocity[1])) < 100.0f + && pm->ps->velocity[2] > -100.0f ) +#endif + /* + if ( (pSelfVeh->m_ulFlags&VEH_GEARSOPEN) + && trace->plane.normal[2] > 0.7f + && fabs(pSelfVeh->m_vOrientation[PITCH]) < 0.2f + && fabs(pSelfVeh->m_vOrientation[ROLL]) < 0.2f )*/ + {//we're landing, we're cool + //FIXME: some sort of landing "thump", not the impactFX + /* + if ( pSelfVeh->m_pVehicleInfo->iImpactFX ) + { + vec3_t up = {0,0,1}; +#ifdef QAGAME + G_PlayEffectID( pSelfVeh->m_pVehicleInfo->iImpactFX, pm->ps->origin, up ); +#else + trap_FX_PlayEffectID( pSelfVeh->m_pVehicleInfo->iImpactFX, pm->ps->origin, up, -1, -1 ); +#endif + } + */ + //this was annoying me -rww + //FIXME: this shouldn't even be getting called when the vehicle is at rest! +#ifdef QAGAME + if (hitEnt && (hitEnt->s.eType == ET_PLAYER || hitEnt->s.eType == ET_NPC) && pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) + { //always smack players + } + else +#endif + { + return; + } + } + if ( pSelfVeh && + (pSelfVeh->m_pVehicleInfo->type == VH_SPEEDER || pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) && //this is kind of weird on tauntauns and atst's.. + (magnitude >= 100||forceSurfDestruction) ) + { + if ( pEnt->m_pVehicle->m_iHitDebounce < pm->cmd.serverTime + || forceSurfDestruction ) + {//a bit of a hack, may conflict with getting shot, but... + //FIXME: impact sound and effect should be gotten from g_vehicleInfo...? + //FIXME: should pass in trace.endpos and trace.plane.normal + vec3_t vehUp; +#ifndef QAGAME + bgEntity_t *hitEnt; +#endif + + if ( trace && !pSelfVeh->m_iRemovedSurfaces && !forceSurfDestruction ) + { + qboolean turnFromImpact = qfalse, turnHitEnt = qfalse; + float l = pm->ps->speed*0.5f; + vec3_t bounceDir; +#ifndef QAGAME + bgEntity_t *hitEnt = PM_BGEntForNum(trace->entityNum); +#endif + if ( (trace->entityNum == ENTITYNUM_WORLD || hitEnt->s.solid == SOLID_BMODEL)//bounce off any brush + && !VectorCompare(trace->plane.normal, vec3_origin) )//have a valid plane to bounce off of + { //bounce off in the opposite direction of the impact + if (pSelfVeh->m_pVehicleInfo->type == VH_SPEEDER) + { + pm->ps->speed *= pml.frametime; + VectorCopy(trace->plane.normal, bounceDir); + } + else if ( trace->plane.normal[2] >= MIN_LANDING_SLOPE//flat enough to land on + && pSelfVeh->m_LandTrace.fraction < 1.0f //ground present + && pm->ps->speed <= MIN_LANDING_SPEED ) + {//could land here, don't bounce off, in fact, return altogether! + return; + } + else + { + if (pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) + { + turnFromImpact = qtrue; + } + VectorCopy(trace->plane.normal, bounceDir); + } + } + else if ( pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER ) + {//check for impact with another fighter +#ifndef QAGAME + bgEntity_t *hitEnt = PM_BGEntForNum(trace->entityNum); +#endif + if ( hitEnt->s.NPC_class == CLASS_VEHICLE + && hitEnt->m_pVehicle + && hitEnt->m_pVehicle->m_pVehicleInfo + && hitEnt->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER ) + {//two vehicles hit each other, turn away from the impact + turnFromImpact = qtrue; + turnHitEnt = qtrue; +#ifndef QAGAME + VectorSubtract( pm->ps->origin, hitEnt->s.origin, bounceDir ); +#else + VectorSubtract( pm->ps->origin, hitEnt->r.currentOrigin, bounceDir ); +#endif + VectorNormalize( bounceDir ); + } + } + if ( turnFromImpact ) + {//bounce off impact surf and turn away + vec3_t pushDir={0}, turnAwayAngles, turnDelta; + float turnStrength, pitchTurnStrength, yawTurnStrength; + vec3_t moveDir; + float bounceDot, turnDivider; + //bounce + if ( !turnHitEnt ) + {//hit wall + VectorScale(bounceDir, (pm->ps->speed*0.25f/pSelfVeh->m_pVehicleInfo->mass), pushDir); + } + else + {//hit another fighter +#ifndef QAGAME + VectorScale( bounceDir, (pm->ps->speed+hitEnt->s.speed)*0.5f, bounceDir ); +#else + if ( hitEnt->client ) + { + VectorScale( bounceDir, (pm->ps->speed+hitEnt->client->ps.speed)*0.5f, pushDir ); + } + else + { + VectorScale( bounceDir, (pm->ps->speed+hitEnt->s.speed)*0.5f, pushDir ); + } +#endif + VectorScale(pushDir, (l/pSelfVeh->m_pVehicleInfo->mass), pushDir); + VectorScale(pushDir, 0.1f, pushDir); + } + VectorNormalize2( pm->ps->velocity, moveDir ); + bounceDot = DotProduct( moveDir, bounceDir )*-1; + if ( bounceDot < 0.1f ) + { + bounceDot = 0.1f; + } + VectorScale( pushDir, bounceDot, pushDir ); + VectorAdd(pm->ps->velocity, pushDir, pm->ps->velocity); + //turn + turnDivider = (pSelfVeh->m_pVehicleInfo->mass/400.0f); + if ( turnHitEnt ) + {//don't turn as much when hit another ship + turnDivider *= 4.0f; + } + if ( turnDivider < 0.5f ) + { + turnDivider = 0.5f; + } + turnStrength = (magnitude/2000.0f); + if ( turnStrength < 0.1f ) + { + turnStrength = 0.1f; + } + else if ( turnStrength > 2.0f ) + { + turnStrength = 2.0f; + } + //get the angles we are going to turn towards + vectoangles( bounceDir, turnAwayAngles ); + //get the delta from our current angles to those new angles + AnglesSubtract( turnAwayAngles, pSelfVeh->m_vOrientation, turnDelta ); + //now do pitch + if ( !bounceDir[2] ) + {//shouldn't be any pitch + } + else + { + pitchTurnStrength = turnStrength*turnDelta[PITCH]; + if ( pitchTurnStrength > MAX_IMPACT_TURN_ANGLE ) + { + pitchTurnStrength = MAX_IMPACT_TURN_ANGLE; + } + else if ( pitchTurnStrength < -MAX_IMPACT_TURN_ANGLE ) + { + pitchTurnStrength = -MAX_IMPACT_TURN_ANGLE; + } + //pSelfVeh->m_vOrientation[PITCH] = AngleNormalize180(pSelfVeh->m_vOrientation[PITCH]+pitchTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + pSelfVeh->m_vFullAngleVelocity[PITCH] = AngleNormalize180(pSelfVeh->m_vOrientation[PITCH]+pitchTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + } + //now do yaw + if ( !bounceDir[0] + && !bounceDir[1] ) + {//shouldn't be any yaw + } + else + { + yawTurnStrength = turnStrength*turnDelta[YAW]; + if ( yawTurnStrength > MAX_IMPACT_TURN_ANGLE ) + { + yawTurnStrength = MAX_IMPACT_TURN_ANGLE; + } + else if ( yawTurnStrength < -MAX_IMPACT_TURN_ANGLE ) + { + yawTurnStrength = -MAX_IMPACT_TURN_ANGLE; + } + //pSelfVeh->m_vOrientation[ROLL] = AngleNormalize180(pSelfVeh->m_vOrientation[ROLL]-yawTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + pSelfVeh->m_vFullAngleVelocity[ROLL] = AngleNormalize180(pSelfVeh->m_vOrientation[ROLL]-yawTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + } + /* + PM_SetPMViewAngle(pm->ps, pSelfVeh->m_vOrientation, &pSelfVeh->m_ucmd); + if ( pm_entVeh ) + {//I'm a vehicle, so pm_entVeh is actually my pilot + bgEntity_t *pilot = pm_entVeh; + if ( !BG_UnrestrainedPitchRoll( pilot->playerState, pSelfVeh ) ) + { + //set the rider's viewangles to the vehicle's viewangles + PM_SetPMViewAngle(pilot->playerState, pSelfVeh->m_vOrientation, &pSelfVeh->m_ucmd); + } + } + */ +#ifdef QAGAME//server-side, turn the guy we hit away from us, too + if ( turnHitEnt//make the other guy turn and get pushed + && hitEnt->client //must be a valid client + && !FighterIsLanded( hitEnt->m_pVehicle, &hitEnt->client->ps )//but not if landed + && !(hitEnt->spawnflags&2) )//and not if suspended + { + l = hitEnt->client->ps.speed; + //now bounce *them* away and turn them + //flip the bounceDir + VectorScale( bounceDir, -1, bounceDir ); + //do bounce + VectorScale( bounceDir, (pm->ps->speed+l)*0.5f, pushDir ); + VectorScale(pushDir, (l*0.5f/hitEnt->m_pVehicle->m_pVehicleInfo->mass), pushDir); + VectorNormalize2( hitEnt->client->ps.velocity, moveDir ); + bounceDot = DotProduct( moveDir, bounceDir )*-1; + if ( bounceDot < 0.1f ) + { + bounceDot = 0.1f; + } + VectorScale( pushDir, bounceDot, pushDir ); + VectorAdd(hitEnt->client->ps.velocity, pushDir, hitEnt->client->ps.velocity); + //turn + turnDivider = (hitEnt->m_pVehicle->m_pVehicleInfo->mass/400.0f); + if ( turnHitEnt ) + {//don't turn as much when hit another ship + turnDivider *= 4.0f; + } + if ( turnDivider < 0.5f ) + { + turnDivider = 0.5f; + } + //get the angles we are going to turn towards + vectoangles( bounceDir, turnAwayAngles ); + //get the delta from our current angles to those new angles + AnglesSubtract( turnAwayAngles, hitEnt->m_pVehicle->m_vOrientation, turnDelta ); + //now do pitch + if ( !bounceDir[2] ) + {//shouldn't be any pitch + } + else + { + pitchTurnStrength = turnStrength*turnDelta[PITCH]; + if ( pitchTurnStrength > MAX_IMPACT_TURN_ANGLE ) + { + pitchTurnStrength = MAX_IMPACT_TURN_ANGLE; + } + else if ( pitchTurnStrength < -MAX_IMPACT_TURN_ANGLE ) + { + pitchTurnStrength = -MAX_IMPACT_TURN_ANGLE; + } + //hitEnt->m_pVehicle->m_vOrientation[PITCH] = AngleNormalize180(hitEnt->m_pVehicle->m_vOrientation[PITCH]+pitchTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + hitEnt->m_pVehicle->m_vFullAngleVelocity[PITCH] = AngleNormalize180(hitEnt->m_pVehicle->m_vOrientation[PITCH]+pitchTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + } + //now do yaw + if ( !bounceDir[0] + && !bounceDir[1] ) + {//shouldn't be any yaw + } + else + { + yawTurnStrength = turnStrength*turnDelta[YAW]; + if ( yawTurnStrength > MAX_IMPACT_TURN_ANGLE ) + { + yawTurnStrength = MAX_IMPACT_TURN_ANGLE; + } + else if ( yawTurnStrength < -MAX_IMPACT_TURN_ANGLE ) + { + yawTurnStrength = -MAX_IMPACT_TURN_ANGLE; + } + //hitEnt->m_pVehicle->m_vOrientation[ROLL] = AngleNormalize180(hitEnt->m_pVehicle->m_vOrientation[ROLL]-yawTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + hitEnt->m_pVehicle->m_vFullAngleVelocity[ROLL] = AngleNormalize180(hitEnt->m_pVehicle->m_vOrientation[ROLL]-yawTurnStrength/turnDivider*pSelfVeh->m_fTimeModifier); + } + //NOTE: will these angle changes stick or will they be stomped + // when the vehicle goes through its own update and re-grabs + // its angles from its pilot...? Should we do a + // SetClientViewAngles on the pilot? + /* + SetClientViewAngle( hitEnt, hitEnt->m_pVehicle->m_vOrientation ); + if ( hitEnt->m_pVehicle->m_pPilot + && ((gentity_t *)hitEnt->m_pVehicle->m_pPilot)->client ) + { + SetClientViewAngle( (gentity_t *)hitEnt->m_pVehicle->m_pPilot, hitEnt->m_pVehicle->m_vOrientation ); + } + */ + } +#endif + } + } + +#ifdef QAGAME + if (!hitEnt) + { + return; + } + + AngleVectors( pSelfVeh->m_vOrientation, NULL, NULL, vehUp ); + if ( pSelfVeh->m_pVehicleInfo->iImpactFX ) + { + //G_PlayEffectID( pSelfVeh->m_pVehicleInfo->iImpactFX, pm->ps->origin, vehUp ); + //tempent use bad! + G_AddEvent((gentity_t *)pEnt, EV_PLAY_EFFECT_ID, pSelfVeh->m_pVehicleInfo->iImpactFX); + } + pEnt->m_pVehicle->m_iHitDebounce = pm->cmd.serverTime + 200; + magnitude /= pSelfVeh->m_pVehicleInfo->toughness * 50.0f; + + if (hitEnt && (hitEnt->s.eType != ET_TERRAIN || !(hitEnt->spawnflags & 1) || pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER)) + { //don't damage the vehicle from terrain that doesn't want to damage vehicles + if (pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) + { //increase the damage... + float mult = (pSelfVeh->m_vOrientation[PITCH]*0.1f); + if (mult < 1.0f) + { + mult = 1.0f; + } + if (hitEnt->inuse && hitEnt->takedamage) + { //if the other guy takes damage, don't hurt us a lot for ramming him + //unless it's a vehicle, then we get 1.5 times damage + if (hitEnt->s.eType == ET_NPC && + hitEnt->s.NPC_class == CLASS_VEHICLE && + hitEnt->m_pVehicle) + { + mult = 1.5f; + } + else + { + mult = 0.5f; + } + } + + magnitude *= mult; + } + pSelfVeh->m_iLastImpactDmg = magnitude; + //FIXME: what about proper death credit to the guy who shot you down? + //FIXME: actually damage part of the ship that impacted? + G_Damage( (gentity_t *)pEnt, NULL, NULL, NULL, pm->ps->origin, magnitude*5, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT + + if (pSelfVeh->m_pVehicleInfo->surfDestruction) + { + G_FlyVehicleSurfaceDestruction((gentity_t *)pEnt, trace, magnitude, forceSurfDestruction ); + } + + pSelfVeh->m_ulFlags |= VEH_CRASHING; + } + + if (hitEnt && + hitEnt->inuse && + hitEnt->takedamage) + { //damage this guy because we hit him + float pmult = 1.0f; + int finalD; + gentity_t *attackEnt; + + if ( (hitEnt->s.eType == ET_PLAYER && hitEnt->s.number < MAX_CLIENTS) || + (hitEnt->s.eType == ET_NPC && hitEnt->s.NPC_class != CLASS_VEHICLE) ) + { //probably a humanoid, or something + if (pSelfVeh->m_pVehicleInfo->type == VH_FIGHTER) + { //player die good.. if me fighter + pmult = 2000.0f; + } + else + { + pmult = 40.0f; + } + + if (hitEnt->client && + BG_KnockDownable(&hitEnt->client->ps) && + G_CanBeEnemy((gentity_t *)pEnt, hitEnt)) + { //smash! + if (hitEnt->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN) + { + hitEnt->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + hitEnt->client->ps.forceHandExtendTime = pm->cmd.serverTime + 1100; + hitEnt->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim + } + + hitEnt->client->ps.otherKiller = pEnt->s.number; + hitEnt->client->ps.otherKillerTime = pm->cmd.serverTime + 5000; + hitEnt->client->ps.otherKillerDebounceTime = pm->cmd.serverTime + 100; + + //add my velocity into his to force him along in the correct direction from impact + VectorAdd(hitEnt->client->ps.velocity, pm->ps->velocity, hitEnt->client->ps.velocity); + //upward thrust + hitEnt->client->ps.velocity[2] += 200.0f; + } + } + + if (pSelfVeh->m_pPilot) + { + attackEnt = (gentity_t *)pSelfVeh->m_pPilot; + } + else + { + attackEnt = (gentity_t *)pEnt; + } + + finalD = magnitude*pmult; + if (finalD < 1) + { + finalD = 1; + } + G_Damage( hitEnt, attackEnt, attackEnt, NULL, pm->ps->origin, finalD, 0, MOD_MELEE );//FIXME: MOD_IMPACT + } +#else //this is gonna result in "double effects" for the client doing the prediction. + //it doesn't look bad though. could just use predicted events, but I'm too lazy. + hitEnt = PM_BGEntForNum(trace->entityNum); + + if (!hitEnt || hitEnt->s.owner != pEnt->s.number) + { //don't hit your own missiles! + AngleVectors( pSelfVeh->m_vOrientation, NULL, NULL, vehUp ); + pEnt->m_pVehicle->m_iHitDebounce = pm->cmd.serverTime + 200; + trap_FX_PlayEffectID( pSelfVeh->m_pVehicleInfo->iImpactFX, pm->ps->origin, vehUp, -1, -1 ); + + pSelfVeh->m_ulFlags |= VEH_CRASHING; + } +#endif + } + } +} + +qboolean PM_GroundSlideOkay( float zNormal ) +{ + if ( zNormal > 0 ) + { + if ( pm->ps->velocity[2] > 0 ) + { + if ( pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT + || pm->ps->legsAnim == BOTH_WALL_RUN_RIGHT_STOP + || pm->ps->legsAnim == BOTH_WALL_RUN_LEFT_STOP + || pm->ps->legsAnim == BOTH_FORCEWALLRUNFLIP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_START + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_ATTACK + || pm->ps->legsAnim == BOTH_FORCELONGLEAP_LAND + || BG_InReboundJump( pm->ps->legsAnim )) + { + return qfalse; + } + } + } + return qtrue; +} + +/* +=============== +qboolean PM_ClientImpact( trace_t *trace, qboolean damageSelf ) + +=============== +*/ +#ifdef QAGAME +extern void Client_CheckImpactBBrush( gentity_t *self, gentity_t *other ); +qboolean PM_ClientImpact( trace_t *trace ) +{ + //don't try to predict this + gentity_t *traceEnt; + int otherEntityNum = trace->entityNum; + + if ( !pm_entSelf ) + { + return qfalse; + } + + if ( otherEntityNum >= ENTITYNUM_WORLD ) + { + return qfalse; + } + + traceEnt = &g_entities[otherEntityNum]; + + if( VectorLength( pm->ps->velocity ) >= 100 + && pm_entSelf->s.NPC_class != CLASS_VEHICLE + && pm->ps->lastOnGround+100 < level.time ) + //&& pm->ps->groundEntityNum == ENTITYNUM_NONE ) + { + Client_CheckImpactBBrush( (gentity_t *)(pm_entSelf), &g_entities[otherEntityNum] ); + } + + if ( !traceEnt + || !(traceEnt->r.contents&pm->tracemask) ) + {//it's dead or not in my way anymore, don't clip against it + return qtrue; + } + + return qfalse; +} +#endif + +/* +================== +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 normal, 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; + //qboolean damageSelf = qtrue; + + 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 ) { + if ( PM_GroundSlideOkay( pml.groundTrace.plane.normal[2] ) ) + {// 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] ); + if ( !PM_GroundSlideOkay( planes[0][2] ) ) + { + planes[0][2] = 0; + VectorNormalize( 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 ); + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + bgEntity_t *pEnt = pm_entSelf; + + if (pEnt && pEnt->s.eType == ET_NPC && pEnt->s.NPC_class == CLASS_VEHICLE && + pEnt->m_pVehicle) + { //do vehicle impact stuff then + PM_VehicleImpact(pEnt, &trace); + } + } +#ifdef QAGAME + else + { + if ( PM_ClientImpact( &trace ) ) + { + continue; + } + } +#endif + + time_left -= time_left * trace.fraction; + + if (numplanes >= MAX_CLIP_PLANES) { + // this shouldn't really happen + VectorClear( pm->ps->velocity ); + return qtrue; + } + + VectorCopy( trace.plane.normal, normal ); + + if ( !PM_GroundSlideOkay( normal[2] ) ) + {//wall-running + //never push up off a sloped wall + normal[2] = 0; + VectorNormalize( normal ); + } + // + // if this is the same plane we hit before, nudge velocity + // out along it, which fixes some epsilon issues with + // non-axial planes + // + if ( !(pm->ps->pm_flags&PMF_STUCK_TO_WALL) ) + {//no sliding if stuck to wall! + for ( i = 0 ; i < numplanes ; i++ ) { + if ( VectorCompare( normal, planes[i] ) ) {//DotProduct( normal, planes[i] ) > 0.99 ) { + VectorAdd( normal, pm->ps->velocity, pm->ps->velocity ); + break; + } + } + if ( i < numplanes ) { + continue; + } + } + VectorCopy (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 triple 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; + qboolean isGiant = qfalse; + bgEntity_t *pEnt; + qboolean skipStep = qfalse; + + VectorCopy (pm->ps->origin, start_o); + VectorCopy (pm->ps->velocity, start_v); + + if ( BG_InReboundHold( pm->ps->legsAnim ) ) + { + gravity = qfalse; + } + + if ( PM_SlideMove( gravity ) == 0 ) { + return; // we got exactly where we wanted to go first try + } + + pEnt = pm_entSelf; + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + if (pEnt && pEnt->s.NPC_class == CLASS_VEHICLE && + pEnt->m_pVehicle && pEnt->m_pVehicle->m_pVehicleInfo->hoverHeight > 0) + { + return; + } + } + + 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); + + if (pm->ps->clientNum >= MAX_CLIENTS) + { + // apply ground friction, even if on ladder + if (pEnt && + pEnt->s.NPC_class == CLASS_ATST || + (pEnt->s.NPC_class == CLASS_VEHICLE && + pEnt->m_pVehicle && + pEnt->m_pVehicle->m_pVehicleInfo->type == VH_WALKER) + ) + {//AT-STs can step high + up[2] += 66.0f; + isGiant = qtrue; + } + else if ( pEnt && pEnt->s.NPC_class == CLASS_RANCOR ) + {//also can step up high + up[2] += 64.0f; + isGiant = qtrue; + } + else + { + up[2] += STEPSIZE; + } + } + else + { + 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 ( pm->stepSlideFix ) + { + if ( pm->ps->clientNum < MAX_CLIENTS + && trace.plane.normal[2] < MIN_WALK_NORMAL ) + {//normal players cannot step up slopes that are too steep to walk on! + vec3_t stepVec; + //okay, the step up ends on a slope that it too steep to step up onto, + //BUT: + //If the step looks like this: + // (B)\__ + // \_____(A) + //Then it might still be okay, so we figure out the slope of the entire move + //from (A) to (B) and if that slope is walk-upabble, then it's okay + VectorSubtract( trace.endpos, down_o, stepVec ); + VectorNormalize( stepVec ); + if ( stepVec[2] > (1.0f-MIN_WALK_NORMAL) ) + { + skipStep = qtrue; + } + } + } + + if ( !trace.allsolid + && !skipStep ) //normal players cannot step up slopes that are too steep to walk on! + { + if ( pm->ps->clientNum >= MAX_CLIENTS//NPC + && isGiant + && trace.entityNum < MAX_CLIENTS + && pEnt + && pEnt->s.NPC_class == CLASS_RANCOR ) + {//Rancor don't step on clients + if ( pm->stepSlideFix ) + { + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + } + else + { + VectorCopy (start_o, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + } + } + /* + else if ( pm->ps->clientNum >= MAX_CLIENTS//NPC + && isGiant + && trace.entityNum < MAX_CLIENTS + && pEnt + && pEnt->s.NPC_class == CLASS_ATST + && OnSameTeam( pEnt, traceEnt) ) + {//NPC AT-ST's don't step up on allies + VectorCopy (start_o, pm->ps->origin); + VectorCopy (start_v, pm->ps->velocity); + } + */ + else + { + VectorCopy (trace.endpos, pm->ps->origin); + if ( pm->stepSlideFix ) + { + if ( trace.fraction < 1.0 ) { + PM_ClipVelocity( pm->ps->velocity, trace.plane.normal, pm->ps->velocity, OVERCLIP ); + } + } + } + } + else + { + if ( pm->stepSlideFix ) + { + VectorCopy (down_o, pm->ps->origin); + VectorCopy (down_v, pm->ps->velocity); + } + } + if ( !pm->stepSlideFix ) + { + 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); + } + } +} + +#include "../namespace_end.h" + diff --git a/code/game/bg_strap.h b/code/game/bg_strap.h new file mode 100644 index 0000000..7114d00 --- /dev/null +++ b/code/game/bg_strap.h @@ -0,0 +1,38 @@ +//rww - shared trap call system +#include "q_shared.h" +#include "bg_public.h" + +//#include "../namespace_begin.h" + +qboolean strap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); + +qboolean strap_G2API_GetBoltMatrix_NoReconstruct(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); + +qboolean strap_G2API_GetBoltMatrix_NoRecNoRot(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); + +qboolean strap_G2API_SetBoneAngles(void *ghoul2, int modelIndex, const char *boneName, const vec3_t angles, const int flags, + const int up, const int right, const int forward, qhandle_t *modelList, + int blendTime , int currentTime ); + +qboolean strap_G2API_SetBoneAnim(void *ghoul2, const int modelIndex, const char *boneName, const int startFrame, const int endFrame, + const int flags, const float animSpeed, const int currentTime, const float setFrame , const int blendTime ); + +qboolean strap_G2API_GetBoneAnim(void *ghoul2, const char *boneName, const int currentTime, float *currentFrame, + int *startFrame, int *endFrame, int *flags, float *animSpeed, int *modelList, const int modelIndex); + +void strap_G2API_SetRagDoll(void *ghoul2, sharedRagDollParams_t *params); + +void strap_G2API_AnimateG2Models(void *ghoul2, int time, sharedRagDollUpdateParams_t *params); + +qboolean strap_G2API_SetBoneIKState(void *ghoul2, int time, const char *boneName, int ikState, sharedSetBoneIKStateParams_t *params); + +qboolean strap_G2API_IKMove(void *ghoul2, int time, sharedIKMoveParams_t *params); + +void strap_TrueMalloc(void **ptr, int size); + +void strap_TrueFree(void **ptr); + +//#include "../namespace_end.h" diff --git a/code/game/bg_vehicleLoad.c b/code/game/bg_vehicleLoad.c new file mode 100644 index 0000000..3c001c8 --- /dev/null +++ b/code/game/bg_vehicleLoad.c @@ -0,0 +1,1670 @@ +//bg_vehicleLoad.c + +#ifdef _JK2 //SP does not have this preprocessor for game like MP does +#ifndef _JK2MP +#define _JK2MP +#endif +#endif + +#ifdef _JK2MP + #include "../qcommon/q_shared.h" + #include "../game/bg_public.h" + #include "../game/bg_vehicles.h" + #include "../game/bg_weapons.h" + + //Could use strap stuff but I don't particularly care at the moment anyway. +//#include "../namespace_begin.h" + extern int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); + extern void trap_FS_Read( void *buffer, int len, fileHandle_t f ); + extern void trap_FS_Write( const void *buffer, int len, fileHandle_t f ); + extern void trap_FS_FCloseFile( fileHandle_t f ); + extern int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +//#include "../namespace_end.h" +#else + #include "g_local.h" + #define QAGAME +#endif + + +#ifdef _JK2MP +#ifndef QAGAME +#ifndef CGAME +#define WE_ARE_IN_THE_UI +#include "../ui/ui_local.h" +#endif +#endif +#endif + +#ifndef _JK2MP +#include "..\Ratl\string_vs.h" +#endif + +#ifdef QAGAME +extern void G_SetSharedVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern int G_ModelIndex( const char *name ); +extern int G_SoundIndex( const char *name ); + #ifdef _JK2MP + extern int G_EffectIndex( const char *name ); + #endif +#elif CGAME +//#include "../namespace_begin.h" +extern qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +extern qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +extern qhandle_t trap_R_RegisterShader( const char *name ); +extern qhandle_t trap_R_RegisterShaderNoMip( const char *name ); +extern int trap_FX_RegisterEffect(const char *file); +extern sfxHandle_t trap_S_RegisterSound( const char *sample); // returns buzz if not found +//#include "../namespace_end.h" +#else//UI +//#include "../namespace_begin.h" +extern qhandle_t trap_R_RegisterModel( const char *name ); // returns rgb axis if not found +extern qhandle_t trap_R_RegisterSkin( const char *name ); // returns all white if not found +extern qhandle_t trap_R_RegisterShader( const char *name ); // returns all white if not found +extern qhandle_t trap_R_RegisterShaderNoMip( const char *name ); // returns all white if not found +extern sfxHandle_t trap_S_RegisterSound( const char *sample); // returns buzz if not found +//#include "../namespace_end.h" +#endif + +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +// These buffers are filled in with the same contents and then just read from in +// a few places. We only need one copy on Xbox. +#define MAX_VEH_WEAPON_DATA_SIZE 0x4000 +#define MAX_VEHICLE_DATA_SIZE 0x10000 + +#if !defined(_XBOX) || defined(QAGAME) + char VehWeaponParms[MAX_VEH_WEAPON_DATA_SIZE]; + char VehicleParms[MAX_VEHICLE_DATA_SIZE]; + +void BG_ClearVehicleParseParms(void) +{ + //You can't strcat to these forever without clearing them! + VehWeaponParms[0] = 0; + VehicleParms[0] = 0; +} + +#else + extern char VehWeaponParms[MAX_VEH_WEAPON_DATA_SIZE]; + extern char VehicleParms[MAX_VEHICLE_DATA_SIZE]; +#endif + +#ifdef _JK2MP +//#include "../namespace_begin.h" +#endif + +#ifndef WE_ARE_IN_THE_UI +//These funcs are actually shared in both projects +extern void G_SetAnimalVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetSpeederVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetWalkerVehicleFunctions( vehicleInfo_t *pVehInfo ); +extern void G_SetFighterVehicleFunctions( vehicleInfo_t *pVehInfo ); +#endif + +vehWeaponInfo_t g_vehWeaponInfo[MAX_VEH_WEAPONS]; +int numVehicleWeapons = 1;//first one is null/default + +vehicleInfo_t g_vehicleInfo[MAX_VEHICLES]; +int numVehicles = 0;//first one is null/default + +void BG_VehicleLoadParms( void ); + +typedef enum { + VF_IGNORE, + VF_INT, + VF_FLOAT, + VF_LSTRING, // string on disk, pointer in memory, TAG_LEVEL + VF_VECTOR, + VF_BOOL, + VF_VEHTYPE, + VF_ANIM, + VF_WEAPON, // take string, resolve into index into VehWeaponParms + VF_MODEL, // take the string, get the G_ModelIndex + VF_MODEL_CLIENT, // (cgame only) take the string, get the G_ModelIndex + VF_EFFECT, // take the string, get the G_EffectIndex + VF_EFFECT_CLIENT, // (cgame only) take the string, get the index + VF_SHADER, // (cgame only) take the string, call trap_R_RegisterShader + VF_SHADER_NOMIP,// (cgame only) take the string, call trap_R_RegisterShaderNoMip + VF_SOUND, // take the string, get the G_SoundIndex + VF_SOUND_CLIENT // (cgame only) take the string, get the index +} vehFieldType_t; + +typedef struct +{ + char *name; + int ofs; + vehFieldType_t type; +} vehField_t; + +vehField_t vehWeaponFields[NUM_VWEAP_PARMS] = +{ + {"name", VWFOFS(name), VF_LSTRING}, //unique name of the vehicle + {"projectile", VWFOFS(bIsProjectile), VF_BOOL}, //traceline or entity? + {"hasGravity", VWFOFS(bHasGravity), VF_BOOL}, //if a projectile, drops + {"ionWeapon", VWFOFS(bIonWeapon), VF_BOOL}, //disables ship shields and sends them out of control + {"saberBlockable", VWFOFS(bSaberBlockable), VF_BOOL}, //lightsabers can deflect this projectile + {"muzzleFX", VWFOFS(iMuzzleFX), VF_EFFECT_CLIENT}, //index of Muzzle Effect + {"model", VWFOFS(iModel), VF_MODEL_CLIENT}, //handle to the model used by this projectile + {"shotFX", VWFOFS(iShotFX), VF_EFFECT_CLIENT}, //index of Shot Effect + {"impactFX", VWFOFS(iImpactFX), VF_EFFECT_CLIENT}, //index of Impact Effect + {"g2MarkShader", VWFOFS(iG2MarkShaderHandle), VF_SHADER}, //index of shader to use for G2 marks made on other models when hit by this projectile + {"g2MarkSize", VWFOFS(fG2MarkSize), VF_FLOAT}, //size (diameter) of the ghoul2 mark + {"loopSound", VWFOFS(iLoopSound), VF_SOUND_CLIENT}, //index of loopSound + {"speed", VWFOFS(fSpeed), VF_FLOAT}, //speed of projectile/range of traceline + {"homing", VWFOFS(fHoming), VF_FLOAT}, //0.0 = not homing, 0.5 = half vel to targ, half cur vel, 1.0 = all vel to targ + {"homingFOV", VWFOFS(fHomingFOV), VF_FLOAT},//missile will lose lock on if DotProduct of missile direction and direction to target ever drops below this (-1 to 1, -1 = never lose target, 0 = lose if ship gets behind missile, 1 = pretty much will lose it's target right away) + {"lockOnTime", VWFOFS(iLockOnTime), VF_INT}, //0 = no lock time needed, else # of ms needed to lock on + {"damage", VWFOFS(iDamage), VF_INT}, //damage done when traceline or projectile directly hits target + {"splashDamage", VWFOFS(iSplashDamage), VF_INT},//damage done to ents in splashRadius of end of traceline or projectile origin on impact + {"splashRadius", VWFOFS(fSplashRadius), VF_FLOAT},//radius that ent must be in to take splashDamage (linear fall-off) + {"ammoPerShot", VWFOFS(iAmmoPerShot), VF_INT},//how much "ammo" each shot takes + {"health", VWFOFS(iHealth), VF_INT}, //if non-zero, projectile can be shot, takes this much damage before being destroyed + {"width", VWFOFS(fWidth), VF_FLOAT}, //width of traceline or bounding box of projecile (non-rotating!) + {"height", VWFOFS(fHeight), VF_FLOAT}, //height of traceline or bounding box of projecile (non-rotating!) + {"lifetime", VWFOFS(iLifeTime), VF_INT}, //removes itself after this amount of time + {"explodeOnExpire", VWFOFS(bExplodeOnExpire), VF_BOOL}, //when iLifeTime is up, explodes rather than simply removing itself +}; + +static qboolean BG_ParseVehWeaponParm( vehWeaponInfo_t *vehWeapon, char *parmName, char *pValue ) +{ + int i; + vec3_t vec; + byte *b = (byte *)vehWeapon; + int _iFieldsRead = 0; + vehicleType_t vehType; + char value[1024]; + + Q_strncpyz( value, pValue, sizeof(value) ); + + // Loop through possible parameters + for ( i = 0; i < NUM_VWEAP_PARMS; i++ ) + { + if ( vehWeaponFields[i].name && !Q_stricmp( vehWeaponFields[i].name, parmName ) ) + { + // found it + switch( vehWeaponFields[i].type ) + { + case VF_INT: + *(int *)(b+vehWeaponFields[i].ofs) = atoi(value); + break; + case VF_FLOAT: + *(float *)(b+vehWeaponFields[i].ofs) = atof(value); + break; + case VF_LSTRING: // string on disk, pointer in memory, TAG_LEVEL + if (!*(char **)(b+vehWeaponFields[i].ofs)) + { //just use 1024 bytes in case we want to write over the string +#ifdef _JK2MP + *(char **)(b+vehWeaponFields[i].ofs) = (char *)BG_Alloc(1024);//(char *)BG_Alloc(strlen(value)); + strcpy(*(char **)(b+vehWeaponFields[i].ofs), value); +#else + (*(char **)(b+vehWeaponFields[i].ofs)) = G_NewString( value ); +#endif + } + + break; + case VF_VECTOR: + _iFieldsRead = sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + assert(_iFieldsRead==3 ); + if (_iFieldsRead!=3) + { + Com_Printf (S_COLOR_YELLOW"BG_ParseVehWeaponParm: VEC3 sscanf() failed to read 3 floats ('angle' key bug?)\n"); + } + ((float *)(b+vehWeaponFields[i].ofs))[0] = vec[0]; + ((float *)(b+vehWeaponFields[i].ofs))[1] = vec[1]; + ((float *)(b+vehWeaponFields[i].ofs))[2] = vec[2]; + break; + case VF_BOOL: + *(qboolean *)(b+vehWeaponFields[i].ofs) = (qboolean)(atof(value)!=0); + break; + case VF_VEHTYPE: + vehType = (vehicleType_t)GetIDForString( VehicleTable, value ); + *(vehicleType_t *)(b+vehWeaponFields[i].ofs) = vehType; + break; + case VF_ANIM: + { + int anim = GetIDForString( animTable, value ); + *(int *)(b+vehWeaponFields[i].ofs) = anim; + } + break; + case VF_WEAPON: // take string, resolve into index into VehWeaponParms + //*(int *)(b+vehWeaponFields[i].ofs) = VEH_VehWeaponIndexForName( value ); + break; + case VF_MODEL:// take the string, get the G_ModelIndex +#ifdef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_MODEL_CLIENT: // (MP cgame only) take the string, get the G_ModelIndex +#ifndef _JK2MP + *(int *)(b+vehWeaponFields[i].ofs) = G_ModelIndex( value ); +#elif QAGAME + //*(int *)(b+vehWeaponFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_EFFECT: // take the string, get the G_EffectIndex +#ifdef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_EFFECT_CLIENT: // (MP cgame only) take the string, get the index +#ifndef _JK2MP + *(int *)(b+vehWeaponFields[i].ofs) = G_EffectIndex( value ); +#elif QAGAME + //*(int *)(b+vehWeaponFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_SHADER: // (cgame only) take the string, call trap_R_RegisterShader +#ifdef WE_ARE_IN_THE_UI + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#elif CGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterShader( value ); +#endif + break; + case VF_SHADER_NOMIP:// (cgame only) take the string, call trap_R_RegisterShaderNoMip +#ifndef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#endif + break; + case VF_SOUND: // take the string, get the G_SoundIndex +#ifdef QAGAME + *(int *)(b+vehWeaponFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + case VF_SOUND_CLIENT: // (MP cgame only) take the string, get the index +#ifndef _JK2MP + *(int *)(b+vehWeaponFields[i].ofs) = G_SoundIndex( value ); +#elif QAGAME + //*(int *)(b+vehWeaponFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehWeaponFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + default: + //Unknown type? + return qfalse; + break; + } + break; + } + } + if ( i == NUM_VWEAP_PARMS ) + { + return qfalse; + } + else + { + return qtrue; + } +} + +int VEH_LoadVehWeapon( const char *vehWeaponName ) +{//load up specified vehWeapon and save in array: g_vehWeaponInfo + const char *token; + char parmName[128];//we'll assume that no parm name is longer than 128 + char *value; + char *p; + vehWeaponInfo_t *vehWeapon = NULL; + + //BG_VehWeaponSetDefaults( &g_vehWeaponInfo[0] );//set the first vehicle to default data + + //try to parse data out + p = VehWeaponParms; + +#ifdef _JK2MP + COM_BeginParseSession("vehWeapons"); +#else + COM_BeginParseSession(); +#endif + + vehWeapon = &g_vehWeaponInfo[numVehicleWeapons]; + // look for the right vehicle weapon + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return qfalse; + } + + if ( !Q_stricmp( token, vehWeaponName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + if ( !p ) + { + return qfalse; + } + + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + {//barf + return VEH_WEAPON_NONE; + } + + if ( Q_stricmp( token, "{" ) != 0 ) + { + return VEH_WEAPON_NONE; + } + + // parse the vehWeapon info block + while ( 1 ) + { + SkipRestOfLine( &p ); + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing Vehicle Weapon '%s'\n", vehWeaponName ); + return VEH_WEAPON_NONE; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + Q_strncpyz( parmName, token, sizeof(parmName) ); + value = COM_ParseExt( &p, qtrue ); + if ( !value || !value[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Vehicle Weapon token '%s' has no value!\n", parmName ); + } + else + { + if ( !BG_ParseVehWeaponParm( vehWeapon, parmName, value ) ) + { + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle Weapon key/value pair '%s','%s'!\n", parmName, value ); + } + } + } + if ( vehWeapon->fHoming ) + {//all lock-on weapons use these 2 sounds +#ifdef QAGAME + //Hmm, no need fo have server register this, is there? + //G_SoundIndex( "sound/weapons/torpedo/tick.wav" ); + //G_SoundIndex( "sound/weapons/torpedo/lock.wav" ); +#elif CGAME + trap_S_RegisterSound( "sound/vehicles/weapons/common/tick.wav" ); + trap_S_RegisterSound( "sound/vehicles/weapons/common/lock.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm1.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm2.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm3.wav" ); +#else + trap_S_RegisterSound( "sound/vehicles/weapons/common/tick.wav" ); + trap_S_RegisterSound( "sound/vehicles/weapons/common/lock.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm1.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm2.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/lockalarm3.wav" ); +#endif + } + return (numVehicleWeapons++); +} + +int VEH_VehWeaponIndexForName( const char *vehWeaponName ) +{ + int vw; + if ( !vehWeaponName || !vehWeaponName[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Trying to read Vehicle Weapon with no name!\n" ); + return VEH_WEAPON_NONE; + } + for ( vw = VEH_WEAPON_BASE; vw < numVehicleWeapons; vw++ ) + { + if ( g_vehWeaponInfo[vw].name + && Q_stricmp( g_vehWeaponInfo[vw].name, vehWeaponName ) == 0 ) + {//already loaded this one + return vw; + } + } + //haven't loaded it yet + if ( vw >= MAX_VEH_WEAPONS ) + {//no more room! + Com_Printf( S_COLOR_RED"ERROR: Too many Vehicle Weapons (max 16), aborting load on %s!\n", vehWeaponName ); + return VEH_WEAPON_NONE; + } + //we have room for another one, load it up and return the index + //HMM... should we not even load the .vwp file until we want to? + vw = VEH_LoadVehWeapon( vehWeaponName ); + if ( vw == VEH_WEAPON_NONE ) + { + Com_Printf( S_COLOR_RED"ERROR: Could not find Vehicle Weapon %s!\n", vehWeaponName ); + } + return vw; +} + +vehField_t vehicleFields[] = +{ + {"name", VFOFS(name), VF_LSTRING}, //unique name of the vehicle + + //general data + {"type", VFOFS(type), VF_VEHTYPE}, //what kind of vehicle + {"numHands", VFOFS(numHands), VF_INT}, //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + {"lookPitch", VFOFS(lookPitch), VF_FLOAT}, //How far you can look up and down off the forward of the vehicle + {"lookYaw", VFOFS(lookYaw), VF_FLOAT}, //How far you can look left and right off the forward of the vehicle + {"length", VFOFS(length), VF_FLOAT}, //how long it is - used for body length traces when turning/moving? + {"width", VFOFS(width), VF_FLOAT}, //how wide it is - used for body length traces when turning/moving? + {"height", VFOFS(height), VF_FLOAT}, //how tall it is - used for body length traces when turning/moving? + {"centerOfGravity", VFOFS(centerOfGravity), VF_VECTOR},//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats + {"speedMax", VFOFS(speedMax), VF_FLOAT}, //top speed + {"turboSpeed", VFOFS(turboSpeed), VF_FLOAT}, //turbo speed + {"speedMin", VFOFS(speedMin), VF_FLOAT}, //if < 0, can go in reverse + {"speedIdle", VFOFS(speedIdle), VF_FLOAT}, //what speed it drifts to when no accel/decel input is given + {"accelIdle", VFOFS(accelIdle), VF_FLOAT}, //if speedIdle > 0, how quickly it goes up to that speed + {"acceleration", VFOFS(acceleration), VF_FLOAT}, //when pressing on accelerator + {"decelIdle", VFOFS(decelIdle), VF_FLOAT}, //when giving no input, how quickly it drops to speedIdle + {"throttleSticks", VFOFS(throttleSticks), VF_BOOL},//if true, speed stays at whatever you accel/decel to, unless you turbo or brake + {"strafePerc", VFOFS(strafePerc), VF_FLOAT}, //multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + {"bankingSpeed", VFOFS(bankingSpeed), VF_FLOAT}, //how quickly it pitches and rolls (not under player control) + {"pitchLimit", VFOFS(pitchLimit), VF_FLOAT}, //how far it can roll forward or backward + {"rollLimit", VFOFS(rollLimit), VF_FLOAT}, //how far it can roll to either side + {"braking", VFOFS(braking), VF_FLOAT}, //when pressing on decelerator + {"mouseYaw", VFOFS(mouseYaw), VF_FLOAT}, // The mouse yaw override. + {"mousePitch", VFOFS(mousePitch), VF_FLOAT}, // The mouse yaw override. + {"turningSpeed", VFOFS(turningSpeed), VF_FLOAT}, //how quickly you can turn + {"turnWhenStopped", VFOFS(turnWhenStopped), VF_BOOL},//whether or not you can turn when not moving + {"traction", VFOFS(traction), VF_FLOAT}, //how much your command input affects velocity + {"friction", VFOFS(friction), VF_FLOAT}, //how much velocity is cut on its own + {"maxSlope", VFOFS(maxSlope), VF_FLOAT}, //the max slope that it can go up with control + {"speedDependantTurning", VFOFS(speedDependantTurning), VF_BOOL},//vehicle turns faster the faster it's going + + //durability stats + {"mass", VFOFS(mass), VF_INT}, //for momentum and impact force (player mass is 10) + {"armor", VFOFS(armor), VF_INT}, //total points of damage it can take + {"shields", VFOFS(shields), VF_INT}, //energy shield damage points + {"shieldRechargeMS", VFOFS(shieldRechargeMS), VF_INT},//energy shield milliseconds per point recharged + {"toughness", VFOFS(toughness), VF_FLOAT}, //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + {"malfunctionArmorLevel", VFOFS(malfunctionArmorLevel), VF_INT},//when armor drops to or below this point, start malfunctioning + {"surfDestruction", VFOFS(surfDestruction), VF_INT}, + + //visuals & sounds + {"model", VFOFS(model), VF_LSTRING}, //what model to use - if make it an NPC's primary model, don't need this? + {"skin", VFOFS(skin), VF_LSTRING}, //what skin to use - if make it an NPC's primary model, don't need this? + {"g2radius", VFOFS(g2radius), VF_INT}, //render radius (really diameter, but...) for the ghoul2 model + {"riderAnim", VFOFS(riderAnim), VF_ANIM}, //what animation the rider uses + {"droidNPC", VFOFS(droidNPC), VF_LSTRING}, //NPC to attach to *droidunit tag (if it exists in the model) + +#ifdef _JK2MP + {"radarIcon", VFOFS(radarIconHandle), VF_SHADER_NOMIP}, //what icon to show on radar in MP + {"dmgIndicFrame", VFOFS(dmgIndicFrameHandle), VF_SHADER_NOMIP}, //what image to use for the frame of the damage indicator + {"dmgIndicShield", VFOFS(dmgIndicShieldHandle), VF_SHADER_NOMIP},//what image to use for the shield of the damage indicator + {"dmgIndicBackground", VFOFS(dmgIndicBackgroundHandle), VF_SHADER_NOMIP},//what image to use for the background of the damage indicator + {"icon_front", VFOFS(iconFrontHandle), VF_SHADER_NOMIP}, //what image to use for the front of the ship on the damage indicator + {"icon_back", VFOFS(iconBackHandle), VF_SHADER_NOMIP}, //what image to use for the back of the ship on the damage indicator + {"icon_right", VFOFS(iconRightHandle), VF_SHADER_NOMIP}, //what image to use for the right of the ship on the damage indicator + {"icon_left", VFOFS(iconLeftHandle), VF_SHADER_NOMIP}, //what image to use for the left of the ship on the damage indicator + {"crosshairShader", VFOFS(crosshairShaderHandle), VF_SHADER_NOMIP}, //what image to use as the crosshair + {"shieldShader", VFOFS(shieldShaderHandle), VF_SHADER}, //What shader to use when drawing the shield shell + + //individual "area" health -rww + {"health_front", VFOFS(health_front), VF_INT}, + {"health_back", VFOFS(health_back), VF_INT}, + {"health_right", VFOFS(health_right), VF_INT}, + {"health_left", VFOFS(health_left), VF_INT}, +#else + {"radarIcon", 0, VF_IGNORE}, //what icon to show on radar in MP +#endif + + {"soundOn", VFOFS(soundOn), VF_SOUND},//sound to play when get on it + {"soundOff", VFOFS(soundOff), VF_SOUND},//sound to play when get off + {"soundLoop", VFOFS(soundLoop), VF_SOUND},//sound to loop while riding it + {"soundTakeOff", VFOFS(soundTakeOff), VF_SOUND},//sound to play when ship takes off + {"soundEngineStart",VFOFS(soundEngineStart),VF_SOUND_CLIENT},//sound to play when ship's thrusters first activate + {"soundSpin", VFOFS(soundSpin), VF_SOUND},//sound to loop while spiraling out of control + {"soundTurbo", VFOFS(soundTurbo), VF_SOUND},//sound to play when turbo/afterburner kicks in + {"soundHyper", VFOFS(soundHyper), VF_SOUND_CLIENT},//sound to play when hits hyperspace + {"soundLand", VFOFS(soundLand), VF_SOUND},//sound to play when ship lands + {"soundFlyBy", VFOFS(soundFlyBy), VF_SOUND_CLIENT},//sound to play when they buzz you + {"soundFlyBy2", VFOFS(soundFlyBy2), VF_SOUND_CLIENT},//alternate sound to play when they buzz you + {"soundShift1", VFOFS(soundShift1), VF_SOUND},//sound to play when changing speeds + {"soundShift2", VFOFS(soundShift2), VF_SOUND},//sound to play when changing speeds + {"soundShift3", VFOFS(soundShift3), VF_SOUND},//sound to play when changing speeds + {"soundShift4", VFOFS(soundShift4), VF_SOUND},//sound to play when changing speeds + + {"exhaustFX", VFOFS(iExhaustFX), VF_EFFECT_CLIENT}, //exhaust effect, played from "*exhaust" bolt(s) + {"turboFX", VFOFS(iTurboFX), VF_EFFECT_CLIENT}, //turbo exhaust effect, played from "*exhaust" bolt(s) when ship is in "turbo" mode + {"turboStartFX", VFOFS(iTurboStartFX), VF_EFFECT}, //turbo start effect, played from "*exhaust" bolt(s) when ship is in "turbo" mode + {"trailFX", VFOFS(iTrailFX), VF_EFFECT_CLIENT}, //trail effect, played from "*trail" bolt(s) + {"impactFX", VFOFS(iImpactFX), VF_EFFECT_CLIENT}, //impact effect, for when it bumps into something + {"explodeFX", VFOFS(iExplodeFX), VF_EFFECT}, //explosion effect, for when it blows up (should have the sound built into explosion effect) + {"wakeFX", VFOFS(iWakeFX), VF_EFFECT_CLIENT}, //effect it makes when going across water + {"dmgFX", VFOFS(iDmgFX), VF_EFFECT_CLIENT}, //effect to play on damage from a weapon or something +#ifdef _JK2MP + {"injureFX", VFOFS(iInjureFX), VF_EFFECT_CLIENT}, //effect to play on partially damaged ship surface + {"noseFX", VFOFS(iNoseFX), VF_EFFECT_CLIENT}, //effect for nose piece flying away when blown off + {"lwingFX", VFOFS(iLWingFX), VF_EFFECT_CLIENT}, //effect for left wing piece flying away when blown off + {"rwingFX", VFOFS(iRWingFX), VF_EFFECT_CLIENT}, //effect for right wing piece flying away when blown off +#else + {"armorLowFX", VFOFS(iArmorLowFX), VF_EFFECT_CLIENT}, //effect to play on damage from a weapon or something + {"armorGoneFX", VFOFS(iArmorGoneFX), VF_EFFECT_CLIENT}, //effect to play on damage from a weapon or something +#endif + + // Weapon stuff: + {"weap1", VFOFS(weapon[0].ID), VF_WEAPON}, //weapon used when press fire + {"weap2", VFOFS(weapon[1].ID), VF_WEAPON},//weapon used when press alt-fire + // The delay between shots for this weapon. + {"weap1Delay", VFOFS(weapon[0].delay), VF_INT}, + {"weap2Delay", VFOFS(weapon[1].delay), VF_INT}, + // Whether or not all the muzzles for each weapon can be linked together (linked delay = weapon delay * number of muzzles linked!) + {"weap1Link", VFOFS(weapon[0].linkable), VF_INT}, + {"weap2Link", VFOFS(weapon[1].linkable), VF_INT}, + // Whether or not to auto-aim the projectiles at the thing under the crosshair when we fire + {"weap1Aim", VFOFS(weapon[0].aimCorrect), VF_BOOL}, + {"weap2Aim", VFOFS(weapon[1].aimCorrect), VF_BOOL}, + //maximum ammo + {"weap1AmmoMax", VFOFS(weapon[0].ammoMax), VF_INT}, + {"weap2AmmoMax", VFOFS(weapon[1].ammoMax), VF_INT}, + //ammo recharge rate - milliseconds per unit (minimum of 100, which is 10 ammo per second) + {"weap1AmmoRechargeMS", VFOFS(weapon[0].ammoRechargeMS), VF_INT}, + {"weap2AmmoRechargeMS", VFOFS(weapon[1].ammoRechargeMS), VF_INT}, + //sound to play when out of ammo (plays default "no ammo" sound if none specified) + {"weap1SoundNoAmmo", VFOFS(weapon[0].soundNoAmmo), VF_SOUND_CLIENT},//sound to play when try to fire weapon 1 with no ammo + {"weap2SoundNoAmmo", VFOFS(weapon[1].soundNoAmmo), VF_SOUND_CLIENT},//sound to play when try to fire weapon 2 with no ammo + + // Which weapon a muzzle fires (has to match one of the weapons this vehicle has). + {"weapMuzzle1", VFOFS(weapMuzzle[0]), VF_WEAPON}, + {"weapMuzzle2", VFOFS(weapMuzzle[1]), VF_WEAPON}, + {"weapMuzzle3", VFOFS(weapMuzzle[2]), VF_WEAPON}, + {"weapMuzzle4", VFOFS(weapMuzzle[3]), VF_WEAPON}, + {"weapMuzzle5", VFOFS(weapMuzzle[4]), VF_WEAPON}, + {"weapMuzzle6", VFOFS(weapMuzzle[5]), VF_WEAPON}, + {"weapMuzzle7", VFOFS(weapMuzzle[6]), VF_WEAPON}, + {"weapMuzzle8", VFOFS(weapMuzzle[7]), VF_WEAPON}, + {"weapMuzzle9", VFOFS(weapMuzzle[8]), VF_WEAPON}, + {"weapMuzzle10", VFOFS(weapMuzzle[9]), VF_WEAPON}, + + // The max height before this ship (?) starts (auto)landing. + {"landingHeight", VFOFS(landingHeight), VF_FLOAT}, + + //other misc stats + {"gravity", VFOFS(gravity), VF_INT}, //normal is 800 + {"hoverHeight", VFOFS(hoverHeight), VF_FLOAT}, //if 0, it's a ground vehicle + {"hoverStrength", VFOFS(hoverStrength), VF_FLOAT}, //how hard it pushes off ground when less than hover height... causes "bounce", like shocks + {"waterProof", VFOFS(waterProof), VF_BOOL}, //can drive underwater if it has to + {"bouyancy", VFOFS(bouyancy), VF_FLOAT}, //when in water, how high it floats (1 is neutral bouyancy) + {"fuelMax", VFOFS(fuelMax), VF_INT}, //how much fuel it can hold (capacity) + {"fuelRate", VFOFS(fuelRate), VF_INT}, //how quickly is uses up fuel + {"turboDuration", VFOFS(turboDuration), VF_INT}, //how long turbo lasts + {"turboRecharge", VFOFS(turboRecharge), VF_INT}, //how long turbo takes to recharge + {"visibility", VFOFS(visibility), VF_INT}, //for sight alerts + {"loudness", VFOFS(loudness), VF_INT}, //for sound alerts + {"explosionRadius", VFOFS(explosionRadius), VF_FLOAT},//range of explosion + {"explosionDamage", VFOFS(explosionDamage), VF_INT},//damage of explosion + + //new stuff + {"maxPassengers", VFOFS(maxPassengers), VF_INT}, // The max number of passengers this vehicle may have (Default = 0). + {"hideRider", VFOFS(hideRider), VF_BOOL}, // rider (and passengers?) should not be drawn + {"killRiderOnDeath", VFOFS(killRiderOnDeath), VF_BOOL},//if rider is on vehicle when it dies, they should die + {"flammable", VFOFS(flammable), VF_BOOL}, //whether or not the vehicle should catch on fire before it explodes + {"explosionDelay", VFOFS(explosionDelay), VF_INT}, //how long the vehicle should be on fire/dying before it explodes + //camera stuff + {"cameraOverride", VFOFS(cameraOverride), VF_BOOL},//override the third person camera with the below values - normal is 0 (off) + {"cameraRange", VFOFS(cameraRange), VF_FLOAT}, //how far back the camera should be - normal is 80 + {"cameraVertOffset", VFOFS(cameraVertOffset), VF_FLOAT},//how high over the vehicle origin the camera should be - normal is 16 + {"cameraHorzOffset", VFOFS(cameraHorzOffset), VF_FLOAT},//how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + {"cameraPitchOffset", VFOFS(cameraPitchOffset), VF_FLOAT},//a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + {"cameraFOV", VFOFS(cameraFOV), VF_FLOAT}, //third person camera FOV, default is 80 + {"cameraAlpha", VFOFS(cameraAlpha), VF_FLOAT}, //fade out the vehicle to this alpha (0.1-1.0f) if it's in the way of the crosshair + {"cameraPitchDependantVertOffset", VFOFS(cameraPitchDependantVertOffset), VF_BOOL}, //use the hacky AT-ST pitch dependant vertical offset +//===TURRETS=========================================================================== + //Turret 1 + {"turret1Weap", VFOFS(turret[0].iWeapon), VF_WEAPON}, + {"turret1Delay", VFOFS(turret[0].iDelay), VF_INT}, + {"turret1AmmoMax", VFOFS(turret[0].iAmmoMax), VF_INT}, + {"turret1AmmoRechargeMS", VFOFS(turret[0].iAmmoRechargeMS), VF_INT}, + {"turret1YawBone", VFOFS(turret[0].yawBone), VF_LSTRING}, + {"turret1PitchBone", VFOFS(turret[0].pitchBone), VF_LSTRING}, + {"turret1YawAxis", VFOFS(turret[0].yawAxis), VF_INT}, + {"turret1PitchAxis", VFOFS(turret[0].pitchAxis), VF_INT}, + {"turret1ClampYawL", VFOFS(turret[0].yawClampLeft), VF_FLOAT}, //how far the turret is allowed to turn left + {"turret1ClampYawR", VFOFS(turret[0].yawClampRight), VF_FLOAT}, //how far the turret is allowed to turn right + {"turret1ClampPitchU", VFOFS(turret[0].pitchClampUp), VF_FLOAT}, //how far the turret is allowed to title up + {"turret1ClampPitchD", VFOFS(turret[0].pitchClampDown), VF_FLOAT}, //how far the turret is allowed to tilt down + {"turret1Muzzle1", VFOFS(turret[0].iMuzzle[0]), VF_INT}, + {"turret1Muzzle2", VFOFS(turret[0].iMuzzle[1]), VF_INT}, + {"turret1TurnSpeed", VFOFS(turret[0].fTurnSpeed), VF_FLOAT}, + {"turret1AI", VFOFS(turret[0].bAI), VF_BOOL}, + {"turret1AILead", VFOFS(turret[0].bAILead), VF_BOOL}, + {"turret1AIRange", VFOFS(turret[0].fAIRange), VF_FLOAT}, + {"turret1PassengerNum", VFOFS(turret[0].passengerNum), VF_INT},//which number passenger can control this turret + {"turret1GunnerViewTag", VFOFS(turret[0].gunnerViewTag), VF_LSTRING}, + + //Turret 2 + {"turret2Weap", VFOFS(turret[1].iWeapon), VF_WEAPON}, + {"turret2Delay", VFOFS(turret[1].iDelay), VF_INT}, + {"turret2AmmoMax", VFOFS(turret[1].iAmmoMax), VF_INT}, + {"turret2AmmoRechargeMS", VFOFS(turret[1].iAmmoRechargeMS), VF_INT}, + {"turret2YawBone", VFOFS(turret[1].yawBone), VF_LSTRING}, + {"turret2PitchBone", VFOFS(turret[1].pitchBone), VF_LSTRING}, + {"turret2YawAxis", VFOFS(turret[1].yawAxis), VF_INT}, + {"turret2PitchAxis", VFOFS(turret[1].pitchAxis), VF_INT}, + {"turret2ClampYawL", VFOFS(turret[1].yawClampLeft), VF_FLOAT}, //how far the turret is allowed to turn left + {"turret2ClampYawR", VFOFS(turret[1].yawClampRight), VF_FLOAT}, //how far the turret is allowed to turn right + {"turret2ClampPitchU", VFOFS(turret[1].pitchClampUp), VF_FLOAT}, //how far the turret is allowed to title up + {"turret2ClampPitchD", VFOFS(turret[1].pitchClampDown), VF_FLOAT}, //how far the turret is allowed to tilt down + {"turret2Muzzle1", VFOFS(turret[1].iMuzzle[0]), VF_INT}, + {"turret2Muzzle2", VFOFS(turret[1].iMuzzle[1]), VF_INT}, + {"turret2TurnSpeed", VFOFS(turret[1].fTurnSpeed), VF_FLOAT}, + {"turret2AI", VFOFS(turret[1].bAI), VF_BOOL}, + {"turret2AILead", VFOFS(turret[1].bAILead), VF_BOOL}, + {"turret2AIRange", VFOFS(turret[1].fAIRange), VF_FLOAT}, + {"turret2PassengerNum", VFOFS(turret[1].passengerNum), VF_INT},//which number passenger can control this turret + {"turret2GunnerViewTag", VFOFS(turret[1].gunnerViewTag), VF_LSTRING}, +//===END TURRETS=========================================================================== + //terminating entry + {0, -1, VF_INT} +}; + +stringID_table_t VehicleTable[VH_NUM_VEHICLES+1] = +{ + ENUM2STRING(VH_NONE), + ENUM2STRING(VH_WALKER), //something you ride inside of, it walks like you, like an AT-ST + ENUM2STRING(VH_FIGHTER), //something you fly inside of, like an X-Wing or TIE fighter + ENUM2STRING(VH_SPEEDER), //something you ride on that hovers, like a speeder or swoop + ENUM2STRING(VH_ANIMAL), //animal you ride on top of that walks, like a tauntaun + ENUM2STRING(VH_FLIER), //animal you ride on top of that flies, like a giant mynoc? + 0, -1 +}; + +// Setup the shared functions (one's that all vehicles would generally use). +void BG_SetSharedVehicleFunctions( vehicleInfo_t *pVehInfo ) +{ +#ifdef QAGAME + //only do the whole thing if we're on game + G_SetSharedVehicleFunctions(pVehInfo); +#endif + +#ifndef WE_ARE_IN_THE_UI + switch( pVehInfo->type ) + { + case VH_SPEEDER: + G_SetSpeederVehicleFunctions( pVehInfo ); + break; + case VH_ANIMAL: + G_SetAnimalVehicleFunctions( pVehInfo ); + break; + case VH_FIGHTER: + G_SetFighterVehicleFunctions( pVehInfo ); + break; + case VH_WALKER: + G_SetWalkerVehicleFunctions( pVehInfo ); + break; + } +#endif +} + +void BG_VehicleSetDefaults( vehicleInfo_t *vehicle ) +{ + memset(vehicle, 0, sizeof(vehicleInfo_t)); +/* +#if _JK2MP + if (!vehicle->name) + { + vehicle->name = (char *)BG_Alloc(1024); + } + strcpy(vehicle->name, "default"); +#else + vehicle->name = G_NewString( "default" ); +#endif + + //general data + vehicle->type = VH_SPEEDER; //what kind of vehicle + //FIXME: no saber or weapons if numHands = 2, should switch to speeder weapon, no attack anim on player + vehicle->numHands = 0; //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + vehicle->lookPitch = 0; //How far you can look up and down off the forward of the vehicle + vehicle->lookYaw = 5; //How far you can look left and right off the forward of the vehicle + vehicle->length = 0; //how long it is - used for body length traces when turning/moving? + vehicle->width = 0; //how wide it is - used for body length traces when turning/moving? + vehicle->height = 0; //how tall it is - used for body length traces when turning/moving? + VectorClear( vehicle->centerOfGravity );//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats - note: these are DESIRED speed, not actual current speed/velocity + vehicle->speedMax = VEH_DEFAULT_SPEED_MAX; //top speed + vehicle->turboSpeed = 0; //turboBoost + vehicle->speedMin = 0; //if < 0, can go in reverse + vehicle->speedIdle = 0; //what speed it drifts to when no accel/decel input is given + vehicle->accelIdle = 0; //if speedIdle > 0, how quickly it goes up to that speed + vehicle->acceleration = VEH_DEFAULT_ACCEL; //when pressing on accelerator (1/2 this when going in reverse) + vehicle->decelIdle = VEH_DEFAULT_DECEL; //when giving no input, how quickly it desired speed drops to speedIdle + vehicle->strafePerc = VEH_DEFAULT_STRAFE_PERC;//multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + vehicle->bankingSpeed = VEH_DEFAULT_BANKING_SPEED; //how quickly it pitches and rolls (not under player control) + vehicle->rollLimit = VEH_DEFAULT_ROLL_LIMIT; //how far it can roll to either side + vehicle->pitchLimit = VEH_DEFAULT_PITCH_LIMIT; //how far it can pitch forward or backward + vehicle->braking = VEH_DEFAULT_BRAKING; //when pressing on decelerator (backwards) + vehicle->turningSpeed = VEH_DEFAULT_TURNING_SPEED; //how quickly you can turn + vehicle->turnWhenStopped = qfalse; //whether or not you can turn when not moving + vehicle->traction = VEH_DEFAULT_TRACTION; //how much your command input affects velocity + vehicle->friction = VEH_DEFAULT_FRICTION; //how much velocity is cut on its own + vehicle->maxSlope = VEH_DEFAULT_MAX_SLOPE; //the max slope that it can go up with control + + //durability stats + vehicle->mass = VEH_DEFAULT_MASS; //for momentum and impact force (player mass is 10) + vehicle->armor = VEH_DEFAULT_MAX_ARMOR; //total points of damage it can take + vehicle->toughness = VEH_DEFAULT_TOUGHNESS; //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + vehicle->malfunctionArmorLevel = 0; //when armor drops to or below this point, start malfunctioning + + //visuals & sounds + //vehicle->model = "models/map_objects/ships/swoop.md3"; //what model to use - if make it an NPC's primary model, don't need this? + if (!vehicle->model) + { + vehicle->model = (char *)BG_Alloc(1024); + } + strcpy(vehicle->model, "models/map_objects/ships/swoop.md3"); + + vehicle->modelIndex = 0; //set internally, not until this vehicle is spawned into the level + vehicle->skin = NULL; //what skin to use - if make it an NPC's primary model, don't need this? + vehicle->riderAnim = BOTH_GUNSIT1; //what animation the rider uses + + vehicle->soundOn = NULL; //sound to play when get on it + vehicle->soundLoop = NULL; //sound to loop while riding it + vehicle->soundOff = NULL; //sound to play when get off + vehicle->exhaustFX = NULL; //exhaust effect, played from "*exhaust" bolt(s) + vehicle->trailFX = NULL; //trail effect, played from "*trail" bolt(s) + vehicle->impactFX = NULL; //explosion effect, for when it blows up (should have the sound built into explosion effect) + vehicle->explodeFX = NULL; //explosion effect, for when it blows up (should have the sound built into explosion effect) + vehicle->wakeFX = NULL; //effect itmakes when going across water + + //other misc stats + vehicle->gravity = VEH_DEFAULT_GRAVITY; //normal is 800 + vehicle->hoverHeight = 0;//VEH_DEFAULT_HOVER_HEIGHT; //if 0, it's a ground vehicle + vehicle->hoverStrength = 0;//VEH_DEFAULT_HOVER_STRENGTH;//how hard it pushes off ground when less than hover height... causes "bounce", like shocks + vehicle->waterProof = qtrue; //can drive underwater if it has to + vehicle->bouyancy = 1.0f; //when in water, how high it floats (1 is neutral bouyancy) + vehicle->fuelMax = 1000; //how much fuel it can hold (capacity) + vehicle->fuelRate = 1; //how quickly is uses up fuel + vehicle->visibility = VEH_DEFAULT_VISIBILITY; //radius for sight alerts + vehicle->loudness = VEH_DEFAULT_LOUDNESS; //radius for sound alerts + vehicle->explosionRadius = VEH_DEFAULT_EXP_RAD; + vehicle->explosionDamage = VEH_DEFAULT_EXP_DMG; + vehicle->maxPassengers = 0; + + //new stuff + vehicle->hideRider = qfalse; // rider (and passengers?) should not be drawn + vehicle->killRiderOnDeath = qfalse; //if rider is on vehicle when it dies, they should die + vehicle->flammable = qfalse; //whether or not the vehicle should catch on fire before it explodes + vehicle->explosionDelay = 0; //how long the vehicle should be on fire/dying before it explodes + //camera stuff + vehicle->cameraOverride = qfalse; //whether or not to use all of the following 3rd person camera override values + vehicle->cameraRange = 0.0f; //how far back the camera should be - normal is 80 + vehicle->cameraVertOffset = 0.0f; //how high over the vehicle origin the camera should be - normal is 16 + vehicle->cameraHorzOffset = 0.0f; //how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + vehicle->cameraPitchOffset = 0.0f; //a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + vehicle->cameraFOV = 0.0f; //third person camera FOV, default is 80 + vehicle->cameraAlpha = qfalse; //fade out the vehicle if it's in the way of the crosshair +*/ +} + +void BG_VehicleClampData( vehicleInfo_t *vehicle ) +{//sanity check and clamp the vehicle's data + int i; + + for ( i = 0; i < 3; i++ ) + { + if ( vehicle->centerOfGravity[i] > 1.0f ) + { + vehicle->centerOfGravity[i] = 1.0f; + } + else if ( vehicle->centerOfGravity[i] < -1.0f ) + { + vehicle->centerOfGravity[i] = -1.0f; + } + } + + // Validate passenger max. + if ( vehicle->maxPassengers > VEH_MAX_PASSENGERS ) + { + vehicle->maxPassengers = VEH_MAX_PASSENGERS; + } + else if ( vehicle->maxPassengers < 0 ) + { + vehicle->maxPassengers = 0; + } +} + +static qboolean BG_ParseVehicleParm( vehicleInfo_t *vehicle, char *parmName, char *pValue ) +{ + int i; + vec3_t vec; + byte *b = (byte *)vehicle; + int _iFieldsRead = 0; + vehicleType_t vehType; + char value[1024]; + + Q_strncpyz( value, pValue, sizeof(value) ); + + // Loop through possible parameters + for ( i = 0; vehicleFields[i].ofs != -1; i++ ) + { + if ( !Q_stricmp( vehicleFields[i].name, parmName ) ) + { + // found it + switch( vehicleFields[i].type ) + { + case VF_IGNORE: + break; + case VF_INT: + *(int *)(b+vehicleFields[i].ofs) = atoi(value); + break; + case VF_FLOAT: + *(float *)(b+vehicleFields[i].ofs) = atof(value); + break; + case VF_LSTRING: // string on disk, pointer in memory, TAG_LEVEL + if (!*(char **)(b+vehicleFields[i].ofs)) + { //just use 128 bytes in case we want to write over the string +#ifdef _JK2MP + *(char **)(b+vehicleFields[i].ofs) = (char *)BG_Alloc(128);//(char *)BG_Alloc(strlen(value)); + strcpy(*(char **)(b+vehicleFields[i].ofs), value); +#else + (*(char **)(b+vehicleFields[i].ofs)) = G_NewString( value ); +#endif + } + + break; + case VF_VECTOR: + _iFieldsRead = sscanf (value, "%f %f %f", &vec[0], &vec[1], &vec[2]); + assert(_iFieldsRead==3 ); + if (_iFieldsRead!=3) + { + Com_Printf (S_COLOR_YELLOW"BG_ParseVehicleParm: VEC3 sscanf() failed to read 3 floats ('angle' key bug?)\n"); + } + ((float *)(b+vehWeaponFields[i].ofs))[0] = vec[0]; + ((float *)(b+vehWeaponFields[i].ofs))[1] = vec[1]; + ((float *)(b+vehWeaponFields[i].ofs))[2] = vec[2]; + break; + case VF_BOOL: + *(qboolean *)(b+vehicleFields[i].ofs) = (qboolean)(atof(value)!=0); + break; + case VF_VEHTYPE: + vehType = (vehicleType_t)GetIDForString( VehicleTable, value ); + *(vehicleType_t *)(b+vehicleFields[i].ofs) = vehType; + break; + case VF_ANIM: + { + int anim = GetIDForString( animTable, value ); + *(int *)(b+vehicleFields[i].ofs) = anim; + } + break; + case VF_WEAPON: // take string, resolve into index into VehWeaponParms + *(int *)(b+vehicleFields[i].ofs) = VEH_VehWeaponIndexForName( value ); + break; + case VF_MODEL: // take the string, get the G_ModelIndex +#ifdef QAGAME + *(int *)(b+vehicleFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_MODEL_CLIENT: // (MP cgame only) take the string, get the G_ModelIndex +#ifndef _JK2MP + *(int *)(b+vehicleFields[i].ofs) = G_ModelIndex( value ); +#elif QAGAME + //*(int *)(b+vehicleFields[i].ofs) = G_ModelIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterModel( value ); +#endif + break; + case VF_EFFECT: // take the string, get the G_EffectIndex +#ifdef QAGAME + *(int *)(b+vehicleFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehicleFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_EFFECT_CLIENT: // (MP cgame only) take the string, get the G_EffectIndex +#ifndef _JK2MP + *(int *)(b+vehicleFields[i].ofs) = G_EffectIndex( value ); +#elif QAGAME + //*(int *)(b+vehicleFields[i].ofs) = G_EffectIndex( value ); +#elif CGAME + *(int *)(b+vehicleFields[i].ofs) = trap_FX_RegisterEffect( value ); +#endif + break; + case VF_SHADER: // (cgame only) take the string, call trap_R_RegisterShader +#ifdef WE_ARE_IN_THE_UI + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#elif CGAME + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterShader( value ); +#endif + break; + case VF_SHADER_NOMIP:// (cgame only) take the string, call trap_R_RegisterShaderNoMip +#ifndef QAGAME + *(int *)(b+vehicleFields[i].ofs) = trap_R_RegisterShaderNoMip( value ); +#endif + break; + case VF_SOUND: // take the string, get the G_SoundIndex +#ifdef QAGAME + *(int *)(b+vehicleFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + case VF_SOUND_CLIENT: // (MP cgame only) take the string, get the G_SoundIndex +#ifndef _JK2MP + *(int *)(b+vehicleFields[i].ofs) = G_SoundIndex( value ); +#elif QAGAME + //*(int *)(b+vehicleFields[i].ofs) = G_SoundIndex( value ); +#else + *(int *)(b+vehicleFields[i].ofs) = trap_S_RegisterSound( value ); +#endif + break; + default: + //Unknown type? + return qfalse; + break; + } + break; + } + } + if ( vehicleFields[i].ofs == -1 ) + { + return qfalse; + } + else + { + return qtrue; + } +} + +int VEH_LoadVehicle( const char *vehicleName ) +{//load up specified vehicle and save in array: g_vehicleInfo + const char *token; + //we'll assume that no parm name is longer than 128 + char parmName[128] = { 0 }; + char weap1[128] = { 0 }, weap2[128] = { 0 }; + char weapMuzzle1[128] = { 0 }; + char weapMuzzle2[128] = { 0 }; + char weapMuzzle3[128] = { 0 }; + char weapMuzzle4[128] = { 0 }; + char weapMuzzle5[128] = { 0 }; + char weapMuzzle6[128] = { 0 }; + char weapMuzzle7[128] = { 0 }; + char weapMuzzle8[128] = { 0 }; + char weapMuzzle9[128] = { 0 }; + char weapMuzzle10[128] = { 0 }; + char *value = NULL; + char *p = NULL; + vehicleInfo_t *vehicle = NULL; + + // Load the vehicle parms if no vehicles have been loaded yet. + if ( numVehicles == 0 ) + { + BG_VehicleLoadParms(); + } + + //try to parse data out + p = VehicleParms; + +#ifdef _JK2MP + COM_BeginParseSession("vehicles"); +#else + COM_BeginParseSession(); +#endif + + vehicle = &g_vehicleInfo[numVehicles]; + // look for the right vehicle + while ( p ) + { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + { + return VEHICLE_NONE; + } + + if ( !Q_stricmp( token, vehicleName ) ) + { + break; + } + + SkipBracedSection( &p ); + } + + if ( !p ) + { + return VEHICLE_NONE; + } + + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) + {//barf + return VEHICLE_NONE; + } + + if ( Q_stricmp( token, "{" ) != 0 ) + { + return VEHICLE_NONE; + } + + BG_VehicleSetDefaults( vehicle ); + // parse the vehicle info block + while ( 1 ) + { + SkipRestOfLine( &p ); + token = COM_ParseExt( &p, qtrue ); + if ( !token[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: unexpected EOF while parsing Vehicle '%s'\n", vehicleName ); + return VEHICLE_NONE; + } + + if ( !Q_stricmp( token, "}" ) ) + { + break; + } + Q_strncpyz( parmName, token, sizeof(parmName) ); + value = COM_ParseExt( &p, qtrue ); + if ( !value || !value[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Vehicle token '%s' has no value!\n", parmName ); + } + else if ( Q_stricmp( "weap1", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weap1, value, sizeof(weap1) ); + } + else if ( Q_stricmp( "weap2", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weap2, value, sizeof(weap2) ); + } + else if ( Q_stricmp( "weapMuzzle1", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle1, value, sizeof(weapMuzzle1) ); + } + else if ( Q_stricmp( "weapMuzzle2", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle2, value, sizeof(weapMuzzle2) ); + } + else if ( Q_stricmp( "weapMuzzle3", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle3, value, sizeof(weapMuzzle3) ); + } + else if ( Q_stricmp( "weapMuzzle4", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle4, value, sizeof(weapMuzzle4) ); + } + else if ( Q_stricmp( "weapMuzzle5", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle5, value, sizeof(weapMuzzle5) ); + } + else if ( Q_stricmp( "weapMuzzle6", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle6, value, sizeof(weapMuzzle6) ); + } + else if ( Q_stricmp( "weapMuzzle7", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle7, value, sizeof(weapMuzzle7) ); + } + else if ( Q_stricmp( "weapMuzzle8", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle8, value, sizeof(weapMuzzle8) ); + } + else if ( Q_stricmp( "weapMuzzle9", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle9, value, sizeof(weapMuzzle9) ); + } + else if ( Q_stricmp( "weapMuzzle10", parmName ) == 0 ) + {//hmm, store this off because we don't want to call another one of these text parsing routines while we're in the middle of one... + Q_strncpyz( weapMuzzle10, value, sizeof(weapMuzzle10) ); + } + else + { + if ( !BG_ParseVehicleParm( vehicle, parmName, value ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair '%s', '%s'!\n", parmName, value ); +#endif + } + } + } + //NOW: if we have any weapons, go ahead and load them + if ( weap1[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weap1", weap1 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weap1', '%s'!\n", weap1 ); +#endif + } + } + if ( weap2[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weap2", weap2 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weap2', '%s'!\n", weap2 ); +#endif + } + } + if ( weapMuzzle1[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle1", weapMuzzle1 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle1', '%s'!\n", weapMuzzle1 ); +#endif + } + } + if ( weapMuzzle2[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle2", weapMuzzle2 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle2', '%s'!\n", weapMuzzle2 ); +#endif + } + } + if ( weapMuzzle3[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle3", weapMuzzle3 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle3', '%s'!\n", weapMuzzle3 ); +#endif + } + } + if ( weapMuzzle4[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle4", weapMuzzle4 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle4', '%s'!\n", weapMuzzle4 ); +#endif + } + } + if ( weapMuzzle5[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle5", weapMuzzle5 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle5', '%s'!\n", weapMuzzle5 ); +#endif + } + } + if ( weapMuzzle6[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle6", weapMuzzle6 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle6', '%s'!\n", weapMuzzle6 ); +#endif + } + } + if ( weapMuzzle7[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle7", weapMuzzle7 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle7', '%s'!\n", weapMuzzle7 ); +#endif + } + } + if ( weapMuzzle8[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle8", weapMuzzle8 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle8', '%s'!\n", weapMuzzle8 ); +#endif + } + } + if ( weapMuzzle9[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle9", weapMuzzle9 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle9', '%s'!\n", weapMuzzle9 ); +#endif + } + } + if ( weapMuzzle10[0] ) + { + if ( !BG_ParseVehicleParm( vehicle, "weapMuzzle10", weapMuzzle10 ) ) + { +#ifndef FINAL_BUILD + Com_Printf( S_COLOR_RED"ERROR: Unknown Vehicle key/value pair 'weapMuzzle10', '%s'!\n", weapMuzzle10 ); +#endif + } + } + +#ifdef _JK2MP + //let's give these guys some defaults + if (!vehicle->health_front) + { + vehicle->health_front = vehicle->armor/4; + } + if (!vehicle->health_back) + { + vehicle->health_back = vehicle->armor/4; + } + if (!vehicle->health_right) + { + vehicle->health_right = vehicle->armor/4; + } + if (!vehicle->health_left) + { + vehicle->health_left = vehicle->armor/4; + } +#endif + + if ( vehicle->model ) + { +#ifdef QAGAME + vehicle->modelIndex = G_ModelIndex( va( "models/players/%s/model.glm", vehicle->model ) ); +#else + vehicle->modelIndex = trap_R_RegisterModel( va( "models/players/%s/model.glm", vehicle->model ) ); +#endif + } + +#ifndef _JK2MP + //SP + if ( vehicle->skin + && vehicle->skin[0] ) + { + ratl::string_vs<256> skins(vehicle->skin); + for (ratl::string_vs<256>::tokenizer i = skins.begin("|"); i!=skins.end(); i++) + { + //this will just turn off surfs if there is a *off shader on a surf, the skin will actually get thrown away when cgame starts + gi.RE_RegisterSkin( va( "models/players/%s/model_%s.skin", vehicle->model, *i) ); + //this is for the server-side call, it will propgate down to cgame with configstrings and register it at the same time as all the other skins for ghoul2 models + G_SkinIndex( va( "models/players/%s/model_%s.skin", vehicle->model, *i) ); + } + } + else + { + //this will just turn off surfs if there is a *off shader on a surf, the skin will actually get thrown away when cgame starts + gi.RE_RegisterSkin( va( "models/players/%s/model_default.skin", vehicle->model) ); + //this is for the server-side call, it will propgate down to cgame with configstrings and register it at the same time as all the other skins for ghoul2 models + G_SkinIndex( va( "models/players/%s/model_default.skin", vehicle->model) ); + } +#else +#ifndef QAGAME + if ( vehicle->skin + && vehicle->skin[0] ) + { + trap_R_RegisterSkin( va( "models/players/%s/model_%s.skin", vehicle->model, vehicle->skin) ); + } +#endif +#endif + //sanity check and clamp the vehicle's data + BG_VehicleClampData( vehicle ); + // Setup the shared function pointers. + BG_SetSharedVehicleFunctions( vehicle ); + //misc effects... FIXME: not even used in MP, are they? + if ( vehicle->explosionDamage ) + { +#ifdef QAGAME + G_EffectIndex( "ships/ship_explosion_mark" ); +#elif CGAME + trap_FX_RegisterEffect( "ships/ship_explosion_mark" ); +#endif + } + if ( vehicle->flammable ) + { +#ifdef QAGAME + G_SoundIndex( "sound/vehicles/common/fire_lp.wav" ); +#elif CGAME + trap_S_RegisterSound( "sound/vehicles/common/fire_lp.wav" ); +#else + trap_S_RegisterSound( "sound/vehicles/common/fire_lp.wav" ); +#endif + } + + if ( vehicle->hoverHeight > 0 ) + { +#ifndef _JK2MP + G_EffectIndex( "ships/swoop_dust" ); +#elif QAGAME + G_EffectIndex( "ships/swoop_dust" ); +#elif CGAME + trap_FX_RegisterEffect( "ships/swoop_dust" ); +#endif + } + +#ifdef QAGAME + G_EffectIndex( "volumetric/black_smoke" ); + G_EffectIndex( "ships/fire" ); + G_SoundIndex( "sound/vehicles/common/release.wav" ); +#elif CGAME + trap_R_RegisterShader( "gfx/menus/radar/bracket" ); + trap_R_RegisterShader( "gfx/menus/radar/lead" ); + trap_R_RegisterShaderNoMip( "gfx/menus/radar/asteroid" ); + trap_S_RegisterSound( "sound/vehicles/common/impactalarm.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/linkweaps.wav" ); + trap_S_RegisterSound( "sound/vehicles/common/release.wav" ); + trap_FX_RegisterEffect("effects/ships/dest_burning.efx"); + trap_FX_RegisterEffect("effects/ships/dest_destroyed.efx"); + trap_FX_RegisterEffect( "volumetric/black_smoke" ); + trap_FX_RegisterEffect( "ships/fire" ); + trap_FX_RegisterEffect("ships/hyperspace_stars"); + + if ( vehicle->hideRider ) + { + trap_R_RegisterShaderNoMip( "gfx/menus/radar/circle_base" ); + trap_R_RegisterShaderNoMip( "gfx/menus/radar/circle_base_frame" ); + trap_R_RegisterShaderNoMip( "gfx/menus/radar/circle_base_shield" ); + } +#endif + + return (numVehicles++); +} + +int VEH_VehicleIndexForName( const char *vehicleName ) +{ + int v; + if ( !vehicleName || !vehicleName[0] ) + { + Com_Printf( S_COLOR_RED"ERROR: Trying to read Vehicle with no name!\n" ); + return VEHICLE_NONE; + } + for ( v = VEHICLE_BASE; v < numVehicles; v++ ) + { + if ( g_vehicleInfo[v].name + && Q_stricmp( g_vehicleInfo[v].name, vehicleName ) == 0 ) + {//already loaded this one + return v; + } + } + //haven't loaded it yet + if ( v >= MAX_VEHICLES ) + {//no more room! + Com_Printf( S_COLOR_RED"ERROR: Too many Vehicles (max 64), aborting load on %s!\n", vehicleName ); + return VEHICLE_NONE; + } + //we have room for another one, load it up and return the index + //HMM... should we not even load the .veh file until we want to? + v = VEH_LoadVehicle( vehicleName ); + if ( v == VEHICLE_NONE ) + { + Com_Printf( S_COLOR_RED"ERROR: Could not find Vehicle %s!\n", vehicleName ); + } + return v; +} + +void BG_VehWeaponLoadParms( void ) +{ + int len, totallen, vehExtFNLen, mainBlockLen, fileCnt, i; + char *holdChar, *marker; + char vehWeaponExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + char *tempReadBuffer; + + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = VehWeaponParms+totallen; + *marker = 0; + + //now load in the extra .veh extensions +#ifdef _JK2MP + fileCnt = trap_FS_GetFileList("ext_data/vehicles/weapons", ".vwp", vehWeaponExtensionListBuf, sizeof(vehWeaponExtensionListBuf) ); +#else + fileCnt = gi.FS_GetFileList("ext_data/vehicles/weapons", ".vwp", vehWeaponExtensionListBuf, sizeof(vehWeaponExtensionListBuf) ); +#endif + + holdChar = vehWeaponExtensionListBuf; + +#ifdef _JK2MP + tempReadBuffer = (char *)BG_TempAlloc(MAX_VEH_WEAPON_DATA_SIZE); +#else + tempReadBuffer = (char *)gi.Malloc( MAX_VEH_WEAPON_DATA_SIZE, TAG_G_ALLOC, qtrue ); +#endif + + // NOTE: Not use TempAlloc anymore... + //Make ABSOLUTELY CERTAIN that BG_Alloc/etc. is not used before + //the subsequent BG_TempFree or the pool will be screwed. + + for ( i = 0; i < fileCnt; i++, holdChar += vehExtFNLen + 1 ) + { + vehExtFNLen = strlen( holdChar ); + +// Com_Printf( "Parsing %s\n", holdChar ); + +#ifdef _JK2MP + len = trap_FS_FOpenFile(va( "ext_data/vehicles/weapons/%s", holdChar), &f, FS_READ); +#else +// len = gi.FS_ReadFile( va( "ext_data/vehicles/weapons/%s", holdChar), (void **) &buffer ); + len = gi.FS_FOpenFile(va( "ext_data/vehicles/weapons/%s", holdChar), &f, FS_READ); +#endif + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { +#ifdef _JK2MP + trap_FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#else + gi.FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#endif + + // Don't let it end on a } because that should be a stand-alone token. + if ( totallen && *(marker-1) == '}' ) + { + strcat( marker, " " ); + totallen++; + marker++; + } + + if ( totallen + len >= MAX_VEH_WEAPON_DATA_SIZE ) { + Com_Error(ERR_DROP, "Vehicle Weapon extensions (*.vwp) are too large" ); + } + strcat( marker, tempReadBuffer ); +#ifdef _JK2MP + trap_FS_FCloseFile( f ); +#else + gi.FS_FCloseFile( f ); +#endif + + totallen += len; + marker = VehWeaponParms+totallen; + } + } + +#ifdef _JK2MP + BG_TempFree(MAX_VEH_WEAPON_DATA_SIZE); +#else + gi.Free(tempReadBuffer); tempReadBuffer = NULL; +#endif +} + +void BG_VehicleLoadParms( void ) +{//HMM... only do this if there's a vehicle on the level? + int len, totallen, vehExtFNLen, mainBlockLen, fileCnt, i; +// const char *filename = "ext_data/vehicles.dat"; + char *holdChar, *marker; + char vehExtensionListBuf[2048]; // The list of file names read in + fileHandle_t f; + char *tempReadBuffer; + + len = 0; + + //remember where to store the next one + totallen = mainBlockLen = len; + marker = VehicleParms+totallen; + *marker = 0; + + //now load in the extra .veh extensions +#ifdef _JK2MP + fileCnt = trap_FS_GetFileList("ext_data/vehicles", ".veh", vehExtensionListBuf, sizeof(vehExtensionListBuf) ); +#else + fileCnt = gi.FS_GetFileList("ext_data/vehicles", ".veh", vehExtensionListBuf, sizeof(vehExtensionListBuf) ); +#endif + + holdChar = vehExtensionListBuf; + +#ifdef _JK2MP + tempReadBuffer = (char *)BG_TempAlloc(MAX_VEHICLE_DATA_SIZE); +#else + tempReadBuffer = (char *)gi.Malloc( MAX_VEHICLE_DATA_SIZE, TAG_G_ALLOC, qtrue ); +#endif + + // NOTE: Not use TempAlloc anymore... + //Make ABSOLUTELY CERTAIN that BG_Alloc/etc. is not used before + //the subsequent BG_TempFree or the pool will be screwed. + + for ( i = 0; i < fileCnt; i++, holdChar += vehExtFNLen + 1 ) + { + vehExtFNLen = strlen( holdChar ); + +// Com_Printf( "Parsing %s\n", holdChar ); + +#ifdef _JK2MP + len = trap_FS_FOpenFile(va( "ext_data/vehicles/%s", holdChar), &f, FS_READ); +#else +// len = gi.FS_ReadFile( va( "ext_data/vehicles/%s", holdChar), (void **) &buffer ); + len = gi.FS_FOpenFile(va( "ext_data/vehicles/%s", holdChar), &f, FS_READ); +#endif + + if ( len == -1 ) + { + Com_Printf( "error reading file\n" ); + } + else + { +#ifdef _JK2MP + trap_FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#else + gi.FS_Read(tempReadBuffer, len, f); + tempReadBuffer[len] = 0; +#endif + + // Don't let it end on a } because that should be a stand-alone token. + if ( totallen && *(marker-1) == '}' ) + { + strcat( marker, " " ); + totallen++; + marker++; + } + + if ( totallen + len >= MAX_VEHICLE_DATA_SIZE ) { + Com_Error(ERR_DROP, "Vehicle extensions (*.veh) are too large" ); + } + strcat( marker, tempReadBuffer ); +#ifdef _JK2MP + trap_FS_FCloseFile( f ); +#else + gi.FS_FCloseFile( f ); +#endif + + totallen += len; + marker = VehicleParms+totallen; + } + } + +#ifdef _JK2MP + BG_TempFree(MAX_VEHICLE_DATA_SIZE); +#else + gi.Free(tempReadBuffer); tempReadBuffer = NULL; +#endif + + numVehicles = 1;//first one is null/default + //set the first vehicle to default data + BG_VehicleSetDefaults( &g_vehicleInfo[VEHICLE_BASE] ); + //sanity check and clamp the vehicle's data + BG_VehicleClampData( &g_vehicleInfo[VEHICLE_BASE] ); + // Setup the shared function pointers. + BG_SetSharedVehicleFunctions( &g_vehicleInfo[VEHICLE_BASE] ); + + //Load the Vehicle Weapons data, too + BG_VehWeaponLoadParms(); +} + +int BG_VehicleGetIndex( const char *vehicleName ) +{ + return (VEH_VehicleIndexForName( vehicleName )); +} + +//We get the vehicle name passed in as modelname +//with a $ in front of it. +//we are expected to then get the model for the +//vehicle and stomp over modelname with it. +void BG_GetVehicleModelName(char *modelname) +{ + char *vehName = &modelname[1]; + int vIndex = BG_VehicleGetIndex(vehName); + assert(modelname[0] == '$'); + + if (vIndex == VEHICLE_NONE) + { + Com_Error(ERR_DROP, "BG_GetVehicleModelName: couldn't find vehicle %s", vehName); + } + + strcpy(modelname, g_vehicleInfo[vIndex].model); +} + +void BG_GetVehicleSkinName(char *skinname) +{ + char *vehName = &skinname[1]; + int vIndex = BG_VehicleGetIndex(vehName); + assert(skinname[0] == '$'); + + if (vIndex == VEHICLE_NONE) + { + Com_Error(ERR_DROP, "BG_GetVehicleSkinName: couldn't find vehicle %s", vehName); + } + + if ( !g_vehicleInfo[vIndex].skin + || !g_vehicleInfo[vIndex].skin[0] ) + { + skinname[0] = 0; + } + else + { + strcpy(skinname, g_vehicleInfo[vIndex].skin); + } +} + +#ifdef _JK2MP +#ifndef WE_ARE_IN_THE_UI +//so cgame can assign the function pointer for the vehicle attachment without having to +//bother with all the other funcs that don't really exist cgame-side. +extern int BG_GetTime(void); +extern int trap_G2API_AddBolt(void *ghoul2, int modelIndex, const char *boneName); +extern qboolean trap_G2API_GetBoltMatrix(void *ghoul2, const int modelIndex, const int boltIndex, mdxaBone_t *matrix, + const vec3_t angles, const vec3_t position, const int frameNum, qhandle_t *modelList, vec3_t scale); +void AttachRidersGeneric( Vehicle_t *pVeh ) +{ + // If we have a pilot, attach him to the driver tag. + if ( pVeh->m_pPilot ) + { + mdxaBone_t boltMatrix; + vec3_t yawOnlyAngles; + bgEntity_t *parent = pVeh->m_pParentEntity; + bgEntity_t *pilot = pVeh->m_pPilot; + int crotchBolt = trap_G2API_AddBolt(parent->ghoul2, 0, "*driver"); + + assert(parent->playerState); + + VectorSet(yawOnlyAngles, 0, parent->playerState->viewangles[YAW], 0); + + // Get the driver tag. + trap_G2API_GetBoltMatrix( parent->ghoul2, 0, crotchBolt, &boltMatrix, + yawOnlyAngles, parent->playerState->origin, + BG_GetTime(), NULL, parent->modelScale ); + BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, pilot->playerState->origin ); + } +} +#endif + +//#include "../namespace_end.h" + +#endif // _JK2MP + diff --git a/code/game/bg_vehicles.h b/code/game/bg_vehicles.h new file mode 100644 index 0000000..4d2bdaf --- /dev/null +++ b/code/game/bg_vehicles.h @@ -0,0 +1,629 @@ +#ifndef __BG_VEHICLES_H +#define __BG_VEHICLES_H + +#include "q_shared.h" + +typedef struct Vehicle_s Vehicle_t; +typedef struct bgEntity_s bgEntity_t; + +typedef enum +{ + VH_NONE = 0, //0 just in case anyone confuses VH_NONE and VEHICLE_NONE below + VH_WALKER, //something you ride inside of, it walks like you, like an AT-ST + VH_FIGHTER, //something you fly inside of, like an X-Wing or TIE fighter + VH_SPEEDER, //something you ride on that hovers, like a speeder or swoop + VH_ANIMAL, //animal you ride on top of that walks, like a tauntaun + VH_FLIER, //animal you ride on top of that flies, like a giant mynoc? + VH_NUM_VEHICLES +} vehicleType_t; + +typedef enum +{ + WPOSE_NONE = 0, + WPOSE_BLASTER, + WPOSE_SABERLEFT, + WPOSE_SABERRIGHT, +} EWeaponPose; + +//#include "../namespace_begin.h" +extern stringID_table_t VehicleTable[VH_NUM_VEHICLES+1]; +//#include "../namespace_end.h" + +//=========================================================================================================== +//START VEHICLE WEAPONS +//=========================================================================================================== +typedef struct +{ +//*** IMPORTANT!!! *** the number of variables in the vehWeaponStats_t struct (including all elements of arrays) must be reflected by NUM_VWEAP_PARMS!!! +//*** IMPORTANT!!! *** vWeapFields table correponds to this structure! + char *name; + qboolean bIsProjectile; //traceline or entity? + qboolean bHasGravity; //if a projectile, drops + qboolean bIonWeapon;//disables ship shields and sends them out of control + qboolean bSaberBlockable;//lightsabers can deflect this projectile + int iMuzzleFX; //index of Muzzle Effect + int iModel; //handle to the model used by this projectile + int iShotFX; //index of Shot Effect + int iImpactFX; //index of Impact Effect + int iG2MarkShaderHandle; //index of shader to use for G2 marks made on other models when hit by this projectile + float fG2MarkSize;//size (diameter) of the ghoul2 mark + int iLoopSound; //index of loopSound + float fSpeed; //speed of projectile/range of traceline + float fHoming; //0.0 = not homing, 0.5 = half vel to targ, half cur vel, 1.0 = all vel to targ + float fHomingFOV; //missile will lose lock on if DotProduct of missile direction and direction to target ever drops below this (-1 to 1, -1 = never lose target, 0 = lose if ship gets behind missile, 1 = pretty much will lose it's target right away) + int iLockOnTime; //0 = no lock time needed, else # of ms needed to lock on + int iDamage; //damage done when traceline or projectile directly hits target + int iSplashDamage;//damage done to ents in splashRadius of end of traceline or projectile origin on impact + float fSplashRadius;//radius that ent must be in to take splashDamage (linear fall-off) + int iAmmoPerShot; //how much "ammo" each shot takes + int iHealth; //if non-zero, projectile can be shot, takes this much damage before being destroyed + float fWidth; //width of traceline or bounding box of projecile (non-rotating!) + float fHeight; //height of traceline or bounding box of projecile (non-rotating!) + int iLifeTime; //removes itself after this amount of time + qboolean bExplodeOnExpire; //when iLifeTime is up, explodes rather than simply removing itself +} vehWeaponInfo_t; +//NOTE: this MUST stay up to date with the number of variables in the vehFields table!!! +#define NUM_VWEAP_PARMS 25 + +#define VWFOFS(x) ((int)&(((vehWeaponInfo_t *)0)->x)) + +#define MAX_VEH_WEAPONS 16 //sigh... no more than 16 different vehicle weapons +#define VEH_WEAPON_BASE 0 +#define VEH_WEAPON_NONE -1 + +//#include "../namespace_begin.h" +extern vehWeaponInfo_t g_vehWeaponInfo[MAX_VEH_WEAPONS]; +extern int numVehicleWeapons; +//#include "../namespace_end.h" + +//=========================================================================================================== +//END VEHICLE WEAPONS +//=========================================================================================================== + +#define MAX_VEHICLE_MUZZLES 12 +#define MAX_VEHICLE_EXHAUSTS 12 +#define MAX_VEHICLE_WEAPONS 2 +#define MAX_VEHICLE_TURRETS 2 +#define MAX_VEHICLE_TURRET_MUZZLES 2 + +typedef struct +{ + int iWeapon; //what vehWeaponInfo index to use + int iDelay; //delay between turret muzzle shots + int iAmmoMax; //how much ammo it has + int iAmmoRechargeMS; //how many MS between every point of recharged ammo + char *yawBone; //bone on ship that this turret uses to yaw + char *pitchBone; //bone on ship that this turret uses to pitch + int yawAxis; //axis on yawBone to which we should to apply the yaw angles + int pitchAxis; //axis on pitchBone to which we should to apply the pitch angles + float yawClampLeft; //how far the turret is allowed to turn left + float yawClampRight; //how far the turret is allowed to turn right + float pitchClampUp; //how far the turret is allowed to title up + float pitchClampDown; //how far the turret is allowed to tilt down + int iMuzzle[MAX_VEHICLE_TURRET_MUZZLES];//iMuzzle-1 = index of ship's muzzle to fire this turret's 1st and 2nd shots from + char *gunnerViewTag;//Where to put the view origin of the gunner (name) + float fTurnSpeed; //how quickly the turret can turn + qboolean bAI; //whether or not the turret auto-targets enemies when it's not manned + qboolean bAILead;//whether + float fAIRange; //how far away the AI will look for enemies + int passengerNum;//which passenger, if any, has control of this turret (overrides AI) +} turretStats_t; + +typedef struct +{ +//*** IMPORTANT!!! *** See note at top of next structure!!! *** + // Weapon stuff. + int ID;//index into the weapon data + // The delay between shots for each weapon. + int delay; + // Whether or not all the muzzles for each weapon can be linked together (linked delay = weapon delay * number of muzzles linked!) + int linkable; + // Whether or not to auto-aim the projectiles/tracelines at the thing under the crosshair when we fire + qboolean aimCorrect; + //maximum ammo + int ammoMax; + //ammo recharge rate - milliseconds per unit (minimum of 100, which is 10 ammo per second) + int ammoRechargeMS; + //sound to play when out of ammo (plays default "no ammo" sound if none specified) + int soundNoAmmo; +} vehWeaponStats_t; + +typedef struct +{ +//*** IMPORTANT!!! *** vehFields table correponds to this structure! + char *name; //unique name of the vehicle + + //general data + vehicleType_t type; //what kind of vehicle + int numHands; //if 2 hands, no weapons, if 1 hand, can use 1-handed weapons, if 0 hands, can use 2-handed weapons + float lookPitch; //How far you can look up and down off the forward of the vehicle + float lookYaw; //How far you can look left and right off the forward of the vehicle + float length; //how long it is - used for body length traces when turning/moving? + float width; //how wide it is - used for body length traces when turning/moving? + float height; //how tall it is - used for body length traces when turning/moving? + vec3_t centerOfGravity;//offset from origin: {forward, right, up} as a modifier on that dimension (-1.0f is all the way back, 1.0f is all the way forward) + + //speed stats + float speedMax; //top speed + float turboSpeed; //turbo speed + float speedMin; //if < 0, can go in reverse + float speedIdle; //what speed it drifts to when no accel/decel input is given + float accelIdle; //if speedIdle > 0, how quickly it goes up to that speed + float acceleration; //when pressing on accelerator + float decelIdle; //when giving no input, how quickly it drops to speedIdle + float throttleSticks; //if true, speed stays at whatever you accel/decel to, unless you turbo or brake + float strafePerc; //multiplier on current speed for strafing. If 1.0f, you can strafe at the same speed as you're going forward, 0.5 is half, 0 is no strafing + + //handling stats + float bankingSpeed; //how quickly it pitches and rolls (not under player control) + float rollLimit; //how far it can roll to either side + float pitchLimit; //how far it can roll forward or backward + float braking; //when pressing on decelerator + float mouseYaw; // The mouse yaw override. + float mousePitch; // The mouse pitch override. + float turningSpeed; //how quickly you can turn + qboolean turnWhenStopped;//whether or not you can turn when not moving + float traction; //how much your command input affects velocity + float friction; //how much velocity is cut on its own + float maxSlope; //the max slope that it can go up with control + qboolean speedDependantTurning;//vehicle turns faster the faster it's going + + //durability stats + int mass; //for momentum and impact force (player mass is 10) + int armor; //total points of damage it can take + int shields; //energy shield damage points + int shieldRechargeMS;//energy shield milliseconds per point recharged + float toughness; //modifies incoming damage, 1.0 is normal, 0.5 is half, etc. Simulates being made of tougher materials/construction + int malfunctionArmorLevel;//when armor drops to or below this point, start malfunctioning + int surfDestruction; //can parts of this thing be torn off on impact? -rww + + //individual "area" health -rww + int health_front; + int health_back; + int health_right; + int health_left; + + //visuals & sounds + char *model; //what model to use - if make it an NPC's primary model, don't need this? + char *skin; //what skin to use - if make it an NPC's primary model, don't need this? + int g2radius; //render radius for the ghoul2 model + int riderAnim; //what animation the rider uses + int radarIconHandle;//what icon to show on radar in MP + int dmgIndicFrameHandle;//what image to use for the frame of the damage indicator + int dmgIndicShieldHandle;//what image to use for the shield of the damage indicator + int dmgIndicBackgroundHandle;//what image to use for the background of the damage indicator + int iconFrontHandle;//what image to use for the front of the ship on the damage indicator + int iconBackHandle; //what image to use for the back of the ship on the damage indicator + int iconRightHandle;//what image to use for the right of the ship on the damage indicator + int iconLeftHandle; //what image to use for the left of the ship on the damage indicator + int crosshairShaderHandle;//what image to use for the left of the ship on the damage indicator + int shieldShaderHandle;//What shader to use when drawing the shield shell + char *droidNPC; //NPC to attach to *droidunit tag (if it exists in the model) + + int soundOn; //sound to play when get on it + int soundTakeOff; //sound to play when ship takes off + int soundEngineStart;//sound to play when ship's thrusters first activate + int soundLoop; //sound to loop while riding it + int soundSpin; //sound to loop while spiraling out of control + int soundTurbo; //sound to play when turbo/afterburner kicks in + int soundHyper; //sound to play when ship lands + int soundLand; //sound to play when ship lands + int soundOff; //sound to play when get off + int soundFlyBy; //sound to play when they buzz you + int soundFlyBy2; //alternate sound to play when they buzz you + int soundShift1; //sound to play when accelerating + int soundShift2; //sound to play when accelerating + int soundShift3; //sound to play when decelerating + int soundShift4; //sound to play when decelerating + + int iExhaustFX; //exhaust effect, played from "*exhaust" bolt(s) + int iTurboFX; //turbo exhaust effect, played from "*exhaust" bolt(s) when ship is in "turbo" mode + int iTurboStartFX; //turbo begin effect, played from "*exhaust" bolts when "turbo" mode begins + int iTrailFX; //trail effect, played from "*trail" bolt(s) + int iImpactFX; //impact effect, for when it bumps into something + int iExplodeFX; //explosion effect, for when it blows up (should have the sound built into explosion effect) + int iWakeFX; //effect it makes when going across water + int iDmgFX; //effect to play on damage from a weapon or something + int iInjureFX; + int iNoseFX; //effect for nose piece flying away when blown off + int iLWingFX; //effect for left wing piece flying away when blown off + int iRWingFX; //effect for right wing piece flying away when blown off + + //Weapon stats + vehWeaponStats_t weapon[MAX_VEHICLE_WEAPONS]; + + // Which weapon a muzzle fires (has to match one of the weapons this vehicle has). So 1 would be weapon 1, + // 2 would be weapon 2 and so on. + int weapMuzzle[MAX_VEHICLE_MUZZLES]; + + //turrets (if any) on the vehicle + turretStats_t turret[MAX_VEHICLE_TURRETS]; + + // The max height before this ship (?) starts (auto)landing. + float landingHeight; + + //other misc stats + int gravity; //normal is 800 + float hoverHeight; //if 0, it's a ground vehicle + float hoverStrength; //how hard it pushes off ground when less than hover height... causes "bounce", like shocks + qboolean waterProof; //can drive underwater if it has to + float bouyancy; //when in water, how high it floats (1 is neutral bouyancy) + int fuelMax; //how much fuel it can hold (capacity) + int fuelRate; //how quickly is uses up fuel + int turboDuration; //how long turbo lasts + int turboRecharge; //how long turbo takes to recharge + int visibility; //for sight alerts + int loudness; //for sound alerts + float explosionRadius;//range of explosion + int explosionDamage;//damage of explosion + + int maxPassengers; // The max number of passengers this vehicle may have (Default = 0). + qboolean hideRider; // rider (and passengers?) should not be drawn + qboolean killRiderOnDeath;//if rider is on vehicle when it dies, they should die + qboolean flammable; //whether or not the vehicle should catch on fire before it explodes + int explosionDelay; //how long the vehicle should be on fire/dying before it explodes + //camera stuff + qboolean cameraOverride; //whether or not to use all of the following 3rd person camera override values + float cameraRange; //how far back the camera should be - normal is 80 + float cameraVertOffset;//how high over the vehicle origin the camera should be - normal is 16 + float cameraHorzOffset;//how far to left/right (negative/positive) of of the vehicle origin the camera should be - normal is 0 + float cameraPitchOffset;//a modifier on the camera's pitch (up/down angle) to the vehicle - normal is 0 + float cameraFOV; //third person camera FOV, default is 80 + float cameraAlpha; //fade out the vehicle to this alpha (0.1-1.0f) if it's in the way of the crosshair + qboolean cameraPitchDependantVertOffset;//use the hacky AT-ST pitch dependant vertical offset + + //NOTE: some info on what vehicle weapon to use? Like ATST or TIE bomber or TIE fighter or X-Wing...? + +//===VEH_PARM_MAX======================================================================== +//*** IMPORTANT!!! *** vehFields table correponds to this structure! + +//THE FOLLOWING FIELDS are not in the vehFields table because they are internal variables, not read in from the .veh file + int modelIndex; //set internally, not until this vehicle is spawned into the level + + // NOTE: Please note that most of this stuff has been converted from C++ classes to generic C. + // This part of the structure is used to simulate inheritance for vehicles. The basic idea is that all vehicle use + // this vehicle interface since they declare their own functions and assign the function pointer to the + // corresponding function. Meanwhile, the base logic can still call the appropriate functions. In C++ talk all + // of these functions (pointers) are pure virtuals and this is an abstract base class (although it cannot be + // inherited from, only contained and reimplemented (through an object and a setup function respectively)). -AReis + + // Makes sure that the vehicle is properly animated. + void (*AnimateVehicle)( Vehicle_t *pVeh ); + + // Makes sure that the rider's in this vehicle are properly animated. + void (*AnimateRiders)( Vehicle_t *pVeh ); + + // Determine whether this entity is able to board this vehicle or not. + qboolean (*ValidateBoard)( Vehicle_t *pVeh, bgEntity_t *pEnt ); + + // Set the parent entity of this Vehicle NPC. + void (*SetParent)( Vehicle_t *pVeh, bgEntity_t *pParentEntity ); + + // Add a pilot to the vehicle. + void (*SetPilot)( Vehicle_t *pVeh, bgEntity_t *pPilot ); + + // Add a passenger to the vehicle (false if we're full). + qboolean (*AddPassenger)( Vehicle_t *pVeh ); + + // Animate the vehicle and it's riders. + void (*Animate)( Vehicle_t *pVeh ); + + // Board this Vehicle (get on). The first entity to board an empty vehicle becomes the Pilot. + qboolean (*Board)( Vehicle_t *pVeh, bgEntity_t *pEnt ); + + // Eject an entity from the vehicle. + qboolean (*Eject)( Vehicle_t *pVeh, bgEntity_t *pEnt, qboolean forceEject ); + + // Eject all the inhabitants of this vehicle. + qboolean (*EjectAll)( Vehicle_t *pVeh ); + + // Start a delay until the vehicle dies. + void (*StartDeathDelay)( Vehicle_t *pVeh, int iDelayTime ); + + // Update death sequence. + void (*DeathUpdate)( Vehicle_t *pVeh ); + + // Register all the assets used by this vehicle. + void (*RegisterAssets)( Vehicle_t *pVeh ); + + // Initialize the vehicle (should be called by Spawn?). + qboolean (*Initialize)( Vehicle_t *pVeh ); + + // Like a think or move command, this updates various vehicle properties. + qboolean (*Update)( Vehicle_t *pVeh, const usercmd_t *pUcmd ); + + // Update the properties of a Rider (that may reflect what happens to the vehicle). + // + // [return] bool True if still in vehicle, false if otherwise. + qboolean (*UpdateRider)( Vehicle_t *pVeh, bgEntity_t *pRider, usercmd_t *pUcmd ); + + // ProcessMoveCommands the Vehicle. + void (*ProcessMoveCommands)( Vehicle_t *pVeh ); + + // ProcessOrientCommands the Vehicle. + void (*ProcessOrientCommands)( Vehicle_t *pVeh ); + + // Attachs all the riders of this vehicle to their appropriate position/tag (*driver, *pass1, *pass2, whatever...). + void (*AttachRiders)( Vehicle_t *pVeh ); + + // Make someone invisible and un-collidable. + void (*Ghost)( Vehicle_t *pVeh, bgEntity_t *pEnt ); + + // Make someone visible and collidable. + void (*UnGhost)( Vehicle_t *pVeh, bgEntity_t *pEnt ); + + // Get the pilot of this vehicle. + const bgEntity_t *(*GetPilot)( Vehicle_t *pVeh ); + + // Whether this vehicle is currently inhabited (by anyone) or not. + qboolean (*Inhabited)( Vehicle_t *pVeh ); +} vehicleInfo_t; + + +#define VFOFS(x) ((int)&(((vehicleInfo_t *)0)->x)) + +#define MAX_VEHICLES 16 //sigh... no more than 64 individual vehicles +#define VEHICLE_BASE 0 +#define VEHICLE_NONE -1 + +//#include "../namespace_begin.h" +extern vehicleInfo_t g_vehicleInfo[MAX_VEHICLES]; +extern int numVehicles; +//#include "../namespace_end.h" + +#define VEH_DEFAULT_SPEED_MAX 800.0f +#define VEH_DEFAULT_ACCEL 10.0f +#define VEH_DEFAULT_DECEL 10.0f +#define VEH_DEFAULT_STRAFE_PERC 0.5f +#define VEH_DEFAULT_BANKING_SPEED 0.5f +#define VEH_DEFAULT_ROLL_LIMIT 60.0f +#define VEH_DEFAULT_PITCH_LIMIT 90.0f +#define VEH_DEFAULT_BRAKING 10.0f +#define VEH_DEFAULT_TURNING_SPEED 1.0f +#define VEH_DEFAULT_TRACTION 8.0f +#define VEH_DEFAULT_FRICTION 1.0f +#define VEH_DEFAULT_MAX_SLOPE 0.85f +#define VEH_DEFAULT_MASS 200 +#define VEH_DEFAULT_MAX_ARMOR 200 +#define VEH_DEFAULT_TOUGHNESS 2.5f +#define VEH_DEFAULT_GRAVITY 800 +#define VEH_DEFAULT_HOVER_HEIGHT 64.0f +#define VEH_DEFAULT_HOVER_STRENGTH 10.0f +#define VEH_DEFAULT_VISIBILITY 0 +#define VEH_DEFAULT_LOUDNESS 0 +#define VEH_DEFAULT_EXP_RAD 400.0f +#define VEH_DEFAULT_EXP_DMG 1000 +#define VEH_MAX_PASSENGERS 10 + +#define MAX_STRAFE_TIME 2000.0f//FIXME: extern? +#define MIN_LANDING_SPEED 200//equal to or less than this and close to ground = auto-slow-down to land +#define MIN_LANDING_SLOPE 0.8f//must be pretty flat to land on the surf + +#define VEH_MOUNT_THROW_LEFT -5 +#define VEH_MOUNT_THROW_RIGHT -6 + + +typedef enum +{ + VEH_EJECT_LEFT, + VEH_EJECT_RIGHT, + VEH_EJECT_FRONT, + VEH_EJECT_REAR, + VEH_EJECT_TOP, + VEH_EJECT_BOTTOM +}; + +// Vehicle flags. +typedef enum +{ + VEH_NONE = 0, VEH_FLYING = 0x00000001, VEH_CRASHING = 0x00000002, + VEH_LANDING = 0x00000004, VEH_BUCKING = 0x00000010, VEH_WINGSOPEN = 0x00000020, + VEH_GEARSOPEN = 0x00000040, VEH_SLIDEBREAKING = 0x00000080, VEH_SPINNING = 0x00000100, + VEH_OUTOFCONTROL = 0x00000200, + VEH_SABERINLEFTHAND = 0x00000400 +} vehFlags_t; + +//defines for impact damage surface stuff +#define SHIPSURF_FRONT 0 +#define SHIPSURF_BACK 1 +#define SHIPSURF_RIGHT 2 +#define SHIPSURF_LEFT 3 + +#define SHIPSURF_DAMAGE_FRONT_LIGHT 0 +#define SHIPSURF_DAMAGE_BACK_LIGHT 1 +#define SHIPSURF_DAMAGE_RIGHT_LIGHT 2 +#define SHIPSURF_DAMAGE_LEFT_LIGHT 3 +#define SHIPSURF_DAMAGE_FRONT_HEAVY 4 +#define SHIPSURF_DAMAGE_BACK_HEAVY 5 +#define SHIPSURF_DAMAGE_RIGHT_HEAVY 6 +#define SHIPSURF_DAMAGE_LEFT_HEAVY 7 + +//generic part bits +#define SHIPSURF_BROKEN_A (1<<0) //gear 1 +#define SHIPSURF_BROKEN_B (1<<1) //gear 1 +#define SHIPSURF_BROKEN_C (1<<2) //wing 1 +#define SHIPSURF_BROKEN_D (1<<3) //wing 2 +#define SHIPSURF_BROKEN_E (1<<4) //wing 3 +#define SHIPSURF_BROKEN_F (1<<5) //wing 4 +#define SHIPSURF_BROKEN_G (1<<6) //front + +typedef struct +{ + //linked firing mode + qboolean linked;//weapon 1's muzzles are in linked firing mode + //current weapon ammo + int ammo; + //debouncer for ammo recharge + int lastAmmoInc; + //which muzzle will fire next + int nextMuzzle; +} vehWeaponStatus_t; + +typedef struct +{ + //current weapon ammo + int ammo; + //debouncer for ammo recharge + int lastAmmoInc; + //which muzzle will fire next + int nextMuzzle; + //which entity they're after + int enemyEntNum; + //how long to hold on to our current enemy + int enemyHoldTime; +} vehTurretStatus_t; +// This is the implementation of the vehicle interface and any of the other variables needed. This +// is what actually represents a vehicle. -AReis. +typedef struct Vehicle_s +{ + // The entity who pilots/drives this vehicle. + // NOTE: This is redundant (since m_pParentEntity->owner _should_ be the pilot). This makes things clearer though. + bgEntity_t *m_pPilot; + + int m_iPilotTime; //if spawnflag to die without pilot and this < level.time then die. + int m_iPilotLastIndex; //index to last pilot + qboolean m_bHasHadPilot; //qtrue once the vehicle gets its first pilot + + // The passengers of this vehicle. + //bgEntity_t **m_ppPassengers; + bgEntity_t *m_ppPassengers[VEH_MAX_PASSENGERS]; + + //the droid unit NPC for this vehicle, if any + bgEntity_t *m_pDroidUnit; + + // The number of passengers currently in this vehicle. + int m_iNumPassengers; + + // The entity from which this NPC comes from. + bgEntity_t *m_pParentEntity; + + // If not zero, how long to wait before we can do anything with the vehicle (we're getting on still). + // -1 = board from left, -2 = board from right, -3 = jump/quick board. -4 & -5 = throw off existing pilot + int m_iBoarding; + + // Used to check if we've just started the boarding process + qboolean m_bWasBoarding; + + // The speed the vehicle maintains while boarding occurs (often zero) + vec3_t m_vBoardingVelocity; + + // Time modifier (must only be used in ProcessMoveCommands() and ProcessOrientCommands() and is updated in Update()). + float m_fTimeModifier; + + // Ghoul2 Animation info. + //int m_iDriverTag; + int m_iLeftExhaustTag; + int m_iRightExhaustTag; + int m_iGun1Tag; + int m_iGun1Bone; + int m_iLeftWingBone; + int m_iRightWingBone; + + int m_iExhaustTag[MAX_VEHICLE_EXHAUSTS]; + int m_iMuzzleTag[MAX_VEHICLE_MUZZLES]; + int m_iDroidUnitTag; + int m_iGunnerViewTag[MAX_VEHICLE_TURRETS];//Where to put the view origin of the gunner (index) + + //this stuff is a little bit different from SP, because I am lazy -rww + int m_iMuzzleTime[MAX_VEHICLE_MUZZLES]; + // These are updated every frame and represent the current position and direction for the specific muzzle. + vec3_t m_vMuzzlePos[MAX_VEHICLE_MUZZLES], m_vMuzzleDir[MAX_VEHICLE_MUZZLES]; + + // This is how long to wait before being able to fire a specific muzzle again. This is based on the firing rate + // so that a firing rate of 10 rounds/sec would make this value initially 100 miliseconds. + int m_iMuzzleWait[MAX_VEHICLE_MUZZLES]; + + // The user commands structure. + usercmd_t m_ucmd; + + // The direction an entity will eject from the vehicle towards. + int m_EjectDir; + + // Flags that describe the vehicles behavior. + unsigned long m_ulFlags; + + // NOTE: Vehicle Type ID, Orientation, and Armor MUST be transmitted over the net. + + // The ID of the type of vehicle this is. + int m_iVehicleTypeID; + + // Current angles of this vehicle. + //vec3_t m_vOrientation; + float *m_vOrientation; + //Yeah, since we use the SP code for vehicles, I want to use this value, but I'm going + //to make it a pointer to a vec3_t in the playerstate for prediction's sake. -rww + + // How long you have strafed left or right (increments every frame that you strafe to right, decrements every frame you strafe left) + int m_fStrafeTime; + + // Previous angles of this vehicle. + vec3_t m_vPrevOrientation; + + // Previous viewangles of the rider + vec3_t m_vPrevRiderViewAngles; + + // When control is lost on a speeder, current angular velocity is stored here and applied until landing + float m_vAngularVelocity; + + vec3_t m_vFullAngleVelocity; + + // Current armor and shields of your vehicle (explodes if armor to 0). + int m_iArmor; //hull strength - STAT_HEALTH on NPC + int m_iShields; //energy shielding - STAT_ARMOR on NPC + + //mp-specific + int m_iHitDebounce; + + // Timer for all cgame-FX...? ex: exhaust? + int m_iLastFXTime; + + // When to die. + int m_iDieTime; + + // This pointer is to a valid VehicleInfo (which could be an animal, speeder, fighter, whatever). This + // contains the functions actually used to do things to this specific kind of vehicle as well as shared + // information (max speed, type, etc...). + vehicleInfo_t *m_pVehicleInfo; + + // This trace tells us if we're within landing height. + trace_t m_LandTrace; + + // TEMP: The wing angles (used to animate it). + vec3_t m_vWingAngles; + + //amount of damage done last impact + int m_iLastImpactDmg; + + //bitflag of surfaces that have broken off + int m_iRemovedSurfaces; + + int m_iDmgEffectTime; + + // the last time this vehicle fired a turbo burst + int m_iTurboTime; + + //how long it should drop like a rock for after freed from SUSPEND + int m_iDropTime; + + int m_iSoundDebounceTimer; + + //last time we incremented the shields + int lastShieldInc; + + //so we don't hold it down and toggle it back and forth + qboolean linkWeaponToggleHeld; + + //info about our weapons (linked, ammo, etc.) + vehWeaponStatus_t weaponStatus[MAX_VEHICLE_WEAPONS]; + vehTurretStatus_t turretStatus[MAX_VEHICLE_TURRETS]; + + //the guy who was previously the pilot + bgEntity_t * m_pOldPilot; + +} Vehicle_t; + +//#include "../namespace_begin.h" +extern int BG_VehicleGetIndex( const char *vehicleName ); +//#include "../namespace_end.h" + +#endif // __BG_VEHICLES_H diff --git a/code/game/bg_weapons.c b/code/game/bg_weapons.c new file mode 100644 index 0000000..c155b81 --- /dev/null +++ b/code/game/bg_weapons.c @@ -0,0 +1,402 @@ +// Copyright (C) 2001-2002 Raven Software +// +// bg_weapons.c -- part of bg_pmove functionality + +#include "../qcommon/q_shared.h" +#include "../game/bg_public.h" +#include "../game/bg_local.h" + +// Muzzle point table... +vec3_t WP_MuzzlePoint[WP_NUM_WEAPONS] = +{// Fwd, right, up. + {0, 0, 0 }, // WP_NONE, + {0 , 8, 0 }, // WP_STUN_BATON, + {0 , 8, 0 }, // WP_MELEE, + {8 , 16, 0 }, // WP_SABER, + {12, 6, -6 }, // WP_BRYAR_PISTOL, + {12, 6, -6 }, // WP_BLASTER, + {12, 6, -6 }, // WP_DISRUPTOR, + {12, 2, -6 }, // WP_BOWCASTER, + {12, 4.5, -6 }, // WP_REPEATER, + {12, 6, -6 }, // WP_DEMP2, + {12, 6, -6 }, // WP_FLECHETTE, + {12, 8, -4 }, // WP_ROCKET_LAUNCHER, + {12, 0, -4 }, // WP_THERMAL, + {12, 0, -10 }, // WP_TRIP_MINE, + {12, 0, -4 }, // WP_DET_PACK, + {12, 6, -6 }, // WP_CONCUSSION + {12, 6, -6 }, // WP_BRYAR_OLD, +}; + +weaponData_t weaponData[WP_NUM_WEAPONS] = +{ + { // WP_NONE +// "No Weapon", // char classname[32]; // Spawning name + AMMO_NONE, // int ammoIndex; // Index to proper ammo slot + 0, // int ammoLow; // Count when ammo is low + 0, // int energyPerShot; // Amount of energy used per shot + 0, // int fireTime; // Amount of time between firings + 0, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 0, // int altFireTime; // Amount of time between alt-firings + 0, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_STUN_BATON +// "Stun Baton", // char classname[32]; // Spawning name + AMMO_NONE, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 0, // int energyPerShot; // Amount of energy used per shot + 400, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_MELEE +// "Melee", // char classname[32]; // Spawning name + AMMO_NONE, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 0, // int energyPerShot; // Amount of energy used per shot + 400, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_SABER, +// "Lightsaber", // char classname[32]; // Spawning name + AMMO_NONE, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 0, // int energyPerShot; // Amount of energy used per shot + 100, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 100, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_BRYAR_PISTOL, +// "Bryar Pistol", // char classname[32]; // Spawning name + AMMO_BLASTER, // int ammoIndex; // Index to proper ammo slot + 0,//15, // int ammoLow; // Count when ammo is low + 0,//2, // int energyPerShot; // Amount of energy used per shot + 800,//400, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0,//2, // int altEnergyPerShot; // Amount of energy used for alt-fire + 800,//400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0,//200, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0,//1, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0,//1500 // int altMaxCharge; // above for secondary + }, + { // WP_BLASTER +// "E11 Blaster Rifle", // char classname[32]; // Spawning name + AMMO_BLASTER, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 2, // int energyPerShot; // Amount of energy used per shot + 350, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 3, // int altEnergyPerShot; // Amount of energy used for alt-fire + 150, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_DISRUPTOR +// "Tenloss Disruptor Rifle",// char classname[32]; // Spawning name + AMMO_POWERCELL, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 5, // int energyPerShot; // Amount of energy used per shot + 600, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 6, // int altEnergyPerShot; // Amount of energy used for alt-fire + 1300, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 200, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 3, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 1700 // int altMaxCharge; // above for secondary + }, + { // WP_BOWCASTER +// "Wookiee Bowcaster", // char classname[32]; // Spawning name + AMMO_POWERCELL, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 5, // int energyPerShot; // Amount of energy used per shot + 1000, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 5, // int altEnergyPerShot; // Amount of energy used for alt-fire + 750, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 400, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 5, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 1700, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_REPEATER +// "Imperial Heavy Repeater",// char classname[32]; // Spawning name + AMMO_METAL_BOLTS, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 100, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 15, // int altEnergyPerShot; // Amount of energy used for alt-fire + 800, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_DEMP2 +// "DEMP2", // char classname[32]; // Spawning name + AMMO_POWERCELL, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 8, // int energyPerShot; // Amount of energy used per shot + 500, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 6, // int altEnergyPerShot; // Amount of energy used for alt-fire + 900, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 250, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 3, // int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 2100 // int altMaxCharge; // above for secondary + }, + { // WP_FLECHETTE +// "Golan Arms Flechette", // char classname[32]; // Spawning name + AMMO_METAL_BOLTS, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 10, // int energyPerShot; // Amount of energy used per shot + 700, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 15, // int altEnergyPerShot; // Amount of energy used for alt-fire + 800, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_ROCKET_LAUNCHER +// "Merr-Sonn Missile System", // char classname[32]; // Spawning name + AMMO_ROCKETS, // int ammoIndex; // Index to proper ammo slot + 5, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 900, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 2, // int altEnergyPerShot; // Amount of energy used for alt-fire + 1200, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_THERMAL +// "Thermal Detonator", // char classname[32]; // Spawning name + AMMO_THERMAL, // int ammoIndex; // Index to proper ammo slot + 0, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 800, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 1, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_TRIP_MINE +// "Trip Mine", // char classname[32]; // Spawning name + AMMO_TRIPMINE, // int ammoIndex; // Index to proper ammo slot + 0, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 800, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 1, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_DET_PACK +// "Det Pack", // char classname[32]; // Spawning name + AMMO_DETPACK, // int ammoIndex; // Index to proper ammo slot + 0, // int ammoLow; // Count when ammo is low + 1, // int energyPerShot; // Amount of energy used per shot + 800, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_CONCUSSION +// "Concussion Rifle", // char classname[32]; // Spawning name + AMMO_METAL_BOLTS, // int ammoIndex; // Index to proper ammo slot + 40, // int ammoLow; // Count when ammo is low + 40, // int energyPerShot; // Amount of energy used per shot + 800, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 50, // int altEnergyPerShot; // Amount of energy used for alt-fire + 1200, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, // int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_BRYAR_OLD, +// "Bryar Pistol", // char classname[32]; // Spawning name + AMMO_BLASTER, // int ammoIndex; // Index to proper ammo slot + 15, // int ammoLow; // Count when ammo is low + 2, // int energyPerShot; // Amount of energy used per shot + 400, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + 2, // int altEnergyPerShot; // Amount of energy used for alt-fire + 400, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 200, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 1, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 1500 // int altMaxCharge; // above for secondary + }, + { // WP_EMPLCACED_GUN +// "Emplaced Gun", // char classname[32]; // Spawning name + /*AMMO_BLASTER*/0, // int ammoIndex; // Index to proper ammo slot + /*5*/0, // int ammoLow; // Count when ammo is low + /*2*/0, // int energyPerShot; // Amount of energy used per shot + 100, // int fireTime; // Amount of time between firings + 8192, // int range; // Range of weapon + /*3*/0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 100, // int altFireTime; // Amount of time between alt-firings + 8192, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + }, + { // WP_TURRET - NOTE NOT ACTUALLY USEABLE BY PLAYER! +// "Emplaced Gun", // char classname[32]; // Spawning name + /*AMMO_BLASTER*/0, // int ammoIndex; // Index to proper ammo slot + /*5*/0, // int ammoLow; // Count when ammo is low + /*2*/0, // int energyPerShot; // Amount of energy used per shot + 0, // int fireTime; // Amount of time between firings + 0, // int range; // Range of weapon + /*3*/0, // int altEnergyPerShot; // Amount of energy used for alt-fire + 0, // int altFireTime; // Amount of time between alt-firings + 0, // int altRange; // Range of alt-fire + 0, // int chargeSubTime; // ms interval for subtracting ammo during charge + 0, // int altChargeSubTime; // above for secondary + 0, // int chargeSub; // amount to subtract during charge on each interval + 0, //int altChargeSub; // above for secondary + 0, // int maxCharge; // stop subtracting once charged for this many ms + 0 // int altMaxCharge; // above for secondary + } +}; + +ammoData_t ammoData[AMMO_MAX] = +{ + { // AMMO_NONE +// "", // char icon[32]; // Name of ammo icon file + 0 // int max; // Max amount player can hold of ammo + }, + { // AMMO_FORCE +// "", // char icon[32]; // Name of ammo icon file + 100 // int max; // Max amount player can hold of ammo + }, + { // AMMO_BLASTER +// "", // char icon[32]; // Name of ammo icon file + 300 // int max; // Max amount player can hold of ammo + }, + { // AMMO_POWERCELL +// "", // char icon[32]; // Name of ammo icon file + 300 // int max; // Max amount player can hold of ammo + }, + { // AMMO_METAL_BOLTS +// "", // char icon[32]; // Name of ammo icon file + 300 // int max; // Max amount player can hold of ammo + }, + { // AMMO_ROCKETS +// "", // char icon[32]; // Name of ammo icon file + 25 // int max; // Max amount player can hold of ammo + }, + { // AMMO_EMPLACED +// "", // char icon[32]; // Name of ammo icon file + 800 // int max; // Max amount player can hold of ammo + }, + { // AMMO_THERMAL +// "", // char icon[32]; // Name of ammo icon file + 10 // int max; // Max amount player can hold of ammo + }, + { // AMMO_TRIPMINE +// "", // char icon[32]; // Name of ammo icon file + 10 // int max; // Max amount player can hold of ammo + }, + { // AMMO_DETPACK +// "", // char icon[32]; // Name of ammo icon file + 10 // int max; // Max amount player can hold of ammo + } +}; + + diff --git a/code/game/bg_weapons.h b/code/game/bg_weapons.h new file mode 100644 index 0000000..1e47571 --- /dev/null +++ b/code/game/bg_weapons.h @@ -0,0 +1,113 @@ +// Filename:- bg_weapons.h +// +// This crosses both client and server. It could all be crammed into bg_public, but isolation of this type of data is best. + +#ifndef __WEAPONS_H__ +#define __WEAPONS_H__ + +typedef enum { + WP_NONE, + + WP_STUN_BATON, + WP_MELEE, + WP_SABER, + WP_BRYAR_PISTOL, + WP_BLASTER, + WP_DISRUPTOR, + WP_BOWCASTER, + WP_REPEATER, + WP_DEMP2, + WP_FLECHETTE, + WP_ROCKET_LAUNCHER, + WP_THERMAL, + WP_TRIP_MINE, + WP_DET_PACK, + WP_CONCUSSION, + WP_BRYAR_OLD, + WP_EMPLACED_GUN, + WP_TURRET, + +// WP_GAUNTLET, +// WP_MACHINEGUN, // Bryar +// WP_SHOTGUN, // Blaster +// WP_GRENADE_LAUNCHER, // Thermal +// WP_LIGHTNING, // +// WP_RAILGUN, // +// WP_GRAPPLING_HOOK, + + WP_NUM_WEAPONS +}; +typedef int weapon_t; + +//anything > this will be considered not player useable +#define LAST_USEABLE_WEAPON WP_BRYAR_OLD + +typedef enum //# ammo_e +{ + AMMO_NONE, + AMMO_FORCE, // AMMO_PHASER + AMMO_BLASTER, // AMMO_STARFLEET, + AMMO_POWERCELL, // AMMO_ALIEN, + AMMO_METAL_BOLTS, + AMMO_ROCKETS, + AMMO_EMPLACED, + AMMO_THERMAL, + AMMO_TRIPMINE, + AMMO_DETPACK, + AMMO_MAX +} ammo_t; + + +typedef struct weaponData_s +{ +// char classname[32]; // Spawning name + + int ammoIndex; // Index to proper ammo slot + int ammoLow; // Count when ammo is low + + int energyPerShot; // Amount of energy used per shot + int fireTime; // Amount of time between firings + int range; // Range of weapon + + int altEnergyPerShot; // Amount of energy used for alt-fire + int altFireTime; // Amount of time between alt-firings + int altRange; // Range of alt-fire + + int chargeSubTime; // ms interval for subtracting ammo during charge + int altChargeSubTime; // above for secondary + + int chargeSub; // amount to subtract during charge on each interval + int altChargeSub; // above for secondary + + int maxCharge; // stop subtracting once charged for this many ms + int altMaxCharge; // above for secondary +} weaponData_t; + + +typedef struct ammoData_s +{ +// char icon[32]; // Name of ammo icon file + int max; // Max amount player can hold of ammo +} ammoData_t; + + +extern weaponData_t weaponData[WP_NUM_WEAPONS]; +extern ammoData_t ammoData[AMMO_MAX]; + + +// Specific weapon information + +#define FIRST_WEAPON WP_BRYAR_PISTOL // this is the first weapon for next and prev weapon switching +#define MAX_PLAYER_WEAPONS WP_NUM_WEAPONS-1 // this is the max you can switch to and get with the give all. + + +#define DEFAULT_SHOTGUN_SPREAD 700 +#define DEFAULT_SHOTGUN_COUNT 11 + +#define LIGHTNING_RANGE 768 + + + + + +#endif//#ifndef __WEAPONS_H__ diff --git a/code/game/botlib.h b/code/game/botlib.h new file mode 100644 index 0000000..621477a --- /dev/null +++ b/code/game/botlib.h @@ -0,0 +1,508 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +/***************************************************************************** + * name: botlib.h + * + * desc: bot AI library + * + * $Archive: /source/code/game/botai.h $ + * $Author: osman $ + * $Revision: 1.4 $ + * $Modtime: 03/01/00 3:32p $ + * $Date: 2003/03/15 23:44:00 $ + * + *****************************************************************************/ + +#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_FORCEPOWER 0x0100000 +#define ACTION_ALT_ATTACK 0x0200000 +/* +#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; + int torsoAnim; +} 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_Alt_Attack)(int client); + void (*EA_ForcePower)(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); + int (*PC_LoadGlobalDefines)(const char* filename ); + void (*PC_RemoveAllGlobalDefines) ( void ); + + //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/code/game/chars.h b/code/game/chars.h new file mode 100644 index 0000000..98d046e --- /dev/null +++ b/code/game/chars.h @@ -0,0 +1,124 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +//=========================================================================== +// +// Name: chars.h +// Function: bot characteristics +// Programmer: Mr Elusive (MrElusive@idsoftware.com) +// Last update: 1999-09-08 +// 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] + diff --git a/code/game/g_ICARUScb.c b/code/game/g_ICARUScb.c new file mode 100644 index 0000000..2d76637 --- /dev/null +++ b/code/game/g_ICARUScb.c @@ -0,0 +1,6060 @@ +//==================================================================================== +// +//rww - ICARUS callback file, all that can be handled within vm's is handled in here. +// +//==================================================================================== + +#include "q_shared.h" +#include "bg_public.h" +#include "b_local.h" +#include "../icarus/Q3_Interface.h" +#include "../icarus/Q3_Registers.h" +#include "g_nav.h" + +#include "../namespace_begin.h" +qboolean BG_SabersOff( playerState_t *ps ); +extern stringID_table_t WPTable[]; +extern stringID_table_t BSTable[]; +#include "../namespace_end.h" + + +//This is a hack I guess. It's because we can't include the file this enum is in +//unless we're using cpp. But we need it for the interpreter stuff. +//In any case, DO NOT modify this enum. + +// Hack++ +// This code is compiled as C++ on Xbox. We could try and rig something above +// so that we only get the C version of the includes (no full Icarus) in that +// scenario, but I think we'll just try to leave this out instead. +#ifndef _XBOX +#ifndef __linux__ +enum +{ + TK_EOF = -1, + TK_UNDEFINED, + TK_COMMENT, + TK_EOL, + TK_CHAR, + TK_STRING, + TK_INT, + TK_INTEGER = TK_INT, + TK_FLOAT, + TK_IDENTIFIER, + TK_USERDEF, +}; +#endif +#endif + +#include "../icarus/interpreter.h" + +extern stringID_table_t animTable [MAX_ANIMATIONS+1]; + +stringID_table_t setTable[] = +{ + ENUM2STRING(SET_SPAWNSCRIPT),//0 + ENUM2STRING(SET_USESCRIPT), + ENUM2STRING(SET_AWAKESCRIPT), + ENUM2STRING(SET_ANGERSCRIPT), + ENUM2STRING(SET_ATTACKSCRIPT), + ENUM2STRING(SET_VICTORYSCRIPT), + ENUM2STRING(SET_PAINSCRIPT), + ENUM2STRING(SET_FLEESCRIPT), + ENUM2STRING(SET_DEATHSCRIPT), + ENUM2STRING(SET_DELAYEDSCRIPT), + ENUM2STRING(SET_BLOCKEDSCRIPT), + ENUM2STRING(SET_FFIRESCRIPT), + ENUM2STRING(SET_FFDEATHSCRIPT), + ENUM2STRING(SET_MINDTRICKSCRIPT), + ENUM2STRING(SET_NO_MINDTRICK), + ENUM2STRING(SET_ORIGIN), + ENUM2STRING(SET_TELEPORT_DEST), + ENUM2STRING(SET_ANGLES), + ENUM2STRING(SET_XVELOCITY), + ENUM2STRING(SET_YVELOCITY), + ENUM2STRING(SET_ZVELOCITY), + ENUM2STRING(SET_Z_OFFSET), + ENUM2STRING(SET_ENEMY), + ENUM2STRING(SET_LEADER), + ENUM2STRING(SET_NAVGOAL), + ENUM2STRING(SET_ANIM_UPPER), + ENUM2STRING(SET_ANIM_LOWER), + ENUM2STRING(SET_ANIM_BOTH), + ENUM2STRING(SET_ANIM_HOLDTIME_LOWER), + ENUM2STRING(SET_ANIM_HOLDTIME_UPPER), + ENUM2STRING(SET_ANIM_HOLDTIME_BOTH), + ENUM2STRING(SET_PLAYER_TEAM), + ENUM2STRING(SET_ENEMY_TEAM), + ENUM2STRING(SET_BEHAVIOR_STATE), + ENUM2STRING(SET_BEHAVIOR_STATE), + ENUM2STRING(SET_HEALTH), + ENUM2STRING(SET_ARMOR), + ENUM2STRING(SET_DEFAULT_BSTATE), + ENUM2STRING(SET_CAPTURE), + ENUM2STRING(SET_DPITCH), + ENUM2STRING(SET_DYAW), + ENUM2STRING(SET_EVENT), + ENUM2STRING(SET_TEMP_BSTATE), + ENUM2STRING(SET_COPY_ORIGIN), + ENUM2STRING(SET_VIEWTARGET), + ENUM2STRING(SET_WEAPON), + ENUM2STRING(SET_ITEM), + ENUM2STRING(SET_WALKSPEED), + ENUM2STRING(SET_RUNSPEED), + ENUM2STRING(SET_YAWSPEED), + ENUM2STRING(SET_AGGRESSION), + ENUM2STRING(SET_AIM), + ENUM2STRING(SET_FRICTION), + ENUM2STRING(SET_GRAVITY), + ENUM2STRING(SET_IGNOREPAIN), + ENUM2STRING(SET_IGNOREENEMIES), + ENUM2STRING(SET_IGNOREALERTS), + ENUM2STRING(SET_DONTSHOOT), + ENUM2STRING(SET_DONTFIRE), + ENUM2STRING(SET_LOCKED_ENEMY), + ENUM2STRING(SET_NOTARGET), + ENUM2STRING(SET_LEAN), + ENUM2STRING(SET_CROUCHED), + ENUM2STRING(SET_WALKING), + ENUM2STRING(SET_RUNNING), + ENUM2STRING(SET_CHASE_ENEMIES), + ENUM2STRING(SET_LOOK_FOR_ENEMIES), + ENUM2STRING(SET_FACE_MOVE_DIR), + ENUM2STRING(SET_ALT_FIRE), + ENUM2STRING(SET_DONT_FLEE), + ENUM2STRING(SET_FORCED_MARCH), + ENUM2STRING(SET_NO_RESPONSE), + ENUM2STRING(SET_NO_COMBAT_TALK), + ENUM2STRING(SET_NO_ALERT_TALK), + ENUM2STRING(SET_UNDYING), + ENUM2STRING(SET_TREASONED), + ENUM2STRING(SET_DISABLE_SHADER_ANIM), + ENUM2STRING(SET_SHADER_ANIM), + ENUM2STRING(SET_INVINCIBLE), + ENUM2STRING(SET_NOAVOID), + ENUM2STRING(SET_SHOOTDIST), + ENUM2STRING(SET_TARGETNAME), + ENUM2STRING(SET_TARGET), + ENUM2STRING(SET_TARGET2), + ENUM2STRING(SET_LOCATION), + ENUM2STRING(SET_PAINTARGET), + ENUM2STRING(SET_TIMESCALE), + ENUM2STRING(SET_VISRANGE), + ENUM2STRING(SET_EARSHOT), + ENUM2STRING(SET_VIGILANCE), + ENUM2STRING(SET_HFOV), + ENUM2STRING(SET_VFOV), + ENUM2STRING(SET_DELAYSCRIPTTIME), + ENUM2STRING(SET_FORWARDMOVE), + ENUM2STRING(SET_RIGHTMOVE), + ENUM2STRING(SET_LOCKYAW), + ENUM2STRING(SET_SOLID), + ENUM2STRING(SET_CAMERA_GROUP), + ENUM2STRING(SET_CAMERA_GROUP_Z_OFS), + ENUM2STRING(SET_CAMERA_GROUP_TAG), + ENUM2STRING(SET_LOOK_TARGET), + ENUM2STRING(SET_ADDRHANDBOLT_MODEL), + ENUM2STRING(SET_REMOVERHANDBOLT_MODEL), + ENUM2STRING(SET_ADDLHANDBOLT_MODEL), + ENUM2STRING(SET_REMOVELHANDBOLT_MODEL), + ENUM2STRING(SET_FACEAUX), + ENUM2STRING(SET_FACEBLINK), + ENUM2STRING(SET_FACEBLINKFROWN), + ENUM2STRING(SET_FACEFROWN), + ENUM2STRING(SET_FACENORMAL), + ENUM2STRING(SET_FACEEYESCLOSED), + ENUM2STRING(SET_FACEEYESOPENED), + ENUM2STRING(SET_SCROLLTEXT), + ENUM2STRING(SET_LCARSTEXT), + ENUM2STRING(SET_SCROLLTEXTCOLOR), + ENUM2STRING(SET_CAPTIONTEXTCOLOR), + ENUM2STRING(SET_CENTERTEXTCOLOR), + ENUM2STRING(SET_PLAYER_USABLE), + ENUM2STRING(SET_STARTFRAME), + ENUM2STRING(SET_ENDFRAME), + ENUM2STRING(SET_ANIMFRAME), + ENUM2STRING(SET_LOOP_ANIM), + ENUM2STRING(SET_INTERFACE), + ENUM2STRING(SET_SHIELDS), + ENUM2STRING(SET_NO_KNOCKBACK), + ENUM2STRING(SET_INVISIBLE), + ENUM2STRING(SET_VAMPIRE), + ENUM2STRING(SET_FORCE_INVINCIBLE), + ENUM2STRING(SET_GREET_ALLIES), + ENUM2STRING(SET_PLAYER_LOCKED), + ENUM2STRING(SET_LOCK_PLAYER_WEAPONS), + ENUM2STRING(SET_NO_IMPACT_DAMAGE), + ENUM2STRING(SET_PARM1), + ENUM2STRING(SET_PARM2), + ENUM2STRING(SET_PARM3), + ENUM2STRING(SET_PARM4), + ENUM2STRING(SET_PARM5), + ENUM2STRING(SET_PARM6), + ENUM2STRING(SET_PARM7), + ENUM2STRING(SET_PARM8), + ENUM2STRING(SET_PARM9), + ENUM2STRING(SET_PARM10), + ENUM2STRING(SET_PARM11), + ENUM2STRING(SET_PARM12), + ENUM2STRING(SET_PARM13), + ENUM2STRING(SET_PARM14), + ENUM2STRING(SET_PARM15), + ENUM2STRING(SET_PARM16), + ENUM2STRING(SET_DEFEND_TARGET), + ENUM2STRING(SET_WAIT), + ENUM2STRING(SET_COUNT), + ENUM2STRING(SET_SHOT_SPACING), + ENUM2STRING(SET_VIDEO_PLAY), + ENUM2STRING(SET_VIDEO_FADE_IN), + ENUM2STRING(SET_VIDEO_FADE_OUT), + ENUM2STRING(SET_REMOVE_TARGET), + ENUM2STRING(SET_LOADGAME), + ENUM2STRING(SET_MENU_SCREEN), + ENUM2STRING(SET_OBJECTIVE_SHOW), + ENUM2STRING(SET_OBJECTIVE_HIDE), + ENUM2STRING(SET_OBJECTIVE_SUCCEEDED), + ENUM2STRING(SET_OBJECTIVE_FAILED), + ENUM2STRING(SET_MISSIONFAILED), + ENUM2STRING(SET_TACTICAL_SHOW), + ENUM2STRING(SET_TACTICAL_HIDE), + ENUM2STRING(SET_FOLLOWDIST), + ENUM2STRING(SET_SCALE), + ENUM2STRING(SET_OBJECTIVE_CLEARALL), + ENUM2STRING(SET_MISSIONSTATUSTEXT), + ENUM2STRING(SET_WIDTH), + ENUM2STRING(SET_CLOSINGCREDITS), + ENUM2STRING(SET_SKILL), + ENUM2STRING(SET_MISSIONSTATUSTIME), + ENUM2STRING(SET_FULLNAME), + ENUM2STRING(SET_FORCE_HEAL_LEVEL), + ENUM2STRING(SET_FORCE_JUMP_LEVEL), + ENUM2STRING(SET_FORCE_SPEED_LEVEL), + ENUM2STRING(SET_FORCE_PUSH_LEVEL), + ENUM2STRING(SET_FORCE_PULL_LEVEL), + ENUM2STRING(SET_FORCE_MINDTRICK_LEVEL), + ENUM2STRING(SET_FORCE_GRIP_LEVEL), + ENUM2STRING(SET_FORCE_LIGHTNING_LEVEL), + ENUM2STRING(SET_SABER_THROW), + ENUM2STRING(SET_SABER_DEFENSE), + ENUM2STRING(SET_SABER_OFFENSE), + ENUM2STRING(SET_VIEWENTITY), + ENUM2STRING(SET_WATCHTARGET), + ENUM2STRING(SET_SABERACTIVE), + ENUM2STRING(SET_ADJUST_AREA_PORTALS), + ENUM2STRING(SET_DMG_BY_HEAVY_WEAP_ONLY), + ENUM2STRING(SET_SHIELDED), + ENUM2STRING(SET_NO_GROUPS), + ENUM2STRING(SET_FIRE_WEAPON), + ENUM2STRING(SET_INACTIVE), + ENUM2STRING(SET_FUNC_USABLE_VISIBLE), + ENUM2STRING(SET_MISSION_STATUS_SCREEN), + ENUM2STRING(SET_END_SCREENDISSOLVE), + ENUM2STRING(SET_LOOPSOUND), + ENUM2STRING(SET_ICARUS_FREEZE), + ENUM2STRING(SET_ICARUS_UNFREEZE), + ENUM2STRING(SET_USE_CP_NEAREST), + ENUM2STRING(SET_MORELIGHT), + ENUM2STRING(SET_CINEMATIC_SKIPSCRIPT), + ENUM2STRING(SET_NO_FORCE), + ENUM2STRING(SET_NO_FALLTODEATH), + ENUM2STRING(SET_DISMEMBERABLE), + ENUM2STRING(SET_NO_ACROBATICS), + ENUM2STRING(SET_MUSIC_STATE), + ENUM2STRING(SET_USE_SUBTITLES), + ENUM2STRING(SET_CLEAN_DAMAGING_ENTS), + ENUM2STRING(SET_HUD), + +//FIXME: add BOTH_ attributes here too + "", SET_, +}; + +void Q3_TaskIDClear( int *taskID ) +{ + *taskID = -1; +} + +void G_DebugPrint( int level, const char *format, ... ) +{ + va_list argptr; + char text[1024]; + + //Don't print messages they don't want to see + //if ( g_ICARUSDebug->integer < level ) + if (g_developer.integer != 2) + return; + + va_start (argptr, format); + vsprintf (text, format, argptr); + va_end (argptr); + + //Add the color formatting + switch ( level ) + { + case WL_ERROR: + Com_Printf ( S_COLOR_RED"ERROR: %s", text ); + break; + + case WL_WARNING: + Com_Printf ( S_COLOR_YELLOW"WARNING: %s", text ); + break; + + case WL_DEBUG: + { + int entNum; + char *buffer; + + sscanf( text, "%d", &entNum ); + + //if ( ( ICARUS_entFilter >= 0 ) && ( ICARUS_entFilter != entNum ) ) + // return; + + buffer = (char *) text; + buffer += 5; + + if ( ( entNum < 0 ) || ( entNum > MAX_GENTITIES ) ) + entNum = 0; + + Com_Printf ( S_COLOR_BLUE"DEBUG: %s(%d): %s\n", g_entities[entNum].script_targetname, entNum, buffer ); + break; + } + default: + case WL_VERBOSE: + Com_Printf ( S_COLOR_GREEN"INFO: %s", text ); + break; + } +} + +/* +------------------------- +Q3_GetAnimLower +------------------------- +*/ +static char *Q3_GetAnimLower( gentity_t *ent ) +{ + int anim = 0; + + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetAnimLower: attempted to read animation state off non-client!\n" ); + return NULL; + } + + anim = ent->client->ps.legsAnim; + + return (char *)animTable[anim].name; +} + +/* +------------------------- +Q3_GetAnimUpper +------------------------- +*/ +static char *Q3_GetAnimUpper( gentity_t *ent ) +{ + int anim = 0; + + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetAnimUpper: attempted to read animation state off non-client!\n" ); + return NULL; + } + + anim = ent->client->ps.torsoAnim; + + return (char *)animTable[anim].name; +} + +/* +------------------------- +Q3_GetAnimBoth +------------------------- +*/ +static char *Q3_GetAnimBoth( gentity_t *ent ) +{ + char *lowerName, *upperName; + + lowerName = Q3_GetAnimLower( ent ); + upperName = Q3_GetAnimUpper( ent ); + + if ( !lowerName || !lowerName[0] ) + { + G_DebugPrint( WL_WARNING, "Q3_GetAnimBoth: NULL legs animation string found!\n" ); + return NULL; + } + + if ( !upperName || !upperName[0] ) + { + G_DebugPrint( WL_WARNING, "Q3_GetAnimBoth: NULL torso animation string found!\n" ); + return NULL; + } + + if ( Q_stricmp( lowerName, upperName ) ) + { +#ifdef _DEBUG // sigh, cut down on tester reports that aren't important + G_DebugPrint( WL_WARNING, "Q3_GetAnimBoth: legs and torso animations did not match : returning legs\n" ); +#endif + } + + return lowerName; +} + +int Q3_PlaySound( int taskID, int entID, const char *name, const char *channel ) +{ + gentity_t *ent = &g_entities[entID]; + char finalName[MAX_QPATH]; + soundChannel_t voice_chan = CHAN_VOICE; // set a default so the compiler doesn't bitch + qboolean type_voice = qfalse; + int soundHandle; + qboolean bBroadcast; + + Q_strncpyz( finalName, name, MAX_QPATH ); + Q_strupr(finalName); + //G_AddSexToMunroString( finalName, qtrue ); + + COM_StripExtension( (const char *)finalName, finalName ); + + soundHandle = G_SoundIndex( (char *) finalName ); + bBroadcast = qfalse; + + if ( ( Q_stricmp( channel, "CHAN_ANNOUNCER" ) == 0 ) || (ent->classname && Q_stricmp("target_scriptrunner", ent->classname ) == 0) ) { + bBroadcast = qtrue; + } + + + // moved here from further down so I can easily check channel-type without code dup... + // + if ( Q_stricmp( channel, "CHAN_VOICE" ) == 0 ) + { + voice_chan = CHAN_VOICE; + type_voice = qtrue; + } + else if ( Q_stricmp( channel, "CHAN_VOICE_ATTEN" ) == 0 ) + { + voice_chan = CHAN_AUTO;//CHAN_VOICE_ATTEN; + type_voice = qtrue; + } + else if ( Q_stricmp( channel, "CHAN_VOICE_GLOBAL" ) == 0 ) // this should broadcast to everyone, put only casue animation on G_SoundOnEnt... + { + voice_chan = CHAN_AUTO;//CHAN_VOICE_GLOBAL; + type_voice = qtrue; + bBroadcast = qtrue; + } + + // if we're in-camera, check for skipping cinematic and ifso, no subtitle print (since screen is not being + // updated anyway during skipping). This stops leftover subtitles being left onscreen after unskipping. + // + /* + if (!in_camera || + (!g_skippingcin || !g_skippingcin->integer) + ) // paranoia towards project end + { + // Text on + // certain NPC's we always want to use subtitles regardless of subtitle setting + if (g_subtitles->integer == 1 || (ent->NPC && (ent->NPC->scriptFlags & SCF_USE_SUBTITLES) ) ) // Show all text + { + if ( in_camera) // Cinematic + { + trap_SendServerCommand( -1, va("ct \"%s\" %i", finalName, soundHandle) ); + } + else //if (precacheWav[i].speaker==SP_NONE) // lower screen text + { + sharedEntity_t *ent2 = SV_GentityNum(0); + // the numbers in here were either the original ones Bob entered (350), or one arrived at from checking the distance Chell stands at in stasis2 by the computer core that was submitted as a bug report... + // + if (bBroadcast || (DistanceSquared(ent->currentOrigin, ent2->currentOrigin) < ((voice_chan == CHAN_VOICE_ATTEN)?(350 * 350):(1200 * 1200)) ) ) + { + trap_SendServerCommand( -1, va("ct \"%s\" %i", finalName, soundHandle) ); + } + } + } + // Cinematic only + else if (g_subtitles->integer == 2) // Show only talking head text and CINEMATIC + { + if ( in_camera) // Cinematic text + { + trap_SendServerCommand( -1, va("ct \"%s\" %i", finalName, soundHandle)); + } + } + + } + */ + + if ( type_voice ) + { + char buf[128]; + float tFVal = 0; + + trap_Cvar_VariableStringBuffer("timescale", buf, sizeof(buf)); + + tFVal = atof(buf); + + + if ( tFVal > 1.0f ) + {//Skip the damn sound! + return qtrue; + } + else + { + //This the voice channel + G_Sound( ent, voice_chan, G_SoundIndex((char *) finalName) ); + } + //Remember we're waiting for this + trap_ICARUS_TaskIDSet( ent, TID_CHAN_VOICE, taskID ); + + return qfalse; + } + + if ( bBroadcast ) + {//Broadcast the sound + gentity_t *te; + + te = G_TempEntity( ent->r.currentOrigin, EV_GLOBAL_SOUND ); + te->s.eventParm = soundHandle; + te->r.svFlags |= SVF_BROADCAST; + } + else + { + G_Sound( ent, CHAN_AUTO, soundHandle ); + } + + return qtrue; +} + +/* +------------------------- +Q3_Play +------------------------- +*/ +void Q3_Play( int taskID, int entID, const char *type, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !Q_stricmp( type, "PLAY_ROFF" ) ) + { + // Try to load the requested ROFF + ent->roffid = trap_ROFF_Cache((char*)name); + if ( ent->roffid ) + { + ent->roffname = G_NewString( name ); + + // Start the roff from the beginning + //ent->roff_ctr = 0; + + //Save this off for later + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + + // Let the ROFF playing start. + //ent->next_roff_time = level.time; + + //rww - Maybe use pos1 and pos2? I don't think we need to care if these values are sent across the net. + // These need to be initialised up front... + //VectorCopy( ent->r.currentOrigin, ent->pos1 ); + //VectorCopy( ent->r.currentAngles, ent->pos2 ); + VectorCopy( ent->r.currentOrigin, ent->s.origin2 ); + VectorCopy( ent->r.currentAngles, ent->s.angles2 ); + + trap_LinkEntity( ent ); + + trap_ROFF_Play(ent->s.number, ent->roffid, qtrue); + } + } +} + +/* +============= +anglerCallback + +Utility function +============= +*/ +void anglerCallback( gentity_t *ent ) +{ + //Complete the task + trap_ICARUS_TaskIDComplete( ent, TID_ANGLE_FACE ); + + //Set the currentAngles, clear all movement + VectorMA( ent->s.apos.trBase, (ent->s.apos.trDuration*0.001f), ent->s.apos.trDelta, ent->r.currentAngles ); + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + VectorClear( ent->s.apos.trDelta ); + ent->s.apos.trDuration = 1; + ent->s.apos.trType = TR_STATIONARY; + ent->s.apos.trTime = level.time; + + //Stop thinking + ent->reached = 0; + if ( ent->think == anglerCallback ) + { + ent->think = 0; + } + + //link + trap_LinkEntity( ent ); +} + +void MatchTeam( gentity_t *teamLeader, int moverState, int time ); +void Blocked_Mover( gentity_t *ent, gentity_t *other ); + +/* +============= +moverCallback + +Utility function +============= +*/ +void moverCallback( gentity_t *ent ) +{ //complete the task + trap_ICARUS_TaskIDComplete( ent, TID_MOVE_NAV ); + + // play sound + ent->s.loopSound = 0;//stop looping sound + ent->s.loopIsSoundset = qfalse; + G_PlayDoorSound( ent, BMS_END );//play end sound + + if ( ent->moverState == MOVER_1TO2 ) + {//reached open + // reached pos2 + MatchTeam( ent, MOVER_POS2, level.time ); + //SetMoverState( ent, MOVER_POS2, level.time ); + } + else if ( ent->moverState == MOVER_2TO1 ) + {//reached closed + MatchTeam( ent, MOVER_POS1, level.time ); + //SetMoverState( ent, MOVER_POS1, level.time ); + } + + if ( ent->blocked == Blocked_Mover ) + { + ent->blocked = 0; + } + +// if ( !Q_stricmp( "misc_model_breakable", ent->classname ) && ent->physicsBounce ) +// {//a gravity-affected model +// misc_model_breakable_gravity_init( ent, qfalse ); +// } +} + +void Blocked_Mover( gentity_t *ent, gentity_t *other ) +{ + // remove anything other than a client -- no longer the case + + // don't remove security keys or goodie keys + if ( (other->s.eType == ET_ITEM) ) + { + // should we be doing anything special if a key blocks it... move it somehow..? + } + // if your not a client, or your a dead client remove yourself... + else if ( other->s.number && (!other->client || (other->client && other->health <= 0 && other->r.contents == CONTENTS_CORPSE && !other->message)) ) + { + //if ( !other->taskManager || !other->taskManager->IsRunning() ) + { + // if an item or weapon can we do a little explosion..? + G_FreeEntity( other ); + return; + } + } + + if ( ent->damage ) { + G_Damage( other, ent, ent, NULL, NULL, ent->damage, 0, MOD_CRUSH ); + } +} + +/* +============= +moveAndRotateCallback + +Utility function +============= +*/ +void moveAndRotateCallback( gentity_t *ent ) +{ + //stop turning + anglerCallback( ent ); + //stop moving + moverCallback( ent ); +} + +/* +============= +Q3_Lerp2Start + +Lerps the origin of an entity to its starting position +============= +*/ +void Q3_Lerp2Start( int entID, int taskID, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2Start: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2Start: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + //FIXME: set up correctly!!! + ent->moverState = MOVER_2TO1; + ent->s.eType = ET_MOVER; + ent->reached = moverCallback; //Callsback the the completion of the move + if ( ent->damage ) + { + ent->blocked = Blocked_Mover; + } + + ent->s.pos.trDuration = duration * 10; //In seconds + ent->s.pos.trTime = level.time; + + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + trap_LinkEntity( ent ); +} + +/* +============= +Q3_Lerp2End + +Lerps the origin of an entity to its ending position +============= +*/ +void Q3_Lerp2End( int entID, int taskID, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2End: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2End: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + //FIXME: set up correctly!!! + ent->moverState = MOVER_1TO2; + ent->s.eType = ET_MOVER; + ent->reached = moverCallback; //Callsback the the completion of the move + if ( ent->damage ) + { + ent->blocked = Blocked_Mover; + } + + ent->s.pos.trDuration = duration * 10; //In seconds + ent->s.time = level.time; + + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + trap_LinkEntity( ent ); +} + +void InitMoverTrData( gentity_t *ent ); + +/* +============= +Q3_Lerp2Pos + +Lerps the origin and angles of an entity to the destination values + +============= +*/ +void Q3_Lerp2Pos( int taskID, int entID, vec3_t origin, vec3_t angles, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t ang; + int i; + moverState_t moverState; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2Pos: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2Pos: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + //Don't allow a zero duration + if ( duration == 0 ) + duration = 1; + + // + // Movement + + moverState = ent->moverState; + + if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) + { + VectorCopy( ent->r.currentOrigin, ent->pos1 ); + VectorCopy( origin, ent->pos2 ); + + moverState = MOVER_1TO2; + } + else + { + VectorCopy( ent->r.currentOrigin, ent->pos2 ); + VectorCopy( origin, ent->pos1 ); + + moverState = MOVER_2TO1; + } + + InitMoverTrData( ent ); + + ent->s.pos.trDuration = duration; + + // start it going + MatchTeam( ent, moverState, level.time ); + //SetMoverState( ent, moverState, level.time ); + + //Only do the angles if specified + if ( angles != NULL ) + { + // + // Rotation + + for ( i = 0; i < 3; i++ ) + { + ang[i] = AngleDelta( angles[i], ent->r.currentAngles[i] ); + ent->s.apos.trDelta[i] = ( ang[i] / ( duration * 0.001f ) ); + } + + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + ent->s.apos.trDuration = duration; + + ent->s.apos.trTime = level.time; + + ent->reached = moveAndRotateCallback; + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + } + else + { + //Setup the last bits of information + ent->reached = moverCallback; + } + + if ( ent->damage ) + { + ent->blocked = Blocked_Mover; + } + + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + // starting sound + G_PlayDoorLoopSound( ent ); + G_PlayDoorSound( ent, BMS_START ); //?? + + trap_LinkEntity( ent ); +} + +/* +============= +Q3_LerpAngles + +Lerps the angles to the destination value +============= +*/ +void Q3_Lerp2Angles( int taskID, int entID, vec3_t angles, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t ang; + int i; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2Angles: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2Angles: ent %d is NOT a mover!\n", entID); + return; + } + + //If we want an instant move, don't send 0... + ent->s.apos.trDuration = (duration>0) ? duration : 1; + + for ( i = 0; i < 3; i++ ) + { + ang [i] = AngleSubtract( angles[i], ent->r.currentAngles[i]); + ent->s.apos.trDelta[i] = ( ang[i] / ( ent->s.apos.trDuration * 0.001f ) ); + } + + VectorCopy( ent->r.currentAngles, ent->s.apos.trBase ); + + if ( ent->alt_fire ) + { + ent->s.apos.trType = TR_LINEAR_STOP; + } + else + { + ent->s.apos.trType = TR_NONLINEAR_STOP; + } + + ent->s.apos.trTime = level.time; + + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + + //ent->e_ReachedFunc = reachedF_NULL; + ent->think = anglerCallback; + ent->nextthink = level.time + duration; + + trap_LinkEntity( ent ); +} + +/* +============= +Q3_GetTag + +Gets the value of a tag by the give name +============= +*/ +int Q3_GetTag( int entID, const char *name, int lookup, vec3_t info ) +{ + gentity_t *ent = &g_entities[entID]; + + if (!ent->inuse) + { + assert(0); + return 0; + } + + switch ( lookup ) + { + case TYPE_ORIGIN: + return TAG_GetOrigin( ent->ownername, name, info ); + break; + + case TYPE_ANGLES: + return TAG_GetAngles( ent->ownername, name, info ); + break; + } + + return 0; +} + +//----------------------------------------------- + +/* +============ +Q3_Use + +Uses an entity +============ +*/ +void Q3_Use( int entID, const char *target ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_Use: invalid entID %d\n", entID); + return; + } + + if( !target || !target[0] ) + { + G_DebugPrint( WL_WARNING, "Q3_Use: string is NULL!\n" ); + return; + } + + G_UseTargets2(ent, ent, target); +} + +/* +============ +Q3_Kill + Description : + Return type : void + Argument : int entID + Argument : const char *name +============ +*/ +void Q3_Kill( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + gentity_t *victim = NULL; + int o_health; + + if( !Q_stricmp( name, "self") ) + { + victim = ent; + } + else if( !Q_stricmp( name, "enemy" ) ) + { + victim = ent->enemy; + } + else + { + victim = G_Find (NULL, FOFS(targetname), (char *) name ); + } + + if ( !victim ) + { + G_DebugPrint( WL_WARNING, "Q3_Kill: can't find %s\n", name); + return; + } + + //rww - I guess this would only apply to NPCs anyway. I'm not going to bother. + //if ( victim == ent ) + //{//don't ICARUS_FreeEnt me, I'm in the middle of a script! (FIXME: shouldn't ICARUS handle this internally?) + // victim->svFlags |= SVF_KILLED_SELF; + //} + + o_health = victim->health; + victim->health = 0; + if ( victim->client ) + { + victim->flags |= FL_NO_KNOCKBACK; + } + //G_SetEnemy(victim, ent); + if( victim->die != NULL ) // check can be omitted + { + //GEntity_DieFunc( victim, NULL, NULL, o_health, MOD_UNKNOWN ); + victim->die(victim, victim, victim, o_health, MOD_UNKNOWN); + } +} + +/* +============ +Q3_RemoveEnt + Description : + Return type : void + Argument : sharedEntity_t *victim +============ +*/ +void Q3_RemoveEnt( gentity_t *victim ) +{ + if( victim->client ) + { + if ( victim->s.eType != ET_NPC ) + { + G_DebugPrint( WL_WARNING, "Q3_RemoveEnt: You can't remove clients in MP!\n" ); + assert(0); //can't remove clients in MP + } + else + {//remove the NPC + if ( victim->client->NPC_class == CLASS_VEHICLE ) + {//eject everyone out of a vehicle that's about to remove itself + Vehicle_t *pVeh = victim->m_pVehicle; + if ( pVeh && pVeh->m_pVehicleInfo ) + { + pVeh->m_pVehicleInfo->EjectAll( pVeh ); + } + } + victim->think = G_FreeEntity; + victim->nextthink = level.time + 100; + } + /* + //ClientDisconnect(ent); + victim->s.eFlags |= EF_NODRAW; + victim->s.eType = ET_INVISIBLE; + victim->contents = 0; + victim->health = 0; + victim->targetname = NULL; + + if ( victim->NPC && victim->NPC->tempGoal != NULL ) + { + G_FreeEntity( victim->NPC->tempGoal ); + victim->NPC->tempGoal = NULL; + } + if ( victim->client->ps.saberEntityNum != ENTITYNUM_NONE && victim->client->ps.saberEntityNum > 0 ) + { + if ( g_entities[victim->client->ps.saberEntityNum].inuse ) + { + G_FreeEntity( &g_entities[victim->client->ps.saberEntityNum] ); + } + victim->client->ps.saberEntityNum = ENTITYNUM_NONE; + } + //Disappear in half a second + victim->e_ThinkFunc = thinkF_G_FreeEntity; + victim->nextthink = level.time + 500; + return; + */ + } + else + { + victim->think = G_FreeEntity; + victim->nextthink = level.time + 100; + } +} + + +/* +============ +Q3_Remove + Description : + Return type : void + Argument : int entID + Argument : const char *name +============ +*/ +void Q3_Remove( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + gentity_t *victim = NULL; + + if( !Q_stricmp( "self", name ) ) + { + victim = ent; + if ( !victim ) + { + G_DebugPrint( WL_WARNING, "Q3_Remove: can't find %s\n", name ); + return; + } + Q3_RemoveEnt( victim ); + } + else if( !Q_stricmp( "enemy", name ) ) + { + victim = ent->enemy; + if ( !victim ) + { + G_DebugPrint( WL_WARNING, "Q3_Remove: can't find %s\n", name ); + return; + } + Q3_RemoveEnt( victim ); + } + else + { + victim = G_Find( NULL, FOFS(targetname), (char *) name ); + if ( !victim ) + { + G_DebugPrint( WL_WARNING, "Q3_Remove: can't find %s\n", name ); + return; + } + + while ( victim ) + { + Q3_RemoveEnt( victim ); + victim = G_Find( victim, FOFS(targetname), (char *) name ); + } + } +} + +/* +================================================= + + Get / Set Functions + +================================================= +*/ + +/* +============ +Q3_GetFloat + Description : + Return type : int + Argument : int entID + Argument : int type + Argument : const char *name + Argument : float *value +============ +*/ +int Q3_GetFloat( int entID, int type, const char *name, float *value ) +{ + gentity_t *ent = &g_entities[entID]; + int toGet = 0; + + if ( !ent ) + { + return 0; + } + + toGet = GetIDForString( setTable, name ); //FIXME: May want to make a "getTable" as well + //FIXME: I'm getting really sick of these huge switch statements! + + //NOTENOTE: return true if the value was correctly obtained + switch ( toGet ) + { + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + if (ent->parms == NULL) + { + G_DebugPrint( WL_ERROR, "GET_PARM: %s %s did not have any parms set!\n", ent->classname, ent->targetname ); + return 0; // would prefer qfalse, but I'm fitting in with what's here + } + *value = atof( ent->parms->parm[toGet - SET_PARM1] ); + break; + + case SET_COUNT: + *value = ent->count; + break; + + case SET_HEALTH: + *value = ent->health; + break; + + case SET_SKILL: + return 0; + break; + + case SET_XVELOCITY://## %f="0.0" # Velocity along X axis + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_XVELOCITY, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.velocity[0]; + break; + + case SET_YVELOCITY://## %f="0.0" # Velocity along Y axis + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_YVELOCITY, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.velocity[1]; + break; + + case SET_ZVELOCITY://## %f="0.0" # Velocity along Z axis + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ZVELOCITY, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.velocity[2]; + break; + + case SET_Z_OFFSET: + *value = ent->r.currentOrigin[2] - ent->s.origin[2]; + break; + + case SET_DPITCH://## %f="0.0" # Pitch for NPC to turn to + return 0; + break; + + case SET_DYAW://## %f="0.0" # Yaw for NPC to turn to + return 0; + break; + + case SET_WIDTH://## %f="0.0" # Width of NPC bounding box + *value = ent->r.mins[0]; + break; + case SET_TIMESCALE://## %f="0.0" # Speed-up slow down game (0 - 1.0) + return 0; + break; + case SET_CAMERA_GROUP_Z_OFS://## %s="NULL" # all ents with this cameraGroup will be focused on + return 0; + break; + + case SET_VISRANGE://## %f="0.0" # How far away NPC can see + return 0; + break; + + case SET_EARSHOT://## %f="0.0" # How far an NPC can hear + return 0; + break; + + case SET_VIGILANCE://## %f="0.0" # How often to look for enemies (0 - 1.0) + return 0; + break; + + case SET_GRAVITY://## %f="0.0" # Change this ent's gravity - 800 default + *value = g_gravity.value; + break; + + case SET_FACEEYESCLOSED: + case SET_FACEEYESOPENED: + case SET_FACEAUX: //## %f="0.0" # Set face to Aux expression for number of seconds + case SET_FACEBLINK: //## %f="0.0" # Set face to Blink expression for number of seconds + case SET_FACEBLINKFROWN: //## %f="0.0" # Set face to Blinkfrown expression for number of seconds + case SET_FACEFROWN: //## %f="0.0" # Set face to Frown expression for number of seconds + case SET_FACENORMAL: //## %f="0.0" # Set face to Normal expression for number of seconds + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_FACE___ not implemented\n" ); + return 0; + break; + case SET_WAIT: //## %f="0.0" # Change an entity's wait field + *value = ent->wait; + break; + case SET_FOLLOWDIST: //## %f="0.0" # How far away to stay from leader in BS_FOLLOW_LEADER + return 0; + break; + //# #sep ints + case SET_ANIM_HOLDTIME_LOWER://## %d="0" # Hold lower anim for number of milliseconds + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ANIM_HOLDTIME_LOWER, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.legsTimer; + break; + case SET_ANIM_HOLDTIME_UPPER://## %d="0" # Hold upper anim for number of milliseconds + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ANIM_HOLDTIME_UPPER, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.torsoTimer; + break; + case SET_ANIM_HOLDTIME_BOTH://## %d="0" # Hold lower and upper anims for number of milliseconds + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ANIM_HOLDTIME_BOTH not implemented\n" ); + return 0; + break; + case SET_ARMOR://## %d="0" # Change armor + if ( ent->client == NULL ) + { + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_ARMOR, %s not a client\n", ent->targetname ); + return 0; + } + *value = ent->client->ps.stats[STAT_ARMOR]; + break; + case SET_WALKSPEED://## %d="0" # Change walkSpeed + return 0; + break; + case SET_RUNSPEED://## %d="0" # Change runSpeed + return 0; + break; + case SET_YAWSPEED://## %d="0" # Change yawSpeed + return 0; + break; + case SET_AGGRESSION://## %d="0" # Change aggression 1-5 + return 0; + break; + case SET_AIM://## %d="0" # Change aim 1-5 + return 0; + break; + case SET_FRICTION://## %d="0" # Change ent's friction - 6 default + return 0; + break; + case SET_SHOOTDIST://## %d="0" # How far the ent can shoot - 0 uses weapon + return 0; + break; + case SET_HFOV://## %d="0" # Horizontal field of view + return 0; + break; + case SET_VFOV://## %d="0" # Vertical field of view + return 0; + break; + case SET_DELAYSCRIPTTIME://## %d="0" # How many seconds to wait before running delayscript + return 0; + break; + case SET_FORWARDMOVE://## %d="0" # NPC move forward -127(back) to 127 + return 0; + break; + case SET_RIGHTMOVE://## %d="0" # NPC move right -127(left) to 127 + return 0; + break; + case SET_STARTFRAME: //## %d="0" # frame to start animation sequence on + return 0; + break; + case SET_ENDFRAME: //## %d="0" # frame to end animation sequence on + return 0; + break; + case SET_ANIMFRAME: //## %d="0" # of current frame + return 0; + break; + + case SET_SHOT_SPACING://## %d="1000" # Time between shots for an NPC - reset to defaults when changes weapon + return 0; + break; + case SET_MISSIONSTATUSTIME://## %d="0" # Amount of time until Mission Status should be shown after death + return 0; + break; + //# #sep booleans + case SET_IGNOREPAIN://## %t="BOOL_TYPES" # Do not react to pain + return 0; + break; + case SET_IGNOREENEMIES://## %t="BOOL_TYPES" # Do not acquire enemies + return 0; + break; + case SET_IGNOREALERTS://## Do not get enemy set by allies in area(ambush) + return 0; + break; + case SET_DONTSHOOT://## %t="BOOL_TYPES" # Others won't shoot you + return 0; + break; + case SET_NOTARGET://## %t="BOOL_TYPES" # Others won't pick you as enemy + *value = (ent->flags&FL_NOTARGET); + break; + case SET_DONTFIRE://## %t="BOOL_TYPES" # Don't fire your weapon + return 0; + break; + + case SET_LOCKED_ENEMY://## %t="BOOL_TYPES" # Keep current enemy until dead + return 0; + break; + case SET_CROUCHED://## %t="BOOL_TYPES" # Force NPC to crouch + return 0; + break; + case SET_WALKING://## %t="BOOL_TYPES" # Force NPC to move at walkSpeed + return 0; + break; + case SET_RUNNING://## %t="BOOL_TYPES" # Force NPC to move at runSpeed + return 0; + break; + case SET_CHASE_ENEMIES://## %t="BOOL_TYPES" # NPC will chase after enemies + return 0; + break; + case SET_LOOK_FOR_ENEMIES://## %t="BOOL_TYPES" # NPC will be on the lookout for enemies + return 0; + break; + case SET_FACE_MOVE_DIR://## %t="BOOL_TYPES" # NPC will face in the direction it's moving + return 0; + break; + case SET_FORCED_MARCH://## %t="BOOL_TYPES" # Force NPC to move at runSpeed + return 0; + break; + case SET_UNDYING://## %t="BOOL_TYPES" # Can take damage down to 1 but not die + return 0; + break; + case SET_NOAVOID://## %t="BOOL_TYPES" # Will not avoid other NPCs or architecture + return 0; + break; + + case SET_SOLID://## %t="BOOL_TYPES" # Make yourself notsolid or solid + *value = ent->r.contents; + break; + case SET_PLAYER_USABLE://## %t="BOOL_TYPES" # Can be activateby the player's "use" button + *value = (ent->r.svFlags&SVF_PLAYER_USABLE); + break; + case SET_LOOP_ANIM://## %t="BOOL_TYPES" # For non-NPCs: loop your animation sequence + return 0; + break; + case SET_INTERFACE://## %t="BOOL_TYPES" # Player interface on/off + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_INTERFACE not implemented\n" ); + return 0; + break; + case SET_SHIELDS://## %t="BOOL_TYPES" # NPC has no shields (Borg do not adapt) + return 0; + break; + case SET_INVISIBLE://## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + *value = (ent->s.eFlags&EF_NODRAW); + break; + case SET_VAMPIRE://## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + return 0; + break; + case SET_FORCE_INVINCIBLE://## %t="BOOL_TYPES" # Makes an NPC not solid and not visible + return 0; + break; + case SET_GREET_ALLIES://## %t="BOOL_TYPES" # Makes an NPC greet teammates + return 0; + break; + case SET_VIDEO_FADE_IN://## %t="BOOL_TYPES" # Makes video playback fade in + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_VIDEO_FADE_IN not implemented\n" ); + return 0; + break; + case SET_VIDEO_FADE_OUT://## %t="BOOL_TYPES" # Makes video playback fade out + G_DebugPrint( WL_WARNING, "Q3_GetFloat: SET_VIDEO_FADE_OUT not implemented\n" ); + return 0; + break; + case SET_PLAYER_LOCKED://## %t="BOOL_TYPES" # Makes it so player cannot move + return 0; + break; + case SET_LOCK_PLAYER_WEAPONS://## %t="BOOL_TYPES" # Makes it so player cannot switch weapons + return 0; + break; + case SET_NO_IMPACT_DAMAGE://## %t="BOOL_TYPES" # Makes it so player cannot switch weapons + return 0; + break; + case SET_NO_KNOCKBACK://## %t="BOOL_TYPES" # Stops this ent from taking knockback from weapons + *value = (ent->flags&FL_NO_KNOCKBACK); + break; + case SET_ALT_FIRE://## %t="BOOL_TYPES" # Force NPC to use altfire when shooting + return 0; + break; + case SET_NO_RESPONSE://## %t="BOOL_TYPES" # NPCs will do generic responses when this is on (usescripts override generic responses as well) + return 0; + break; + case SET_INVINCIBLE://## %t="BOOL_TYPES" # Completely unkillable + *value = (ent->flags&FL_GODMODE); + break; + case SET_MISSIONSTATUSACTIVE: //# Turns on Mission Status Screen + return 0; + break; + case SET_NO_COMBAT_TALK://## %t="BOOL_TYPES" # NPCs will not do their combat talking noises when this is on + return 0; + break; + case SET_NO_ALERT_TALK://## %t="BOOL_TYPES" # NPCs will not do their combat talking noises when this is on + return 0; + break; + case SET_USE_CP_NEAREST://## %t="BOOL_TYPES" # NPCs will use their closest combat points, not try and find ones next to the player, or flank player + return 0; + break; + case SET_DISMEMBERABLE://## %t="BOOL_TYPES" # NPC will not be affected by force powers + return 0; + break; + case SET_NO_FORCE: + return 0; + break; + case SET_NO_ACROBATICS: + return 0; + break; + case SET_USE_SUBTITLES: + return 0; + break; + case SET_NO_FALLTODEATH://## %t="BOOL_TYPES" # NPC will not be affected by force powers + return 0; + break; + case SET_MORELIGHT://## %t="BOOL_TYPES" # NPCs will use their closest combat points, not try and find ones next to the player, or flank player + return 0; + break; + case SET_TREASONED://## %t="BOOL_TYPES" # Player has turned on his own- scripts will stop: NPCs will turn on him and level changes load the brig + return 0; + break; + case SET_DISABLE_SHADER_ANIM: //## %t="BOOL_TYPES" # Shaders won't animate + return 0; + break; + case SET_SHADER_ANIM: //## %t="BOOL_TYPES" # Shader will be under frame control + return 0; + break; + + default: + if ( trap_ICARUS_VariableDeclared( name ) != VTYPE_FLOAT ) + return 0; + + return trap_ICARUS_GetFloatVariable( name, value ); + } + + return 1; +} + + +/* +============ +Q3_GetVector + Description : + Return type : int + Argument : int entID + Argument : int type + Argument : const char *name + Argument : vec3_t value +============ +*/ +int Q3_GetVector( int entID, int type, const char *name, vec3_t value ) +{ + gentity_t *ent = &g_entities[entID]; + int toGet = 0; + if ( !ent ) + { + return 0; + } + + toGet = GetIDForString( setTable, name ); //FIXME: May want to make a "getTable" as well + //FIXME: I'm getting really sick of these huge switch statements! + + //NOTENOTE: return true if the value was correctly obtained + switch ( toGet ) + { + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + sscanf( ent->parms->parm[toGet - SET_PARM1], "%f %f %f", &value[0], &value[1], &value[2] ); + break; + + case SET_ORIGIN: + VectorCopy(ent->r.currentOrigin, value); + break; + + case SET_ANGLES: + VectorCopy(ent->r.currentAngles, value); + break; + + case SET_TELEPORT_DEST://## %v="0.0 0.0 0.0" # Set origin here as soon as the area is clear + G_DebugPrint( WL_WARNING, "Q3_GetVector: SET_TELEPORT_DEST not implemented\n" ); + return 0; + break; + + default: + + if ( trap_ICARUS_VariableDeclared( name ) != VTYPE_VECTOR ) + return 0; + + return trap_ICARUS_GetVectorVariable( name, value ); + } + + return 1; +} + +/* +============ +Q3_GetString + Description : + Return type : int + Argument : int entID + Argument : int type + Argument : const char *name + Argument : char **value +============ +*/ +int Q3_GetString( int entID, int type, const char *name, char **value ) +{ + gentity_t *ent = &g_entities[entID]; + int toGet = 0; + if ( !ent ) + { + return 0; + } + + toGet = GetIDForString( setTable, name ); //FIXME: May want to make a "getTable" as well + + switch ( toGet ) + { + case SET_ANIM_BOTH: + *value = (char *) Q3_GetAnimBoth( ent ); + + if ( !value || !value[0] ) + return 0; + + break; + + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + if ( ent->parms ) + { + *value = (char *) ent->parms->parm[toGet - SET_PARM1]; + } + else + { + G_DebugPrint( WL_WARNING, "Q3_GetString: invalid ent %s has no parms!\n", ent->targetname ); + return 0; + } + break; + + case SET_TARGET: + *value = (char *) ent->target; + break; + + case SET_LOCATION: + return 0; + break; + + //# #sep Scripts and other file paths + case SET_SPAWNSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when spawned //0 - do not change these, these are equal to BSET_SPAWN, etc + *value = ent->behaviorSet[BSET_SPAWN]; + break; + case SET_USESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when used + *value = ent->behaviorSet[BSET_USE]; + break; + case SET_AWAKESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when startled + *value = ent->behaviorSet[BSET_AWAKE]; + break; + case SET_ANGERSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script run when find an enemy for the first time + *value = ent->behaviorSet[BSET_ANGER]; + break; + case SET_ATTACKSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when you shoot + *value = ent->behaviorSet[BSET_ATTACK]; + break; + case SET_VICTORYSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when killed someone + *value = ent->behaviorSet[BSET_VICTORY]; + break; + case SET_LOSTENEMYSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when you can't find your enemy + *value = ent->behaviorSet[BSET_LOSTENEMY]; + break; + case SET_PAINSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when hit + *value = ent->behaviorSet[BSET_PAIN]; + break; + case SET_FLEESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when hit and low health + *value = ent->behaviorSet[BSET_FLEE]; + break; + case SET_DEATHSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when killed + *value = ent->behaviorSet[BSET_DEATH]; + break; + case SET_DELAYEDSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run after a delay + *value = ent->behaviorSet[BSET_DELAYED]; + break; + case SET_BLOCKEDSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when blocked by teammate + *value = ent->behaviorSet[BSET_BLOCKED]; + break; + case SET_FFIRESCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when player has shot own team repeatedly + *value = ent->behaviorSet[BSET_FFIRE]; + break; + case SET_FFDEATHSCRIPT://## %s="NULL" !!"W:\game\base\scripts\!!#*.txt" # Script to run when player kills a teammate + *value = ent->behaviorSet[BSET_FFDEATH]; + break; + + //# #sep Standard strings + case SET_ENEMY://## %s="NULL" # Set enemy by targetname + return 0; + break; + case SET_LEADER://## %s="NULL" # Set for BS_FOLLOW_LEADER + return 0; + break; + case SET_CAPTURE://## %s="NULL" # Set captureGoal by targetname + return 0; + break; + + case SET_TARGETNAME://## %s="NULL" # Set/change your targetname + *value = ent->targetname; + break; + case SET_PAINTARGET://## %s="NULL" # Set/change what to use when hit + return 0; + break; + case SET_CAMERA_GROUP://## %s="NULL" # all ents with this cameraGroup will be focused on + return 0; + break; + case SET_CAMERA_GROUP_TAG://## %s="NULL" # all ents with this cameraGroup will be focused on + return 0; + break; + case SET_LOOK_TARGET://## %s="NULL" # object for NPC to look at + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_LOOK_TARGET, NOT SUPPORTED IN MULTIPLAYER\n" ); + break; + case SET_TARGET2://## %s="NULL" # Set/change your target2: on NPC's: this fires when they're knocked out by the red hypo + return 0; + break; + + case SET_REMOVE_TARGET://## %s="NULL" # Target that is fired when someone completes the BS_REMOVE behaviorState + return 0; + break; + case SET_WEAPON: + return 0; + break; + + case SET_ITEM: + return 0; + break; + case SET_MUSIC_STATE: + return 0; + break; + //The below cannot be gotten + case SET_NAVGOAL://## %s="NULL" # *Move to this navgoal then continue script + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_NAVGOAL not implemented\n" ); + return 0; + break; + case SET_VIEWTARGET://## %s="NULL" # Set angles toward ent by targetname + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_VIEWTARGET not implemented\n" ); + return 0; + break; + case SET_WATCHTARGET://## %s="NULL" # Set angles toward ent by targetname + return 0; + break; + case SET_VIEWENTITY: + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_VIEWENTITY not implemented\n" ); + return 0; + break; + case SET_CAPTIONTEXTCOLOR: //## %s="" # Color of text RED:WHITE:BLUE: YELLOW + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_CAPTIONTEXTCOLOR not implemented\n" ); + return 0; + break; + case SET_CENTERTEXTCOLOR: //## %s="" # Color of text RED:WHITE:BLUE: YELLOW + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_CENTERTEXTCOLOR not implemented\n" ); + return 0; + break; + case SET_SCROLLTEXTCOLOR: //## %s="" # Color of text RED:WHITE:BLUE: YELLOW + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_SCROLLTEXTCOLOR not implemented\n" ); + return 0; + break; + case SET_COPY_ORIGIN://## %s="targetname" # Copy the origin of the ent with targetname to your origin + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_COPY_ORIGIN not implemented\n" ); + return 0; + break; + case SET_DEFEND_TARGET://## %s="targetname" # This NPC will attack the target NPC's enemies + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_COPY_ORIGIN not implemented\n" ); + return 0; + break; + case SET_VIDEO_PLAY://## %s="filename" !!"W:\game\base\video\!!#*.roq" # Play a Video (inGame) + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_VIDEO_PLAY not implemented\n" ); + return 0; + break; + case SET_LOADGAME://## %s="exitholodeck" # Load the savegame that was auto-saved when you started the holodeck + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_LOADGAME not implemented\n" ); + return 0; + break; + case SET_LOCKYAW://## %s="off" # Lock legs to a certain yaw angle (or "off" or "auto" uses current) + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_LOCKYAW not implemented\n" ); + return 0; + break; + case SET_SCROLLTEXT: //## %s="" # key of text string to print + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_SCROLLTEXT not implemented\n" ); + return 0; + break; + case SET_LCARSTEXT: //## %s="" # key of text string to print in LCARS frame + G_DebugPrint( WL_WARNING, "Q3_GetString: SET_LCARSTEXT not implemented\n" ); + return 0; + break; + + case SET_FULLNAME://## %s="NULL" # Set/change your targetname + *value = ent->fullName; + break; + default: + + if ( trap_ICARUS_VariableDeclared( name ) != VTYPE_STRING ) + return 0; + + return trap_ICARUS_GetStringVariable( name, (const char *) *value ); + } + + return 1; +} + +/* +============ +MoveOwner + Description : + Return type : void + Argument : sharedEntity_t *self +============ +*/ +qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ); +void MoveOwner( gentity_t *self ) +{ + gentity_t *owner = &g_entities[self->r.ownerNum]; + + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + + if ( !owner || !owner->inuse ) + { + return; + } + + if ( SpotWouldTelefrag2( owner, self->r.currentOrigin ) ) + { + self->think = MoveOwner; + } + else + { + G_SetOrigin( owner, self->r.currentOrigin ); + trap_ICARUS_TaskIDComplete( owner, TID_MOVE_NAV ); + } +} + +/* +============= +Q3_SetTeleportDest + +Copies passed origin to ent running script once there is nothing there blocking the spot +============= +*/ +static qboolean Q3_SetTeleportDest( int entID, vec3_t org ) +{ + gentity_t *teleEnt = &g_entities[entID]; + + if ( teleEnt ) + { + if ( SpotWouldTelefrag2( teleEnt, org ) ) + { + gentity_t *teleporter = G_Spawn(); + + G_SetOrigin( teleporter, org ); + teleporter->r.ownerNum = teleEnt->s.number; + + teleporter->think = MoveOwner; + teleporter->nextthink = level.time + FRAMETIME; + + return qfalse; + } + else + { + G_SetOrigin( teleEnt, org ); + } + } + + return qtrue; +} + +/* +============= +Q3_SetOrigin + +Sets the origin of an entity directly +============= +*/ +static void Q3_SetOrigin( int entID, vec3_t origin ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetOrigin: bad ent %d\n", entID); + return; + } + + trap_UnlinkEntity (ent); + + if(ent->client) + { + VectorCopy(origin, ent->client->ps.origin); + VectorCopy(origin, ent->r.currentOrigin); + ent->client->ps.origin[2] += 1; + + VectorClear (ent->client->ps.velocity); + ent->client->ps.pm_time = 160; // hold time + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + + ent->client->ps.eFlags ^= EF_TELEPORT_BIT; + +// G_KillBox (ent); + } + else + { + G_SetOrigin( ent, origin ); + } + + trap_LinkEntity( ent ); +} + +/* +============= +Q3_SetCopyOrigin + +Copies origin of found ent into ent running script +=============` +*/ +static void Q3_SetCopyOrigin( int entID, const char *name ) +{ + gentity_t *found = G_Find( NULL, FOFS(targetname), (char *) name); + + if(found) + { + Q3_SetOrigin( entID, found->r.currentOrigin ); + SetClientViewAngle( &g_entities[entID], found->s.angles ); + } + else + { + G_DebugPrint( WL_WARNING, "Q3_SetCopyOrigin: ent %s not found!\n", name); + } +} + +/* +============= +Q3_SetVelocity + +Set the velocity of an entity directly +============= +*/ +static void Q3_SetVelocity( int entID, int axis, float speed ) +{ + gentity_t *found = &g_entities[entID]; + //FIXME: Not supported + if(!found) + { + G_DebugPrint( WL_WARNING, "Q3_SetVelocity invalid entID %d\n", entID); + return; + } + + if(!found->client) + { + G_DebugPrint( WL_WARNING, "Q3_SetVelocity: not a client %d\n", entID); + return; + } + + //FIXME: add or set? + found->client->ps.velocity[axis] += speed; + + found->client->ps.pm_time = 500; + found->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; +} + +/* +============= +Q3_SetAngles + +Sets the angles of an entity directly +============= +*/ +static void Q3_SetAngles( int entID, vec3_t angles ) +{ + gentity_t *ent = &g_entities[entID]; + + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetAngles: bad ent %d\n", entID); + return; + } + + if (ent->client) + { + SetClientViewAngle( ent, angles ); + } + else + { + VectorCopy( angles, ent->s.angles ); + } + trap_LinkEntity( ent ); +} + +/* +============= +Q3_Lerp2Origin + +Lerps the origin to the destination value +============= +*/ +void Q3_Lerp2Origin( int taskID, int entID, vec3_t origin, float duration ) +{ + gentity_t *ent = &g_entities[entID]; + moverState_t moverState; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_Lerp2Origin: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_Lerp2Origin: ent %d is NOT a mover!\n", entID); + return; + } + + if ( ent->s.eType != ET_MOVER ) + { + ent->s.eType = ET_MOVER; + } + + moverState = ent->moverState; + + if ( moverState == MOVER_POS1 || moverState == MOVER_2TO1 ) + { + VectorCopy( ent->r.currentOrigin, ent->pos1 ); + VectorCopy( origin, ent->pos2 ); + + moverState = MOVER_1TO2; + } + else if ( moverState == MOVER_POS2 || moverState == MOVER_1TO2 ) + { + VectorCopy( ent->r.currentOrigin, ent->pos2 ); + VectorCopy( origin, ent->pos1 ); + + moverState = MOVER_2TO1; + } + + InitMoverTrData( ent ); //FIXME: This will probably break normal things that are being moved... + + ent->s.pos.trDuration = duration; + + // start it going + MatchTeam( ent, moverState, level.time ); + //SetMoverState( ent, moverState, level.time ); + + ent->reached = moverCallback; + if ( ent->damage ) + { + ent->blocked = Blocked_Mover; + } + if ( taskID != -1 ) + { + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + } + // starting sound + G_PlayDoorLoopSound( ent );//start looping sound + G_PlayDoorSound( ent, BMS_START ); //play start sound + + trap_LinkEntity( ent ); +} + +static void Q3_SetOriginOffset( int entID, int axis, float offset ) +{ + gentity_t *ent = &g_entities[entID]; + vec3_t origin; + float duration; + + if(!ent) + { + G_DebugPrint( WL_WARNING, "Q3_SetOriginOffset: invalid entID %d\n", entID); + return; + } + + if ( ent->client || Q_stricmp(ent->classname, "target_scriptrunner") == 0 ) + { + G_DebugPrint( WL_ERROR, "Q3_SetOriginOffset: ent %d is NOT a mover!\n", entID); + return; + } + + VectorCopy( ent->s.origin, origin ); + origin[axis] += offset; + duration = 0; + if ( ent->speed ) + { + duration = fabs(offset)/fabs(ent->speed)*1000.0f; + } + Q3_Lerp2Origin( -1, entID, origin, duration ); +} + +/* +============= +Q3_SetEnemy + +Sets the enemy of an entity +============= +*/ +static void Q3_SetEnemy( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetEnemy: invalid entID %d\n", entID); + return; + } + + if( !Q_stricmp("NONE", name) || !Q_stricmp("NULL", name)) + { + if(ent->NPC) + { + G_ClearEnemy(ent); + } + else + { + ent->enemy = NULL; + } + } + else + { + gentity_t *enemy = G_Find( NULL, FOFS(targetname), (char *) name); + + if(enemy == NULL) + { + G_DebugPrint( WL_ERROR, "Q3_SetEnemy: no such enemy: '%s'\n", name ); + return; + } + /*else if(enemy->health <= 0) + { + //G_DebugPrint( WL_ERROR, "Q3_SetEnemy: ERROR - desired enemy has health %d\n", enemy->health ); + return; + }*/ + else + { + if(ent->NPC) + { + G_SetEnemy( ent, enemy ); + ent->cantHitEnemyCounter = 0; + } + else + { + G_SetEnemy(ent, enemy); + } + } + } +} + + +/* +============= +Q3_SetLeader + +Sets the leader of an NPC +============= +*/ +static void Q3_SetLeader( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetLeader: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetLeader: ent %d is NOT a player or NPC!\n", entID); + return; + } + + if( !Q_stricmp("NONE", name) || !Q_stricmp("NULL", name)) + { + ent->client->leader = NULL; + } + else + { + gentity_t *leader = G_Find( NULL, FOFS(targetname), (char *) name); + + if(leader == NULL) + { + //G_DebugPrint( WL_ERROR,"Q3_SetEnemy: unable to locate enemy: '%s'\n", name ); + return; + } + else if(leader->health <= 0) + { + //G_DebugPrint( WL_ERROR,"Q3_SetEnemy: ERROR - desired enemy has health %d\n", enemy->health ); + return; + } + else + { + ent->client->leader = leader; + } + } +} + +/* +============= +Q3_SetNavGoal + +Sets the navigational goal of an entity +============= +*/ +static qboolean Q3_SetNavGoal( int entID, const char *name ) +{ + gentity_t *ent = &g_entities[ entID ]; + vec3_t goalPos; + + if ( !ent->health ) + { + G_DebugPrint( WL_ERROR, "Q3_SetNavGoal: tried to set a navgoal (\"%s\") on a corpse! \"%s\"\n", name, ent->script_targetname ); + return qfalse; + } + if ( !ent->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetNavGoal: tried to set a navgoal (\"%s\") on a non-NPC: \"%s\"\n", name, ent->script_targetname ); + return qfalse; + } + if ( !ent->NPC->tempGoal ) + { + G_DebugPrint( WL_ERROR, "Q3_SetNavGoal: tried to set a navgoal (\"%s\") on a dead NPC: \"%s\"\n", name, ent->script_targetname ); + return qfalse; + } + if ( !ent->NPC->tempGoal->inuse ) + { + G_DebugPrint( WL_ERROR, "Q3_SetNavGoal: NPC's (\"%s\") navgoal is freed: \"%s\"\n", name, ent->script_targetname ); + return qfalse; + } + if( Q_stricmp( "null", name) == 0 + || Q_stricmp( "NULL", name) == 0 ) + { + ent->NPC->goalEntity = NULL; + trap_ICARUS_TaskIDComplete( ent, TID_MOVE_NAV ); + return qfalse; + } + else + { + //Get the position of the goal + if ( TAG_GetOrigin2( NULL, name, goalPos ) == qfalse ) + { + gentity_t *targ = G_Find(NULL, FOFS(targetname), (char*)name); + if ( !targ ) + { + G_DebugPrint( WL_ERROR, "Q3_SetNavGoal: can't find NAVGOAL \"%s\"\n", name ); + return qfalse; + } + else + { + ent->NPC->goalEntity = targ; + ent->NPC->goalRadius = sqrt(ent->r.maxs[0]+ent->r.maxs[0]) + sqrt(targ->r.maxs[0]+targ->r.maxs[0]); + ent->NPC->aiFlags &= ~NPCAI_TOUCHED_GOAL; + } + } + else + { + int goalRadius = TAG_GetRadius( NULL, name ); + NPC_SetMoveGoal( ent, goalPos, goalRadius, qtrue, -1, NULL ); + //We know we want to clear the lastWaypoint here + ent->NPC->goalEntity->lastWaypoint = WAYPOINT_NONE; + ent->NPC->aiFlags &= ~NPCAI_TOUCHED_GOAL; + #ifdef _DEBUG + //this is *only* for debugging navigation + ent->NPC->tempGoal->target = G_NewString( name ); + #endif// _DEBUG + return qtrue; + } + } + return qfalse; +} +/* +============ +SetLowerAnim + Description : + Return type : static void + Argument : int entID + Argument : int animID +============ +*/ +static void SetLowerAnim( int entID, int animID) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "SetLowerAnim: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "SetLowerAnim: ent %d is NOT a player or NPC!\n", entID); + return; + } + + G_SetAnim(ent,NULL,SETANIM_LEGS,animID,SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE,0); +} + +/* +============ +SetUpperAnim + Description : + Return type : static void + Argument : int entID + Argument : int animID +============ +*/ +static void SetUpperAnim ( int entID, int animID) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "SetUpperAnim: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "SetLowerAnim: ent %d is NOT a player or NPC!\n", entID); + return; + } + + G_SetAnim(ent,NULL,SETANIM_TORSO,animID,SETANIM_FLAG_RESTART|SETANIM_FLAG_HOLD|SETANIM_FLAG_OVERRIDE,0); +} + +/* +============= +Q3_SetAnimUpper + +Sets the upper animation of an entity +============= +*/ +static qboolean Q3_SetAnimUpper( int entID, const char *anim_name ) +{ + int animID = 0; + + animID = GetIDForString( animTable, anim_name ); + + if( animID == -1 ) + { + G_DebugPrint( WL_WARNING, "Q3_SetAnimUpper: unknown animation sequence '%s'\n", anim_name ); + return qfalse; + } + + /* + if ( !PM_HasAnimation( SV_GentityNum(entID), animID ) ) + { + return qfalse; + } + */ + + SetUpperAnim( entID, animID ); + return qtrue; +} + +/* +============= +Q3_SetAnimLower + +Sets the lower animation of an entity +============= +*/ +static qboolean Q3_SetAnimLower( int entID, const char *anim_name ) +{ + int animID = 0; + + //FIXME: Setting duck anim does not actually duck! + + animID = GetIDForString( animTable, anim_name ); + + if( animID == -1 ) + { + G_DebugPrint( WL_WARNING, "Q3_SetAnimLower: unknown animation sequence '%s'\n", anim_name ); + return qfalse; + } + + /* + if ( !PM_HasAnimation( SV_GentityNum(entID), animID ) ) + { + return qfalse; + } + */ + + SetLowerAnim( entID, animID ); + return qtrue; +} + +/* +============ +Q3_SetAnimHoldTime + Description : + Return type : static void + Argument : int entID + Argument : int int_data + Argument : qboolean lower +============ +*/ +static void Q3_SetAnimHoldTime( int entID, int int_data, qboolean lower ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAnimHoldTime is not currently supported in MP\n"); + /* + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetAnimHoldTime: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetAnimHoldTime: ent %d is NOT a player or NPC!\n", entID); + return; + } + + if(lower) + { + PM_SetLegsAnimTimer( ent, &ent->client->ps.legsAnimTimer, int_data ); + } + else + { + PM_SetTorsoAnimTimer( ent, &ent->client->ps.torsoAnimTimer, int_data ); + } + */ +} + +/* +============ +Q3_SetHealth + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetHealth( int entID, int data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetHealth: invalid entID %d\n", entID); + return; + } + + if ( data < 0 ) + { + data = 0; + } + + ent->health = data; + + if(!ent->client) + { + return; + } + + ent->client->ps.stats[STAT_HEALTH] = data; + + if ( ent->client->ps.stats[STAT_HEALTH] > ent->client->ps.stats[STAT_MAX_HEALTH] ) + { + ent->health = ent->client->ps.stats[STAT_HEALTH] = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + if ( data == 0 ) + { + ent->health = 1; + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + { //this would be silly + return; + } + + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; + player_die (ent, ent, ent, 100000, MOD_FALLING); + } +} + + +/* +============ +Q3_SetArmor + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetArmor( int entID, int data ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetArmor: invalid entID %d\n", entID); + return; + } + + if(!ent->client) + { + return; + } + + ent->client->ps.stats[STAT_ARMOR] = data; + if ( ent->client->ps.stats[STAT_ARMOR] > ent->client->ps.stats[STAT_MAX_HEALTH] ) + { + ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH]; + } +} + + +/* +============ +Q3_SetBState + Description : + Return type : static qboolean + Argument : int entID + Argument : const char *bs_name +FIXME: this should be a general NPC wrapper function + that is called ANY time a bState is changed... +============ +*/ +static qboolean Q3_SetBState( int entID, const char *bs_name ) +{ + gentity_t *ent = &g_entities[entID]; + bState_t bSID; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetBState: invalid entID %d\n", entID); + return qtrue; + } + + if ( !ent->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetBState: '%s' is not an NPC\n", ent->targetname ); + return qtrue;//ok to complete + } + + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + if ( bSID > -1 ) + { + if ( bSID == BS_SEARCH || bSID == BS_WANDER ) + { + //FIXME: Reimplement + + if( ent->waypoint != WAYPOINT_NONE ) + { + NPC_BSSearchStart( ent->waypoint, bSID ); + } + else + { + ent->waypoint = NAV_FindClosestWaypointForEnt( ent, WAYPOINT_NONE ); + + if( ent->waypoint != WAYPOINT_NONE ) + { + NPC_BSSearchStart( ent->waypoint, bSID ); + } + /*else if( ent->lastWaypoint >=0 && ent->lastWaypoint < num_waypoints ) + { + NPC_BSSearchStart( ent->lastWaypoint, bSID ); + } + else if( ent->lastValidWaypoint >=0 && ent->lastValidWaypoint < num_waypoints ) + { + NPC_BSSearchStart( ent->lastValidWaypoint, bSID ); + }*/ + else + { + G_DebugPrint( WL_ERROR, "Q3_SetBState: '%s' is not in a valid waypoint to search from!\n", ent->targetname ); + return qtrue; + } + } + } + + + ent->NPC->tempBehavior = BS_DEFAULT;//need to clear any temp behaviour + if ( ent->NPC->behaviorState == BS_NOCLIP && bSID != BS_NOCLIP ) + {//need to rise up out of the floor after noclipping + ent->r.currentOrigin[2] += 0.125; + G_SetOrigin( ent, ent->r.currentOrigin ); + } + ent->NPC->behaviorState = bSID; + if ( bSID == BS_DEFAULT ) + { + ent->NPC->defaultBehavior = bSID; + } + } + + ent->NPC->aiFlags &= ~NPCAI_TOUCHED_GOAL; + +// if ( bSID == BS_FLY ) +// {//FIXME: need a set bState wrapper +// ent->client->moveType = MT_FLYSWIM; +// } +// else + { + //FIXME: these are presumptions! + //Q3_SetGravity( entID, g_gravity->value ); + //ent->client->moveType = MT_RUNJUMP; + } + + if ( bSID == BS_NOCLIP ) + { + ent->client->noclip = qtrue; + } + else + { + ent->client->noclip = qfalse; + } + +/* + if ( bSID == BS_FACE || bSID == BS_POINT_AND_SHOOT || bSID == BS_FACE_ENEMY ) + { + ent->NPC->aimTime = level.time + 5 * 1000;//try for 5 seconds + return qfalse;//need to wait for task complete message + } +*/ + +// if ( bSID == BS_SNIPER || bSID == BS_ADVANCE_FIGHT ) + if ( bSID == BS_ADVANCE_FIGHT ) + { + return qfalse;//need to wait for task complete message + } + +/* + if ( bSID == BS_SHOOT || bSID == BS_POINT_AND_SHOOT ) + {//Let them shoot right NOW + ent->NPC->shotTime = ent->attackDebounceTime = level.time; + } +*/ + if ( bSID == BS_JUMP ) + { + ent->NPC->jumpState = JS_FACING; + } + + return qtrue;//ok to complete +} + + +/* +============ +Q3_SetTempBState + Description : + Return type : static qboolean + Argument : int entID + Argument : const char *bs_name +============ +*/ +static qboolean Q3_SetTempBState( int entID, const char *bs_name ) +{ + gentity_t *ent = &g_entities[entID]; + bState_t bSID; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetTempBState: invalid entID %d\n", entID); + return qtrue; + } + + if ( !ent->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetTempBState: '%s' is not an NPC\n", ent->targetname ); + return qtrue;//ok to complete + } + + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + if ( bSID > -1 ) + { + ent->NPC->tempBehavior = bSID; + } + +/* + if ( bSID == BS_FACE || bSID == BS_POINT_AND_SHOOT || bSID == BS_FACE_ENEMY ) + { + ent->NPC->aimTime = level.time + 5 * 1000;//try for 5 seconds + return qfalse;//need to wait for task complete message + } +*/ + +/* + if ( bSID == BS_SHOOT || bSID == BS_POINT_AND_SHOOT ) + {//Let them shoot right NOW + ent->NPC->shotTime = ent->attackDebounceTime = level.time; + } +*/ + return qtrue;//ok to complete +} + + +/* +============ +Q3_SetDefaultBState + Description : + Return type : static void + Argument : int entID + Argument : const char *bs_name +============ +*/ +static void Q3_SetDefaultBState( int entID, const char *bs_name ) +{ + gentity_t *ent = &g_entities[entID]; + bState_t bSID; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetDefaultBState: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetDefaultBState: '%s' is not an NPC\n", ent->targetname ); + return; + } + + bSID = (bState_t)(GetIDForString( BSTable, bs_name )); + if ( bSID > -1 ) + { + ent->NPC->defaultBehavior = bSID; + } +} + + +/* +============ +Q3_SetDPitch + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetDPitch( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDPitch: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetDYaw + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetDYaw( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDYaw: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetShootDist + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetShootDist( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetShootDist: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetVisrange + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetVisrange( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetVisrange: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetEarshot + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetEarshot( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetEarshot: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetVigilance + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetVigilance( int entID, float data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetVigilance: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetVFOV + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetVFOV( int entID, int data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetVFOV: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetHFOV + Description : + Return type : static void + Argument : int entID + Argument : int data +============ +*/ +static void Q3_SetHFOV( int entID, int data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetHFOV: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetWidth + Description : + Return type : static void + Argument : int entID + Argument : float data +============ +*/ +static void Q3_SetWidth( int entID, int data ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetWidth: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetTimeScale + Description : + Return type : static void + Argument : int entID + Argument : const char *data +============ +*/ +static void Q3_SetTimeScale( int entID, const char *data ) +{ + trap_Cvar_Set("timescale", data); +} + + +/* +============ +Q3_SetInvisible + Description : + Return type : static void + Argument : int entID + Argument : qboolean invisible +============ +*/ +static void Q3_SetInvisible( int entID, qboolean invisible ) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetInvisible: invalid entID %d\n", entID); + return; + } + + if ( invisible ) + { + self->s.eFlags |= EF_NODRAW; + if ( self->client ) + { + self->client->ps.eFlags |= EF_NODRAW; + } + self->r.contents = 0; + } + else + { + self->s.eFlags &= ~EF_NODRAW; + if ( self->client ) + { + self->client->ps.eFlags &= ~EF_NODRAW; + } + } +} + +/* +============ +Q3_SetVampire + Description : + Return type : static void + Argument : int entID + Argument : qboolean vampire +============ +*/ +static void Q3_SetVampire( int entID, qboolean vampire ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetVampire: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_SetGreetAllies + Description : + Return type : static void + Argument : int entID + Argument : qboolean greet +============ +*/ +static void Q3_SetGreetAllies( int entID, qboolean greet ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetGreetAllies: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetViewTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *name +============ +*/ +static void Q3_SetViewTarget (int entID, const char *name) +{ + G_DebugPrint( WL_WARNING, "Q3_SetViewTarget: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetWatchTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *name +============ +*/ +static void Q3_SetWatchTarget (int entID, const char *name) +{ + G_DebugPrint( WL_WARNING, "Q3_SetWatchTarget: NOT SUPPORTED IN MP\n"); + return; +} + +void Q3_SetLoopSound(int entID, const char *name) +{ + sfxHandle_t index; + gentity_t *self = &g_entities[entID]; + + if ( Q_stricmp( "NULL", name ) == 0 || Q_stricmp( "NONE", name )==0) + { + self->s.loopSound = 0; + self->s.loopIsSoundset = qfalse; + return; + } + + index = G_SoundIndex( (char*)name ); + + if (index) + { + self->s.loopSound = index; + self->s.loopIsSoundset = qfalse; + } + else + { + G_DebugPrint( WL_WARNING, "Q3_SetLoopSound: can't find sound file: '%s'\n", name ); + } +} + +void Q3_SetICARUSFreeze( int entID, const char *name, qboolean freeze ) +{ + gentity_t *self = G_Find( NULL, FOFS(targetname), name ); + if ( !self ) + {//hmm, targetname failed, try script_targetname? + self = G_Find( NULL, FOFS(script_targetname), name ); + } + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetICARUSFreeze: invalid ent %s\n", name); + return; + } + + if ( freeze ) + { + self->r.svFlags |= SVF_ICARUS_FREEZE; + } + else + { + self->r.svFlags &= ~SVF_ICARUS_FREEZE; + } +} + +/* +============ +Q3_SetViewEntity + Description : + Return type : static void + Argument : int entID + Argument : const char *name +============ +*/ +void Q3_SetViewEntity(int entID, const char *name) +{ + G_DebugPrint( WL_WARNING, "Q3_SetViewEntity currently unsupported in MP, ask if you need it.\n"); +} + +/* +============ +Q3_SetWeapon + Description : + Return type : static void + Argument : int entID + Argument : const char *wp_name +============ +*/ +extern void ChangeWeapon( gentity_t *ent, int newWeapon ); +static void Q3_SetWeapon (int entID, const char *wp_name) +{ + gentity_t *ent = &g_entities[entID]; + int wp = GetIDForString( WPTable, wp_name ); + + ent->client->ps.stats[STAT_WEAPONS] = (1<NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetWalkSpeed: '%s' is not an NPC!\n", self->targetname ); + return; + } + + if(int_data == 0) + { + self->NPC->stats.walkSpeed = self->client->ps.speed = 1; + } + + self->NPC->stats.walkSpeed = self->client->ps.speed = int_data; +} + + +/* +============ +Q3_SetRunSpeed + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetRunSpeed (int entID, int int_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetRunSpeed: invalid entID %d\n", entID); + return; + } + + if ( !self->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetRunSpeed: '%s' is not an NPC!\n", self->targetname ); + return; + } + + if(int_data == 0) + { + self->NPC->stats.runSpeed = self->client->ps.speed = 1; + } + + self->NPC->stats.runSpeed = self->client->ps.speed = int_data; +} + + +/* +============ +Q3_SetYawSpeed + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetYawSpeed (int entID, float float_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetYawSpeed: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetAggression + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetAggression(int entID, int int_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAggression: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetAim + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetAim(int entID, int int_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAim: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetFriction + Description : + Return type : static void + Argument : int entID + Argument : int int_data +============ +*/ +static void Q3_SetFriction(int entID, int int_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetFriction: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetFriction: '%s' is not an NPC/player!\n", self->targetname ); + return; + } + + G_DebugPrint( WL_WARNING, "Q3_SetFriction currently unsupported in MP\n"); +// self->client->ps.friction = int_data; +} + + +/* +============ +Q3_SetGravity + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetGravity(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetGravity: invalid entID %d\n", entID); + return; + } + + if ( !self->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetGravity: '%s' is not an NPC/player!\n", self->targetname ); + return; + } + + //FIXME: what if we want to return them to normal global gravity? + if ( self->NPC ) + { + self->NPC->aiFlags |= NPCAI_CUSTOM_GRAVITY; + } + self->client->ps.gravity = float_data; +} + + +/* +============ +Q3_SetWait + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetWait(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetWait: invalid entID %d\n", entID); + return; + } + + self->wait = float_data; +} + + +static void Q3_SetShotSpacing(int entID, int int_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetShotSpacing: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetFollowDist + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetFollowDist(int entID, float float_data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetFollowDist: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetScale + Description : + Return type : static void + Argument : int entID + Argument : float float_data +============ +*/ +static void Q3_SetScale(int entID, float float_data) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetScale: invalid entID %d\n", entID); + return; + } + + if (self->client) + { + self->client->ps.iModelScale = float_data*100.0f; + } + else + { + self->s.iModelScale = float_data*100.0f; + } +} + +/* +============ +Q3_GameSideCheckStringCounterIncrement + Description : + Return type : static float + Argument : const char *string +============ +*/ +static float Q3_GameSideCheckStringCounterIncrement( const char *string ) +{ + char *numString; + float val = 0.0f; + + if ( string[0] == '+' ) + {//We want to increment whatever the value is by whatever follows the + + if ( string[1] ) + { + numString = (char *)&string[1]; + val = atof( numString ); + } + } + else if ( string[0] == '-' ) + {//we want to decrement + if ( string[1] ) + { + numString = (char *)&string[1]; + val = atof( numString ) * -1; + } + } + + return val; +} + +/* +============ +Q3_SetCount + Description : + Return type : static void + Argument : int entID + Argument : const char *data +============ +*/ +static void Q3_SetCount(int entID, const char *data) +{ + gentity_t *self = &g_entities[entID]; + float val = 0.0f; + + //FIXME: use FOFS() stuff here to make a generic entity field setting? + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetCount: invalid entID %d\n", entID); + return; + } + + if ( (val = Q3_GameSideCheckStringCounterIncrement( data )) ) + { + self->count += (int)(val); + } + else + { + self->count = atoi((char *) data); + } +} + + +/* +============ +Q3_SetTargetName + Description : + Return type : static void + Argument : int entID + Argument : const char *targetname +============ +*/ +static void Q3_SetTargetName (int entID, const char *targetname) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetTargetName: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)targetname))) + { + self->targetname = NULL; + } + else + { + self->targetname = G_NewString( targetname ); + } +} + + +/* +============ +Q3_SetTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *target +============ +*/ +static void Q3_SetTarget (int entID, const char *target) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetTarget: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)target))) + { + self->target = NULL; + } + else + { + self->target = G_NewString( target ); + } +} + +/* +============ +Q3_SetTarget2 + Description : + Return type : static void + Argument : int entID + Argument : const char *target +============ +*/ +static void Q3_SetTarget2 (int entID, const char *target2) +{ + G_DebugPrint( WL_WARNING, "Q3_SetTarget2 does not exist in MP\n"); + /* + sharedEntity_t *self = SV_GentityNum(entID); + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetTarget2: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)target2))) + { + self->target2 = NULL; + } + else + { + self->target2 = G_NewString( target2 ); + } + */ +} +/* +============ +Q3_SetRemoveTarget + Description : + Return type : static void + Argument : int entID + Argument : const char *target +============ +*/ +static void Q3_SetRemoveTarget (int entID, const char *target) +{ + G_DebugPrint( WL_WARNING, "Q3_SetRemoveTarget: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetPainTarget + Description : + Return type : void + Argument : int entID + Argument : const char *targetname +============ +*/ +static void Q3_SetPainTarget (int entID, const char *targetname) +{ + G_DebugPrint( WL_WARNING, "Q3_SetPainTarget: NOT SUPPORTED IN MP\n"); + /* + sharedEntity_t *self = SV_GentityNum(entID); + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetPainTarget: invalid entID %d\n", entID); + return; + } + + if(Q_stricmp("NULL", ((char *)targetname)) == 0) + { + self->paintarget = NULL; + } + else + { + self->paintarget = G_NewString((char *)targetname); + } + */ +} + +/* +============ +Q3_SetFullName + Description : + Return type : static void + Argument : int entID + Argument : const char *fullName +============ +*/ +static void Q3_SetFullName (int entID, const char *fullName) +{ + gentity_t *self = &g_entities[entID]; + + if ( !self ) + { + G_DebugPrint( WL_WARNING, "Q3_SetFullName: invalid entID %d\n", entID); + return; + } + + if(!Q_stricmp("NULL", ((char *)fullName))) + { + self->fullName = NULL; + } + else + { + self->fullName = G_NewString( fullName ); + } +} + +static void Q3_SetMusicState( const char *dms ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetMusicState: NOT SUPPORTED IN MP\n"); + return; +} + +static void Q3_SetForcePowerLevel ( int entID, int forcePower, int forceLevel ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetForcePowerLevel: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_SetParm + Description : + Return type : void + Argument : int entID + Argument : int parmNum + Argument : const char *parmValue +============ +*/ +void Q3_SetParm (int entID, int parmNum, const char *parmValue) +{ + gentity_t *ent = &g_entities[entID]; + float val; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetParm: invalid entID %d\n", entID); + return; + } + + if ( parmNum < 0 || parmNum >= MAX_PARMS ) + { + G_DebugPrint( WL_WARNING, "SET_PARM: parmNum %d out of range!\n", parmNum ); + return; + } + + if( !ent->parms ) + { + ent->parms = (parms_t *)G_Alloc( sizeof(parms_t) ); + memset( ent->parms, 0, sizeof(parms_t) ); + } + + if ( (val = Q3_GameSideCheckStringCounterIncrement( parmValue )) ) + { + val += atof( ent->parms->parm[parmNum] ); + Com_sprintf( ent->parms->parm[parmNum], sizeof(ent->parms->parm), "%f", val ); + } + else + {//Just copy the string + //copy only 16 characters + strncpy( ent->parms->parm[parmNum], parmValue, sizeof(ent->parms->parm[0]) ); + //set the last charcter to null in case we had to truncate their passed string + if ( ent->parms->parm[parmNum][sizeof(ent->parms->parm[0]) - 1] != 0 ) + {//Tried to set a string that is too long + ent->parms->parm[parmNum][sizeof(ent->parms->parm[0]) - 1] = 0; + G_DebugPrint( WL_WARNING, "SET_PARM: parm%d string too long, truncated to '%s'!\n", parmNum, ent->parms->parm[parmNum] ); + } + } +} + + + +/* +============= +Q3_SetCaptureGoal + +Sets the capture spot goal of an entity +============= +*/ +static void Q3_SetCaptureGoal( int entID, const char *name ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCaptureGoal: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============= +Q3_SetEvent + +? +============= +*/ +static void Q3_SetEvent( int entID, const char *event_name ) +{ //rwwFIXMEFIXME: Use in MP? + G_DebugPrint( WL_WARNING, "Q3_SetEvent: NOT SUPPORTED IN MP (may be in future, ask if needed)\n"); + return; +} + +/* +============ +Q3_SetIgnorePain + +? +============ +*/ +static void Q3_SetIgnorePain( int entID, qboolean data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetIgnorePain: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetIgnoreEnemies + +? +============ +*/ +static void Q3_SetIgnoreEnemies( int entID, qboolean data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetIgnoreEnemies: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetIgnoreAlerts + +? +============ +*/ +static void Q3_SetIgnoreAlerts( int entID, qboolean data) +{ + G_DebugPrint( WL_WARNING, "Q3_SetIgnoreAlerts: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetNoTarget + +? +============ +*/ +static void Q3_SetNoTarget( int entID, qboolean data) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetNoTarget: invalid entID %d\n", entID); + return; + } + + if(data) + ent->flags |= FL_NOTARGET; + else + ent->flags &= ~FL_NOTARGET; +} + +/* +============ +Q3_SetDontShoot + +? +============ +*/ +static void Q3_SetDontShoot( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDontShoot: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetDontFire + +? +============ +*/ +static void Q3_SetDontFire( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDontFire: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetFireWeapon + +? +============ +*/ +static void Q3_SetFireWeapon(int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetFireWeapon: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetInactive + +? +============ +*/ +static void Q3_SetInactive(int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetInactive: invalid entID %d\n", entID); + return; + } + + if(add) + { + ent->flags |= FL_INACTIVE; + } + else + { + ent->flags &= ~FL_INACTIVE; + } +} + +/* +============ +Q3_SetFuncUsableVisible + +? +============ +*/ +static void Q3_SetFuncUsableVisible(int entID, qboolean visible ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetFuncUsableVisible: invalid entID %d\n", entID); + return; + } + + // Yeah, I know that this doesn't even do half of what the func_usable use code does, but if I've got two things on top of each other...and only + // one is visible at a time....and neither can ever be used......and finally, the shader on it has the shader_anim stuff going on....It doesn't seem + // like I can easily use the other version without nasty side effects. + if( visible ) + { + ent->r.svFlags &= ~SVF_NOCLIENT; + ent->s.eFlags &= ~EF_NODRAW; + } + else + { + ent->r.svFlags |= SVF_NOCLIENT; + ent->s.eFlags |= EF_NODRAW; + } +} + +/* +============ +Q3_SetLockedEnemy + +? +============ +*/ +static void Q3_SetLockedEnemy ( int entID, qboolean locked) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLockedEnemy: NOT SUPPORTED IN MP\n"); + return; +} + +char cinematicSkipScript[1024]; + +/* +============ +Q3_SetCinematicSkipScript + +============ +*/ +static void Q3_SetCinematicSkipScript( char *scriptname ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCinematicSkipScript: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoMindTrick + +? +============ +*/ +static void Q3_SetNoMindTrick( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoMindTrick: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetCrouched + +? +============ +*/ +static void Q3_SetCrouched( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCrouched: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetWalking + +? +============ +*/ +static void Q3_SetWalking( int entID, qboolean add) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetWalking: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetWalking: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(add) + { + ent->NPC->scriptFlags |= SCF_WALKING; + } + else + { + ent->NPC->scriptFlags &= ~SCF_WALKING; + } + return; +} + +/* +============ +Q3_SetRunning + +? +============ +*/ +static void Q3_SetRunning( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetRunning: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetForcedMarch + +? +============ +*/ +static void Q3_SetForcedMarch( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetForcedMarch: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_SetChaseEnemies + +indicates whether the npc should chase after an enemy +============ +*/ +static void Q3_SetChaseEnemies( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetChaseEnemies: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetLookForEnemies + +if set npc will be on the look out for potential enemies +if not set, npc will ignore enemies +============ +*/ +static void Q3_SetLookForEnemies( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLookForEnemies: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetFaceMoveDir + +============ +*/ +static void Q3_SetFaceMoveDir( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetFaceMoveDir: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetAltFire + +? +============ +*/ +static void Q3_SetAltFire( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAltFire: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetDontFlee + +? +============ +*/ +static void Q3_SetDontFlee( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDontFlee: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoResponse + +? +============ +*/ +static void Q3_SetNoResponse( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoResponse: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetCombatTalk + +? +============ +*/ +static void Q3_SetCombatTalk( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCombatTalk: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetAlertTalk + +? +============ +*/ +static void Q3_SetAlertTalk( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAlertTalk: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetUseCpNearest + +? +============ +*/ +static void Q3_SetUseCpNearest( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetUseCpNearest: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoForce + +? +============ +*/ +static void Q3_SetNoForce( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoForce: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoAcrobatics + +? +============ +*/ +static void Q3_SetNoAcrobatics( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoAcrobatics: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetUseSubtitles + +? +============ +*/ +static void Q3_SetUseSubtitles( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetUseSubtitles: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoFallToDeath + +? +============ +*/ +static void Q3_SetNoFallToDeath( int entID, qboolean add) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoFallToDeath: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetDismemberable + +? +============ +*/ +static void Q3_SetDismemberable( int entID, qboolean dismemberable) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDismemberable: NOT SUPPORTED IN MP\n"); + return; +} + + +/* +============ +Q3_SetMoreLight + +? +============ +*/ +static void Q3_SetMoreLight( int entID, qboolean add ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetMoreLight: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetUndying + +? +============ +*/ +static void Q3_SetUndying( int entID, qboolean undying) +{ + G_DebugPrint( WL_WARNING, "Q3_SetUndying: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetInvincible + +? +============ +*/ +static void Q3_SetInvincible( int entID, qboolean invincible) +{ + G_DebugPrint( WL_WARNING, "Q3_SetInvicible: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_SetForceInvincible + Description : + Return type : static void + Argument : int entID + Argument : qboolean forceInv +============ +*/ +static void Q3_SetForceInvincible( int entID, qboolean forceInv ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetForceInvicible: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetNoAvoid + +? +============ +*/ +static void Q3_SetNoAvoid( int entID, qboolean noAvoid) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetNoAvoid: invalid entID %d\n", entID); + return; + } + + if ( !ent->NPC ) + { + G_DebugPrint( WL_ERROR, "Q3_SetNoAvoid: '%s' is not an NPC!\n", ent->targetname ); + return; + } + + if(noAvoid) + { + ent->NPC->aiFlags |= NPCAI_NO_COLL_AVOID; + } + else + { + ent->NPC->aiFlags &= ~NPCAI_NO_COLL_AVOID; + } +} + +/* +============ +SolidifyOwner + Description : + Return type : void + Argument : sharedEntity_t *self +============ +*/ +void SolidifyOwner( gentity_t *self ) +{ + int oldContents; + gentity_t *owner = &g_entities[self->r.ownerNum]; + + self->nextthink = level.time + FRAMETIME; + self->think = G_FreeEntity; + + if ( !owner || !owner->inuse ) + { + return; + } + + oldContents = owner->r.contents; + owner->r.contents = CONTENTS_BODY; + if ( SpotWouldTelefrag2( owner, owner->r.currentOrigin ) ) + { + owner->r.contents = oldContents; + self->think = SolidifyOwner; + } + else + { + trap_ICARUS_TaskIDComplete( owner, TID_RESIZE ); + } +} + + +/* +============ +Q3_SetSolid + +? +============ +*/ +static qboolean Q3_SetSolid( int entID, qboolean solid) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent || !ent->inuse ) + { + G_DebugPrint( WL_WARNING, "Q3_SetSolid: invalid entID %d\n", entID); + return qtrue; + } + + if ( solid ) + {//FIXME: Presumption + int oldContents = ent->r.contents; + ent->r.contents = CONTENTS_BODY; + if ( SpotWouldTelefrag2( ent, ent->r.currentOrigin ) ) + { + gentity_t *solidifier = G_Spawn(); + + solidifier->r.ownerNum = ent->s.number; + + solidifier->think = SolidifyOwner; + solidifier->nextthink = level.time + FRAMETIME; + + ent->r.contents = oldContents; + return qfalse; + } + ent->clipmask |= CONTENTS_BODY; + } + else + {//FIXME: Presumption + if ( ent->s.eFlags & EF_NODRAW ) + {//We're invisible too, so set contents to none + ent->r.contents = 0; + } + else + { + ent->r.contents = CONTENTS_CORPSE; + } + } + return qtrue; +} + +/* +============ +Q3_SetForwardMove + +? +============ +*/ +static void Q3_SetForwardMove( int entID, int fmoveVal) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetForwardMove: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetForwardMove: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + G_DebugPrint( WL_WARNING, "Q3_SetForwardMove: NOT SUPPORTED IN MP\n"); + //ent->client->forced_forwardmove = fmoveVal; +} + +/* +============ +Q3_SetRightMove + +? +============ +*/ +static void Q3_SetRightMove( int entID, int rmoveVal) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetRightMove: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetRightMove: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + G_DebugPrint( WL_WARNING, "Q3_SetRightMove: NOT SUPPORTED IN MP\n"); + //ent->client->forced_rightmove = rmoveVal; +} + +/* +============ +Q3_SetLockAngle + +? +============ +*/ +static void Q3_SetLockAngle( int entID, const char *lockAngle) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetLockAngle: invalid entID %d\n", entID); + return; + } + + if ( !ent->client ) + { + G_DebugPrint( WL_ERROR, "Q3_SetLockAngle: '%s' is not an NPC/player!\n", ent->targetname ); + return; + } + + G_DebugPrint( WL_WARNING, "Q3_SetLockAngle is not currently available. Ask if you really need it.\n"); + /* + if(Q_stricmp("off", lockAngle) == 0) + {//free it + ent->client->renderInfo.renderFlags &= ~RF_LOCKEDANGLE; + } + else + { + ent->client->renderInfo.renderFlags |= RF_LOCKEDANGLE; + + + if(Q_stricmp("auto", lockAngle) == 0) + {//use current yaw + ent->client->renderInfo.lockYaw = ent->client->ps.viewangles[YAW]; + } + else + {//specified yaw + ent->client->renderInfo.lockYaw = atof((char *)lockAngle); + } + } + */ +} + + +/* +============ +Q3_CameraGroup + +? +============ +*/ +static void Q3_CameraGroup( int entID, char *camG) +{ + G_DebugPrint( WL_WARNING, "Q3_CameraGroup: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_CameraGroupZOfs + +? +============ +*/ +static void Q3_CameraGroupZOfs( float camGZOfs ) +{ + G_DebugPrint( WL_WARNING, "Q3_CameraGroupZOfs: NOT SUPPORTED IN MP\n"); + return; +} +/* +============ +Q3_CameraGroup + +? +============ +*/ +static void Q3_CameraGroupTag( char *camGTag ) +{ + G_DebugPrint( WL_WARNING, "Q3_CameraGroupTag: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_RemoveRHandModel +============ +*/ +static void Q3_RemoveRHandModel( int entID, char *addModel) +{ + G_DebugPrint( WL_WARNING, "Q3_RemoveRHandModel: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_AddRHandModel +============ +*/ +static void Q3_AddRHandModel( int entID, char *addModel) +{ + G_DebugPrint( WL_WARNING, "Q3_AddRHandModel: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_AddLHandModel +============ +*/ +static void Q3_AddLHandModel( int entID, char *addModel) +{ + G_DebugPrint( WL_WARNING, "Q3_AddLHandModel: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_RemoveLHandModel +============ +*/ +static void Q3_RemoveLHandModel( int entID, char *addModel) +{ + G_DebugPrint( WL_WARNING, "Q3_RemoveLHandModel: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_LookTarget + +? +============ +*/ +static void Q3_LookTarget( int entID, char *targetName) +{ + G_DebugPrint( WL_WARNING, "Q3_LookTarget: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_Face + +? +============ +*/ +static void Q3_Face( int entID,int expression, float holdtime) +{ + G_DebugPrint( WL_WARNING, "Q3_Face: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetLocation + Description : + Return type : qboolean + Argument : int entID + Argument : const char *location +============ +*/ +static qboolean Q3_SetLocation( int entID, const char *location ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLocation: NOT SUPPORTED IN MP\n"); + return qtrue; +} + +/* +============ +Q3_SetPlayerLocked + Description : + Return type : void + Argument : int entID + Argument : qboolean locked +============ +*/ +qboolean player_locked = qfalse; +static void Q3_SetPlayerLocked( int entID, qboolean locked ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetPlayerLocked: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetLockPlayerWeapons + Description : + Return type : void + Argument : int entID + Argument : qboolean locked +============ +*/ +static void Q3_SetLockPlayerWeapons( int entID, qboolean locked ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLockPlayerWeapons: NOT SUPPORTED IN MP\n"); +} + + +/* +============ +Q3_SetNoImpactDamage + Description : + Return type : void + Argument : int entID + Argument : qboolean locked +============ +*/ +static void Q3_SetNoImpactDamage( int entID, qboolean noImp ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetNoImpactDamage: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetBehaviorSet + +? +============ +*/ +static qboolean Q3_SetBehaviorSet( int entID, int toSet, const char *scriptname) +{ + gentity_t *ent = &g_entities[entID]; + bSet_t bSet = BSET_INVALID; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetBehaviorSet: invalid entID %d\n", entID); + return qfalse; + } + + switch(toSet) + { + case SET_SPAWNSCRIPT: + bSet = BSET_SPAWN; + break; + case SET_USESCRIPT: + bSet = BSET_USE; + break; + case SET_AWAKESCRIPT: + bSet = BSET_AWAKE; + break; + case SET_ANGERSCRIPT: + bSet = BSET_ANGER; + break; + case SET_ATTACKSCRIPT: + bSet = BSET_ATTACK; + break; + case SET_VICTORYSCRIPT: + bSet = BSET_VICTORY; + break; + case SET_LOSTENEMYSCRIPT: + bSet = BSET_LOSTENEMY; + break; + case SET_PAINSCRIPT: + bSet = BSET_PAIN; + break; + case SET_FLEESCRIPT: + bSet = BSET_FLEE; + break; + case SET_DEATHSCRIPT: + bSet = BSET_DEATH; + break; + case SET_DELAYEDSCRIPT: + bSet = BSET_DELAYED; + break; + case SET_BLOCKEDSCRIPT: + bSet = BSET_BLOCKED; + break; + case SET_FFIRESCRIPT: + bSet = BSET_FFIRE; + break; + case SET_FFDEATHSCRIPT: + bSet = BSET_FFDEATH; + break; + case SET_MINDTRICKSCRIPT: + bSet = BSET_MINDTRICK; + break; + } + + if(bSet < BSET_SPAWN || bSet >= NUM_BSETS) + { + return qfalse; + } + + if(!Q_stricmp("NULL", scriptname)) + { + if ( ent->behaviorSet[bSet] != NULL ) + { +// gi.TagFree( ent->behaviorSet[bSet] ); + } + + ent->behaviorSet[bSet] = NULL; + //memset( &ent->behaviorSet[bSet], 0, sizeof(ent->behaviorSet[bSet]) ); + } + else + { + if ( scriptname ) + { + if ( ent->behaviorSet[bSet] != NULL ) + { +// gi.TagFree( ent->behaviorSet[bSet] ); + } + + ent->behaviorSet[bSet] = G_NewString( (char *) scriptname ); //FIXME: This really isn't good... + } + + //ent->behaviorSet[bSet] = scriptname; + //strncpy( (char *) &ent->behaviorSet[bSet], scriptname, MAX_BSET_LENGTH ); + } + return qtrue; +} + +/* +============ +Q3_SetDelayScriptTime + +? +============ +*/ +static void Q3_SetDelayScriptTime(int entID, int delayTime) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDelayScriptTime: NOT SUPPORTED IN MP\n"); +} + + + + +/* +============ +Q3_SetPlayerUsable + Description : + Return type : void + Argument : int entID + Argument : qboolean usable +============ +*/ +static void Q3_SetPlayerUsable( int entID, qboolean usable ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetPlayerUsable: invalid entID %d\n", entID); + return; + } + + if(usable) + { + ent->r.svFlags |= SVF_PLAYER_USABLE; + } + else + { + ent->r.svFlags &= ~SVF_PLAYER_USABLE; + } +} + +/* +============ +Q3_SetDisableShaderAnims + Description : + Return type : static void + Argument : int entID + Argument : int disabled +============ +*/ +static void Q3_SetDisableShaderAnims( int entID, int disabled ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetDisableShaderAnims: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetShaderAnim + Description : + Return type : static void + Argument : int entID + Argument : int disabled +============ +*/ +static void Q3_SetShaderAnim( int entID, int disabled ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetShaderAnim: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetStartFrame + Description : + Return type : static void + Argument : int entID + Argument : int startFrame +============ +*/ +static void Q3_SetStartFrame( int entID, int startFrame ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetStartFrame: NOT SUPPORTED IN MP\n"); +} + + +/* +============ +Q3_SetEndFrame + Description : + Return type : static void + Argument : int entID + Argument : int endFrame +============ +*/ +static void Q3_SetEndFrame( int entID, int endFrame ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetEndFrame: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetAnimFrame + Description : + Return type : static void + Argument : int entID + Argument : int startFrame +============ +*/ +static void Q3_SetAnimFrame( int entID, int animFrame ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetAnimFrame: NOT SUPPORTED IN MP\n"); +} + +/* +============ +Q3_SetLoopAnim + Description : + Return type : void + Argument : int entID + Argument : qboolean loopAnim +============ +*/ +static void Q3_SetLoopAnim( int entID, qboolean loopAnim ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetLoopAnim: NOT SUPPORTED IN MP\n"); +} + + +/* +============ +Q3_SetShields + Description : + Return type : void + Argument : int entID + Argument : qboolean shields +============ +*/ +static void Q3_SetShields( int entID, qboolean shields ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetShields: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============ +Q3_SetSaberActive + Description : + Return type : void + Argument : int entID + Argument : qboolean shields +============ +*/ +static void Q3_SetSaberActive( int entID, qboolean active ) +{ + gentity_t *ent = &g_entities[entID]; + + if (!ent || !ent->inuse) + { + return; + } + + if (!ent->client) + { + G_DebugPrint( WL_WARNING, "Q3_SetSaberActive: %d is not a client\n", entID); + } + + //fixme: Take into account player being in state where saber won't toggle? For now we simply won't care. + if (!ent->client->ps.saberHolstered && active) + { + Cmd_ToggleSaber_f(ent); + } + else if (BG_SabersOff( &ent->client->ps ) && !active) + { + Cmd_ToggleSaber_f(ent); + } +} + +/* +============ +Q3_SetNoKnockback + Description : + Return type : void + Argument : int entID + Argument : qboolean noKnockback +============ +*/ +static void Q3_SetNoKnockback( int entID, qboolean noKnockback ) +{ + gentity_t *ent = &g_entities[entID]; + + if ( !ent ) + { + G_DebugPrint( WL_WARNING, "Q3_SetNoKnockback: invalid entID %d\n", entID); + return; + } + + if ( noKnockback ) + { + ent->flags |= FL_NO_KNOCKBACK; + } + else + { + ent->flags &= ~FL_NO_KNOCKBACK; + } +} + +/* +============ +Q3_SetCleanDamagingEnts + Description : + Return type : void +============ +*/ +static void Q3_SetCleanDamagingEnts( void ) +{ + G_DebugPrint( WL_WARNING, "Q3_SetCleanDamagingEnts: NOT SUPPORTED IN MP\n"); + return; +} + +vec4_t textcolor_caption; +vec4_t textcolor_center; +vec4_t textcolor_scroll; + +/* +------------------------- +SetTextColor +------------------------- +*/ +static void SetTextColor ( vec4_t textcolor,const char *color) +{ + G_DebugPrint( WL_WARNING, "SetTextColor: NOT SUPPORTED IN MP\n"); + return; +} + +/* +============= +Q3_SetCaptionTextColor + +Change color text prints in +============= +*/ +static void Q3_SetCaptionTextColor ( const char *color) +{ + SetTextColor(textcolor_caption,color); +} + +/* +============= +Q3_SetCenterTextColor + +Change color text prints in +============= +*/ +static void Q3_SetCenterTextColor ( const char *color) +{ + SetTextColor(textcolor_center,color); +} + +/* +============= +Q3_SetScrollTextColor + +Change color text prints in +============= +*/ +static void Q3_SetScrollTextColor ( const char *color) +{ + SetTextColor(textcolor_scroll,color); +} + +/* +============= +Q3_ScrollText + +Prints a message in the center of the screen +============= +*/ +static void Q3_ScrollText ( const char *id) +{ + G_DebugPrint( WL_WARNING, "Q3_ScrollText: NOT SUPPORTED IN MP\n"); + //trap_SendServerCommand( -1, va("st \"%s\"", id)); + + return; +} + +/* +============= +Q3_LCARSText + +Prints a message in the center of the screen giving it an LCARS frame around it +============= +*/ +static void Q3_LCARSText ( const char *id) +{ + G_DebugPrint( WL_WARNING, "Q3_ScrollText: NOT SUPPORTED IN MP\n"); + //trap_SendServerCommand( -1, va("lt \"%s\"", id)); + + return; +} + +void UnLockDoors(gentity_t *const ent); +void LockDoors(gentity_t *const ent); + +//returns qtrue if it got to the end, otherwise qfalse. +qboolean Q3_Set( int taskID, int entID, const char *type_name, const char *data ) +{ + gentity_t *ent = &g_entities[entID]; + float float_data; + int int_data, toSet; + vec3_t vector_data; + + //Set this for callbacks + toSet = GetIDForString( setTable, type_name ); + + //TODO: Throw in a showscript command that will list each command and what they're doing... + // maybe as simple as printing that line of the script to the console preceeded by the person's name? + // showscript can take any number of targetnames or "all"? Groupname? + switch ( toSet ) + { + case SET_ORIGIN: + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + G_SetOrigin( ent, vector_data ); + if ( Q_strncmp( "NPC_", ent->classname, 4 ) == 0 ) + {//hack for moving spawners + VectorCopy( vector_data, ent->s.origin); + } + break; + + case SET_TELEPORT_DEST: + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + if ( !Q3_SetTeleportDest( entID, vector_data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + return qfalse; + } + break; + + case SET_COPY_ORIGIN: + Q3_SetCopyOrigin( entID, (char *) data ); + break; + + case SET_ANGLES: + //Q3_SetAngles( entID, *(vec3_t *) data); + sscanf( data, "%f %f %f", &vector_data[0], &vector_data[1], &vector_data[2] ); + Q3_SetAngles( entID, vector_data); + break; + + case SET_XVELOCITY: + float_data = atof((char *) data); + Q3_SetVelocity( entID, 0, float_data); + break; + + case SET_YVELOCITY: + float_data = atof((char *) data); + Q3_SetVelocity( entID, 1, float_data); + break; + + case SET_ZVELOCITY: + float_data = atof((char *) data); + Q3_SetVelocity( entID, 2, float_data); + break; + + case SET_Z_OFFSET: + float_data = atof((char *) data); + Q3_SetOriginOffset( entID, 2, float_data); + break; + + case SET_ENEMY: + Q3_SetEnemy( entID, (char *) data ); + break; + + case SET_LEADER: + Q3_SetLeader( entID, (char *) data ); + break; + + case SET_NAVGOAL: + if ( Q3_SetNavGoal( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_MOVE_NAV, taskID ); + return qfalse; //Don't call it back + } + break; + + case SET_ANIM_UPPER: + if ( Q3_SetAnimUpper( entID, (char *) data ) ) + { + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the top + trap_ICARUS_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + return qfalse; //Don't call it back + } + break; + + case SET_ANIM_LOWER: + if ( Q3_SetAnimLower( entID, (char *) data ) ) + { + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the bottom + trap_ICARUS_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + return qfalse; //Don't call it back + } + break; + + case SET_ANIM_BOTH: + { + int both = 0; + if ( Q3_SetAnimUpper( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + both++; + } + else + { + G_DebugPrint( WL_ERROR, "Q3_SetAnimUpper: %s does not have anim %s!\n", ent->targetname, (char *)data ); + } + if ( Q3_SetAnimLower( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + both++; + } + else + { + G_DebugPrint( WL_ERROR, "Q3_SetAnimLower: %s does not have anim %s!\n", ent->targetname, (char *)data ); + } + if ( both >= 2 ) + { + trap_ICARUS_TaskIDSet( ent, TID_ANIM_BOTH, taskID ); + } + if ( both ) + { + return qfalse; //Don't call it back + } + } + break; + + case SET_ANIM_HOLDTIME_LOWER: + int_data = atoi((char *) data); + Q3_SetAnimHoldTime( entID, int_data, qtrue ); + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the bottom + trap_ICARUS_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + return qfalse; //Don't call it back + break; + + case SET_ANIM_HOLDTIME_UPPER: + int_data = atoi((char *) data); + Q3_SetAnimHoldTime( entID, int_data, qfalse ); + Q3_TaskIDClear( &ent->taskID[TID_ANIM_BOTH] );//We only want to wait for the top + trap_ICARUS_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + return qfalse; //Don't call it back + break; + + case SET_ANIM_HOLDTIME_BOTH: + int_data = atoi((char *) data); + Q3_SetAnimHoldTime( entID, int_data, qfalse ); + Q3_SetAnimHoldTime( entID, int_data, qtrue ); + trap_ICARUS_TaskIDSet( ent, TID_ANIM_BOTH, taskID ); + trap_ICARUS_TaskIDSet( ent, TID_ANIM_UPPER, taskID ); + trap_ICARUS_TaskIDSet( ent, TID_ANIM_LOWER, taskID ); + return qfalse; //Don't call it back + break; + + case SET_PLAYER_TEAM: + G_DebugPrint( WL_WARNING, "Q3_SetPlayerTeam: Not in MP ATM, let a programmer (ideally Rich) know if you need it\n"); + break; + + case SET_ENEMY_TEAM: + G_DebugPrint( WL_WARNING, "Q3_SetEnemyTeam: NOT SUPPORTED IN MP\n"); + break; + + case SET_HEALTH: + int_data = atoi((char *) data); + Q3_SetHealth( entID, int_data ); + break; + + case SET_ARMOR: + int_data = atoi((char *) data); + Q3_SetArmor( entID, int_data ); + break; + + case SET_BEHAVIOR_STATE: + if( !Q3_SetBState( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_BSTATE, taskID ); + return qfalse;//don't complete + } + break; + + case SET_DEFAULT_BSTATE: + Q3_SetDefaultBState( entID, (char *) data ); + break; + + case SET_TEMP_BSTATE: + if( !Q3_SetTempBState( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_BSTATE, taskID ); + return qfalse;//don't complete + } + break; + + case SET_CAPTURE: + Q3_SetCaptureGoal( entID, (char *) data ); + break; + + case SET_DPITCH://FIXME: make these set tempBehavior to BS_FACE and await completion? Or set lockedDesiredPitch/Yaw and aimTime? + float_data = atof((char *) data); + Q3_SetDPitch( entID, float_data ); + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + return qfalse; + break; + + case SET_DYAW: + float_data = atof((char *) data); + Q3_SetDYaw( entID, float_data ); + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + return qfalse; + break; + + case SET_EVENT: + Q3_SetEvent( entID, (char *) data ); + break; + + case SET_VIEWTARGET: + Q3_SetViewTarget( entID, (char *) data ); + trap_ICARUS_TaskIDSet( ent, TID_ANGLE_FACE, taskID ); + return qfalse; + break; + + case SET_WATCHTARGET: + Q3_SetWatchTarget( entID, (char *) data ); + break; + + case SET_VIEWENTITY: + Q3_SetViewEntity( entID, (char *) data ); + break; + + case SET_LOOPSOUND: + Q3_SetLoopSound( entID, (char *) data ); + break; + + case SET_ICARUS_FREEZE: + case SET_ICARUS_UNFREEZE: + Q3_SetICARUSFreeze( entID, (char *) data, (qboolean)(toSet==SET_ICARUS_FREEZE) ); + break; + + case SET_WEAPON: + Q3_SetWeapon ( entID, (char *) data); + break; + + case SET_ITEM: + Q3_SetItem ( entID, (char *) data); + break; + + case SET_WALKSPEED: + int_data = atoi((char *) data); + Q3_SetWalkSpeed ( entID, int_data); + break; + + case SET_RUNSPEED: + int_data = atoi((char *) data); + Q3_SetRunSpeed ( entID, int_data); + break; + + case SET_WIDTH: + int_data = atoi((char *) data); + Q3_SetWidth( entID, int_data ); + return qfalse; + break; + + case SET_YAWSPEED: + float_data = atof((char *) data); + Q3_SetYawSpeed ( entID, float_data); + break; + + case SET_AGGRESSION: + int_data = atoi((char *) data); + Q3_SetAggression ( entID, int_data); + break; + + case SET_AIM: + int_data = atoi((char *) data); + Q3_SetAim ( entID, int_data); + break; + + case SET_FRICTION: + int_data = atoi((char *) data); + Q3_SetFriction ( entID, int_data); + break; + + case SET_GRAVITY: + float_data = atof((char *) data); + Q3_SetGravity ( entID, float_data); + break; + + case SET_WAIT: + float_data = atof((char *) data); + Q3_SetWait( entID, float_data); + break; + + case SET_FOLLOWDIST: + float_data = atof((char *) data); + Q3_SetFollowDist( entID, float_data); + break; + + case SET_SCALE: + float_data = atof((char *) data); + Q3_SetScale( entID, float_data); + break; + + case SET_COUNT: + Q3_SetCount( entID, (char *) data); + break; + + case SET_SHOT_SPACING: + int_data = atoi((char *) data); + Q3_SetShotSpacing( entID, int_data ); + break; + + case SET_IGNOREPAIN: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetIgnorePain( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetIgnorePain( entID, qfalse); + break; + + case SET_IGNOREENEMIES: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetIgnoreEnemies( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetIgnoreEnemies( entID, qfalse); + break; + + case SET_IGNOREALERTS: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetIgnoreAlerts( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetIgnoreAlerts( entID, qfalse); + break; + + case SET_DONTSHOOT: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetDontShoot( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetDontShoot( entID, qfalse); + break; + + case SET_DONTFIRE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetDontFire( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetDontFire( entID, qfalse); + break; + + case SET_LOCKED_ENEMY: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetLockedEnemy( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetLockedEnemy( entID, qfalse); + break; + + case SET_NOTARGET: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoTarget( entID, qtrue); + else if(!Q_stricmp("false", ((char *)data))) + Q3_SetNoTarget( entID, qfalse); + break; + + case SET_LEAN: + G_DebugPrint( WL_WARNING, "SET_LEAN NOT SUPPORTED IN MP\n" ); + break; + + case SET_SHOOTDIST: + float_data = atof((char *) data); + Q3_SetShootDist( entID, float_data ); + break; + + case SET_TIMESCALE: + Q3_SetTimeScale( entID, (char *) data ); + break; + + case SET_VISRANGE: + float_data = atof((char *) data); + Q3_SetVisrange( entID, float_data ); + break; + + case SET_EARSHOT: + float_data = atof((char *) data); + Q3_SetEarshot( entID, float_data ); + break; + + case SET_VIGILANCE: + float_data = atof((char *) data); + Q3_SetVigilance( entID, float_data ); + break; + + case SET_VFOV: + int_data = atoi((char *) data); + Q3_SetVFOV( entID, int_data ); + break; + + case SET_HFOV: + int_data = atoi((char *) data); + Q3_SetHFOV( entID, int_data ); + break; + + case SET_TARGETNAME: + Q3_SetTargetName( entID, (char *) data ); + break; + + case SET_TARGET: + Q3_SetTarget( entID, (char *) data ); + break; + + case SET_TARGET2: + Q3_SetTarget2( entID, (char *) data ); + break; + + case SET_LOCATION: + if ( !Q3_SetLocation( entID, (char *) data ) ) + { + trap_ICARUS_TaskIDSet( ent, TID_LOCATION, taskID ); + return qfalse; + } + break; + + case SET_PAINTARGET: + Q3_SetPainTarget( entID, (char *) data ); + break; + + case SET_DEFEND_TARGET: + G_DebugPrint( WL_WARNING, "Q3_SetDefendTarget unimplemented\n", entID ); + //Q3_SetEnemy( entID, (char *) data); + break; + + case SET_PARM1: + case SET_PARM2: + case SET_PARM3: + case SET_PARM4: + case SET_PARM5: + case SET_PARM6: + case SET_PARM7: + case SET_PARM8: + case SET_PARM9: + case SET_PARM10: + case SET_PARM11: + case SET_PARM12: + case SET_PARM13: + case SET_PARM14: + case SET_PARM15: + case SET_PARM16: + Q3_SetParm( entID, (toSet-SET_PARM1), (char *) data ); + break; + + case SET_SPAWNSCRIPT: + case SET_USESCRIPT: + case SET_AWAKESCRIPT: + case SET_ANGERSCRIPT: + case SET_ATTACKSCRIPT: + case SET_VICTORYSCRIPT: + case SET_PAINSCRIPT: + case SET_FLEESCRIPT: + case SET_DEATHSCRIPT: + case SET_DELAYEDSCRIPT: + case SET_BLOCKEDSCRIPT: + case SET_FFIRESCRIPT: + case SET_FFDEATHSCRIPT: + case SET_MINDTRICKSCRIPT: + if( !Q3_SetBehaviorSet(entID, toSet, (char *) data) ) + G_DebugPrint( WL_ERROR, "Q3_SetBehaviorSet: Invalid bSet %s\n", type_name ); + break; + + case SET_NO_MINDTRICK: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoMindTrick( entID, qtrue); + else + Q3_SetNoMindTrick( entID, qfalse); + break; + + case SET_CINEMATIC_SKIPSCRIPT : + Q3_SetCinematicSkipScript((char *) data); + break; + + + case SET_DELAYSCRIPTTIME: + int_data = atoi((char *) data); + Q3_SetDelayScriptTime( entID, int_data ); + break; + + case SET_CROUCHED: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetCrouched( entID, qtrue); + else + Q3_SetCrouched( entID, qfalse); + break; + + case SET_WALKING: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetWalking( entID, qtrue); + else + Q3_SetWalking( entID, qfalse); + break; + + case SET_RUNNING: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetRunning( entID, qtrue); + else + Q3_SetRunning( entID, qfalse); + break; + + case SET_CHASE_ENEMIES: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetChaseEnemies( entID, qtrue); + else + Q3_SetChaseEnemies( entID, qfalse); + break; + + case SET_LOOK_FOR_ENEMIES: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetLookForEnemies( entID, qtrue); + else + Q3_SetLookForEnemies( entID, qfalse); + break; + + case SET_FACE_MOVE_DIR: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetFaceMoveDir( entID, qtrue); + else + Q3_SetFaceMoveDir( entID, qfalse); + break; + + case SET_ALT_FIRE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetAltFire( entID, qtrue); + else + Q3_SetAltFire( entID, qfalse); + break; + + case SET_DONT_FLEE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetDontFlee( entID, qtrue); + else + Q3_SetDontFlee( entID, qfalse); + break; + + case SET_FORCED_MARCH: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetForcedMarch( entID, qtrue); + else + Q3_SetForcedMarch( entID, qfalse); + break; + + case SET_NO_RESPONSE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoResponse( entID, qtrue); + else + Q3_SetNoResponse( entID, qfalse); + break; + + case SET_NO_COMBAT_TALK: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetCombatTalk( entID, qtrue); + else + Q3_SetCombatTalk( entID, qfalse); + break; + + case SET_NO_ALERT_TALK: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetAlertTalk( entID, qtrue); + else + Q3_SetAlertTalk( entID, qfalse); + break; + + case SET_USE_CP_NEAREST: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetUseCpNearest( entID, qtrue); + else + Q3_SetUseCpNearest( entID, qfalse); + break; + + case SET_NO_FORCE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoForce( entID, qtrue); + else + Q3_SetNoForce( entID, qfalse); + break; + + case SET_NO_ACROBATICS: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoAcrobatics( entID, qtrue); + else + Q3_SetNoAcrobatics( entID, qfalse); + break; + + case SET_USE_SUBTITLES: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetUseSubtitles( entID, qtrue); + else + Q3_SetUseSubtitles( entID, qfalse); + break; + + case SET_NO_FALLTODEATH: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoFallToDeath( entID, qtrue); + else + Q3_SetNoFallToDeath( entID, qfalse); + break; + + case SET_DISMEMBERABLE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetDismemberable( entID, qtrue); + else + Q3_SetDismemberable( entID, qfalse); + break; + + case SET_MORELIGHT: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetMoreLight( entID, qtrue); + else + Q3_SetMoreLight( entID, qfalse); + break; + + + case SET_TREASONED: + G_DebugPrint( WL_VERBOSE, "SET_TREASONED is disabled, do not use\n" ); + /* + G_TeamRetaliation( NULL, SV_GentityNum(0), qfalse ); + ffireLevel = FFIRE_LEVEL_RETALIATION; + */ + break; + + case SET_UNDYING: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetUndying( entID, qtrue); + else + Q3_SetUndying( entID, qfalse); + break; + + case SET_INVINCIBLE: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetInvincible( entID, qtrue); + else + Q3_SetInvincible( entID, qfalse); + break; + + case SET_NOAVOID: + if(!Q_stricmp("true", ((char *)data))) + Q3_SetNoAvoid( entID, qtrue); + else + Q3_SetNoAvoid( entID, qfalse); + break; + + case SET_SOLID: + if(!Q_stricmp("true", ((char *)data))) + { + if ( !Q3_SetSolid( entID, qtrue) ) + { + trap_ICARUS_TaskIDSet( ent, TID_RESIZE, taskID ); + return qfalse; + } + } + else + { + Q3_SetSolid( entID, qfalse); + } + break; + + case SET_INVISIBLE: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetInvisible( entID, qtrue ); + else + Q3_SetInvisible( entID, qfalse ); + break; + + case SET_VAMPIRE: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetVampire( entID, qtrue ); + else + Q3_SetVampire( entID, qfalse ); + break; + + case SET_FORCE_INVINCIBLE: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetForceInvincible( entID, qtrue ); + else + Q3_SetForceInvincible( entID, qfalse ); + break; + + case SET_GREET_ALLIES: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetGreetAllies( entID, qtrue ); + else + Q3_SetGreetAllies( entID, qfalse ); + break; + + case SET_PLAYER_LOCKED: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetPlayerLocked( entID, qtrue ); + else + Q3_SetPlayerLocked( entID, qfalse ); + break; + + case SET_LOCK_PLAYER_WEAPONS: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetLockPlayerWeapons( entID, qtrue ); + else + Q3_SetLockPlayerWeapons( entID, qfalse ); + break; + + case SET_NO_IMPACT_DAMAGE: + if( !Q_stricmp("true", ((char *)data)) ) + Q3_SetNoImpactDamage( entID, qtrue ); + else + Q3_SetNoImpactDamage( entID, qfalse ); + break; + + case SET_FORWARDMOVE: + int_data = atoi((char *) data); + Q3_SetForwardMove( entID, int_data); + break; + + case SET_RIGHTMOVE: + int_data = atoi((char *) data); + Q3_SetRightMove( entID, int_data); + break; + + case SET_LOCKYAW: + Q3_SetLockAngle( entID, data); + break; + + case SET_CAMERA_GROUP: + Q3_CameraGroup(entID, (char *)data); + break; + case SET_CAMERA_GROUP_Z_OFS: + float_data = atof((char *) data); + Q3_CameraGroupZOfs( float_data ); + break; + case SET_CAMERA_GROUP_TAG: + Q3_CameraGroupTag( (char *)data ); + break; + + //FIXME: put these into camera commands + case SET_LOOK_TARGET: + Q3_LookTarget(entID, (char *)data); + break; + + case SET_ADDRHANDBOLT_MODEL: + Q3_AddRHandModel(entID, (char *)data); + break; + + case SET_REMOVERHANDBOLT_MODEL: + Q3_RemoveRHandModel(entID, (char *)data); + break; + + case SET_ADDLHANDBOLT_MODEL: + Q3_AddLHandModel(entID, (char *)data); + break; + + case SET_REMOVELHANDBOLT_MODEL: + Q3_RemoveLHandModel(entID, (char *)data); + break; + + case SET_FACEEYESCLOSED: + case SET_FACEEYESOPENED: + case SET_FACEAUX: + case SET_FACEBLINK: + case SET_FACEBLINKFROWN: + case SET_FACEFROWN: + case SET_FACENORMAL: + float_data = atof((char *) data); + Q3_Face(entID, toSet, float_data); + break; + + case SET_SCROLLTEXT: + Q3_ScrollText( (char *)data ); + break; + + case SET_LCARSTEXT: + Q3_LCARSText( (char *)data ); + break; + + case SET_CAPTIONTEXTCOLOR: + Q3_SetCaptionTextColor ( (char *)data ); + break; + case SET_CENTERTEXTCOLOR: + Q3_SetCenterTextColor ( (char *)data ); + break; + case SET_SCROLLTEXTCOLOR: + Q3_SetScrollTextColor ( (char *)data ); + break; + + case SET_PLAYER_USABLE: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetPlayerUsable(entID, qtrue); + } + else + { + Q3_SetPlayerUsable(entID, qfalse); + } + break; + + case SET_STARTFRAME: + int_data = atoi((char *) data); + Q3_SetStartFrame(entID, int_data); + break; + + case SET_ENDFRAME: + int_data = atoi((char *) data); + Q3_SetEndFrame(entID, int_data); + + trap_ICARUS_TaskIDSet( ent, TID_ANIM_BOTH, taskID ); + return qfalse; + break; + + case SET_ANIMFRAME: + int_data = atoi((char *) data); + Q3_SetAnimFrame(entID, int_data); + return qfalse; + break; + + case SET_LOOP_ANIM: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetLoopAnim(entID, qtrue); + } + else + { + Q3_SetLoopAnim(entID, qfalse); + } + break; + + case SET_INTERFACE: + G_DebugPrint( WL_WARNING, "Q3_SetInterface: NOT SUPPORTED IN MP\n"); + + break; + + case SET_SHIELDS: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetShields(entID, qtrue); + } + else + { + Q3_SetShields(entID, qfalse); + } + break; + + case SET_SABERACTIVE: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetSaberActive( entID, qtrue ); + } + else + { + Q3_SetSaberActive( entID, qfalse ); + } + break; + + case SET_ADJUST_AREA_PORTALS: + G_DebugPrint( WL_WARNING, "Q3_SetAdjustAreaPortals: NOT SUPPORTED IN MP\n"); + break; + + case SET_DMG_BY_HEAVY_WEAP_ONLY: + G_DebugPrint( WL_WARNING, "Q3_SetDmgByHeavyWeapOnly: NOT SUPPORTED IN MP\n"); + break; + + case SET_SHIELDED: + G_DebugPrint( WL_WARNING, "Q3_SetShielded: NOT SUPPORTED IN MP\n"); + break; + + case SET_NO_GROUPS: + G_DebugPrint( WL_WARNING, "Q3_SetNoGroups: NOT SUPPORTED IN MP\n"); + break; + + case SET_FIRE_WEAPON: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetFireWeapon( entID, qtrue); + } + else if(!Q_stricmp("false", ((char *)data))) + { + Q3_SetFireWeapon( entID, qfalse); + } + break; + + case SET_INACTIVE: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetInactive( entID, qtrue); + } + else if(!Q_stricmp("false", ((char *)data))) + { + Q3_SetInactive( entID, qfalse); + } + else if(!Q_stricmp("unlocked", ((char *)data))) + { + UnLockDoors(&g_entities[entID]); + } + else if(!Q_stricmp("locked", ((char *)data))) + { + LockDoors(&g_entities[entID]); + } + break; + case SET_END_SCREENDISSOLVE: + G_DebugPrint( WL_WARNING, "SET_END_SCREENDISSOLVE: NOT SUPPORTED IN MP\n"); + break; + + case SET_MISSION_STATUS_SCREEN: + //Cvar_Set("cg_missionstatusscreen", "1"); + G_DebugPrint( WL_WARNING, "SET_MISSION_STATUS_SCREEN: NOT SUPPORTED IN MP\n"); + break; + + case SET_FUNC_USABLE_VISIBLE: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetFuncUsableVisible( entID, qtrue); + } + else if(!Q_stricmp("false", ((char *)data))) + { + Q3_SetFuncUsableVisible( entID, qfalse); + } + break; + + case SET_NO_KNOCKBACK: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetNoKnockback(entID, qtrue); + } + else + { + Q3_SetNoKnockback(entID, qfalse); + } + break; + + case SET_VIDEO_PLAY: + // don't do this check now, James doesn't want a scripted cinematic to also skip any Video cinematics as well, + // the "timescale" and "skippingCinematic" cvars will be set back to normal in the Video code, so doing a + // skip will now only skip one section of a multiple-part story (eg VOY1 bridge sequence) + // +// if ( g_timescale->value <= 1.0f ) + { + G_DebugPrint( WL_WARNING, "SET_VIDEO_PLAY: NOT SUPPORTED IN MP\n"); + //SV_SendConsoleCommand( va("inGameCinematic %s\n", (char *)data) ); + } + break; + + case SET_VIDEO_FADE_IN: + G_DebugPrint( WL_WARNING, "SET_VIDEO_FADE_IN: NOT SUPPORTED IN MP\n"); + break; + + case SET_VIDEO_FADE_OUT: + G_DebugPrint( WL_WARNING, "SET_VIDEO_FADE_OUT: NOT SUPPORTED IN MP\n"); + break; + case SET_REMOVE_TARGET: + Q3_SetRemoveTarget( entID, (const char *) data ); + break; + + case SET_LOADGAME: + //gi.SendConsoleCommand( va("load %s\n", (const char *) data ) ); + G_DebugPrint( WL_WARNING, "SET_LOADGAME: NOT SUPPORTED IN MP\n"); + break; + + case SET_MENU_SCREEN: + //UI_SetActiveMenu( (const char *) data ); + break; + + case SET_OBJECTIVE_SHOW: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_SHOW: NOT SUPPORTED IN MP\n"); + break; + case SET_OBJECTIVE_HIDE: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_HIDE: NOT SUPPORTED IN MP\n"); + break; + case SET_OBJECTIVE_SUCCEEDED: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_SUCCEEDED: NOT SUPPORTED IN MP\n"); + break; + case SET_OBJECTIVE_FAILED: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_FAILED: NOT SUPPORTED IN MP\n"); + break; + + case SET_OBJECTIVE_CLEARALL: + G_DebugPrint( WL_WARNING, "SET_OBJECTIVE_CLEARALL: NOT SUPPORTED IN MP\n"); + break; + + case SET_MISSIONFAILED: + G_DebugPrint( WL_WARNING, "SET_MISSIONFAILED: NOT SUPPORTED IN MP\n"); + break; + + case SET_MISSIONSTATUSTEXT: + G_DebugPrint( WL_WARNING, "SET_MISSIONSTATUSTEXT: NOT SUPPORTED IN MP\n"); + break; + + case SET_MISSIONSTATUSTIME: + G_DebugPrint( WL_WARNING, "SET_MISSIONSTATUSTIME: NOT SUPPORTED IN MP\n"); + break; + + case SET_CLOSINGCREDITS: + G_DebugPrint( WL_WARNING, "SET_CLOSINGCREDITS: NOT SUPPORTED IN MP\n"); + break; + + case SET_SKILL: +// //can never be set + break; + + case SET_FULLNAME: + Q3_SetFullName( entID, (char *) data ); + break; + + case SET_DISABLE_SHADER_ANIM: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetDisableShaderAnims( entID, qtrue); + } + else + { + Q3_SetDisableShaderAnims( entID, qfalse); + } + break; + + case SET_SHADER_ANIM: + if(!Q_stricmp("true", ((char *)data))) + { + Q3_SetShaderAnim( entID, qtrue); + } + else + { + Q3_SetShaderAnim( entID, qfalse); + } + break; + + case SET_MUSIC_STATE: + Q3_SetMusicState( (char *) data ); + break; + + case SET_CLEAN_DAMAGING_ENTS: + Q3_SetCleanDamagingEnts(); + break; + + case SET_HUD: + G_DebugPrint( WL_WARNING, "SET_HUD: NOT SUPPORTED IN MP\n"); + break; + + case SET_FORCE_HEAL_LEVEL: + case SET_FORCE_JUMP_LEVEL: + case SET_FORCE_SPEED_LEVEL: + case SET_FORCE_PUSH_LEVEL: + case SET_FORCE_PULL_LEVEL: + case SET_FORCE_MINDTRICK_LEVEL: + case SET_FORCE_GRIP_LEVEL: + case SET_FORCE_LIGHTNING_LEVEL: + case SET_SABER_THROW: + case SET_SABER_DEFENSE: + case SET_SABER_OFFENSE: + int_data = atoi((char *) data); + Q3_SetForcePowerLevel( entID, (toSet-SET_FORCE_HEAL_LEVEL), int_data ); + break; + + default: + //G_DebugPrint( WL_ERROR, "Q3_Set: '%s' is not a valid set field\n", type_name ); + trap_ICARUS_SetVar( taskID, entID, type_name, data ); + break; + } + + return qtrue; +} diff --git a/code/game/g_ICARUScb.h b/code/game/g_ICARUScb.h new file mode 100644 index 0000000..f80b3e2 --- /dev/null +++ b/code/game/g_ICARUScb.h @@ -0,0 +1,15 @@ +int Q3_PlaySound( int taskID, int entID, const char *name, const char *channel ); +qboolean Q3_Set( int taskID, int entID, const char *type_name, const char *data ); +void Q3_Lerp2Pos( int taskID, int entID, vec3_t origin, vec3_t angles, float duration ); +void Q3_Lerp2Origin( int taskID, int entID, vec3_t origin, float duration ); +void Q3_Lerp2Angles( int taskID, int entID, vec3_t angles, float duration ); +int Q3_GetTag( int entID, const char *name, int lookup, vec3_t info ); +void Q3_Lerp2Start( int entID, int taskID, float duration ); +void Q3_Lerp2End( int entID, int taskID, float duration ); +void Q3_Use( int entID, const char *target ); +void Q3_Kill( int entID, const char *name ); +void Q3_Remove( int entID, const char *name ); +void Q3_Play( int taskID, int entID, const char *type, const char *name ); +int Q3_GetFloat( int entID, int type, const char *name, float *value ); +int Q3_GetVector( int entID, int type, const char *name, vec3_t value ); +int Q3_GetString( int entID, int type, const char *name, char **value ); diff --git a/code/game/g_active.c b/code/game/g_active.c new file mode 100644 index 0000000..d643e91 --- /dev/null +++ b/code/game/g_active.c @@ -0,0 +1,3848 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// + +#include "g_local.h" +#include "bg_saga.h" + +extern void Jedi_Cloak( gentity_t *self ); +extern void Jedi_Decloak( gentity_t *self ); + +#include "../namespace_begin.h" +qboolean PM_SaberInTransition( int move ); +qboolean PM_SaberInStart( int move ); +qboolean PM_SaberInReturn( int move ); +qboolean WP_SaberStyleValidForSaber( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int saberAnimLevel ); +#include "../namespace_end.h" +qboolean saberCheckKnockdown_DuelLoss(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other); + +extern vmCvar_t g_saberLockRandomNess; + +void P_SetTwitchInfo(gclient_t *client) +{ + client->ps.painTime = level.time; + client->ps.painDirection ^= 1; +} + +/* +=============== +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; + + //cap them since we can't send negative values in here across the net + if (client->ps.damagePitch < 0) + { + client->ps.damagePitch = 0; + } + if (client->ps.damageYaw < 0) + { + client->ps.damageYaw = 0; + } + } + + // play an apropriate pain sound + if ( (level.time > player->pain_debounce_time) && !(player->flags & FL_GODMODE) && !(player->s.eFlags & EF_DEAD) ) { + + // don't do more than two pain sounds a second + // nmckenzie: also don't make him loud and whiny if he's only getting nicked. + if ( level.time - client->ps.painTime < 500 || count < 10) { + return; + } + P_SetTwitchInfo(client); + player->pain_debounce_time = level.time + 700; + + G_AddEvent( player, EV_PAIN, player->health ); + client->ps.damageEvent++; + + if (client->damage_armor && !client->damage_blood) + { + client->ps.damageType = 1; //pure shields + } + else if (client->damage_armor) + { + client->ps.damageType = 2; //shields and health + } + else + { + client->ps.damageType = 0; //pure health + } + } + + + 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"*/"sound/player/gurp1.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); + } + } + } + } +} + + + + + +//============================================================== +extern void G_ApplyKnockback( gentity_t *targ, vec3_t newDir, float knockback ); +void DoImpact( gentity_t *self, gentity_t *other, qboolean damageSelf ) +{ + float magnitude, my_mass; + vec3_t velocity; + int cont; + qboolean easyBreakBrush = qtrue; + + if( self->client ) + { + VectorCopy( self->client->ps.velocity, velocity ); + if( !self->mass ) + { + my_mass = 10; + } + else + { + my_mass = self->mass; + } + } + else + { + VectorCopy( self->s.pos.trDelta, velocity ); + if ( self->s.pos.trType == TR_GRAVITY ) + { + velocity[2] -= 0.25f * g_gravity.value; + } + if( !self->mass ) + { + my_mass = 1; + } + else if ( self->mass <= 10 ) + { + my_mass = 10; + } + else + { + my_mass = self->mass;///10; + } + } + + magnitude = VectorLength( velocity ) * my_mass / 10; + + /* + if(pointcontents(self.absmax)==CONTENT_WATER)//FIXME: or other watertypes + magnitude/=3; //water absorbs 2/3 velocity + + if(self.classname=="barrel"&&self.aflag)//rolling barrels are made for impacts! + magnitude*=3; + + if(self.frozen>0&&magnitude<300&&self.flags&FL_ONGROUND&&loser==world&&self.velocity_z<-20&&self.last_onground+0.3material == MAT_GLASS + || other->material == MAT_GLASS_METAL + || other->material == MAT_GRATE1 + || ((other->flags&FL_BBRUSH)&&(other->spawnflags&8/*THIN*/)) + || (other->r.svFlags&SVF_GLASS_BRUSH) ) + { + easyBreakBrush = qtrue; + } + + if ( !self->client || self->client->ps.lastOnGround+300client->ps.lastOnGround+100 < level.time && easyBreakBrush ) ) + { + vec3_t dir1, dir2; + float force = 0, dot; + + if ( easyBreakBrush ) + magnitude *= 2; + + //damage them + if ( magnitude >= 100 && other->s.number < ENTITYNUM_WORLD ) + { + VectorCopy( velocity, dir1 ); + VectorNormalize( dir1 ); + if( VectorCompare( other->r.currentOrigin, vec3_origin ) ) + {//a brush with no origin + VectorCopy ( dir1, dir2 ); + } + else + { + VectorSubtract( other->r.currentOrigin, self->r.currentOrigin, dir2 ); + VectorNormalize( dir2 ); + } + + dot = DotProduct( dir1, dir2 ); + + if ( dot >= 0.2 ) + { + force = dot; + } + else + { + force = 0; + } + + force *= (magnitude/50); + + cont = trap_PointContents( other->r.absmax, other->s.number ); + if( (cont&CONTENTS_WATER) )//|| (self.classname=="barrel"&&self.aflag))//FIXME: or other watertypes + { + force /= 3; //water absorbs 2/3 velocity + } + + /* + if(self.frozen>0&&force>10) + force=10; + */ + + if( ( force >= 1 && other->s.number != 0 ) || force >= 10) + { + /* + dprint("Damage other ("); + dprint(loser.classname); + dprint("): "); + dprint(ftos(force)); + dprint("\n"); + */ + if ( other->r.svFlags & SVF_GLASS_BRUSH ) + { + other->splashRadius = (float)(self->r.maxs[0] - self->r.mins[0])/4.0f; + } + if ( other->takedamage ) + { + G_Damage( other, self, self, velocity, self->r.currentOrigin, force, DAMAGE_NO_ARMOR, MOD_CRUSH);//FIXME: MOD_IMPACT + } + else + { + G_ApplyKnockback( other, dir2, force ); + } + } + } + + if ( damageSelf && self->takedamage ) + { + //Now damage me + //FIXME: more lenient falling damage, especially for when driving a vehicle + if ( self->client && self->client->ps.fd.forceJumpZStart ) + {//we were force-jumping + if ( self->r.currentOrigin[2] >= self->client->ps.fd.forceJumpZStart ) + {//we landed at same height or higher than we landed + magnitude = 0; + } + else + {//FIXME: take off some of it, at least? + magnitude = (self->client->ps.fd.forceJumpZStart-self->r.currentOrigin[2])/3; + } + } + //if(self.classname!="monster_mezzoman"&&self.netname!="spider")//Cats always land on their feet + if( ( magnitude >= 100 + self->health && self->s.number != 0 && self->s.weapon != WP_SABER ) || ( magnitude >= 700 ) )//&& self.safe_time < level.time ))//health here is used to simulate structural integrity + { + if ( (self->s.weapon == WP_SABER || self->s.number == 0) && self->client && self->client->ps.groundEntityNum < ENTITYNUM_NONE && magnitude < 1000 ) + {//players and jedi take less impact damage + //allow for some lenience on high falls + magnitude /= 2; + /* + if ( self.absorb_time >= time )//crouching on impact absorbs 1/2 the damage + { + magnitude/=2; + } + */ + } + magnitude /= 40; + magnitude = magnitude - force/2;//If damage other, subtract half of that damage off of own injury + if ( magnitude >= 1 ) + { + //FIXME: Put in a thingtype impact sound function + /* + dprint("Damage self ("); + dprint(self.classname); + dprint("): "); + dprint(ftos(magnitude)); + dprint("\n"); + */ + /* + if ( self.classname=="player_sheep "&& self.flags&FL_ONGROUND && self.velocity_z > -50 ) + return; + */ + G_Damage( self, NULL, NULL, NULL, self->r.currentOrigin, magnitude/2, DAMAGE_NO_ARMOR, MOD_FALLING );//FIXME: MOD_IMPACT + } + } + } + + //FIXME: slow my velocity some? + + // NOTENOTE We don't use lastimpact as of yet +// self->lastImpact = level.time; + + /* + if(self.flags&FL_ONGROUND) + self.last_onground=time; + */ + } +} + +void Client_CheckImpactBBrush( gentity_t *self, gentity_t *other ) +{ + if ( !other || !other->inuse ) + { + return; + } + if (!self || !self->inuse || !self->client || + self->client->tempSpectate >= level.time || + self->client->sess.sessionTeam == TEAM_SPECTATOR) + { //hmm.. let's not let spectators ram into breakables. + return; + } + + /* + if (BG_InSpecialJump(self->client->ps.legsAnim)) + { //don't do this either, qa says it creates "balance issues" + return; + } + */ + + if ( other->material == MAT_GLASS + || other->material == MAT_GLASS_METAL + || other->material == MAT_GRATE1 + || ((other->flags&FL_BBRUSH)&&(other->spawnflags&8/*THIN*/)) + || ((other->flags&FL_BBRUSH)&&(other->health<=10)) + || (other->r.svFlags&SVF_GLASS_BRUSH) ) + {//clients only do impact damage against easy-break breakables + DoImpact( self, other, qfalse ); + } +} + + +/* +=============== +G_SetClientSound +=============== +*/ +void G_SetClientSound( gentity_t *ent ) { + if (ent->client && ent->client->isHacking) + { //loop hacking sound + ent->client->ps.loopSound = level.snd_hack; + ent->s.loopIsSoundset = qfalse; + } + else if (ent->client && ent->client->isMedHealed > level.time) + { //loop healing sound + ent->client->ps.loopSound = level.snd_medHealed; + ent->s.loopIsSoundset = qfalse; + } + else if (ent->client && ent->client->isMedSupplied > level.time) + { //loop supplying sound + ent->client->ps.loopSound = level.snd_medSupplied; + ent->s.loopIsSoundset = qfalse; + } + else if (ent->waterlevel && (ent->watertype&(CONTENTS_LAVA|CONTENTS_SLIME)) ) { + ent->client->ps.loopSound = level.snd_fry; + ent->s.loopIsSoundset = qfalse; + } else { + ent->client->ps.loopSound = 0; + ent->s.loopIsSoundset = qfalse; + } +} + + + +//============================================================== + +/* +============== +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] ]; + + 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->r.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 + if ( hit->s.eType == ET_ITEM ) { + 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; + } +} + + +/* +============ +G_MoverTouchTriggers + +Find all trigger entities that ent's current position touches. +Spectators will only interact with teleporters. +============ +*/ +void G_MoverTouchPushTriggers( gentity_t *ent, vec3_t oldOrg ) +{ + int i, num; + float step, stepSize, dist; + int touch[MAX_GENTITIES]; + gentity_t *hit; + trace_t trace; + vec3_t mins, maxs, dir, size, checkSpot; + const vec3_t range = { 40, 40, 52 }; + + // non-moving movers don't hit triggers! + if ( !VectorLengthSquared( ent->s.pos.trDelta ) ) + { + return; + } + + VectorSubtract( ent->r.mins, ent->r.maxs, size ); + stepSize = VectorLength( size ); + if ( stepSize < 1 ) + { + stepSize = 1; + } + + VectorSubtract( ent->r.currentOrigin, oldOrg, dir ); + dist = VectorNormalize( dir ); + for ( step = 0; step <= dist; step += stepSize ) + { + VectorMA( ent->r.currentOrigin, step, dir, checkSpot ); + VectorSubtract( checkSpot, range, mins ); + VectorAdd( checkSpot, range, maxs ); + + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + // can't use ent->r.absmin, because that has a one unit pad + VectorAdd( checkSpot, ent->r.mins, mins ); + VectorAdd( checkSpot, ent->r.maxs, maxs ); + + for ( i=0 ; is.eType != ET_PUSH_TRIGGER ) + { + continue; + } + + if ( hit->touch == NULL ) + { + continue; + } + + if ( !( hit->r.contents & CONTENTS_TRIGGER ) ) + { + continue; + } + + + if ( !trap_EntityContact( mins, maxs, hit ) ) + { + continue; + } + + memset( &trace, 0, sizeof(trace) ); + + if ( hit->touch != NULL ) + { + hit->touch(hit, ent, &trace); + } + } + } +} + +/* +================= +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 + client->ps.basespeed = 400; + + //hmm, shouldn't have an anim if you're a spectator, make sure + //it gets cleared. + client->ps.legsAnim = 0; + client->ps.legsTimer = 0; + client->ps.torsoAnim = 0; + client->ps.torsoTimer = 0; + + // 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; + + pm.noSpecMove = g_noSpecMove.integer; + + pm.animations = NULL; + pm.nonHumanoid = qfalse; + + //Set up bg entity data + pm.baseEnt = (bgEntity_t *)g_entities; + pm.entSize = sizeof(gentity_t); + + // perform a pmove + Pmove (&pm); + // save results of pmove + VectorCopy( client->ps.origin, ent->s.origin ); + + if (ent->client->tempSpectate < level.time) + { + G_TouchTriggers( ent ); + } + trap_UnlinkEntity( ent ); + } + + client->oldbuttons = client->buttons; + client->buttons = ucmd->buttons; + + if (client->tempSpectate < level.time) + { + // attack button cycles through spectators + if ( ( client->buttons & BUTTON_ATTACK ) && ! ( client->oldbuttons & BUTTON_ATTACK ) ) { + Cmd_FollowCycle_f( ent, 1 ); + } + + if (client->sess.spectatorState == SPECTATOR_FOLLOW && (ucmd->upmove > 0)) + { //jump now removes you from follow mode + StopFollowing(ent); + } + } +} + + + +/* +================= +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|BUTTON_ALT_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; + + client = ent->client; + client->timeResidual += msec; + + while ( client->timeResidual >= 1000 ) + { + client->timeResidual -= 1000; + + // count down health when over max + if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] ) { + ent->health--; + } + + // count down armor when over max + if ( client->ps.stats[STAT_ARMOR] > client->ps.stats[STAT_MAX_HEALTH] ) { + client->ps.stats[STAT_ARMOR]--; + } + } +} + +/* +==================== +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; + } +} + +extern void NPC_SetAnim(gentity_t *ent,int setAnimParts,int anim,int setAnimFlags); +void G_VehicleAttachDroidUnit( gentity_t *vehEnt ) +{ + if ( vehEnt && vehEnt->m_pVehicle && vehEnt->m_pVehicle->m_pDroidUnit != NULL ) + { + gentity_t *droidEnt = (gentity_t *)vehEnt->m_pVehicle->m_pDroidUnit; + mdxaBone_t boltMatrix; + vec3_t fwd; + + trap_G2API_GetBoltMatrix(vehEnt->ghoul2, 0, vehEnt->m_pVehicle->m_iDroidUnitTag, &boltMatrix, vehEnt->r.currentAngles, vehEnt->r.currentOrigin, level.time, + NULL, vehEnt->modelScale); + BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, droidEnt->r.currentOrigin); + BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_Y, fwd); + vectoangles( fwd, droidEnt->r.currentAngles ); + + if ( droidEnt->client ) + { + VectorCopy( droidEnt->r.currentAngles, droidEnt->client->ps.viewangles ); + VectorCopy( droidEnt->r.currentOrigin, droidEnt->client->ps.origin ); + } + + G_SetOrigin( droidEnt, droidEnt->r.currentOrigin ); + trap_LinkEntity( droidEnt ); + + if ( droidEnt->NPC ) + { + NPC_SetAnim( droidEnt, SETANIM_BOTH, BOTH_STAND2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD ); + } + } +} + +//called gameside only from pmove code (convenience) +void G_CheapWeaponFire(int entNum, int ev) +{ + gentity_t *ent = &g_entities[entNum]; + + if (!ent->inuse || !ent->client) + { + return; + } + + switch (ev) + { + case EV_FIRE_WEAPON: + if (ent->m_pVehicle && ent->m_pVehicle->m_pVehicleInfo->type == VH_SPEEDER && + ent->client && ent->client->ps.m_iVehicleNum) + { //a speeder with a pilot + gentity_t *rider = &g_entities[ent->client->ps.m_iVehicleNum-1]; + if (rider->inuse && rider->client) + { //pilot is valid... + if (rider->client->ps.weapon != WP_MELEE && + (rider->client->ps.weapon != WP_SABER || !rider->client->ps.saberHolstered)) + { //can only attack on speeder when using melee or when saber is holstered + break; + } + } + } + + FireWeapon( ent, qfalse ); + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + case EV_ALT_FIRE: + FireWeapon( ent, qtrue ); + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + } +} + +/* +================ +ClientEvents + +Events will be passed on to the clients for presentation, +but any server game effects are handled here +================ +*/ +#include "../namespace_begin.h" +qboolean BG_InKnockDownOnly( int anim ); +#include "../namespace_end.h" + +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: + case EV_ROLL: + { + int delta = client->ps.eventParms[ i & (MAX_PS_EVENTS-1) ]; + qboolean knockDownage = qfalse; + + if (ent->client && ent->client->ps.fallingToDeath) + { + break; + } + + if ( ent->s.eType != ET_PLAYER ) + { + break; // not in the player model + } + + if ( g_dmflags.integer & DF_NO_FALLING ) + { + break; + } + + if (BG_InKnockDownOnly(ent->client->ps.legsAnim)) + { + if (delta <= 14) + { + break; + } + knockDownage = qtrue; + } + else + { + if (delta <= 44) + { + break; + } + } + + if (knockDownage) + { + damage = delta*1; //you suffer for falling unprepared. A lot. Makes throws and things useful, and more realistic I suppose. + } + else + { + if (g_gametype.integer == GT_SIEGE && + delta > 60) + { //longer falls hurt more + damage = delta*1; //good enough for now, I guess + } + else + { + damage = delta*0.16; //good enough for now, I guess + } + } + + VectorSet (dir, 0, 0, 1); + ent->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage (ent, NULL, NULL, NULL, NULL, damage, DAMAGE_NO_ARMOR, MOD_FALLING); + + if (ent->health < 1) + { + G_Sound(ent, CHAN_AUTO, G_SoundIndex( "sound/player/fallsplat.wav" )); + } + } + break; + case EV_FIRE_WEAPON: + FireWeapon( ent, qfalse ); + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + + case EV_ALT_FIRE: + FireWeapon( ent, qtrue ); + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + + case EV_SABER_ATTACK: + ent->client->dangerTime = level.time; + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + ent->client->invulnerableTimer = 0; + break; + + //rww - Note that these must be in the same order (ITEM#-wise) as they are in holdable_t + case EV_USE_ITEM1: //seeker droid + ItemUse_Seeker(ent); + break; + case EV_USE_ITEM2: //shield + ItemUse_Shield(ent); + break; + case EV_USE_ITEM3: //medpack + ItemUse_MedPack(ent); + break; + case EV_USE_ITEM4: //big medpack + ItemUse_MedPack_Big(ent); + break; + case EV_USE_ITEM5: //binoculars + ItemUse_Binoculars(ent); + break; + case EV_USE_ITEM6: //sentry gun + ItemUse_Sentry(ent); + break; + case EV_USE_ITEM7: //jetpack + ItemUse_Jetpack(ent); + break; + case EV_USE_ITEM8: //health disp + //ItemUse_UseDisp(ent, HI_HEALTHDISP); + break; + case EV_USE_ITEM9: //ammo disp + //ItemUse_UseDisp(ent, HI_AMMODISP); + break; + case EV_USE_ITEM10: //eweb + ItemUse_UseEWeb(ent); + break; + case EV_USE_ITEM11: //cloak + ItemUse_UseCloak(ent); + break; + default: + break; + } + } + +} + +/* +============== +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; + } +} + +/* +================== +G_UpdateClientBroadcasts + +Determines whether this client should be broadcast to any other clients. +A client is broadcast when another client is using force sight or is +================== +*/ +#define MAX_JEDIMASTER_DISTANCE 2500 +#define MAX_JEDIMASTER_FOV 100 + +#define MAX_SIGHT_DISTANCE 1500 +#define MAX_SIGHT_FOV 100 + +static void G_UpdateForceSightBroadcasts ( gentity_t *self ) +{ + int i; + + // Any clients with force sight on should see this client + for ( i = 0; i < level.numConnectedClients; i ++ ) + { + gentity_t *ent = &g_entities[level.sortedClients[i]]; + float dist; + vec3_t angles; + + if ( ent == self ) + { + continue; + } + + // Not using force sight so we shouldnt broadcast to this one + if ( !(ent->client->ps.fd.forcePowersActive & (1<client->ps.origin, ent->client->ps.origin, angles ); + dist = VectorLengthSquared ( angles ); + vectoangles ( angles, angles ); + + // Too far away then just forget it + if ( dist > MAX_SIGHT_DISTANCE * MAX_SIGHT_DISTANCE ) + { + continue; + } + + // If not within the field of view then forget it + if ( !InFieldOfVision ( ent->client->ps.viewangles, MAX_SIGHT_FOV, angles ) ) + { + break; + } + + // Turn on the broadcast bit for the master and since there is only one + // master we are done + self->r.broadcastClients[ent->s.clientNum/32] |= (1 << (ent->s.clientNum%32)); + + break; + } +} + +static void G_UpdateJediMasterBroadcasts ( gentity_t *self ) +{ + int i; + + // Not jedi master mode then nothing to do + if ( g_gametype.integer != GT_JEDIMASTER ) + { + return; + } + + // This client isnt the jedi master so it shouldnt broadcast + if ( !self->client->ps.isJediMaster ) + { + return; + } + + // Broadcast ourself to all clients within range + for ( i = 0; i < level.numConnectedClients; i ++ ) + { + gentity_t *ent = &g_entities[level.sortedClients[i]]; + float dist; + vec3_t angles; + + if ( ent == self ) + { + continue; + } + + VectorSubtract( self->client->ps.origin, ent->client->ps.origin, angles ); + dist = VectorLengthSquared ( angles ); + vectoangles ( angles, angles ); + + // Too far away then just forget it + if ( dist > MAX_JEDIMASTER_DISTANCE * MAX_JEDIMASTER_DISTANCE ) + { + continue; + } + + // If not within the field of view then forget it + if ( !InFieldOfVision ( ent->client->ps.viewangles, MAX_JEDIMASTER_FOV, angles ) ) + { + continue; + } + + // Turn on the broadcast bit for the master and since there is only one + // master we are done + self->r.broadcastClients[ent->s.clientNum/32] |= (1 << (ent->s.clientNum%32)); + } +} + +void G_UpdateClientBroadcasts ( gentity_t *self ) +{ + // Clear all the broadcast bits for this client + memset ( self->r.broadcastClients, 0, sizeof ( self->r.broadcastClients ) ); + + // The jedi master is broadcast to everyone in range + G_UpdateJediMasterBroadcasts ( self ); + + // Anyone with force sight on should see this client + G_UpdateForceSightBroadcasts ( self ); +} + +void G_AddPushVecToUcmd( gentity_t *self, usercmd_t *ucmd ) +{ + vec3_t forward, right, moveDir; + float pushSpeed, fMove, rMove; + + if ( !self->client ) + { + return; + } + pushSpeed = VectorLengthSquared(self->client->pushVec); + if(!pushSpeed) + {//not being pushed + return; + } + + AngleVectors(self->client->ps.viewangles, forward, right, NULL); + VectorScale(forward, ucmd->forwardmove/127.0f * self->client->ps.speed, moveDir); + VectorMA(moveDir, ucmd->rightmove/127.0f * self->client->ps.speed, right, moveDir); + //moveDir is now our intended move velocity + + VectorAdd(moveDir, self->client->pushVec, moveDir); + self->client->ps.speed = VectorNormalize(moveDir); + //moveDir is now our intended move velocity plus our push Vector + + fMove = 127.0 * DotProduct(forward, moveDir); + rMove = 127.0 * DotProduct(right, moveDir); + ucmd->forwardmove = floor(fMove);//If in the same dir , will be positive + ucmd->rightmove = floor(rMove);//If in the same dir , will be positive + + if ( self->client->pushVecTime < level.time ) + { + VectorClear( self->client->pushVec ); + } +} + +qboolean G_StandingAnim( int anim ) +{//NOTE: does not check idles or special (cinematic) stands + switch ( anim ) + { + case BOTH_STAND1: + case BOTH_STAND2: + case BOTH_STAND3: + case BOTH_STAND4: + return qtrue; + break; + } + return qfalse; +} + +qboolean G_ActionButtonPressed(int buttons) +{ + if (buttons & BUTTON_ATTACK) + { + return qtrue; + } + else if (buttons & BUTTON_USE_HOLDABLE) + { + return qtrue; + } + else if (buttons & BUTTON_GESTURE) + { + return qtrue; + } + else if (buttons & BUTTON_USE) + { + return qtrue; + } + else if (buttons & BUTTON_FORCEGRIP) + { + return qtrue; + } + else if (buttons & BUTTON_ALT_ATTACK) + { + return qtrue; + } + else if (buttons & BUTTON_FORCEPOWER) + { + return qtrue; + } + else if (buttons & BUTTON_FORCE_LIGHTNING) + { + return qtrue; + } + else if (buttons & BUTTON_FORCE_DRAIN) + { + return qtrue; + } + + return qfalse; +} + +void G_CheckClientIdle( gentity_t *ent, usercmd_t *ucmd ) +{ + vec3_t viewChange; + qboolean actionPressed; + int buttons; + + if ( !ent || !ent->client || ent->health <= 0 || ent->client->ps.stats[STAT_HEALTH] <= 0 || + ent->client->sess.sessionTeam == TEAM_SPECTATOR || (ent->client->ps.pm_flags & PMF_FOLLOW)) + { + return; + } + + buttons = ucmd->buttons; + + if (ent->r.svFlags & SVF_BOT) + { //they press use all the time.. + buttons &= ~BUTTON_USE; + } + actionPressed = G_ActionButtonPressed(buttons); + + VectorSubtract(ent->client->ps.viewangles, ent->client->idleViewAngles, viewChange); + if ( !VectorCompare( vec3_origin, ent->client->ps.velocity ) + || actionPressed || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove + || !G_StandingAnim( ent->client->ps.legsAnim ) + || (ent->health+ent->client->ps.stats[STAT_ARMOR]) != ent->client->idleHealth + || VectorLength(viewChange) > 10 + || ent->client->ps.legsTimer > 0 + || ent->client->ps.torsoTimer > 0 + || ent->client->ps.weaponTime > 0 + || ent->client->ps.weaponstate == WEAPON_CHARGING + || ent->client->ps.weaponstate == WEAPON_CHARGING_ALT + || ent->client->ps.zoomMode + || (ent->client->ps.weaponstate != WEAPON_READY && ent->client->ps.weapon != WP_SABER) + || ent->client->ps.forceHandExtend != HANDEXTEND_NONE + || ent->client->ps.saberBlocked != BLOCKED_NONE + || ent->client->ps.saberBlocking >= level.time + || ent->client->ps.weapon == WP_MELEE + || (ent->client->ps.weapon != ent->client->pers.cmd.weapon && ent->s.eType != ET_NPC)) + {//FIXME: also check for turning? + qboolean brokeOut = qfalse; + + if ( !VectorCompare( vec3_origin, ent->client->ps.velocity ) + || actionPressed || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove + || (ent->health+ent->client->ps.stats[STAT_ARMOR]) != ent->client->idleHealth + || ent->client->ps.zoomMode + || (ent->client->ps.weaponstate != WEAPON_READY && ent->client->ps.weapon != WP_SABER) + || (ent->client->ps.weaponTime > 0 && ent->client->ps.weapon == WP_SABER) + || ent->client->ps.weaponstate == WEAPON_CHARGING + || ent->client->ps.weaponstate == WEAPON_CHARGING_ALT + || ent->client->ps.forceHandExtend != HANDEXTEND_NONE + || ent->client->ps.saberBlocked != BLOCKED_NONE + || ent->client->ps.saberBlocking >= level.time + || ent->client->ps.weapon == WP_MELEE + || (ent->client->ps.weapon != ent->client->pers.cmd.weapon && ent->s.eType != ET_NPC)) + { + //if in an idle, break out + switch ( ent->client->ps.legsAnim ) + { + case BOTH_STAND1IDLE1: + case BOTH_STAND2IDLE1: + case BOTH_STAND2IDLE2: + case BOTH_STAND3IDLE1: + case BOTH_STAND5IDLE1: + ent->client->ps.legsTimer = 0; + brokeOut = qtrue; + break; + } + switch ( ent->client->ps.torsoAnim ) + { + case BOTH_STAND1IDLE1: + case BOTH_STAND2IDLE1: + case BOTH_STAND2IDLE2: + case BOTH_STAND3IDLE1: + case BOTH_STAND5IDLE1: + ent->client->ps.torsoTimer = 0; + ent->client->ps.weaponTime = 0; + ent->client->ps.saberMove = LS_READY; + brokeOut = qtrue; + break; + } + } + // + ent->client->idleHealth = (ent->health+ent->client->ps.stats[STAT_ARMOR]); + VectorCopy(ent->client->ps.viewangles, ent->client->idleViewAngles); + if ( ent->client->idleTime < level.time ) + { + ent->client->idleTime = level.time; + } + + if (brokeOut && + (ent->client->ps.weaponstate == WEAPON_CHARGING || ent->client->ps.weaponstate == WEAPON_CHARGING_ALT)) + { + ent->client->ps.torsoAnim = TORSO_RAISEWEAP1; + } + } + else if ( level.time - ent->client->idleTime > 5000 ) + {//been idle for 5 seconds + int idleAnim = -1; + switch ( ent->client->ps.legsAnim ) + { + case BOTH_STAND1: + idleAnim = BOTH_STAND1IDLE1; + break; + case BOTH_STAND2: + idleAnim = BOTH_STAND2IDLE1;//Q_irand(BOTH_STAND2IDLE1,BOTH_STAND2IDLE2); + break; + case BOTH_STAND3: + idleAnim = BOTH_STAND3IDLE1; + break; + case BOTH_STAND5: + idleAnim = BOTH_STAND5IDLE1; + break; + } + + if (idleAnim == BOTH_STAND2IDLE1 && Q_irand(1, 10) <= 5) + { + idleAnim = BOTH_STAND2IDLE2; + } + + if ( idleAnim != -1 && /*PM_HasAnimation( ent, idleAnim )*/idleAnim > 0 && idleAnim < MAX_ANIMATIONS ) + { + G_SetAnim(ent, ucmd, SETANIM_BOTH, idleAnim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + + //don't idle again after this anim for a while + //ent->client->idleTime = level.time + PM_AnimLength( ent->client->clientInfo.animFileIndex, (animNumber_t)idleAnim ) + Q_irand( 0, 2000 ); + ent->client->idleTime = level.time + ent->client->ps.legsTimer + Q_irand( 0, 2000 ); + } + } +} + +void NPC_Accelerate( gentity_t *ent, qboolean fullWalkAcc, qboolean fullRunAcc ) +{ + if ( !ent->client || !ent->NPC ) + { + return; + } + + if ( !ent->NPC->stats.acceleration ) + {//No acceleration means just start and stop + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + //FIXME: in cinematics always accel/decel? + else if ( ent->NPC->desiredSpeed <= ent->NPC->stats.walkSpeed ) + {//Only accelerate if at walkSpeeds + if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration ) + { + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed += ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed ) + { + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + else if ( fullWalkAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration ) + {//decelerate even when walking + ent->NPC->currentSpeed -= ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) + {//stop on a dime + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + } + else// if ( ent->NPC->desiredSpeed > ent->NPC->stats.walkSpeed ) + {//Only decelerate if at runSpeeds + if ( fullRunAcc && ent->NPC->desiredSpeed > ent->NPC->currentSpeed + ent->NPC->stats.acceleration ) + {//Accelerate to runspeed + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed += ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed > ent->NPC->currentSpeed ) + {//accelerate instantly + //ent->client->ps.friction = 0; + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + else if ( fullRunAcc && ent->NPC->desiredSpeed < ent->NPC->currentSpeed - ent->NPC->stats.acceleration ) + { + ent->NPC->currentSpeed -= ent->NPC->stats.acceleration; + } + else if ( ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) + { + ent->NPC->currentSpeed = ent->NPC->desiredSpeed; + } + } +} + +/* +------------------------- +NPC_GetWalkSpeed +------------------------- +*/ + +static int NPC_GetWalkSpeed( gentity_t *ent ) +{ + int walkSpeed = 0; + + if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) ) + return 0; + + switch ( ent->client->playerTeam ) + { + case NPCTEAM_PLAYER: //To shutup compiler, will add entries later (this is stub code) + default: + walkSpeed = ent->NPC->stats.walkSpeed; + break; + } + + return walkSpeed; +} + +/* +------------------------- +NPC_GetRunSpeed +------------------------- +*/ +static int NPC_GetRunSpeed( gentity_t *ent ) +{ + int runSpeed = 0; + + if ( ( ent->client == NULL ) || ( ent->NPC == NULL ) ) + return 0; +/* + switch ( ent->client->playerTeam ) + { + case TEAM_BORG: + runSpeed = ent->NPC->stats.runSpeed; + runSpeed += BORG_RUN_INCR * (g_spskill->integer%3); + break; + + case TEAM_8472: + runSpeed = ent->NPC->stats.runSpeed; + runSpeed += SPECIES_RUN_INCR * (g_spskill->integer%3); + break; + + case TEAM_STASIS: + runSpeed = ent->NPC->stats.runSpeed; + runSpeed += STASIS_RUN_INCR * (g_spskill->integer%3); + break; + + case TEAM_BOTS: + runSpeed = ent->NPC->stats.runSpeed; + break; + + default: + runSpeed = ent->NPC->stats.runSpeed; + break; + } +*/ + // team no longer indicates species/race. Use NPC_class to adjust speed for specific npc types + switch( ent->client->NPC_class) + { + case CLASS_PROBE: // droid cases here to shut-up compiler + case CLASS_GONK: + case CLASS_R2D2: + case CLASS_R5D2: + case CLASS_MARK1: + case CLASS_MARK2: + case CLASS_PROTOCOL: + case CLASS_ATST: // hmm, not really your average droid + case CLASS_MOUSE: + case CLASS_SEEKER: + case CLASS_REMOTE: + runSpeed = ent->NPC->stats.runSpeed; + break; + + default: + runSpeed = ent->NPC->stats.runSpeed*1.3f; //rww - seems to slow in MP for some reason. + break; + } + + return runSpeed; +} + +//Seems like a slightly less than ideal method for this, could it be done on the client? +extern qboolean FlyingCreature( gentity_t *ent ); +void G_CheckMovingLoopingSounds( gentity_t *ent, usercmd_t *ucmd ) +{ + if ( ent->client ) + { + if ( (ent->NPC&&!VectorCompare( vec3_origin, ent->client->ps.moveDir ))//moving using moveDir + || ucmd->forwardmove || ucmd->rightmove//moving using ucmds + || (ucmd->upmove&&FlyingCreature( ent ))//flier using ucmds to move + || (FlyingCreature( ent )&&!VectorCompare( vec3_origin, ent->client->ps.velocity )&&ent->health>0))//flier using velocity to move + { + switch( ent->client->NPC_class ) + { + case CLASS_R2D2: + ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp.wav" ); + break; + case CLASS_R5D2: + ent->s.loopSound = G_SoundIndex( "sound/chars/r2d2/misc/r2_move_lp2.wav" ); + break; + case CLASS_MARK2: + ent->s.loopSound = G_SoundIndex( "sound/chars/mark2/misc/mark2_move_lp" ); + break; + case CLASS_MOUSE: + ent->s.loopSound = G_SoundIndex( "sound/chars/mouse/misc/mouse_lp" ); + break; + case CLASS_PROBE: + ent->s.loopSound = G_SoundIndex( "sound/chars/probe/misc/probedroidloop" ); + } + } + else + {//not moving under your own control, stop loopSound + if ( ent->client->NPC_class == CLASS_R2D2 || ent->client->NPC_class == CLASS_R5D2 + || ent->client->NPC_class == CLASS_MARK2 || ent->client->NPC_class == CLASS_MOUSE + || ent->client->NPC_class == CLASS_PROBE ) + { + ent->s.loopSound = 0; + } + } + } +} + +void G_HeldByMonster( gentity_t *ent, usercmd_t **ucmd ) +{ + if ( ent + && ent->client + && ent->client->ps.hasLookTarget )//NOTE: lookTarget is an entity number, so this presumes that client 0 is NOT a Rancor... + { + gentity_t *monster = &g_entities[ent->client->ps.lookTarget]; + if ( monster && monster->client ) + { + //take the monster's waypoint as your own + ent->waypoint = monster->waypoint; + if ( monster->s.NPC_class == CLASS_RANCOR ) + {//only possibility right now, may add Wampa and Sand Creature later + BG_AttachToRancor( monster->ghoul2, //ghoul2 info + monster->r.currentAngles[YAW], + monster->r.currentOrigin, + level.time, + NULL, + monster->modelScale, + (monster->client->ps.eFlags2&EF2_GENERIC_NPC_FLAG), + ent->client->ps.origin, + ent->client->ps.viewangles, + NULL ); + } + VectorClear( ent->client->ps.velocity ); + G_SetOrigin( ent, ent->client->ps.origin ); + SetClientViewAngle( ent, ent->client->ps.viewangles ); + G_SetAngles( ent, ent->client->ps.viewangles ); + trap_LinkEntity( ent );//redundant? + } + } + // don't allow movement, weapon switching, and most kinds of button presses + (*ucmd)->forwardmove = 0; + (*ucmd)->rightmove = 0; + (*ucmd)->upmove = 0; +} + +typedef enum +{ + TAUNT_TAUNT = 0, + TAUNT_BOW, + TAUNT_MEDITATE, + TAUNT_FLOURISH, + TAUNT_GLOAT +}; + +void G_SetTauntAnim( gentity_t *ent, int taunt ) +{ + if (ent->client->pers.cmd.upmove || + ent->client->pers.cmd.forwardmove || + ent->client->pers.cmd.rightmove) + { //hack, don't do while moving + return; + } + if ( taunt != TAUNT_TAUNT ) + {//normal taunt always allowed + if ( g_gametype.integer != GT_DUEL + && g_gametype.integer != GT_POWERDUEL ) + {//no taunts unless in Duel + return; + } + } + if ( ent->client->ps.torsoTimer < 1 + && ent->client->ps.forceHandExtend == HANDEXTEND_NONE + && ent->client->ps.legsTimer < 1 + && ent->client->ps.weaponTime < 1 + && ent->client->ps.saberLockTime < level.time ) + { + int anim = -1; + switch ( taunt ) + { + case TAUNT_TAUNT: + if ( ent->client->ps.weapon != WP_SABER ) + { + anim = BOTH_ENGAGETAUNT; + } + else if ( ent->client->saber[0].tauntAnim != -1 ) + { + anim = ent->client->saber[0].tauntAnim; + } + else if ( ent->client->saber[1].model + && ent->client->saber[1].model[0] + && ent->client->saber[1].tauntAnim != -1 ) + { + anim = ent->client->saber[1].tauntAnim; + } + else + { + switch ( ent->client->ps.fd.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn off second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); + } + else if ( ent->client->ps.saberHolstered == 0 ) + {//turn off first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + } + ent->client->ps.saberHolstered = 2; + anim = BOTH_GESTURE1; + break; + case SS_MEDIUM: + case SS_STRONG: + case SS_DESANN: + anim = BOTH_ENGAGETAUNT; + break; + case SS_DUAL: + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn on second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOn ); + } + else if ( ent->client->ps.saberHolstered == 2 ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_DUAL_TAUNT; + break; + case SS_STAFF: + if ( ent->client->ps.saberHolstered > 0 ) + {//turn on all blades + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_STAFF_TAUNT; + break; + } + } + break; + case TAUNT_BOW: + if ( ent->client->saber[0].bowAnim != -1 ) + { + anim = ent->client->saber[0].bowAnim; + } + else if ( ent->client->saber[1].model + && ent->client->saber[1].model[0] + && ent->client->saber[1].bowAnim != -1 ) + { + anim = ent->client->saber[1].bowAnim; + } + else + { + anim = BOTH_BOW; + } + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn off second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); + } + else if ( ent->client->ps.saberHolstered == 0 ) + {//turn off first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + } + ent->client->ps.saberHolstered = 2; + break; + case TAUNT_MEDITATE: + if ( ent->client->saber[0].meditateAnim != -1 ) + { + anim = ent->client->saber[0].meditateAnim; + } + else if ( ent->client->saber[1].model + && ent->client->saber[1].model[0] + && ent->client->saber[1].meditateAnim != -1 ) + { + anim = ent->client->saber[1].meditateAnim; + } + else + { + anim = BOTH_MEDITATE; + } + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn off second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOff ); + } + else if ( ent->client->ps.saberHolstered == 0 ) + {//turn off first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOff ); + } + ent->client->ps.saberHolstered = 2; + break; + case TAUNT_FLOURISH: + if ( ent->client->ps.weapon == WP_SABER ) + { + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn on second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOn ); + } + else if ( ent->client->ps.saberHolstered == 2 ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + if ( ent->client->saber[0].flourishAnim != -1 ) + { + anim = ent->client->saber[0].flourishAnim; + } + else if ( ent->client->saber[1].model + && ent->client->saber[1].model[0] + && ent->client->saber[1].flourishAnim != -1 ) + { + anim = ent->client->saber[1].flourishAnim; + } + else + { + switch ( ent->client->ps.fd.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + anim = BOTH_SHOWOFF_FAST; + break; + case SS_MEDIUM: + anim = BOTH_SHOWOFF_MEDIUM; + break; + case SS_STRONG: + case SS_DESANN: + anim = BOTH_SHOWOFF_STRONG; + break; + case SS_DUAL: + anim = BOTH_SHOWOFF_DUAL; + break; + case SS_STAFF: + anim = BOTH_SHOWOFF_STAFF; + break; + } + } + } + break; + case TAUNT_GLOAT: + if ( ent->client->saber[0].gloatAnim != -1 ) + { + anim = ent->client->saber[0].gloatAnim; + } + else if ( ent->client->saber[1].model + && ent->client->saber[1].model[0] + && ent->client->saber[1].gloatAnim != -1 ) + { + anim = ent->client->saber[1].gloatAnim; + } + else + { + switch ( ent->client->ps.fd.saberAnimLevel ) + { + case SS_FAST: + case SS_TAVION: + anim = BOTH_VICTORY_FAST; + break; + case SS_MEDIUM: + anim = BOTH_VICTORY_MEDIUM; + break; + case SS_STRONG: + case SS_DESANN: + if ( ent->client->ps.saberHolstered ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_VICTORY_STRONG; + break; + case SS_DUAL: + if ( ent->client->ps.saberHolstered == 1 + && ent->client->saber[1].model + && ent->client->saber[1].model[0] ) + {//turn on second saber + G_Sound( ent, CHAN_WEAPON, ent->client->saber[1].soundOn ); + } + else if ( ent->client->ps.saberHolstered == 2 ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_VICTORY_DUAL; + break; + case SS_STAFF: + if ( ent->client->ps.saberHolstered ) + {//turn on first + G_Sound( ent, CHAN_WEAPON, ent->client->saber[0].soundOn ); + } + ent->client->ps.saberHolstered = 0; + anim = BOTH_VICTORY_STAFF; + break; + } + } + break; + } + if ( anim != -1 ) + { + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + { + ent->client->ps.forceHandExtend = HANDEXTEND_TAUNT; + ent->client->ps.forceDodgeAnim = anim; + ent->client->ps.forceHandExtendTime = level.time + BG_AnimLength(ent->localAnimIndex, (animNumber_t)anim); + } + if ( taunt != TAUNT_MEDITATE + && taunt != TAUNT_BOW ) + {//no sound for meditate or bow + G_AddEvent( ent, EV_TAUNT, taunt ); + } + } + } +} + +/* +============== +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; + qboolean isNPC = qfalse; + qboolean controlledByPlayer = qfalse; + qboolean killJetFlags = qtrue; + + client = ent->client; + + if (ent->s.eType == ET_NPC) + { + isNPC = qtrue; + } + + // don't think if the client is not yet connected (and thus not yet spawned in) + if (client->pers.connected != CON_CONNECTED && !isNPC) { + return; + } + + // This code was moved here from clientThink to fix a problem with g_synchronousClients + // being set to 1 when in vehicles. + if ( ent->s.number < MAX_CLIENTS && ent->client->ps.m_iVehicleNum ) + {//driving a vehicle + if (g_entities[ent->client->ps.m_iVehicleNum].client) + { + gentity_t *veh = &g_entities[ent->client->ps.m_iVehicleNum]; + + if (veh->m_pVehicle && + veh->m_pVehicle->m_pPilot == (bgEntity_t *)ent) + { //only take input from the pilot... + veh->client->ps.commandTime = ent->client->ps.commandTime; + memcpy(&veh->m_pVehicle->m_ucmd, &ent->client->pers.cmd, sizeof(usercmd_t)); + if ( veh->m_pVehicle->m_ucmd.buttons & BUTTON_TALK ) + { //forced input if "chat bubble" is up + veh->m_pVehicle->m_ucmd.buttons = BUTTON_TALK; + veh->m_pVehicle->m_ucmd.forwardmove = 0; + veh->m_pVehicle->m_ucmd.rightmove = 0; + veh->m_pVehicle->m_ucmd.upmove = 0; + } + } + } + } + + if (!(client->ps.pm_flags & PMF_FOLLOW)) + { + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 && + bgSiegeClasses[client->siegeClass].saberStance) + { //the class says we have to use this stance set. + if (!(bgSiegeClasses[client->siegeClass].saberStance & (1 << client->ps.fd.saberAnimLevel))) + { //the current stance is not in the bitmask, so find the first one that is. + int i = SS_FAST; + + while (i < SS_NUM_SABER_STYLES) + { + if (bgSiegeClasses[client->siegeClass].saberStance & (1 << i)) + { + if (i == SS_DUAL + && client->ps.saberHolstered == 1 ) + {//one saber should be off, adjust saberAnimLevel accordinly + client->ps.fd.saberAnimLevelBase = i; + client->ps.fd.saberAnimLevel = SS_FAST; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + else if ( i == SS_STAFF + && client->ps.saberHolstered == 1 + && client->saber[0].singleBladeStyle != SS_NONE) + {//one saber or blade should be off, adjust saberAnimLevel accordinly + client->ps.fd.saberAnimLevelBase = i; + client->ps.fd.saberAnimLevel = client->saber[0].singleBladeStyle; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + else + { + client->ps.fd.saberAnimLevelBase = client->ps.fd.saberAnimLevel = i; + client->ps.fd.saberDrawAnimLevel = i; + } + break; + } + + i++; + } + } + } + else if (client->saber[0].model[0] && client->saber[1].model[0]) + { //with two sabs always use akimbo style + if ( client->ps.saberHolstered == 1 ) + {//one saber should be off, adjust saberAnimLevel accordinly + client->ps.fd.saberAnimLevelBase = SS_DUAL; + client->ps.fd.saberAnimLevel = SS_FAST; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + else + { + if ( !WP_SaberStyleValidForSaber( &client->saber[0], &client->saber[1], client->ps.saberHolstered, client->ps.fd.saberAnimLevel ) ) + {//only use dual style if the style we're trying to use isn't valid + client->ps.fd.saberAnimLevelBase = client->ps.fd.saberAnimLevel = SS_DUAL; + } + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + } + else + { + if (client->saber[0].stylesLearned == (1<ps.fd.saberAnimLevelBase = SS_STAFF; + } + if ( client->ps.fd.saberAnimLevelBase == SS_STAFF ) + {//using staff style + if ( client->ps.saberHolstered == 1 + && client->saber[0].singleBladeStyle != SS_NONE) + {//one blade should be off, adjust saberAnimLevel accordinly + client->ps.fd.saberAnimLevel = client->saber[0].singleBladeStyle; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + else + { + client->ps.fd.saberAnimLevel = SS_STAFF; + client->ps.fd.saberDrawAnimLevel = client->ps.fd.saberAnimLevel; + } + } + } + } + + // mark the time, so the connection sprite can be removed + ucmd = &ent->client->pers.cmd; + + if ( client && (client->ps.eFlags2&EF2_HELD_BY_MONSTER) ) + { + G_HeldByMonster( ent, &ucmd ); + } + + // 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" ); + } + + if (isNPC && (ucmd->serverTime - client->ps.commandTime) < 1) + { + ucmd->serverTime = client->ps.commandTime + 100; + } + + 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 ) + { + if ( ent->s.number < MAX_CLIENTS + || client->NPC_class == CLASS_VEHICLE ) + {//players and vehicles do nothing in intermissions + ClientIntermissionThink( client ); + return; + } + } + + // spectators don't do much + if ( client->sess.sessionTeam == TEAM_SPECTATOR || client->tempSpectate > level.time ) { + if ( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) { + return; + } + SpectatorThink( ent, ucmd ); + return; + } + + if (ent && ent->client && (ent->client->ps.eFlags & EF_INVULNERABLE)) + { + if (ent->client->invulnerableTimer <= level.time) + { + ent->client->ps.eFlags &= ~EF_INVULNERABLE; + } + } + + if (ent->s.eType != ET_NPC) + { + // check for inactivity timer, but never drop the local client of a non-dedicated server + if ( !ClientInactivityTimer( client ) ) { + return; + } + } + + //Check if we should have a fullbody push effect around the player + if (client->pushEffectTime > level.time) + { + client->ps.eFlags |= EF_BODYPUSH; + } + else if (client->pushEffectTime) + { + client->pushEffectTime = 0; + client->ps.eFlags &= ~EF_BODYPUSH; + } + + if (client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_JETPACK)) + { + client->ps.eFlags |= EF_JETPACK; + } + else + { + client->ps.eFlags &= ~EF_JETPACK; + } + + if ( client->noclip ) { + client->ps.pm_type = PM_NOCLIP; + } else if ( client->ps.eFlags & EF_DISINTEGRATION ) { + client->ps.pm_type = PM_NOCLIP; + } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + client->ps.pm_type = PM_DEAD; + } else { + if (client->ps.forceGripChangeMovetype) + { + client->ps.pm_type = client->ps.forceGripChangeMovetype; + } + else + { + if (client->jetPackOn) + { + client->ps.pm_type = PM_JETPACK; + client->ps.eFlags |= EF_JETPACK_ACTIVE; + killJetFlags = qfalse; + } + else + { + client->ps.pm_type = PM_NORMAL; + } + } + } + + if (killJetFlags) + { + client->ps.eFlags &= ~EF_JETPACK_ACTIVE; + client->ps.eFlags &= ~EF_JETPACK_FLAMING; + } + +#define SLOWDOWN_DIST 128.0f +#define MIN_NPC_SPEED 16.0f + + if (client->bodyGrabIndex != ENTITYNUM_NONE) + { + gentity_t *grabbed = &g_entities[client->bodyGrabIndex]; + + if (!grabbed->inuse || grabbed->s.eType != ET_BODY || + (grabbed->s.eFlags & EF_DISINTEGRATION) || + (grabbed->s.eFlags & EF_NODRAW)) + { + if (grabbed->inuse && grabbed->s.eType == ET_BODY) + { + grabbed->s.ragAttach = 0; + } + client->bodyGrabIndex = ENTITYNUM_NONE; + } + else + { + mdxaBone_t rhMat; + vec3_t rhOrg, tAng; + vec3_t bodyDir; + float bodyDist; + + ent->client->ps.forceHandExtend = HANDEXTEND_DRAGGING; + + if (ent->client->ps.forceHandExtendTime < level.time + 500) + { + ent->client->ps.forceHandExtendTime = level.time + 1000; + } + + VectorSet(tAng, 0, ent->client->ps.viewangles[YAW], 0); + trap_G2API_GetBoltMatrix(ent->ghoul2, 0, 0, &rhMat, tAng, ent->client->ps.origin, level.time, + NULL, ent->modelScale); //0 is always going to be right hand bolt + BG_GiveMeVectorFromMatrix(&rhMat, ORIGIN, rhOrg); + + VectorSubtract(rhOrg, grabbed->r.currentOrigin, bodyDir); + bodyDist = VectorLength(bodyDir); + + if (bodyDist > 40.0f) + { //can no longer reach + grabbed->s.ragAttach = 0; + client->bodyGrabIndex = ENTITYNUM_NONE; + } + else if (bodyDist > 24.0f) + { + bodyDir[2] = 0; //don't want it floating + //VectorScale(bodyDir, 0.1f, bodyDir); + VectorAdd(grabbed->epVelocity, bodyDir, grabbed->epVelocity); + G_Sound(grabbed, CHAN_AUTO, G_SoundIndex("sound/player/roll1.wav")); + } + } + } + else if (ent->client->ps.forceHandExtend == HANDEXTEND_DRAGGING) + { + ent->client->ps.forceHandExtend = HANDEXTEND_WEAPONREADY; + } + + if (ent->NPC && ent->s.NPC_class != CLASS_VEHICLE) //vehicles manage their own speed + { + //FIXME: swoop should keep turning (and moving forward?) for a little bit? + if ( ent->NPC->combatMove == qfalse ) + { + //if ( !(ucmd->buttons & BUTTON_USE) ) + if (1) + {//Not leaning + qboolean Flying = (ucmd->upmove && (ent->client->ps.eFlags2&EF2_FLYING));//ent->client->moveType == MT_FLYSWIM); + qboolean Climbing = (ucmd->upmove && ent->watertype&CONTENTS_LADDER ); + + //client->ps.friction = 6; + + if ( ucmd->forwardmove || ucmd->rightmove || Flying ) + { + //if ( ent->NPC->behaviorState != BS_FORMATION ) + {//In - Formation NPCs set thier desiredSpeed themselves + if ( ucmd->buttons & BUTTON_WALKING ) + { + ent->NPC->desiredSpeed = NPC_GetWalkSpeed( ent );//ent->NPC->stats.walkSpeed; + } + else//running + { + ent->NPC->desiredSpeed = NPC_GetRunSpeed( ent );//ent->NPC->stats.runSpeed; + } + + if ( ent->NPC->currentSpeed >= 80 && !controlledByPlayer ) + {//At higher speeds, need to slow down close to stuff + //Slow down as you approach your goal + // if ( ent->NPC->distToGoal < SLOWDOWN_DIST && client->race != RACE_BORG && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 + if ( ent->NPC->distToGoal < SLOWDOWN_DIST && !(ent->NPC->aiFlags&NPCAI_NO_SLOWDOWN) )//128 + { + if ( ent->NPC->desiredSpeed > MIN_NPC_SPEED ) + { + float slowdownSpeed = ((float)ent->NPC->desiredSpeed) * ent->NPC->distToGoal / SLOWDOWN_DIST; + + ent->NPC->desiredSpeed = ceil(slowdownSpeed); + if ( ent->NPC->desiredSpeed < MIN_NPC_SPEED ) + {//don't slow down too much + ent->NPC->desiredSpeed = MIN_NPC_SPEED; + } + } + } + } + } + } + else if ( Climbing ) + { + ent->NPC->desiredSpeed = ent->NPC->stats.walkSpeed; + } + else + {//We want to stop + ent->NPC->desiredSpeed = 0; + } + + NPC_Accelerate( ent, qfalse, qfalse ); + + if ( ent->NPC->currentSpeed <= 24 && ent->NPC->desiredSpeed < ent->NPC->currentSpeed ) + {//No-one walks this slow + client->ps.speed = ent->NPC->currentSpeed = 0;//Full stop + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + } + else + { + if ( ent->NPC->currentSpeed <= ent->NPC->stats.walkSpeed ) + {//Play the walkanim + ucmd->buttons |= BUTTON_WALKING; + } + else + { + ucmd->buttons &= ~BUTTON_WALKING; + } + + if ( ent->NPC->currentSpeed > 0 ) + {//We should be moving + if ( Climbing || Flying ) + { + if ( !ucmd->upmove ) + {//We need to force them to take a couple more steps until stopped + ucmd->upmove = ent->NPC->last_ucmd.upmove;//was last_upmove; + } + } + else if ( !ucmd->forwardmove && !ucmd->rightmove ) + {//We need to force them to take a couple more steps until stopped + ucmd->forwardmove = ent->NPC->last_ucmd.forwardmove;//was last_forwardmove; + ucmd->rightmove = ent->NPC->last_ucmd.rightmove;//was last_rightmove; + } + } + + client->ps.speed = ent->NPC->currentSpeed; + // if ( player && player->client && player->client->ps.viewEntity == ent->s.number ) + // { + // } + // else + //rwwFIXMEFIXME: do this and also check for all real client + if (1) + { + //Slow down on turns - don't orbit!!! + float turndelta = 0; + // if the NPC is locked into a Yaw, we want to check the lockedDesiredYaw...otherwise the NPC can't walk backwards, because it always thinks it trying to turn according to desiredYaw + //if( client->renderInfo.renderFlags & RF_LOCKEDANGLE ) // yeah I know the RF_ flag is a pretty ugly hack... + if (0) //rwwFIXMEFIXME: ... + { + turndelta = (180 - fabs( AngleDelta( ent->r.currentAngles[YAW], ent->NPC->lockedDesiredYaw ) ))/180; + } + else + { + turndelta = (180 - fabs( AngleDelta( ent->r.currentAngles[YAW], ent->NPC->desiredYaw ) ))/180; + } + + if ( turndelta < 0.75f ) + { + client->ps.speed = 0; + } + else if ( ent->NPC->distToGoal < 100 && turndelta < 1.0 ) + {//Turn is greater than 45 degrees or closer than 100 to goal + client->ps.speed = floor(((float)(client->ps.speed))*turndelta); + } + } + } + } + } + else + { + ent->NPC->desiredSpeed = ( ucmd->buttons & BUTTON_WALKING ) ? NPC_GetWalkSpeed( ent ) : NPC_GetRunSpeed( ent ); + + client->ps.speed = ent->NPC->desiredSpeed; + } + + if (ucmd->buttons & BUTTON_WALKING) + { //sort of a hack I guess since MP handles walking differently from SP (has some proxy cheat prevention methods) + /* + if (ent->client->ps.speed > 64) + { + ent->client->ps.speed = 64; + } + */ + + if (ucmd->forwardmove > 64) + { + ucmd->forwardmove = 64; + } + else if (ucmd->forwardmove < -64) + { + ucmd->forwardmove = -64; + } + + if (ucmd->rightmove > 64) + { + ucmd->rightmove = 64; + } + else if ( ucmd->rightmove < -64) + { + ucmd->rightmove = -64; + } + + //ent->client->ps.speed = ent->client->ps.basespeed = NPC_GetRunSpeed( ent ); + } + client->ps.basespeed = client->ps.speed; + } + else if (!client->ps.m_iVehicleNum && + (!ent->NPC || ent->s.NPC_class != CLASS_VEHICLE)) //if riding a vehicle it will manage our speed and such + { + // set speed + client->ps.speed = g_speed.value; + + //Check for a siege class speed multiplier + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1) + { + client->ps.speed *= bgSiegeClasses[client->siegeClass].speed; + } + + if (client->bodyGrabIndex != ENTITYNUM_NONE) + { //can't go nearly as fast when dragging a body around + client->ps.speed *= 0.2f; + } + + client->ps.basespeed = client->ps.speed; + } + + if ( !ent->NPC || !(ent->NPC->aiFlags&NPCAI_CUSTOM_GRAVITY) ) + {//use global gravity + if (ent->NPC && ent->s.NPC_class == CLASS_VEHICLE && + ent->m_pVehicle && ent->m_pVehicle->m_pVehicleInfo->gravity) + { //use custom veh gravity + client->ps.gravity = ent->m_pVehicle->m_pVehicleInfo->gravity; + } + else + { + if (ent->client->inSpaceIndex && ent->client->inSpaceIndex != ENTITYNUM_NONE) + { //in space, so no gravity... + client->ps.gravity = 1.0f; + if (ent->s.number < MAX_CLIENTS) + { + VectorScale(client->ps.velocity, 0.8f, client->ps.velocity); + } + } + else + { + if (client->ps.eFlags2 & EF2_SHIP_DEATH) + { //float there + VectorClear(client->ps.velocity); + client->ps.gravity = 1.0f; + } + else + { + client->ps.gravity = g_gravity.value; + } + } + } + } + + if (ent->client->ps.duelInProgress) + { + gentity_t *duelAgainst = &g_entities[ent->client->ps.duelIndex]; + + //Keep the time updated, so once this duel ends this player can't engage in a duel for another + //10 seconds. This will give other people a chance to engage in duels in case this player wants + //to engage again right after he's done fighting and someone else is waiting. + ent->client->ps.fd.privateDuelTime = level.time + 10000; + + if (ent->client->ps.duelTime < level.time) + { + //Bring out the sabers + if (ent->client->ps.weapon == WP_SABER + && ent->client->ps.saberHolstered + && ent->client->ps.duelTime ) + { + ent->client->ps.saberHolstered = 0; + + if (ent->client->saber[0].soundOn) + { + G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn); + } + if (ent->client->saber[1].soundOn) + { + G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn); + } + + G_AddEvent(ent, EV_PRIVATE_DUEL, 2); + + ent->client->ps.duelTime = 0; + } + + if (duelAgainst + && duelAgainst->client + && duelAgainst->inuse + && duelAgainst->client->ps.weapon == WP_SABER + && duelAgainst->client->ps.saberHolstered + && duelAgainst->client->ps.duelTime) + { + duelAgainst->client->ps.saberHolstered = 0; + + if (duelAgainst->client->saber[0].soundOn) + { + G_Sound(duelAgainst, CHAN_AUTO, duelAgainst->client->saber[0].soundOn); + } + if (duelAgainst->client->saber[1].soundOn) + { + G_Sound(duelAgainst, CHAN_AUTO, duelAgainst->client->saber[1].soundOn); + } + + G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 2); + + duelAgainst->client->ps.duelTime = 0; + } + } + else + { + client->ps.speed = 0; + client->ps.basespeed = 0; + ucmd->forwardmove = 0; + ucmd->rightmove = 0; + ucmd->upmove = 0; + } + + if (!duelAgainst || !duelAgainst->client || !duelAgainst->inuse || + duelAgainst->client->ps.duelIndex != ent->s.number) + { + ent->client->ps.duelInProgress = 0; + G_AddEvent(ent, EV_PRIVATE_DUEL, 0); + } + else if (duelAgainst->health < 1 || duelAgainst->client->ps.stats[STAT_HEALTH] < 1) + { + ent->client->ps.duelInProgress = 0; + duelAgainst->client->ps.duelInProgress = 0; + + G_AddEvent(ent, EV_PRIVATE_DUEL, 0); + G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 0); + + //Winner gets full health.. providing he's still alive + if (ent->health > 0 && ent->client->ps.stats[STAT_HEALTH] > 0) + { + if (ent->health < ent->client->ps.stats[STAT_MAX_HEALTH]) + { + ent->client->ps.stats[STAT_HEALTH] = ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + + if (g_spawnInvulnerability.integer) + { + ent->client->ps.eFlags |= EF_INVULNERABLE; + ent->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer; + } + } + + /* + trap_SendServerCommand( ent-g_entities, va("print \"%s %s\n\"", ent->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELWINNER")) ); + trap_SendServerCommand( duelAgainst-g_entities, va("print \"%s %s\n\"", ent->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELWINNER")) ); + */ + //Private duel announcements are now made globally because we only want one duel at a time. + if (ent->health > 0 && ent->client->ps.stats[STAT_HEALTH] > 0) + { + trap_SendServerCommand( -1, va("cp \"%s %s %s!\n\"", ent->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELWINNER"), duelAgainst->client->pers.netname) ); + } + else + { //it was a draw, because we both managed to die in the same frame + trap_SendServerCommand( -1, va("cp \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PLDUELTIE")) ); + } + } + else + { + vec3_t vSub; + float subLen = 0; + + VectorSubtract(ent->client->ps.origin, duelAgainst->client->ps.origin, vSub); + subLen = VectorLength(vSub); + + if (subLen >= 1024) + { + ent->client->ps.duelInProgress = 0; + duelAgainst->client->ps.duelInProgress = 0; + + G_AddEvent(ent, EV_PRIVATE_DUEL, 0); + G_AddEvent(duelAgainst, EV_PRIVATE_DUEL, 0); + + trap_SendServerCommand( -1, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PLDUELSTOP")) ); + } + } + } + + if (ent->client->doingThrow > level.time) + { + gentity_t *throwee = &g_entities[ent->client->throwingIndex]; + + if (!throwee->inuse || !throwee->client || throwee->health < 1 || + throwee->client->sess.sessionTeam == TEAM_SPECTATOR || + (throwee->client->ps.pm_flags & PMF_FOLLOW) || + throwee->client->throwingIndex != ent->s.number) + { + ent->client->doingThrow = 0; + ent->client->ps.forceHandExtend = HANDEXTEND_NONE; + + if (throwee->inuse && throwee->client) + { + throwee->client->ps.heldByClient = 0; + throwee->client->beingThrown = 0; + + if (throwee->client->ps.forceHandExtend != HANDEXTEND_POSTTHROWN) + { + throwee->client->ps.forceHandExtend = HANDEXTEND_NONE; + } + } + } + } + + if (ent->client->beingThrown > level.time) + { + gentity_t *thrower = &g_entities[ent->client->throwingIndex]; + + if (!thrower->inuse || !thrower->client || thrower->health < 1 || + thrower->client->sess.sessionTeam == TEAM_SPECTATOR || + (thrower->client->ps.pm_flags & PMF_FOLLOW) || + thrower->client->throwingIndex != ent->s.number) + { + ent->client->ps.heldByClient = 0; + ent->client->beingThrown = 0; + + if (ent->client->ps.forceHandExtend != HANDEXTEND_POSTTHROWN) + { + ent->client->ps.forceHandExtend = HANDEXTEND_NONE; + } + + if (thrower->inuse && thrower->client) + { + thrower->client->doingThrow = 0; + thrower->client->ps.forceHandExtend = HANDEXTEND_NONE; + } + } + else if (thrower->inuse && thrower->client && thrower->ghoul2 && + trap_G2_HaveWeGhoul2Models(thrower->ghoul2)) + { +#if 0 + int lHandBolt = trap_G2API_AddBolt(thrower->ghoul2, 0, "*l_hand"); + int pelBolt = trap_G2API_AddBolt(thrower->ghoul2, 0, "pelvis"); + + + if (lHandBolt != -1 && pelBolt != -1) +#endif + { + float pDif = 40.0f; + vec3_t boltOrg, pBoltOrg; + vec3_t tAngles; + vec3_t vDif; + vec3_t entDir, otherAngles; + vec3_t fwd, right; + + //Always look at the thrower. + VectorSubtract( thrower->client->ps.origin, ent->client->ps.origin, entDir ); + VectorCopy( ent->client->ps.viewangles, otherAngles ); + otherAngles[YAW] = vectoyaw( entDir ); + SetClientViewAngle( ent, otherAngles ); + + VectorCopy(thrower->client->ps.viewangles, tAngles); + tAngles[PITCH] = tAngles[ROLL] = 0; + + //Get the direction between the pelvis and position of the hand +#if 0 + mdxaBone_t boltMatrix, pBoltMatrix; + + trap_G2API_GetBoltMatrix(thrower->ghoul2, 0, lHandBolt, &boltMatrix, tAngles, thrower->client->ps.origin, level.time, 0, thrower->modelScale); + boltOrg[0] = boltMatrix.matrix[0][3]; + boltOrg[1] = boltMatrix.matrix[1][3]; + boltOrg[2] = boltMatrix.matrix[2][3]; + + trap_G2API_GetBoltMatrix(thrower->ghoul2, 0, pelBolt, &pBoltMatrix, tAngles, thrower->client->ps.origin, level.time, 0, thrower->modelScale); + pBoltOrg[0] = pBoltMatrix.matrix[0][3]; + pBoltOrg[1] = pBoltMatrix.matrix[1][3]; + pBoltOrg[2] = pBoltMatrix.matrix[2][3]; +#else //above tends to not work once in a while, for various reasons I suppose. + VectorCopy(thrower->client->ps.origin, pBoltOrg); + AngleVectors(tAngles, fwd, right, 0); + boltOrg[0] = pBoltOrg[0] + fwd[0]*8 + right[0]*pDif; + boltOrg[1] = pBoltOrg[1] + fwd[1]*8 + right[1]*pDif; + boltOrg[2] = pBoltOrg[2]; +#endif + //G_TestLine(boltOrg, pBoltOrg, 0x0000ff, 50); + + VectorSubtract(ent->client->ps.origin, boltOrg, vDif); + if (VectorLength(vDif) > 32.0f && (thrower->client->doingThrow - level.time) < 4500) + { //the hand is too far away, and can no longer hold onto us, so escape. + ent->client->ps.heldByClient = 0; + ent->client->beingThrown = 0; + thrower->client->doingThrow = 0; + + thrower->client->ps.forceHandExtend = HANDEXTEND_NONE; + G_EntitySound( thrower, CHAN_VOICE, G_SoundIndex("*pain25.wav") ); + + ent->client->ps.forceDodgeAnim = 2; + ent->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + ent->client->ps.forceHandExtendTime = level.time + 500; + ent->client->ps.velocity[2] = 400; + G_PreDefSound(ent->client->ps.origin, PDSOUND_FORCEJUMP); + } + else if ((client->beingThrown - level.time) < 4000) + { //step into the next part of the throw, and go flying back + float vScale = 400.0f; + ent->client->ps.forceHandExtend = HANDEXTEND_POSTTHROWN; + ent->client->ps.forceHandExtendTime = level.time + 1200; + ent->client->ps.forceDodgeAnim = 0; + + thrower->client->ps.forceHandExtend = HANDEXTEND_POSTTHROW; + thrower->client->ps.forceHandExtendTime = level.time + 200; + + ent->client->ps.heldByClient = 0; + + ent->client->ps.heldByClient = 0; + ent->client->beingThrown = 0; + thrower->client->doingThrow = 0; + + AngleVectors(thrower->client->ps.viewangles, vDif, 0, 0); + ent->client->ps.velocity[0] = vDif[0]*vScale; + ent->client->ps.velocity[1] = vDif[1]*vScale; + ent->client->ps.velocity[2] = 400; + + G_EntitySound( ent, CHAN_VOICE, G_SoundIndex("*pain100.wav") ); + G_EntitySound( thrower, CHAN_VOICE, G_SoundIndex("*jump1.wav") ); + + //Set the thrower as the "other killer", so if we die from fall/impact damage he is credited. + ent->client->ps.otherKiller = thrower->s.number; + ent->client->ps.otherKillerTime = level.time + 8000; + ent->client->ps.otherKillerDebounceTime = level.time + 100; + } + else + { //see if we can move to be next to the hand.. if it's not clear, break the throw. + vec3_t intendedOrigin; + trace_t tr; + trace_t tr2; + + VectorSubtract(boltOrg, pBoltOrg, vDif); + VectorNormalize(vDif); + + VectorClear(ent->client->ps.velocity); + intendedOrigin[0] = pBoltOrg[0] + vDif[0]*pDif; + intendedOrigin[1] = pBoltOrg[1] + vDif[1]*pDif; + intendedOrigin[2] = thrower->client->ps.origin[2]; + + trap_Trace(&tr, intendedOrigin, ent->r.mins, ent->r.maxs, intendedOrigin, ent->s.number, ent->clipmask); + trap_Trace(&tr2, ent->client->ps.origin, ent->r.mins, ent->r.maxs, intendedOrigin, ent->s.number, CONTENTS_SOLID); + + if (tr.fraction == 1.0 && !tr.startsolid && tr2.fraction == 1.0 && !tr2.startsolid) + { + VectorCopy(intendedOrigin, ent->client->ps.origin); + + if ((client->beingThrown - level.time) < 4800) + { + ent->client->ps.heldByClient = thrower->s.number+1; + } + } + else + { //if the guy can't be put here then it's time to break the throw off. + ent->client->ps.heldByClient = 0; + ent->client->beingThrown = 0; + thrower->client->doingThrow = 0; + + thrower->client->ps.forceHandExtend = HANDEXTEND_NONE; + G_EntitySound( thrower, CHAN_VOICE, G_SoundIndex("*pain25.wav") ); + + ent->client->ps.forceDodgeAnim = 2; + ent->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + ent->client->ps.forceHandExtendTime = level.time + 500; + ent->client->ps.velocity[2] = 400; + G_PreDefSound(ent->client->ps.origin, PDSOUND_FORCEJUMP); + } + } + } + } + } + else if (ent->client->ps.heldByClient) + { + ent->client->ps.heldByClient = 0; + } + + /* + if ( client->ps.powerups[PW_HASTE] ) { + client->ps.speed *= 1.3; + } + */ + + //Will probably never need this again, since we have g2 properly serverside now. + //But just in case. + /* + if (client->ps.usingATST && ent->health > 0) + { //we have special shot clip boxes as an ATST + ent->r.contents |= CONTENTS_NOSHOT; + ATST_ManageDamageBoxes(ent); + } + else + { + ent->r.contents &= ~CONTENTS_NOSHOT; + client->damageBoxHandle_Head = 0; + client->damageBoxHandle_RLeg = 0; + client->damageBoxHandle_LLeg = 0; + } + */ + + //rww - moved this stuff into the pmove code so that it's predicted properly + //BG_AdjustClientSpeed(&client->ps, &client->pers.cmd, level.time); + + // set up for pmove + oldEventSequence = client->ps.eventSequence; + + memset (&pm, 0, sizeof(pm)); + + if ( ent->flags & FL_FORCE_GESTURE ) { + ent->flags &= ~FL_FORCE_GESTURE; + ent->client->pers.cmd.buttons |= BUTTON_GESTURE; + } + + if (ent->client && ent->client->ps.fallingToDeath && + (level.time - FALL_FADE_TIME) > ent->client->ps.fallingToDeath) + { //die! + if (ent->health > 0) + { + gentity_t *otherKiller = ent; + if (ent->client->ps.otherKillerTime > level.time && + ent->client->ps.otherKiller != ENTITYNUM_NONE) + { + otherKiller = &g_entities[ent->client->ps.otherKiller]; + + if (!otherKiller->inuse) + { + otherKiller = ent; + } + } + G_Damage(ent, otherKiller, otherKiller, NULL, ent->client->ps.origin, 9999, DAMAGE_NO_PROTECTION, MOD_FALLING); + //player_die(ent, ent, ent, 100000, MOD_FALLING); + // if (!ent->NPC) + // { + // respawn(ent); + // } + // ent->client->ps.fallingToDeath = 0; + + G_MuteSound(ent->s.number, CHAN_VOICE); //stop screaming, because you are dead! + } + } + + if (ent->client->ps.otherKillerTime > level.time && + ent->client->ps.groundEntityNum != ENTITYNUM_NONE && + ent->client->ps.otherKillerDebounceTime < level.time) + { + ent->client->ps.otherKillerTime = 0; + ent->client->ps.otherKiller = ENTITYNUM_NONE; + } + else if (ent->client->ps.otherKillerTime > level.time && + ent->client->ps.groundEntityNum == ENTITYNUM_NONE) + { + if (ent->client->ps.otherKillerDebounceTime < (level.time + 100)) + { + ent->client->ps.otherKillerDebounceTime = level.time + 100; + } + } + +// WP_ForcePowersUpdate( ent, msec, ucmd); //update any active force powers +// WP_SaberPositionUpdate(ent, ucmd); //check the server-side saber point, do apprioriate server-side actions (effects are cs-only) + + //NOTE: can't put USE here *before* PMove!! + if ( ent->client->ps.useDelay > level.time + && ent->client->ps.m_iVehicleNum ) + {//when in a vehicle, debounce the use... + ucmd->buttons &= ~BUTTON_USE; + } + + //FIXME: need to do this before check to avoid walls and cliffs (or just cliffs?) + G_AddPushVecToUcmd( ent, ucmd ); + + //play/stop any looping sounds tied to controlled movement + G_CheckMovingLoopingSounds( ent, ucmd ); + + pm.ps = &client->ps; + 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_MONSTERCLIP; + } + 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; + + pm.animations = bgAllAnims[ent->localAnimIndex].anims;//NULL; + + //rww - bgghoul2 + pm.ghoul2 = NULL; + +#ifdef _DEBUG + if (g_disableServerG2.integer) + { + + } + else +#endif + if (ent->ghoul2) + { + if (ent->localAnimIndex > 1) + { //if it isn't humanoid then we will be having none of this. + pm.ghoul2 = NULL; + } + else + { + pm.ghoul2 = ent->ghoul2; + pm.g2Bolts_LFoot = trap_G2API_AddBolt(ent->ghoul2, 0, "*l_leg_foot"); + pm.g2Bolts_RFoot = trap_G2API_AddBolt(ent->ghoul2, 0, "*r_leg_foot"); + } + } + + //point the saber data to the right place +#if 0 + k = 0; + while (k < MAX_SABERS) + { + if (ent->client->saber[k].model[0]) + { + pm.saber[k] = &ent->client->saber[k]; + } + else + { + pm.saber[k] = NULL; + } + k++; + } +#endif + + //I'll just do this every frame in case the scale changes in realtime (don't need to update the g2 inst for that) + VectorCopy(ent->modelScale, pm.modelScale); + //rww end bgghoul2 + + pm.gametype = g_gametype.integer; + pm.debugMelee = g_debugMelee.integer; + pm.stepSlideFix = g_stepSlideFix.integer; + + pm.noSpecMove = g_noSpecMove.integer; + + pm.nonHumanoid = (ent->localAnimIndex > 0); + + VectorCopy( client->ps.origin, client->oldOrigin ); + + /* + 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; + } + } + */ + + //Set up bg entity data + pm.baseEnt = (bgEntity_t *)g_entities; + pm.entSize = sizeof(gentity_t); + + if (ent->client->ps.saberLockTime > level.time) + { + gentity_t *blockOpp = &g_entities[ent->client->ps.saberLockEnemy]; + + if (blockOpp && blockOpp->inuse && blockOpp->client) + { + vec3_t lockDir, lockAng; + + //VectorClear( ent->client->ps.velocity ); + VectorSubtract( blockOpp->r.currentOrigin, ent->r.currentOrigin, lockDir ); + //lockAng[YAW] = vectoyaw( defDir ); + vectoangles(lockDir, lockAng); + SetClientViewAngle( ent, lockAng ); + } + + if ( ent->client->ps.saberLockHitCheckTime < level.time ) + {//have moved to next frame since last lock push + ent->client->ps.saberLockHitCheckTime = level.time;//so we don't push more than once per server frame + if ( ( ent->client->buttons & BUTTON_ATTACK ) && ! ( ent->client->oldbuttons & BUTTON_ATTACK ) ) + { + if ( ent->client->ps.saberLockHitIncrementTime < level.time ) + {//have moved to next frame since last saberlock attack button press + int lockHits = 0; + ent->client->ps.saberLockHitIncrementTime = level.time;//so we don't register an attack key press more than once per server frame + //NOTE: FP_SABER_OFFENSE level already taken into account in PM_SaberLocked + if ( (ent->client->ps.fd.forcePowersActive&(1<client->ps.fd.forcePowerLevel[FP_RAGE]; + } + else + {//normal attack + switch ( ent->client->ps.fd.saberAnimLevel ) + { + case SS_FAST: + lockHits = 1; + break; + case SS_MEDIUM: + case SS_TAVION: + case SS_DUAL: + case SS_STAFF: + lockHits = 2; + break; + case SS_STRONG: + case SS_DESANN: + lockHits = 3; + break; + } + } + if ( ent->client->ps.fd.forceRageRecoveryTime > level.time + && Q_irand( 0, 1 ) ) + {//finished raging: weak + lockHits -= 1; + } + lockHits += ent->client->saber[0].lockBonus; + if ( ent->client->saber[1].model + && ent->client->saber[1].model[0] + && !ent->client->ps.saberHolstered ) + { + lockHits += ent->client->saber[1].lockBonus; + } + ent->client->ps.saberLockHits += lockHits; + if ( g_saberLockRandomNess.integer ) + { + ent->client->ps.saberLockHits += Q_irand( 0, g_saberLockRandomNess.integer ); + if ( ent->client->ps.saberLockHits < 0 ) + { + ent->client->ps.saberLockHits = 0; + } + } + } + } + if ( ent->client->ps.saberLockHits > 0 ) + { + if ( !ent->client->ps.saberLockAdvance ) + { + ent->client->ps.saberLockHits--; + } + ent->client->ps.saberLockAdvance = qtrue; + } + } + } + else + { + ent->client->ps.saberLockFrame = 0; + //check for taunt + if ( (pm.cmd.generic_cmd == GENCMD_ENGAGE_DUEL) && (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) ) + {//already in a duel, make it a taunt command + pm.cmd.buttons |= BUTTON_GESTURE; + } + } + + if (ent->s.number >= MAX_CLIENTS) + { + VectorCopy(ent->r.mins, pm.mins); + VectorCopy(ent->r.maxs, pm.maxs); +#if 1 + if (ent->s.NPC_class == CLASS_VEHICLE && + ent->m_pVehicle ) + { + if ( ent->m_pVehicle->m_pPilot) + { //vehicles want to use their last pilot ucmd I guess + if ((level.time - ent->m_pVehicle->m_ucmd.serverTime) > 2000) + { //Previous owner disconnected, maybe + ent->m_pVehicle->m_ucmd.serverTime = level.time; + ent->client->ps.commandTime = level.time-100; + msec = 100; + } + + memcpy(&pm.cmd, &ent->m_pVehicle->m_ucmd, sizeof(usercmd_t)); + + //no veh can strafe + pm.cmd.rightmove = 0; + //no crouching or jumping! + pm.cmd.upmove = 0; + + //NOTE: button presses were getting lost! + assert(g_entities[ent->m_pVehicle->m_pPilot->s.number].client); + pm.cmd.buttons = (g_entities[ent->m_pVehicle->m_pPilot->s.number].client->pers.cmd.buttons&(BUTTON_ATTACK|BUTTON_ALT_ATTACK)); + } + if ( ent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER ) + { + if ( ent->client->ps.groundEntityNum != ENTITYNUM_NONE ) + {//ATST crushes anything underneath it + gentity_t *under = &g_entities[ent->client->ps.groundEntityNum]; + if ( under && under->health && under->takedamage ) + { + vec3_t down = {0,0,-1}; + //FIXME: we'll be doing traces down from each foot, so we'll have a real impact origin + G_Damage( under, ent, ent, down, under->r.currentOrigin, 100, 0, MOD_CRUSH ); + } + } + } + } +#endif + } + + Pmove (&pm); + + if (ent->client->solidHack) + { + if (ent->client->solidHack > level.time) + { //whee! + ent->r.contents = 0; + } + else + { + ent->r.contents = CONTENTS_BODY; + ent->client->solidHack = 0; + } + } + + if ( ent->NPC ) + { + VectorCopy( ent->client->ps.viewangles, ent->r.currentAngles ); + } + + if (pm.checkDuelLoss) + { + if (pm.checkDuelLoss > 0 && (pm.checkDuelLoss <= MAX_CLIENTS || (pm.checkDuelLoss < (MAX_GENTITIES-1) && g_entities[pm.checkDuelLoss-1].s.eType == ET_NPC) ) ) + { + gentity_t *clientLost = &g_entities[pm.checkDuelLoss-1]; + + if (clientLost && clientLost->inuse && clientLost->client && Q_irand(0, 40) > clientLost->health) + { + vec3_t attDir; + VectorSubtract(ent->client->ps.origin, clientLost->client->ps.origin, attDir); + VectorNormalize(attDir); + + VectorClear(clientLost->client->ps.velocity); + clientLost->client->ps.forceHandExtend = HANDEXTEND_NONE; + clientLost->client->ps.forceHandExtendTime = 0; + + gGAvoidDismember = 1; + G_Damage(clientLost, ent, ent, attDir, clientLost->client->ps.origin, 9999, DAMAGE_NO_PROTECTION, MOD_SABER); + + if (clientLost->health < 1) + { + gGAvoidDismember = 2; + G_CheckForDismemberment(clientLost, ent, clientLost->client->ps.origin, 999, (clientLost->client->ps.legsAnim), qfalse); + } + + gGAvoidDismember = 0; + } + else if (clientLost && clientLost->inuse && clientLost->client && + clientLost->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN && clientLost->client->ps.saberEntityNum) + { //if we didn't knock down it was a circle lock. So as punishment, make them lose their saber and go into a proper anim + saberCheckKnockdown_DuelLoss(&g_entities[clientLost->client->ps.saberEntityNum], clientLost, ent); + } + } + + pm.checkDuelLoss = 0; + } + + if (pm.cmd.generic_cmd && + (pm.cmd.generic_cmd != ent->client->lastGenCmd || ent->client->lastGenCmdTime < level.time)) + { + ent->client->lastGenCmd = pm.cmd.generic_cmd; + if (pm.cmd.generic_cmd != GENCMD_FORCE_THROW && + pm.cmd.generic_cmd != GENCMD_FORCE_PULL) + { //these are the only two where you wouldn't care about a delay between + ent->client->lastGenCmdTime = level.time + 300; //default 100ms debounce between issuing the same command. + } + + switch(pm.cmd.generic_cmd) + { + case 0: + break; + case GENCMD_SABERSWITCH: + Cmd_ToggleSaber_f(ent); + break; + case GENCMD_ENGAGE_DUEL: + if ( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL ) + {//already in a duel, made it a taunt command + } + else + { + Cmd_EngageDuel_f(ent); + } + break; + case GENCMD_FORCE_HEAL: + ForceHeal(ent); + break; + case GENCMD_FORCE_SPEED: + ForceSpeed(ent, 0); + break; + case GENCMD_FORCE_THROW: + ForceThrow(ent, qfalse); + break; + case GENCMD_FORCE_PULL: + ForceThrow(ent, qtrue); + break; + case GENCMD_FORCE_DISTRACT: + ForceTelepathy(ent); + break; + case GENCMD_FORCE_RAGE: + ForceRage(ent); + break; + case GENCMD_FORCE_PROTECT: + ForceProtect(ent); + break; + case GENCMD_FORCE_ABSORB: + ForceAbsorb(ent); + break; + case GENCMD_FORCE_HEALOTHER: + ForceTeamHeal(ent); + break; + case GENCMD_FORCE_FORCEPOWEROTHER: + ForceTeamForceReplenish(ent); + break; + case GENCMD_FORCE_SEEING: + ForceSeeing(ent); + break; + case GENCMD_USE_SEEKER: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SEEKER)) && + G_ItemUsable(&ent->client->ps, HI_SEEKER) ) + { + ItemUse_Seeker(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_SEEKER, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SEEKER); + } + break; + case GENCMD_USE_FIELD: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SHIELD)) && + G_ItemUsable(&ent->client->ps, HI_SHIELD) ) + { + ItemUse_Shield(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_SHIELD, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SHIELD); + } + break; + case GENCMD_USE_BACTA: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC)) && + G_ItemUsable(&ent->client->ps, HI_MEDPAC) ) + { + ItemUse_MedPack(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_MEDPAC, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_MEDPAC); + } + break; + case GENCMD_USE_BACTABIG: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_MEDPAC_BIG)) && + G_ItemUsable(&ent->client->ps, HI_MEDPAC_BIG) ) + { + ItemUse_MedPack_Big(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_MEDPAC_BIG, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_MEDPAC_BIG); + } + break; + case GENCMD_USE_ELECTROBINOCULARS: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_BINOCULARS)) && + G_ItemUsable(&ent->client->ps, HI_BINOCULARS) ) + { + ItemUse_Binoculars(ent); + if (ent->client->ps.zoomMode == 0) + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); + } + else + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); + } + } + break; + case GENCMD_ZOOM: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_BINOCULARS)) && + G_ItemUsable(&ent->client->ps, HI_BINOCULARS) ) + { + ItemUse_Binoculars(ent); + if (ent->client->ps.zoomMode == 0) + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); + } + else + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); + } + } + break; + case GENCMD_USE_SENTRY: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_SENTRY_GUN)) && + G_ItemUsable(&ent->client->ps, HI_SENTRY_GUN) ) + { + ItemUse_Sentry(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_SENTRY_GUN, 0); + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] &= ~(1 << HI_SENTRY_GUN); + } + break; + case GENCMD_USE_JETPACK: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_JETPACK)) && + G_ItemUsable(&ent->client->ps, HI_JETPACK) ) + { + ItemUse_Jetpack(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_JETPACK, 0); + /* + if (ent->client->ps.zoomMode == 0) + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 1); + } + else + { + G_AddEvent(ent, EV_USE_ITEM0+HI_BINOCULARS, 2); + } + */ + } + break; + case GENCMD_USE_HEALTHDISP: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_HEALTHDISP)) && + G_ItemUsable(&ent->client->ps, HI_HEALTHDISP) ) + { + //ItemUse_UseDisp(ent, HI_HEALTHDISP); + G_AddEvent(ent, EV_USE_ITEM0+HI_HEALTHDISP, 0); + } + break; + case GENCMD_USE_AMMODISP: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_AMMODISP)) && + G_ItemUsable(&ent->client->ps, HI_AMMODISP) ) + { + //ItemUse_UseDisp(ent, HI_AMMODISP); + G_AddEvent(ent, EV_USE_ITEM0+HI_AMMODISP, 0); + } + break; + case GENCMD_USE_EWEB: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_EWEB)) && + G_ItemUsable(&ent->client->ps, HI_EWEB) ) + { + ItemUse_UseEWeb(ent); + G_AddEvent(ent, EV_USE_ITEM0+HI_EWEB, 0); + } + break; + case GENCMD_USE_CLOAK: + if ( (ent->client->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << HI_CLOAK)) && + G_ItemUsable(&ent->client->ps, HI_CLOAK) ) + { + if ( ent->client->ps.powerups[PW_CLOAKED] ) + {//decloak + Jedi_Decloak( ent ); + } + else + {//cloak + Jedi_Cloak( ent ); + } + } + break; + case GENCMD_SABERATTACKCYCLE: + Cmd_SaberAttackCycle_f(ent); + break; + case GENCMD_TAUNT: + G_SetTauntAnim( ent, TAUNT_TAUNT ); + break; + case GENCMD_BOW: + G_SetTauntAnim( ent, TAUNT_BOW ); + break; + case GENCMD_MEDITATE: + G_SetTauntAnim( ent, TAUNT_MEDITATE ); + break; + case GENCMD_FLOURISH: + G_SetTauntAnim( ent, TAUNT_FLOURISH ); + break; + case GENCMD_GLOAT: + G_SetTauntAnim( ent, TAUNT_GLOAT ); + break; + default: + break; + } + } + + // 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, qfalse ); + //rww - 12-03-02 - Don't snap the origin of players! It screws prediction all up. + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qfalse ); + } + + if (isNPC) + { + ent->s.eType = ET_NPC; + } + + SendPendingPredictableEvents( &ent->client->ps ); + + if ( !( ent->client->ps.eFlags & EF_FIRING ) ) { + client->fireHeld = qfalse; // for grapple + } + + // use the snapped origin for linking so it matches client predicted versions + VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); + + if (ent->s.eType != ET_NPC || + ent->s.NPC_class != CLASS_VEHICLE || + !ent->m_pVehicle || + !ent->m_pVehicle->m_iRemovedSurfaces) + { //let vehicles that are getting broken apart do their own crazy sizing stuff + 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 ); + + if ( pm.useEvent ) + { + //TODO: Use +// TryUse( ent ); + } + if ((ent->client->pers.cmd.buttons & BUTTON_USE) && ent->client->ps.useDelay < level.time) + { + TryUse(ent); + ent->client->ps.useDelay = level.time + 100; + } + + // 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; + +// G_VehicleAttachDroidUnit( ent ); + + // Did we kick someone in our pmove sequence? + if (client->ps.forceKickFlip) + { + gentity_t *faceKicked = &g_entities[client->ps.forceKickFlip-1]; + + if (faceKicked && faceKicked->client && (!OnSameTeam(ent, faceKicked) || g_friendlyFire.integer) && + (!faceKicked->client->ps.duelInProgress || faceKicked->client->ps.duelIndex == ent->s.number) && + (!ent->client->ps.duelInProgress || ent->client->ps.duelIndex == faceKicked->s.number)) + { + if ( faceKicked && faceKicked->client && faceKicked->health && faceKicked->takedamage ) + {//push them away and do pain + vec3_t oppDir; + int strength = (int)VectorNormalize2( client->ps.velocity, oppDir ); + + strength *= 0.05; + + VectorScale( oppDir, -1, oppDir ); + + G_Damage( faceKicked, ent, ent, oppDir, client->ps.origin, strength, DAMAGE_NO_ARMOR, MOD_MELEE ); + + if ( faceKicked->client->ps.weapon != WP_SABER || + faceKicked->client->ps.fd.saberAnimLevel != FORCE_LEVEL_3 || + (!BG_SaberInAttack(faceKicked->client->ps.saberMove) && !PM_SaberInStart(faceKicked->client->ps.saberMove) && !PM_SaberInReturn(faceKicked->client->ps.saberMove) && !PM_SaberInTransition(faceKicked->client->ps.saberMove)) ) + { + if (faceKicked->health > 0 && + faceKicked->client->ps.stats[STAT_HEALTH] > 0 && + faceKicked->client->ps.forceHandExtend != HANDEXTEND_KNOCKDOWN) + { + if (BG_KnockDownable(&faceKicked->client->ps) && Q_irand(1, 10) <= 3) + { //only actually knock over sometimes, but always do velocity hit + faceKicked->client->ps.forceHandExtend = HANDEXTEND_KNOCKDOWN; + faceKicked->client->ps.forceHandExtendTime = level.time + 1100; + faceKicked->client->ps.forceDodgeAnim = 0; //this toggles between 1 and 0, when it's 1 we should play the get up anim + } + + faceKicked->client->ps.otherKiller = ent->s.number; + faceKicked->client->ps.otherKillerTime = level.time + 5000; + faceKicked->client->ps.otherKillerDebounceTime = level.time + 100; + + faceKicked->client->ps.velocity[0] = oppDir[0]*(strength*40); + faceKicked->client->ps.velocity[1] = oppDir[1]*(strength*40); + faceKicked->client->ps.velocity[2] = 200; + } + } + + G_Sound( faceKicked, CHAN_AUTO, G_SoundIndex( va("sound/weapons/melee/punch%d", Q_irand(1, 4)) ) ); + } + } + + client->ps.forceKickFlip = 0; + } + + // check for respawning + if ( client->ps.stats[STAT_HEALTH] <= 0 + && !(client->ps.eFlags2&EF2_HELD_BY_MONSTER)//can't respawn while being eaten + && ent->s.eType != ET_NPC ) { + // wait for the attack button to be pressed + if ( level.time > client->respawnTime && !gDoSlowMoDuel ) { + // forcerespawn is to prevent users from waiting out powerups + int forceRes = g_forcerespawn.integer; + + if (g_gametype.integer == GT_POWERDUEL) + { + forceRes = 1; + } + else if (g_gametype.integer == GT_SIEGE && + g_siegeRespawn.integer) + { //wave respawning on + forceRes = 1; + } + + if ( forceRes > 0 && + ( level.time - client->respawnTime ) > forceRes * 1000 ) { + respawn( ent ); + return; + } + + // pressing attack or use is the normal respawn method + if ( ucmd->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) ) { + respawn( ent ); + } + } + else if (gDoSlowMoDuel) + { + client->respawnTime = level.time + 1000; + } + return; + } + + // perform once-a-second actions + ClientTimerActions( ent, msec ); + + G_UpdateClientBroadcasts ( ent ); + + //try some idle anims on ent if getting no input and not moving for some time + G_CheckClientIdle( ent, ucmd ); + + // This code was moved here from clientThink to fix a problem with g_synchronousClients + // being set to 1 when in vehicles. + if ( ent->s.number < MAX_CLIENTS && ent->client->ps.m_iVehicleNum ) + {//driving a vehicle + //run it + if (g_entities[ent->client->ps.m_iVehicleNum].inuse && g_entities[ent->client->ps.m_iVehicleNum].client) + { + ClientThink(ent->client->ps.m_iVehicleNum, &g_entities[ent->client->ps.m_iVehicleNum].m_pVehicle->m_ucmd); + } + else + { //vehicle no longer valid? + ent->client->ps.m_iVehicleNum = 0; + } + } + +} + +/* +================== +G_CheckClientTimeouts + +Checks whether a client has exceded any timeouts and act accordingly +================== +*/ +void G_CheckClientTimeouts ( gentity_t *ent ) +{ + // Only timeout supported right now is the timeout to spectator mode + if ( !g_timeouttospec.integer ) + { + return; + } + + // Already a spectator, no need to boot them to spectator + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) + { + return; + } + + // See how long its been since a command was received by the client and if its + // longer than the timeout to spectator then force this client into spectator mode + if ( level.time - ent->client->pers.cmd.serverTime > g_timeouttospec.integer * 1000 ) + { + SetTeam ( ent, "spectator" ); + } +} + +/* +================== +ClientThink + +A new command has arrived from the client +================== +*/ +void ClientThink( int clientNum,usercmd_t *ucmd ) { + gentity_t *ent; + + ent = g_entities + clientNum; + if (clientNum < MAX_CLIENTS) + { + 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 (ucmd) + { + ent->client->pers.cmd = *ucmd; + } + +/* This was moved to clientthink_real, but since its sort of a risky change i left it here for + now as a more concrete reference - BSD + + if ( clientNum < MAX_CLIENTS + && ent->client->ps.m_iVehicleNum ) + {//driving a vehicle + if (g_entities[ent->client->ps.m_iVehicleNum].client) + { + gentity_t *veh = &g_entities[ent->client->ps.m_iVehicleNum]; + + if (veh->m_pVehicle && + veh->m_pVehicle->m_pPilot == (bgEntity_t *)ent) + { //only take input from the pilot... + veh->client->ps.commandTime = ent->client->ps.commandTime; + memcpy(&veh->m_pVehicle->m_ucmd, &ent->client->pers.cmd, sizeof(usercmd_t)); + if ( veh->m_pVehicle->m_ucmd.buttons & BUTTON_TALK ) + { //forced input if "chat bubble" is up + veh->m_pVehicle->m_ucmd.buttons = BUTTON_TALK; + veh->m_pVehicle->m_ucmd.forwardmove = 0; + veh->m_pVehicle->m_ucmd.rightmove = 0; + veh->m_pVehicle->m_ucmd.upmove = 0; + } + } + } + } +*/ + if ( !(ent->r.svFlags & SVF_BOT) && !g_synchronousClients.integer ) { + ClientThink_real( ent ); + } + // vehicles are clients and when running synchronous they still need to think here + // so special case them. + else if ( clientNum >= MAX_CLIENTS ) { + ClientThink_real( ent ); + } + +/* This was moved to clientthink_real, but since its sort of a risky change i left it here for + now as a more concrete reference - BSD + + if ( clientNum < MAX_CLIENTS + && ent->client->ps.m_iVehicleNum ) + {//driving a vehicle + //run it + if (g_entities[ent->client->ps.m_iVehicleNum].inuse && + g_entities[ent->client->ps.m_iVehicleNum].client) + { + ClientThink(ent->client->ps.m_iVehicleNum, &g_entities[ent->client->ps.m_iVehicleNum].m_pVehicle->m_ucmd); + } + else + { //vehicle no longer valid? + ent->client->ps.m_iVehicleNum = 0; + } + } +*/ +} + + +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 (ent->s.eType == ET_NPC) + { + assert(0); + return; + } + + // 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->mGameFlags & ~(PSG_VOTED | PSG_TEAMVOTED)) | (ent->client->mGameFlags & (PSG_VOTED | PSG_TEAMVOTED)); + //ent->client->mGameFlags = flags; + ent->client->ps.eFlags = cl->ps.eFlags; + ent->client->ps = cl->ps; + ent->client->ps.pm_flags |= PMF_FOLLOW; + 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, qtrue ); + } + } + } + } + + 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; + qboolean isNPC = qfalse; + + if (ent->s.eType == ET_NPC) + { + isNPC = qtrue; + } + + 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; + } + } + + // save network bandwidth +#if 0 + if ( !g_synchronousClients->integer && (ent->client->ps.pm_type == PM_NORMAL || ent->client->ps.pm_type == PM_JETPACK || ent->client->ps.pm_type == PM_FLOAT) ) { + // 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 ) { + if ( ent->s.number < MAX_CLIENTS + || ent->client->NPC_class == CLASS_VEHICLE ) + {//players and vehicles do nothing in intermissions + 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, qfalse ); + //rww - 12-03-02 - Don't snap the origin of players! It screws prediction all up. + } + else { + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qfalse ); + } + + if (isNPC) + { + ent->s.eType = ET_NPC; + } + + 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/code/game/g_arenas.c b/code/game/g_arenas.c new file mode 100644 index 0000000..8d9d187 --- /dev/null +++ b/code/game/g_arenas.c @@ -0,0 +1,343 @@ +// 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; + int score1, score2; + qboolean won; + char buf[32]; + char msg[MAX_STRING_CHARS]; + + // 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 ) { + Com_sprintf( msg, sizeof(msg), "postgame %i %i 0 0 0 0 0 0 0 0 0 0 0", level.numNonSpectatorClients, playerClientNum ); + } + else { + if( player->client->accuracy_shots ) { + accuracy = player->client->accuracy_hits * 100 / player->client->accuracy_shots; + } + else { + accuracy = 0; + } + 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; + } + 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] ); + } + + 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 = WeaponReadyAnim[ent->s.weapon]; + body->s.torsoAnim = WeaponReadyAnim[ent->s.weapon]; + if( body->s.weapon == WP_NONE ) { + body->s.weapon = WP_BRYAR_PISTOL; + } + if( body->s.weapon == WP_SABER) { + body->s.torsoAnim = BOTH_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; + + if( player->s.weapon == WP_SABER) { + anim = BOTH_STAND2; + } + else { + anim = WeaponReadyAnim[player->s.weapon]; + } + 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 ) | BOTH_TALKGESTURE1; + 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; + + AngleVectors( level.intermission_angle, vec, NULL, NULL ); + 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 ); + + AngleVectors( level.intermission_angle, vec, NULL, NULL ); + 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; + } +} +*/ \ No newline at end of file diff --git a/code/game/g_bot.c b/code/game/g_bot.c new file mode 100644 index 0000000..60b32f3 --- /dev/null +++ b/code/game/g_bot.c @@ -0,0 +1,1308 @@ +// 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; + +#include "../namespace_begin.h" +float trap_Cvar_VariableValue( const char *var_name ) { + char buf[128]; + + trap_Cvar_VariableStringBuffer(var_name, buf, sizeof(buf)); + return atof(buf); +} +#include "../namespace_end.h" + + +/* +=============== +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( (const char **)(&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( (const char **)(&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( (const char **)(&buf), qfalse ); + if ( !token[0] ) { + strcpy( token, "" ); + } + Info_SetValueForKey( info, key, token ); + } + //NOTE: extra space for arena number + infos[count] = (char *) 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] ); +} + +int G_GetMapTypeBits(char *type) +{ + int typeBits = 0; + + if( *type ) { + if( strstr( type, "ffa" ) ) { + typeBits |= (1 << GT_FFA); + typeBits |= (1 << GT_TEAM); + } + if( strstr( type, "holocron" ) ) { + typeBits |= (1 << GT_HOLOCRON); + } + if( strstr( type, "jedimaster" ) ) { + typeBits |= (1 << GT_JEDIMASTER); + } + if( strstr( type, "duel" ) ) { + typeBits |= (1 << GT_DUEL); + typeBits |= (1 << GT_POWERDUEL); + } + if( strstr( type, "powerduel" ) ) { + typeBits |= (1 << GT_DUEL); + typeBits |= (1 << GT_POWERDUEL); + } + if( strstr( type, "siege" ) ) { + typeBits |= (1 << GT_SIEGE); + } + if( strstr( type, "ctf" ) ) { + typeBits |= (1 << GT_CTF); + } + if( strstr( type, "cty" ) ) { + typeBits |= (1 << GT_CTY); + } + } else { + typeBits |= (1 << GT_FFA); + } + + return typeBits; +} + +qboolean G_DoesMapSupportGametype(const char *mapname, int gametype) +{ + int typeBits = 0; + int thisLevel = -1; + int n = 0; + char *type = NULL; + + if (!g_arenaInfos[0]) + { + return qfalse; + } + + if (!mapname || !mapname[0]) + { + return qfalse; + } + + for( n = 0; n < g_numArenas; n++ ) + { + type = Info_ValueForKey( g_arenaInfos[n], "map" ); + + if (Q_stricmp(mapname, type) == 0) + { + thisLevel = n; + break; + } + } + + if (thisLevel == -1) + { + return qfalse; + } + + type = Info_ValueForKey(g_arenaInfos[thisLevel], "type"); + + typeBits = G_GetMapTypeBits(type); + if (typeBits & (1 << gametype)) + { //the map in question supports the gametype in question, so.. + return qtrue; + } + + return qfalse; +} + +//rww - auto-obtain nextmap. I could've sworn Q3 had something like this, but I guess not. +const char *G_RefreshNextMap(int gametype, qboolean forced) +{ + int typeBits = 0; + int thisLevel = 0; + int desiredMap = 0; + int n = 0; + char *type = NULL; + qboolean loopingUp = qfalse; + vmCvar_t mapname; + + if (!g_autoMapCycle.integer && !forced) + { + return NULL; + } + + if (!g_arenaInfos[0]) + { + return NULL; + } + + trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM ); + for( n = 0; n < g_numArenas; n++ ) + { + type = Info_ValueForKey( g_arenaInfos[n], "map" ); + + if (Q_stricmp(mapname.string, type) == 0) + { + thisLevel = n; + break; + } + } + + desiredMap = thisLevel; + + n = thisLevel+1; + while (n != thisLevel) + { //now cycle through the arena list and find the next map that matches the gametype we're in + if (!g_arenaInfos[n] || n >= g_numArenas) + { + if (loopingUp) + { //this shouldn't happen, but if it does we have a null entry break in the arena file + //if this is the case just break out of the loop instead of sticking in an infinite loop + break; + } + n = 0; + loopingUp = qtrue; + } + + type = Info_ValueForKey(g_arenaInfos[n], "type"); + + typeBits = G_GetMapTypeBits(type); + if (typeBits & (1 << gametype)) + { + desiredMap = n; + break; + } + + n++; + } + + if (desiredMap == thisLevel) + { //If this is the only level for this game mode or we just can't find a map for this game mode, then nextmap + //will always restart. + trap_Cvar_Set( "nextmap", "map_restart 0"); + } + else + { //otherwise we have a valid nextmap to cycle to, so use it. + type = Info_ValueForKey( g_arenaInfos[desiredMap], "map" ); + trap_Cvar_Set( "nextmap", va("map %s", type)); + } + + return Info_ValueForKey( g_arenaInfos[desiredMap], "map" ); +} + +/* +=============== +G_LoadArenas +=============== +*/ +static void G_LoadArenas( void ) { + int numdirs; + char filename[128]; + char dirlist[1024]; + char* dirptr; + int i, n; + int dirlen; + + g_numArenas = 0; + + // 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_RefreshNextMap(g_gametype.integer, qfalse); +} + + +/* +=============== +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; +} + +#if 0 +/* +================= +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 ) ); +} +#endif + +/* +=============== +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 (g_gametype.integer == GT_SIEGE) + { + if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) { + continue; + } + } + else + { + 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 (g_gametype.integer == GT_SIEGE) + { + if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) { + continue; + } + } + else + { + 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); + 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 (g_gametype.integer == GT_SIEGE) + { + if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) { + continue; + } + } + else + { + if ( team >= 0 && cl->sess.sessionTeam != team ) { + continue; + } + } + strcpy(netname, cl->pers.netname); + Q_CleanStr(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 (g_gametype.integer == GT_SIEGE) + { + if ( team >= 0 && cl->sess.siegeDesiredTeam != team ) { + continue; + } + } + else + { + if ( team >= 0 && cl->sess.sessionTeam != team ) { + 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 (g_gametype.integer == GT_SIEGE) + { + return; + } + + 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 (minplayers > g_maxclients.integer) + { + minplayers = g_maxclients.integer; + } + + humanplayers = G_CountHumanPlayers( -1 ); + botplayers = G_CountBotPlayers( -1 ); + + if ((humanplayers+botplayers) < minplayers) + { + G_AddRandomBot(-1); + } + 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); + } + } + + /* + if (g_gametype.integer >= GT_TEAM) { + int humanplayers2, botplayers2; + if (minplayers >= g_maxclients.integer / 2) { + minplayers = (g_maxclients.integer / 2) -1; + } + + humanplayers = G_CountHumanPlayers( TEAM_RED ); + botplayers = G_CountBotPlayers( TEAM_RED ); + humanplayers2 = G_CountHumanPlayers( TEAM_BLUE ); + botplayers2 = G_CountBotPlayers( TEAM_BLUE ); + // + if ((humanplayers+botplayers+humanplayers2+botplayers) < minplayers) + { + if ((humanplayers+botplayers) < (humanplayers2+botplayers2)) + { + G_AddRandomBot( TEAM_RED ); + } + else + { + G_AddRandomBot( TEAM_BLUE ); + } + } + else if ((humanplayers+botplayers+humanplayers2+botplayers) > minplayers && botplayers) + { + if ((humanplayers+botplayers) < (humanplayers2+botplayers2)) + { + G_RemoveRandomBot( TEAM_BLUE ); + } + else + { + G_RemoveRandomBot( TEAM_RED ); + } + } + } + else if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) { + 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 ); + } + } + else if (g_gametype.integer == GT_HOLOCRON || g_gametype.integer == GT_JEDIMASTER) { + 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; + + G_CheckMinimumPlayers(); + + for( n = 0; n < BOT_SPAWN_QUEUE_DEPTH; n++ ) { + if( !botSpawnQueue[n].spawnTime ) { + continue; + } + if ( botSpawnQueue[n].spawnTime > level.time ) { + continue; + } + ClientBegin( botSpawnQueue[n].clientNum, qfalse ); + 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" ); + ClientBegin( clientNum, qfalse ); +} + + +/* +=============== +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.personalityfile, Info_ValueForKey( userinfo, "personality" ), sizeof(settings.personalityfile) ); + 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]; + int preTeam = 0; + + // 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 = "kyle/default"; + } + 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 = "4"; + } + Info_SetValueForKey( userinfo, key, s ); + + key = "saber1"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "single_1"; + } + Info_SetValueForKey( userinfo, key, s ); + + key = "saber2"; + s = Info_ValueForKey( botinfo, key ); + if ( !*s ) { + s = "none"; + } + Info_SetValueForKey( userinfo, key, s ); + + s = Info_ValueForKey(botinfo, "personality"); + if (!*s ) + { + Info_SetValueForKey( userinfo, "personality", "botfiles/default.jkb" ); + } + else + { + Info_SetValueForKey( userinfo, "personality", s ); + } + + // 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.\n" ); + trap_SendServerCommand( -1, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "UNABLE_TO_ADD_BOT"))); + 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; + bot->inuse = qtrue; + + // register the userinfo + trap_SetUserinfo( clientNum, userinfo ); + + if (g_gametype.integer >= GT_TEAM) + { + if (team && Q_stricmp(team, "red") == 0) + { + bot->client->sess.sessionTeam = TEAM_RED; + } + else if (team && Q_stricmp(team, "blue") == 0) + { + bot->client->sess.sessionTeam = TEAM_BLUE; + } + else + { + bot->client->sess.sessionTeam = PickTeam( -1 ); + } + } + + if (g_gametype.integer == GT_SIEGE) + { + bot->client->sess.siegeDesiredTeam = bot->client->sess.sessionTeam; + bot->client->sess.sessionTeam = TEAM_SPECTATOR; + } + + preTeam = bot->client->sess.sessionTeam; + + // have it connect to the game as a normal client + if ( ClientConnect( clientNum, qtrue, qtrue ) ) { + return; + } + + if (bot->client->sess.sessionTeam != preTeam) + { + trap_GetUserinfo(clientNum, userinfo, MAX_INFO_STRING); + + if (bot->client->sess.sessionTeam == TEAM_SPECTATOR) + { + bot->client->sess.sessionTeam = preTeam; + } + + if (bot->client->sess.sessionTeam == TEAM_RED) + { + team = "Red"; + } + else + { + if (g_gametype.integer == GT_SIEGE) + { + if (bot->client->sess.sessionTeam == TEAM_BLUE) + { + team = "Blue"; + } + else + { + team = "s"; + } + } + else + { + team = "Blue"; + } + } + + Info_SetValueForKey( userinfo, "team", team ); + + trap_SetUserinfo( clientNum, userinfo ); + + bot->client->ps.persistant[ PERS_TEAM ] = bot->client->sess.sessionTeam; + + G_ReadSessionData( bot->client ); + ClientUserinfoChanged( clientNum ); + } + + if (g_gametype.integer == GT_DUEL || + g_gametype.integer == GT_POWERDUEL) + { + int loners = 0; + int doubles = 0; + + bot->client->sess.duelTeam = 0; + G_PowerDuelCount(&loners, &doubles, qtrue); + + if (!doubles || loners > (doubles/2)) + { + bot->client->sess.duelTeam = DUELTEAM_DOUBLE; + } + else + { + bot->client->sess.duelTeam = DUELTEAM_LONE; + } + + bot->client->sess.sessionTeam = TEAM_SPECTATOR; + SetTeam(bot, "s"); + } + else + { + if( delay == 0 ) { + ClientBegin( clientNum, qfalse ); + 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 + } +} + +/* +=============== +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 personality[MAX_TOKEN_CHARS]; + + trap_Printf("^1name model personality funname\n"); + for (i = 0; i < g_numBots; i++) { + strcpy(name, Info_ValueForKey( g_botInfos[i], "name" )); + if ( !*name ) { + strcpy(name, "Padawan"); + } + strcpy(funname, Info_ValueForKey( g_botInfos[i], "funname" )); + if ( !*funname ) { + strcpy(funname, ""); + } + strcpy(model, Info_ValueForKey( g_botInfos[i], "model" )); + if ( !*model ) { + strcpy(model, "kyle/default"); + } + strcpy(personality, Info_ValueForKey( g_botInfos[i], "personality")); + if (!*personality ) { + strcpy(personality, "botfiles/kyle.jkb"); + } + trap_Printf(va("%-16s %-16s %-20s %-20s\n", name, model, personality, funname)); + } +} + +#if 0 +/* +=============== +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" + trap_SendConsoleCommand( EXEC_INSERT, va("addbot \"%s\" %f free %i\n", bot, skill, delay) ); + + delay += BOT_BEGIN_DELAY_INCREMENT; + } +} +#endif + + +/* +=============== +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 ) { + G_LoadBotsFromFile(botsFile.string); + } + else { + //G_LoadBotsFromFile("scripts/bots.txt"); + G_LoadBotsFromFile("botfiles/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; +} + +//rww - pd +void LoadPath_ThisLevel(void); +//end rww + +/* +=============== +G_InitBots +=============== +*/ +void G_InitBots( qboolean restart ) { + G_LoadBots(); + G_LoadArenas(); + + trap_Cvar_Register( &bot_minplayers, "bot_minplayers", "0", CVAR_SERVERINFO ); + + //rww - new bot route stuff + LoadPath_ThisLevel(); + //end rww +} diff --git a/code/game/g_client.c b/code/game/g_client.c new file mode 100644 index 0000000..03e8f1d --- /dev/null +++ b/code/game/g_client.c @@ -0,0 +1,3931 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#include "g_local.h" +#include "../ghoul2/G2.h" +#include "bg_saga.h" + +// g_client.c -- client functions that don't happen every frame + +static vec3_t playerMins = {-15, -15, DEFAULT_MINS_2}; +static vec3_t playerMaxs = {15, 15, DEFAULT_MAXS_2}; + +extern int g_siegeRespawnCheck; + +void WP_SaberAddG2Model( gentity_t *saberent, const char *saberModel, qhandle_t saberSkin ); +void WP_SaberRemoveG2Model( gentity_t *saberent ); +extern qboolean WP_SaberStyleValidForSaber( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int saberAnimLevel ); +extern qboolean WP_UseFirstValidSaberStyle( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int *saberAnimLevel ); + +forcedata_t Client_Force[MAX_CLIENTS]; + +/*QUAKED info_player_duel (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for duelists in duel. +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_duel( 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_duel1 (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for lone duelists in powerduel. +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_duel1( 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_duel2 (1 0 1) (-16 -16 -24) (16 16 32) initial +potential spawning position for paired duelists in powerduel. +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_duel2( 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; + } +} + +/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) +Targets will be fired when someone spawns in on them. +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_start_red (1 0 0) (-16 -16 -24) (16 16 32) INITIAL +For Red Team DM starts + +Targets will be fired when someone spawns in on them. +equivalent to info_player_deathmatch + +INITIAL - The first time a player enters the game, they will be at an 'initial' spot. + +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_start_red(gentity_t *ent) { + SP_info_player_deathmatch( ent ); +} + +/*QUAKED info_player_start_blue (1 0 0) (-16 -16 -24) (16 16 32) INITIAL +For Blue Team DM starts + +Targets will be fired when someone spawns in on them. +equivalent to info_player_deathmatch + +INITIAL - The first time a player enters the game, they will be at an 'initial' spot. + +"nobots" will prevent bots from using this spot. +"nohumans" will prevent non-bots from using this spot. +*/ +void SP_info_player_start_blue(gentity_t *ent) { + SP_info_player_deathmatch( ent ); +} + +void SiegePointUse( gentity_t *self, gentity_t *other, gentity_t *activator ) +{ + //Toggle the point on/off + if (self->genericValue1) + { + self->genericValue1 = 0; + } + else + { + self->genericValue1 = 1; + } +} + +/*QUAKED info_player_siegeteam1 (1 0 0) (-16 -16 -24) (16 16 32) +siege start point - team1 +name and behavior of team1 depends on what is defined in the +.siege file for this level + +startoff - if non-0 spawn point will be disabled until used +idealclass - if specified, this spawn point will be considered +"ideal" for players of this class name. Corresponds to the name +entry in the .scl (siege class) file. +Targets will be fired when someone spawns in on them. +*/ +void SP_info_player_siegeteam1(gentity_t *ent) { + int soff = 0; + + if (g_gametype.integer != GT_SIEGE) + { //turn into a DM spawn if not in siege game mode + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); + + return; + } + + G_SpawnInt("startoff", "0", &soff); + + if (soff) + { //start disabled + ent->genericValue1 = 0; + } + else + { + ent->genericValue1 = 1; + } + + ent->use = SiegePointUse; +} + +/*QUAKED info_player_siegeteam2 (0 0 1) (-16 -16 -24) (16 16 32) +siege start point - team2 +name and behavior of team2 depends on what is defined in the +.siege file for this level + +startoff - if non-0 spawn point will be disabled until used +idealclass - if specified, this spawn point will be considered +"ideal" for players of this class name. Corresponds to the name +entry in the .scl (siege class) file. +Targets will be fired when someone spawns in on them. +*/ +void SP_info_player_siegeteam2(gentity_t *ent) { + int soff = 0; + + if (g_gametype.integer != GT_SIEGE) + { //turn into a DM spawn if not in siege game mode + ent->classname = "info_player_deathmatch"; + SP_info_player_deathmatch( ent ); + + return; + } + + G_SpawnInt("startoff", "0", &soff); + + if (soff) + { //start disabled + ent->genericValue1 = 0; + } + else + { + ent->genericValue1 = 1; + } + + ent->use = SiegePointUse; +} + +/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) RED BLUE +The intermission will be viewed from this point. Target an info_notnull for the view direction. +RED - In a Siege game, the intermission will happen here if the Red (attacking) team wins +BLUE - In a Siege game, the intermission will happen here if the Blue (defending) team wins +*/ +void SP_info_player_intermission( gentity_t *ent ) { + +} + +/*QUAKED info_player_intermission_red (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. + +In a Siege game, the intermission will happen here if the Red (attacking) team wins +target - ent to look at +target2 - ents to use when this intermission point is chosen +*/ +void SP_info_player_intermission_red( gentity_t *ent ) { + +} + +/*QUAKED info_player_intermission_blue (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. + +In a Siege game, the intermission will happen here if the Blue (defending) team wins +target - ent to look at +target2 - ents to use when this intermission point is chosen +*/ +void SP_info_player_intermission_blue( gentity_t *ent ) { + +} + +#define JMSABER_RESPAWN_TIME 20000 //in case it gets stuck somewhere no one can reach + +void ThrowSaberToAttacker(gentity_t *self, gentity_t *attacker) +{ + gentity_t *ent = &g_entities[self->client->ps.saberIndex]; + vec3_t a; + int altVelocity = 0; + + if (!ent || ent->enemy != self) + { //something has gone very wrong (this should never happen) + //but in case it does.. find the saber manually +#ifdef _DEBUG + Com_Printf("Lost the saber! Attempting to use global pointer..\n"); +#endif + ent = gJMSaberEnt; + + if (!ent) + { +#ifdef _DEBUG + Com_Printf("The global pointer was NULL. This is a bad thing.\n"); +#endif + return; + } + +#ifdef _DEBUG + Com_Printf("Got it (%i). Setting enemy to client %i.\n", ent->s.number, self->s.number); +#endif + + ent->enemy = self; + self->client->ps.saberIndex = ent->s.number; + } + + trap_SetConfigstring ( CS_CLIENT_JEDIMASTER, "-1" ); + + if (attacker && attacker->client && self->client->ps.saberInFlight) + { //someone killed us and we had the saber thrown, so actually move this saber to the saber location + //if we killed ourselves with saber thrown, however, same suicide rules of respawning at spawn spot still + //apply. + gentity_t *flyingsaber = &g_entities[self->client->ps.saberEntityNum]; + + if (flyingsaber && flyingsaber->inuse) + { + VectorCopy(flyingsaber->s.pos.trBase, ent->s.pos.trBase); + VectorCopy(flyingsaber->s.pos.trDelta, ent->s.pos.trDelta); + VectorCopy(flyingsaber->s.apos.trBase, ent->s.apos.trBase); + VectorCopy(flyingsaber->s.apos.trDelta, ent->s.apos.trDelta); + + VectorCopy(flyingsaber->r.currentOrigin, ent->r.currentOrigin); + VectorCopy(flyingsaber->r.currentAngles, ent->r.currentAngles); + altVelocity = 1; + } + } + + self->client->ps.saberInFlight = qtrue; //say he threw it anyway in order to properly remove from dead body + + WP_SaberAddG2Model( ent, self->client->saber[0].model, self->client->saber[0].skin ); + + ent->s.eFlags &= ~(EF_NODRAW); + ent->s.modelGhoul2 = 1; + ent->s.eType = ET_MISSILE; + ent->enemy = NULL; + + if (!attacker || !attacker->client) + { + VectorCopy(ent->s.origin2, ent->s.pos.trBase); + VectorCopy(ent->s.origin2, ent->s.origin); + VectorCopy(ent->s.origin2, ent->r.currentOrigin); + ent->pos2[0] = 0; + trap_LinkEntity(ent); + return; + } + + if (!altVelocity) + { + VectorCopy(self->s.pos.trBase, ent->s.pos.trBase); + VectorCopy(self->s.pos.trBase, ent->s.origin); + VectorCopy(self->s.pos.trBase, ent->r.currentOrigin); + + VectorSubtract(attacker->client->ps.origin, ent->s.pos.trBase, a); + + VectorNormalize(a); + + ent->s.pos.trDelta[0] = a[0]*256; + ent->s.pos.trDelta[1] = a[1]*256; + ent->s.pos.trDelta[2] = 256; + } + + trap_LinkEntity(ent); +} + +void JMSaberThink(gentity_t *ent) +{ + gJMSaberEnt = ent; + + if (ent->enemy) + { + if (!ent->enemy->client || !ent->enemy->inuse) + { //disconnected? + VectorCopy(ent->enemy->s.pos.trBase, ent->s.pos.trBase); + VectorCopy(ent->enemy->s.pos.trBase, ent->s.origin); + VectorCopy(ent->enemy->s.pos.trBase, ent->r.currentOrigin); + ent->s.modelindex = G_ModelIndex("models/weapons2/saber/saber_w.glm"); + ent->s.eFlags &= ~(EF_NODRAW); + ent->s.modelGhoul2 = 1; + ent->s.eType = ET_MISSILE; + ent->enemy = NULL; + + ent->pos2[0] = 1; + ent->pos2[1] = 0; //respawn next think + trap_LinkEntity(ent); + } + else + { + ent->pos2[1] = level.time + JMSABER_RESPAWN_TIME; + } + } + else if (ent->pos2[0] && ent->pos2[1] < level.time) + { + VectorCopy(ent->s.origin2, ent->s.pos.trBase); + VectorCopy(ent->s.origin2, ent->s.origin); + VectorCopy(ent->s.origin2, ent->r.currentOrigin); + ent->pos2[0] = 0; + trap_LinkEntity(ent); + } + + ent->nextthink = level.time + 50; + G_RunObject(ent); +} + +void JMSaberTouch(gentity_t *self, gentity_t *other, trace_t *trace) +{ + int i = 0; +// gentity_t *te; + + if (!other || !other->client || other->health < 1) + { + return; + } + + if (self->enemy) + { + return; + } + + if (!self->s.modelindex) + { + return; + } + + if (other->client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + return; + } + + if (other->client->ps.isJediMaster) + { + return; + } + + self->enemy = other; + other->client->ps.stats[STAT_WEAPONS] = (1 << WP_SABER); + other->client->ps.weapon = WP_SABER; + other->s.weapon = WP_SABER; + G_AddEvent(other, EV_BECOME_JEDIMASTER, 0); + + // Track the jedi master + trap_SetConfigstring ( CS_CLIENT_JEDIMASTER, va("%i", other->s.number ) ); + + if (g_spawnInvulnerability.integer) + { + other->client->ps.eFlags |= EF_INVULNERABLE; + other->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer; + } + + trap_SendServerCommand( -1, va("cp \"%s %s\n\"", other->client->pers.netname, G_GetStringEdString("MP_SVGAME", "BECOMEJM")) ); + + other->client->ps.isJediMaster = qtrue; + other->client->ps.saberIndex = self->s.number; + + if (other->health < 200 && other->health > 0) + { //full health when you become the Jedi Master + other->client->ps.stats[STAT_HEALTH] = other->health = 200; + } + + if (other->client->ps.fd.forcePower < 100) + { + other->client->ps.fd.forcePower = 100; + } + + while (i < NUM_FORCE_POWERS) + { + other->client->ps.fd.forcePowersKnown |= (1 << i); + other->client->ps.fd.forcePowerLevel[i] = FORCE_LEVEL_3; + + i++; + } + + self->pos2[0] = 1; + self->pos2[1] = level.time + JMSABER_RESPAWN_TIME; + + self->s.modelindex = 0; + self->s.eFlags |= EF_NODRAW; + self->s.modelGhoul2 = 0; + self->s.eType = ET_GENERAL; + + /* + te = G_TempEntity( vec3_origin, EV_DESTROY_GHOUL2_INSTANCE ); + te->r.svFlags |= SVF_BROADCAST; + te->s.eventParm = self->s.number; + */ + G_KillG2Queue(self->s.number); + + return; +} + +gentity_t *gJMSaberEnt = NULL; + +/*QUAKED info_jedimaster_start (1 0 0) (-16 -16 -24) (16 16 32) +"jedi master" saber spawn point +*/ +void SP_info_jedimaster_start(gentity_t *ent) +{ + if (g_gametype.integer != GT_JEDIMASTER) + { + gJMSaberEnt = NULL; + G_FreeEntity(ent); + return; + } + + ent->enemy = NULL; + + ent->flags = FL_BOUNCE_HALF; + + ent->s.modelindex = G_ModelIndex("models/weapons2/saber/saber_w.glm"); + ent->s.modelGhoul2 = 1; + ent->s.g2radius = 20; + //ent->s.eType = ET_GENERAL; + ent->s.eType = ET_MISSILE; + ent->s.weapon = WP_SABER; + ent->s.pos.trType = TR_GRAVITY; + ent->s.pos.trTime = level.time; + VectorSet( ent->r.maxs, 3, 3, 3 ); + VectorSet( ent->r.mins, -3, -3, -3 ); + ent->r.contents = CONTENTS_TRIGGER; + ent->clipmask = MASK_SOLID; + + ent->isSaberEntity = qtrue; + + ent->bounceCount = -5; + + ent->physicsObject = qtrue; + + VectorCopy(ent->s.pos.trBase, ent->s.origin2); //remember the spawn spot + + ent->touch = JMSaberTouch; + + trap_LinkEntity(ent); + + ent->think = JMSaberThink; + ent->nextthink = level.time + 50; +} + +/* +======================================================================= + + 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; +} + +qboolean SpotWouldTelefrag2( gentity_t *mover, vec3_t dest ) +{ + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( dest, mover->r.mins, mins ); + VectorAdd( dest, mover->r.maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; ir.contents & mover->r.contents ) + { + return qtrue; + } + } + + return qfalse; +} + +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { + gentity_t *spot; + vec3_t delta; + float dist, nearestDist; + gentity_t *nearestSpot; + + nearestDist = 999999; + nearestSpot = NULL; + spot = NULL; + + 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; +} + + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { + gentity_t *spot; + int count; + int selection; + gentity_t *spots[MAX_SPAWN_POINTS]; + + count = 0; + spot = NULL; + + 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 + return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + } + + selection = rand() % count; + return spots[ selection ]; +} + +/* +=========== +SelectRandomFurthestSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles, team_t team ) { + 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; + + //in Team DM, look for a team start spot first, if any + if ( g_gametype.integer == GT_TEAM + && team != TEAM_FREE + && team != TEAM_SPECTATOR ) + { + char *classname = NULL; + if ( team == TEAM_RED ) + { + classname = "info_player_start_red"; + } + else + { + classname = "info_player_start_blue"; + } + while ((spot = G_Find (spot, FOFS(classname), classname)) != 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 ) + {//couldn't find any of the above + 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]; +} + +gentity_t *SelectDuelSpawnPoint( int team, 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; + char *spotName; + + if (team == DUELTEAM_LONE) + { + spotName = "info_player_duel1"; + } + else if (team == DUELTEAM_DOUBLE) + { + spotName = "info_player_duel2"; + } + else if (team == DUELTEAM_SINGLE) + { + spotName = "info_player_duel"; + } + else + { + spotName = "info_player_deathmatch"; + } +tryAgain: + + numSpots = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), spotName)) != 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) + { + if (Q_stricmp(spotName, "info_player_deathmatch")) + { //try the loop again with info_player_deathmatch as the target if we couldn't find a duel spot + spotName = "info_player_deathmatch"; + goto tryAgain; + } + + //If we got here we found no free duel or DM spots, just try the first DM spot + 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]; +} + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles, team_t team ) { + return SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles, team ); + + /* + gentity_t *spot; + gentity_t *nearestSpot; + + nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); + + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // roll again if it would be real close to point of death + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // last try + spot = SelectRandomDeathmatchSpawnPoint ( ); + } + } + + // find a single player start spot + 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; + */ +} + +/* +=========== +SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles, team_t team ) { + gentity_t *spot; + + spot = NULL; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( spot->spawnflags & 1 ) { + break; + } + } + + if ( !spot || SpotWouldTelefrag( spot ) ) { + return SelectSpawnPoint( vec3_origin, origin, angles, team ); + } + + 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 + +======================================================================= +*/ + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + +#define BODY_SINK_TIME 30000//45000 + +/* +=============== +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 > BODY_SINK_TIME + 2500 ) { + // 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; + + G_AddEvent(ent, EV_BODYFADE, 0); + ent->nextthink = level.time + 18000; + ent->takedamage = qfalse; +} + +/* +============= +CopyToBodyQue + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +static qboolean CopyToBodyQue( gentity_t *ent ) { + gentity_t *body; + int contents; + int islight = 0; + + if (level.intermissiontime) + { + return qfalse; + } + + trap_UnlinkEntity (ent); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( ent->s.origin, -1 ); + if ( contents & CONTENTS_NODROP ) { + return qfalse; + } + + if (ent->client && (ent->client->ps.eFlags & EF_DISINTEGRATION)) + { //for now, just don't spawn a body if you got disint'd + return qfalse; + } + + // 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; + + //avoid oddly angled corpses floating around + body->s.angles[PITCH] = body->s.angles[ROLL] = body->s.apos.trBase[PITCH] = body->s.apos.trBase[ROLL] = 0; + + body->s.g2radius = 100; + + body->s.eType = ET_BODY; + body->s.eFlags = EF_DEAD; // clear EF_TALK, etc + + if (ent->client && (ent->client->ps.eFlags & EF_DISINTEGRATION)) + { + body->s.eFlags |= EF_DISINTEGRATION; + } + + VectorCopy(ent->client->ps.lastHitLoc, body->s.origin2); + + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.loopIsSoundset = qfalse; + 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; + + body->s.weapon = ent->s.bolt2; + + if (body->s.weapon == WP_SABER && ent->client->ps.saberInFlight) + { + body->s.weapon = WP_BLASTER; //lie to keep from putting a saber on the corpse, because it was thrown at death + } + + //G_AddEvent(body, EV_BODY_QUEUE_COPY, ent->s.clientNum); + //Now doing this through a modified version of the rcg reliable command. + if (ent->client && ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE) + { + islight = 1; + } + trap_SendServerCommand(-1, va("ircg %i %i %i %i", ent->s.number, body->s.number, body->s.weapon, islight)); + + body->r.svFlags = ent->r.svFlags | SVF_BROADCAST; + 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->s.torsoAnim = body->s.legsAnim = ent->client->ps.legsAnim; + + body->s.customRGBA[0] = ent->client->ps.customRGBA[0]; + body->s.customRGBA[1] = ent->client->ps.customRGBA[1]; + body->s.customRGBA[2] = ent->client->ps.customRGBA[2]; + body->s.customRGBA[3] = ent->client->ps.customRGBA[3]; + + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + body->r.contents = CONTENTS_CORPSE; + body->r.ownerNum = ent->s.number; + + body->nextthink = level.time + BODY_SINK_TIME; + 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); + + return qtrue; +} + +//====================================================================== + + +/* +================== +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); +} + +void MaintainBodyQueue(gentity_t *ent) +{ //do whatever should be done taking ragdoll and dismemberment states into account. + qboolean doRCG = qfalse; + + assert(ent && ent->client); + if (ent->client->tempSpectate > level.time || + (ent->client->ps.eFlags2 & EF2_SHIP_DEATH)) + { + ent->client->noCorpse = qtrue; + } + + if (!ent->client->noCorpse && !ent->client->ps.fallingToDeath) + { + if (!CopyToBodyQue (ent)) + { + doRCG = qtrue; + } + } + else + { + ent->client->noCorpse = qfalse; //clear it for next time + ent->client->ps.fallingToDeath = qfalse; + doRCG = qtrue; + } + + if (doRCG) + { //bodyque func didn't manage to call ircg so call this to assure our limbs and ragdoll states are proper on the client. + trap_SendServerCommand(-1, va("rcg %i", ent->s.clientNum)); + } +} + +/* +================ +respawn +================ +*/ +void SiegeRespawn(gentity_t *ent); +void respawn( gentity_t *ent ) { + MaintainBodyQueue(ent); + + if (gEscaping || g_gametype.integer == GT_POWERDUEL) + { + ent->client->sess.sessionTeam = TEAM_SPECTATOR; + ent->client->sess.spectatorState = SPECTATOR_FREE; + ent->client->sess.spectatorClient = 0; + + ent->client->pers.teamState.state = TEAM_BEGIN; + ent->client->sess.spectatorTime = level.time; + ClientSpawn(ent); + ent->client->iAmALoser = qtrue; + return; + } + + trap_UnlinkEntity (ent); + + if (g_gametype.integer == GT_SIEGE) + { + if (g_siegeRespawn.integer) + { + if (ent->client->tempSpectate <= level.time) + { + int minDel = g_siegeRespawn.integer* 2000; + if (minDel < 20000) + { + minDel = 20000; + } + ent->client->tempSpectate = level.time + minDel; + ent->health = ent->client->ps.stats[STAT_HEALTH] = 1; + ent->client->ps.weapon = WP_NONE; + ent->client->ps.stats[STAT_WEAPONS] = 0; + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] = 0; + ent->client->ps.stats[STAT_HOLDABLE_ITEM] = 0; + ent->takedamage = qfalse; + trap_LinkEntity(ent); + + // Respawn time. + if ( ent->s.number < MAX_CLIENTS ) + { + gentity_t *te = G_TempEntity( ent->client->ps.origin, EV_SIEGESPEC ); + te->s.time = g_siegeRespawnCheck; + te->s.owner = ent->s.number; + } + + return; + } + } + SiegeRespawn(ent); + } + else + { + gentity_t *tent; + + 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++; + } + else if (g_gametype.integer == GT_SIEGE && + level.clients[i].sess.siegeDesiredTeam == 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, "Padawan", outSize ); + } +} + +#ifdef _DEBUG +void G_DebugWrite(const char *path, const char *text) +{ + fileHandle_t f; + + trap_FS_FOpenFile( path, &f, FS_APPEND ); + trap_FS_Write(text, strlen(text), f); + trap_FS_FCloseFile(f); +} +#endif + +qboolean G_SaberModelSetup(gentity_t *ent) +{ + int i = 0; + qboolean fallbackForSaber = qtrue; + + while (i < MAX_SABERS) + { + if (ent->client->saber[i].model[0]) + { + //first kill it off if we've already got it + if (ent->client->weaponGhoul2[i]) + { + trap_G2API_CleanGhoul2Models(&(ent->client->weaponGhoul2[i])); + } + trap_G2API_InitGhoul2Model(&ent->client->weaponGhoul2[i], ent->client->saber[i].model, 0, 0, -20, 0, 0); + + if (ent->client->weaponGhoul2[i]) + { + int j = 0; + char *tagName; + int tagBolt; + + if (ent->client->saber[i].skin) + { + trap_G2API_SetSkin(ent->client->weaponGhoul2[i], 0, ent->client->saber[i].skin, ent->client->saber[i].skin); + } + + if (ent->client->saber[i].saberFlags & SFL_BOLT_TO_WRIST) + { + trap_G2API_SetBoltInfo(ent->client->weaponGhoul2[i], 0, 3+i); + } + else + { // bolt to right hand for 0, or left hand for 1 + trap_G2API_SetBoltInfo(ent->client->weaponGhoul2[i], 0, i); + } + + //Add all the bolt points + while (j < ent->client->saber[i].numBlades) + { + tagName = va("*blade%i", j+1); + tagBolt = trap_G2API_AddBolt(ent->client->weaponGhoul2[i], 0, tagName); + + if (tagBolt == -1) + { + if (j == 0) + { //guess this is an 0ldsk3wl saber + tagBolt = trap_G2API_AddBolt(ent->client->weaponGhoul2[i], 0, "*flash"); + fallbackForSaber = qfalse; + break; + } + + if (tagBolt == -1) + { + assert(0); + break; + + } + } + j++; + + fallbackForSaber = qfalse; //got at least one custom saber so don't need default + } + + //Copy it into the main instance + trap_G2API_CopySpecificGhoul2Model(ent->client->weaponGhoul2[i], 0, ent->ghoul2, i+1); + } + } + else + { + break; + } + + i++; + } + + return fallbackForSaber; +} + +/* +=========== +SetupGameGhoul2Model + +There are two ghoul2 model instances per player (actually three). One is on the clientinfo (the base for the client side +player, and copied for player spawns and for corpses). One is attached to the centity itself, which is the model acutally +animated and rendered by the system. The final is the game ghoul2 model. This is animated by pmove on the server, and +is used for determining where the lightsaber should be, and for per-poly collision tests. +=========== +*/ +void *g2SaberInstance = NULL; + +#include "../namespace_begin.h" +qboolean BG_IsValidCharacterModel(const char *modelName, const char *skinName); +qboolean BG_ValidateSkinForTeam( const char *modelName, char *skinName, int team, float *colors ); +void BG_GetVehicleModelName(char *modelname); +#include "../namespace_end.h" + +void SetupGameGhoul2Model(gentity_t *ent, char *modelname, char *skinName) +{ + int handle; + char afilename[MAX_QPATH]; +#if 0 + char /**GLAName,*/ *slash; +#endif + char GLAName[MAX_QPATH]; + vec3_t tempVec = {0,0,0}; + + // First things first. If this is a ghoul2 model, then let's make sure we demolish this first. + if (ent->ghoul2 && trap_G2_HaveWeGhoul2Models(ent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(&(ent->ghoul2)); + } + + //rww - just load the "standard" model for the server" + if (!precachedKyle) + { + int defSkin; + + Com_sprintf( afilename, sizeof( afilename ), "models/players/kyle/model.glm" ); + handle = trap_G2API_InitGhoul2Model(&precachedKyle, afilename, 0, 0, -20, 0, 0); + + if (handle<0) + { + return; + } + + defSkin = trap_R_RegisterSkin("models/players/kyle/model_default.skin"); + trap_G2API_SetSkin(precachedKyle, 0, defSkin, defSkin); + } + + if (precachedKyle && trap_G2_HaveWeGhoul2Models(precachedKyle)) + { + if (d_perPlayerGhoul2.integer || ent->s.number >= MAX_CLIENTS || + G_PlayerHasCustomSkeleton(ent)) + { //rww - allow option for perplayer models on server for collision and bolt stuff. + char modelFullPath[MAX_QPATH]; + char truncModelName[MAX_QPATH]; + char skin[MAX_QPATH]; + char vehicleName[MAX_QPATH]; + int skinHandle = 0; + int i = 0; + char *p; + + // If this is a vehicle, get it's model name. + if ( ent->client->NPC_class == CLASS_VEHICLE ) + { + strcpy(vehicleName, modelname); + BG_GetVehicleModelName(modelname); + strcpy(truncModelName, modelname); + skin[0] = 0; + if ( ent->m_pVehicle + && ent->m_pVehicle->m_pVehicleInfo + && ent->m_pVehicle->m_pVehicleInfo->skin + && ent->m_pVehicle->m_pVehicleInfo->skin[0] ) + { + skinHandle = trap_R_RegisterSkin(va("models/players/%s/model_%s.skin", modelname, ent->m_pVehicle->m_pVehicleInfo->skin)); + } + else + { + skinHandle = trap_R_RegisterSkin(va("models/players/%s/model_default.skin", modelname)); + } + } + else + { + if (skinName && skinName[0]) + { + strcpy(skin, skinName); + strcpy(truncModelName, modelname); + } + else + { + strcpy(skin, "default"); + + strcpy(truncModelName, modelname); + p = Q_strrchr(truncModelName, '/'); + + if (p) + { + *p = 0; + p++; + + while (p && *p) + { + skin[i] = *p; + i++; + p++; + } + skin[i] = 0; + i = 0; + } + + if (!BG_IsValidCharacterModel(truncModelName, skin)) + { + strcpy(truncModelName, "kyle"); + strcpy(skin, "default"); + } + + if ( g_gametype.integer >= GT_TEAM && g_gametype.integer != GT_SIEGE && !g_trueJedi.integer ) + { + BG_ValidateSkinForTeam( truncModelName, skin, ent->client->sess.sessionTeam, NULL ); + } + else if (g_gametype.integer == GT_SIEGE) + { //force skin for class if appropriate + if (ent->client->siegeClass != -1) + { + siegeClass_t *scl = &bgSiegeClasses[ent->client->siegeClass]; + if (scl->forcedSkin[0]) + { + strcpy(skin, scl->forcedSkin); + } + } + } + } + } + + if (skin[0]) + { + char *useSkinName; + + if (strchr(skin, '|')) + {//three part skin + useSkinName = va("models/players/%s/|%s", truncModelName, skin); + } + else + { + useSkinName = va("models/players/%s/model_%s.skin", truncModelName, skin); + } + + skinHandle = trap_R_RegisterSkin(useSkinName); + } + + strcpy(modelFullPath, va("models/players/%s/model.glm", truncModelName)); + handle = trap_G2API_InitGhoul2Model(&ent->ghoul2, modelFullPath, 0, skinHandle, -20, 0, 0); + + if (handle<0) + { //Huh. Guess we don't have this model. Use the default. + + if (ent->ghoul2 && trap_G2_HaveWeGhoul2Models(ent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(&(ent->ghoul2)); + } + ent->ghoul2 = NULL; + trap_G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2); + } + else + { + trap_G2API_SetSkin(ent->ghoul2, 0, skinHandle, skinHandle); + + GLAName[0] = 0; + trap_G2API_GetGLAName( ent->ghoul2, 0, GLAName); + + if (!GLAName[0] || (!strstr(GLAName, "players/_humanoid/") && ent->s.number < MAX_CLIENTS && !G_PlayerHasCustomSkeleton(ent))) + { //a bad model + trap_G2API_CleanGhoul2Models(&(ent->ghoul2)); + ent->ghoul2 = NULL; + trap_G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2); + } + + if (ent->s.number >= MAX_CLIENTS) + { + ent->s.modelGhoul2 = 1; //so we know to free it on the client when we're removed. + + if (skin[0]) + { //append it after a * + strcat( modelFullPath, va("*%s", skin) ); + } + + if ( ent->client->NPC_class == CLASS_VEHICLE ) + { //vehicles are tricky and send over their vehicle names as the model (the model is then retrieved based on the vehicle name) + ent->s.modelindex = G_ModelIndex(vehicleName); + } + else + { + ent->s.modelindex = G_ModelIndex(modelFullPath); + } + } + } + } + else + { + trap_G2API_DuplicateGhoul2Instance(precachedKyle, &ent->ghoul2); + } + } + else + { + return; + } + + //Attach the instance to this entity num so we can make use of client-server + //shared operations if possible. + trap_G2API_AttachInstanceToEntNum(ent->ghoul2, ent->s.number, qtrue); + + // The model is now loaded. + + GLAName[0] = 0; + + if (!BGPAFtextLoaded) + { + if (BG_ParseAnimationFile("models/players/_humanoid/animation.cfg", bgHumanoidAnimations, qtrue) == -1) + { + Com_Printf( "Failed to load humanoid animation file\n"); + return; + } + } + + if (ent->s.number >= MAX_CLIENTS || G_PlayerHasCustomSkeleton(ent)) + { + ent->localAnimIndex = -1; + + GLAName[0] = 0; + trap_G2API_GetGLAName(ent->ghoul2, 0, GLAName); + + if (GLAName[0] && + !strstr(GLAName, "players/_humanoid/") /*&& + !strstr(GLAName, "players/rockettrooper/")*/) + { //it doesn't use humanoid anims. + char *slash = Q_strrchr( GLAName, '/' ); + if ( slash ) + { + strcpy(slash, "/animation.cfg"); + + ent->localAnimIndex = BG_ParseAnimationFile(GLAName, NULL, qfalse); + } + } + else + { //humanoid index. + if (strstr(GLAName, "players/rockettrooper/")) + { + ent->localAnimIndex = 1; + } + else + { + ent->localAnimIndex = 0; + } + } + + if (ent->localAnimIndex == -1) + { + Com_Error(ERR_DROP, "NPC had an invalid GLA\n"); + } + } + else + { + GLAName[0] = 0; + trap_G2API_GetGLAName(ent->ghoul2, 0, GLAName); + + if (strstr(GLAName, "players/rockettrooper/")) + { + //assert(!"Should not have gotten in here with rockettrooper skel"); + ent->localAnimIndex = 1; + } + else + { + ent->localAnimIndex = 0; + } + } + + if (ent->s.NPC_class == CLASS_VEHICLE && + ent->m_pVehicle) + { //do special vehicle stuff + char strTemp[128]; + int i; + + // Setup the default first bolt + i = trap_G2API_AddBolt( ent->ghoul2, 0, "model_root" ); + + // Setup the droid unit. + ent->m_pVehicle->m_iDroidUnitTag = trap_G2API_AddBolt( ent->ghoul2, 0, "*droidunit" ); + + // Setup the Exhausts. + for ( i = 0; i < MAX_VEHICLE_EXHAUSTS; i++ ) + { + Com_sprintf( strTemp, 128, "*exhaust%i", i + 1 ); + ent->m_pVehicle->m_iExhaustTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, strTemp ); + } + + // Setup the Muzzles. + for ( i = 0; i < MAX_VEHICLE_MUZZLES; i++ ) + { + Com_sprintf( strTemp, 128, "*muzzle%i", i + 1 ); + ent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, strTemp ); + if ( ent->m_pVehicle->m_iMuzzleTag[i] == -1 ) + {//ergh, try *flash? + Com_sprintf( strTemp, 128, "*flash%i", i + 1 ); + ent->m_pVehicle->m_iMuzzleTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, strTemp ); + } + } + + // Setup the Turrets. + for ( i = 0; i < MAX_VEHICLE_TURRET_MUZZLES; i++ ) + { + if ( ent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ) + { + ent->m_pVehicle->m_iGunnerViewTag[i] = trap_G2API_AddBolt( ent->ghoul2, 0, ent->m_pVehicle->m_pVehicleInfo->turret[i].gunnerViewTag ); + } + else + { + ent->m_pVehicle->m_iGunnerViewTag[i] = -1; + } + } + } + + if (ent->client->ps.weapon == WP_SABER || ent->s.number < MAX_CLIENTS) + { //a player or NPC saber user + trap_G2API_AddBolt(ent->ghoul2, 0, "*r_hand"); + trap_G2API_AddBolt(ent->ghoul2, 0, "*l_hand"); + + //rhand must always be first bolt. lhand always second. Whichever you want the + //jetpack bolted to must always be third. + trap_G2API_AddBolt(ent->ghoul2, 0, "*chestg"); + + //claw bolts + trap_G2API_AddBolt(ent->ghoul2, 0, "*r_hand_cap_r_arm"); + trap_G2API_AddBolt(ent->ghoul2, 0, "*l_hand_cap_l_arm"); + + trap_G2API_SetBoneAnim(ent->ghoul2, 0, "model_root", 0, 12, BONE_ANIM_OVERRIDE_LOOP, 1.0f, level.time, -1, -1); + trap_G2API_SetBoneAngles(ent->ghoul2, 0, "upper_lumbar", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_X, NEGATIVE_Y, NEGATIVE_Z, NULL, 0, level.time); + trap_G2API_SetBoneAngles(ent->ghoul2, 0, "cranium", tempVec, BONE_ANGLES_POSTMULT, POSITIVE_Z, NEGATIVE_Y, POSITIVE_X, NULL, 0, level.time); + + if (!g2SaberInstance) + { + trap_G2API_InitGhoul2Model(&g2SaberInstance, "models/weapons2/saber/saber_w.glm", 0, 0, -20, 0, 0); + + if (g2SaberInstance) + { + // indicate we will be bolted to model 0 (ie the player) on bolt 0 (always the right hand) when we get copied + trap_G2API_SetBoltInfo(g2SaberInstance, 0, 0); + // now set up the gun bolt on it + trap_G2API_AddBolt(g2SaberInstance, 0, "*blade1"); + } + } + + if (G_SaberModelSetup(ent)) + { + if (g2SaberInstance) + { + trap_G2API_CopySpecificGhoul2Model(g2SaberInstance, 0, ent->ghoul2, 1); + } + } + } + + if (ent->s.number >= MAX_CLIENTS) + { //some extra NPC stuff + if (trap_G2API_AddBolt(ent->ghoul2, 0, "lower_lumbar") == -1) + { //check now to see if we have this bone for setting anims and such + ent->noLumbar = qtrue; + } + } +} + + + + +/* +=========== +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. +============ +*/ +qboolean G_SetSaber(gentity_t *ent, int saberNum, char *saberName, qboolean siegeOverride); +void G_ValidateSiegeClassForTeam(gentity_t *ent, int team); +void ClientUserinfoChanged( int clientNum ) { + gentity_t *ent; + int teamTask, teamLeader, team, health; + char *s; + char model[MAX_QPATH]; + //char headModel[MAX_QPATH]; + char forcePowers[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]; + char className[MAX_QPATH]; //name of class type to use in siege + char saberName[MAX_QPATH]; + char saber2Name[MAX_QPATH]; + char *value; + int maxHealth; + qboolean modelChanged = qfalse; + + 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 ) ) + { + if ( client->pers.netnameTime > level.time ) + { + trap_SendServerCommand( clientNum, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NONAMECHANGE")) ); + + Info_SetValueForKey( userinfo, "name", oldname ); + trap_SetUserinfo( clientNum, userinfo ); + strcpy ( client->pers.netname, oldname ); + } + else + { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s %s\n\"", oldname, G_GetStringEdString("MP_SVGAME", "PLRENAME"), client->pers.netname) ); + client->pers.netnameTime = level.time + 5000; + } + } + } + + // set model + Q_strncpyz( model, Info_ValueForKey (userinfo, "model"), sizeof( model ) ); + + if (d_perPlayerGhoul2.integer) + { + if (Q_stricmp(model, client->modelname)) + { + strcpy(client->modelname, model); + modelChanged = qtrue; + } + } + + //Get the skin RGB based on his userinfo + value = Info_ValueForKey (userinfo, "char_color_red"); + if (value) + { + client->ps.customRGBA[0] = atoi(value); + } + else + { + client->ps.customRGBA[0] = 255; + } + + value = Info_ValueForKey (userinfo, "char_color_green"); + if (value) + { + client->ps.customRGBA[1] = atoi(value); + } + else + { + client->ps.customRGBA[1] = 255; + } + + value = Info_ValueForKey (userinfo, "char_color_blue"); + if (value) + { + client->ps.customRGBA[2] = atoi(value); + } + else + { + client->ps.customRGBA[2] = 255; + } + + if ((client->ps.customRGBA[0]+client->ps.customRGBA[1]+client->ps.customRGBA[2]) < 100) + { //hmm, too dark! + client->ps.customRGBA[0] = client->ps.customRGBA[1] = client->ps.customRGBA[2] = 255; + } + + client->ps.customRGBA[3]=255; + + Q_strncpyz( forcePowers, Info_ValueForKey (userinfo, "forcepowers"), sizeof( forcePowers ) ); + + // 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; + } + + //Set the siege class + if (g_gametype.integer == GT_SIEGE) + { + strcpy(className, client->sess.siegeClass); + + //This function will see if the given class is legal for the given team. + //If not className will be filled in with the first legal class for this team. +/* if (!BG_SiegeCheckClassLegality(team, className) && + Q_stricmp(client->sess.siegeClass, "none")) + { //if it isn't legal pop up the class menu + trap_SendServerCommand(ent-g_entities, "scl"); + } +*/ + //Now that the team is legal for sure, we'll go ahead and get an index for it. + client->siegeClass = BG_SiegeFindClassIndexByName(className); + if (client->siegeClass == -1) + { //ok, get the first valid class for the team you're on then, I guess. + BG_SiegeCheckClassLegality(team, className); + strcpy(client->sess.siegeClass, className); + client->siegeClass = BG_SiegeFindClassIndexByName(className); + } + else + { //otherwise, make sure the class we are using is legal. + G_ValidateSiegeClassForTeam(ent, team); + strcpy(className, client->sess.siegeClass); + } + + //Set the sabers if the class dictates + if (client->siegeClass != -1) + { + siegeClass_t *scl = &bgSiegeClasses[client->siegeClass]; + + if (scl->saber1[0]) + { + G_SetSaber(ent, 0, scl->saber1, qtrue); + } + else + { //default I guess + G_SetSaber(ent, 0, "Kyle", qtrue); + } + if (scl->saber2[0]) + { + G_SetSaber(ent, 1, scl->saber2, qtrue); + } + else + { //no second saber then + G_SetSaber(ent, 1, "none", qtrue); + } + + //make sure the saber models are updated + G_SaberModelSetup(ent); + + if (scl->forcedModel[0]) + { //be sure to override the model we actually use + strcpy(model, scl->forcedModel); + if (d_perPlayerGhoul2.integer) + { + if (Q_stricmp(model, client->modelname)) + { + strcpy(client->modelname, model); + modelChanged = qtrue; + } + } + } + + //force them to use their class model on the server, if the class dictates + if (G_PlayerHasCustomSkeleton(ent)) + { + if (Q_stricmp(model, client->modelname) || ent->localAnimIndex == 0) + { + strcpy(client->modelname, model); + modelChanged = qtrue; + } + } + } + } + else + { + strcpy(className, "none"); + } + + //Set the saber name + strcpy(saberName, client->sess.saberType); + strcpy(saber2Name, client->sess.saber2Type); + + // set max health + if (g_gametype.integer == GT_SIEGE && client->siegeClass != -1) + { + siegeClass_t *scl = &bgSiegeClasses[client->siegeClass]; + maxHealth = 100; + + if (scl->maxhealth) + { + maxHealth = scl->maxhealth; + } + + health = maxHealth; + } + else + { + maxHealth = 100; + health = 100; //atoi( Info_ValueForKey( userinfo, "handicap" ) ); + } + client->pers.maxHealth = health; + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth ) { + client->pers.maxHealth = 100; + } + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + +/* 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"); + } +*/ + + 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; + } + } + /* + 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" )); + + // 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 ) { + s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\skill\\%s\\tt\\%d\\tl\\%d\\siegeclass\\%s\\st\\%s\\st2\\%s\\dt\\%i\\sdt\\%i", + client->pers.netname, team, model, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, + Info_ValueForKey( userinfo, "skill" ), teamTask, teamLeader, className, saberName, saber2Name, client->sess.duelTeam, client->sess.siegeDesiredTeam ); + } else { + if (g_gametype.integer == GT_SIEGE) + { //more crap to send + s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d\\siegeclass\\%s\\st\\%s\\st2\\%s\\dt\\%i\\sdt\\%i", + client->pers.netname, client->sess.sessionTeam, model, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader, className, saberName, saber2Name, client->sess.duelTeam, client->sess.siegeDesiredTeam); + } + else + { + s = va("n\\%s\\t\\%i\\model\\%s\\c1\\%s\\c2\\%s\\hc\\%i\\w\\%i\\l\\%i\\tt\\%d\\tl\\%d\\st\\%s\\st2\\%s\\dt\\%i", + client->pers.netname, client->sess.sessionTeam, model, c1, c2, + client->pers.maxHealth, client->sess.wins, client->sess.losses, teamTask, teamLeader, saberName, saber2Name, client->sess.duelTeam); + } + } + + trap_SetConfigstring( CS_PLAYERS+clientNum, s ); + + if (modelChanged) //only going to be true for allowable server-side custom skeleton cases + { //update the server g2 instance if appropriate + char *modelname = Info_ValueForKey (userinfo, "model"); + SetupGameGhoul2Model(ent, modelname, NULL); + + if (ent->ghoul2 && ent->client) + { + ent->client->renderInfo.lastG2 = NULL; //update the renderinfo bolts next update. + } + + client->torsoAnimExecute = client->legsAnimExecute = -1; + client->torsoLastFlip = client->legsLastFlip = qfalse; + } + + if (g_logClientInfo.integer) + { + 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; + gentity_t *te; + + ent = &g_entities[ clientNum ]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if ( G_FilterPacket( value ) ) { + return "Banned."; + } + + if ( !( ent->r.svFlags & SVF_BOT ) && !isBot && g_needpass.integer ) { + // 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) { + static char sTemp[1024]; + Q_strncpyz(sTemp, G_GetStringEdString("MP_SVGAME","INVALID_ESCAPE_TO_MAIN"), sizeof (sTemp) ); + return sTemp;// return "Invalid password"; + } + } + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + + //assign the pointer for bg entity access + ent->playerState = &ent->client->ps; + +// areabits = client->areabits; + + memset( client, 0, sizeof(*client) ); + + client->pers.connected = CON_CONNECTING; + + // read or initialize the session data + if ( firstTime || level.newSession ) { + G_InitSessionData( client, userinfo, isBot ); + } + G_ReadSessionData( client ); + + if (g_gametype.integer == GT_SIEGE && + (firstTime || level.newSession)) + { //if this is the first time then auto-assign a desired siege team and show briefing for that team + client->sess.siegeDesiredTeam = 0;//PickTeam(ent->s.number); + /* + trap_SendServerCommand(ent->s.number, va("sb %i", client->sess.siegeDesiredTeam)); + */ + //don't just show it - they'll see it if they switch to a team on purpose. + } + + + if (g_gametype.integer == GT_SIEGE && client->sess.sessionTeam != TEAM_SPECTATOR) + { + if (firstTime || level.newSession) + { //start as spec + client->sess.siegeDesiredTeam = client->sess.sessionTeam; + client->sess.sessionTeam = TEAM_SPECTATOR; + } + } + else if (g_gametype.integer == GT_POWERDUEL && client->sess.sessionTeam != TEAM_SPECTATOR) + { + client->sess.sessionTeam = TEAM_SPECTATOR; + } + + 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 " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLCONNECT")) ); + } + + if ( g_gametype.integer >= GT_TEAM && + client->sess.sessionTeam != TEAM_SPECTATOR ) { + BroadcastTeamChange( client, -1 ); + } + + // count current clients and rank for scoreboard + CalculateRanks(); + + te = G_TempEntity( vec3_origin, EV_CLIENTJOIN ); + te->r.svFlags |= SVF_BROADCAST; + te->s.eventParm = clientNum; + + // for statistics +// client->areabits = areabits; +// if ( !client->areabits ) +// client->areabits = G_Alloc( (trap_AAS_PointReachabilityAreaIndex( NULL ) + 7) / 8 ); + + return NULL; +} + +void G_WriteClientSessionData( gclient_t *client ); + +#include "../namespace_begin.h" +void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ); +#include "../namespace_end.h" + +/* +=========== +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 +============ +*/ +extern qboolean gSiegeRoundBegun; +extern qboolean gSiegeRoundEnded; +void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin); +void ClientBegin( int clientNum, qboolean allowTeamReset ) { + gentity_t *ent; + gclient_t *client; + gentity_t *tent; + int flags, i; + char userinfo[MAX_INFO_VALUE], *modelname; + + ent = g_entities + clientNum; + + if ((ent->r.svFlags & SVF_BOT) && g_gametype.integer >= GT_TEAM) + { + if (allowTeamReset) + { + const char *team = "Red"; + int preSess; + + //SetTeam(ent, ""); + ent->client->sess.sessionTeam = PickTeam(-1); + trap_GetUserinfo(clientNum, userinfo, MAX_INFO_STRING); + + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) + { + ent->client->sess.sessionTeam = TEAM_RED; + } + + if (ent->client->sess.sessionTeam == TEAM_RED) + { + team = "Red"; + } + else + { + team = "Blue"; + } + + Info_SetValueForKey( userinfo, "team", team ); + + trap_SetUserinfo( clientNum, userinfo ); + + ent->client->ps.persistant[ PERS_TEAM ] = ent->client->sess.sessionTeam; + + preSess = ent->client->sess.sessionTeam; + G_ReadSessionData( ent->client ); + ent->client->sess.sessionTeam = preSess; + G_WriteClientSessionData(ent->client); + ClientUserinfoChanged( clientNum ); + ClientBegin(clientNum, qfalse); + return; + } + } + + client = level.clients + clientNum; + + if ( ent->r.linked ) { + trap_UnlinkEntity( ent ); + } + G_InitGentity( ent ); + ent->touch = 0; + ent->pain = 0; + ent->client = client; + + //assign the pointer for bg entity access + ent->playerState = &ent->client->ps; + + 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; + + i = 0; + + while (i < NUM_FORCE_POWERS) + { + if (ent->client->ps.fd.forcePowersActive & (1 << i)) + { + WP_ForcePowerStop(ent, i); + } + i++; + } + + i = TRACK_CHANNEL_1; + + while (i < NUM_TRACK_CHANNELS) + { + if (ent->client->ps.fd.killSoundEntIndex[i-50] && ent->client->ps.fd.killSoundEntIndex[i-50] < MAX_GENTITIES && ent->client->ps.fd.killSoundEntIndex[i-50] > 0) + { + G_MuteSound(ent->client->ps.fd.killSoundEntIndex[i-50], CHAN_VOICE); + } + i++; + } + i = 0; + + memset( &client->ps, 0, sizeof( client->ps ) ); + client->ps.eFlags = flags; + + client->ps.hasDetPackPlanted = qfalse; + + //first-time force power initialization + WP_InitForcePowers( ent ); + + //init saber ent + WP_SaberInitBladeData( ent ); + + // First time model setup for that player. + trap_GetUserinfo( clientNum, userinfo, sizeof(userinfo) ); + modelname = Info_ValueForKey (userinfo, "model"); + SetupGameGhoul2Model(ent, modelname, NULL); + + if (ent->ghoul2 && ent->client) + { + ent->client->renderInfo.lastG2 = NULL; //update the renderinfo bolts next update. + } + + if (g_gametype.integer == GT_POWERDUEL && client->sess.sessionTeam != TEAM_SPECTATOR && + client->sess.duelTeam == DUELTEAM_FREE) + { + SetTeam(ent, "s"); + } + else + { + if (g_gametype.integer == GT_SIEGE && (!gSiegeRoundBegun || gSiegeRoundEnded)) + { + SetTeamQuick(ent, TEAM_SPECTATOR, qfalse); + } + + if ((ent->r.svFlags & SVF_BOT) && + g_gametype.integer != GT_SIEGE) + { + char *saberVal = Info_ValueForKey(userinfo, "saber1"); + char *saber2Val = Info_ValueForKey(userinfo, "saber2"); + + if (!saberVal || !saberVal[0]) + { //blah, set em up with a random saber + int r = rand()%50; + char sab1[1024]; + char sab2[1024]; + + if (r <= 17) + { + strcpy(sab1, "Katarn"); + strcpy(sab2, "none"); + } + else if (r <= 34) + { + strcpy(sab1, "Katarn"); + strcpy(sab2, "Katarn"); + } + else + { + strcpy(sab1, "dual_1"); + strcpy(sab2, "none"); + } + G_SetSaber(ent, 0, sab1, qfalse); + G_SetSaber(ent, 0, sab2, qfalse); + Info_SetValueForKey( userinfo, "saber1", sab1 ); + Info_SetValueForKey( userinfo, "saber2", sab2 ); + trap_SetUserinfo( clientNum, userinfo ); + } + else + { + G_SetSaber(ent, 0, saberVal, qfalse); + } + + if (saberVal && saberVal[0] && + (!saber2Val || !saber2Val[0])) + { + G_SetSaber(ent, 0, "none", qfalse); + Info_SetValueForKey( userinfo, "saber2", "none" ); + trap_SetUserinfo( clientNum, userinfo ); + } + else + { + G_SetSaber(ent, 0, saber2Val, qfalse); + } + } + + // 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_DUEL || g_gametype.integer == GT_POWERDUEL ) { + trap_SendServerCommand( -1, va("print \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLENTER")) ); + } + } + G_LogPrintf( "ClientBegin: %i\n", clientNum ); + + // count current clients and rank for scoreboard + CalculateRanks(); + + G_ClearClientLog(clientNum); +} + +static qboolean AllForceDisabled(int force) +{ + int i; + + if (force) + { + for (i=0;iclient); + + if (ent->s.NPC_class == CLASS_VEHICLE || ent->localAnimIndex > 1) + { //no broken limbs for vehicles and non-humanoids + return; + } + + if (!arm) + { //repair him + ent->client->ps.brokenLimbs = 0; + return; + } + + if (ent->client->ps.fd.saberAnimLevel == SS_STAFF) + { //I'm too lazy to deal with this as well for now. + return; + } + + if (arm == BROKENLIMB_LARM) + { + if (ent->client->saber[1].model[0] && + ent->client->ps.weapon == WP_SABER && + !ent->client->ps.saberHolstered && + ent->client->saber[1].soundOff) + { //the left arm shuts off its saber upon being broken + G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff); + } + } + + ent->client->ps.brokenLimbs = 0; //make sure it's cleared out + ent->client->ps.brokenLimbs |= (1 << arm); //this arm is now marked as broken + + //Do a pain anim based on the side. Since getting your arm broken does tend to hurt. + if (arm == BROKENLIMB_LARM) + { + anim = BOTH_PAIN2; + } + else if (arm == BROKENLIMB_RARM) + { + anim = BOTH_PAIN3; + } + + if (anim == -1) + { + return; + } + + G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, anim, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); + + //This could be combined into a single event. But I guess limbs don't break often enough to + //worry about it. + G_EntitySound( ent, CHAN_VOICE, G_SoundIndex("*pain25.wav") ); + //FIXME: A nice bone snapping sound instead if possible + G_Sound(ent, CHAN_AUTO, G_SoundIndex( va("sound/player/bodyfall_human%i.wav", Q_irand(1, 3)) )); +} + +//Update the ghoul2 instance anims based on the playerstate values +#include "../namespace_begin.h" +qboolean BG_SaberStanceAnim( int anim ); +qboolean PM_RunningAnim( int anim ); +#include "../namespace_end.h" +void G_UpdateClientAnims(gentity_t *self, float animSpeedScale) +{ + static int f; + static int torsoAnim; + static int legsAnim; + static int firstFrame, lastFrame; + static int aFlags; + static float animSpeed, lAnimSpeedScale; + qboolean setTorso = qfalse; + + torsoAnim = (self->client->ps.torsoAnim); + legsAnim = (self->client->ps.legsAnim); + + if (self->client->ps.saberLockFrame) + { + trap_G2API_SetBoneAnim(self->ghoul2, 0, "model_root", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150); + trap_G2API_SetBoneAnim(self->ghoul2, 0, "lower_lumbar", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150); + trap_G2API_SetBoneAnim(self->ghoul2, 0, "Motion", self->client->ps.saberLockFrame, self->client->ps.saberLockFrame+1, BONE_ANIM_OVERRIDE_FREEZE|BONE_ANIM_BLEND, animSpeedScale, level.time, -1, 150); + return; + } + + if (self->localAnimIndex > 1 && + bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame == 0 && + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames == 0) + { //We'll allow this for non-humanoids. + goto tryTorso; + } + + if (self->client->legsAnimExecute != legsAnim || self->client->legsLastFlip != self->client->ps.legsFlip) + { + animSpeed = 50.0f / bgAllAnims[self->localAnimIndex].anims[legsAnim].frameLerp; + lAnimSpeedScale = (animSpeed *= animSpeedScale); + + if (bgAllAnims[self->localAnimIndex].anims[legsAnim].loopFrames != -1) + { + aFlags = BONE_ANIM_OVERRIDE_LOOP; + } + else + { + aFlags = BONE_ANIM_OVERRIDE_FREEZE; + } + + if (animSpeed < 0) + { + lastFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame; + firstFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames; + } + else + { + firstFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame; + lastFrame = bgAllAnims[self->localAnimIndex].anims[legsAnim].firstFrame + bgAllAnims[self->localAnimIndex].anims[legsAnim].numFrames; + } + + aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on server position, but it's here just for the sake of matching them. + + trap_G2API_SetBoneAnim(self->ghoul2, 0, "model_root", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + self->client->legsAnimExecute = legsAnim; + self->client->legsLastFlip = self->client->ps.legsFlip; + } + +tryTorso: + if (self->localAnimIndex > 1 && + bgAllAnims[self->localAnimIndex].anims[torsoAnim].firstFrame == 0 && + bgAllAnims[self->localAnimIndex].anims[torsoAnim].numFrames == 0) + + { //If this fails as well just return. + return; + } + else if (self->s.number >= MAX_CLIENTS && + self->s.NPC_class == CLASS_VEHICLE) + { //we only want to set the root bone for vehicles + return; + } + + if ((self->client->torsoAnimExecute != torsoAnim || self->client->torsoLastFlip != self->client->ps.torsoFlip) && + !self->noLumbar) + { + aFlags = 0; + animSpeed = 0; + + f = torsoAnim; + + BG_SaberStartTransAnim(self->s.number, self->client->ps.fd.saberAnimLevel, self->client->ps.weapon, f, &animSpeedScale, self->client->ps.brokenLimbs); + + animSpeed = 50.0f / bgAllAnims[self->localAnimIndex].anims[f].frameLerp; + lAnimSpeedScale = (animSpeed *= animSpeedScale); + + if (bgAllAnims[self->localAnimIndex].anims[f].loopFrames != -1) + { + aFlags = BONE_ANIM_OVERRIDE_LOOP; + } + else + { + aFlags = BONE_ANIM_OVERRIDE_FREEZE; + } + + aFlags |= BONE_ANIM_BLEND; //since client defaults to blend. Not sure if this will make much difference if any on client position, but it's here just for the sake of matching them. + + if (animSpeed < 0) + { + lastFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame; + firstFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame + bgAllAnims[self->localAnimIndex].anims[f].numFrames; + } + else + { + firstFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame; + lastFrame = bgAllAnims[self->localAnimIndex].anims[f].firstFrame + bgAllAnims[self->localAnimIndex].anims[f].numFrames; + } + + trap_G2API_SetBoneAnim(self->ghoul2, 0, "lower_lumbar", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, /*firstFrame why was it this before?*/-1, 150); + + self->client->torsoAnimExecute = torsoAnim; + self->client->torsoLastFlip = self->client->ps.torsoFlip; + + setTorso = qtrue; + } + + if (setTorso && + self->localAnimIndex <= 1) + { //only set the motion bone for humanoids. + trap_G2API_SetBoneAnim(self->ghoul2, 0, "Motion", firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + } + +#if 0 //disabled for now + if (self->client->ps.brokenLimbs != self->client->brokenLimbs || + setTorso) + { + if (self->localAnimIndex <= 1 && self->client->ps.brokenLimbs && + (self->client->ps.brokenLimbs & (1 << BROKENLIMB_LARM))) + { //broken left arm + char *brokenBone = "lhumerus"; + animation_t *armAnim; + int armFirstFrame; + int armLastFrame; + int armFlags = 0; + float armAnimSpeed; + + armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_DEAD21 ]; + self->client->brokenLimbs = self->client->ps.brokenLimbs; + + armFirstFrame = armAnim->firstFrame; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND); + + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150); + } + else if (self->localAnimIndex <= 1 && self->client->ps.brokenLimbs && + (self->client->ps.brokenLimbs & (1 << BROKENLIMB_RARM))) + { //broken right arm + char *brokenBone = "rhumerus"; + char *supportBone = "lhumerus"; + + self->client->brokenLimbs = self->client->ps.brokenLimbs; + + //Only put the arm in a broken pose if the anim is such that we + //want to allow it. + if ((//self->client->ps.weapon == WP_MELEE || + self->client->ps.weapon != WP_SABER || + BG_SaberStanceAnim(self->client->ps.torsoAnim) || + PM_RunningAnim(self->client->ps.torsoAnim)) && + (!self->client->saber[1].model[0] || self->client->ps.weapon != WP_SABER)) + { + int armFirstFrame; + int armLastFrame; + int armFlags = 0; + float armAnimSpeed; + animation_t *armAnim; + + if (self->client->ps.weapon == WP_MELEE || + self->client->ps.weapon == WP_SABER || + self->client->ps.weapon == WP_BRYAR_PISTOL) + { //don't affect this arm if holding a gun, just make the other arm support it + armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_ATTACK2 ]; + + //armFirstFrame = armAnim->firstFrame; + armFirstFrame = armAnim->firstFrame+armAnim->numFrames; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND); + + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150); + } + else + { //we want to keep the broken bone updated for some cases + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + } + + if (self->client->ps.torsoAnim != BOTH_MELEE1 && + self->client->ps.torsoAnim != BOTH_MELEE2 && + (self->client->ps.torsoAnim == TORSO_WEAPONREADY2 || self->client->ps.torsoAnim == BOTH_ATTACK2 || self->client->ps.weapon < WP_BRYAR_PISTOL)) + { + //Now set the left arm to "support" the right one + armAnim = &bgAllAnims[self->localAnimIndex].anims[ BOTH_STAND2 ]; + armFirstFrame = armAnim->firstFrame; + armLastFrame = armAnim->firstFrame+armAnim->numFrames; + armAnimSpeed = 50.0f / armAnim->frameLerp; + armFlags = (BONE_ANIM_OVERRIDE_LOOP|BONE_ANIM_BLEND); + + trap_G2API_SetBoneAnim(self->ghoul2, 0, supportBone, armFirstFrame, armLastFrame, armFlags, armAnimSpeed, level.time, -1, 150); + } + else + { //we want to keep the support bone updated for some cases + trap_G2API_SetBoneAnim(self->ghoul2, 0, supportBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + } + } + else + { //otherwise, keep it set to the same as the torso + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + trap_G2API_SetBoneAnim(self->ghoul2, 0, supportBone, firstFrame, lastFrame, aFlags, lAnimSpeedScale, level.time, -1, 150); + } + } + else if (self->client->brokenLimbs) + { //remove the bone now so it can be set again + char *brokenBone = NULL; + int broken = 0; + + //Warning: Don't remove bones that you've added as bolts unless you want to invalidate your bolt index + //(well, in theory, I haven't actually run into the problem) + if (self->client->brokenLimbs & (1<client->brokenLimbs & (1<ghoul2, 0, "lhumerus", 0, 1, 0, 0, level.time, -1, 0); + trap_G2API_RemoveBone(self->ghoul2, "lhumerus", 0); + } + + assert(brokenBone); + + //Set the flags and stuff to 0, so that the remove will succeed + trap_G2API_SetBoneAnim(self->ghoul2, 0, brokenBone, 0, 1, 0, 0, level.time, -1, 0); + + //Now remove it + trap_G2API_RemoveBone(self->ghoul2, brokenBone, 0); + self->client->brokenLimbs &= ~broken; + } + } +#endif +} + +/* +=========== +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 +============ +*/ +extern qboolean WP_HasForcePowers( const playerState_t *ps ); +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, gameFlags; + int savedPing; + int accuracy_hits, accuracy_shots; + int eventSequence; + char userinfo[MAX_INFO_STRING]; + forcedata_t savedForce; + int saveSaberNum = ENTITYNUM_NONE; + int wDisable = 0; + int savedSiegeIndex = 0; + int maxHealth; + saberInfo_t saberSaved[MAX_SABERS]; + int l = 0; + void *g2WeaponPtrs[MAX_SABERS]; + char *value; + char *saber; + qboolean changedSaber = qfalse; + qboolean inSiegeWithClass = qfalse; + + index = ent - g_entities; + client = ent->client; + + //first we want the userinfo so we can see if we should update this client's saber -rww + trap_GetUserinfo( index, userinfo, sizeof(userinfo) ); + while (l < MAX_SABERS) + { + switch (l) + { + case 0: + saber = &ent->client->sess.saberType[0]; + break; + case 1: + saber = &ent->client->sess.saber2Type[0]; + break; + default: + saber = NULL; + break; + } + + value = Info_ValueForKey (userinfo, va("saber%i", l+1)); + if (saber && + value && + (Q_stricmp(value, saber) || !saber[0] || !ent->client->saber[0].model[0])) + { //doesn't match up (or our session saber is BS), we want to try setting it + if (G_SetSaber(ent, l, value, qfalse)) + { + changedSaber = qtrue; + } + else if (!saber[0] || !ent->client->saber[0].model[0]) + { //Well, we still want to say they changed then (it means this is siege and we have some overrides) + changedSaber = qtrue; + } + } + l++; + } + + if (changedSaber) + { //make sure our new info is sent out to all the other clients, and give us a valid stance + ClientUserinfoChanged( ent->s.number ); + + //make sure the saber models are updated + G_SaberModelSetup(ent); + + l = 0; + while (l < MAX_SABERS) + { //go through and make sure both sabers match the userinfo + switch (l) + { + case 0: + saber = &ent->client->sess.saberType[0]; + break; + case 1: + saber = &ent->client->sess.saber2Type[0]; + break; + default: + saber = NULL; + break; + } + + value = Info_ValueForKey (userinfo, va("saber%i", l+1)); + + if (Q_stricmp(value, saber)) + { //they don't match up, force the user info + Info_SetValueForKey(userinfo, va("saber%i", l+1), saber); + trap_SetUserinfo( ent->s.number, userinfo ); + } + l++; + } + + if (ent->client->saber[0].model[0] && + ent->client->saber[1].model[0]) + { //dual + ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_DUAL; + } + else if ((ent->client->saber[0].saberFlags&SFL_TWO_HANDED)) + { //staff + ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = SS_STAFF; + } + else + { + if (ent->client->sess.saberLevel < SS_FAST) + { + ent->client->sess.saberLevel = SS_FAST; + } + else if (ent->client->sess.saberLevel > SS_STRONG) + { + ent->client->sess.saberLevel = SS_STRONG; + } + ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel; + + if (g_gametype.integer != GT_SIEGE && + ent->client->ps.fd.saberAnimLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { + ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel = ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]; + } + } + if ( g_gametype.integer != GT_SIEGE ) + { + //let's just make sure the styles we chose are cool + if ( !WP_SaberStyleValidForSaber( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, ent->client->ps.fd.saberAnimLevel ) ) + { + WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &ent->client->ps.fd.saberAnimLevel ); + ent->client->ps.fd.saberAnimLevelBase = ent->client->saberCycleQueue = ent->client->ps.fd.saberAnimLevel; + } + } + } + l = 0; + + if (client->ps.fd.forceDoInit) + { //force a reread of force powers + WP_InitForcePowers( ent ); + client->ps.fd.forceDoInit = 0; + } + + if (ent->client->ps.fd.saberAnimLevel != SS_STAFF && + ent->client->ps.fd.saberAnimLevel != SS_DUAL && + ent->client->ps.fd.saberAnimLevel == ent->client->ps.fd.saberDrawAnimLevel && + ent->client->ps.fd.saberAnimLevel == ent->client->sess.saberLevel) + { + if (ent->client->sess.saberLevel < SS_FAST) + { + ent->client->sess.saberLevel = SS_FAST; + } + else if (ent->client->sess.saberLevel > SS_STRONG) + { + ent->client->sess.saberLevel = SS_STRONG; + } + ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel; + + if (g_gametype.integer != GT_SIEGE && + ent->client->ps.fd.saberAnimLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { + ent->client->ps.fd.saberAnimLevel = ent->client->ps.fd.saberDrawAnimLevel = ent->client->sess.saberLevel = ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]; + } + } + + // 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 || g_gametype.integer == GT_CTY) { + // all base oriented team games use the CTF spawn points + spawnPoint = SelectCTFSpawnPoint ( + client->sess.sessionTeam, + client->pers.teamState.state, + spawn_origin, spawn_angles); + } + else if (g_gametype.integer == GT_SIEGE) + { + spawnPoint = SelectSiegeSpawnPoint ( + client->siegeClass, + client->sess.sessionTeam, + client->pers.teamState.state, + spawn_origin, spawn_angles); + } + else { + do { + if (g_gametype.integer == GT_POWERDUEL) + { + spawnPoint = SelectDuelSpawnPoint(client->sess.duelTeam, client->ps.origin, spawn_origin, spawn_angles); + } + else if (g_gametype.integer == GT_DUEL) + { // duel + spawnPoint = SelectDuelSpawnPoint(DUELTEAM_SINGLE, client->ps.origin, spawn_origin, spawn_angles); + } + else + { + // the first spawn should be at a good looking spot + if ( !client->pers.initialSpawn && client->pers.localClient ) { + client->pers.initialSpawn = qtrue; + spawnPoint = SelectInitialSpawnPoint( spawn_origin, spawn_angles, client->sess.sessionTeam ); + } else { + // don't spawn near existing origin if possible + spawnPoint = SelectSpawnPoint ( + client->ps.origin, + spawn_origin, spawn_angles, client->sess.sessionTeam ); + } + } + + // 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; + + // 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 ); + flags ^= EF_TELEPORT_BIT; + gameFlags = ent->client->mGameFlags & ( PSG_VOTED | PSG_TEAMVOTED); + + // 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; + + savedForce = client->ps.fd; + + saveSaberNum = client->ps.saberEntityNum; + + savedSiegeIndex = client->siegeClass; + + l = 0; + while (l < MAX_SABERS) + { + saberSaved[l] = client->saber[l]; + g2WeaponPtrs[l] = client->weaponGhoul2[l]; + l++; + } + + i = 0; + while (i < HL_MAX) + { + ent->locationDamage[i] = 0; + i++; + } + + memset (client, 0, sizeof(*client)); // bk FIXME: Com_Memset? + client->bodyGrabIndex = ENTITYNUM_NONE; + + //Get the skin RGB based on his userinfo + value = Info_ValueForKey (userinfo, "char_color_red"); + if (value) + { + client->ps.customRGBA[0] = atoi(value); + } + else + { + client->ps.customRGBA[0] = 255; + } + + value = Info_ValueForKey (userinfo, "char_color_green"); + if (value) + { + client->ps.customRGBA[1] = atoi(value); + } + else + { + client->ps.customRGBA[1] = 255; + } + + value = Info_ValueForKey (userinfo, "char_color_blue"); + if (value) + { + client->ps.customRGBA[2] = atoi(value); + } + else + { + client->ps.customRGBA[2] = 255; + } + + if ((client->ps.customRGBA[0]+client->ps.customRGBA[1]+client->ps.customRGBA[2]) < 100) + { //hmm, too dark! + client->ps.customRGBA[0] = client->ps.customRGBA[1] = client->ps.customRGBA[2] = 255; + } + + client->ps.customRGBA[3]=255; + + client->siegeClass = savedSiegeIndex; + + l = 0; + while (l < MAX_SABERS) + { + client->saber[l] = saberSaved[l]; + client->weaponGhoul2[l] = g2WeaponPtrs[l]; + l++; + } + + //or the saber ent num + client->ps.saberEntityNum = saveSaberNum; + client->saberStoredIndex = saveSaberNum; + + client->ps.fd = savedForce; + + client->ps.duelIndex = ENTITYNUM_NONE; + + //spawn with 100 + client->ps.jetpackFuel = 100; + client->ps.cloakFuel = 100; + + 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; + + // set max health + if (g_gametype.integer == GT_SIEGE && client->siegeClass != -1) + { + siegeClass_t *scl = &bgSiegeClasses[client->siegeClass]; + maxHealth = 100; + + if (scl->maxhealth) + { + maxHealth = scl->maxhealth; + } + } + else + { + maxHealth = 100; + } + client->pers.maxHealth = maxHealth;//atoi( Info_ValueForKey( userinfo, "handicap" ) ); + if ( client->pers.maxHealth < 1 || client->pers.maxHealth > maxHealth ) { + client->pers.maxHealth = 100; + } + // clear entity values + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + client->ps.eFlags = flags; + client->mGameFlags = gameFlags; + + ent->s.groundEntityNum = ENTITYNUM_NONE; + ent->client = &level.clients[index]; + ent->playerState = &ent->client->ps; + 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; + + VectorCopy (playerMins, ent->r.mins); + VectorCopy (playerMaxs, ent->r.maxs); + client->ps.crouchheight = CROUCH_MAXS_2; + client->ps.standheight = DEFAULT_MAXS_2; + + client->ps.clientNum = index; + //give default weapons + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_NONE ); + + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { + wDisable = g_duelWeaponDisable.integer; + } + else + { + wDisable = g_weaponDisable.integer; + } + + + + if ( g_gametype.integer != GT_HOLOCRON + && g_gametype.integer != GT_JEDIMASTER + && !HasSetSaberOnly() + && !AllForceDisabled( g_forcePowerDisable.integer ) + && g_trueJedi.integer ) + { + if ( g_gametype.integer >= GT_TEAM && (client->sess.sessionTeam == TEAM_BLUE || client->sess.sessionTeam == TEAM_RED) ) + {//In Team games, force one side to be merc and other to be jedi + if ( level.numPlayingClients > 0 ) + {//already someone in the game + int i, forceTeam = TEAM_SPECTATOR; + for ( i = 0 ; i < level.maxclients ; i++ ) + { + if ( level.clients[i].pers.connected == CON_DISCONNECTED ) { + continue; + } + if ( level.clients[i].sess.sessionTeam == TEAM_BLUE || level.clients[i].sess.sessionTeam == TEAM_RED ) + {//in-game + if ( WP_HasForcePowers( &level.clients[i].ps ) ) + {//this side is using force + forceTeam = level.clients[i].sess.sessionTeam; + } + else + {//other team is using force + if ( level.clients[i].sess.sessionTeam == TEAM_BLUE ) + { + forceTeam = TEAM_RED; + } + else + { + forceTeam = TEAM_BLUE; + } + } + break; + } + } + if ( WP_HasForcePowers( &client->ps ) && client->sess.sessionTeam != forceTeam ) + {//using force but not on right team, switch him over + const char *teamName = TeamName( forceTeam ); + //client->sess.sessionTeam = forceTeam; + SetTeam( ent, (char *)teamName ); + return; + } + } + } + + if ( WP_HasForcePowers( &client->ps ) ) + { + client->ps.trueNonJedi = qfalse; + client->ps.trueJedi = qtrue; + //make sure they only use the saber + client->ps.weapon = WP_SABER; + client->ps.stats[STAT_WEAPONS] = (1 << WP_SABER); + } + else + {//no force powers set + client->ps.trueNonJedi = qtrue; + client->ps.trueJedi = qfalse; + if (!wDisable || !(wDisable & (1 << WP_BRYAR_PISTOL))) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL ); + } + if (!wDisable || !(wDisable & (1 << WP_BLASTER))) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BLASTER ); + } + if (!wDisable || !(wDisable & (1 << WP_BOWCASTER))) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BOWCASTER ); + } + client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER); + client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE); + client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max; + client->ps.weapon = WP_BRYAR_PISTOL; + } + } + else + {//jediVmerc is incompatible with this gametype, turn it off! + trap_Cvar_Set( "g_jediVmerc", "0" ); + if (g_gametype.integer == GT_HOLOCRON) + { + //always get free saber level 1 in holocron + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //these are precached in g_items, ClearRegisteredItems() + } + else + { + if (client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE]) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_SABER ); //these are precached in g_items, ClearRegisteredItems() + } + else + { //if you don't have saber attack rank then you don't get a saber + client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE); + } + } + + if (g_gametype.integer != GT_SIEGE) + { + if (!wDisable || !(wDisable & (1 << WP_BRYAR_PISTOL))) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL ); + } + else if (g_gametype.integer == GT_JEDIMASTER) + { + client->ps.stats[STAT_WEAPONS] |= ( 1 << WP_BRYAR_PISTOL ); + } + } + + if (g_gametype.integer == GT_JEDIMASTER) + { + client->ps.stats[STAT_WEAPONS] &= ~(1 << WP_SABER); + client->ps.stats[STAT_WEAPONS] |= (1 << WP_MELEE); + } + + if (client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + client->ps.weapon = WP_SABER; + } + else if (client->ps.stats[STAT_WEAPONS] & (1 << WP_BRYAR_PISTOL)) + { + client->ps.weapon = WP_BRYAR_PISTOL; + } + else + { + client->ps.weapon = WP_MELEE; + } + } + + /* + client->ps.stats[STAT_HOLDABLE_ITEMS] |= ( 1 << HI_BINOCULARS ); + client->ps.stats[STAT_HOLDABLE_ITEM] = BG_GetItemIndexByTag(HI_BINOCULARS, IT_HOLDABLE); + */ + + if (g_gametype.integer == GT_SIEGE && client->siegeClass != -1 && + client->sess.sessionTeam != TEAM_SPECTATOR) + { //well then, we will use a custom weaponset for our class + int m = 0; + + client->ps.stats[STAT_WEAPONS] = bgSiegeClasses[client->siegeClass].weapons; + + if (client->ps.stats[STAT_WEAPONS] & (1 << WP_SABER)) + { + client->ps.weapon = WP_SABER; + } + else if (client->ps.stats[STAT_WEAPONS] & (1 << WP_BRYAR_PISTOL)) + { + client->ps.weapon = WP_BRYAR_PISTOL; + } + else + { + client->ps.weapon = WP_MELEE; + } + inSiegeWithClass = qtrue; + + while (m < WP_NUM_WEAPONS) + { + if (client->ps.stats[STAT_WEAPONS] & (1 << m)) + { + if (client->ps.weapon != WP_SABER) + { //try to find the highest ranking weapon we have + if (m > client->ps.weapon) + { + client->ps.weapon = m; + } + } + + if (m >= WP_BRYAR_PISTOL) + { //Max his ammo out for all the weapons he has. + if ( g_gametype.integer == GT_SIEGE + && m == WP_ROCKET_LAUNCHER ) + {//don't give full ammo! + //FIXME: extern this and check it when getting ammo from supplier, pickups or ammo stations! + if ( client->siegeClass != -1 && + (bgSiegeClasses[client->siegeClass].classflags & (1<ps.ammo[weaponData[m].ammoIndex] = 1; + } + else + { + client->ps.ammo[weaponData[m].ammoIndex] = 10; + } + } + else + { + if ( g_gametype.integer == GT_SIEGE + && client->siegeClass != -1 + && (bgSiegeClasses[client->siegeClass].classflags & (1<ps.ammo[weaponData[m].ammoIndex] = ammoData[weaponData[m].ammoIndex].max*2; + client->ps.eFlags |= EF_DOUBLE_AMMO; + } + else + { + client->ps.ammo[weaponData[m].ammoIndex] = ammoData[weaponData[m].ammoIndex].max; + } + } + } + } + m++; + } + } + + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 && + client->sess.sessionTeam != TEAM_SPECTATOR) + { //use class-specified inventory + client->ps.stats[STAT_HOLDABLE_ITEMS] = bgSiegeClasses[client->siegeClass].invenItems; + client->ps.stats[STAT_HOLDABLE_ITEM] = 0; + } + else + { + client->ps.stats[STAT_HOLDABLE_ITEMS] = 0; + client->ps.stats[STAT_HOLDABLE_ITEM] = 0; + } + + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 && + bgSiegeClasses[client->siegeClass].powerups && + client->sess.sessionTeam != TEAM_SPECTATOR) + { //this class has some start powerups + i = 0; + while (i < PW_NUM_POWERUPS) + { + if (bgSiegeClasses[client->siegeClass].powerups & (1 << i)) + { + client->ps.powerups[i] = Q3_INFINITE; + } + i++; + } + } + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) + { + client->ps.stats[STAT_WEAPONS] = 0; + client->ps.stats[STAT_HOLDABLE_ITEMS] = 0; + client->ps.stats[STAT_HOLDABLE_ITEM] = 0; + } + +// nmckenzie: DESERT_SIEGE... or well, siege generally. This was over-writing the max value, which was NOT good for siege. + if ( inSiegeWithClass == qfalse ) + { + client->ps.ammo[AMMO_BLASTER] = 100; //ammoData[AMMO_BLASTER].max; //100 seems fair. + } +// client->ps.ammo[AMMO_POWERCELL] = ammoData[AMMO_POWERCELL].max; +// client->ps.ammo[AMMO_FORCE] = ammoData[AMMO_FORCE].max; +// client->ps.ammo[AMMO_METAL_BOLTS] = ammoData[AMMO_METAL_BOLTS].max; +// client->ps.ammo[AMMO_ROCKETS] = ammoData[AMMO_ROCKETS].max; +/* + client->ps.stats[STAT_WEAPONS] = ( 1 << WP_BRYAR_PISTOL); + if ( g_gametype.integer == GT_TEAM ) { + client->ps.ammo[WP_BRYAR_PISTOL] = 50; + } else { + client->ps.ammo[WP_BRYAR_PISTOL] = 100; + } +*/ + client->ps.rocketLockIndex = ENTITYNUM_NONE; + client->ps.rocketLockTime = 0; + + //rww - Set here to initialize the circling seeker drone to off. + //A quick note about this so I don't forget how it works again: + //ps.genericEnemyIndex is kept in sync between the server and client. + //When it gets set then an entitystate value of the same name gets + //set along with an entitystate flag in the shared bg code. Which + //is why a value needs to be both on the player state and entity state. + //(it doesn't seem to just carry over the entitystate value automatically + //because entity state value is derived from player state data or some + //such) + client->ps.genericEnemyIndex = -1; + + client->ps.isJediMaster = qfalse; + + if (client->ps.fallingToDeath) + { + client->ps.fallingToDeath = 0; + client->noCorpse = qtrue; + } + + //Do per-spawn force power initialization + WP_SpawnInitForcePowers( ent ); + + // health will count down towards max_health + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 && + bgSiegeClasses[client->siegeClass].starthealth) + { //class specifies a start health, so use it + ent->health = client->ps.stats[STAT_HEALTH] = bgSiegeClasses[client->siegeClass].starthealth; + } + else if ( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL ) + {//only start with 100 health in Duel + if ( g_gametype.integer == GT_POWERDUEL && client->sess.duelTeam == DUELTEAM_LONE ) + { + if ( g_duel_fraglimit.integer ) + { + + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = + g_powerDuelStartHealth.integer - ((g_powerDuelStartHealth.integer - g_powerDuelEndHealth.integer) * (float)client->sess.wins / (float)g_duel_fraglimit.integer); + } + else + { + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = 150; + } + } + else + { + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] = 100; + } + } + else if (client->ps.stats[STAT_MAX_HEALTH] <= 100) + { + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] * 1.25; + } + else if (client->ps.stats[STAT_MAX_HEALTH] < 125) + { + ent->health = client->ps.stats[STAT_HEALTH] = 125; + } + else + { + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; + } + + // Start with a small amount of armor as well. + if (g_gametype.integer == GT_SIEGE && + client->siegeClass != -1 /*&& + bgSiegeClasses[client->siegeClass].startarmor*/) + { //class specifies a start armor amount, so use it + client->ps.stats[STAT_ARMOR] = bgSiegeClasses[client->siegeClass].startarmor; + } + else if ( g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL ) + {//no armor in duel + client->ps.stats[STAT_ARMOR] = 0; + } + else + { + client->ps.stats[STAT_ARMOR] = client->ps.stats[STAT_MAX_HEALTH] * 0.25; + } + + 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_BRYAR_PISTOL; + //client->ps.weaponstate = FIRST_WEAPON; + if (client->ps.weapon <= WP_NONE) + { + client->ps.weapon = WP_BRYAR_PISTOL; + } + + client->ps.torsoTimer = client->ps.legsTimer = 0; + + if (client->ps.weapon == WP_SABER) + { + G_SetAnim(ent, NULL, SETANIM_BOTH, BOTH_STAND1TO2, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS, 0); + } + else + { + G_SetAnim(ent, NULL, SETANIM_TORSO, TORSO_RAISEWEAP1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD|SETANIM_FLAG_HOLDLESS, 0); + client->ps.legsAnim = WeaponReadyAnim[client->ps.weapon]; + } + client->ps.weaponstate = WEAPON_RAISING; + client->ps.weaponTime = client->ps.torsoTimer; + } + + // 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; + + 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; + for ( i = WP_NUM_WEAPONS - 1 ; i > 0 ; i-- ) { + if ( client->ps.stats[STAT_WEAPONS] & ( 1 << i ) ) { + client->ps.weapon = i; + break; + } + } + */ + } + + //set teams for NPCs to recognize + if (g_gametype.integer == GT_SIEGE) + { //Imperial (team1) team is allied with "enemy" NPCs in this mode + if (client->sess.sessionTeam == SIEGETEAM_TEAM1) + { + client->playerTeam = ent->s.teamowner = NPCTEAM_ENEMY; + client->enemyTeam = NPCTEAM_PLAYER; + } + else + { + client->playerTeam = ent->s.teamowner = NPCTEAM_PLAYER; + client->enemyTeam = NPCTEAM_ENEMY; + } + } + else + { + client->playerTeam = ent->s.teamowner = NPCTEAM_PLAYER; + client->enemyTeam = NPCTEAM_ENEMY; + } + + /* + //scaling for the power duel opponent + if (g_gametype.integer == GT_POWERDUEL && + client->sess.duelTeam == DUELTEAM_LONE) + { + client->ps.iModelScale = 125; + VectorSet(ent->modelScale, 1.25f, 1.25f, 1.25f); + } + */ + //Disabled. At least for now. Not sure if I'll want to do it or not eventually. + + // 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, NULL ); + + // 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 ); + } + + if (g_spawnInvulnerability.integer) + { + ent->client->ps.eFlags |= EF_INVULNERABLE; + ent->client->invulnerableTimer = level.time + g_spawnInvulnerability.integer; + } + + // run the presend to set anything else + ClientEndFrame( ent ); + + // clear entity state values + BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); + + //rww - make sure client has a valid icarus instance + trap_ICARUS_FreeEnt( ent ); + trap_ICARUS_InitEnt( ent ); +} + + +/* +=========== +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; + } + + i = 0; + + while (i < NUM_FORCE_POWERS) + { + if (ent->client->ps.fd.forcePowersActive & (1 << i)) + { + WP_ForcePowerStop(ent, i); + } + i++; + } + + i = TRACK_CHANNEL_1; + + while (i < NUM_TRACK_CHANNELS) + { + if (ent->client->ps.fd.killSoundEntIndex[i-50] && ent->client->ps.fd.killSoundEntIndex[i-50] < MAX_GENTITIES && ent->client->ps.fd.killSoundEntIndex[i-50] > 0) + { + G_MuteSound(ent->client->ps.fd.killSoundEntIndex[i-50], CHAN_VOICE); + } + i++; + } + i = 0; + + if (ent->client->ps.m_iVehicleNum) + { //tell it I'm getting off + gentity_t *veh = &g_entities[ent->client->ps.m_iVehicleNum]; + + if (veh->inuse && veh->client && veh->m_pVehicle) + { + int pCon = ent->client->pers.connected; + + ent->client->pers.connected = 0; + veh->m_pVehicle->m_pVehicleInfo->Eject(veh->m_pVehicle, (bgEntity_t *)ent, qtrue); + ent->client->pers.connected = pCon; + } + } + + // 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 ); + } + + G_LogPrintf( "ClientDisconnect: %i\n", clientNum ); + + // if we are playing in tourney mode, give a win to the other player and clear his frags for this round + if ( (g_gametype.integer == GT_DUEL ) + && !level.intermissiontime + && !level.warmupTime ) { + if ( level.sortedClients[1] == clientNum ) { + level.clients[ level.sortedClients[0] ].ps.persistant[PERS_SCORE] = 0; + level.clients[ level.sortedClients[0] ].sess.wins++; + ClientUserinfoChanged( level.sortedClients[0] ); + } + else if ( level.sortedClients[0] == clientNum ) { + level.clients[ level.sortedClients[1] ].ps.persistant[PERS_SCORE] = 0; + level.clients[ level.sortedClients[1] ].sess.wins++; + ClientUserinfoChanged( level.sortedClients[1] ); + } + } + + if (ent->ghoul2 && trap_G2_HaveWeGhoul2Models(ent->ghoul2)) + { + trap_G2API_CleanGhoul2Models(&ent->ghoul2); + } + i = 0; + while (i < MAX_SABERS) + { + if (ent->client->weaponGhoul2[i] && trap_G2_HaveWeGhoul2Models(ent->client->weaponGhoul2[i])) + { + trap_G2API_CleanGhoul2Models(&ent->client->weaponGhoul2[i]); + } + i++; + } + + 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; + ent->r.contents = 0; + + trap_SetConfigstring( CS_PLAYERS + clientNum, ""); + + CalculateRanks(); + + if ( ent->r.svFlags & SVF_BOT ) { + BotAIShutdownClient( clientNum, qfalse ); + } + + G_ClearClientLog(clientNum); +} + + diff --git a/code/game/g_cmds.c b/code/game/g_cmds.c new file mode 100644 index 0000000..74a86df --- /dev/null +++ b/code/game/g_cmds.c @@ -0,0 +1,3953 @@ +// Copyright (C) 1999-2000 Id Software, Inc. +// +#include "g_local.h" +#include "bg_saga.h" + +#include "../../ui/menudef.h" // for the voice chats + +//rww - for getting bot commands... +int AcceptBotCommand(char *cmd, gentity_t *pl); +//end rww + +#include "../namespace_begin.h" +void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ); +#include "../namespace_end.h" + +void Cmd_NPC_f( gentity_t *ent ); +void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin); + +/* +================== +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; + + if (numSorted > MAX_CLIENT_SCORE_SEND) + { + numSorted = MAX_CLIENT_SCORE_SEND; + } + + 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; + + Com_sprintf (entry, sizeof(entry), + " %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]); + j = strlen(entry); + if (stringlength + j > 1022) + break; + strcpy (string + stringlength, entry); + stringlength += j; + } + + //still want to know the total # of clients + i = level.numConnectedClients; + + 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 \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCHEATS"))); + return qfalse; + } + if ( ent->health <= 0 ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBEALIVE"))); + 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( (unsigned char) *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 *cmdent, int baseArg) +{ + char name[MAX_TOKEN_CHARS]; + gentity_t *ent; + gitem_t *it; + int i; + qboolean give_all; + gentity_t *it_ent; + trace_t trace; + char arg[MAX_TOKEN_CHARS]; + + if ( !CheatsOk( cmdent ) ) { + return; + } + + if (baseArg) + { + char otherindex[MAX_TOKEN_CHARS]; + + trap_Argv( 1, otherindex, sizeof( otherindex ) ); + + if (!otherindex[0]) + { + Com_Printf("giveother requires that the second argument be a client index number.\n"); + return; + } + + i = atoi(otherindex); + + if (i < 0 || i >= MAX_CLIENTS) + { + Com_Printf("%i is not a client index\n", i); + return; + } + + ent = &g_entities[i]; + + if (!ent->inuse || !ent->client) + { + Com_Printf("%i is not an active client\n", i); + return; + } + } + else + { + ent = cmdent; + } + + trap_Argv( 1+baseArg, name, sizeof( name ) ); + + if (Q_stricmp(name, "all") == 0) + give_all = qtrue; + else + give_all = qfalse; + + if (give_all) + { + i = 0; + while (i < HI_NUM_HOLDABLE) + { + ent->client->ps.stats[STAT_HOLDABLE_ITEMS] |= (1 << i); + i++; + } + i = 0; + } + + if (give_all || Q_stricmp( name, "health") == 0) + { + if (trap_Argc() == 3+baseArg) { + trap_Argv( 2+baseArg, arg, sizeof( arg ) ); + ent->health = atoi(arg); + if (ent->health > ent->client->ps.stats[STAT_MAX_HEALTH]) { + ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + } + else { + 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 << (LAST_USEABLE_WEAPON+1)) - ( 1 << WP_NONE ); + if (!give_all) + return; + } + + if ( !give_all && Q_stricmp(name, "weaponnum") == 0 ) + { + trap_Argv( 2+baseArg, arg, sizeof( arg ) ); + ent->client->ps.stats[STAT_WEAPONS] |= (1 << atoi(arg)); + return; + } + + if (give_all || Q_stricmp(name, "ammo") == 0) + { + int num = 999; + if (trap_Argc() == 3+baseArg) { + trap_Argv( 2+baseArg, arg, sizeof( arg ) ); + num = atoi(arg); + } + for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { + ent->client->ps.ammo[i] = num; + } + if (!give_all) + return; + } + + if (give_all || Q_stricmp(name, "armor") == 0) + { + if (trap_Argc() == 3+baseArg) { + trap_Argv( 2+baseArg, arg, sizeof( arg ) ); + ent->client->ps.stats[STAT_ARMOR] = atoi(arg); + } else { + ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH]; + } + + 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_TeamTask_f + +From TA. +================== +*/ +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; + } + + if ((g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) && + level.numPlayingClients > 1 && !level.warmupTime) + { + if (!g_allowDuelSuicide.integer) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "ATTEMPTDUELKILL")) ); + return; + } + } + + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; + player_die (ent, ent, ent, 100000, MOD_SUICIDE); +} + +gentity_t *G_GetDuelWinner(gclient_t *client) +{ + gclient_t *wCl; + int i; + + for ( i = 0 ; i < level.maxclients ; i++ ) { + wCl = &level.clients[i]; + + if (wCl && wCl != client && /*wCl->ps.clientNum != client->ps.clientNum &&*/ + wCl->pers.connected == CON_CONNECTED && wCl->sess.sessionTeam != TEAM_SPECTATOR) + { + return &g_entities[wCl->ps.clientNum]; + } + } + + return NULL; +} + +/* +================= +BroadCastTeamChange + +Let everyone know about a team change +================= +*/ +void BroadcastTeamChange( gclient_t *client, int oldTeam ) +{ + client->ps.fd.forceDoInit = 1; //every time we change teams make sure our force powers are set right + + if (g_gametype.integer == GT_SIEGE) + { //don't announce these things in siege + return; + } + + if ( client->sess.sessionTeam == TEAM_RED ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEREDTEAM")) ); + } else if ( client->sess.sessionTeam == TEAM_BLUE ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBLUETEAM"))); + } else if ( client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHESPECTATORS"))); + } else if ( client->sess.sessionTeam == TEAM_FREE ) { + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { + /* + gentity_t *currentWinner = G_GetDuelWinner(client); + + if (currentWinner && currentWinner->client) + { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s %s\n\"", + currentWinner->client->pers.netname, G_GetStringEdString("MP_SVGAME", "VERSUS"), client->pers.netname)); + } + else + { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBATTLE"))); + } + */ + //NOTE: Just doing a vs. once it counts two players up + } + else + { + trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", + client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBATTLE"))); + } + } + + G_LogPrintf ( "setteam: %i %s %s\n", + client - &level.clients[0], + TeamName ( oldTeam ), + TeamName ( client->sess.sessionTeam ) ); +} + +qboolean G_PowerDuelCheckFail(gentity_t *ent) +{ + int loners = 0; + int doubles = 0; + + if (!ent->client || ent->client->sess.duelTeam == DUELTEAM_FREE) + { + return qtrue; + } + + G_PowerDuelCount(&loners, &doubles, qfalse); + + if (ent->client->sess.duelTeam == DUELTEAM_LONE && loners >= 1) + { + return qtrue; + } + + if (ent->client->sess.duelTeam == DUELTEAM_DOUBLE && doubles >= 2) + { + return qtrue; + } + + return qfalse; +} + +/* +================= +SetTeam +================= +*/ +qboolean g_dontPenalizeTeam = qfalse; +qboolean g_preventTeamBegin = qfalse; +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 + //For now, don't do this. The legalize function will set powers properly now. + /* + if (g_forceBasedTeams.integer) + { + if (ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE) + { + team = TEAM_BLUE; + } + else + { + team = TEAM_RED; + } + } + else + { + */ + team = PickTeam( clientNum ); + //} + } + + if ( g_teamForceBalance.integer && !g_trueJedi.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 ) { + //For now, don't do this. The legalize function will set powers properly now. + /* + if (g_forceBasedTeams.integer && ent->client->ps.fd.forceSide == FORCE_DARKSIDE) + { + trap_SendServerCommand( ent->client->ps.clientNum, + va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYRED_SWITCH")) ); + } + else + */ + { + trap_SendServerCommand( ent->client->ps.clientNum, + va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYRED")) ); + } + return; // ignore the request + } + if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) { + //For now, don't do this. The legalize function will set powers properly now. + /* + if (g_forceBasedTeams.integer && ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE) + { + trap_SendServerCommand( ent->client->ps.clientNum, + va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYBLUE_SWITCH")) ); + } + else + */ + { + trap_SendServerCommand( ent->client->ps.clientNum, + va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYBLUE")) ); + } + return; // ignore the request + } + + // It's ok, the team we are switching to has less or same number of players + } + + //For now, don't do this. The legalize function will set powers properly now. + /* + if (g_forceBasedTeams.integer) + { + if (team == TEAM_BLUE && ent->client->ps.fd.forceSide != FORCE_LIGHTSIDE) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBELIGHT")) ); + return; + } + if (team == TEAM_RED && ent->client->ps.fd.forceSide != FORCE_DARKSIDE) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBEDARK")) ); + return; + } + } + */ + + } else { + // force them to spectators if there aren't any spots free + team = TEAM_FREE; + } + + if (g_gametype.integer == GT_SIEGE) + { + if (client->tempSpectate >= level.time && + team == TEAM_SPECTATOR) + { //sorry, can't do that. + return; + } + + client->sess.siegeDesiredTeam = team; + //oh well, just let them go. + /* + if (team != TEAM_SPECTATOR) + { //can't switch to anything in siege unless you want to switch to being a fulltime spectator + //fill them in on their objectives for this team now + trap_SendServerCommand(ent-g_entities, va("sb %i", client->sess.siegeDesiredTeam)); + + trap_SendServerCommand( ent-g_entities, va("print \"You will be on the selected team the next time the round begins.\n\"") ); + return; + } + */ + if (client->sess.sessionTeam != TEAM_SPECTATOR && + team != TEAM_SPECTATOR) + { //not a spectator now, and not switching to spec, so you have to wait til you die. + //trap_SendServerCommand( ent-g_entities, va("print \"You will be on the selected team the next time you respawn.\n\"") ); + qboolean doBegin; + if (ent->client->tempSpectate >= level.time) + { + doBegin = qfalse; + } + else + { + doBegin = qtrue; + } + + if (doBegin) + { + // Kill them so they automatically respawn in the team they wanted. + if (ent->health > 0) + { + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + player_die( ent, ent, ent, 100000, MOD_TEAM_CHANGE ); + } + } + + if (ent->client->sess.sessionTeam != ent->client->sess.siegeDesiredTeam) + { + SetTeamQuick(ent, ent->client->sess.siegeDesiredTeam, qfalse); + } + + return; + } + } + + // override decision if limiting the players + if ( (g_gametype.integer == GT_DUEL) + && level.numNonSpectatorClients >= 2 ) + { + team = TEAM_SPECTATOR; + } + else if ( (g_gametype.integer == GT_POWERDUEL) + && (level.numPlayingClients >= 3 || G_PowerDuelCheckFail(ent)) ) + { + 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 it's siege then show the mission briefing for the team you just joined. +// if (g_gametype.integer == GT_SIEGE && team != TEAM_SPECTATOR) +// { +// trap_SendServerCommand(clientNum, va("sb %i", team)); +// } + + // if the player was dead leave the body + if ( client->ps.stats[STAT_HEALTH] <= 0 && client->sess.sessionTeam != TEAM_SPECTATOR ) { + MaintainBodyQueue(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; + g_dontPenalizeTeam = qtrue; + player_die (ent, ent, ent, 100000, MOD_SUICIDE); + g_dontPenalizeTeam = qfalse; + + } + // they go to the end of the line for tournements + if ( team == TEAM_SPECTATOR ) { + if ( (g_gametype.integer != GT_DUEL) || (oldTeam != TEAM_SPECTATOR) ) {//so you don't get dropped to the bottom of the queue for changing skins, etc. + 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 ); + + //make a disappearing effect where they were before teleporting them to the appropriate spawn point, + //if we were not on the spec team + if (oldTeam != TEAM_SPECTATOR) + { + gentity_t *tent = G_TempEntity( client->ps.origin, EV_PLAYER_TELEPORT_OUT ); + tent->s.clientNum = clientNum; + } + + // get and distribute relevent paramters + ClientUserinfoChanged( clientNum ); + + if (!g_preventTeamBegin) + { + ClientBegin( clientNum, qfalse ); + } +} + +/* +================= +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; + ent->client->ps.weapon = WP_NONE; + ent->client->ps.m_iVehicleNum = 0; + ent->client->ps.viewangles[ROLL] = 0.0f; + ent->client->ps.forceHandExtend = HANDEXTEND_NONE; + ent->client->ps.forceHandExtendTime = 0; + ent->client->ps.zoomMode = 0; + ent->client->ps.zoomLocked = 0; + ent->client->ps.zoomLockTime = 0; + ent->client->ps.legsAnim = 0; + ent->client->ps.legsTimer = 0; + ent->client->ps.torsoAnim = 0; + ent->client->ps.torsoTimer = 0; +} + +/* +================= +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, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTBLUETEAM")) ); + break; + case TEAM_RED: + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTREDTEAM")) ); + break; + case TEAM_FREE: + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTFREETEAM")) ); + break; + case TEAM_SPECTATOR: + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTSPECTEAM")) ); + break; + } + return; + } + + if ( ent->client->switchTeamTime > level.time ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) ); + return; + } + + if (gEscaping) + { + return; + } + + // if they are playing a tournement game, count as a loss + if ( g_gametype.integer == GT_DUEL + && ent->client->sess.sessionTeam == TEAM_FREE ) {//in a tournament game + //disallow changing teams + trap_SendServerCommand( ent-g_entities, "print \"Cannot switch teams in Duel\n\"" ); + return; + //FIXME: why should this be a loss??? + //ent->client->sess.losses++; + } + + if (g_gametype.integer == GT_POWERDUEL) + { //don't let clients change teams manually at all in powerduel, it will be taken care of through automated stuff + trap_SendServerCommand( ent-g_entities, "print \"Cannot switch teams in Power Duel\n\"" ); + return; + } + + trap_Argv( 1, s, sizeof( s ) ); + + SetTeam( ent, s ); + + ent->client->switchTeamTime = level.time + 5000; +} + +/* +================= +Cmd_DuelTeam_f +================= +*/ +void Cmd_DuelTeam_f(gentity_t *ent) +{ + int oldTeam; + char s[MAX_TOKEN_CHARS]; + + if (g_gametype.integer != GT_POWERDUEL) + { //don't bother doing anything if this is not power duel + return; + } + + /* + if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) + { + trap_SendServerCommand( ent-g_entities, va("print \"You cannot change your duel team unless you are a spectator.\n\"")); + return; + } + */ + + if ( trap_Argc() != 2 ) + { //No arg so tell what team we're currently on. + oldTeam = ent->client->sess.duelTeam; + switch ( oldTeam ) + { + case DUELTEAM_FREE: + trap_SendServerCommand( ent-g_entities, va("print \"None\n\"") ); + break; + case DUELTEAM_LONE: + trap_SendServerCommand( ent-g_entities, va("print \"Single\n\"") ); + break; + case DUELTEAM_DOUBLE: + trap_SendServerCommand( ent-g_entities, va("print \"Double\n\"") ); + break; + default: + break; + } + return; + } + + if ( ent->client->switchDuelTeamTime > level.time ) + { //debounce for changing + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) ); + return; + } + + trap_Argv( 1, s, sizeof( s ) ); + + oldTeam = ent->client->sess.duelTeam; + + if (!Q_stricmp(s, "free")) + { + ent->client->sess.duelTeam = DUELTEAM_FREE; + } + else if (!Q_stricmp(s, "single")) + { + ent->client->sess.duelTeam = DUELTEAM_LONE; + } + else if (!Q_stricmp(s, "double")) + { + ent->client->sess.duelTeam = DUELTEAM_DOUBLE; + } + else + { + trap_SendServerCommand( ent-g_entities, va("print \"'%s' not a valid duel team.\n\"", s) ); + } + + if (oldTeam == ent->client->sess.duelTeam) + { //didn't actually change, so don't care. + return; + } + + if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) + { //ok..die + int curTeam = ent->client->sess.duelTeam; + ent->client->sess.duelTeam = oldTeam; + G_Damage(ent, ent, ent, NULL, ent->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE); + ent->client->sess.duelTeam = curTeam; + } + //reset wins and losses + ent->client->sess.wins = 0; + ent->client->sess.losses = 0; + + //get and distribute relevent paramters + ClientUserinfoChanged( ent->s.number ); + + ent->client->switchDuelTeamTime = level.time + 5000; +} + +int G_TeamForSiegeClass(const char *clName) +{ + int i = 0; + int team = SIEGETEAM_TEAM1; + siegeTeam_t *stm = BG_SiegeFindThemeForTeam(team); + siegeClass_t *scl; + + if (!stm) + { + return 0; + } + + while (team <= SIEGETEAM_TEAM2) + { + scl = stm->classes[i]; + + if (scl && scl->name[0]) + { + if (!Q_stricmp(clName, scl->name)) + { + return team; + } + } + + i++; + if (i >= MAX_SIEGE_CLASSES || i >= stm->numClasses) + { + if (team == SIEGETEAM_TEAM2) + { + break; + } + team = SIEGETEAM_TEAM2; + stm = BG_SiegeFindThemeForTeam(team); + i = 0; + } + } + + return 0; +} + +/* +================= +Cmd_SiegeClass_f +================= +*/ +void Cmd_SiegeClass_f( gentity_t *ent ) +{ + char className[64]; + int team = 0; + int preScore; + qboolean startedAsSpec = qfalse; + + if (g_gametype.integer != GT_SIEGE) + { //classes are only valid for this gametype + return; + } + + if (!ent->client) + { + return; + } + + if (trap_Argc() < 1) + { + return; + } + + if ( ent->client->switchClassTime > level.time ) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCLASSSWITCH")) ); + return; + } + + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) + { + startedAsSpec = qtrue; + } + + trap_Argv( 1, className, sizeof( className ) ); + + team = G_TeamForSiegeClass(className); + + if (!team) + { //not a valid class name + return; + } + + if (ent->client->sess.sessionTeam != team) + { //try changing it then + g_preventTeamBegin = qtrue; + if (team == TEAM_RED) + { + SetTeam(ent, "red"); + } + else if (team == TEAM_BLUE) + { + SetTeam(ent, "blue"); + } + g_preventTeamBegin = qfalse; + + if (ent->client->sess.sessionTeam != team) + { //failed, oh well + if (ent->client->sess.sessionTeam != TEAM_SPECTATOR || + ent->client->sess.siegeDesiredTeam != team) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCLASSTEAM")) ); + return; + } + } + } + + //preserve 'is score + preScore = ent->client->ps.persistant[PERS_SCORE]; + + //Make sure the class is valid for the team + BG_SiegeCheckClassLegality(team, className); + + //Set the session data + strcpy(ent->client->sess.siegeClass, className); + + // get and distribute relevent paramters + ClientUserinfoChanged( ent->s.number ); + + if (ent->client->tempSpectate < level.time) + { + // Kill him (makes sure he loses flags, etc) + if (ent->health > 0 && !startedAsSpec) + { + ent->flags &= ~FL_GODMODE; + ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; + player_die (ent, ent, ent, 100000, MOD_SUICIDE); + } + + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR || startedAsSpec) + { //respawn them instantly. + ClientBegin( ent->s.number, qfalse ); + } + } + //set it back after we do all the stuff + ent->client->ps.persistant[PERS_SCORE] = preScore; + + ent->client->switchClassTime = level.time + 5000; +} + +/* +================= +Cmd_ForceChanged_f +================= +*/ +void Cmd_ForceChanged_f( gentity_t *ent ) +{ + char fpChStr[1024]; + const char *buf; +// Cmd_Kill_f(ent); + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) + { //if it's a spec, just make the changes now + //trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "FORCEAPPLIED")) ); + //No longer print it, as the UI calls this a lot. + WP_InitForcePowers( ent ); + goto argCheck; + } + + buf = G_GetStringEdString("MP_SVGAME", "FORCEPOWERCHANGED"); + + strcpy(fpChStr, buf); + + trap_SendServerCommand( ent-g_entities, va("print \"%s%s\n\n\"", S_COLOR_GREEN, fpChStr) ); + + ent->client->ps.fd.forceDoInit = 1; +argCheck: + if (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + { //If this is duel, don't even bother changing team in relation to this. + return; + } + + if (trap_Argc() > 1) + { + char arg[MAX_TOKEN_CHARS]; + + trap_Argv( 1, arg, sizeof( arg ) ); + + if (arg && arg[0]) + { //if there's an arg, assume it's a combo team command from the UI. + Cmd_Team_f(ent); + } + } +} + +extern qboolean WP_SaberStyleValidForSaber( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int saberAnimLevel ); +extern qboolean WP_UseFirstValidSaberStyle( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int *saberAnimLevel ); +qboolean G_SetSaber(gentity_t *ent, int saberNum, char *saberName, qboolean siegeOverride) +{ + char truncSaberName[64]; + int i = 0; + + if (!siegeOverride && + g_gametype.integer == GT_SIEGE && + ent->client->siegeClass != -1 && + ( + bgSiegeClasses[ent->client->siegeClass].saberStance || + bgSiegeClasses[ent->client->siegeClass].saber1[0] || + bgSiegeClasses[ent->client->siegeClass].saber2[0] + )) + { //don't let it be changed if the siege class has forced any saber-related things + return qfalse; + } + + while (saberName[i] && i < 64-1) + { + truncSaberName[i] = saberName[i]; + i++; + } + truncSaberName[i] = 0; + + if ( saberNum == 0 && (Q_stricmp( "none", truncSaberName ) == 0 || Q_stricmp( "remove", truncSaberName ) == 0) ) + { //can't remove saber 0 like this + strcpy(truncSaberName, "Kyle"); + } + + //Set the saber with the arg given. If the arg is + //not a valid sabername defaults will be used. + WP_SetSaber(ent->s.number, ent->client->saber, saberNum, truncSaberName); + + if (!ent->client->saber[0].model[0]) + { + assert(0); //should never happen! + strcpy(ent->client->sess.saberType, "none"); + } + else + { + strcpy(ent->client->sess.saberType, ent->client->saber[0].name); + } + + if (!ent->client->saber[1].model[0]) + { + strcpy(ent->client->sess.saber2Type, "none"); + } + else + { + strcpy(ent->client->sess.saber2Type, ent->client->saber[1].name); + } + + if ( !WP_SaberStyleValidForSaber( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, ent->client->ps.fd.saberAnimLevel ) ) + { + WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &ent->client->ps.fd.saberAnimLevel ); + ent->client->ps.fd.saberAnimLevelBase = ent->client->saberCycleQueue = ent->client->ps.fd.saberAnimLevel; + } + + return qtrue; +} + +/* +================= +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; + } + + // if they are playing a tournement game, count as a loss + if ( (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + && ent->client->sess.sessionTeam == TEAM_FREE ) { + //WTF??? + 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; + + // if they are playing a tournement game, count as a loss + if ( (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + && ent->client->sess.sessionTeam == TEAM_FREE ) {\ + //WTF??? + 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, char *locMsg ) +{ + if (!other) { + return; + } + if (!other->inuse) { + return; + } + if (!other->client) { + return; + } + if ( other->client->pers.connected != CON_CONNECTED ) { + return; + } + if ( mode == SAY_TEAM && !OnSameTeam(ent, other) ) { + return; + } + /* + // no chatting to players in tournements + if ( (g_gametype.integer == GT_DUEL || g_gametype.integer == GT_POWERDUEL) + && other->client->sess.sessionTeam == TEAM_FREE + && ent->client->sess.sessionTeam != TEAM_FREE ) { + //Hmm, maybe some option to do so if allowed? Or at least in developer mode... + return; + } + */ + //They've requested I take this out. + + if (g_gametype.integer == GT_SIEGE && + ent->client && (ent->client->tempSpectate >= level.time || ent->client->sess.sessionTeam == TEAM_SPECTATOR) && + other->client->sess.sessionTeam != TEAM_SPECTATOR && + other->client->tempSpectate < level.time) + { //siege temp spectators should not communicate to ingame players + return; + } + + if (locMsg) + { + trap_SendServerCommand( other-g_entities, va("%s \"%s\" \"%s\" \"%c\" \"%s\"", + mode == SAY_TEAM ? "ltchat" : "lchat", + name, locMsg, color, message)); + } + else + { + trap_SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\"", + mode == SAY_TEAM ? "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]; + char *locMsg = NULL; + + 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_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")"EC": ", + ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + locMsg = 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"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); + locMsg = 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, locMsg ); + 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, locMsg ); + } +} + + +/* +================== +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 +================== +*/ +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 ); + } +} + +//siege voice command +static void Cmd_VoiceCommand_f(gentity_t *ent) +{ + gentity_t *te; + char arg[MAX_TOKEN_CHARS]; + char *s; + int i = 0; + + if (g_gametype.integer < GT_TEAM) + { + return; + } + + if (trap_Argc() < 2) + { + return; + } + + if (ent->client->sess.sessionTeam == TEAM_SPECTATOR || + ent->client->tempSpectate >= level.time) + { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOICECHATASSPEC")) ); + return; + } + + trap_Argv(1, arg, sizeof(arg)); + + if (arg[0] == '*') + { //hmm.. don't expect a * to be prepended already. maybe someone is trying to be sneaky. + return; + } + + s = va("*%s", arg); + + //now, make sure it's a valid sound to be playing like this.. so people can't go around + //screaming out death sounds or whatever. + while (i < MAX_CUSTOM_SIEGE_SOUNDS) + { + if (!bg_customSiegeSoundNames[i]) + { + break; + } + if (!Q_stricmp(bg_customSiegeSoundNames[i], s)) + { //it matches this one, so it's ok + break; + } + i++; + } + + if (i == MAX_CUSTOM_SIEGE_SOUNDS || !bg_customSiegeSoundNames[i]) + { //didn't find it in the list + return; + } + + te = G_TempEntity(vec3_origin, EV_VOICECMD_SOUND); + te->s.groundEntityNum = ent->s.number; + te->s.eventParm = G_SoundIndex((char *)bg_customSiegeSoundNames[i]); + te->r.svFlags |= SVF_BROADCAST; +} + + +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", + "Holocron FFA", + "Jedi Master", + "Duel", + "Power Duel", + "Single Player", + "Team FFA", + "Siege", + "Capture the Flag", + "Capture the Ysalamiri" +}; + +/* +================== +G_ClientNumberFromName + +Finds the client number of the client with the given name +================== +*/ +int G_ClientNumberFromName ( const char* name ) +{ + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + int i; + gclient_t* cl; + + // check for a name match + SanitizeString( (char*)name, s2 ); + for ( i=0, cl=level.clients ; i < level.numConnectedClients ; i++, cl++ ) + { + SanitizeString( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) + { + return i; + } + } + + return -1; +} + +/* +================== +SanitizeString2 + +Rich's revised version of SanitizeString +================== +*/ +void SanitizeString2( char *in, char *out ) +{ + int i = 0; + int r = 0; + + while (in[i]) + { + if (i >= MAX_NAME_LENGTH-1) + { //the ui truncates the name here.. + break; + } + + if (in[i] == '^') + { + if (in[i+1] >= 48 && //'0' + in[i+1] <= 57) //'9' + { //only skip it if there's a number after it for the color + i += 2; + continue; + } + else + { //just skip the ^ + i++; + continue; + } + } + + if (in[i] < 32) + { + i++; + continue; + } + + out[r] = in[i]; + r++; + i++; + } + out[r] = 0; +} + +/* +================== +G_ClientNumberFromStrippedName + +Same as above, but strips special characters out of the names before comparing. +================== +*/ +int G_ClientNumberFromStrippedName ( const char* name ) +{ + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + int i; + gclient_t* cl; + + // check for a name match + SanitizeString2( (char*)name, s2 ); + for ( i=0, cl=level.clients ; i < level.numConnectedClients ; i++, cl++ ) + { + SanitizeString2( cl->pers.netname, n2 ); + if ( !strcmp( n2, s2 ) ) + { + return i; + } + } + + return -1; +} + +/* +================== +Cmd_CallVote_f +================== +*/ +extern void SiegeClearSwitchData(void); //g_saga.c +const char *G_GetArenaInfoByMap( const char *map ); +void Cmd_CallVote_f( gentity_t *ent ) { + int i; + char arg1[MAX_STRING_TOKENS]; + char arg2[MAX_STRING_TOKENS]; +// int n = 0; +// char* type = NULL; + char* mapName = 0; + const char* arenaInfo; + + if ( !g_allowVote.integer ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTE")) ); + return; + } + + if ( level.voteTime || level.voteExecuteTime >= level.time ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "VOTEINPROGRESS")) ); + return; + } + if ( ent->client->pers.voteCount >= MAX_VOTE_COUNT ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MAXVOTES")) ); + return; + } + + if (g_gametype.integer != GT_DUEL && + g_gametype.integer != GT_POWERDUEL) + { + if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { + trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSPECVOTE")) ); + 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